Compare commits
141 Commits
feature-pe
...
main
Author | SHA1 | Date | |
---|---|---|---|
|
0b1b2f1d90 | ||
|
89f903f856 | ||
|
8bd54b5f5f | ||
|
5911cc6efa | ||
|
f3491836ce | ||
|
dcabdb446c | ||
|
bce861bb44 | ||
|
716c37225c | ||
|
07e02fa07e | ||
|
9d76959374 | ||
|
7b676b4fa2 | ||
|
bd7e8df0e3 | ||
|
5418848687 | ||
|
4719dbae4d | ||
|
c265be4f4b | ||
|
0f7916cac5 | ||
|
5062817d50 | ||
|
ea2928ccf8 | ||
|
5203c86a48 | ||
|
c92ec13242 | ||
|
dc876e6589 | ||
|
1a94400af2 | ||
|
b647d72c85 | ||
|
41f3078f97 | ||
|
28bf6f124f | ||
|
49aedc0554 | ||
|
fefa8fa7ce | ||
|
cf58f6196e | ||
|
4274ec9fee | ||
|
7f7a497ba0 | ||
|
ff518bd3b9 | ||
|
66ffd16948 | ||
|
5d9af13637 | ||
|
6556039639 | ||
|
0cb5164753 | ||
|
a706ba1432 | ||
|
656c15bdc8 | ||
|
915e0a2aea | ||
|
5df3e36baa | ||
|
648b992f31 | ||
|
36a297b968 | ||
|
0ef49f1a80 | ||
|
79f62ab5e6 | ||
|
67dff70cca | ||
|
acb5d0c8ad | ||
|
5e3ca6c49f | ||
|
1660fcf4e2 | ||
|
d4213cec13 | ||
|
8ee2ebfbb5 | ||
|
db307829f4 | ||
|
7ab861e0ec | ||
|
62f0a7ec35 | ||
|
7bb626cba0 | ||
|
07ec45afdc | ||
|
e0e9c8d929 | ||
|
a15e76bc0c | ||
|
4c20d07f28 | ||
|
daa7a32627 | ||
|
36f6b23c18 | ||
|
fcf9fcea52 | ||
|
476657b7c0 | ||
|
9aaa84bfae | ||
|
dc78137702 | ||
|
56dbbf2665 | ||
|
da529efb1c | ||
|
ff03190bf5 | ||
|
501a6d8d11 | ||
|
dbf360c7b8 | ||
|
20b9e7fe4e | ||
|
a8dff459f3 | ||
|
73218f4eea | ||
|
47d8bf8f99 | ||
|
df842e9b2f | ||
|
f1f5927767 | ||
|
2d63f7c580 | ||
|
35947a559b | ||
|
677b13f47c | ||
|
3b24c5275d | ||
|
3b5c566807 | ||
|
f12510172b | ||
|
3616846c03 | ||
|
fcf45db228 | ||
|
3e185b98d5 | ||
|
f7fb092e67 | ||
|
80407f707d | ||
|
978dd74adc | ||
|
a029cb7385 | ||
|
a3e217e69b | ||
|
89830c2611 | ||
|
263cd8b4e4 | ||
|
ef514a6d1b | ||
|
456abaf799 | ||
|
2ac8c2f04e | ||
|
28e4953570 | ||
|
e36cca6f43 | ||
|
f64570d6b6 | ||
|
65c4c4890a | ||
|
7e40e0cd9f | ||
|
87f0a185d8 | ||
|
4718032415 | ||
|
b4c86515c0 | ||
|
367a4e3d62 | ||
|
91ef4ad32f | ||
|
24ac1f1bcd | ||
|
3adaf41ea0 | ||
|
f4d17d883e | ||
|
2dbd29a90a | ||
|
88e47c054f | ||
|
c03b4ad50b | ||
|
aaa0ed83ad | ||
|
ffc32fa2bf | ||
|
5c4202fa4c | ||
|
016e4c8161 | ||
|
bd2ecf202c | ||
|
401ec916bb | ||
|
aacfed853e | ||
|
6c434f84ab | ||
|
72526ebbbb | ||
|
7556d491f3 | ||
|
3a4a09ce69 | ||
|
df6d10868e | ||
|
3ce8dec639 | ||
|
533bff5f9f | ||
|
05b4615af9 | ||
|
89abe718db | ||
|
547564d921 | ||
|
915ea21316 | ||
|
042c447b55 | ||
|
7ecb17d0a1 | ||
|
0eb5b596dd | ||
|
d81e930da6 | ||
|
5d4619965c | ||
|
5f2444f9c9 | ||
|
f9768627ee | ||
|
e644b20f4e | ||
|
7c5c12178d | ||
|
520b863f2a | ||
|
ea34058fa7 | ||
|
72bc66a340 | ||
|
94a4b2fb2e | ||
|
4d6d1482f2 |
2
.env
@ -1,2 +1,2 @@
|
|||||||
REACT_APP_NAME=$npm_package_name
|
REACT_APP_NAME=$npm_package_name
|
||||||
REACT_APP_GITHUB_REPO="https://github.com/jeremyckahn/chitchatter"
|
REACT_APP_GITHUB_REPO="https://githaven.org/shiloh/remnantchat"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["react-app"],
|
"extends": ["react-app", "plugin:prettier/recommended"],
|
||||||
|
"plugins": ["prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"import/order": [
|
"import/order": [
|
||||||
"error",
|
"error",
|
||||||
|
23
.github/ISSUE_TEMPLATE/room-request.yml
vendored
@ -1,23 +0,0 @@
|
|||||||
name: Community Room Request
|
|
||||||
description: Request a new Community Room
|
|
||||||
title: '[Community Room Request]: '
|
|
||||||
labels: ['room request']
|
|
||||||
assignees:
|
|
||||||
- jeremyckahn
|
|
||||||
body:
|
|
||||||
- type: input
|
|
||||||
id: room-url
|
|
||||||
attributes:
|
|
||||||
label: Room URL
|
|
||||||
description: What is the Chitchatter URL for this room?
|
|
||||||
placeholder: ex. https://chitchatter.im/public/room-subject
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
- type: input
|
|
||||||
id: room-subject
|
|
||||||
attributes:
|
|
||||||
label: Room Subject
|
|
||||||
description: What should be discussed in this room?
|
|
||||||
placeholder: ex. Technology, video games, etc.
|
|
||||||
validations:
|
|
||||||
required: true
|
|
7
.github/workflows/ci.yml
vendored
@ -4,7 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- '**'
|
- '**'
|
||||||
# - '!main'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test_and_build:
|
test_and_build:
|
||||||
@ -13,14 +12,14 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
- run: npm ci
|
- run: npm ci
|
||||||
- run: npm test
|
- run: npm test
|
||||||
|
|
||||||
- name: 'Build web app artifacts'
|
- name: 'Build web app artifacts'
|
||||||
run: npm run build
|
run: npm run build
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: build-output
|
name: build-output
|
||||||
path: build
|
path: dist
|
||||||
|
7
.github/workflows/deploy.yml
vendored
@ -17,18 +17,17 @@ jobs:
|
|||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v4
|
uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Build
|
- name: Build
|
||||||
run: |
|
run: |
|
||||||
npm ci
|
npm ci
|
||||||
npm run build
|
npm run build
|
||||||
npm run analyze -- --html build/bundle-info.html
|
|
||||||
|
|
||||||
- name: Deploy
|
- name: Deploy
|
||||||
uses: peaceiris/actions-gh-pages@v3
|
uses: peaceiris/actions-gh-pages@v4
|
||||||
with:
|
with:
|
||||||
deploy_key: ${{ secrets.DEPLOY_KEY }}
|
deploy_key: ${{ secrets.DEPLOY_KEY }}
|
||||||
publish_dir: ./build
|
publish_dir: ./dist
|
||||||
force_orphan: true
|
force_orphan: true
|
||||||
|
34
.github/workflows/security.yml
vendored
@ -1,34 +0,0 @@
|
|||||||
name: Perform Security Audit with ZAProxy
|
|
||||||
# Use ZAP Proxy to perform a full scan of the production site.
|
|
||||||
|
|
||||||
on:
|
|
||||||
# manually trigger workflow
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
# Listen for Production deployments
|
|
||||||
workflow_run:
|
|
||||||
workflows: [pages-build-deployment]
|
|
||||||
types:
|
|
||||||
- completed
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
zap_scan:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
name: Scan Production Site
|
|
||||||
steps:
|
|
||||||
- name: Set Date (NOW) as Variable
|
|
||||||
id: set-now
|
|
||||||
run: |
|
|
||||||
echo "NOW=$(date +'%Y-%m-%d')" >> "$GITHUB_OUTPUT"
|
|
||||||
|
|
||||||
- name: Checkout Repo for .zap/rules.tsv
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: ZAP Full Scan
|
|
||||||
# https://github.com/zaproxy/action-full-scan
|
|
||||||
uses: zaproxy/action-full-scan@v0.7.0
|
|
||||||
with:
|
|
||||||
target: 'https://chitchatter.im/'
|
|
||||||
rules_file_name: '.zap/rules.tsv'
|
|
||||||
artifact_name: 'zap_scan_${{ steps.set-now.outputs.NOW }}'
|
|
||||||
allow_issue_writing: false
|
|
634
.gitignore
vendored
@ -9,7 +9,9 @@
|
|||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/dist
|
||||||
|
/dev-dist
|
||||||
|
/src/config/rtcConfig.ts
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@ -25,3 +27,633 @@ yarn-error.log*
|
|||||||
|
|
||||||
# Editors
|
# Editors
|
||||||
Session.vim
|
Session.vim
|
||||||
|
build.zip
|
||||||
|
package-lock.json
|
||||||
|
build/404.html
|
||||||
|
build/asset-manifest.json
|
||||||
|
build/CNAME
|
||||||
|
build/favicon.ico
|
||||||
|
build/index.html
|
||||||
|
build/logo192.png
|
||||||
|
build/logo512.png
|
||||||
|
build/manifest.json
|
||||||
|
build/robots.txt
|
||||||
|
build/sdk.js
|
||||||
|
build/sdk.js.map
|
||||||
|
build/service-worker.js
|
||||||
|
build/service-worker.js.LICENSE.txt
|
||||||
|
build/service-worker.js.map
|
||||||
|
build/logo/favicon.svg
|
||||||
|
build/logo/logo.svg
|
||||||
|
build/sounds/new-message.aac
|
||||||
|
build/static/css/3351.c06914f1.chunk.css
|
||||||
|
build/static/css/3351.c06914f1.chunk.css.map
|
||||||
|
build/static/css/main.845e3211.css
|
||||||
|
build/static/css/main.845e3211.css.map
|
||||||
|
build/static/js/787.101cda68.chunk.js
|
||||||
|
build/static/js/787.101cda68.chunk.js.map
|
||||||
|
build/static/js/1879.f9ed8391.chunk.js
|
||||||
|
build/static/js/3254.292fba86.chunk.js
|
||||||
|
build/static/js/3254.292fba86.chunk.js.LICENSE.txt
|
||||||
|
build/static/js/3254.292fba86.chunk.js.map
|
||||||
|
build/static/js/3351.bce00804.chunk.js
|
||||||
|
build/static/js/3351.bce00804.chunk.js.map
|
||||||
|
build/static/js/5801.d5bbfa98.chunk.js
|
||||||
|
build/static/js/5801.d5bbfa98.chunk.js.map
|
||||||
|
build/static/js/6197.a90915c3.chunk.js
|
||||||
|
build/static/js/6197.a90915c3.chunk.js.map
|
||||||
|
build/static/js/6926.fac58f8b.chunk.js
|
||||||
|
build/static/js/6926.fac58f8b.chunk.js.map
|
||||||
|
build/static/js/7918.d34e0d3c.chunk.js
|
||||||
|
build/static/js/8221.f063fca8.chunk.js
|
||||||
|
build/static/js/8221.f063fca8.chunk.js.map
|
||||||
|
build/static/js/8599.bc071e1f.chunk.js
|
||||||
|
build/static/js/8599.bc071e1f.chunk.js.map
|
||||||
|
build/static/js/8778.5ba3e504.chunk.js
|
||||||
|
build/static/js/9056.9898f814.chunk.js
|
||||||
|
build/static/js/9910.7a65fbd4.chunk.js
|
||||||
|
build/static/js/main.93e418b3.js
|
||||||
|
build/static/js/main.93e418b3.js.LICENSE.txt
|
||||||
|
build/static/js/main.93e418b3.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_abap.3770c4ae.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_abap.3770c4ae.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_abnf.ed2d35f9.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_abnf.ed2d35f9.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_actionscript.01709c28.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_actionscript.01709c28.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ada.a714ebfd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ada.a714ebfd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_agda.71bcb375.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_agda.71bcb375.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_al.8523274f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_al.8523274f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_antlr4.9bb977c6.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_antlr4.9bb977c6.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_apacheconf.ca0ca952.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_apacheconf.ca0ca952.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_apex.774f005c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_apex.774f005c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_apl.2e4ed6ff.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_apl.2e4ed6ff.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_applescript.8d6d6fdd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_applescript.8d6d6fdd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_aql.da991faa.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_aql.da991faa.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_arduino.18eeb2bb.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_arduino.18eeb2bb.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_arff.9ce4fbc1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_arff.9ce4fbc1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_asciidoc.30ff6b27.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_asciidoc.30ff6b27.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_asm6502.003753c1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_asm6502.003753c1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_asmatmel.50d5ea57.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_asmatmel.50d5ea57.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_aspnet.1eff1ccc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_aspnet.1eff1ccc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_autohotkey.9feeb630.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_autohotkey.9feeb630.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_autoit.134b2c7a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_autoit.134b2c7a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_avisynth.96691927.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_avisynth.96691927.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_avroIdl.aee3e4e1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_avroIdl.aee3e4e1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bash.3b60685e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bash.3b60685e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_basic.8c068269.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_basic.8c068269.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_batch.2bba26ce.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_batch.2bba26ce.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bbcode.3538ca7e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bbcode.3538ca7e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bicep.c92cc5e7.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bicep.c92cc5e7.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_birb.c7afa520.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_birb.c7afa520.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bison.c77284c9.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bison.c77284c9.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bnf.0da9203c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bnf.0da9203c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_brainfuck.190f2205.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_brainfuck.190f2205.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_brightscript.21bf1aa1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_brightscript.21bf1aa1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bro.e940f7f0.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bro.e940f7f0.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bsl.d5dfde96.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_bsl.d5dfde96.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_c.645e464b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_c.645e464b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cfscript.76838869.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cfscript.76838869.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_chaiscript.9f5cc3bc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_chaiscript.9f5cc3bc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cil.2c271c4f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cil.2c271c4f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_clike.9e99f5b1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_clike.9e99f5b1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_clojure.8ca7261f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_clojure.8ca7261f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cmake.f88c0327.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cmake.f88c0327.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cobol.248d0b73.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cobol.248d0b73.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_coffeescript.6ba75996.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_coffeescript.6ba75996.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_concurnas.50aab7b2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_concurnas.50aab7b2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_coq.00a6b435.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_coq.00a6b435.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cpp.441ea1d2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cpp.441ea1d2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_crystal.d9c98e8d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_crystal.d9c98e8d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_csharp.c5082886.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_csharp.c5082886.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cshtml.d484403e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cshtml.d484403e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_csp.e1cf0b6b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_csp.e1cf0b6b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_css.633090b4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_css.633090b4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cssExtras.d06dea38.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cssExtras.d06dea38.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_csv.aae72df3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_csv.aae72df3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cypher.4beb098b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_cypher.4beb098b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_d.4ef9a382.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_d.4ef9a382.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dart.ed0a0cd1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dart.ed0a0cd1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dataweave.b81624cc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dataweave.b81624cc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dax.d6ba78b6.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dax.d6ba78b6.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dhall.a5132a0b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dhall.a5132a0b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_diff.1d90c683.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_diff.1d90c683.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_django.484b4da1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_django.484b4da1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dnsZoneFile.bd8c712c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dnsZoneFile.bd8c712c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_docker.98f59ca7.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_docker.98f59ca7.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dot.2c3f15c2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_dot.2c3f15c2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ebnf.4f89a118.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ebnf.4f89a118.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_editorconfig.7f184e99.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_editorconfig.7f184e99.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_eiffel.87d2f7e5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_eiffel.87d2f7e5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ejs.72f83a1e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ejs.72f83a1e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_elixir.98b0c164.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_elixir.98b0c164.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_elm.854244b1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_elm.854244b1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_erb.c8ef9ac6.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_erb.c8ef9ac6.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_erlang.807b9c66.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_erlang.807b9c66.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_etlua.ada2074f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_etlua.ada2074f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_excelFormula.c62761a4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_excelFormula.c62761a4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_factor.e0818e95.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_factor.e0818e95.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_falselang.d1c18e87.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_falselang.d1c18e87.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_firestoreSecurityRules.233e2c30.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_firestoreSecurityRules.233e2c30.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_flow.75f21cba.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_flow.75f21cba.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_fortran.6ffc0fd0.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_fortran.6ffc0fd0.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_fsharp.a5e31859.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_fsharp.a5e31859.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ftl.57dc6317.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ftl.57dc6317.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gap.788b042a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gap.788b042a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gcode.c4c7cb2c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gcode.c4c7cb2c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gdscript.19e25055.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gdscript.19e25055.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gedcom.67a66a1d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gedcom.67a66a1d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gherkin.4396cba9.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gherkin.4396cba9.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_git.7ea5ef26.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_git.7ea5ef26.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_glsl.25a4951e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_glsl.25a4951e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gml.a9fedb1b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gml.a9fedb1b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gn.524f85a5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_gn.524f85a5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_go.d3145242.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_go.d3145242.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_goModule.bde00f1f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_goModule.bde00f1f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_graphql.2e97fd83.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_graphql.2e97fd83.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_groovy.2e708f5d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_groovy.2e708f5d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_haml.1557aecb.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_haml.1557aecb.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_handlebars.68cb4366.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_handlebars.68cb4366.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_haskell.1d8f2c81.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_haskell.1d8f2c81.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_haxe.3085b496.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_haxe.3085b496.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hcl.0578839a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hcl.0578839a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hlsl.697c95af.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hlsl.697c95af.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hoon.913023b8.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hoon.913023b8.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hpkp.1080a690.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hpkp.1080a690.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hsts.f53aeeb4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_hsts.f53aeeb4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_http.cd696ebf.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_http.cd696ebf.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ichigojam.7dbb8d08.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ichigojam.7dbb8d08.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_icon.a9155464.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_icon.a9155464.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_icuMessageFormat.4ecf3221.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_icuMessageFormat.4ecf3221.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_idris.4242114e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_idris.4242114e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_iecst.ae41b123.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_iecst.ae41b123.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ignore.9e0c8042.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ignore.9e0c8042.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_inform7.38e524b8.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_inform7.38e524b8.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ini.6692e4dc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ini.6692e4dc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_io.2f9b645e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_io.2f9b645e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_j.5a66e76b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_j.5a66e76b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_java.9ac20c75.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_java.9ac20c75.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javadoc.42dc3422.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javadoc.42dc3422.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javadoclike.c9a887ad.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javadoclike.c9a887ad.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javascript.f300d60e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javascript.f300d60e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javastacktrace.eb5ff277.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_javastacktrace.eb5ff277.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jexl.91bcf086.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jexl.91bcf086.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jolie.a4202373.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jolie.a4202373.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jq.fcdd86e9.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jq.fcdd86e9.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsdoc.22e1f19d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsdoc.22e1f19d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsExtras.48cea1e5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsExtras.48cea1e5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_json.42f1caac.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_json.42f1caac.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_json5.decb5e70.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_json5.decb5e70.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsonp.863d5539.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsonp.863d5539.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsstacktrace.e1cc0564.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsstacktrace.e1cc0564.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsTemplates.6564756e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsTemplates.6564756e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsx.f2f054dd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_jsx.f2f054dd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_julia.95fbc7a3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_julia.95fbc7a3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_keepalived.ffa67ad7.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_keepalived.ffa67ad7.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_keyman.0b72908c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_keyman.0b72908c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_kotlin.691bd59a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_kotlin.691bd59a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_kumir.659105d6.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_kumir.659105d6.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_kusto.b2d7a11c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_kusto.b2d7a11c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_latex.4a07ef71.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_latex.4a07ef71.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_latte.96672677.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_latte.96672677.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_less.f9b4e88b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_less.f9b4e88b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lilypond.e3e741c5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lilypond.e3e741c5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_liquid.c602bc41.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_liquid.c602bc41.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lisp.7ab0d89f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lisp.7ab0d89f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_livescript.ce855174.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_livescript.ce855174.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_llvm.f2bc84d8.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_llvm.f2bc84d8.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_log.3fad41dd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_log.3fad41dd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lolcode.84ecda30.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lolcode.84ecda30.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lua.52a6c33c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_lua.52a6c33c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_magma.0e9af4d7.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_magma.0e9af4d7.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_makefile.266fcc4d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_makefile.266fcc4d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_markdown.e59ba5fd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_markdown.e59ba5fd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_markup.84a87f7a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_markup.84a87f7a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_markupTemplating.a8c1c731.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_markupTemplating.a8c1c731.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_matlab.898a3780.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_matlab.898a3780.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_maxscript.ffc4775e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_maxscript.ffc4775e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mel.d79aeb86.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mel.d79aeb86.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mermaid.f6ba0611.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mermaid.f6ba0611.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mizar.8388b307.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mizar.8388b307.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mongodb.3df51022.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_mongodb.3df51022.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_monkey.72f06aed.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_monkey.72f06aed.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_moonscript.37b0cd37.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_moonscript.37b0cd37.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_n1ql.4b14d687.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_n1ql.4b14d687.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_n4js.0c315394.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_n4js.0c315394.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nand2tetrisHdl.0d22ad45.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nand2tetrisHdl.0d22ad45.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_naniscript.2a3f597b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_naniscript.2a3f597b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nasm.3294a261.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nasm.3294a261.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_neon.d10e68a8.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_neon.d10e68a8.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nevod.e9b4a0d4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nevod.e9b4a0d4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nginx.7996cc1b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nginx.7996cc1b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nim.8d85c302.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nim.8d85c302.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nix.d3e20829.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nix.d3e20829.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nsis.43a7ee18.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_nsis.43a7ee18.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_objectivec.4d2e4bf2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_objectivec.4d2e4bf2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ocaml.1065cadd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ocaml.1065cadd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_opencl.3aa94984.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_opencl.3aa94984.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_openqasm.d6f8f6b6.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_openqasm.d6f8f6b6.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_oz.3a0cb911.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_oz.3a0cb911.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_parigp.f27c627f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_parigp.f27c627f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_parser.ab75055d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_parser.ab75055d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pascal.730cc923.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pascal.730cc923.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pascaligo.47bbefb6.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pascaligo.47bbefb6.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pcaxis.f5f06151.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pcaxis.f5f06151.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_peoplecode.57520fe4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_peoplecode.57520fe4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_perl.528c8b8e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_perl.528c8b8e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_php.7c3a39d1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_php.7c3a39d1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_phpdoc.47465e64.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_phpdoc.47465e64.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_phpExtras.3a545253.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_phpExtras.3a545253.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_plsql.c4805232.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_plsql.c4805232.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_powerquery.e89d0376.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_powerquery.e89d0376.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_powershell.77f12e72.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_powershell.77f12e72.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_processing.80772aab.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_processing.80772aab.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_prolog.97e5cc56.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_prolog.97e5cc56.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_promql.19574401.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_promql.19574401.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_properties.35522519.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_properties.35522519.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_protobuf.56ec3b7e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_protobuf.56ec3b7e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_psl.8ebe9791.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_psl.8ebe9791.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pug.d579f75c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pug.d579f75c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_puppet.d39a39ee.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_puppet.d39a39ee.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pure.94aa86b3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_pure.94aa86b3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_purebasic.edc05bbd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_purebasic.edc05bbd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_purescript.e0467141.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_purescript.e0467141.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_python.925670ec.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_python.925670ec.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_q.1efcbd38.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_q.1efcbd38.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_qml.60a3076c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_qml.60a3076c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_qore.3ba09070.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_qore.3ba09070.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_qsharp.2cc9d9f3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_qsharp.2cc9d9f3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_r.c67cd2ae.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_r.c67cd2ae.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_racket.753cacba.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_racket.753cacba.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_reason.aa4e59ac.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_reason.aa4e59ac.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_regex.af86145a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_regex.af86145a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rego.f02a1247.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rego.f02a1247.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_renpy.c30c8dcf.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_renpy.c30c8dcf.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rest.61c86665.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rest.61c86665.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rip.a6c0e277.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rip.a6c0e277.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_roboconf.ee276d9b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_roboconf.ee276d9b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_robotframework.6b544595.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_robotframework.6b544595.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ruby.23ca0941.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_ruby.23ca0941.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rust.e71280da.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_rust.e71280da.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sas.387f813d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sas.387f813d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sass.c2fd25f2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sass.c2fd25f2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_scala.67511470.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_scala.67511470.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_scheme.3b176b5b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_scheme.3b176b5b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_scss.df549a2a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_scss.df549a2a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_shellSession.945d15cc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_shellSession.945d15cc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_smali.5211dec5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_smali.5211dec5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_smalltalk.d163e812.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_smalltalk.d163e812.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_smarty.39322ef4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_smarty.39322ef4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sml.28438f3c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sml.28438f3c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_solidity.59b31243.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_solidity.59b31243.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_solutionFile.5f18b4a2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_solutionFile.5f18b4a2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_soy.d3f3ff0b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_soy.d3f3ff0b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sparql.4e6dc021.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sparql.4e6dc021.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_splunkSpl.1138e8d9.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_splunkSpl.1138e8d9.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sqf.69cd4c8d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sqf.69cd4c8d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sql.08890780.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_sql.08890780.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_squirrel.e70e2648.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_squirrel.e70e2648.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_stan.c6e2c5cc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_stan.c6e2c5cc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_stylus.ba406826.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_stylus.ba406826.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_swift.1fbe97bc.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_swift.1fbe97bc.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_systemd.beadda10.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_systemd.beadda10.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_t4Cs.c71b771e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_t4Cs.c71b771e.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_t4Templating.7932bd1c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_t4Templating.7932bd1c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_t4Vb.b5d48d85.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_t4Vb.b5d48d85.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tap.8485edd2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tap.8485edd2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tcl.8e60a29c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tcl.8e60a29c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_textile.94e36cdf.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_textile.94e36cdf.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_toml.d425b5b5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_toml.d425b5b5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tremor.4b29f41c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tremor.4b29f41c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tsx.a32466f3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tsx.a32466f3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tt2.381919a4.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_tt2.381919a4.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_turtle.34819f3c.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_turtle.34819f3c.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_twig.147c763f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_twig.147c763f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_typescript.fb01f157.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_typescript.fb01f157.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_typoscript.96ec3e92.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_typoscript.96ec3e92.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_unrealscript.14ef3dfa.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_unrealscript.14ef3dfa.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_uorazor.9a518174.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_uorazor.9a518174.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_uri.992356d2.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_uri.992356d2.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_v.5e01723d.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_v.5e01723d.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vala.987292da.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vala.987292da.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vbnet.9ecb408b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vbnet.9ecb408b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_velocity.7a76a0e3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_velocity.7a76a0e3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_verilog.5ff0845b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_verilog.5ff0845b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vhdl.50fa563a.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vhdl.50fa563a.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vim.efb9f86b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_vim.efb9f86b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_visualBasic.79de8f6b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_visualBasic.79de8f6b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_warpscript.90bf10e8.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_warpscript.90bf10e8.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wasm.89aeb2a1.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wasm.89aeb2a1.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_webIdl.6d6a4c8f.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_webIdl.6d6a4c8f.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wiki.98a5d7a3.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wiki.98a5d7a3.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wolfram.31374c06.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wolfram.31374c06.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wren.850b0d47.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_wren.850b0d47.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xeora.1ea8b0c5.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xeora.1ea8b0c5.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xmlDoc.26ecedcb.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xmlDoc.26ecedcb.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xojo.8a6e77dd.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xojo.8a6e77dd.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xquery.6590358b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_xquery.6590358b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_yaml.144aac25.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_yaml.144aac25.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_yang.7905aac0.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_yang.7905aac0.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_zig.71aa2d4b.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter_languages_refractor_zig.71aa2d4b.chunk.js.map
|
||||||
|
build/static/js/react-syntax-highlighter/refractor-core-import.df24ca2e.chunk.js
|
||||||
|
build/static/js/react-syntax-highlighter/refractor-core-import.df24ca2e.chunk.js.LICENSE.txt
|
||||||
|
build/static/js/react-syntax-highlighter/refractor-core-import.df24ca2e.chunk.js.map
|
||||||
|
build/static/media/logo.65c2273c08a6841a1dc427507f040724.svg
|
||||||
|
build/static/media/roboto-latin-100.a45108d3b34af91f9113.woff
|
||||||
|
build/static/media/roboto-latin-100.c2aa4ab115bf9c6057cb.woff2
|
||||||
|
build/static/media/roboto-latin-100italic.7f839a8652da29745ce4.woff2
|
||||||
|
build/static/media/roboto-latin-100italic.451d4e559d6f57cdf6a1.woff
|
||||||
|
build/static/media/roboto-latin-300.37a7069dc30fc663c878.woff2
|
||||||
|
build/static/media/roboto-latin-300.865f928cbabcc9f8f2b5.woff
|
||||||
|
build/static/media/roboto-latin-300italic.bd5b7a13f2c52b531a2a.woff
|
||||||
|
build/static/media/roboto-latin-300italic.c64e7e354c88e613c77c.woff2
|
||||||
|
build/static/media/roboto-latin-400.49ae34d4cc6b98c00c69.woff
|
||||||
|
build/static/media/roboto-latin-400.176f8f5bd5f02b3abfcf.woff2
|
||||||
|
build/static/media/roboto-latin-400italic.b1d9d9904bfca8802a63.woff
|
||||||
|
build/static/media/roboto-latin-400italic.d022bc70dc1bf7b3425d.woff2
|
||||||
|
build/static/media/roboto-latin-500.cea99d3e3e13a3a599a0.woff
|
||||||
|
build/static/media/roboto-latin-500.f5b74d7ffcdf85b9dd60.woff2
|
||||||
|
build/static/media/roboto-latin-500italic.0d8bb5b3ee5f5dac9e44.woff2
|
||||||
|
build/static/media/roboto-latin-500italic.18d00f739ff1e1c52db1.woff
|
||||||
|
build/static/media/roboto-latin-700.2267169ee7270a22a963.woff
|
||||||
|
build/static/media/roboto-latin-700.c18ee39fb002ad58b6dc.woff2
|
||||||
|
build/static/media/roboto-latin-700italic.7d8125ff7f707231fd89.woff2
|
||||||
|
build/static/media/roboto-latin-700italic.9360531f9bb817f917f0.woff
|
||||||
|
build/static/media/roboto-latin-900.870c8c1486f76054301a.woff2
|
||||||
|
build/static/media/roboto-latin-900.bac8362e7a6ea60b6983.woff
|
||||||
|
build/static/media/roboto-latin-900italic.c20d916c1a1b094c1cec.woff
|
||||||
|
build/static/media/roboto-latin-900italic.cb5ad999740e9d8a8bd1.woff2
|
||||||
|
3
.vim/coc-settings.json
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"eslint.experimental.useFlatConfig": false
|
||||||
|
}
|
7
Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
FROM node:14-alpine
|
||||||
|
WORKDIR /remnantchat/
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["npm", "start"]
|
@ -1 +0,0 @@
|
|||||||
github: [jeremyckahn]
|
|
170
README.md
@ -1,10 +1,8 @@
|
|||||||
# Chitchatter
|
# RemnantChat
|
||||||
|
|
||||||
![Chitchatter logo](./public/logo/logo.svg)
|
![RemnantChat logo](./public/logo/logo.svg)
|
||||||
|
|
||||||
<sub>Logo provided by [@ramyashreeshetty](https://github.com/ramyashreeshetty)</sub>
|
RemnantChat is a free (as in both price and freedom) communication tool. It is forked from the platform ChitChatter [see the upstream project](https://github.com/jeremyckahn/chitchatter/blob/develop/README.md). Designed to be the simplest way to connect with others privately and securely, it is:
|
||||||
|
|
||||||
Chitchatter is a free (as in both price and freedom) communication tool. Designed to be the simplest way to connect with others privately and securely, it is:
|
|
||||||
|
|
||||||
- Fully open source (licensed under [GPL v2](./LICENSE))
|
- Fully open source (licensed under [GPL v2](./LICENSE))
|
||||||
- Peer-to-peer
|
- Peer-to-peer
|
||||||
@ -13,19 +11,19 @@ Chitchatter is a free (as in both price and freedom) communication tool. Designe
|
|||||||
- Ephemeral
|
- Ephemeral
|
||||||
- Message content is never persisted to disk on either the client or server
|
- Message content is never persisted to disk on either the client or server
|
||||||
- Decentralized
|
- Decentralized
|
||||||
- There is no API server. All that's required for Chitchatter to function is availability of GitHub for static assets, and public WebTorrent and STUN/TURN relay servers for establishing peer-to-peer communication.
|
- There is no API server. All that's required for RemnantChat to function is availability of static assets on the server, and public WebTorrent and STUN/TURN relay servers for establishing peer-to-peer communication.
|
||||||
- Embeddable
|
- Embeddable
|
||||||
- [Self-hostable](#self-hosting)
|
- [Self-hostable](#self-hosting)
|
||||||
|
|
||||||
Chitchatter uses the [Create React App](https://github.com/facebook/create-react-app) toolchain. The secure networking and streaming magic would not be possible without [Trystero](https://github.com/dmotz/trystero). File transfer functionality is powered by [`secure-file-transfer`](https://github.com/jeremyckahn/secure-file-transfer).
|
RemnantChat uses the [Create React App](https://github.com/facebook/create-react-app) toolchain. The secure networking and streaming magic would not be possible without [Trystero](https://github.com/dmotz/trystero). File transfer functionality is powered by [`secure-file-transfer`](https://github.com/jeremyckahn/secure-file-transfer).
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
- [![Known Vulnerabilities](https://snyk.io/test/github/jeremyckahn/chitchatter/badge.svg?targetFile=package.json)](https://snyk.io/test/github/jeremyckahn/chitchatter?targetFile=package.json)
|
TBA
|
||||||
|
|
||||||
## How to use it
|
## How to use it
|
||||||
|
|
||||||
Open https://chitchatter.im/ and join a room to start chatting with anyone else who is in the room. By default, room names are random [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)s that are generated client-side. To privately communicate with someone, it is recommended to join one of these randomly-generated rooms and share the URL (via the "🔗" button at the top of the page) to whomever you wish to communicate with via a secure medium of your choosing (such as [Burner Note](https://burnernote.com/) or [Yopass](https://yopass.se/)). Your user name will be presented to you, and it would be good to share that with who you will be chatting with beforehand so they know they're talking to you.
|
Open https://remnant.chat/ and join a room to start chatting with anyone else who is in the room. By default, room names are random [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier)s that are generated client-side. To privately communicate with someone, it is recommended to join one of these randomly-generated rooms and share the URL (via the "🔗" button at the top of the page) to whomever you wish to communicate with via a secure medium of your choosing (such as [Burner Note](https://burnernote.com/) or [Yopass](https://yopass.se/)). Your user name will be presented to you, and it would be good to share that with who you will be chatting with beforehand so they know they're talking to you.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
@ -47,21 +45,21 @@ Open https://chitchatter.im/ and join a room to start chatting with anyone else
|
|||||||
## Anti-features
|
## Anti-features
|
||||||
|
|
||||||
- Messages are never persisted to disk. When you leave a peer room, messages are cleared from memory and cannot be retrieved.
|
- Messages are never persisted to disk. When you leave a peer room, messages are cleared from memory and cannot be retrieved.
|
||||||
- Chitchatter is an entirely client-side communication app. It uses public WebTorrent servers to establish peer connections and STUN/TURN relay servers when direct peer-to-peer connections cannot be established, but there is no Chitchatter API server.
|
- RemnantChat is an entirely client-side communication app. It uses public WebTorrent servers to establish peer connections and STUN/TURN relay servers when direct peer-to-peer connections cannot be established, but there is no RemnantChat API server.
|
||||||
- No analytics, tracking, or telemetry of any kind.
|
- No analytics, tracking, or telemetry of any kind.
|
||||||
- This is a community-driven and unfunded project that makes no money. The users come first and there is no corporate influence or financial interest involved.
|
- This is a Christian community-driven project.
|
||||||
|
|
||||||
## Why another chat app?
|
## Why another chat app?
|
||||||
|
|
||||||
There is no shortage of user-friendly chat apps available, but they rely on a central service to facilitate communication. It is difficult to trust these central services, as commercial interests and [government pressure](https://www.npr.org/2022/08/12/1117092169/nebraska-cops-used-facebook-messages-to-investigate-an-alleged-illegal-abortion) can compel service operators to work against the best interest of the users. Even when user data is handled in good faith by service operators, the possibility remains that [encrypted data held at rest may be decrypted](https://www.cbsnews.com/news/fbi-may-have-found-way-to-unlock-san-bernardino-shooters-iphone/) against the user's will.
|
There is no shortage of user-friendly chat apps available, but they rely on a central service to facilitate communication. It is difficult to trust these central services. Even when user data is handled in good faith by service operators, the possibility remains that encrypted data held at rest may be decrypted against the user's will.
|
||||||
|
|
||||||
Chitchatter designs around these risks with a [web meshe architecture](https://dev.to/jeremyckahn/taking-the-power-back-with-web-meshes-omg). There is no central service operator that stores or potentially mishandles communication data. Some services are required to establish an initial connection between peers, but otherwise the app uses direct peer-to-peer communication for everything. Any services that are used by Chitchatter have no association with the project and are publicly available for all to use.
|
RemnantChat designs around these risks with a web mesh architecture. There is no central service operator that stores or potentially mishandles communication data. Some services are required to establish an initial connection between peers, but otherwise the app uses direct peer-to-peer communication for everything. Any services that are used by RemnantChat have no association with the project and are publicly available for all to use.
|
||||||
|
|
||||||
## Use cases
|
## Use cases
|
||||||
|
|
||||||
Chitchatter offers a private and secure solution for:
|
RemnantChat offers a private and secure solution for:
|
||||||
|
|
||||||
- Organizing groups of people, such as unions or political movements
|
- Churches
|
||||||
- Conveniently moving text or data from one device to another
|
- Conveniently moving text or data from one device to another
|
||||||
- Video chatting with friends and family across operating systems (such as Android and iOS)
|
- Video chatting with friends and family across operating systems (such as Android and iOS)
|
||||||
- IT troubleshooting via screen sharing
|
- IT troubleshooting via screen sharing
|
||||||
@ -75,76 +73,47 @@ Chitchatter offers a private and secure solution for:
|
|||||||
|
|
||||||
### 💻️ Project status
|
### 💻️ Project status
|
||||||
|
|
||||||
I consider Chitchatter feature-complete inasmuch it does all the things I personally need it to do. I don't have specific plans to add significant functionality in the future, but I may do so if it seems fun to me at the time. **I am committed to fixing any significant bugs** that are reported, so please [open an issue](https://github.com/jeremyckahn/chitchatter/issues/new) if you discover one! Aside from that, Chitchatter is effectively in maintenance mode for the foreseeable future.
|
Please [open an issue](https://githaven.org/shiloh/RemnantChat/issues/new) if you discover a bug.
|
||||||
|
|
||||||
If you would like a feature to be implemented and are willing to pay a development cost to ensure it gets done, please file a GitHub issue describing the feature and indicate that you are willing to compensate for the work. If you are not willing to pay, please open a GitHub issue regardless. I may implement it if it seems fun to do so, but other members of the community may also step up to implement it via Pull Requests.
|
If you would like a feature to be implemented please file a GitHaven issue describing the feature. If you are not able to work on it yourself other members of the community may step up to implement it via Pull Requests.
|
||||||
|
|
||||||
I will always make time support Pull Requests from others. If you're willing to put in the work to improve Chitchatter, I am willing to help shepherd that work along and get it shipped.
|
We will always make time to support Pull Requests from others. If you're willing to put in the work to improve RemnantChat, we are willing to help shepherd that work along and get it shipped.
|
||||||
|
|
||||||
If you don't agree with the direction of the project, you are welcome to fork Chitchatter and take it in another one.
|
If you don't agree with the direction of the project, you are welcome to fork RemnantChat and take it in another one.
|
||||||
|
|
||||||
### 🏗️ Support and custom development
|
|
||||||
|
|
||||||
I'm willing to do paid installations and customizations of Chitchatter for your needs. If you'd like to contract me to make a version of Chitchatter that is custom built for you, please email me at jeremyckahn@gmail.com and let me know what you have in mind to get started.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Veracity
|
## Veracity
|
||||||
|
|
||||||
The core of Chitchatter's security model is the fact that it is fully open source. You are free (and encouraged) to fully audit the project source code and infrastructure. Not only is the source code available under the terms of the [GPL](./LICENSE), but [all build logs are publicly accessible](https://github.com/jeremyckahn/chitchatter/actions/workflows/pages/pages-build-deployment) as well.
|
The core of RemnantChat's security model is the fact that it is fully open source. You are free (and encouraged) to fully audit the project source code and infrastructure.
|
||||||
|
|
||||||
If you would like to verify that the app hosted at https://chitchatter.im/ is the one that is hosted on GitHub, you can use `dig`:
|
|
||||||
|
|
||||||
```
|
|
||||||
$ dig chitchatter.im
|
|
||||||
|
|
||||||
; <<>> DiG 9.18.1-1ubuntu1.1-Ubuntu <<>> chitchatter.im
|
|
||||||
;; global options: +cmd
|
|
||||||
;; Got answer:
|
|
||||||
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 61332
|
|
||||||
;; flags: qr rd ra; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1
|
|
||||||
|
|
||||||
;; OPT PSEUDOSECTION:
|
|
||||||
; EDNS: version: 0, flags:; udp: 65494
|
|
||||||
;; QUESTION SECTION:
|
|
||||||
;chitchatter.im. IN A
|
|
||||||
|
|
||||||
;; ANSWER SECTION:
|
|
||||||
chitchatter.im. 231 IN CNAME jeremyckahn.github.io.
|
|
||||||
jeremyckahn.github.io. 231 IN A 185.199.111.153
|
|
||||||
jeremyckahn.github.io. 231 IN A 185.199.110.153
|
|
||||||
jeremyckahn.github.io. 231 IN A 185.199.109.153
|
|
||||||
jeremyckahn.github.io. 231 IN A 185.199.108.153
|
|
||||||
```
|
|
||||||
|
|
||||||
To examine the static assets that are served to end users, you can audit the [`gh-pages` branch](https://github.com/jeremyckahn/chitchatter/tree/gh-pages).
|
|
||||||
|
|
||||||
## Project roadmap
|
## Project roadmap
|
||||||
|
|
||||||
See the full ticket backlog [here](https://github.com/users/jeremyckahn/projects/1).
|
- Add room moderation
|
||||||
|
- Chat history persistence settings
|
||||||
|
- Direct Messaging
|
||||||
|
- Emoticons on desktop (also ability to react to messages)
|
||||||
|
- Respond to messages
|
||||||
|
|
||||||
## Environments
|
## Environments
|
||||||
|
|
||||||
- Production environment: https://chitchatter.im/
|
- Production environment: https://remnant.chat
|
||||||
- Mirror: https://chitchatter.vercel.app/ (note that peers cannot connect across domains)
|
- Staging:
|
||||||
- Staging: https://chitchatter-git-develop-jeremyckahn.vercel.app/
|
|
||||||
|
|
||||||
## SDK
|
## SDK
|
||||||
|
|
||||||
You can use the official Chitchatter SDK to embed the app as a [Web Component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) called `<chat-room />`.
|
You can use the official RemnantChat SDK to embed the app as a [Web Component](https://developer.mozilla.org/en-US/docs/Web/API/Web_components) called `<chat-room />`.
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://chitchatter.im/sdk.js"></script>
|
<script src="https://remnant.chat/sdk.js"></script>
|
||||||
|
|
||||||
<chat-room />
|
<chat-room />
|
||||||
```
|
```
|
||||||
|
|
||||||
The `<chat-room />` component supports the following optional attributes:
|
The `<chat-room />` component supports the following optional attributes:
|
||||||
|
|
||||||
- `room`: The name of the Chitchatter room the user should join. The default value is the URL of the embedding page.
|
- `room`: The name of the RemnantChat room the user should join. The default value is the URL of the embedding page.
|
||||||
- `user-name`: The friendly name of the user (which they can change).
|
- `user-name`: The friendly name of the user (which they can change).
|
||||||
- `user-id`: The static ID of the user. The default value is a random UUID.
|
- `user-id`: The static ID of the user. The default value is a random UUID.
|
||||||
- `root-url`: The URL of the Chitchatter instance to use. The default value is `https://chitchatter.im/`.
|
- `root-url`: The URL of the RemnantChat instance to use. The default value is `https://RemnantChat.im/`.
|
||||||
- `color-mode`: `light` or `dark`. The default value is `dark`.
|
- `color-mode`: `light` or `dark`. The default value is `dark`.
|
||||||
- `play-message-sound`: Whether or not to play a sound when a user receives a message while the window is not in focus. The default value is `false`.
|
- `play-message-sound`: Whether or not to play a sound when a user receives a message while the window is not in focus. The default value is `false`.
|
||||||
|
|
||||||
@ -156,65 +125,86 @@ As well as the following [standard `<iframe />` attributes](https://developer.mo
|
|||||||
- `referrerpolicy`
|
- `referrerpolicy`
|
||||||
- `sandbox`
|
- `sandbox`
|
||||||
|
|
||||||
## Available Scripts
|
## Developing Chitchatter
|
||||||
|
|
||||||
|
> [!IMPORTANT]
|
||||||
|
> Presently Chitchatter can only be developed on \*NIX systems such as Linux and macOS. If you are using Windows, you can use [WSL](https://learn.microsoft.com/en-us/windows/wsl/install) to set up a Linux environment.
|
||||||
|
|
||||||
|
To make changes to Chitchatter, clone the source code from GitHub. Ensure you have [Node and NPM](https://nodejs.org) installed. Then in the project directory, run:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
This will install all of the dependencies.
|
||||||
|
|
||||||
|
### Available Scripts
|
||||||
|
|
||||||
|
Note: make sure to run 'npm install' after cloning the repo.
|
||||||
|
|
||||||
In the project directory, you can run:
|
In the project directory, you can run:
|
||||||
|
|
||||||
### `npm dev`
|
#### `npm dev`
|
||||||
|
|
||||||
Runs the entire stack (client + WebTorrent tracker) locally.
|
Runs the entire stack (client + WebTorrent tracker) locally.
|
||||||
|
|
||||||
### `npm start`
|
#### `npm start`
|
||||||
|
|
||||||
Runs the front end app in the development mode. Uses public WebTorrent trackers.\
|
Runs the front end app in the development mode. Uses public WebTorrent trackers. Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
||||||
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
|
|
||||||
|
|
||||||
The page will reload when you make changes. You may also see any lint errors in the console.
|
The page will reload when you make changes. You may also see any lint errors in the console.
|
||||||
|
|
||||||
### `npm test`
|
#### `npm test`
|
||||||
|
|
||||||
Launches the test runner in the interactive watch mode. See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
|
Launches the test runner in the interactive watch mode.
|
||||||
|
|
||||||
### `npm run build`
|
#### `npm run build`
|
||||||
|
|
||||||
Builds the app for production to the `build` folder. It correctly bundles React in production mode and optimizes the build for the best performance.
|
Builds the app for production to the `dist` folder. It correctly bundles React in production mode and optimizes the build for the best performance.
|
||||||
|
|
||||||
The build is minified and the filenames include the hashes.
|
The build is minified and the filenames include the hashes.
|
||||||
|
|
||||||
### Self-hosting
|
### Self-hosting
|
||||||
|
|
||||||
Chitchatter is designed to be forked and self-hosted. If you would like to change pairing or relay server configuration, or you simply prefer to control your own builds and versions, just [fork this repo](https://github.com/jeremyckahn/chitchatter/fork) and follow the steps below.
|
RemnantChat is designed to be forked and self-hosted. If you would like to change pairing or relay server configuration, or you simply prefer to control your own builds and versions, just [fork this repo](https://githaven.org/repo/fork/57) and follow the steps below.
|
||||||
|
|
||||||
#### Caveats
|
#### Caveats
|
||||||
|
|
||||||
Chitchatter peer connections are bound to the instance's domain. So, a user of Chitchatter at https://chitchatter.im/ would not be able to connect to a user of a Chitchatter instance on another domain (such as a personal GitHub Pages-hosted fork).
|
RemnantChat peer connections are bound to the instance's domain. So, a user of RemnantChat at https://remnant.chat/ would not be able to connect to a user of a RemnantChat instance on another domain.
|
||||||
|
|
||||||
#### Necessary steps after forking
|
#### Necessary steps after forking
|
||||||
|
|
||||||
Assuming you are hosting Chitchatter on [GitHub Pages](https://pages.github.com/):
|
Assuming you are hosting RemnantChat on [GitHub Pages](https://pages.github.com/):
|
||||||
|
|
||||||
1. Change the [`homepage` property in `package.json`](https://github.com/jeremyckahn/chitchatter/blob/1ea67e2c3a45115e054ebfe3457f2c3572c6213b/package.json#L4) to whatever URL your Chitchatter instance will be hosted from. This will be something like `https://github_user_or_org_name.github.io/chitchatter/`.
|
1. Change the [`homepage` property in `package.json`](https://github.com/jeremyckahn/RemnantChat/blob/1ea67e2c3a45115e054ebfe3457f2c3572c6213b/package.json#L4) to whatever URL your RemnantChat instance will be hosted from. This will be something like `https://github_user_or_org_name.github.io/RemnantChat/`.
|
||||||
2. Define a [`DEPLOY_KEY` GitHub Action secret](https://github.com/jeremyckahn/chitchatter/blob/e2bac732cf1288f7b5d0bec151098f18e8b1d0d6/.github/workflows/deploy.yml#L28-L31) (at `https://github.com/github_user_or_org_name/chitchatter/settings/secrets/actions`). See the docs for [`peaceiris/actions-gh-pages`](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-set-ssh-private-key-deploy_key) for more information.
|
2. Define a [`DEPLOY_KEY` GitHub Action secret](https://github.com/jeremyckahn/RemnantChat/blob/e2bac732cf1288f7b5d0bec151098f18e8b1d0d6/.github/workflows/deploy.yml#L28-L31) (at `https://github.com/github_user_or_org_name/RemnantChat/settings/secrets/actions`). See the docs for [`peaceiris/actions-gh-pages`](https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-set-ssh-private-key-deploy_key) for more information.
|
||||||
|
> > > > > > > 6003270 (Main: Begin derived app rewrite into remnantchat.)
|
||||||
|
|
||||||
#### Deployment
|
#### Deployment
|
||||||
|
|
||||||
##### On GitHub
|
##### On GitHub
|
||||||
|
|
||||||
When hosted on GitHub Pages and the configuration above has been done, the Production environment is updated when the remote `main` branch is updated (once GitHub Actions are enabled).
|
# When hosted on GitHub Pages and the configuration above has been done, the Production environment is updated when the remote `main` branch is updated (once GitHub Actions are enabled).
|
||||||
|
|
||||||
|
#### Deployment After Forking
|
||||||
|
|
||||||
|
> > > > > > > 811c35b (main: prelim update to documention.)
|
||||||
|
|
||||||
##### On non-GitHub hosts
|
##### On non-GitHub hosts
|
||||||
|
|
||||||
Build the app with `PUBLIC_URL="https://your-domain-here.com" npm run build`, and then serve the `build` directory. Any static file serving solution should work provided it is using a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
|
Build the app with `npm run build`, and then serve the `dist` directory. Any static file serving solution should work provided it is using a [secure context](https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts).
|
||||||
|
|
||||||
#### Runtime configuration
|
#### Runtime configuration
|
||||||
|
|
||||||
Explore the files in `src/config` to modify pairing and relay server configuration.
|
Explore the files in `src/config` to modify pairing and relay server configuration.
|
||||||
|
|
||||||
|
#### Theme customization
|
||||||
|
|
||||||
|
Chitchatter utilizes the [MUI component library](https://mui.com/) which is [themeable](https://mui.com/material-ui/customization/theming/). You can customize Chitchatter's look and feel by modifying [the shell theme definition](https://github.com/jeremyckahn/chitchatter/blob/dc78137702bb9d6bf1be289e469e080cd7d5dc8b/src/components/Shell/useShellTheme.ts#L11-L18).
|
||||||
|
|
||||||
### Troubleshooting
|
### Troubleshooting
|
||||||
|
|
||||||
If you run into any issues with a custom Chitchatter installation, first ensure that you are using [the latest version of the code](https://github.com/jeremyckahn/chitchatter/tree/main). If you are hosting your installation with GitHub Pages, sync your `main` branch and _not_ your `gh-pages` branch. Updating your `main` branch will trigger a proper rebuild of your `gh-pages` branch.
|
If you run into any issues with a custom RemnantChat installation, first ensure that you are using [the latest version of the code](https://githaven.org/shiloh/RemnantChat/tree/main).
|
||||||
|
|
||||||
- https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork
|
|
||||||
|
|
||||||
#### Peers won't connect
|
#### Peers won't connect
|
||||||
|
|
||||||
@ -222,28 +212,24 @@ This could happen for a variety of reasons. The most likely of which is that one
|
|||||||
|
|
||||||
##### Issues specific to browsers with ad blocking extensions
|
##### Issues specific to browsers with ad blocking extensions
|
||||||
|
|
||||||
Some ad blockers (such as uBlock Origin) prevent connections to certain WebTorrent servers. This prevents Chitchatter peers from connecting. To work around this, you can either disable your ad blocker or [self-host your own Chitchatter instance](#self-hosting).
|
Some ad blockers (such as uBlock Origin) prevent connections to certain WebTorrent servers. This prevents RemnantChat peers from connecting. To work around this, you can either disable your ad blocker or [self-host your own RemnantChat instance](#self-hosting).
|
||||||
|
|
||||||
##### Issues specific to iOS Safari
|
##### Issues specific to iOS Safari
|
||||||
|
|
||||||
Chitchatter works on iOS Safari, but browser-level bugs often prevent peers from rejoining the room when the browser is closed and later reopened (for instance, when switching applications). The suggested workaround for this issue is to refresh the page to rejoin the room.
|
RemnantChat works on iOS Safari, but browser-level bugs often prevent peers from rejoining the room when the browser is closed and later reopened (for instance, when switching applications). The suggested workaround for this issue is to refresh the page to rejoin the room.
|
||||||
|
|
||||||
##### Issues specific to Firefox
|
##### Issues specific to Firefox
|
||||||
|
|
||||||
Per [#36](https://github.com/jeremyckahn/chitchatter/issues/36), check your `about:config` settings and ensure that `media.peerconnection.enabled` is **enabled**.
|
check your `about:config` settings and ensure that `media.peerconnection.enabled` is **enabled**.
|
||||||
|
|
||||||
##### Security
|
#### Offered files can't be downloaded from peers
|
||||||
|
|
||||||
Chitchatter undergoes [weekly security audits](https://github.com/jeremyckahn/chitchatter/blob/develop/.github/workflows/security.yml) to identify and address potential vulnerabilities. Reports from all audits can be found in [**Issues**](https://github.com/jeremyckahn/chitchatter/issues?q=%22Security+Report+-+%22).
|
Chitchatter uses [StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js) to facilitate large file transfers. Download managers such as [FDM](https://www.freedownloadmanager.org/) are [known to interfere with StreamSaver.js](https://github.com/jimmywarting/StreamSaver.js/issues/325), so it is recommended to disable such download managers when trying to receive files.
|
||||||
|
|
||||||
|
### Security
|
||||||
|
|
||||||
### Contributors
|
### Contributors
|
||||||
|
|
||||||
<p align="center">
|
## ⚠️ [Disclaimer](https://remnant.chat/disclaimer)
|
||||||
<a href="https://github.com/jeremyckahn/chitchatter/graphs/contributors">
|
|
||||||
<img src="https://contrib.rocks/image?repo=jeremyckahn/chitchatter" />
|
|
||||||
</a>
|
|
||||||
</p>
|
|
||||||
|
|
||||||
## ⚠️ [Disclaimer](https://chitchatter.im/disclaimer)
|
By using RemnantChat, you agree to accept **full responsibility** for your actions related to its use. Additionally, you agree **not** to hold any contributors to the RemnantChat project responsible for any result of your use of it. The developers of RemnantChat do not endorse illegal activity.
|
||||||
|
|
||||||
By using Chitchatter, you agree to accept **full responsibility** for your actions related to its use. Additionally, you agree **not** to hold any contributors to the Chitchatter project responsible for any result of your use of it. The developers of Chitchatter do not endorse illegal activity.
|
|
||||||
|
10
docker-compose.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: '3'
|
||||||
|
services:
|
||||||
|
remnantchat:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- '3000:3000'
|
||||||
|
volumes:
|
||||||
|
- '.:/remnantchat/'
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
@ -1,30 +1,25 @@
|
|||||||
<!DOCTYPE html>
|
<!doctype html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
<link rel="icon" href="/favicon.ico" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="theme-color" content="#000000" />
|
<meta name="theme-color" content="#000000" />
|
||||||
<meta
|
<meta
|
||||||
name="description"
|
name="description"
|
||||||
content="A peer-to-peer chat app that is serverless, decentralized, and ephemeral"
|
content="A peer-to-peer chat app that is serverless, decentralized, and ephemeral"
|
||||||
/>
|
/>
|
||||||
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
|
<link rel="apple-touch-icon" href="/logo192.png" />
|
||||||
<!--
|
<!--
|
||||||
manifest.json provides metadata used when your web app is installed on a
|
Notice the use of in the tags above.
|
||||||
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
|
|
||||||
-->
|
|
||||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
|
|
||||||
<!--
|
|
||||||
Notice the use of %PUBLIC_URL% in the tags above.
|
|
||||||
It will be replaced with the URL of the `public` folder during the build.
|
It will be replaced with the URL of the `public` folder during the build.
|
||||||
Only files inside the `public` folder can be referenced from the HTML.
|
Only files inside the `public` folder can be referenced from the HTML.
|
||||||
|
|
||||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
Unlike "/favicon.ico" or "favicon.ico", "/favicon.ico" will
|
||||||
work correctly both with client-side routing and a non-root public URL.
|
work correctly both with client-side routing and a non-root public URL.
|
||||||
Learn how to configure a non-root public URL by running `npm run build`.
|
Learn how to configure a non-root public URL by running `npm run build`.
|
||||||
-->
|
-->
|
||||||
<title>Chitchatter</title>
|
<title>RemnantChat</title>
|
||||||
<!-- Start Single Page Apps for GitHub Pages -->
|
<!-- Start Single Page Apps for GitHub Pages -->
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
// Single Page Apps for GitHub Pages
|
// Single Page Apps for GitHub Pages
|
||||||
@ -69,5 +64,6 @@
|
|||||||
To begin the development, run `npm start` or `yarn start`.
|
To begin the development, run `npm start` or `yarn start`.
|
||||||
To create a production bundle, use `npm run build` or `yarn build`.
|
To create a production bundle, use `npm run build` or `yarn build`.
|
||||||
-->
|
-->
|
||||||
|
<script type="module" src="/src/index.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
91
manifest.ts
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { ManifestOptions } from 'vite-plugin-pwa'
|
||||||
|
|
||||||
|
export const manifest: Partial<ManifestOptions> = {
|
||||||
|
short_name: 'Chitchatter',
|
||||||
|
name: 'Chitchatter',
|
||||||
|
description:
|
||||||
|
'This is a communication tool that is free, open source, and designed for simplicity and security. All communication between you and your online peers is encrypted. There is no trace of your conversation once you leave.',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'favicon.ico',
|
||||||
|
sizes: '64x64 32x32 24x24 16x16',
|
||||||
|
type: 'image/x-icon',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'logo192.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '192x192',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'logo512.png',
|
||||||
|
type: 'image/png',
|
||||||
|
sizes: '512x512',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
start_url: './',
|
||||||
|
display: 'fullscreen',
|
||||||
|
theme_color: '#000000',
|
||||||
|
background_color: '#222222',
|
||||||
|
screenshots: [
|
||||||
|
{
|
||||||
|
src: 'screenshots/home-desktop.png',
|
||||||
|
sizes: '2160x1620',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'screenshots/public-room-desktop.png',
|
||||||
|
sizes: '2160x1620',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'screenshots/public-room-desktop-with-video.png',
|
||||||
|
sizes: '2160x1620',
|
||||||
|
type: 'image/png',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'screenshots/home-mobile-dark.png',
|
||||||
|
sizes: '750x1334',
|
||||||
|
type: 'image/png',
|
||||||
|
form_factor: 'narrow',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'screenshots/home-mobile-light.png',
|
||||||
|
sizes: '750x1334',
|
||||||
|
type: 'image/png',
|
||||||
|
form_factor: 'narrow',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
src: 'screenshots/public-room-mobile.png',
|
||||||
|
sizes: '750x1334',
|
||||||
|
type: 'image/png',
|
||||||
|
form_factor: 'narrow',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
shortcuts: [
|
||||||
|
{
|
||||||
|
name: 'About',
|
||||||
|
url: './about',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'logo512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
purpose: 'any',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Disclaimer',
|
||||||
|
url: './disclaimer',
|
||||||
|
icons: [
|
||||||
|
{
|
||||||
|
src: 'logo512.png',
|
||||||
|
sizes: '512x512',
|
||||||
|
type: 'image/png',
|
||||||
|
purpose: 'any',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
42408
package-lock.json
generated
92
package.json
@ -1,81 +1,76 @@
|
|||||||
{
|
{
|
||||||
"name": "chitchatter",
|
"name": "remnantchat",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"homepage": "https://chitchatter.im/",
|
"homepage": "https://remnant.chat",
|
||||||
"author": "Jeremy Kahn <jeremyckahn@gmail.com>",
|
"author": "Shiloh",
|
||||||
"license": "GPL-2.0-or-later",
|
"license": "GPL-2.0-or-later",
|
||||||
|
"type": "module",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.10.0",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.10.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
"@mui/icons-material": "^5.8.4",
|
"@mui/icons-material": "^5.16.7",
|
||||||
"@mui/material": "^5.14.12",
|
"@mui/material": "^5.16.7",
|
||||||
"@react-hook/debounce": "^4.0.0",
|
"@react-hook/debounce": "^4.0.0",
|
||||||
"@react-hook/window-size": "^3.1.1",
|
"@react-hook/window-size": "^3.1.1",
|
||||||
"@testing-library/jest-dom": "^5.16.5",
|
"@testing-library/jest-dom": "^6.5.0",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^15.0.7",
|
||||||
"@testing-library/user-event": "^13.5.0",
|
"@testing-library/user-event": "^14.5.2",
|
||||||
"@types/jest": "^28.1.6",
|
"@types/node": "^18.19.55",
|
||||||
"@types/node": "^18.18.4",
|
|
||||||
"@types/react": "^18.2.25",
|
|
||||||
"@types/react-dom": "^18.0.6",
|
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"classnames": "^2.3.1",
|
"detectincognitojs": "^1.3.5",
|
||||||
"detectincognitojs": "^1.1.2",
|
|
||||||
"fast-memoize": "^2.5.2",
|
"fast-memoize": "^2.5.2",
|
||||||
"file-saver": "^2.0.5",
|
"file-saver": "^2.0.5",
|
||||||
"fun-animal-names": "^0.1.1",
|
"fun-animal-names": "^0.1.1",
|
||||||
"idb-chunk-store": "^1.0.1",
|
"idb-chunk-store": "^1.0.1",
|
||||||
"localforage": "^1.10.0",
|
"localforage": "^1.10.0",
|
||||||
|
"modern-normalize": "^2.0.0",
|
||||||
"mui-markdown": "^0.5.5",
|
"mui-markdown": "^0.5.5",
|
||||||
"querystring": "^0.2.1",
|
"querystring": "^0.2.1",
|
||||||
"react": "^18.2.0",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.3.1",
|
||||||
"react-file-reader-input": "^2.0.0",
|
"react-file-reader-input": "^2.0.0",
|
||||||
"react-git-info": "^2.0.1",
|
"react-git-info": "^2.0.1",
|
||||||
"react-markdown": "^8.0.3",
|
"react-markdown": "^8.0.3",
|
||||||
"react-qrcode-logo": "^2.8.0",
|
"react-qrcode-logo": "^2.10.0",
|
||||||
"react-router-dom": "^6.16.0",
|
"react-router-dom": "^6.27.0",
|
||||||
"react-scripts": "5.0.1",
|
|
||||||
"react-syntax-highlighter": "^15.5.0",
|
"react-syntax-highlighter": "^15.5.0",
|
||||||
"react-youtube": "^10.1.0",
|
"react-youtube": "^10.1.0",
|
||||||
"readable-web-to-node-stream": "^3.0.2",
|
"readable-web-to-node-stream": "^3.0.2",
|
||||||
"remark-gfm": "^3.0.1",
|
"remark-gfm": "^3.0.1",
|
||||||
"sass": "^1.54.3",
|
|
||||||
"sdp": "^3.2.0",
|
"sdp": "^3.2.0",
|
||||||
"secure-file-transfer": "^0.0.7",
|
"secure-file-transfer": "^0.0.8",
|
||||||
"streamsaver": "^2.0.6",
|
"streamsaver": "^2.0.6",
|
||||||
"trystero": "^0.15.0",
|
"trystero": "^0.20.0",
|
||||||
"typeface-public-sans": "^1.1.13",
|
"typeface-public-sans": "^1.1.13",
|
||||||
"typeface-roboto": "^1.1.13",
|
"typeface-roboto": "^1.1.13",
|
||||||
"typescript": "^4.9.5",
|
"typescript": "^5.6.3",
|
||||||
"uuid": "^8.3.2",
|
"uuid": "^8.3.2",
|
||||||
|
"vite-plugin-babel-macros": "^1.0.6",
|
||||||
"web-vitals": "^2.1.4",
|
"web-vitals": "^2.1.4",
|
||||||
"webrtc-adapter": "^8.2.2"
|
"webrtc-adapter": "^8.2.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "source-map-explorer 'build/static/js/*.js'",
|
"start": "cross-env VITE_HOMEPAGE=$(npm pkg get homepage) vite --port 3000",
|
||||||
"start": "cross-env REACT_APP_HOMEPAGE=$(npm pkg get homepage) react-scripts start",
|
|
||||||
"start:tracker": "bittorrent-tracker",
|
"start:tracker": "bittorrent-tracker",
|
||||||
"start:streamsaver": "serve -p 3015 node_modules/streamsaver",
|
"start:streamsaver": "serve -p 3015 node_modules/streamsaver",
|
||||||
"dev": "mprocs \"npx cross-env REACT_APP_TRACKER_URL=\"ws://localhost:8000\" REACT_APP_STREAMSAVER_URL=\"http://localhost:3015/mitm.html\" npm run start\" \"npm run start:tracker\" \"npm run start:streamsaver\"",
|
"dev": "mprocs \"npx cross-env VITE_TRACKER_URL=\"ws://localhost:8000\" VITE_STREAMSAVER_URL=\"http://localhost:3015/mitm.html\" npm run start\" \"npm run start:tracker\" \"npm run start:streamsaver\"",
|
||||||
"build": "npm run build:app && npm run build:sdk",
|
"build": "npm run build:app && npm run build:sdk",
|
||||||
"build:app": "cross-env REACT_APP_HOMEPAGE=$(npm pkg get homepage) react-scripts build",
|
"build:app": "cross-env VITE_HOMEPAGE=$(npm pkg get homepage) vite build",
|
||||||
"build:sdk": "parcel build sdk/sdk.ts --dist-dir build --no-content-hash",
|
"build:sdk": "parcel build sdk/sdk.ts --no-content-hash",
|
||||||
"build:sdk:watch": "nodemon --exec \"npm run build:sdk\"",
|
"build:sdk:watch": "nodemon --exec \"npm run build:sdk\"",
|
||||||
"test": "react-scripts test",
|
"test": "vitest",
|
||||||
"prepare": "husky install",
|
"prepare": "husky install",
|
||||||
"prettier": "prettier 'src/**/*.js' --write",
|
"prettier": "prettier \"**/*.{ts,tsx}\" --write",
|
||||||
"lint": "eslint src --max-warnings=0"
|
"lint": "eslint src --max-warnings=0"
|
||||||
},
|
},
|
||||||
"eslintConfig": {
|
"eslintConfig": {
|
||||||
"extends": [
|
"extends": [
|
||||||
"react-app",
|
"react-app"
|
||||||
"react-app/jest"
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "18.12.1",
|
"node": "20.12.1",
|
||||||
"npm": "8.19.2"
|
"npm": "10.5.0"
|
||||||
},
|
},
|
||||||
"browserslist": {
|
"browserslist": {
|
||||||
"production": [
|
"production": [
|
||||||
@ -92,6 +87,8 @@
|
|||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
|
||||||
"@types/file-saver": "^2.0.7",
|
"@types/file-saver": "^2.0.7",
|
||||||
|
"@types/react": "^18.2.72",
|
||||||
|
"@types/react-dom": "^18.3.1",
|
||||||
"@types/react-file-reader-input": "^2.0.4",
|
"@types/react-file-reader-input": "^2.0.4",
|
||||||
"@types/react-syntax-highlighter": "^15.5.5",
|
"@types/react-syntax-highlighter": "^15.5.5",
|
||||||
"@types/streamsaver": "^2.0.1",
|
"@types/streamsaver": "^2.0.1",
|
||||||
@ -99,28 +96,34 @@
|
|||||||
"@types/webtorrent": "^0.109.3",
|
"@types/webtorrent": "^0.109.3",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
"@typescript-eslint/eslint-plugin": "^5.33.0",
|
||||||
"@typescript-eslint/parser": "^5.33.0",
|
"@typescript-eslint/parser": "^5.33.0",
|
||||||
|
"@vitejs/plugin-react": "^4.2.1",
|
||||||
"autoprefixer": "^10.4.8",
|
"autoprefixer": "^10.4.8",
|
||||||
"bittorrent-tracker": "^9.19.0",
|
"bittorrent-tracker": "^9.19.0",
|
||||||
"cross-env": "^7.0.3",
|
"cross-env": "^7.0.3",
|
||||||
"eslint": "^8.21.0",
|
"eslint": "^8.21.0",
|
||||||
|
"eslint-config-prettier": "^9.1.0",
|
||||||
"eslint-config-react-app": "^7.0.1",
|
"eslint-config-react-app": "^7.0.1",
|
||||||
"eslint-plugin-import": "^2.26.0",
|
"eslint-plugin-import": "^2.26.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.6.1",
|
"eslint-plugin-jsx-a11y": "^6.6.1",
|
||||||
|
"eslint-plugin-prettier": "^5.1.3",
|
||||||
"eslint-plugin-react": "^7.30.1",
|
"eslint-plugin-react": "^7.30.1",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^4.6.0",
|
||||||
"husky": "^8.0.1",
|
"husky": "^8.0.1",
|
||||||
|
"jsdom": "^24.0.0",
|
||||||
"mprocs": "^0.6.4",
|
"mprocs": "^0.6.4",
|
||||||
"nodemon": "^3.0.1",
|
"nodemon": "^3.0.1",
|
||||||
"parcel": "^2.10.0",
|
"parcel": "^2.10.0",
|
||||||
"postcss": "^8.4.31",
|
"prettier": "^3.2.5",
|
||||||
"prettier": "^2.7.1",
|
"pretty-quick": "^4.0.0",
|
||||||
"pretty-quick": "^3.1.3",
|
|
||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"serve": "^14.1.2",
|
"serve": "^14.1.2",
|
||||||
"source-map-explorer": "^2.5.3",
|
|
||||||
"tailwindcss": "^3.1.8",
|
|
||||||
"url": "^0.11.0",
|
"url": "^0.11.0",
|
||||||
"util": "^0.12.5"
|
"util": "^0.12.5",
|
||||||
|
"vite": "^5.4.6",
|
||||||
|
"vite-plugin-node-polyfills": "^0.19.0",
|
||||||
|
"vite-plugin-pwa": "^0.19.2",
|
||||||
|
"vite-plugin-svgr": "^4.2.0",
|
||||||
|
"vitest": "^1.3.1"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"ipfs-core": "npm:dry-uninstall",
|
"ipfs-core": "npm:dry-uninstall",
|
||||||
@ -134,10 +137,5 @@
|
|||||||
"resolve-url-loader": {
|
"resolve-url-loader": {
|
||||||
"postcss": "8.4.31"
|
"postcss": "8.4.31"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"jest": {
|
|
||||||
"transformIgnorePatterns": [
|
|
||||||
"node_modules/(?!trystero)/"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
},
|
|
||||||
}
|
|
@ -1 +1 @@
|
|||||||
chitchatter.im
|
remnant.chat
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 4.2 KiB |
@ -1 +1,9 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="500" zoomAndPan="magnify" viewBox="0 0 375 374.999991" height="500" preserveAspectRatio="xMidYMid meet" version="1.0"><defs><clipPath id="7de9461a18"><path d="M 71.738281 39.585938 L 303.476562 39.585938 L 303.476562 271.671875 L 71.738281 271.671875 Z M 71.738281 39.585938 " clip-rule="nonzero"/></clipPath><clipPath id="dd6ae01401"><path d="M 161.007812 254.363281 L 294.71875 254.363281 L 294.71875 335.777344 L 161.007812 335.777344 Z M 161.007812 254.363281 " clip-rule="nonzero"/></clipPath></defs><g clip-path="url(#7de9461a18)"><path fill="#1976d2" d="M 148.070312 249.8125 C 118.234375 232.945312 98.09375 200.933594 98.09375 164.207031 C 98.09375 109.9375 142.074219 65.945312 196.332031 65.945312 C 226.316406 65.945312 253.160156 79.390625 271.179688 100.574219 L 303.476562 100.574219 C 281.75 64.054688 241.898438 39.585938 196.332031 39.585938 C 127.519531 39.585938 71.738281 95.382812 71.738281 164.207031 C 71.738281 209.835938 96.257812 249.730469 132.828125 271.445312 L 148.070312 249.8125 " fill-opacity="1" fill-rule="nonzero"/></g><g clip-path="url(#dd6ae01401)"><path fill="#1976d2" d="M 225.441406 258.078125 C 224.351562 258.417969 223.253906 258.738281 222.152344 259.039062 L 161.152344 283.789062 C 165.308594 285.011719 169.554688 286.027344 173.882812 286.8125 L 294.566406 335.777344 L 237.386719 254.613281 C 233.496094 256.03125 229.507812 257.195312 225.441406 258.078125 " fill-opacity="1" fill-rule="nonzero"/></g><path fill="#d9d9d9" d="M 205.59375 231.441406 C 241.128906 231.441406 269.9375 202.625 269.9375 167.078125 C 269.9375 131.535156 241.128906 102.71875 205.59375 102.71875 C 185.953125 102.71875 168.371094 111.523438 156.566406 125.398438 L 135.414062 125.398438 C 149.644531 101.480469 175.746094 85.453125 205.59375 85.453125 C 250.664062 85.453125 287.199219 122 287.199219 167.078125 C 287.199219 207.140625 258.347656 240.460938 220.296875 247.382812 L 141.25 279.453125 L 179.089844 225.742188 C 187.171875 229.402344 196.144531 231.441406 205.59375 231.441406 " fill-opacity="1" fill-rule="nonzero"/><path fill="#1976d2" d="M 181.148438 164.210938 C 181.148438 170.050781 176.414062 174.789062 170.574219 174.789062 C 164.734375 174.789062 160 170.050781 160 164.210938 C 160 158.367188 164.734375 153.632812 170.574219 153.632812 C 176.414062 153.632812 181.148438 158.367188 181.148438 164.210938 " fill-opacity="1" fill-rule="nonzero"/><path fill="#1976d2" d="M 212.996094 164.210938 C 212.996094 170.050781 208.261719 174.789062 202.421875 174.789062 C 196.578125 174.789062 191.84375 170.050781 191.84375 164.210938 C 191.84375 158.367188 196.578125 153.632812 202.421875 153.632812 C 208.261719 153.632812 212.996094 158.367188 212.996094 164.210938 " fill-opacity="1" fill-rule="nonzero"/><path fill="#1976d2" d="M 244.84375 164.210938 C 244.84375 170.050781 240.105469 174.789062 234.265625 174.789062 C 228.425781 174.789062 223.691406 170.050781 223.691406 164.210938 C 223.691406 158.367188 228.425781 153.632812 234.265625 153.632812 C 240.105469 153.632812 244.84375 158.367188 244.84375 164.210938 " fill-opacity="1" fill-rule="nonzero"/></svg>
|
<svg xmlns="http://www.w3.org/2000/svg" width="230.66" height="230.66">
|
||||||
|
<g stroke="#fff" transform="translate(-35.2 -24.467)">
|
||||||
|
<circle cx="135.53" cy="124.796" r="92.163" fill="none" stroke-width="16.334"/>
|
||||||
|
<g stroke-width="10">
|
||||||
|
<path fill="#00bcf3" d="m104.967 121.955 61.125 61.434m0-61.434-61.125 61.434"/>
|
||||||
|
<path fill="none" d="M135.448 191.103V69.043c-.033-6.933 25.967-7.86 26.01 0v22.159"/>
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 442 B |
@ -1,266 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<svg xmlns="http://www.w3.org/2000/svg" width="230.66" height="230.66">
|
||||||
<svg
|
<g stroke="#fff" transform="translate(-35.2 -24.467)">
|
||||||
width="472.85938"
|
<circle cx="135.53" cy="124.796" r="92.163" fill="none" stroke-width="16.334"/>
|
||||||
zoomAndPan="magnify"
|
<g stroke-width="10">
|
||||||
viewBox="0 0 354.64453 128.97656"
|
<path fill="#00bcf3" d="m104.967 121.955 61.125 61.434m0-61.434-61.125 61.434"/>
|
||||||
height="171.96875"
|
<path fill="none" d="M135.448 191.103V69.043c-.033-6.933 25.967-7.86 26.01 0v22.159"/>
|
||||||
preserveAspectRatio="xMidYMid"
|
|
||||||
version="1.0"
|
|
||||||
id="svg110"
|
|
||||||
sodipodi:docname="logo.svg"
|
|
||||||
inkscape:version="1.2.1 (9c6d41e410, 2022-07-14)"
|
|
||||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
|
||||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
xmlns:svg="http://www.w3.org/2000/svg">
|
|
||||||
<sodipodi:namedview
|
|
||||||
id="namedview112"
|
|
||||||
pagecolor="#ffffff"
|
|
||||||
bordercolor="#000000"
|
|
||||||
borderopacity="0.25"
|
|
||||||
inkscape:showpageshadow="2"
|
|
||||||
inkscape:pageopacity="0.0"
|
|
||||||
inkscape:pagecheckerboard="0"
|
|
||||||
inkscape:deskcolor="#d1d1d1"
|
|
||||||
showgrid="false"
|
|
||||||
inkscape:zoom="1.3350176"
|
|
||||||
inkscape:cx="234.45384"
|
|
||||||
inkscape:cy="86.141186"
|
|
||||||
inkscape:window-width="1728"
|
|
||||||
inkscape:window-height="1043"
|
|
||||||
inkscape:window-x="0"
|
|
||||||
inkscape:window-y="0"
|
|
||||||
inkscape:window-maximized="1"
|
|
||||||
inkscape:current-layer="svg110" />
|
|
||||||
<defs
|
|
||||||
id="defs10">
|
|
||||||
<g
|
|
||||||
id="g2" />
|
|
||||||
<clipPath
|
|
||||||
id="3bacd8e954">
|
|
||||||
<path
|
|
||||||
d="M 11.863281,122.92969 H 113.10938 v 101.0625 H 11.863281 Z m 0,0"
|
|
||||||
clip-rule="nonzero"
|
|
||||||
id="path4" />
|
|
||||||
</clipPath>
|
|
||||||
<clipPath
|
|
||||||
id="abfee98d57">
|
|
||||||
<path
|
|
||||||
d="m 50.867188,216.45703 h 58.414062 v 35.44922 H 50.867188 Z m 0,0"
|
|
||||||
clip-rule="nonzero"
|
|
||||||
id="path7" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
clip-path="url(#3bacd8e954)"
|
|
||||||
id="g14"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="M 45.214844,214.47266 C 32.179688,207.12891 23.378906,193.1875 23.378906,177.19531 c 0,-23.6289 19.214844,-42.78906 42.917969,-42.78906 13.101563,0 24.828125,5.85547 32.703125,15.08203 h 14.10938 c -9.49219,-15.90234 -26.902349,-26.55859 -46.812505,-26.55859 -30.0625,0 -54.433594,24.29687 -54.433594,54.26562 0,19.8711 10.710938,37.24219 26.691407,46.69922 l 6.660156,-9.42187"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path12" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
clip-path="url(#abfee98d57)"
|
|
||||||
id="g18"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 79.015625,218.07422 c -0.476563,0.14844 -0.953125,0.28906 -1.4375,0.41797 l -26.652344,10.77734 c 1.820313,0.53125 3.675781,0.97266 5.566407,1.31641 l 52.726562,21.32422 -24.984375,-35.34375 c -1.699219,0.61718 -3.441406,1.12109 -5.21875,1.50781"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path16" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
fill="#d9d9d9"
|
|
||||||
d="m 58.480469,83.542972 c 15.527344,0 28.113281,-12.54688 28.113281,-28.02344 0,-15.48047 -12.585937,-28.02735 -28.113281,-28.02735 -8.582031,0 -16.261719,3.83204 -21.417969,9.875 h -9.242187 c 6.21875,-10.41406 17.621094,-17.39453 30.660156,-17.39453 19.691406,0 35.65234,15.91407 35.65234,35.54688 0,17.4414 -12.601559,31.95312 -29.226559,34.96875 L 30.371094,104.45312 46.902344,81.062502 c 3.53125,1.59375 7.449219,2.48047 11.578125,2.48047"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path20" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 47.800781,54.269532 c 0,2.54297 -2.066406,4.60547 -4.621093,4.60547 -2.550781,0 -4.617188,-2.0625 -4.617188,-4.60547 0,-2.54688 2.066407,-4.60938 4.617188,-4.60938 2.554687,0 4.621093,2.0625 4.621093,4.60938"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path22" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 61.714844,54.269532 c 0,2.54297 -2.066406,4.60547 -4.621094,4.60547 -2.550781,0 -4.617187,-2.0625 -4.617187,-4.60547 0,-2.54688 2.066406,-4.60938 4.617187,-4.60938 2.554688,0 4.621094,2.0625 4.621094,4.60938"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path24" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 75.628907,54.269532 c 0,2.54297 -2.070313,4.60547 -4.621094,4.60547 -2.550782,0 -4.621094,-2.0625 -4.621094,-4.60547 0,-2.54688 2.070312,-4.60938 4.621094,-4.60938 2.550781,0 4.621094,2.0625 4.621094,4.60938"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path26" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 104.62891,40.363282 h 250.01562 v 48.73828 H 104.62891 v -48.73828"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path28" />
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g36"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(124.78224,199.77909)"
|
|
||||||
id="g34">
|
|
||||||
<g
|
|
||||||
id="g32">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 0.332031,-24.335938 h 4.507813 v 11.015626 h 9.480468 v -11.015626 h 4.542969 L 21.601562,0 h -4.84375 V -7.746094 H 7.277344 V 0 Z m 0,0"
|
|
||||||
id="path30" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g44"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(148.85163,199.77909)"
|
|
||||||
id="g42">
|
|
||||||
<g
|
|
||||||
id="g40">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 0.5,-24.335938 H 7.144531 L 7.445312,0 Z m 0,0"
|
|
||||||
id="path38" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g52"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(158.7331,199.77909)"
|
|
||||||
id="g50">
|
|
||||||
<g
|
|
||||||
id="g48">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,-20.464844 v -3.871094 h 20.796875 v 3.90625 L 15.054688,-20.765625 15.65625,0 h -5.640625 l 0.601563,-20.765625 z m 0,0"
|
|
||||||
id="path46" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g60"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(184.40489,199.77909)"
|
|
||||||
id="g58">
|
|
||||||
<g
|
|
||||||
id="g56">
|
|
||||||
<path
|
|
||||||
d="m 22.300781,-18.027344 c -3.707031,-2.539062 -5.742187,-2.90625 -8.277343,-2.90625 -4.910157,0 -7.046876,3.039063 -7.046876,8.78125 0.066407,4.972656 2.503907,6.609375 7.546876,6.609375 2.535156,0 5.039062,-1.199219 7.777343,-3.703125 v 7.175782 c -2.46875,1.667968 -5.074219,2.539062 -7.945312,2.539062 -7.378907,0 -11.917969,-4.875 -11.917969,-12.621094 0,-7.710937 4.039062,-12.285156 11.417969,-12.621094 2.835937,-0.132812 5.007812,0.03516 8.445312,1.703126 z m 0,0"
|
|
||||||
id="path54" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g68"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(209.14196,199.77909)"
|
|
||||||
id="g66">
|
|
||||||
<g
|
|
||||||
id="g64">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 0.332031,-24.335938 h 4.507813 v 11.015626 h 9.480468 v -11.015626 h 4.542969 L 21.601562,0 h -4.84375 V -7.746094 H 7.277344 V 0 Z m 0,0"
|
|
||||||
id="path62" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g76"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(233.21136,199.77909)"
|
|
||||||
id="g74">
|
|
||||||
<g
|
|
||||||
id="g72">
|
|
||||||
<path
|
|
||||||
d="m 17.359375,-8.679688 -3.4375,-10.183593 c -0.265625,0.867187 -0.566406,1.703125 -0.835937,2.539062 -0.265626,0.832031 -0.535157,1.667969 -0.800782,2.46875 -0.300781,0.835938 -0.566406,1.671875 -0.867187,2.539063 -0.265625,0.867187 -0.566407,1.734375 -0.867188,2.636718 z M 2.4375,0 12.050781,-24.335938 h 3.773438 L 25.4375,0 H 19.53125 L 18.664062,-3.773438 H 9.179688 L 8.011719,0 Z m 0,0"
|
|
||||||
id="path70" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g84"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(261.08647,199.77909)"
|
|
||||||
id="g82">
|
|
||||||
<g
|
|
||||||
id="g80">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,-20.464844 v -3.871094 h 20.796875 v 3.90625 L 15.054688,-20.765625 15.65625,0 h -5.640625 l 0.601563,-20.765625 z m 0,0"
|
|
||||||
id="path78" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g92"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(286.75826,199.77909)"
|
|
||||||
id="g90">
|
|
||||||
<g
|
|
||||||
id="g88">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,-20.464844 v -3.871094 h 20.796875 v 3.90625 L 15.054688,-20.765625 15.65625,0 h -5.640625 l 0.601563,-20.765625 z m 0,0"
|
|
||||||
id="path86" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g100"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(312.43006,199.77909)"
|
|
||||||
id="g98">
|
|
||||||
<g
|
|
||||||
id="g96">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 v -24.335938 h 15.855469 v 3.871094 H 6.808594 v 5.609375 h 10.984375 v 3.839844 H 6.808594 v 4.972656 H 18.5625 V 0 Z m 0,0"
|
|
||||||
id="path94" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g108"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(333.42821,199.77909)"
|
|
||||||
id="g106">
|
|
||||||
<g
|
|
||||||
id="g104">
|
|
||||||
<path
|
|
||||||
d="m 18.128906,-17.058594 c 0,2.636719 -1.003906,4.574219 -2.773437,5.640625 1.101562,0.835938 2.070312,2.269531 3.074219,4.207031 0.53125,1.035157 1.167968,2.335938 1.835937,3.640626 0.632813,1.265624 1.300781,2.535156 1.800781,3.570312 h -5.875 l -2.46875,-6.308594 v 0.03125 C 12.320312,-9.179688 11.316406,-9.75 9.113281,-9.75 H 7.410156 V 0 H 2.4375 v -24.335938 h 7.679688 c 2.535156,0 4.503906,0.597657 5.875,1.800782 1.402343,1.203125 2.136718,3.039062 2.136718,5.476562 z m -11.019531,-4.40625 v 7.277344 h 3.339844 c 2.101562,0 4.238281,-1.4375 4.238281,-2.871094 0,-3.472656 -2.167969,-4.40625 -4.238281,-4.40625 z m 0,0"
|
|
||||||
id="path102" />
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 442 B |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 33 KiB |
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 19 KiB |
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"short_name": "Chitchatter",
|
"short_name": "RemnantChat",
|
||||||
"name": "Chitchatter",
|
"name": "RemnantChat",
|
||||||
"description": "This is a communication tool that is free, open source, and designed for simplicity and security. All communication between you and your online peers is encrypted. There is no trace of your conversation once you leave.",
|
"description": "This is a communication tool that is free, open source, and designed for simplicity and security. All communication between you and your online peers is encrypted. There is no trace of your conversation once you leave.",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
|
Before Width: | Height: | Size: 191 KiB |
Before Width: | Height: | Size: 128 KiB |
Before Width: | Height: | Size: 125 KiB |
Before Width: | Height: | Size: 1.3 MiB |
Before Width: | Height: | Size: 160 KiB |
Before Width: | Height: | Size: 87 KiB |
@ -8,7 +8,7 @@ import { QueryParamKeys } from '../src/models/shell'
|
|||||||
import { isColorMode, UserSettings } from '../src/models/settings'
|
import { isColorMode, UserSettings } from '../src/models/settings'
|
||||||
import { iframeFeatureAllowList } from '../src/config/iframeFeatureAllowList'
|
import { iframeFeatureAllowList } from '../src/config/iframeFeatureAllowList'
|
||||||
|
|
||||||
export const defaultRoot = 'https://chitchatter.im/'
|
export const defaultRoot = 'https://remnant.chat/'
|
||||||
|
|
||||||
// NOTE: This is a subset of standard iframe attributes:
|
// NOTE: This is a subset of standard iframe attributes:
|
||||||
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attributes
|
// https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attributes
|
||||||
@ -103,7 +103,7 @@ class ChatEmbed extends HTMLElement {
|
|||||||
window.addEventListener('message', this.handleConfigRequestedMessage)
|
window.addEventListener('message', this.handleConfigRequestedMessage)
|
||||||
|
|
||||||
this.configRequestExpirationTimout = setTimeout(() => {
|
this.configRequestExpirationTimout = setTimeout(() => {
|
||||||
console.error(`[chitchatter-sdk] configuration was not sent successfully`)
|
console.error(`[remnantchat-sdk] configuration was not sent successfully`)
|
||||||
this.stopListeningForConfigRequest()
|
this.stopListeningForConfigRequest()
|
||||||
}, configRequestTimeout)
|
}, configRequestTimeout)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
// NOTE: This file is a shim to enable the Bootstrap component to be
|
|
||||||
// lazy-loaded.
|
|
||||||
import Bootstrap from './Bootstrap.tsx'
|
|
||||||
export default Bootstrap
|
|
@ -1,9 +1,10 @@
|
|||||||
|
import { vi } from 'vitest'
|
||||||
import { act, render } from '@testing-library/react'
|
import { act, render } from '@testing-library/react'
|
||||||
import localforage from 'localforage'
|
import persistedStorage from 'localforage'
|
||||||
|
|
||||||
import { PersistedStorageKeys } from 'models/storage'
|
import { PersistedStorageKeys } from 'models/storage'
|
||||||
import {
|
import {
|
||||||
mockSerializationService,
|
mockSerialization,
|
||||||
mockSerializedPrivateKey,
|
mockSerializedPrivateKey,
|
||||||
mockSerializedPublicKey,
|
mockSerializedPublicKey,
|
||||||
} from 'test-utils/mocks/mockSerializationService'
|
} from 'test-utils/mocks/mockSerializationService'
|
||||||
@ -11,30 +12,16 @@ import { userSettingsStubFactory } from 'test-utils/stubs/userSettings'
|
|||||||
|
|
||||||
import Bootstrap, { BootstrapProps } from './Bootstrap'
|
import Bootstrap, { BootstrapProps } from './Bootstrap'
|
||||||
|
|
||||||
const mockPersistedStorage =
|
vi.mock('localforage')
|
||||||
jest.createMockFromModule<jest.Mock<typeof localforage>>('localforage')
|
|
||||||
|
|
||||||
const mockGetItem = jest.fn()
|
|
||||||
const mockSetItem = jest.fn()
|
|
||||||
|
|
||||||
const userSettingsStub = userSettingsStubFactory()
|
const userSettingsStub = userSettingsStubFactory()
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockGetItem.mockImplementation(() => Promise.resolve(null))
|
|
||||||
mockSetItem.mockImplementation((data: any) => Promise.resolve(data))
|
|
||||||
})
|
|
||||||
|
|
||||||
const renderBootstrap = async (overrides: Partial<BootstrapProps> = {}) => {
|
const renderBootstrap = async (overrides: Partial<BootstrapProps> = {}) => {
|
||||||
Object.assign(mockPersistedStorage, {
|
|
||||||
getItem: mockGetItem,
|
|
||||||
setItem: mockSetItem,
|
|
||||||
})
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<Bootstrap
|
<Bootstrap
|
||||||
persistedStorage={mockPersistedStorage as any as typeof localforage}
|
persistedStorage={persistedStorage}
|
||||||
initialUserSettings={userSettingsStub}
|
initialUserSettings={userSettingsStub}
|
||||||
serializationService={mockSerializationService}
|
serializationService={mockSerialization}
|
||||||
{...overrides}
|
{...overrides}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@ -51,7 +38,9 @@ test('renders', async () => {
|
|||||||
|
|
||||||
test('checks persistedStorage for user settings', async () => {
|
test('checks persistedStorage for user settings', async () => {
|
||||||
await renderBootstrap()
|
await renderBootstrap()
|
||||||
expect(mockGetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS)
|
expect(persistedStorage.getItem).toHaveBeenCalledWith(
|
||||||
|
PersistedStorageKeys.USER_SETTINGS
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test('updates persisted user settings', async () => {
|
test('updates persisted user settings', async () => {
|
||||||
@ -59,7 +48,9 @@ test('updates persisted user settings', async () => {
|
|||||||
initialUserSettings: { ...userSettingsStub, userId: 'abc123' },
|
initialUserSettings: { ...userSettingsStub, userId: 'abc123' },
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(mockSetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS, {
|
expect(persistedStorage.setItem).toHaveBeenCalledWith(
|
||||||
|
PersistedStorageKeys.USER_SETTINGS,
|
||||||
|
{
|
||||||
colorMode: 'dark',
|
colorMode: 'dark',
|
||||||
userId: 'abc123',
|
userId: 'abc123',
|
||||||
customUsername: '',
|
customUsername: '',
|
||||||
@ -68,5 +59,6 @@ test('updates persisted user settings', async () => {
|
|||||||
showActiveTypingStatus: true,
|
showActiveTypingStatus: true,
|
||||||
publicKey: mockSerializedPublicKey,
|
publicKey: mockSerializedPublicKey,
|
||||||
privateKey: mockSerializedPrivateKey,
|
privateKey: mockSerializedPrivateKey,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
@ -6,8 +6,8 @@ import {
|
|||||||
Navigate,
|
Navigate,
|
||||||
} from 'react-router-dom'
|
} from 'react-router-dom'
|
||||||
import localforage from 'localforage'
|
import localforage from 'localforage'
|
||||||
|
import { useRegisterSW } from 'virtual:pwa-register/react'
|
||||||
|
|
||||||
import * as serviceWorkerRegistration from 'serviceWorkerRegistration'
|
|
||||||
import { StorageContext } from 'contexts/StorageContext'
|
import { StorageContext } from 'contexts/StorageContext'
|
||||||
import { SettingsContext } from 'contexts/SettingsContext'
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
import { homepageUrl, routes } from 'config/routes'
|
import { homepageUrl, routes } from 'config/routes'
|
||||||
@ -27,15 +27,12 @@ import {
|
|||||||
PostMessageEvent,
|
PostMessageEvent,
|
||||||
PostMessageEventName,
|
PostMessageEventName,
|
||||||
} from 'models/sdk'
|
} from 'models/sdk'
|
||||||
import {
|
import { serialization, SerializedUserSettings } from 'services/Serialization'
|
||||||
serializationService as serializationServiceInstance,
|
|
||||||
SerializedUserSettings,
|
|
||||||
} from 'services/Serialization'
|
|
||||||
|
|
||||||
export interface BootstrapProps {
|
export interface BootstrapProps {
|
||||||
persistedStorage?: typeof localforage
|
persistedStorage?: typeof localforage
|
||||||
initialUserSettings: UserSettings
|
initialUserSettings: UserSettings
|
||||||
serializationService?: typeof serializationServiceInstance
|
serializationService?: typeof serialization
|
||||||
}
|
}
|
||||||
|
|
||||||
const configListenerTimeout = 3000
|
const configListenerTimeout = 3000
|
||||||
@ -78,11 +75,11 @@ const getConfigFromSdk = () => {
|
|||||||
|
|
||||||
const Bootstrap = ({
|
const Bootstrap = ({
|
||||||
persistedStorage: persistedStorageProp = localforage.createInstance({
|
persistedStorage: persistedStorageProp = localforage.createInstance({
|
||||||
name: 'chitchatter',
|
name: 'remnantchat',
|
||||||
description: 'Persisted settings data for chitchatter',
|
description: 'Persisted settings data for remnantchat',
|
||||||
}),
|
}),
|
||||||
initialUserSettings,
|
initialUserSettings,
|
||||||
serializationService = serializationServiceInstance,
|
serializationService = serialization,
|
||||||
}: BootstrapProps) => {
|
}: BootstrapProps) => {
|
||||||
const queryParams = useMemo(
|
const queryParams = useMemo(
|
||||||
() => new URLSearchParams(window.location.search),
|
() => new URLSearchParams(window.location.search),
|
||||||
@ -90,16 +87,11 @@ const Bootstrap = ({
|
|||||||
)
|
)
|
||||||
|
|
||||||
const [persistedStorage] = useState(persistedStorageProp)
|
const [persistedStorage] = useState(persistedStorageProp)
|
||||||
const [appNeedsUpdate, setAppNeedsUpdate] = useState(false)
|
|
||||||
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
|
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
|
||||||
const [userSettings, setUserSettings] =
|
const [userSettings, setUserSettings] =
|
||||||
useState<UserSettings>(initialUserSettings)
|
useState<UserSettings>(initialUserSettings)
|
||||||
const { userId } = userSettings
|
const { userId } = userSettings
|
||||||
|
|
||||||
const handleServiceWorkerUpdate = () => {
|
|
||||||
setAppNeedsUpdate(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
const persistUserSettings = useCallback(
|
const persistUserSettings = useCallback(
|
||||||
async (newUserSettings: UserSettings) => {
|
async (newUserSettings: UserSettings) => {
|
||||||
if (queryParams.has(QueryParamKeys.IS_EMBEDDED)) {
|
if (queryParams.has(QueryParamKeys.IS_EMBEDDED)) {
|
||||||
@ -117,9 +109,9 @@ const Bootstrap = ({
|
|||||||
[persistedStorageProp, queryParams, serializationService, userSettings]
|
[persistedStorageProp, queryParams, serializationService, userSettings]
|
||||||
)
|
)
|
||||||
|
|
||||||
useEffect(() => {
|
const {
|
||||||
serviceWorkerRegistration.register({ onUpdate: handleServiceWorkerUpdate })
|
needRefresh: [appNeedsUpdate],
|
||||||
}, [])
|
} = useRegisterSW()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
@ -152,7 +144,7 @@ const Bootstrap = ({
|
|||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(
|
console.error(
|
||||||
'Chitchatter configuration from parent frame could not be loaded'
|
'RemnantChat configuration from parent frame could not be loaded'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
10
src/Init.tsx
@ -3,7 +3,7 @@ import Box from '@mui/material/Box'
|
|||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
import { encryptionService } from 'services/Encryption'
|
import { encryption } from 'services/Encryption'
|
||||||
import {
|
import {
|
||||||
EnvironmentUnsupportedDialog,
|
EnvironmentUnsupportedDialog,
|
||||||
isEnvironmentSupported,
|
isEnvironmentSupported,
|
||||||
@ -13,8 +13,7 @@ import { ColorMode, UserSettings } from 'models/settings'
|
|||||||
|
|
||||||
import type { BootstrapProps } from './Bootstrap'
|
import type { BootstrapProps } from './Bootstrap'
|
||||||
|
|
||||||
// @ts-expect-error
|
const Bootstrap = lazy(() => import('./Bootstrap'))
|
||||||
const Bootstrap = lazy(() => import('./Bootstrap.js'))
|
|
||||||
|
|
||||||
export interface InitProps extends Omit<BootstrapProps, 'initialUserSettings'> {
|
export interface InitProps extends Omit<BootstrapProps, 'initialUserSettings'> {
|
||||||
getUuid?: typeof uuid
|
getUuid?: typeof uuid
|
||||||
@ -32,8 +31,7 @@ const Init = ({ getUuid = uuid, ...props }: InitProps) => {
|
|||||||
if (userSettings !== null) return
|
if (userSettings !== null) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { publicKey, privateKey } =
|
const { publicKey, privateKey } = await encryption.generateKeyPair()
|
||||||
await encryptionService.generateKeyPair()
|
|
||||||
|
|
||||||
setUserSettings({
|
setUserSettings({
|
||||||
userId: getUuid(),
|
userId: getUuid(),
|
||||||
@ -48,7 +46,7 @@ const Init = ({ getUuid = uuid, ...props }: InitProps) => {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
setErrorMessage(
|
setErrorMessage(
|
||||||
'Chitchatter was unable to boot up. Please check the browser console.'
|
'RemnantChat was unable to boot up. Please check the browser console.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -1,16 +1,25 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import Slider from '@mui/material/Slider'
|
import Slider from '@mui/material/Slider'
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
|
import Paper from '@mui/material/Paper'
|
||||||
import ListItemIcon from '@mui/material/ListItemIcon'
|
import ListItemIcon from '@mui/material/ListItemIcon'
|
||||||
import VolumeUp from '@mui/icons-material/VolumeUp'
|
import VolumeUpIcon from '@mui/icons-material/VolumeUp'
|
||||||
import VolumeDown from '@mui/icons-material/VolumeDown'
|
import VolumeDownIcon from '@mui/icons-material/VolumeDown'
|
||||||
import VolumeMute from '@mui/icons-material/VolumeMute'
|
import VolumeMuteIcon from '@mui/icons-material/VolumeMute'
|
||||||
|
import MicIcon from '@mui/icons-material/Mic'
|
||||||
|
import LaptopWindowsIcon from '@mui/icons-material/LaptopWindows'
|
||||||
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
import { AudioChannelName } from 'models/chat'
|
||||||
|
|
||||||
interface AudioVolumeProps {
|
interface AudioVolumeProps {
|
||||||
audioEl: HTMLAudioElement
|
audioEl: HTMLAudioElement
|
||||||
|
audioChannelName: AudioChannelName
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AudioVolume = ({ audioEl }: AudioVolumeProps) => {
|
export const AudioVolume = ({
|
||||||
|
audioEl,
|
||||||
|
audioChannelName,
|
||||||
|
}: AudioVolumeProps) => {
|
||||||
const [audioVolume, setAudioVolume] = useState(audioEl.volume)
|
const [audioVolume, setAudioVolume] = useState(audioEl.volume)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -32,19 +41,39 @@ export const AudioVolume = ({ audioEl }: AudioVolumeProps) => {
|
|||||||
|
|
||||||
const formatLabelValue = () => `${Math.round(audioVolume * 100)}%`
|
const formatLabelValue = () => `${Math.round(audioVolume * 100)}%`
|
||||||
|
|
||||||
let VolumeIcon = VolumeUp
|
let VolumeIcon = VolumeUpIcon
|
||||||
|
|
||||||
if (audioVolume === 0) {
|
if (audioVolume === 0) {
|
||||||
VolumeIcon = VolumeMute
|
VolumeIcon = VolumeMuteIcon
|
||||||
} else if (audioVolume < 0.5) {
|
} else if (audioVolume < 0.5) {
|
||||||
VolumeIcon = VolumeDown
|
VolumeIcon = VolumeDownIcon
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box sx={{ display: 'flex', pt: 1, pr: 3, alignItems: 'center' }}>
|
<Paper
|
||||||
<ListItemIcon>
|
sx={{
|
||||||
<VolumeIcon sx={{ cursor: 'pointer' }} onClick={handleIconClick} />
|
alignItems: 'center',
|
||||||
|
display: 'flex',
|
||||||
|
mt: 1.5,
|
||||||
|
pl: 2,
|
||||||
|
pr: 3,
|
||||||
|
py: 1,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ListItemIcon sx={{ cursor: 'pointer' }} onClick={handleIconClick}>
|
||||||
|
<VolumeIcon fontSize="small" />
|
||||||
|
{audioChannelName === AudioChannelName.MICROPHONE && (
|
||||||
|
<Tooltip title="Their microphone volume">
|
||||||
|
<MicIcon fontSize="small" sx={{ ml: 1, mr: 2 }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
{audioChannelName === AudioChannelName.SCREEN_SHARE && (
|
||||||
|
<Tooltip title="Their screen's volume">
|
||||||
|
<LaptopWindowsIcon fontSize="small" sx={{ ml: 1, mr: 2 }} />
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
|
<Box display="flex" width={1}>
|
||||||
<Slider
|
<Slider
|
||||||
aria-label="Volume"
|
aria-label="Volume"
|
||||||
getAriaValueText={formatLabelValue}
|
getAriaValueText={formatLabelValue}
|
||||||
@ -54,5 +83,6 @@ export const AudioVolume = ({ audioEl }: AudioVolumeProps) => {
|
|||||||
value={audioVolume * 100}
|
value={audioVolume * 100}
|
||||||
></Slider>
|
></Slider>
|
||||||
</Box>
|
</Box>
|
||||||
|
</Paper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react'
|
import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react'
|
||||||
import cx from 'classnames'
|
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import useTheme from '@mui/material/styles/useTheme'
|
import useTheme from '@mui/material/styles/useTheme'
|
||||||
|
|
||||||
@ -12,11 +11,7 @@ export interface ChatTranscriptProps extends HTMLAttributes<HTMLDivElement> {
|
|||||||
userId: string
|
userId: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ChatTranscript = ({
|
export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => {
|
||||||
className,
|
|
||||||
messageLog,
|
|
||||||
userId,
|
|
||||||
}: ChatTranscriptProps) => {
|
|
||||||
const { showRoomControls } = useContext(ShellContext)
|
const { showRoomControls } = useContext(ShellContext)
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
const boxRef = useRef<HTMLDivElement>(null)
|
const boxRef = useRef<HTMLDivElement>(null)
|
||||||
@ -66,11 +61,13 @@ export const ChatTranscript = ({
|
|||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
ref={boxRef}
|
ref={boxRef}
|
||||||
className={cx('ChatTranscript', className)}
|
className="ChatTranscript"
|
||||||
sx={{
|
sx={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
py: transcriptMinPadding,
|
flexGrow: 1,
|
||||||
|
overflow: 'auto',
|
||||||
|
pb: transcriptMinPadding,
|
||||||
pt: showRoomControls ? theme.spacing(10) : theme.spacing(2),
|
pt: showRoomControls ? theme.spacing(10) : theme.spacing(2),
|
||||||
px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`,
|
px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`,
|
||||||
transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`,
|
transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`,
|
||||||
|
9
src/components/Elements/index.tsx
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import styled from '@mui/material/styles/styled'
|
||||||
|
|
||||||
|
// NOTE: These components are defined to enable raw DOM elements to be styled
|
||||||
|
// with MUI's sx prop.
|
||||||
|
// @see https://mui.com/system/styled/
|
||||||
|
// @see https://mui.com/system/getting-started/the-sx-prop/
|
||||||
|
export const Form = styled('form')({})
|
||||||
|
export const Input = styled('input')({})
|
||||||
|
export const Main = styled('main')({})
|
@ -1,9 +1,10 @@
|
|||||||
import { useContext, useEffect, useRef, useState } from 'react'
|
import { useContext, useEffect, useRef, useState } from 'react'
|
||||||
import { TorrentFile } from 'webtorrent'
|
import { TorrentFile } from 'webtorrent'
|
||||||
|
import Box from '@mui/material/Box'
|
||||||
import CircularProgress from '@mui/material/CircularProgress'
|
import CircularProgress from '@mui/material/CircularProgress'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
|
|
||||||
import { fileTransfer } from 'services/FileTransfer'
|
import { fileTransfer } from 'lib/FileTransfer'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
|
|
||||||
type TorrentFiles = Awaited<ReturnType<typeof fileTransfer.download>>
|
type TorrentFiles = Awaited<ReturnType<typeof fileTransfer.download>>
|
||||||
@ -16,34 +17,65 @@ interface InlineFileProps {
|
|||||||
file: TorrentFile
|
file: TorrentFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NOTE: These filename extensions are copied from render-media, the upstream
|
||||||
|
// library used to embed media files:
|
||||||
|
// https://github.com/feross/render-media/blob/a445b2ab90fcd4a248552d32027b2bc6a02600c8/index.js#L15-L72
|
||||||
|
const supportedImageExtensions = [
|
||||||
|
'.bmp',
|
||||||
|
'.gif',
|
||||||
|
'.jpeg',
|
||||||
|
'.jpg',
|
||||||
|
'.png',
|
||||||
|
'.svg',
|
||||||
|
]
|
||||||
|
|
||||||
|
const supportedAudioExtensions = ['.aac', '.oga', '.ogg', '.wav', '.flac']
|
||||||
|
|
||||||
|
const supportedMediaExtensions = [
|
||||||
|
...supportedImageExtensions,
|
||||||
|
...supportedAudioExtensions,
|
||||||
|
]
|
||||||
|
|
||||||
export const InlineFile = ({ file }: InlineFileProps) => {
|
export const InlineFile = ({ file }: InlineFileProps) => {
|
||||||
const containerRef = useRef(null)
|
const containerRef = useRef(null)
|
||||||
const [didRenderingMediaFail, setDidRenderingMediaFail] = useState(false)
|
const [didRenderingMediaFail, setDidRenderingMediaFail] = useState(false)
|
||||||
|
const [isMediaSupported, setIsMediaSupported] = useState(true)
|
||||||
const shellContext = useContext(ShellContext)
|
const shellContext = useContext(ShellContext)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
|
||||||
const { current: container } = containerRef
|
const { current: container } = containerRef
|
||||||
|
|
||||||
if (!container) return
|
if (!container) return
|
||||||
|
|
||||||
|
const { name } = file
|
||||||
|
const fileNameExtension = name.split('.').pop() ?? ''
|
||||||
|
|
||||||
|
if (!supportedMediaExtensions.includes(`.${fileNameExtension}`)) {
|
||||||
|
setIsMediaSupported(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
file.appendTo(container)
|
file.appendTo(container)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
setDidRenderingMediaFail(true)
|
setDidRenderingMediaFail(true)
|
||||||
}
|
}
|
||||||
})()
|
|
||||||
}, [file, containerRef, shellContext.roomId])
|
}, [file, containerRef, shellContext.roomId])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div ref={containerRef}>
|
<Box ref={containerRef} sx={{ '& img': { maxWidth: '100%' } }}>
|
||||||
|
{!isMediaSupported && (
|
||||||
|
<Typography sx={{ fontStyle: 'italic' }}>
|
||||||
|
Media preview not supported
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
{didRenderingMediaFail && (
|
{didRenderingMediaFail && (
|
||||||
<Typography sx={{ fontStyle: 'italic' }}>
|
<Typography sx={{ fontStyle: 'italic' }}>
|
||||||
Media failed to render
|
Media failed to render
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Box>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
.Message
|
|
||||||
pre
|
|
||||||
overflow: auto
|
|
@ -5,20 +5,10 @@ import Box from '@mui/material/Box'
|
|||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
import Typography, { TypographyProps } from '@mui/material/Typography'
|
import Typography, { TypographyProps } from '@mui/material/Typography'
|
||||||
import Link, { LinkProps } from '@mui/material/Link'
|
import Link, { LinkProps } from '@mui/material/Link'
|
||||||
|
import styled from '@mui/material/styles/styled'
|
||||||
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||||
|
|
||||||
// These imports need to be ts-ignored to prevent spurious errors that look
|
|
||||||
// like this:
|
|
||||||
//
|
|
||||||
// Module 'react-markdown' cannot be imported using this construct. The
|
|
||||||
// specifier only resolves to an ES module, which cannot be imported
|
|
||||||
// synchronously. Use dynamic import instead. (tsserver 1471)
|
|
||||||
//
|
|
||||||
// @ts-ignore
|
|
||||||
import Markdown from 'react-markdown'
|
import Markdown from 'react-markdown'
|
||||||
// @ts-ignore
|
|
||||||
import { CodeProps } from 'react-markdown/lib/ast-to-react'
|
import { CodeProps } from 'react-markdown/lib/ast-to-react'
|
||||||
// @ts-ignore
|
|
||||||
import remarkGfm from 'remark-gfm'
|
import remarkGfm from 'remark-gfm'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@ -32,7 +22,7 @@ import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
|
|||||||
|
|
||||||
import { InlineMedia } from './InlineMedia'
|
import { InlineMedia } from './InlineMedia'
|
||||||
|
|
||||||
import './Message.sass'
|
const StyledMarkdown = styled(Markdown)({})
|
||||||
|
|
||||||
export interface MessageProps {
|
export interface MessageProps {
|
||||||
message: IMessage | I_InlineMedia
|
message: IMessage | I_InlineMedia
|
||||||
@ -154,13 +144,26 @@ export const Message = ({ message, showAuthor, userId }: MessageProps) => {
|
|||||||
) : isYouTubeLink(message) ? (
|
) : isYouTubeLink(message) ? (
|
||||||
<YouTube videoId={getYouTubeVideoId(message.text)} />
|
<YouTube videoId={getYouTubeVideoId(message.text)} />
|
||||||
) : (
|
) : (
|
||||||
<Markdown
|
<StyledMarkdown
|
||||||
components={componentMap}
|
components={componentMap}
|
||||||
remarkPlugins={[remarkGfm]}
|
remarkPlugins={[remarkGfm]}
|
||||||
linkTarget="_blank"
|
linkTarget="_blank"
|
||||||
|
sx={{
|
||||||
|
'& pre': {
|
||||||
|
overflow: 'auto',
|
||||||
|
},
|
||||||
|
'& ol': {
|
||||||
|
pl: 2,
|
||||||
|
listStyleType: 'decimal',
|
||||||
|
},
|
||||||
|
'& ul': {
|
||||||
|
pl: 2,
|
||||||
|
listStyleType: 'disc',
|
||||||
|
},
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{message.text}
|
{message.text}
|
||||||
</Markdown>
|
</StyledMarkdown>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
@ -14,7 +14,7 @@ import ArrowUpward from '@mui/icons-material/ArrowUpward'
|
|||||||
|
|
||||||
import { messageCharacterSizeLimit } from 'config/messaging'
|
import { messageCharacterSizeLimit } from 'config/messaging'
|
||||||
import { SettingsContext } from 'contexts/SettingsContext'
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
import classNames from 'classnames'
|
import { Form } from 'components/Elements'
|
||||||
|
|
||||||
interface MessageFormProps {
|
interface MessageFormProps {
|
||||||
onMessageSubmit: (message: string) => void
|
onMessageSubmit: (message: string) => void
|
||||||
@ -76,12 +76,17 @@ export const MessageForm = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<Form
|
||||||
onSubmit={handleMessageSubmit}
|
onSubmit={handleMessageSubmit}
|
||||||
className={classNames({
|
sx={{
|
||||||
'pt-4 px-4': showActiveTypingStatus,
|
...(showActiveTypingStatus && {
|
||||||
'p-4': !showActiveTypingStatus,
|
pt: 2,
|
||||||
})}
|
px: 2,
|
||||||
|
}),
|
||||||
|
...(!showActiveTypingStatus && {
|
||||||
|
p: 2,
|
||||||
|
}),
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
<Stack direction="row" spacing={2}>
|
<Stack direction="row" spacing={2}>
|
||||||
<FormControl fullWidth>
|
<FormControl fullWidth>
|
||||||
@ -110,6 +115,6 @@ export const MessageForm = ({
|
|||||||
<ArrowUpward />
|
<ArrowUpward />
|
||||||
</Fab>
|
</Fab>
|
||||||
</Stack>
|
</Stack>
|
||||||
</form>
|
</Form>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,19 @@
|
|||||||
import { ChangeEvent, useState, SyntheticEvent } from 'react'
|
import { ChangeEvent, useState, SyntheticEvent, useMemo } from 'react'
|
||||||
|
import { useNavigate } from 'react-router-dom'
|
||||||
import Button from '@mui/material/Button'
|
import Button from '@mui/material/Button'
|
||||||
|
import InputAdornment from '@mui/material/InputAdornment'
|
||||||
|
import IconButton from '@mui/material/IconButton'
|
||||||
import TextField from '@mui/material/TextField'
|
import TextField from '@mui/material/TextField'
|
||||||
import Dialog from '@mui/material/Dialog'
|
import Dialog from '@mui/material/Dialog'
|
||||||
import DialogActions from '@mui/material/DialogActions'
|
import DialogActions from '@mui/material/DialogActions'
|
||||||
import DialogContent from '@mui/material/DialogContent'
|
import DialogContent from '@mui/material/DialogContent'
|
||||||
import DialogContentText from '@mui/material/DialogContentText'
|
import DialogContentText from '@mui/material/DialogContentText'
|
||||||
import DialogTitle from '@mui/material/DialogTitle'
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
import Visibility from '@mui/icons-material/Visibility'
|
||||||
|
import VisibilityOff from '@mui/icons-material/VisibilityOff'
|
||||||
|
|
||||||
|
import { QueryParamKeys } from 'models/shell'
|
||||||
|
|
||||||
interface PasswordPromptProps {
|
interface PasswordPromptProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@ -17,16 +25,36 @@ export const PasswordPrompt = ({
|
|||||||
onPasswordEntered,
|
onPasswordEntered,
|
||||||
}: PasswordPromptProps) => {
|
}: PasswordPromptProps) => {
|
||||||
const [password, setPassword] = useState('')
|
const [password, setPassword] = useState('')
|
||||||
|
const [showPassword, setShowPassword] = useState(false)
|
||||||
|
|
||||||
|
const queryParams = useMemo(
|
||||||
|
() => new URLSearchParams(window.location.search),
|
||||||
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
|
const isEmbedded = queryParams.has(QueryParamKeys.IS_EMBEDDED)
|
||||||
|
|
||||||
|
const navigate = useNavigate()
|
||||||
|
|
||||||
const handleFormSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
|
const handleFormSubmit = (event: SyntheticEvent<HTMLFormElement>) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
onPasswordEntered(password)
|
onPasswordEntered(password)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClickShowPassword = () => {
|
||||||
|
setShowPassword(!showPassword)
|
||||||
|
}
|
||||||
|
|
||||||
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
|
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
|
||||||
setPassword(e.target.value)
|
setPassword(e.target.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleGoBackClick = () => {
|
||||||
|
navigate(-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const passwordToggleLabel = showPassword ? 'Hide password' : 'Show password'
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dialog open={isOpen}>
|
<Dialog open={isOpen}>
|
||||||
<form onSubmit={handleFormSubmit}>
|
<form onSubmit={handleFormSubmit}>
|
||||||
@ -34,11 +62,11 @@ export const PasswordPrompt = ({
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText sx={{ mb: 2 }}>
|
<DialogContentText sx={{ mb: 2 }}>
|
||||||
You will only be able to connect to room peers that enter the same
|
You will only be able to connect to room peers that enter the same
|
||||||
password. Due to the decentralized nature of Chitchatter, it is
|
password. Due to the decentralized nature of RemnantChat, it is
|
||||||
impossible to know if the password you enter will match the password
|
impossible to know if the password you enter will match the password
|
||||||
entered by other peers.
|
entered by other peers.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<DialogContentText>
|
<DialogContentText sx={{ mb: 2 }}>
|
||||||
If there is a mismatch, you will be in the room but be unable to
|
If there is a mismatch, you will be in the room but be unable to
|
||||||
connect to others. An error will not be shown.
|
connect to others. An error will not be shown.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
@ -47,14 +75,33 @@ export const PasswordPrompt = ({
|
|||||||
margin="dense"
|
margin="dense"
|
||||||
id="password"
|
id="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type={showPassword ? 'text' : 'password'}
|
||||||
fullWidth
|
fullWidth
|
||||||
variant="standard"
|
variant="outlined"
|
||||||
value={password}
|
value={password}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
|
InputProps={{
|
||||||
|
endAdornment: (
|
||||||
|
<InputAdornment position="end">
|
||||||
|
<Tooltip title={passwordToggleLabel}>
|
||||||
|
<IconButton
|
||||||
|
aria-label={passwordToggleLabel}
|
||||||
|
onClick={handleClickShowPassword}
|
||||||
|
>
|
||||||
|
{showPassword ? <Visibility /> : <VisibilityOff />}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</InputAdornment>
|
||||||
|
),
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
<DialogActions>
|
<DialogActions>
|
||||||
|
{!isEmbedded && (
|
||||||
|
<Button color="secondary" onClick={handleGoBackClick}>
|
||||||
|
Go back
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
<Button type="submit" disabled={password.length === 0}>
|
<Button type="submit" disabled={password.length === 0}>
|
||||||
Submit
|
Submit
|
||||||
</Button>
|
</Button>
|
||||||
|
@ -19,7 +19,7 @@ export const usePeerNameDisplay = () => {
|
|||||||
const getCustomUsername = (userId: string) =>
|
const getCustomUsername = (userId: string) =>
|
||||||
isPeerSelf(userId)
|
isPeerSelf(userId)
|
||||||
? selfCustomUsername
|
? selfCustomUsername
|
||||||
: getPeer(userId)?.customUsername ?? ''
|
: (getPeer(userId)?.customUsername ?? '')
|
||||||
|
|
||||||
const getFriendlyName = (userId: string) => {
|
const getFriendlyName = (userId: string) => {
|
||||||
const customUsername = getCustomUsername(userId)
|
const customUsername = getCustomUsername(userId)
|
||||||
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
|
|||||||
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
|
||||||
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
|
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||||
import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
|
import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
|
||||||
import { encryptionService } from 'services/Encryption/Encryption'
|
import { encryption } from 'services/Encryption/Encryption'
|
||||||
|
|
||||||
interface PeerPublicKeyProps {
|
interface PeerPublicKeyProps {
|
||||||
publicKey: CryptoKey
|
publicKey: CryptoKey
|
||||||
@ -13,7 +13,7 @@ export const PublicKey = ({ publicKey }: PeerPublicKeyProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
setPublicKeyString(await encryptionService.stringifyCryptoKey(publicKey))
|
setPublicKeyString(await encryption.stringifyCryptoKey(publicKey))
|
||||||
})()
|
})()
|
||||||
}, [publicKey])
|
}, [publicKey])
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ import Paper from '@mui/material/Paper'
|
|||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
|
||||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||||
import { VideoStreamType } from 'models/chat'
|
import { StreamType } from 'models/chat'
|
||||||
|
|
||||||
import { SelectedPeerStream } from './RoomVideoDisplay'
|
import { SelectedPeerStream } from './RoomVideoDisplay'
|
||||||
|
|
||||||
@ -13,13 +13,13 @@ interface PeerVideoProps {
|
|||||||
numberOfVideos: number
|
numberOfVideos: number
|
||||||
onVideoClick?: (
|
onVideoClick?: (
|
||||||
userId: string,
|
userId: string,
|
||||||
videoStreamType: VideoStreamType,
|
streamType: StreamType,
|
||||||
videoStream: MediaStream
|
videoStream: MediaStream
|
||||||
) => void
|
) => void
|
||||||
selectedPeerStream: SelectedPeerStream | null
|
selectedPeerStream: SelectedPeerStream | null
|
||||||
userId: string
|
userId: string
|
||||||
videoStream: MediaStream
|
videoStream: MediaStream
|
||||||
videoStreamType: VideoStreamType
|
streamType: StreamType
|
||||||
}
|
}
|
||||||
|
|
||||||
// Adapted from https://www.geeksforgeeks.org/find-the-next-perfect-square-greater-than-a-given-number/
|
// Adapted from https://www.geeksforgeeks.org/find-the-next-perfect-square-greater-than-a-given-number/
|
||||||
@ -37,7 +37,7 @@ export const PeerVideo = ({
|
|||||||
userId,
|
userId,
|
||||||
selectedPeerStream,
|
selectedPeerStream,
|
||||||
videoStream,
|
videoStream,
|
||||||
videoStreamType,
|
streamType,
|
||||||
}: PeerVideoProps) => {
|
}: PeerVideoProps) => {
|
||||||
const videoRef = useRef<HTMLVideoElement>(null)
|
const videoRef = useRef<HTMLVideoElement>(null)
|
||||||
|
|
||||||
@ -47,13 +47,14 @@ export const PeerVideo = ({
|
|||||||
|
|
||||||
video.autoplay = true
|
video.autoplay = true
|
||||||
video.srcObject = videoStream
|
video.srcObject = videoStream
|
||||||
|
video.muted = true
|
||||||
}, [videoRef, videoStream])
|
}, [videoRef, videoStream])
|
||||||
|
|
||||||
const cols = Math.sqrt(nextPerfectSquare(numberOfVideos - 1))
|
const cols = Math.sqrt(nextPerfectSquare(numberOfVideos - 1))
|
||||||
const rows = Math.ceil(numberOfVideos / cols)
|
const rows = Math.ceil(numberOfVideos / cols)
|
||||||
|
|
||||||
const handleVideoClick = () => {
|
const handleVideoClick = () => {
|
||||||
onVideoClick?.(userId, videoStreamType, videoStream)
|
onVideoClick?.(userId, streamType, videoStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
|
import { vi } from 'vitest'
|
||||||
import { PropsWithChildren } from 'react'
|
import { PropsWithChildren } from 'react'
|
||||||
import { waitFor, render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import userEvent from '@testing-library/user-event'
|
import userEvent from '@testing-library/user-event'
|
||||||
import { MemoryRouter as Router, Route, Routes } from 'react-router-dom'
|
import { MemoryRouter as Router, Route, Routes } from 'react-router-dom'
|
||||||
|
|
||||||
import { userSettingsContextStubFactory } from 'test-utils/stubs/settingsContext'
|
import { userSettingsContextStubFactory } from 'test-utils/stubs/settingsContext'
|
||||||
import { mockEncryptionService } from 'test-utils/mocks/mockEncryptionService'
|
import { mockEncryptionService } from 'test-utils/mocks/mockEncryptionService'
|
||||||
|
|
||||||
import { SettingsContext } from 'contexts/SettingsContext'
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
|
import { Time } from 'lib/Time'
|
||||||
|
|
||||||
import { Room, RoomProps } from './'
|
import { Room, RoomProps } from './'
|
||||||
|
|
||||||
@ -17,13 +18,17 @@ const userSettingsStub = userSettingsContextStubFactory({
|
|||||||
userId: mockUserId,
|
userId: mockUserId,
|
||||||
})
|
})
|
||||||
|
|
||||||
window.AudioContext = jest.fn().mockImplementation()
|
window.AudioContext = vi.fn().mockImplementation(() => {})
|
||||||
const mockGetUuid = jest.fn()
|
const mockGetUuid = vi.fn()
|
||||||
const mockMessagedSender = jest
|
const mockMessagedSender = vi.fn().mockImplementation(() => Promise.resolve([]))
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => Promise.resolve([]))
|
|
||||||
|
|
||||||
jest.mock('trystero', () => ({
|
const mockTimeService = new Time()
|
||||||
|
const mockNowTime = 1234
|
||||||
|
mockTimeService.now = () => mockNowTime
|
||||||
|
|
||||||
|
vi.mock('../../lib/Audio')
|
||||||
|
|
||||||
|
vi.mock('trystero/torrent', () => ({
|
||||||
joinRoom: () => ({
|
joinRoom: () => ({
|
||||||
makeAction: () => [mockMessagedSender, () => {}, () => {}],
|
makeAction: () => [mockMessagedSender, () => {}, () => {}],
|
||||||
ping: () => Promise.resolve(0),
|
ping: () => Promise.resolve(0),
|
||||||
@ -53,10 +58,14 @@ const RouteStub = ({ children }: PropsWithChildren) => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
jest.useFakeTimers().setSystemTime(100)
|
|
||||||
|
|
||||||
const RoomStub = (props: RoomProps) => {
|
const RoomStub = (props: RoomProps) => {
|
||||||
return <Room encryptionService={mockEncryptionService} {...props} />
|
return (
|
||||||
|
<Room
|
||||||
|
encryptionService={mockEncryptionService}
|
||||||
|
timeService={mockTimeService}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('Room', () => {
|
describe('Room', () => {
|
||||||
@ -89,9 +98,7 @@ describe('Room', () => {
|
|||||||
const sendButton = screen.getByLabelText('Send')
|
const sendButton = screen.getByLabelText('Send')
|
||||||
const textInput = screen.getByPlaceholderText('Your message')
|
const textInput = screen.getByPlaceholderText('Your message')
|
||||||
|
|
||||||
await waitFor(() => {
|
await userEvent.type(textInput, 'hello')
|
||||||
userEvent.type(textInput, 'hello')
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(sendButton).not.toBeDisabled()
|
expect(sendButton).not.toBeDisabled()
|
||||||
})
|
})
|
||||||
@ -106,13 +113,8 @@ describe('Room', () => {
|
|||||||
const sendButton = screen.getByLabelText('Send')
|
const sendButton = screen.getByLabelText('Send')
|
||||||
const textInput = screen.getByPlaceholderText('Your message')
|
const textInput = screen.getByPlaceholderText('Your message')
|
||||||
|
|
||||||
await waitFor(() => {
|
await userEvent.type(textInput, 'hello')
|
||||||
userEvent.type(textInput, 'hello')
|
await userEvent.click(sendButton)
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
userEvent.click(sendButton)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(textInput).toHaveValue('')
|
expect(textInput).toHaveValue('')
|
||||||
})
|
})
|
||||||
@ -131,18 +133,13 @@ describe('Room', () => {
|
|||||||
const sendButton = screen.getByLabelText('Send')
|
const sendButton = screen.getByLabelText('Send')
|
||||||
const textInput = screen.getByPlaceholderText('Your message')
|
const textInput = screen.getByPlaceholderText('Your message')
|
||||||
|
|
||||||
await waitFor(() => {
|
await userEvent.type(textInput, 'hello')
|
||||||
userEvent.type(textInput, 'hello')
|
await userEvent.click(sendButton)
|
||||||
})
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
userEvent.click(sendButton)
|
|
||||||
})
|
|
||||||
|
|
||||||
expect(mockMessagedSender).toHaveBeenCalledWith({
|
expect(mockMessagedSender).toHaveBeenCalledWith({
|
||||||
authorId: mockUserId,
|
authorId: mockUserId,
|
||||||
text: 'hello',
|
text: 'hello',
|
||||||
timeSent: 100,
|
timeSent: mockNowTime,
|
||||||
id: 'abc123',
|
id: 'abc123',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -8,11 +8,12 @@ import { v4 as uuid } from 'uuid'
|
|||||||
|
|
||||||
import { rtcConfig } from 'config/rtcConfig'
|
import { rtcConfig } from 'config/rtcConfig'
|
||||||
import { trackerUrls } from 'config/trackerUrls'
|
import { trackerUrls } from 'config/trackerUrls'
|
||||||
|
import { time } from 'lib/Time'
|
||||||
import { RoomContext } from 'contexts/RoomContext'
|
import { RoomContext } from 'contexts/RoomContext'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { MessageForm } from 'components/MessageForm'
|
import { MessageForm } from 'components/MessageForm'
|
||||||
import { ChatTranscript } from 'components/ChatTranscript'
|
import { ChatTranscript } from 'components/ChatTranscript'
|
||||||
import { encryptionService as encryptionServiceInstance } from 'services/Encryption'
|
import { encryption } from 'services/Encryption'
|
||||||
import { SettingsContext } from 'contexts/SettingsContext'
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
|
|
||||||
import { useRoom } from './useRoom'
|
import { useRoom } from './useRoom'
|
||||||
@ -30,13 +31,15 @@ export interface RoomProps {
|
|||||||
password?: string
|
password?: string
|
||||||
roomId: string
|
roomId: string
|
||||||
userId: string
|
userId: string
|
||||||
encryptionService?: typeof encryptionServiceInstance
|
encryptionService?: typeof encryption
|
||||||
|
timeService?: typeof time
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Room({
|
export function Room({
|
||||||
appId = `${encodeURI(window.location.origin)}_${process.env.REACT_APP_NAME}`,
|
appId = `${encodeURI(window.location.origin)}_${process.env.VITE_NAME}`,
|
||||||
getUuid = uuid,
|
getUuid = uuid,
|
||||||
encryptionService = encryptionServiceInstance,
|
encryptionService = encryption,
|
||||||
|
timeService = time,
|
||||||
roomId,
|
roomId,
|
||||||
password,
|
password,
|
||||||
userId,
|
userId,
|
||||||
@ -57,10 +60,10 @@ export function Room({
|
|||||||
} = useRoom(
|
} = useRoom(
|
||||||
{
|
{
|
||||||
appId,
|
appId,
|
||||||
trackerUrls,
|
relayUrls: trackerUrls,
|
||||||
rtcConfig,
|
rtcConfig,
|
||||||
password,
|
password,
|
||||||
trackerRedundancy: 4,
|
relayRedundancy: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
roomId,
|
roomId,
|
||||||
@ -68,6 +71,7 @@ export function Room({
|
|||||||
getUuid,
|
getUuid,
|
||||||
publicKey,
|
publicKey,
|
||||||
encryptionService,
|
encryptionService,
|
||||||
|
timeService,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -153,11 +157,7 @@ export function Room({
|
|||||||
height: landscape ? '100%' : '40%',
|
height: landscape ? '100%' : '40%',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ChatTranscript
|
<ChatTranscript messageLog={messageLog} userId={userId} />
|
||||||
messageLog={messageLog}
|
|
||||||
userId={userId}
|
|
||||||
className="grow overflow-auto"
|
|
||||||
/>
|
|
||||||
<Divider />
|
<Divider />
|
||||||
<Box>
|
<Box>
|
||||||
<MessageForm
|
<MessageForm
|
||||||
|
@ -9,7 +9,7 @@ import Menu from '@mui/material/Menu'
|
|||||||
import MenuItem from '@mui/material/MenuItem'
|
import MenuItem from '@mui/material/MenuItem'
|
||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
|
||||||
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
|
import { PeerRoom } from 'lib/PeerRoom'
|
||||||
|
|
||||||
import { useRoomAudio } from './useRoomAudio'
|
import { useRoomAudio } from './useRoomAudio'
|
||||||
import { MediaButton } from './MediaButton'
|
import { MediaButton } from './MediaButton'
|
||||||
|
@ -6,7 +6,9 @@ import Tooltip from '@mui/material/Tooltip'
|
|||||||
import CircularProgress from '@mui/material/CircularProgress'
|
import CircularProgress from '@mui/material/CircularProgress'
|
||||||
|
|
||||||
import { RoomContext } from 'contexts/RoomContext'
|
import { RoomContext } from 'contexts/RoomContext'
|
||||||
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
|
import { PeerRoom } from 'lib/PeerRoom'
|
||||||
|
|
||||||
|
import { Input } from 'components/Elements'
|
||||||
|
|
||||||
import { useRoomFileShare } from './useRoomFileShare'
|
import { useRoomFileShare } from './useRoomFileShare'
|
||||||
import { MediaButton } from './MediaButton'
|
import { MediaButton } from './MediaButton'
|
||||||
@ -73,12 +75,12 @@ export function RoomFileUploadControls({
|
|||||||
px: 1,
|
px: 1,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<input
|
<Input
|
||||||
multiple
|
multiple
|
||||||
ref={fileInputRef}
|
ref={fileInputRef}
|
||||||
type="file"
|
type="file"
|
||||||
id="file-upload"
|
id="file-upload"
|
||||||
className="hidden"
|
sx={{ display: 'none' }}
|
||||||
onChange={handleFileSelect}
|
onChange={handleFileSelect}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -3,7 +3,7 @@ import ScreenShare from '@mui/icons-material/ScreenShare'
|
|||||||
import StopScreenShare from '@mui/icons-material/StopScreenShare'
|
import StopScreenShare from '@mui/icons-material/StopScreenShare'
|
||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
|
||||||
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
|
import { PeerRoom } from 'lib/PeerRoom'
|
||||||
|
|
||||||
import { useRoomScreenShare } from './useRoomScreenShare'
|
import { useRoomScreenShare } from './useRoomScreenShare'
|
||||||
import { MediaButton } from './MediaButton'
|
import { MediaButton } from './MediaButton'
|
||||||
|
@ -9,7 +9,7 @@ import Menu from '@mui/material/Menu'
|
|||||||
import MenuItem from '@mui/material/MenuItem'
|
import MenuItem from '@mui/material/MenuItem'
|
||||||
import Tooltip from '@mui/material/Tooltip'
|
import Tooltip from '@mui/material/Tooltip'
|
||||||
|
|
||||||
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
|
import { PeerRoom } from 'lib/PeerRoom'
|
||||||
|
|
||||||
import { useRoomVideo } from './useRoomVideo'
|
import { useRoomVideo } from './useRoomVideo'
|
||||||
import { MediaButton } from './MediaButton'
|
import { MediaButton } from './MediaButton'
|
||||||
|
@ -4,7 +4,7 @@ import Paper from '@mui/material/Paper'
|
|||||||
|
|
||||||
import { RoomContext } from 'contexts/RoomContext'
|
import { RoomContext } from 'contexts/RoomContext'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { Peer, VideoStreamType } from 'models/chat'
|
import { Peer, StreamType } from 'models/chat'
|
||||||
|
|
||||||
import { PeerVideo } from './PeerVideo'
|
import { PeerVideo } from './PeerVideo'
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ interface PeerWithVideo {
|
|||||||
|
|
||||||
export interface SelectedPeerStream {
|
export interface SelectedPeerStream {
|
||||||
peerId: string
|
peerId: string
|
||||||
videoStreamType: VideoStreamType
|
streamType: StreamType
|
||||||
videoStream: MediaStream
|
videoStream: MediaStream
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +105,13 @@ export const RoomVideoDisplay = ({
|
|||||||
|
|
||||||
const handleVideoClick = (
|
const handleVideoClick = (
|
||||||
peerId: string,
|
peerId: string,
|
||||||
videoStreamType: VideoStreamType,
|
streamType: StreamType,
|
||||||
videoStream: MediaStream
|
videoStream: MediaStream
|
||||||
) => {
|
) => {
|
||||||
if (selectedPeerStream?.videoStream === videoStream) {
|
if (selectedPeerStream?.videoStream === videoStream) {
|
||||||
setSelectedPeerStream(null)
|
setSelectedPeerStream(null)
|
||||||
} else if (numberOfVideos > 1) {
|
} else if (numberOfVideos > 1) {
|
||||||
setSelectedPeerStream({ peerId, videoStreamType, videoStream })
|
setSelectedPeerStream({ peerId, streamType, videoStream })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +139,7 @@ export const RoomVideoDisplay = ({
|
|||||||
userId={selectedPeerStream.peerId}
|
userId={selectedPeerStream.peerId}
|
||||||
selectedPeerStream={selectedPeerStream}
|
selectedPeerStream={selectedPeerStream}
|
||||||
videoStream={selectedPeerStream.videoStream}
|
videoStream={selectedPeerStream.videoStream}
|
||||||
videoStreamType={selectedPeerStream.videoStreamType}
|
streamType={selectedPeerStream.streamType}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
@ -168,7 +168,7 @@ export const RoomVideoDisplay = ({
|
|||||||
userId={userId}
|
userId={userId}
|
||||||
selectedPeerStream={selectedPeerStream}
|
selectedPeerStream={selectedPeerStream}
|
||||||
videoStream={selfVideoStream}
|
videoStream={selfVideoStream}
|
||||||
videoStreamType={VideoStreamType.WEBCAM}
|
streamType={StreamType.WEBCAM}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{selfScreenStream && (
|
{selfScreenStream && (
|
||||||
@ -179,7 +179,7 @@ export const RoomVideoDisplay = ({
|
|||||||
userId={userId}
|
userId={userId}
|
||||||
selectedPeerStream={selectedPeerStream}
|
selectedPeerStream={selectedPeerStream}
|
||||||
videoStream={selfScreenStream}
|
videoStream={selfScreenStream}
|
||||||
videoStreamType={VideoStreamType.SCREEN_SHARE}
|
streamType={StreamType.SCREEN_SHARE}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{peersWithVideo.map(peerWithVideo => (
|
{peersWithVideo.map(peerWithVideo => (
|
||||||
@ -191,7 +191,7 @@ export const RoomVideoDisplay = ({
|
|||||||
userId={peerWithVideo.peer.userId}
|
userId={peerWithVideo.peer.userId}
|
||||||
selectedPeerStream={selectedPeerStream}
|
selectedPeerStream={selectedPeerStream}
|
||||||
videoStream={peerWithVideo.videoStream}
|
videoStream={peerWithVideo.videoStream}
|
||||||
videoStreamType={VideoStreamType.WEBCAM}
|
streamType={StreamType.WEBCAM}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{peerWithVideo.screenStream && (
|
{peerWithVideo.screenStream && (
|
||||||
@ -201,7 +201,7 @@ export const RoomVideoDisplay = ({
|
|||||||
userId={peerWithVideo.peer.userId}
|
userId={peerWithVideo.peer.userId}
|
||||||
selectedPeerStream={selectedPeerStream}
|
selectedPeerStream={selectedPeerStream}
|
||||||
videoStream={peerWithVideo.screenStream}
|
videoStream={peerWithVideo.screenStream}
|
||||||
videoStreamType={VideoStreamType.SCREEN_SHARE}
|
streamType={StreamType.SCREEN_SHARE}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useContext, useEffect, useState } from 'react'
|
import { useCallback, useContext, useEffect, useState } from 'react'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { Peer, PeerVerificationState } from 'models/chat'
|
import { Peer, PeerVerificationState } from 'models/chat'
|
||||||
import { encryptionService as encryptionServiceInstance } from 'services/Encryption'
|
import { encryption } from 'services/Encryption'
|
||||||
import { PeerRoom } from 'services/PeerRoom'
|
import { PeerRoom } from 'lib/PeerRoom'
|
||||||
import { PeerActions } from 'models/network'
|
import { PeerActions } from 'models/network'
|
||||||
import { verificationTimeout } from 'config/messaging'
|
import { verificationTimeout } from 'config/messaging'
|
||||||
import { usePeerNameDisplay } from 'components/PeerNameDisplay'
|
import { usePeerNameDisplay } from 'components/PeerNameDisplay'
|
||||||
@ -10,13 +10,13 @@ import { usePeerNameDisplay } from 'components/PeerNameDisplay'
|
|||||||
interface UserPeerVerificationProps {
|
interface UserPeerVerificationProps {
|
||||||
peerRoom: PeerRoom
|
peerRoom: PeerRoom
|
||||||
privateKey: CryptoKey
|
privateKey: CryptoKey
|
||||||
encryptionService?: typeof encryptionServiceInstance
|
encryptionService?: typeof encryption
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePeerVerification = ({
|
export const usePeerVerification = ({
|
||||||
peerRoom,
|
peerRoom,
|
||||||
privateKey,
|
privateKey,
|
||||||
encryptionService = encryptionServiceInstance,
|
encryptionService = encryption,
|
||||||
}: UserPeerVerificationProps) => {
|
}: UserPeerVerificationProps) => {
|
||||||
const { updatePeer, peerList, showAlert } = useContext(ShellContext)
|
const { updatePeer, peerList, showAlert } = useContext(ShellContext)
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { useContext, useEffect, useMemo, useState } from 'react'
|
import { useContext, useEffect, useMemo, useState } from 'react'
|
||||||
import { BaseRoomConfig } from 'trystero'
|
import { BaseRoomConfig } from 'trystero'
|
||||||
import { TorrentRoomConfig } from 'trystero/torrent'
|
import { RelayConfig } from 'trystero/torrent'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { useDebounce } from '@react-hook/debounce'
|
import { useDebounce } from '@react-hook/debounce'
|
||||||
|
|
||||||
@ -23,16 +23,15 @@ import {
|
|||||||
TypingStatus,
|
TypingStatus,
|
||||||
Peer,
|
Peer,
|
||||||
PeerVerificationState,
|
PeerVerificationState,
|
||||||
|
AudioChannelName,
|
||||||
} from 'models/chat'
|
} from 'models/chat'
|
||||||
import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay'
|
import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay'
|
||||||
import { NotificationService } from 'services/Notification'
|
import { Audio } from 'lib/Audio'
|
||||||
import { Audio as AudioService } from 'services/Audio'
|
import { time } from 'lib/Time'
|
||||||
import { PeerRoom, PeerHookType } from 'services/PeerRoom'
|
import { PeerRoom, PeerHookType } from 'lib/PeerRoom'
|
||||||
import { fileTransfer } from 'services/FileTransfer'
|
import { notification } from 'services/Notification'
|
||||||
import {
|
import { fileTransfer } from 'lib/FileTransfer'
|
||||||
AllowedKeyType,
|
import { AllowedKeyType, encryption } from 'services/Encryption'
|
||||||
encryptionService as encryptionServiceInstance,
|
|
||||||
} from 'services/Encryption'
|
|
||||||
|
|
||||||
import { messageTranscriptSizeLimit } from 'config/messaging'
|
import { messageTranscriptSizeLimit } from 'config/messaging'
|
||||||
|
|
||||||
@ -43,23 +42,25 @@ interface UseRoomConfig {
|
|||||||
userId: string
|
userId: string
|
||||||
publicKey: CryptoKey
|
publicKey: CryptoKey
|
||||||
getUuid?: typeof uuid
|
getUuid?: typeof uuid
|
||||||
encryptionService?: typeof encryptionServiceInstance
|
encryptionService?: typeof encryption
|
||||||
|
timeService?: typeof time
|
||||||
}
|
}
|
||||||
|
|
||||||
interface UserMetadata {
|
interface UserMetadata extends Record<string, any> {
|
||||||
userId: string
|
userId: string
|
||||||
customUsername: string
|
customUsername: string
|
||||||
publicKeyString: string
|
publicKeyString: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRoom(
|
export function useRoom(
|
||||||
{ password, ...roomConfig }: BaseRoomConfig & TorrentRoomConfig,
|
{ password, ...roomConfig }: BaseRoomConfig & RelayConfig,
|
||||||
{
|
{
|
||||||
roomId,
|
roomId,
|
||||||
userId,
|
userId,
|
||||||
publicKey,
|
publicKey,
|
||||||
getUuid = uuid,
|
getUuid = uuid,
|
||||||
encryptionService = encryptionServiceInstance,
|
encryptionService = encryption,
|
||||||
|
timeService = time,
|
||||||
}: UseRoomConfig
|
}: UseRoomConfig
|
||||||
) {
|
) {
|
||||||
const isPrivate = password !== undefined
|
const isPrivate = password !== undefined
|
||||||
@ -86,9 +87,7 @@ export function useRoom(
|
|||||||
const [messageLog, _setMessageLog] = useState<Array<Message | InlineMedia>>(
|
const [messageLog, _setMessageLog] = useState<Array<Message | InlineMedia>>(
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
const [newMessageAudio] = useState(
|
const [newMessageAudio] = useState(() => new Audio('/sounds/new-message.aac'))
|
||||||
() => new AudioService(process.env.PUBLIC_URL + '/sounds/new-message.aac')
|
|
||||||
)
|
|
||||||
|
|
||||||
const { getDisplayUsername } = usePeerNameDisplay()
|
const { getDisplayUsername } = usePeerNameDisplay()
|
||||||
|
|
||||||
@ -240,7 +239,7 @@ export function useRoom(
|
|||||||
const unsentMessage: UnsentMessage = {
|
const unsentMessage: UnsentMessage = {
|
||||||
authorId: userId,
|
authorId: userId,
|
||||||
text: message,
|
text: message,
|
||||||
timeSent: Date.now(),
|
timeSent: timeService.now(),
|
||||||
id: getUuid(),
|
id: getUuid(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -251,7 +250,7 @@ export function useRoom(
|
|||||||
|
|
||||||
setMessageLog([
|
setMessageLog([
|
||||||
...messageLog,
|
...messageLog,
|
||||||
{ ...unsentMessage, timeReceived: Date.now() },
|
{ ...unsentMessage, timeReceived: timeService.now() },
|
||||||
])
|
])
|
||||||
setIsMessageSending(false)
|
setIsMessageSending(false)
|
||||||
}
|
}
|
||||||
@ -271,7 +270,10 @@ export function useRoom(
|
|||||||
userId,
|
userId,
|
||||||
publicKey,
|
publicKey,
|
||||||
customUsername,
|
customUsername,
|
||||||
audioState: AudioState.STOPPED,
|
audioChannelState: {
|
||||||
|
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
|
||||||
|
[AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED,
|
||||||
|
},
|
||||||
videoState: VideoState.STOPPED,
|
videoState: VideoState.STOPPED,
|
||||||
screenShareState: ScreenShareState.NOT_SHARING,
|
screenShareState: ScreenShareState.NOT_SHARING,
|
||||||
offeredFileId: null,
|
offeredFileId: null,
|
||||||
@ -323,13 +325,14 @@ export function useRoom(
|
|||||||
if (userSettings.showNotificationOnNewMessage) {
|
if (userSettings.showNotificationOnNewMessage) {
|
||||||
const displayUsername = getDisplayUsername(message.authorId)
|
const displayUsername = getDisplayUsername(message.authorId)
|
||||||
|
|
||||||
NotificationService.showNotification(
|
notification.showNotification(`${displayUsername}: ${message.text}`)
|
||||||
`${displayUsername}: ${message.text}`
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessageLog([...messageLog, { ...message, timeReceived: Date.now() }])
|
setMessageLog([
|
||||||
|
...messageLog,
|
||||||
|
{ ...message, timeReceived: timeService.now() },
|
||||||
|
])
|
||||||
updatePeer(peerId, { isTyping: false })
|
updatePeer(peerId, { isTyping: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -339,9 +342,8 @@ export function useRoom(
|
|||||||
})
|
})
|
||||||
;(async () => {
|
;(async () => {
|
||||||
try {
|
try {
|
||||||
const publicKeyString = await encryptionService.stringifyCryptoKey(
|
const publicKeyString =
|
||||||
publicKey
|
await encryptionService.stringifyCryptoKey(publicKey)
|
||||||
)
|
|
||||||
|
|
||||||
const promises: Promise<any>[] = [
|
const promises: Promise<any>[] = [
|
||||||
sendPeerMetadata({ userId, customUsername, publicKeyString }, peerId),
|
sendPeerMetadata({ userId, customUsername, publicKeyString }, peerId),
|
||||||
@ -396,7 +398,7 @@ export function useRoom(
|
|||||||
const unsentInlineMedia: UnsentInlineMedia = {
|
const unsentInlineMedia: UnsentInlineMedia = {
|
||||||
authorId: userId,
|
authorId: userId,
|
||||||
magnetURI: fileOfferId,
|
magnetURI: fileOfferId,
|
||||||
timeSent: Date.now(),
|
timeSent: timeService.now(),
|
||||||
id: getUuid(),
|
id: getUuid(),
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,7 +409,7 @@ export function useRoom(
|
|||||||
|
|
||||||
setMessageLog([
|
setMessageLog([
|
||||||
...messageLog,
|
...messageLog,
|
||||||
{ ...unsentInlineMedia, timeReceived: Date.now() },
|
{ ...unsentInlineMedia, timeReceived: timeService.now() },
|
||||||
])
|
])
|
||||||
setIsMessageSending(false)
|
setIsMessageSending(false)
|
||||||
}
|
}
|
||||||
@ -433,13 +435,16 @@ export function useRoom(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (userSettings.showNotificationOnNewMessage) {
|
if (userSettings.showNotificationOnNewMessage) {
|
||||||
NotificationService.showNotification(
|
notification.showNotification(
|
||||||
`${getDisplayUsername(inlineMedia.authorId)} shared media`
|
`${getDisplayUsername(inlineMedia.authorId)} shared media`
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setMessageLog([...messageLog, { ...inlineMedia, timeReceived: Date.now() }])
|
setMessageLog([
|
||||||
|
...messageLog,
|
||||||
|
{ ...inlineMedia, timeReceived: timeService.now() },
|
||||||
|
])
|
||||||
})
|
})
|
||||||
|
|
||||||
receiveTypingStatusChange((typingStatus, peerId) => {
|
receiveTypingStatusChange((typingStatus, peerId) => {
|
||||||
@ -449,9 +454,8 @@ export function useRoom(
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
;(async () => {
|
;(async () => {
|
||||||
const publicKeyString = await encryptionService.stringifyCryptoKey(
|
const publicKeyString =
|
||||||
publicKey
|
await encryptionService.stringifyCryptoKey(publicKey)
|
||||||
)
|
|
||||||
|
|
||||||
sendPeerMetadata({
|
sendPeerMetadata({
|
||||||
customUsername,
|
customUsername,
|
||||||
|
@ -2,8 +2,14 @@ import { useContext, useEffect, useCallback, useState } from 'react'
|
|||||||
|
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { PeerActions } from 'models/network'
|
import { PeerActions } from 'models/network'
|
||||||
import { AudioState, Peer } from 'models/chat'
|
import {
|
||||||
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
|
AudioState,
|
||||||
|
Peer,
|
||||||
|
AudioChannelName,
|
||||||
|
PeerAudioChannelState,
|
||||||
|
StreamType,
|
||||||
|
} from 'models/chat'
|
||||||
|
import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
|
||||||
|
|
||||||
interface UseRoomAudioConfig {
|
interface UseRoomAudioConfig {
|
||||||
peerRoom: PeerRoom
|
peerRoom: PeerRoom
|
||||||
@ -19,7 +25,7 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
string | null
|
string | null
|
||||||
>(null)
|
>(null)
|
||||||
|
|
||||||
const { peerList, setPeerList, setAudioState, peerAudios, setPeerAudios } =
|
const { setPeerList, setAudioChannelState, setPeerAudioChannels } =
|
||||||
shellContext
|
shellContext
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -32,29 +38,46 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
})()
|
})()
|
||||||
}, [audioStream])
|
}, [audioStream])
|
||||||
|
|
||||||
const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction<AudioState>(
|
const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction<
|
||||||
PeerActions.AUDIO_CHANGE
|
Partial<PeerAudioChannelState>
|
||||||
)
|
>(PeerActions.AUDIO_CHANGE)
|
||||||
|
|
||||||
receiveAudioChange((audioState, peerId) => {
|
receiveAudioChange((peerAudioChannelState, peerId) => {
|
||||||
const newPeerList = peerList.map(peer => {
|
setPeerList(peerList => {
|
||||||
|
return peerList.map(peer => {
|
||||||
const newPeer: Peer = { ...peer }
|
const newPeer: Peer = { ...peer }
|
||||||
|
|
||||||
if (peer.peerId === peerId) {
|
const microphoneAudioChannel =
|
||||||
newPeer.audioState = audioState
|
peerAudioChannelState[AudioChannelName.MICROPHONE]
|
||||||
|
|
||||||
if (audioState === AudioState.STOPPED) {
|
if (microphoneAudioChannel) {
|
||||||
|
if (peer.peerId === peerId) {
|
||||||
|
newPeer.audioChannelState = {
|
||||||
|
...newPeer.audioChannelState,
|
||||||
|
...peerAudioChannelState,
|
||||||
|
}
|
||||||
|
|
||||||
|
if (microphoneAudioChannel === AudioState.STOPPED) {
|
||||||
deletePeerAudio(peerId)
|
deletePeerAudio(peerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return newPeer
|
return newPeer
|
||||||
})
|
})
|
||||||
|
})
|
||||||
setPeerList(newPeerList)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.onPeerStream(PeerStreamType.AUDIO, (stream, peerId) => {
|
peerRoom.onPeerStream(PeerStreamType.AUDIO, (stream, peerId, metadata) => {
|
||||||
|
if (
|
||||||
|
typeof metadata === 'object' &&
|
||||||
|
metadata !== null &&
|
||||||
|
'type' in metadata &&
|
||||||
|
metadata.type !== StreamType.MICROPHONE
|
||||||
|
) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const audioTracks = stream.getAudioTracks()
|
const audioTracks = stream.getAudioTracks()
|
||||||
|
|
||||||
if (audioTracks.length === 0) return
|
if (audioTracks.length === 0) return
|
||||||
@ -63,7 +86,13 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
audio.srcObject = stream
|
audio.srcObject = stream
|
||||||
audio.autoplay = true
|
audio.autoplay = true
|
||||||
|
|
||||||
setPeerAudios({ ...peerAudios, [peerId]: audio })
|
setPeerAudioChannels(peerAudioChannels => ({
|
||||||
|
...peerAudioChannels,
|
||||||
|
[peerId]: {
|
||||||
|
...peerAudioChannels[peerId],
|
||||||
|
[AudioChannelName.MICROPHONE]: audio,
|
||||||
|
},
|
||||||
|
}))
|
||||||
})
|
})
|
||||||
|
|
||||||
const cleanupAudio = useCallback(() => {
|
const cleanupAudio = useCallback(() => {
|
||||||
@ -86,9 +115,19 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
video: false,
|
video: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.addStream(newSelfStream)
|
peerRoom.addStream(newSelfStream, null, {
|
||||||
sendAudioChange(AudioState.PLAYING)
|
type: StreamType.MICROPHONE,
|
||||||
setAudioState(AudioState.PLAYING)
|
})
|
||||||
|
|
||||||
|
sendAudioChange({
|
||||||
|
[AudioChannelName.MICROPHONE]: AudioState.PLAYING,
|
||||||
|
})
|
||||||
|
|
||||||
|
setAudioChannelState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[AudioChannelName.MICROPHONE]: AudioState.PLAYING,
|
||||||
|
}))
|
||||||
|
|
||||||
setAudioStream(newSelfStream)
|
setAudioStream(newSelfStream)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -96,8 +135,16 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
cleanupAudio()
|
cleanupAudio()
|
||||||
|
|
||||||
peerRoom.removeStream(audioStream, peerRoom.getPeers())
|
peerRoom.removeStream(audioStream, peerRoom.getPeers())
|
||||||
sendAudioChange(AudioState.STOPPED)
|
|
||||||
setAudioState(AudioState.STOPPED)
|
sendAudioChange({
|
||||||
|
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
|
||||||
|
})
|
||||||
|
|
||||||
|
setAudioChannelState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
|
||||||
|
}))
|
||||||
|
|
||||||
setAudioStream(null)
|
setAudioStream(null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -106,11 +153,10 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
audioStream,
|
audioStream,
|
||||||
cleanupAudio,
|
cleanupAudio,
|
||||||
isSpeakingToRoom,
|
isSpeakingToRoom,
|
||||||
peerAudios,
|
|
||||||
peerRoom,
|
peerRoom,
|
||||||
selectedAudioDeviceId,
|
selectedAudioDeviceId,
|
||||||
sendAudioChange,
|
sendAudioChange,
|
||||||
setAudioState,
|
setAudioChannelState,
|
||||||
])
|
])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -139,27 +185,45 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
|
|||||||
video: false,
|
video: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.addStream(newSelfStream)
|
peerRoom.addStream(newSelfStream, null, {
|
||||||
|
type: StreamType.MICROPHONE,
|
||||||
|
})
|
||||||
|
|
||||||
setAudioStream(newSelfStream)
|
setAudioStream(newSelfStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletePeerAudio = (peerId: string) => {
|
const deletePeerAudio = (peerId: string) => {
|
||||||
const newPeerAudios = { ...peerAudios }
|
setPeerAudioChannels(({ ...newPeerAudios }) => {
|
||||||
delete newPeerAudios[peerId]
|
if (!newPeerAudios[peerId]) {
|
||||||
setPeerAudios(newPeerAudios)
|
return newPeerAudios
|
||||||
|
}
|
||||||
|
|
||||||
|
const microphoneAudio = newPeerAudios[peerId][AudioChannelName.MICROPHONE]
|
||||||
|
microphoneAudio?.pause()
|
||||||
|
|
||||||
|
const { [AudioChannelName.MICROPHONE]: _, ...newPeerAudioChannels } =
|
||||||
|
newPeerAudios[peerId]
|
||||||
|
|
||||||
|
newPeerAudios[peerId] = newPeerAudioChannels
|
||||||
|
|
||||||
|
return newPeerAudios
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAudioForNewPeer = (peerId: string) => {
|
const handleAudioForNewPeer = (peerId: string) => {
|
||||||
if (audioStream) {
|
if (audioStream) {
|
||||||
peerRoom.addStream(audioStream, peerId)
|
peerRoom.addStream(audioStream, peerId, {
|
||||||
|
type: StreamType.MICROPHONE,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAudioForLeavingPeer = (peerId: string) => {
|
const handleAudioForLeavingPeer = (peerId: string) => {
|
||||||
if (audioStream) {
|
if (audioStream) {
|
||||||
peerRoom.removeStream(audioStream, peerId)
|
peerRoom.removeStream(audioStream, peerId)
|
||||||
deletePeerAudio(peerId)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
deletePeerAudio(peerId)
|
||||||
}
|
}
|
||||||
|
|
||||||
peerRoom.onPeerJoin(PeerHookType.AUDIO, (peerId: string) => {
|
peerRoom.onPeerJoin(PeerHookType.AUDIO, (peerId: string) => {
|
||||||
|
@ -1,13 +1,12 @@
|
|||||||
import { useContext, useEffect, useState } from 'react'
|
import { useContext, useEffect, useState } from 'react'
|
||||||
|
|
||||||
import { sleep } from 'utils'
|
import { sleep } from 'lib/sleep'
|
||||||
import { RoomContext } from 'contexts/RoomContext'
|
import { RoomContext } from 'contexts/RoomContext'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { PeerActions } from 'models/network'
|
import { PeerActions } from 'models/network'
|
||||||
import { FileOfferMetadata, Peer } from 'models/chat'
|
import { FileOfferMetadata, Peer } from 'models/chat'
|
||||||
import { PeerRoom, PeerHookType } from 'services/PeerRoom'
|
import { PeerRoom, PeerHookType } from 'lib/PeerRoom'
|
||||||
|
import { fileTransfer } from 'lib/FileTransfer'
|
||||||
import { fileTransfer } from 'services/FileTransfer/index'
|
|
||||||
|
|
||||||
interface UseRoomFileShareConfig {
|
interface UseRoomFileShareConfig {
|
||||||
onInlineMediaUpload: (files: File[]) => void
|
onInlineMediaUpload: (files: File[]) => void
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import { useContext, useEffect, useCallback, useState } from 'react'
|
import { useContext, useEffect, useCallback, useState } from 'react'
|
||||||
|
|
||||||
import { isRecord } from 'utils'
|
import { isRecord } from 'lib/type-guards'
|
||||||
import { RoomContext } from 'contexts/RoomContext'
|
import { RoomContext } from 'contexts/RoomContext'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { PeerActions } from 'models/network'
|
import { PeerActions } from 'models/network'
|
||||||
import { ScreenShareState, Peer, VideoStreamType } from 'models/chat'
|
import {
|
||||||
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
|
ScreenShareState,
|
||||||
|
Peer,
|
||||||
|
StreamType,
|
||||||
|
AudioChannelName,
|
||||||
|
AudioState,
|
||||||
|
} from 'models/chat'
|
||||||
|
import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
|
||||||
|
|
||||||
interface UseRoomScreenShareConfig {
|
interface UseRoomScreenShareConfig {
|
||||||
peerRoom: PeerRoom
|
peerRoom: PeerRoom
|
||||||
@ -16,7 +22,13 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
|
|||||||
const roomContext = useContext(RoomContext)
|
const roomContext = useContext(RoomContext)
|
||||||
const [isSharingScreen, setIsSharingScreen] = useState(false)
|
const [isSharingScreen, setIsSharingScreen] = useState(false)
|
||||||
|
|
||||||
const { peerList, setPeerList, setScreenState } = shellContext
|
const {
|
||||||
|
peerList,
|
||||||
|
setPeerList,
|
||||||
|
setScreenState,
|
||||||
|
setAudioChannelState,
|
||||||
|
setPeerAudioChannels,
|
||||||
|
} = shellContext
|
||||||
|
|
||||||
const {
|
const {
|
||||||
peerScreenStreams,
|
peerScreenStreams,
|
||||||
@ -50,7 +62,7 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
|
|||||||
const isScreenShareStream =
|
const isScreenShareStream =
|
||||||
isRecord(metadata) &&
|
isRecord(metadata) &&
|
||||||
'type' in metadata &&
|
'type' in metadata &&
|
||||||
metadata.type === VideoStreamType.SCREEN_SHARE
|
metadata.type === StreamType.SCREEN_SHARE
|
||||||
|
|
||||||
if (!isScreenShareStream) return
|
if (!isScreenShareStream) return
|
||||||
|
|
||||||
@ -58,6 +70,33 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
|
|||||||
...peerScreenStreams,
|
...peerScreenStreams,
|
||||||
[peerId]: stream,
|
[peerId]: stream,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const [audioStream] = stream.getAudioTracks()
|
||||||
|
|
||||||
|
if (audioStream) {
|
||||||
|
setAudioChannelState(prevState => ({
|
||||||
|
...prevState,
|
||||||
|
[AudioChannelName.SCREEN_SHARE]: AudioState.PLAYING,
|
||||||
|
}))
|
||||||
|
|
||||||
|
const audioTracks = stream.getAudioTracks()
|
||||||
|
|
||||||
|
if (audioTracks.length > 0) {
|
||||||
|
const audio = new Audio()
|
||||||
|
audio.srcObject = stream
|
||||||
|
audio.autoplay = true
|
||||||
|
|
||||||
|
setPeerAudioChannels(peerAudioChannels => {
|
||||||
|
return {
|
||||||
|
...peerAudioChannels,
|
||||||
|
[peerId]: {
|
||||||
|
...peerAudioChannels[peerId],
|
||||||
|
[AudioChannelName.SCREEN_SHARE]: audio,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const cleanupScreenStream = useCallback(() => {
|
const cleanupScreenStream = useCallback(() => {
|
||||||
@ -78,8 +117,9 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.addStream(displayMedia, null, {
|
peerRoom.addStream(displayMedia, null, {
|
||||||
type: VideoStreamType.SCREEN_SHARE,
|
type: StreamType.SCREEN_SHARE,
|
||||||
})
|
})
|
||||||
|
|
||||||
setSelfScreenStream(displayMedia)
|
setSelfScreenStream(displayMedia)
|
||||||
sendScreenShare(ScreenShareState.SHARING)
|
sendScreenShare(ScreenShareState.SHARING)
|
||||||
setScreenState(ScreenShareState.SHARING)
|
setScreenState(ScreenShareState.SHARING)
|
||||||
@ -119,15 +159,33 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
|
|||||||
}, [setPeerScreenStreams])
|
}, [setPeerScreenStreams])
|
||||||
|
|
||||||
const deletePeerScreen = (peerId: string) => {
|
const deletePeerScreen = (peerId: string) => {
|
||||||
const newPeerScreens = { ...peerScreenStreams }
|
setPeerScreenStreams(({ [peerId]: _, ...newPeerScreens }) => {
|
||||||
delete newPeerScreens[peerId]
|
return newPeerScreens
|
||||||
setPeerScreenStreams(newPeerScreens)
|
})
|
||||||
|
|
||||||
|
setPeerAudioChannels(({ ...newPeerAudios }) => {
|
||||||
|
if (!newPeerAudios[peerId]) {
|
||||||
|
return newPeerAudios
|
||||||
|
}
|
||||||
|
|
||||||
|
const screenShareAudio =
|
||||||
|
newPeerAudios[peerId][AudioChannelName.SCREEN_SHARE]
|
||||||
|
|
||||||
|
screenShareAudio?.pause()
|
||||||
|
|
||||||
|
const { [AudioChannelName.SCREEN_SHARE]: _, ...newPeerAudioChannels } =
|
||||||
|
newPeerAudios[peerId]
|
||||||
|
|
||||||
|
newPeerAudios[peerId] = newPeerAudioChannels
|
||||||
|
|
||||||
|
return newPeerAudios
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleScreenForNewPeer = (peerId: string) => {
|
const handleScreenForNewPeer = (peerId: string) => {
|
||||||
if (selfScreenStream) {
|
if (selfScreenStream) {
|
||||||
peerRoom.addStream(selfScreenStream, peerId, {
|
peerRoom.addStream(selfScreenStream, peerId, {
|
||||||
type: VideoStreamType.SCREEN_SHARE,
|
type: StreamType.SCREEN_SHARE,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,10 +3,9 @@ import { useContext, useEffect, useCallback, useState } from 'react'
|
|||||||
import { RoomContext } from 'contexts/RoomContext'
|
import { RoomContext } from 'contexts/RoomContext'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { PeerActions } from 'models/network'
|
import { PeerActions } from 'models/network'
|
||||||
import { VideoState, Peer, VideoStreamType } from 'models/chat'
|
import { VideoState, Peer, StreamType } from 'models/chat'
|
||||||
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
|
import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
|
||||||
|
import { isRecord } from 'lib/type-guards'
|
||||||
import { isRecord } from 'utils'
|
|
||||||
|
|
||||||
interface UseRoomVideoConfig {
|
interface UseRoomVideoConfig {
|
||||||
peerRoom: PeerRoom
|
peerRoom: PeerRoom
|
||||||
@ -61,8 +60,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.addStream(newSelfStream, null, {
|
peerRoom.addStream(newSelfStream, null, {
|
||||||
type: VideoStreamType.WEBCAM,
|
type: StreamType.WEBCAM,
|
||||||
})
|
})
|
||||||
|
|
||||||
setSelfVideoStream(newSelfStream)
|
setSelfVideoStream(newSelfStream)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
@ -94,7 +94,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
|||||||
const isWebcamStream =
|
const isWebcamStream =
|
||||||
isRecord(metadata) &&
|
isRecord(metadata) &&
|
||||||
'type' in metadata &&
|
'type' in metadata &&
|
||||||
metadata.type === VideoStreamType.WEBCAM
|
metadata.type === StreamType.WEBCAM
|
||||||
|
|
||||||
if (!isWebcamStream) return
|
if (!isWebcamStream) return
|
||||||
|
|
||||||
@ -125,8 +125,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.addStream(newSelfStream, null, {
|
peerRoom.addStream(newSelfStream, null, {
|
||||||
type: VideoStreamType.WEBCAM,
|
type: StreamType.WEBCAM,
|
||||||
})
|
})
|
||||||
|
|
||||||
sendVideoChange(VideoState.PLAYING)
|
sendVideoChange(VideoState.PLAYING)
|
||||||
setVideoState(VideoState.PLAYING)
|
setVideoState(VideoState.PLAYING)
|
||||||
setSelfVideoStream(newSelfStream)
|
setSelfVideoStream(newSelfStream)
|
||||||
@ -194,7 +195,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
peerRoom.addStream(newSelfStream, null, { type: VideoStreamType.WEBCAM })
|
peerRoom.addStream(newSelfStream, null, { type: StreamType.WEBCAM })
|
||||||
setSelfVideoStream(newSelfStream)
|
setSelfVideoStream(newSelfStream)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +208,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
|
|||||||
const handleVideoForNewPeer = (peerId: string) => {
|
const handleVideoForNewPeer = (peerId: string) => {
|
||||||
if (selfVideoStream) {
|
if (selfVideoStream) {
|
||||||
peerRoom.addStream(selfVideoStream, peerId, {
|
peerRoom.addStream(selfVideoStream, peerId, {
|
||||||
type: VideoStreamType.WEBCAM,
|
type: StreamType.WEBCAM,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,7 @@ import Circle from '@mui/icons-material/FiberManualRecord'
|
|||||||
import { Box } from '@mui/system'
|
import { Box } from '@mui/system'
|
||||||
import ReportIcon from '@mui/icons-material/Report'
|
import ReportIcon from '@mui/icons-material/Report'
|
||||||
|
|
||||||
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
|
import { TrackerConnection } from 'lib/ConnectionTest'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
|
|
||||||
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'
|
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'
|
||||||
|
@ -145,7 +145,9 @@ export const Drawer = ({ isDrawerOpen, onDrawerClose, theme }: DrawerProps) => {
|
|||||||
<MuiLink
|
<MuiLink
|
||||||
target="_blank"
|
target="_blank"
|
||||||
rel="noopener"
|
rel="noopener"
|
||||||
href={`${process.env.REACT_APP_GITHUB_REPO}/commit/${commit.hash}`}
|
href={`${import.meta.env.VITE_GITHUB_REPO}/commit/${
|
||||||
|
commit.hash
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{commit.shortHash}
|
{commit.shortHash}
|
||||||
</MuiLink>
|
</MuiLink>
|
||||||
|
@ -12,7 +12,7 @@ const { isSecureContext, RTCDataChannel } = window
|
|||||||
const doesSupportWebRtc = RTCDataChannel !== undefined
|
const doesSupportWebRtc = RTCDataChannel !== undefined
|
||||||
|
|
||||||
export const isEnvironmentSupported =
|
export const isEnvironmentSupported =
|
||||||
(isSecureContext && doesSupportWebRtc) || process.env.NODE_ENV === 'test'
|
(isSecureContext && doesSupportWebRtc) || import.meta.env.MODE === 'test'
|
||||||
|
|
||||||
export const EnvironmentUnsupportedDialog = () => {
|
export const EnvironmentUnsupportedDialog = () => {
|
||||||
const theme = useTheme()
|
const theme = useTheme()
|
||||||
@ -37,7 +37,7 @@ export const EnvironmentUnsupportedDialog = () => {
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
Chitchatter is unable to start up. The following issues were detected:
|
RemnantChat is unable to start up. The following issues were detected:
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
<Typography
|
<Typography
|
||||||
component="ul"
|
component="ul"
|
||||||
|
@ -2,12 +2,11 @@ import { forwardRef } from 'react'
|
|||||||
import Snackbar from '@mui/material/Snackbar'
|
import Snackbar from '@mui/material/Snackbar'
|
||||||
import MuiAlert, { AlertProps, AlertColor } from '@mui/material/Alert'
|
import MuiAlert, { AlertProps, AlertColor } from '@mui/material/Alert'
|
||||||
|
|
||||||
const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
|
const Alert = forwardRef<HTMLDivElement, AlertProps>(
|
||||||
props,
|
function Alert(props, ref) {
|
||||||
ref
|
|
||||||
) {
|
|
||||||
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />
|
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
interface NotificationAreaProps {
|
interface NotificationAreaProps {
|
||||||
alertSeverity: AlertColor
|
alertSeverity: AlertColor
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
.PeerDownloadFileButton
|
|
||||||
.MuiCircularProgress-circle
|
|
||||||
transition: none !important
|
|
@ -5,12 +5,11 @@ import Tooltip from '@mui/material/Tooltip'
|
|||||||
import Download from '@mui/icons-material/Download'
|
import Download from '@mui/icons-material/Download'
|
||||||
import CircularProgress from '@mui/material/CircularProgress'
|
import CircularProgress from '@mui/material/CircularProgress'
|
||||||
|
|
||||||
import { isError } from 'utils'
|
import { isError } from 'lib/type-guards'
|
||||||
import { fileTransfer } from 'services/FileTransfer/index'
|
import { fileTransfer } from 'lib/FileTransfer'
|
||||||
import { Peer } from 'models/chat'
|
import { Peer } from 'models/chat'
|
||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
|
|
||||||
import './PeerDownloadFileButton.sass'
|
|
||||||
import { usePeerNameDisplay } from 'components/PeerNameDisplay/usePeerNameDisplay'
|
import { usePeerNameDisplay } from 'components/PeerNameDisplay/usePeerNameDisplay'
|
||||||
|
|
||||||
interface PeerDownloadFileButtonProps {
|
interface PeerDownloadFileButtonProps {
|
||||||
@ -65,6 +64,9 @@ export const PeerDownloadFileButton = ({
|
|||||||
<CircularProgress
|
<CircularProgress
|
||||||
variant={downloadProgress === null ? 'indeterminate' : 'determinate'}
|
variant={downloadProgress === null ? 'indeterminate' : 'determinate'}
|
||||||
value={downloadProgress === null ? undefined : downloadProgress}
|
value={downloadProgress === null ? undefined : downloadProgress}
|
||||||
|
sx={{
|
||||||
|
transition: 'none',
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
|
@ -9,9 +9,15 @@ import Box from '@mui/material/Box'
|
|||||||
import CircularProgress from '@mui/material/CircularProgress'
|
import CircularProgress from '@mui/material/CircularProgress'
|
||||||
|
|
||||||
import { UserInfo } from 'components/UserInfo'
|
import { UserInfo } from 'components/UserInfo'
|
||||||
import { AudioState, Peer } from 'models/chat'
|
import {
|
||||||
import { PeerConnectionType } from 'services/PeerRoom'
|
AudioState,
|
||||||
import { TrackerConnection } from 'services/ConnectionTest'
|
Peer,
|
||||||
|
AudioChannel,
|
||||||
|
AudioChannelName,
|
||||||
|
PeerAudioChannelState,
|
||||||
|
} from 'models/chat'
|
||||||
|
import { PeerConnectionType } from 'lib/PeerRoom'
|
||||||
|
import { TrackerConnection } from 'lib/ConnectionTest'
|
||||||
|
|
||||||
import { PeerListHeader } from './PeerListHeader'
|
import { PeerListHeader } from './PeerListHeader'
|
||||||
import { PeerListItem } from './PeerListItem'
|
import { PeerListItem } from './PeerListItem'
|
||||||
@ -25,8 +31,8 @@ export interface PeerListProps extends PropsWithChildren {
|
|||||||
onPeerListClose: () => void
|
onPeerListClose: () => void
|
||||||
peerList: Peer[]
|
peerList: Peer[]
|
||||||
peerConnectionTypes: Record<string, PeerConnectionType>
|
peerConnectionTypes: Record<string, PeerConnectionType>
|
||||||
audioState: AudioState
|
peerAudioChannelState: PeerAudioChannelState
|
||||||
peerAudios: Record<string, HTMLAudioElement>
|
peerAudioChannels: Record<string, AudioChannel>
|
||||||
connectionTestResults: IConnectionTestResults
|
connectionTestResults: IConnectionTestResults
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,8 +42,8 @@ export const PeerList = ({
|
|||||||
onPeerListClose,
|
onPeerListClose,
|
||||||
peerList,
|
peerList,
|
||||||
peerConnectionTypes,
|
peerConnectionTypes,
|
||||||
audioState,
|
peerAudioChannelState,
|
||||||
peerAudios,
|
peerAudioChannels,
|
||||||
connectionTestResults,
|
connectionTestResults,
|
||||||
}: PeerListProps) => {
|
}: PeerListProps) => {
|
||||||
return (
|
return (
|
||||||
@ -49,7 +55,8 @@ export const PeerList = ({
|
|||||||
<Divider />
|
<Divider />
|
||||||
<List>
|
<List>
|
||||||
<ListItem divider={true}>
|
<ListItem divider={true}>
|
||||||
{audioState === AudioState.PLAYING && (
|
{peerAudioChannelState[AudioChannelName.MICROPHONE] ===
|
||||||
|
AudioState.PLAYING && (
|
||||||
<ListItemIcon>
|
<ListItemIcon>
|
||||||
<VolumeUp />
|
<VolumeUp />
|
||||||
</ListItemIcon>
|
</ListItemIcon>
|
||||||
@ -63,7 +70,7 @@ export const PeerList = ({
|
|||||||
key={peer.peerId}
|
key={peer.peerId}
|
||||||
peer={peer}
|
peer={peer}
|
||||||
peerConnectionTypes={peerConnectionTypes}
|
peerConnectionTypes={peerConnectionTypes}
|
||||||
peerAudios={peerAudios}
|
peerAudioChannels={peerAudioChannels}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{peerList.length === 0 &&
|
{peerList.length === 0 &&
|
||||||
|
@ -18,15 +18,20 @@ import EnhancedEncryptionIcon from '@mui/icons-material/EnhancedEncryption'
|
|||||||
import { AudioVolume } from 'components/AudioVolume'
|
import { AudioVolume } from 'components/AudioVolume'
|
||||||
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
import { PeerNameDisplay } from 'components/PeerNameDisplay'
|
||||||
import { PublicKey } from 'components/PublicKey'
|
import { PublicKey } from 'components/PublicKey'
|
||||||
import { Peer, PeerVerificationState } from 'models/chat'
|
import {
|
||||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
Peer,
|
||||||
|
AudioChannel,
|
||||||
|
AudioChannelName,
|
||||||
|
PeerVerificationState,
|
||||||
|
} from 'models/chat'
|
||||||
|
import { PeerConnectionType } from 'lib/PeerRoom'
|
||||||
|
|
||||||
import { PeerDownloadFileButton } from './PeerDownloadFileButton'
|
import { PeerDownloadFileButton } from './PeerDownloadFileButton'
|
||||||
|
|
||||||
interface PeerListItemProps {
|
interface PeerListItemProps {
|
||||||
peer: Peer
|
peer: Peer
|
||||||
peerConnectionTypes: Record<string, PeerConnectionType>
|
peerConnectionTypes: Record<string, PeerConnectionType>
|
||||||
peerAudios: Record<string, HTMLAudioElement>
|
peerAudioChannels: Record<string, AudioChannel>
|
||||||
}
|
}
|
||||||
|
|
||||||
const verificationStateDisplayMap = {
|
const verificationStateDisplayMap = {
|
||||||
@ -52,8 +57,8 @@ const iconRightPadding = 1
|
|||||||
export const PeerListItem = ({
|
export const PeerListItem = ({
|
||||||
peer,
|
peer,
|
||||||
peerConnectionTypes,
|
peerConnectionTypes,
|
||||||
peerAudios,
|
peerAudioChannels,
|
||||||
}: PeerListItemProps): JSX.Element => {
|
}: PeerListItemProps) => {
|
||||||
const [showPeerDialog, setShowPeerDialog] = useState(false)
|
const [showPeerDialog, setShowPeerDialog] = useState(false)
|
||||||
|
|
||||||
const hasPeerConnection = peer.peerId in peerConnectionTypes
|
const hasPeerConnection = peer.peerId in peerConnectionTypes
|
||||||
@ -69,6 +74,11 @@ export const PeerListItem = ({
|
|||||||
setShowPeerDialog(false)
|
setShowPeerDialog(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const microphoneAudio =
|
||||||
|
peerAudioChannels[peer.peerId]?.[AudioChannelName.MICROPHONE]
|
||||||
|
const screenShareAudio =
|
||||||
|
peerAudioChannels[peer.peerId]?.[AudioChannelName.SCREEN_SHARE]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListItem key={peer.peerId} divider={true}>
|
<ListItem key={peer.peerId} divider={true}>
|
||||||
@ -124,8 +134,17 @@ export const PeerListItem = ({
|
|||||||
</Box>
|
</Box>
|
||||||
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
|
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
|
||||||
</Box>
|
</Box>
|
||||||
{peer.peerId in peerAudios && (
|
{microphoneAudio && (
|
||||||
<AudioVolume audioEl={peerAudios[peer.peerId]} />
|
<AudioVolume
|
||||||
|
audioEl={microphoneAudio}
|
||||||
|
audioChannelName={AudioChannelName.MICROPHONE}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{screenShareAudio && (
|
||||||
|
<AudioVolume
|
||||||
|
audioEl={screenShareAudio}
|
||||||
|
audioChannelName={AudioChannelName.SCREEN_SHARE}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</ListItemText>
|
</ListItemText>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
@ -14,8 +14,8 @@ import CloseIcon from '@mui/icons-material/Close'
|
|||||||
|
|
||||||
import { AlertOptions } from 'models/shell'
|
import { AlertOptions } from 'models/shell'
|
||||||
import { useEffect, useState, SyntheticEvent } from 'react'
|
import { useEffect, useState, SyntheticEvent } from 'react'
|
||||||
import { sleep } from 'utils'
|
import { sleep } from 'lib/sleep'
|
||||||
import { encryptionService } from 'services/Encryption'
|
import { encryption } from 'services/Encryption'
|
||||||
|
|
||||||
export interface RoomShareDialogProps {
|
export interface RoomShareDialogProps {
|
||||||
isOpen: boolean
|
isOpen: boolean
|
||||||
@ -51,10 +51,7 @@ export function RoomShareDialog(props: RoomShareDialogProps) {
|
|||||||
const url = window.location.href.split('#')[0]
|
const url = window.location.href.split('#')[0]
|
||||||
|
|
||||||
const copyWithPass = async () => {
|
const copyWithPass = async () => {
|
||||||
const encoded = await encryptionService.encodePassword(
|
const encoded = await encryption.encodePassword(props.roomId, password)
|
||||||
props.roomId,
|
|
||||||
password
|
|
||||||
)
|
|
||||||
|
|
||||||
if (encoded === props.password) {
|
if (encoded === props.password) {
|
||||||
const params = new URLSearchParams()
|
const params = new URLSearchParams()
|
||||||
|
@ -35,7 +35,7 @@ describe('Shell', () => {
|
|||||||
userEvent.click(menuButton)
|
userEvent.click(menuButton)
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigation = screen.getByRole('navigation')
|
const navigation = screen.getByLabelText('Navigation menu')
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(navigation).toBeVisible()
|
expect(navigation).toBeVisible()
|
||||||
@ -56,7 +56,7 @@ describe('Shell', () => {
|
|||||||
userEvent.click(closeMenu)
|
userEvent.click(closeMenu)
|
||||||
})
|
})
|
||||||
|
|
||||||
const navigation = screen.getByRole('navigation')
|
const navigation = screen.getByLabelText('Navigation menu')
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
expect(navigation).not.toBeVisible()
|
expect(navigation).not.toBeVisible()
|
||||||
|
@ -8,7 +8,7 @@ import {
|
|||||||
useState,
|
useState,
|
||||||
} from 'react'
|
} from 'react'
|
||||||
import CssBaseline from '@mui/material/CssBaseline'
|
import CssBaseline from '@mui/material/CssBaseline'
|
||||||
import { ThemeProvider, createTheme } from '@mui/material/styles'
|
import { ThemeProvider } from '@mui/material/styles'
|
||||||
import Box from '@mui/material/Box'
|
import Box from '@mui/material/Box'
|
||||||
import Typography from '@mui/material/Typography'
|
import Typography from '@mui/material/Typography'
|
||||||
import { AlertColor } from '@mui/material/Alert'
|
import { AlertColor } from '@mui/material/Alert'
|
||||||
@ -19,10 +19,17 @@ import { useWindowSize } from '@react-hook/window-size'
|
|||||||
import { ShellContext } from 'contexts/ShellContext'
|
import { ShellContext } from 'contexts/ShellContext'
|
||||||
import { SettingsContext } from 'contexts/SettingsContext'
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
import { AlertOptions, QueryParamKeys } from 'models/shell'
|
import { AlertOptions, QueryParamKeys } from 'models/shell'
|
||||||
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
|
import {
|
||||||
|
AudioState,
|
||||||
|
ScreenShareState,
|
||||||
|
VideoState,
|
||||||
|
Peer,
|
||||||
|
AudioChannel,
|
||||||
|
PeerAudioChannelState,
|
||||||
|
AudioChannelName,
|
||||||
|
} from 'models/chat'
|
||||||
import { ErrorBoundary } from 'components/ErrorBoundary'
|
import { ErrorBoundary } from 'components/ErrorBoundary'
|
||||||
|
import { PeerConnectionType } from 'lib/PeerRoom'
|
||||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
|
||||||
|
|
||||||
import { Drawer } from './Drawer'
|
import { Drawer } from './Drawer'
|
||||||
import { UpgradeDialog } from './UpgradeDialog'
|
import { UpgradeDialog } from './UpgradeDialog'
|
||||||
@ -38,6 +45,7 @@ import {
|
|||||||
EnvironmentUnsupportedDialog,
|
EnvironmentUnsupportedDialog,
|
||||||
isEnvironmentSupported,
|
isEnvironmentSupported,
|
||||||
} from './EnvironmentUnsupportedDialog'
|
} from './EnvironmentUnsupportedDialog'
|
||||||
|
import { useShellTheme } from './useShellTheme'
|
||||||
|
|
||||||
export interface ShellProps extends PropsWithChildren {
|
export interface ShellProps extends PropsWithChildren {
|
||||||
userPeerId: string
|
userPeerId: string
|
||||||
@ -50,17 +58,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
const { getUserSettings, updateUserSettings } = useContext(SettingsContext)
|
const { getUserSettings, updateUserSettings } = useContext(SettingsContext)
|
||||||
const isEmbedded = queryParams.get(QueryParamKeys.IS_EMBEDDED) !== null
|
const isEmbedded = queryParams.get(QueryParamKeys.IS_EMBEDDED) !== null
|
||||||
|
|
||||||
const { colorMode } = getUserSettings()
|
const theme = useShellTheme()
|
||||||
|
|
||||||
const theme = useMemo(
|
|
||||||
() =>
|
|
||||||
createTheme({
|
|
||||||
palette: {
|
|
||||||
mode: colorMode,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
[colorMode]
|
|
||||||
)
|
|
||||||
|
|
||||||
const [windowWidth] = useWindowSize()
|
const [windowWidth] = useWindowSize()
|
||||||
const defaultSidebarsOpen = windowWidth >= theme.breakpoints.values.lg
|
const defaultSidebarsOpen = windowWidth >= theme.breakpoints.values.lg
|
||||||
@ -87,7 +85,11 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
Record<string, PeerConnectionType>
|
Record<string, PeerConnectionType>
|
||||||
>({})
|
>({})
|
||||||
const [tabHasFocus, setTabHasFocus] = useState(true)
|
const [tabHasFocus, setTabHasFocus] = useState(true)
|
||||||
const [audioState, setAudioState] = useState<AudioState>(AudioState.STOPPED)
|
const [audioChannelState, setAudioChannelState] =
|
||||||
|
useState<PeerAudioChannelState>({
|
||||||
|
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
|
||||||
|
[AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED,
|
||||||
|
})
|
||||||
const [videoState, setVideoState] = useState<VideoState>(VideoState.STOPPED)
|
const [videoState, setVideoState] = useState<VideoState>(VideoState.STOPPED)
|
||||||
const [screenState, setScreenState] = useState<ScreenShareState>(
|
const [screenState, setScreenState] = useState<ScreenShareState>(
|
||||||
ScreenShareState.NOT_SHARING
|
ScreenShareState.NOT_SHARING
|
||||||
@ -95,8 +97,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
const [customUsername, setCustomUsername] = useState(
|
const [customUsername, setCustomUsername] = useState(
|
||||||
getUserSettings().customUsername
|
getUserSettings().customUsername
|
||||||
)
|
)
|
||||||
const [peerAudios, setPeerAudios] = useState<
|
const [peerAudioChannels, setPeerAudioChannels] = useState<
|
||||||
Record<string, HTMLAudioElement>
|
Record<string, AudioChannel>
|
||||||
>({})
|
>({})
|
||||||
|
|
||||||
const showAlert = useCallback((message: string, options?: AlertOptions) => {
|
const showAlert = useCallback((message: string, options?: AlertOptions) => {
|
||||||
@ -145,14 +147,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
setIsServerConnectionFailureDialogOpen,
|
setIsServerConnectionFailureDialogOpen,
|
||||||
peerConnectionTypes,
|
peerConnectionTypes,
|
||||||
setPeerConnectionTypes,
|
setPeerConnectionTypes,
|
||||||
audioState,
|
audioChannelState,
|
||||||
setAudioState,
|
setAudioChannelState,
|
||||||
videoState,
|
videoState,
|
||||||
setVideoState,
|
setVideoState,
|
||||||
screenState,
|
screenState,
|
||||||
setScreenState,
|
setScreenState,
|
||||||
peerAudios,
|
peerAudioChannels,
|
||||||
setPeerAudios,
|
setPeerAudioChannels,
|
||||||
customUsername,
|
customUsername,
|
||||||
setCustomUsername,
|
setCustomUsername,
|
||||||
connectionTestResults,
|
connectionTestResults,
|
||||||
@ -175,14 +177,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
setShowRoomControls,
|
setShowRoomControls,
|
||||||
setTitle,
|
setTitle,
|
||||||
showAlert,
|
showAlert,
|
||||||
audioState,
|
audioChannelState,
|
||||||
setAudioState,
|
setAudioChannelState,
|
||||||
videoState,
|
videoState,
|
||||||
setVideoState,
|
setVideoState,
|
||||||
screenState,
|
screenState,
|
||||||
setScreenState,
|
setScreenState,
|
||||||
peerAudios,
|
peerAudioChannels,
|
||||||
setPeerAudios,
|
setPeerAudioChannels,
|
||||||
customUsername,
|
customUsername,
|
||||||
setCustomUsername,
|
setCustomUsername,
|
||||||
connectionTestResults,
|
connectionTestResults,
|
||||||
@ -330,7 +332,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
<>
|
<>
|
||||||
<UpgradeDialog appNeedsUpdate={appNeedsUpdate} />
|
<UpgradeDialog appNeedsUpdate={appNeedsUpdate} />
|
||||||
<Box
|
<Box
|
||||||
className="Chitchatter"
|
className="RemnantChat"
|
||||||
sx={{
|
sx={{
|
||||||
height: '100vh',
|
height: '100vh',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
@ -394,8 +396,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
onPeerListClose={handlePeerListClick}
|
onPeerListClose={handlePeerListClick}
|
||||||
peerList={peerList}
|
peerList={peerList}
|
||||||
peerConnectionTypes={peerConnectionTypes}
|
peerConnectionTypes={peerConnectionTypes}
|
||||||
audioState={audioState}
|
peerAudioChannelState={audioChannelState}
|
||||||
peerAudios={peerAudios}
|
peerAudioChannels={peerAudioChannels}
|
||||||
connectionTestResults={connectionTestResults}
|
connectionTestResults={connectionTestResults}
|
||||||
/>
|
/>
|
||||||
{isEmbedded ? (
|
{isEmbedded ? (
|
||||||
@ -408,10 +410,10 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
|
|||||||
>
|
>
|
||||||
This conversation is powered by{' '}
|
This conversation is powered by{' '}
|
||||||
<Link
|
<Link
|
||||||
href="https://github.com/jeremyckahn/chitchatter"
|
href="https://githaven.org/shiloh/remnantchat"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Chitchatter
|
RemnantChat
|
||||||
</Link>
|
</Link>
|
||||||
</Typography>
|
</Typography>
|
||||||
) : null}
|
) : null}
|
||||||
|
@ -6,14 +6,17 @@ import DialogContent from '@mui/material/DialogContent'
|
|||||||
import DialogContentText from '@mui/material/DialogContentText'
|
import DialogContentText from '@mui/material/DialogContentText'
|
||||||
import DialogTitle from '@mui/material/DialogTitle'
|
import DialogTitle from '@mui/material/DialogTitle'
|
||||||
import WarningIcon from '@mui/icons-material/Warning'
|
import WarningIcon from '@mui/icons-material/Warning'
|
||||||
|
import { useRegisterSW } from 'virtual:pwa-register/react'
|
||||||
|
|
||||||
interface UpgradeDialogProps {
|
interface UpgradeDialogProps {
|
||||||
appNeedsUpdate: boolean
|
appNeedsUpdate: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const UpgradeDialog = ({ appNeedsUpdate }: UpgradeDialogProps) => {
|
export const UpgradeDialog = ({ appNeedsUpdate }: UpgradeDialogProps) => {
|
||||||
|
const { updateServiceWorker } = useRegisterSW()
|
||||||
|
|
||||||
const handleRestartClick = () => {
|
const handleRestartClick = () => {
|
||||||
window.location.reload()
|
updateServiceWorker(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -36,7 +39,7 @@ export const UpgradeDialog = ({ appNeedsUpdate }: UpgradeDialogProps) => {
|
|||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogContentText id="alert-dialog-description">
|
<DialogContentText id="alert-dialog-description">
|
||||||
In order to function properly, Chitchatter needs to be updated. The
|
In order to function properly, RemnantChat needs to be updated. The
|
||||||
update has already been installed in the background. All you need to
|
update has already been installed in the background. All you need to
|
||||||
do is reload the page or click "Refresh" below.
|
do is reload the page or click "Refresh" below.
|
||||||
</DialogContentText>
|
</DialogContentText>
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { sleep } from 'utils'
|
|
||||||
|
import { sleep } from 'lib/sleep'
|
||||||
import {
|
import {
|
||||||
ConnectionTest,
|
ConnectionTest,
|
||||||
ConnectionTestEvent,
|
ConnectionTestEvent,
|
||||||
ConnectionTestEvents,
|
ConnectionTestEvents,
|
||||||
TrackerConnection,
|
TrackerConnection,
|
||||||
} from 'services/ConnectionTest/ConnectionTest'
|
} from 'lib/ConnectionTest'
|
||||||
|
|
||||||
export interface ConnectionTestResults {
|
export interface ConnectionTestResults {
|
||||||
hasHost: boolean
|
hasHost: boolean
|
||||||
|
23
src/components/Shell/useShellTheme.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { SettingsContext } from 'contexts/SettingsContext'
|
||||||
|
import { useContext, useMemo } from 'react'
|
||||||
|
import { createTheme } from '@mui/material/styles'
|
||||||
|
|
||||||
|
export const useShellTheme = () => {
|
||||||
|
const { getUserSettings } = useContext(SettingsContext)
|
||||||
|
const { colorMode } = getUserSettings()
|
||||||
|
|
||||||
|
const theme = useMemo(
|
||||||
|
() =>
|
||||||
|
// NOTE: You can make theme customizations here. It is recommended to use
|
||||||
|
// the default theme viewer as a reference:
|
||||||
|
// https://mui.com/material-ui/customization/default-theme/
|
||||||
|
createTheme({
|
||||||
|
palette: {
|
||||||
|
mode: colorMode,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
[colorMode]
|
||||||
|
)
|
||||||
|
|
||||||
|
return theme
|
||||||
|
}
|
1
src/config/communityRooms.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const communityRoomNames = ['General', 'Prayer']
|
@ -9,5 +9,5 @@ export enum routes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const homepageUrl = new URL(
|
export const homepageUrl = new URL(
|
||||||
process.env.REACT_APP_HOMEPAGE ?? 'https://chitchatter.im/'
|
process.env.REACT_APP_HOMEPAGE ?? 'https://remnant.chat/'
|
||||||
)
|
)
|
||||||
|
@ -7,26 +7,36 @@ export const rtcConfig: RTCConfiguration = {
|
|||||||
// connection cannot be made. Feel free to change them as you'd like. If you
|
// connection cannot be made. Feel free to change them as you'd like. If you
|
||||||
// would like to disable relay servers entirely, remove the `iceServers`
|
// would like to disable relay servers entirely, remove the `iceServers`
|
||||||
// property from the rtcConfig object. IF YOU DISABLE RELAY SERVERS,
|
// property from the rtcConfig object. IF YOU DISABLE RELAY SERVERS,
|
||||||
// CHITCHATTER PEERS MAY NOT BE ABLE TO CONNECT DEPENDING ON HOW THEY ARE
|
// REMNANTCHAT PEERS MAY NOT BE ABLE TO CONNECT DEPENDING ON HOW THEY ARE
|
||||||
// CONNECTED TO THE INTERNET.
|
// CONNECTED TO THE INTERNET.
|
||||||
iceServers: [
|
iceServers: [
|
||||||
{
|
{
|
||||||
urls: 'stun:188.148.133.173:3478',
|
urls: 'turn:relay1.expressturn.com:3478',
|
||||||
|
username: 'efQUQ79N77B5BNVVKF',
|
||||||
|
credential: 'N4EAUgpjMzPLrxSS',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urls: 'turn:188.148.133.173:3478',
|
urls: 'stun:stun.relay.metered.ca:80',
|
||||||
username: 'c386d75b5633456cb3bc13812858098d',
|
|
||||||
credential: '58fd06d85fe14c0f9f46220748b0f565',
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urls: 'turn:188.148.133.173:3478',
|
urls: 'turn:global.relay.metered.ca:80',
|
||||||
username: '0e2f563eacfd4c4a82ea239b04d1d494',
|
username: 'cfd3dccc1b9162a136a80482',
|
||||||
credential: '8179b4b533f240ad9fe590663bef1bc9',
|
credential: '1TgyKEd7B6iUxIn9',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
urls: 'turn:188.148.133.173:3478',
|
urls: 'turn:global.relay.metered.ca:80?transport=tcp',
|
||||||
username: 'feab95c3fcd147a2a96a3d3590bf9cda',
|
username: 'cfd3dccc1b9162a136a80482',
|
||||||
credential: '654cafd885424b7fb974e65f631f25f9',
|
credential: '1TgyKEd7B6iUxIn9',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urls: 'turn:global.relay.metered.ca:443',
|
||||||
|
username: 'cfd3dccc1b9162a136a80482',
|
||||||
|
credential: '1TgyKEd7B6iUxIn9',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
urls: 'turns:global.relay.metered.ca:443?transport=tcp',
|
||||||
|
username: 'cfd3dccc1b9162a136a80482',
|
||||||
|
credential: '1TgyKEd7B6iUxIn9',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
export const streamSaverUrl =
|
export const streamSaverUrl =
|
||||||
process.env.REACT_APP_STREAMSAVER_URL ??
|
import.meta.env.VITE_STREAMSAVER_URL ??
|
||||||
// If you would like to host your own Chitchatter instance with an
|
// If you would like to host your own Chitchatter instance with an
|
||||||
// alternative StreamSaver fork to facilitate file sharing, change this
|
// alternative StreamSaver fork to facilitate file sharing, change this
|
||||||
// string to its respective .mitm.html URL.
|
// string to its respective .mitm.html URL.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
let trackerUrls: string[] | undefined = [
|
let trackerUrls: string[] | undefined = [
|
||||||
// If you would like to host your own Chitchatter instance with alternative
|
// If you would like to host your own RemnantChat instance with alternative
|
||||||
// WebTorrent trackers to connect peers, add them to this array. This array
|
// WebTorrent trackers to connect peers, add them to this array. This array
|
||||||
// gets provided to Trystero as the `trackerUrls` configuration option:
|
// gets provided to Trystero as the `trackerUrls` configuration option:
|
||||||
// https://github.com/dmotz/trystero#joinroomconfig-namespace
|
// https://github.com/dmotz/trystero#joinroomconfig-namespace
|
||||||
@ -8,12 +8,12 @@ let trackerUrls: string[] | undefined = [
|
|||||||
// https://github.com/dmotz/trystero/blob/694f49974974cc9df8b621db09215d6df10fad09/src/torrent.js#L27-L33
|
// https://github.com/dmotz/trystero/blob/694f49974974cc9df8b621db09215d6df10fad09/src/torrent.js#L27-L33
|
||||||
]
|
]
|
||||||
|
|
||||||
// If a tracker URL has been provided via the REACT_APP_TRACKER_URL environment
|
// If a tracker URL has been provided via the VITE_TRACKER_URL environment
|
||||||
// variable, prioritize using it. This is mainly relevant for local development
|
// variable, prioritize using it. This is mainly relevant for local development
|
||||||
// when using the `npm run dev` script. If you are hosting your own Chitchatter
|
// when using the `npm run dev` script. If you are hosting your own RemnantChat
|
||||||
// instance, consider populating the trackerUrls above instead.
|
// instance, consider populating the trackerUrls above instead.
|
||||||
if (process.env.REACT_APP_TRACKER_URL) {
|
if (import.meta.env.VITE_TRACKER_URL) {
|
||||||
trackerUrls.unshift(process.env.REACT_APP_TRACKER_URL)
|
trackerUrls.unshift(import.meta.env.VITE_TRACKER_URL)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no tracker URL overrides have been provided, set trackerUrls to undefined
|
// If no tracker URL overrides have been provided, set trackerUrls to undefined
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
|
|
||||||
import { ColorMode, UserSettings } from 'models/settings'
|
import { ColorMode, UserSettings } from 'models/settings'
|
||||||
import { encryptionService } from 'services/Encryption'
|
import { encryption } from 'services/Encryption'
|
||||||
|
|
||||||
export interface SettingsContextProps {
|
export interface SettingsContextProps {
|
||||||
updateUserSettings: (settings: Partial<UserSettings>) => Promise<void>
|
updateUserSettings: (settings: Partial<UserSettings>) => Promise<void>
|
||||||
@ -17,7 +17,7 @@ export const SettingsContext = createContext<SettingsContextProps>({
|
|||||||
playSoundOnNewMessage: true,
|
playSoundOnNewMessage: true,
|
||||||
showNotificationOnNewMessage: true,
|
showNotificationOnNewMessage: true,
|
||||||
showActiveTypingStatus: true,
|
showActiveTypingStatus: true,
|
||||||
publicKey: encryptionService.cryptoKeyStub,
|
publicKey: encryption.cryptoKeyStub,
|
||||||
privateKey: encryptionService.cryptoKeyStub,
|
privateKey: encryption.cryptoKeyStub,
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
|
@ -1,10 +1,18 @@
|
|||||||
import { createContext, Dispatch, SetStateAction } from 'react'
|
import { createContext, Dispatch, SetStateAction } from 'react'
|
||||||
|
|
||||||
import { AlertOptions } from 'models/shell'
|
import { AlertOptions } from 'models/shell'
|
||||||
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
|
import {
|
||||||
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
|
AudioState,
|
||||||
|
ScreenShareState,
|
||||||
|
VideoState,
|
||||||
|
Peer,
|
||||||
|
AudioChannel,
|
||||||
|
PeerAudioChannelState,
|
||||||
|
AudioChannelName,
|
||||||
|
} from 'models/chat'
|
||||||
|
import { PeerConnectionType } from 'lib/PeerRoom'
|
||||||
import { ConnectionTestResults } from 'components/Shell/useConnectionTest'
|
import { ConnectionTestResults } from 'components/Shell/useConnectionTest'
|
||||||
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
|
import { TrackerConnection } from 'lib/ConnectionTest'
|
||||||
|
|
||||||
interface ShellContextProps {
|
interface ShellContextProps {
|
||||||
isEmbedded: boolean
|
isEmbedded: boolean
|
||||||
@ -27,14 +35,14 @@ interface ShellContextProps {
|
|||||||
setPeerConnectionTypes: Dispatch<
|
setPeerConnectionTypes: Dispatch<
|
||||||
SetStateAction<Record<string, PeerConnectionType>>
|
SetStateAction<Record<string, PeerConnectionType>>
|
||||||
>
|
>
|
||||||
audioState: AudioState
|
audioChannelState: PeerAudioChannelState
|
||||||
setAudioState: Dispatch<SetStateAction<AudioState>>
|
setAudioChannelState: Dispatch<SetStateAction<PeerAudioChannelState>>
|
||||||
videoState: VideoState
|
videoState: VideoState
|
||||||
setVideoState: Dispatch<SetStateAction<VideoState>>
|
setVideoState: Dispatch<SetStateAction<VideoState>>
|
||||||
screenState: ScreenShareState
|
screenState: ScreenShareState
|
||||||
setScreenState: Dispatch<SetStateAction<ScreenShareState>>
|
setScreenState: Dispatch<SetStateAction<ScreenShareState>>
|
||||||
peerAudios: Record<string, HTMLAudioElement>
|
peerAudioChannels: Record<string, AudioChannel>
|
||||||
setPeerAudios: Dispatch<SetStateAction<Record<string, HTMLAudioElement>>>
|
setPeerAudioChannels: Dispatch<SetStateAction<Record<string, AudioChannel>>>
|
||||||
customUsername: string
|
customUsername: string
|
||||||
setCustomUsername: Dispatch<SetStateAction<string>>
|
setCustomUsername: Dispatch<SetStateAction<string>>
|
||||||
connectionTestResults: ConnectionTestResults
|
connectionTestResults: ConnectionTestResults
|
||||||
@ -60,14 +68,17 @@ export const ShellContext = createContext<ShellContextProps>({
|
|||||||
setIsServerConnectionFailureDialogOpen: () => {},
|
setIsServerConnectionFailureDialogOpen: () => {},
|
||||||
peerConnectionTypes: {},
|
peerConnectionTypes: {},
|
||||||
setPeerConnectionTypes: () => {},
|
setPeerConnectionTypes: () => {},
|
||||||
audioState: AudioState.STOPPED,
|
audioChannelState: {
|
||||||
setAudioState: () => {},
|
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
|
||||||
|
[AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED,
|
||||||
|
},
|
||||||
|
setAudioChannelState: () => {},
|
||||||
videoState: VideoState.STOPPED,
|
videoState: VideoState.STOPPED,
|
||||||
setVideoState: () => {},
|
setVideoState: () => {},
|
||||||
screenState: ScreenShareState.NOT_SHARING,
|
screenState: ScreenShareState.NOT_SHARING,
|
||||||
setScreenState: () => {},
|
setScreenState: () => {},
|
||||||
peerAudios: {},
|
peerAudioChannels: {},
|
||||||
setPeerAudios: () => {},
|
setPeerAudioChannels: () => {},
|
||||||
customUsername: '',
|
customUsername: '',
|
||||||
setCustomUsername: () => {},
|
setCustomUsername: () => {},
|
||||||
connectionTestResults: {
|
connectionTestResults: {
|
||||||
|
12
src/img/githaven.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="111.116" height="113.192">
|
||||||
|
<g transform="translate(-17.69 -20.226)">
|
||||||
|
<path fill="#abb2bf" d="M43.197 44.234C17.834 36.212.77 83.012 44.97 81.708l63.995-1.066c31.18-3.757 24.112-54.732-10.714-46.395-4.372 1.047-4.163-10.459-18.76-13.595-10.377-2.23-31.074 4.1-36.294 23.582Z"/>
|
||||||
|
<ellipse cx="60.447" cy="53.457" fill="#fff" rx="4.576" ry="6.554"/>
|
||||||
|
<ellipse cx="88.011" cy="53.81" fill="#fff" rx="4.576" ry="6.554"/>
|
||||||
|
<path stroke="#98c379" stroke-linecap="round" stroke-linejoin="round" stroke-width="5" d="m74.506 85.27-.55 32.67"/>
|
||||||
|
<path fill="none" stroke="#98c379" stroke-linecap="round" stroke-linejoin="round" stroke-width="5" d="M56.42 86.168c.824 24.124-3.143 19.799-16.234 19.568m51.48-20.137c-.824 24.124 3.143 19.799 16.234 19.568"/>
|
||||||
|
<ellipse cx="34.197" cy="105.935" fill="none" stroke="#98c379" stroke-linecap="round" stroke-linejoin="round" stroke-width="5" rx="5.831" ry="6.122"/>
|
||||||
|
<ellipse cx="74.147" cy="124.797" fill="none" stroke="#98c379" stroke-linecap="round" stroke-linejoin="round" stroke-width="5" rx="5.831" ry="6.122"/>
|
||||||
|
<ellipse cx="115.998" cy="104.792" fill="none" stroke="#98c379" stroke-linecap="round" stroke-linejoin="round" stroke-width="5" rx="5.831" ry="6.122"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
241
src/img/logo.svg
@ -1,238 +1,9 @@
|
|||||||
<svg
|
<svg xmlns="http://www.w3.org/2000/svg" width="230.66" height="230.66">
|
||||||
width="100%"
|
<g stroke="#fff" transform="translate(-35.2 -24.467)">
|
||||||
zoomAndPan="magnify"
|
<circle cx="135.53" cy="124.796" r="92.163" fill="none" stroke-width="16.334"/>
|
||||||
viewBox="0 0 354.64453 128.97656"
|
<g stroke-width="10">
|
||||||
height="unset"
|
<path fill="#00bcf3" d="m104.967 121.955 61.125 61.434m0-61.434-61.125 61.434"/>
|
||||||
preserveAspectRatio="xMidYMid"
|
<path fill="none" d="M135.448 191.103V69.043c-.033-6.933 25.967-7.86 26.01 0v22.159"/>
|
||||||
version="1.0"
|
|
||||||
id="svg110">
|
|
||||||
<defs
|
|
||||||
id="defs10">
|
|
||||||
<clipPath
|
|
||||||
id="3bacd8e954">
|
|
||||||
<path
|
|
||||||
d="M 11.863281,122.92969 H 113.10938 v 101.0625 H 11.863281 Z m 0,0"
|
|
||||||
clip-rule="nonzero"
|
|
||||||
id="path4" />
|
|
||||||
</clipPath>
|
|
||||||
<clipPath
|
|
||||||
id="abfee98d57">
|
|
||||||
<path
|
|
||||||
d="m 50.867188,216.45703 h 58.414062 v 35.44922 H 50.867188 Z m 0,0"
|
|
||||||
clip-rule="nonzero"
|
|
||||||
id="path7" />
|
|
||||||
</clipPath>
|
|
||||||
</defs>
|
|
||||||
<g
|
|
||||||
clip-path="url(#3bacd8e954)"
|
|
||||||
id="g14"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="M 45.214844,214.47266 C 32.179688,207.12891 23.378906,193.1875 23.378906,177.19531 c 0,-23.6289 19.214844,-42.78906 42.917969,-42.78906 13.101563,0 24.828125,5.85547 32.703125,15.08203 h 14.10938 c -9.49219,-15.90234 -26.902349,-26.55859 -46.812505,-26.55859 -30.0625,0 -54.433594,24.29687 -54.433594,54.26562 0,19.8711 10.710938,37.24219 26.691407,46.69922 l 6.660156,-9.42187"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path12" />
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
clip-path="url(#abfee98d57)"
|
|
||||||
id="g18"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 79.015625,218.07422 c -0.476563,0.14844 -0.953125,0.28906 -1.4375,0.41797 l -26.652344,10.77734 c 1.820313,0.53125 3.675781,0.97266 5.566407,1.31641 l 52.726562,21.32422 -24.984375,-35.34375 c -1.699219,0.61718 -3.441406,1.12109 -5.21875,1.50781"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path16" />
|
|
||||||
</g>
|
|
||||||
<path
|
|
||||||
fill="#d9d9d9"
|
|
||||||
d="m 58.480469,83.542972 c 15.527344,0 28.113281,-12.54688 28.113281,-28.02344 0,-15.48047 -12.585937,-28.02735 -28.113281,-28.02735 -8.582031,0 -16.261719,3.83204 -21.417969,9.875 h -9.242187 c 6.21875,-10.41406 17.621094,-17.39453 30.660156,-17.39453 19.691406,0 35.65234,15.91407 35.65234,35.54688 0,17.4414 -12.601559,31.95312 -29.226559,34.96875 L 30.371094,104.45312 46.902344,81.062502 c 3.53125,1.59375 7.449219,2.48047 11.578125,2.48047"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path20" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 47.800781,54.269532 c 0,2.54297 -2.066406,4.60547 -4.621093,4.60547 -2.550781,0 -4.617188,-2.0625 -4.617188,-4.60547 0,-2.54688 2.066407,-4.60938 4.617188,-4.60938 2.554687,0 4.621093,2.0625 4.621093,4.60938"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path22" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 61.714844,54.269532 c 0,2.54297 -2.066406,4.60547 -4.621094,4.60547 -2.550781,0 -4.617187,-2.0625 -4.617187,-4.60547 0,-2.54688 2.066406,-4.60938 4.617187,-4.60938 2.554688,0 4.621094,2.0625 4.621094,4.60938"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path24" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 75.628907,54.269532 c 0,2.54297 -2.070313,4.60547 -4.621094,4.60547 -2.550782,0 -4.621094,-2.0625 -4.621094,-4.60547 0,-2.54688 2.070312,-4.60938 4.621094,-4.60938 2.550781,0 4.621094,2.0625 4.621094,4.60938"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path26" />
|
|
||||||
<path
|
|
||||||
fill="#1976d2"
|
|
||||||
d="m 104.62891,40.363282 h 250.01562 v 48.73828 H 104.62891 v -48.73828"
|
|
||||||
fill-opacity="1"
|
|
||||||
fill-rule="nonzero"
|
|
||||||
id="path28" />
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g36"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(124.78224,199.77909)"
|
|
||||||
id="g34">
|
|
||||||
<g
|
|
||||||
id="g32">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 0.332031,-24.335938 h 4.507813 v 11.015626 h 9.480468 v -11.015626 h 4.542969 L 21.601562,0 h -4.84375 V -7.746094 H 7.277344 V 0 Z m 0,0"
|
|
||||||
id="path30" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g44"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(148.85163,199.77909)"
|
|
||||||
id="g42">
|
|
||||||
<g
|
|
||||||
id="g40">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 0.5,-24.335938 H 7.144531 L 7.445312,0 Z m 0,0"
|
|
||||||
id="path38" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g52"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(158.7331,199.77909)"
|
|
||||||
id="g50">
|
|
||||||
<g
|
|
||||||
id="g48">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,-20.464844 v -3.871094 h 20.796875 v 3.90625 L 15.054688,-20.765625 15.65625,0 h -5.640625 l 0.601563,-20.765625 z m 0,0"
|
|
||||||
id="path46" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g60"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(184.40489,199.77909)"
|
|
||||||
id="g58">
|
|
||||||
<g
|
|
||||||
id="g56">
|
|
||||||
<path
|
|
||||||
d="m 22.300781,-18.027344 c -3.707031,-2.539062 -5.742187,-2.90625 -8.277343,-2.90625 -4.910157,0 -7.046876,3.039063 -7.046876,8.78125 0.066407,4.972656 2.503907,6.609375 7.546876,6.609375 2.535156,0 5.039062,-1.199219 7.777343,-3.703125 v 7.175782 c -2.46875,1.667968 -5.074219,2.539062 -7.945312,2.539062 -7.378907,0 -11.917969,-4.875 -11.917969,-12.621094 0,-7.710937 4.039062,-12.285156 11.417969,-12.621094 2.835937,-0.132812 5.007812,0.03516 8.445312,1.703126 z m 0,0"
|
|
||||||
id="path54" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g68"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(209.14196,199.77909)"
|
|
||||||
id="g66">
|
|
||||||
<g
|
|
||||||
id="g64">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 0.332031,-24.335938 h 4.507813 v 11.015626 h 9.480468 v -11.015626 h 4.542969 L 21.601562,0 h -4.84375 V -7.746094 H 7.277344 V 0 Z m 0,0"
|
|
||||||
id="path62" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g76"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(233.21136,199.77909)"
|
|
||||||
id="g74">
|
|
||||||
<g
|
|
||||||
id="g72">
|
|
||||||
<path
|
|
||||||
d="m 17.359375,-8.679688 -3.4375,-10.183593 c -0.265625,0.867187 -0.566406,1.703125 -0.835937,2.539062 -0.265626,0.832031 -0.535157,1.667969 -0.800782,2.46875 -0.300781,0.835938 -0.566406,1.671875 -0.867187,2.539063 -0.265625,0.867187 -0.566407,1.734375 -0.867188,2.636718 z M 2.4375,0 12.050781,-24.335938 h 3.773438 L 25.4375,0 H 19.53125 L 18.664062,-3.773438 H 9.179688 L 8.011719,0 Z m 0,0"
|
|
||||||
id="path70" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g84"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(261.08647,199.77909)"
|
|
||||||
id="g82">
|
|
||||||
<g
|
|
||||||
id="g80">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,-20.464844 v -3.871094 h 20.796875 v 3.90625 L 15.054688,-20.765625 15.65625,0 h -5.640625 l 0.601563,-20.765625 z m 0,0"
|
|
||||||
id="path78" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g92"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(286.75826,199.77909)"
|
|
||||||
id="g90">
|
|
||||||
<g
|
|
||||||
id="g88">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,-20.464844 v -3.871094 h 20.796875 v 3.90625 L 15.054688,-20.765625 15.65625,0 h -5.640625 l 0.601563,-20.765625 z m 0,0"
|
|
||||||
id="path86" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g100"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(312.43006,199.77909)"
|
|
||||||
id="g98">
|
|
||||||
<g
|
|
||||||
id="g96">
|
|
||||||
<path
|
|
||||||
d="m 2.4375,0 v -24.335938 h 15.855469 v 3.871094 H 6.808594 v 5.609375 h 10.984375 v 3.839844 H 6.808594 v 4.972656 H 18.5625 V 0 Z m 0,0"
|
|
||||||
id="path94" />
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g
|
|
||||||
fill="#ffffff"
|
|
||||||
fill-opacity="1"
|
|
||||||
id="g108"
|
|
||||||
transform="translate(-11.863281,-122.92969)">
|
|
||||||
<g
|
|
||||||
transform="translate(333.42821,199.77909)"
|
|
||||||
id="g106">
|
|
||||||
<g
|
|
||||||
id="g104">
|
|
||||||
<path
|
|
||||||
d="m 18.128906,-17.058594 c 0,2.636719 -1.003906,4.574219 -2.773437,5.640625 1.101562,0.835938 2.070312,2.269531 3.074219,4.207031 0.53125,1.035157 1.167968,2.335938 1.835937,3.640626 0.632813,1.265624 1.300781,2.535156 1.800781,3.570312 h -5.875 l -2.46875,-6.308594 v 0.03125 C 12.320312,-9.179688 11.316406,-9.75 9.113281,-9.75 H 7.410156 V 0 H 2.4375 v -24.335938 h 7.679688 c 2.535156,0 4.503906,0.597657 5.875,1.800782 1.402343,1.203125 2.136718,3.039062 2.136718,5.476562 z m -11.019531,-4.40625 v 7.277344 h 3.339844 c 2.101562,0 4.238281,-1.4375 4.238281,-2.871094 0,-3.472656 -2.167969,-4.40625 -4.238281,-4.40625 z m 0,0"
|
|
||||||
id="path102" />
|
|
||||||
</g>
|
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 442 B |
20
src/index.css
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
a {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote,
|
||||||
|
dl,
|
||||||
|
dd,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6,
|
||||||
|
hr,
|
||||||
|
figure,
|
||||||
|
p,
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
@tailwind base
|
|
||||||
@tailwind components
|
|
||||||
@tailwind utilities
|
|
||||||
|
|
||||||
ol
|
|
||||||
@apply pl-4 list-decimal
|
|
||||||
|
|
||||||
ul
|
|
||||||
@apply pl-4 list-disc
|
|
@ -2,10 +2,29 @@ import './polyfills'
|
|||||||
import ReactDOM from 'react-dom/client'
|
import ReactDOM from 'react-dom/client'
|
||||||
import 'typeface-roboto'
|
import 'typeface-roboto'
|
||||||
|
|
||||||
import './index.sass'
|
import 'modern-normalize/modern-normalize.css'
|
||||||
|
import './index.css'
|
||||||
|
|
||||||
|
import { ThemeProvider, createTheme } from '@mui/material/styles'
|
||||||
|
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
|
||||||
|
|
||||||
import Init from './Init'
|
import Init from './Init'
|
||||||
import reportWebVitals from './reportWebVitals'
|
import reportWebVitals from './reportWebVitals'
|
||||||
|
|
||||||
|
// NOTE: This is a workaround for MUI components attempting to access theme code
|
||||||
|
// before it has loaded.
|
||||||
|
// See: https://stackoverflow.com/a/76017295/470685
|
||||||
|
;<ThemeProvider theme={createTheme()} />
|
||||||
|
|
||||||
|
// NOTE: This is a workaround for SyntaxHighlighter not working reliably in the
|
||||||
|
// EmbedCodeDialog component. It seems to have the effect of warming some
|
||||||
|
// sort of internal cache that avoids a race condition within
|
||||||
|
// SyntaxHighlighter.
|
||||||
|
// See: https://github.com/react-syntax-highlighter/react-syntax-highlighter/issues/513
|
||||||
|
ReactDOM.createRoot(document.createElement('div')).render(
|
||||||
|
<SyntaxHighlighter language="" children={''} />
|
||||||
|
)
|
||||||
|
|
||||||
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
|
||||||
root.render(<Init />)
|
root.render(<Init />)
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { getTrackers } from 'trystero/torrent'
|
import { getRelaySockets } from 'trystero/torrent'
|
||||||
import { rtcConfig } from 'config/rtcConfig'
|
import { rtcConfig } from 'config/rtcConfig'
|
||||||
import { parseCandidate } from 'sdp'
|
import { parseCandidate } from 'sdp'
|
||||||
|
|
||||||
@ -104,17 +104,15 @@ export class ConnectionTest extends EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
testTrackerConnection() {
|
testTrackerConnection() {
|
||||||
const trackers = getTrackers()
|
const relaySockets = Object.values(getRelaySockets())
|
||||||
|
|
||||||
const trackerSockets = Object.values(trackers)
|
if (relaySockets.length === 0) {
|
||||||
|
|
||||||
if (trackerSockets.length === 0) {
|
|
||||||
// Trystero has not yet initialized tracker sockets
|
// Trystero has not yet initialized tracker sockets
|
||||||
this.trackerConnection = TrackerConnection.SEARCHING
|
this.trackerConnection = TrackerConnection.SEARCHING
|
||||||
return this.trackerConnection
|
return this.trackerConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
const readyStates = trackerSockets.map(({ readyState }) => readyState)
|
const readyStates = relaySockets.map(({ readyState }) => readyState)
|
||||||
|
|
||||||
const haveAllTrackerConnectionsFailed = readyStates.every(
|
const haveAllTrackerConnectionsFailed = readyStates.every(
|
||||||
readyState => readyState === WebSocket.CLOSED
|
readyState => readyState === WebSocket.CLOSED
|
@ -1,7 +1,8 @@
|
|||||||
import { joinRoom, Room, BaseRoomConfig } from 'trystero'
|
import { joinRoom, Room, BaseRoomConfig, DataPayload } from 'trystero/torrent'
|
||||||
import { TorrentRoomConfig } from 'trystero/torrent'
|
import { RelayConfig } from 'trystero/torrent'
|
||||||
|
|
||||||
import { sleep } from 'utils'
|
import { sleep } from 'lib/sleep'
|
||||||
|
import { StreamType } from 'models/chat'
|
||||||
|
|
||||||
export enum PeerHookType {
|
export enum PeerHookType {
|
||||||
NEW_PEER = 'NEW_PEER',
|
NEW_PEER = 'NEW_PEER',
|
||||||
@ -27,7 +28,7 @@ const streamQueueAddDelay = 1000
|
|||||||
export class PeerRoom {
|
export class PeerRoom {
|
||||||
private room: Room
|
private room: Room
|
||||||
|
|
||||||
private roomConfig: TorrentRoomConfig & BaseRoomConfig
|
private roomConfig: RelayConfig & BaseRoomConfig
|
||||||
|
|
||||||
private peerJoinHandlers: Map<
|
private peerJoinHandlers: Map<
|
||||||
PeerHookType,
|
PeerHookType,
|
||||||
@ -48,7 +49,19 @@ export class PeerRoom {
|
|||||||
|
|
||||||
private isProcessingPendingStreams = false
|
private isProcessingPendingStreams = false
|
||||||
|
|
||||||
constructor(config: TorrentRoomConfig & BaseRoomConfig, roomId: string) {
|
private processPendingStreams = async () => {
|
||||||
|
if (this.isProcessingPendingStreams) return
|
||||||
|
|
||||||
|
this.isProcessingPendingStreams = true
|
||||||
|
|
||||||
|
while (this.streamQueue.length > 0) {
|
||||||
|
await this.streamQueue.shift()?.()
|
||||||
|
}
|
||||||
|
|
||||||
|
this.isProcessingPendingStreams = false
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(config: RelayConfig & BaseRoomConfig, roomId: string) {
|
||||||
this.roomConfig = config
|
this.roomConfig = config
|
||||||
this.room = joinRoom(this.roomConfig, roomId)
|
this.room = joinRoom(this.roomConfig, roomId)
|
||||||
|
|
||||||
@ -155,34 +168,26 @@ export class PeerRoom {
|
|||||||
return peerConnections
|
return peerConnections
|
||||||
}
|
}
|
||||||
|
|
||||||
makeAction = <T>(namespace: string) => {
|
makeAction = <T extends DataPayload>(namespace: string) => {
|
||||||
return this.room.makeAction<T>(namespace)
|
return this.room.makeAction<T>(namespace)
|
||||||
}
|
}
|
||||||
|
|
||||||
addStream = (...args: Parameters<Room['addStream']>) => {
|
addStream = (
|
||||||
|
stream: Parameters<Room['addStream']>[0],
|
||||||
|
targetPeers: Parameters<Room['addStream']>[1],
|
||||||
|
metadata: { type: StreamType }
|
||||||
|
) => {
|
||||||
// New streams need to be added as a delayed queue to prevent race
|
// New streams need to be added as a delayed queue to prevent race
|
||||||
// conditions on the receiver's end where streams and their metadata get
|
// conditions on the receiver's end where streams and their metadata get
|
||||||
// mixed up.
|
// mixed up.
|
||||||
this.streamQueue.push(
|
this.streamQueue.push(
|
||||||
() => Promise.all(this.room.addStream(...args)),
|
() => Promise.all(this.room.addStream(stream, targetPeers, metadata)),
|
||||||
() => sleep(streamQueueAddDelay)
|
() => sleep(streamQueueAddDelay)
|
||||||
)
|
)
|
||||||
|
|
||||||
this.processPendingStreams()
|
this.processPendingStreams()
|
||||||
}
|
}
|
||||||
|
|
||||||
private processPendingStreams = async () => {
|
|
||||||
if (this.isProcessingPendingStreams) return
|
|
||||||
|
|
||||||
this.isProcessingPendingStreams = true
|
|
||||||
|
|
||||||
while (this.streamQueue.length > 0) {
|
|
||||||
await this.streamQueue.shift()?.()
|
|
||||||
}
|
|
||||||
|
|
||||||
this.isProcessingPendingStreams = false
|
|
||||||
}
|
|
||||||
|
|
||||||
removeStream: Room['removeStream'] = (stream, targetPeers) => {
|
removeStream: Room['removeStream'] = (stream, targetPeers) => {
|
||||||
return this.room.removeStream(stream, targetPeers)
|
return this.room.removeStream(stream, targetPeers)
|
||||||
}
|
}
|
7
src/lib/Time/Time.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export class Time {
|
||||||
|
now = () => {
|
||||||
|
return Date.now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const time = new Time()
|
1
src/lib/Time/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './Time'
|
4
src/lib/sleep.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const sleep = (milliseconds: number): Promise<void> =>
|
||||||
|
new Promise<void>(res => {
|
||||||
|
setTimeout(res, milliseconds)
|
||||||
|
})
|
@ -1,8 +1,3 @@
|
|||||||
export const sleep = (milliseconds: number): Promise<void> =>
|
|
||||||
new Promise<void>(res => {
|
|
||||||
setTimeout(res, milliseconds)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const isRecord = (variable: any): variable is Record<string, any> => {
|
export const isRecord = (variable: any): variable is Record<string, any> => {
|
||||||
return (
|
return (
|
||||||
typeof variable === 'object' &&
|
typeof variable === 'object' &&
|