Compare commits

..

141 Commits

Author SHA1 Message Date
Gary
0b1b2f1d90 update git ignore 2024-11-09 21:22:00 -08:00
Gary
89f903f856 Custom community rooms
Custom home description
2024-11-09 20:55:56 -08:00
Gary
8bd54b5f5f update package modules
enable file sharing streams
2024-11-09 20:25:53 -08:00
Gary
5911cc6efa About page: Fixed typos and links. 2024-11-09 20:20:40 -08:00
Gary
f3491836ce main: added public pages section to Home page. 2024-11-09 20:20:40 -08:00
Gary
dcabdb446c main: update to README 2024-11-09 20:20:40 -08:00
Gary
bce861bb44 main: change to favicon. 2024-11-09 20:20:40 -08:00
Gary
716c37225c main: created docker files 2024-11-09 20:20:40 -08:00
Gary
07e02fa07e main: prelim update to documention. 2024-11-09 20:20:40 -08:00
Gary
9d76959374 Main: Begin derived app rewrite into remnantchat. 2024-11-09 20:20:34 -08:00
Jeremy Kahn
7b676b4fa2
feat(ui): Community rooms (#372)
* chore(vim): disable eslint.experimental.useFlatConfig

https://github.com/neoclide/coc-eslint/issues/41#issuecomment-563879603

* feat(ui): implement community rooms

* feat(ui): remove old community room link

* feat(ui): improve room label
2024-11-06 17:14:31 -06:00
Jeremy Kahn
bd7e8df0e3 chore(deps): [closes #369] use @types/react-dom@18.3.1 2024-11-02 11:45:00 -05:00
Jeremy Kahn
5418848687
fix: upgrade react-router-dom from 6.26.2 to 6.27.0 (#368)
Snyk has created this PR to upgrade react-router-dom from 6.26.2 to 6.27.0.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-11-02 11:42:27 -05:00
Jeremy Kahn
4719dbae4d
fix: upgrade @types/node from 18.19.54 to 18.19.55 (#370)
Snyk has created this PR to upgrade @types/node from 18.19.54 to 18.19.55.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-11-02 11:41:44 -05:00
Jeremy Kahn
c265be4f4b
fix: upgrade typescript from 5.6.2 to 5.6.3 (#371)
Snyk has created this PR to upgrade typescript from 5.6.2 to 5.6.3.

See this package in npm:
typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-11-02 11:40:11 -05:00
dependabot[bot]
0f7916cac5
chore(deps): bump elliptic from 6.5.7 to 6.6.0 (#367)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.7 to 6.6.0.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.7...v6.6.0)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-30 21:17:32 -05:00
dependabot[bot]
5062817d50
chore(deps): bump rollup (#366)
Bumps  and [rollup](https://github.com/rollup/rollup). These dependencies needed to be updated together.

Updates `rollup` from 4.21.3 to 4.24.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.21.3...v4.24.0)

Updates `rollup` from 2.79.1 to 4.24.0
- [Release notes](https://github.com/rollup/rollup/releases)
- [Changelog](https://github.com/rollup/rollup/blob/master/CHANGELOG.md)
- [Commits](https://github.com/rollup/rollup/compare/v4.21.3...v4.24.0)

---
updated-dependencies:
- dependency-name: rollup
  dependency-type: indirect
- dependency-name: rollup
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-10-26 07:11:05 -04:00
Jeremy Kahn
ea2928ccf8
fix: upgrade @types/node from 18.19.50 to 18.19.54 (#365)
Snyk has created this PR to upgrade @types/node from 18.19.50 to 18.19.54.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-10-26 07:05:22 -04:00
Jeremy Kahn
5203c86a48
fix: upgrade react-router-dom from 6.26.1 to 6.26.2 (#363)
Snyk has created this PR to upgrade react-router-dom from 6.26.1 to 6.26.2.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-10-01 07:38:05 -05:00
Jeremy Kahn
c92ec13242
fix: upgrade typescript from 5.5.4 to 5.6.2 (#362)
Snyk has created this PR to upgrade typescript from 5.5.4 to 5.6.2.

See this package in npm:
typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-10-01 07:37:47 -05:00
Jeremy Kahn
dc876e6589
fix: upgrade @types/node from 18.19.49 to 18.19.50 (#361)
Snyk has created this PR to upgrade @types/node from 18.19.49 to 18.19.50.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-26 11:14:17 -04:00
Jeremy Kahn
1a94400af2
fix: upgrade @types/node from 18.19.48 to 18.19.49 (#360)
Snyk has created this PR to upgrade @types/node from 18.19.48 to 18.19.49.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-25 10:56:41 -04:00
Jeremy Kahn
b647d72c85
fix: upgrade @types/node from 18.19.47 to 18.19.48 (#359)
Snyk has created this PR to upgrade @types/node from 18.19.47 to 18.19.48.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-23 07:30:56 -05:00
Jeremy Kahn
41f3078f97
fix: upgrade @types/node from 18.19.46 to 18.19.47 (#358)
Snyk has created this PR to upgrade @types/node from 18.19.46 to 18.19.47.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-19 08:23:59 -05:00
dependabot[bot]
28bf6f124f
chore(deps-dev): bump vite from 5.0.13 to 5.4.6 (#357)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.13 to 5.4.6.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.4.6/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.4.6/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 20:33:20 -05:00
Jeremy Kahn
49aedc0554
fix: upgrade @types/node from 18.19.45 to 18.19.46 (#356)
Snyk has created this PR to upgrade @types/node from 18.19.45 to 18.19.46.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-17 08:44:36 -05:00
Jeremy Kahn
fefa8fa7ce
fix: upgrade @testing-library/jest-dom from 6.4.8 to 6.5.0 (#355)
Snyk has created this PR to upgrade @testing-library/jest-dom from 6.4.8 to 6.5.0.

See this package in npm:
@testing-library/jest-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-14 10:50:26 -05:00
Jeremy Kahn
cf58f6196e
fix: upgrade @emotion/react from 11.13.0 to 11.13.3 (#354)
Snyk has created this PR to upgrade @emotion/react from 11.13.0 to 11.13.3.

See this package in npm:
@emotion/react

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-11 08:18:48 -05:00
Jeremy Kahn
4274ec9fee
fix: upgrade @types/node from 18.19.44 to 18.19.45 (#353)
Snyk has created this PR to upgrade @types/node from 18.19.44 to 18.19.45.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-09 08:11:07 -05:00
Jeremy Kahn
7f7a497ba0
fix: upgrade react-router-dom from 6.26.0 to 6.26.1 (#352)
Snyk has created this PR to upgrade react-router-dom from 6.26.0 to 6.26.1.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-09-06 07:16:00 -05:00
dependabot[bot]
ff518bd3b9
chore(deps-dev): bump micromatch from 4.0.5 to 4.0.8 (#351)
Bumps [micromatch](https://github.com/micromatch/micromatch) from 4.0.5 to 4.0.8.
- [Release notes](https://github.com/micromatch/micromatch/releases)
- [Changelog](https://github.com/micromatch/micromatch/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/micromatch/compare/4.0.5...4.0.8)

---
updated-dependencies:
- dependency-name: micromatch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-31 18:14:11 -05:00
Jeremy Kahn
66ffd16948
fix: upgrade @types/node from 18.19.43 to 18.19.44 (#350)
Snyk has created this PR to upgrade @types/node from 18.19.43 to 18.19.44.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-31 10:55:53 -05:00
Jeremy Kahn
5d9af13637 chore(deps): use @mui/icons-material@5.16.7 and @mui/material@5.16.7
[closes #348]

[closes #349]
2024-08-30 08:22:53 -05:00
Jeremy Kahn
6556039639
fix: upgrade @types/node from 18.19.42 to 18.19.43 (#347)
Snyk has created this PR to upgrade @types/node from 18.19.42 to 18.19.43.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-23 08:13:03 -05:00
Jeremy Kahn
0cb5164753
fix: upgrade react-router-dom from 6.25.1 to 6.26.0 (#346)
Snyk has created this PR to upgrade react-router-dom from 6.25.1 to 6.26.0.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-23 08:12:48 -05:00
Jeremy Kahn
a706ba1432 chore(deps): use @mui/icons-material@5.16.6 and @mui/material@5.16.6
[closes #344]

[closes #345]
2024-08-21 08:18:39 -05:00
dependabot[bot]
656c15bdc8
chore(deps): bump elliptic from 6.5.4 to 6.5.7 (#343)
Bumps [elliptic](https://github.com/indutny/elliptic) from 6.5.4 to 6.5.7.
- [Commits](https://github.com/indutny/elliptic/compare/v6.5.4...v6.5.7)

---
updated-dependencies:
- dependency-name: elliptic
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-18 14:00:24 -05:00
Jeremy Kahn
915e0a2aea chore(deps): use @mui/icons-material@5.16.5 and @mui/material@5.16.5
closes #339

closes #340
2024-08-18 13:57:26 -05:00
Jeremy Kahn
5df3e36baa
fix: upgrade @testing-library/jest-dom from 6.4.7 to 6.4.8 (#341)
Snyk has created this PR to upgrade @testing-library/jest-dom from 6.4.7 to 6.4.8.

See this package in npm:
@testing-library/jest-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-18 13:54:45 -05:00
Jeremy Kahn
648b992f31
fix: upgrade @types/node from 18.19.41 to 18.19.42 (#342)
Snyk has created this PR to upgrade @types/node from 18.19.41 to 18.19.42.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-18 13:54:35 -05:00
Jeremy Kahn
36a297b968
fix: upgrade @testing-library/jest-dom from 6.4.6 to 6.4.7 (#337)
Snyk has created this PR to upgrade @testing-library/jest-dom from 6.4.6 to 6.4.7.

See this package in npm:
@testing-library/jest-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-13 08:23:15 -05:00
Jeremy Kahn
0ef49f1a80
fix: upgrade typescript from 5.5.3 to 5.5.4 (#338)
Snyk has created this PR to upgrade typescript from 5.5.3 to 5.5.4.

See this package in npm:
typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-13 08:22:45 -05:00
Jeremy Kahn
79f62ab5e6 chore(deps): use @emotion/react@11.13.0
[closes #334]
2024-08-10 15:18:48 -05:00
Jeremy Kahn
67dff70cca
fix: upgrade @emotion/styled from 11.12.0 to 11.13.0 (#335)
Snyk has created this PR to upgrade @emotion/styled from 11.12.0 to 11.13.0.

See this package in npm:
@emotion/styled

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-08-10 15:16:23 -05:00
Jeremy Kahn
acb5d0c8ad chore(deps): update deps
use:
  - @emotion/styled@11.12.0
  - @types/node@18.19.41
  - @mui/material@5.16.4
  - @mui/icons-material@5.16.4
  - react-router-dom@6.25.1

[closes #329]
[closes #330]
[closes #331]
[closes #332]
[closes #333]
2024-08-09 08:14:14 -05:00
Jeremy Kahn
5e3ca6c49f chore(deps): use @mui/material@5.16.1 @mui/icons-material@5.16.1
closes #327

closes #328
2024-08-02 08:16:31 -05:00
Jeremy Kahn
1660fcf4e2 chore(deps): use peaceiris/actions-gh-pages@v4 2024-07-27 16:29:01 -05:00
Jeremy Kahn
d4213cec13 chore(deps): use trystero@0.20 2024-07-27 12:34:48 -05:00
Jeremy Kahn
8ee2ebfbb5 chore(deps): use @mui/material@5.16.0 @mui/icons-material@5.16.0
closes #325

closes #326
2024-07-27 10:30:41 -05:00
Jeremy Kahn
db307829f4 docs(readme): fix typo 2024-07-25 08:42:10 -05:00
Jeremy Kahn
7ab861e0ec
fix: upgrade react-router-dom from 6.24.0 to 6.24.1 (#324)
Snyk has created this PR to upgrade react-router-dom from 6.24.0 to 6.24.1.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-07-25 08:10:49 -05:00
Jeremy Kahn
62f0a7ec35
fix: upgrade typescript from 5.5.2 to 5.5.3 (#323)
Snyk has created this PR to upgrade typescript from 5.5.2 to 5.5.3.

See this package in npm:
typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-07-23 08:16:49 -05:00
Jeremy Kahn
7bb626cba0 chore(deps): use @mui/icons-material@5.15.21 and @mui/material@5.15.21
closes #321

closes #322
2024-07-20 10:18:34 -05:00
Jeremy Kahn
07ec45afdc
fix: upgrade @testing-library/jest-dom from 6.4.5 to 6.4.6 (#320)
Snyk has created this PR to upgrade @testing-library/jest-dom from 6.4.5 to 6.4.6.

See this package in npm:
@testing-library/jest-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-07-18 06:07:39 -05:00
Jeremy Kahn
e0e9c8d929 chore(deps): run npm audit fix 2024-07-17 08:08:24 -05:00
Jeremy Kahn
a15e76bc0c chore(deps): use @mui/material@5.15.20 and @mui/icons-material@5.15.20
closes #317

closes #318
2024-07-17 08:06:48 -05:00
Jeremy Kahn
4c20d07f28
fix: upgrade react-router-dom from 6.23.1 to 6.24.0 (#319)
Snyk has created this PR to upgrade react-router-dom from 6.23.1 to 6.24.0.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-07-17 08:04:25 -05:00
Jeremy Kahn
daa7a32627
fix: upgrade @types/node from 18.19.33 to 18.19.39 (#316)
Snyk has created this PR to upgrade @types/node from 18.19.33 to 18.19.39.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-07-17 08:03:35 -05:00
Jeremy Kahn
36f6b23c18
fix: upgrade typescript from 5.4.5 to 5.5.2 (#315)
Snyk has created this PR to upgrade typescript from 5.4.5 to 5.5.2.

See this package in npm:
typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-07-17 08:02:20 -05:00
dependabot[bot]
fcf9fcea52
chore(deps): bump braces from 3.0.2 to 3.0.3 (#313)
Bumps [braces](https://github.com/micromatch/braces) from 3.0.2 to 3.0.3.
- [Changelog](https://github.com/micromatch/braces/blob/master/CHANGELOG.md)
- [Commits](https://github.com/micromatch/braces/compare/3.0.2...3.0.3)

---
updated-dependencies:
- dependency-name: braces
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 17:54:20 -05:00
dependabot[bot]
476657b7c0
chore(deps): bump @grpc/grpc-js from 1.9.14 to 1.9.15 (#312)
Bumps [@grpc/grpc-js](https://github.com/grpc/grpc-node) from 1.9.14 to 1.9.15.
- [Release notes](https://github.com/grpc/grpc-node/releases)
- [Commits](https://github.com/grpc/grpc-node/compare/@grpc/grpc-js@1.9.14...@grpc/grpc-js@1.9.15)

---
updated-dependencies:
- dependency-name: "@grpc/grpc-js"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-06-10 17:37:25 -05:00
Jeremy Kahn
9aaa84bfae docs(readme): explain theme customization 2024-06-10 08:35:16 -05:00
Jeremy Kahn
dc78137702 refactor(shell): move theme definition to its own file 2024-06-10 08:29:00 -05:00
Jeremy Kahn
56dbbf2665 fix(media): [closes #311] don't attempt to render unsupported media 2024-06-08 11:20:07 -05:00
Jeremy Kahn
da529efb1c docs: remove TURN server notice 2024-06-07 08:45:33 -05:00
Jeremy Kahn
ff03190bf5 feat(config): use expressturn TURN server 2024-06-07 08:33:34 -05:00
Jeremy Kahn
501a6d8d11 chore(deps): run npm audit fix 2024-06-07 08:11:33 -05:00
Jeremy Kahn
dbf360c7b8 chore(deps): [closes #310] use @mui/icons-material@5.15.18 2024-06-07 08:10:07 -05:00
Jeremy Kahn
20b9e7fe4e
fix: upgrade @mui/material from 5.15.17 to 5.15.18 (#309)
Snyk has created this PR to upgrade @mui/material from 5.15.17 to 5.15.18.

See this package in npm:
@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-06-07 08:07:54 -05:00
Jeremy Kahn
a8dff459f3 feat(file-sharing): [closes #308] scale shared images to fit 2024-06-05 08:10:27 -05:00
Jeremy Kahn
73218f4eea
fix: upgrade react-router-dom from 6.23.0 to 6.23.1 (#307)
Snyk has created this PR to upgrade react-router-dom from 6.23.0 to 6.23.1.

See this package in npm:
react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-06-01 13:30:17 -05:00
Jeremy Kahn
47d8bf8f99 chore(deps): [closes #304] use @mui/icons-material@5.15.17 2024-05-30 08:05:14 -05:00
Jeremy Kahn
df842e9b2f
fix: upgrade @mui/material from 5.15.16 to 5.15.17 (#305)
Snyk has created this PR to upgrade @mui/material from 5.15.16 to 5.15.17.

See this package in npm:
@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-30 08:03:41 -05:00
Jeremy Kahn
f1f5927767
fix: upgrade @types/node from 18.19.32 to 18.19.33 (#306)
Snyk has created this PR to upgrade @types/node from 18.19.32 to 18.19.33.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-30 08:03:07 -05:00
Jeremy Kahn
2d63f7c580
chore: fix markdown 2024-05-28 19:09:02 -05:00
Jeremy Kahn
35947a559b docs(readme): note TURN server need 2024-05-28 19:08:13 -05:00
Jeremy Kahn
677b13f47c feat(closes #274): add private room password visibility toggle 2024-05-28 08:11:44 -05:00
Jeremy Kahn
3b24c5275d feat(#274): add back button to password prompt 2024-05-28 08:11:44 -05:00
Jeremy Kahn
3b5c566807
fix: upgrade @types/node from 18.19.31 to 18.19.32 (#303)
Snyk has created this PR to upgrade @types/node from 18.19.31 to 18.19.32.

See this package in npm:
@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-27 18:06:46 -05:00
Jeremy Kahn
f12510172b chore(deps): use @mui/icons-material@5.15.16 2024-05-25 09:21:59 -05:00
Jeremy Kahn
3616846c03
fix: upgrade @mui/material from 5.15.15 to 5.15.16 (#301)
Snyk has created this PR to upgrade @mui/material from 5.15.15 to 5.15.16.

See this package in npm:
@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-25 09:08:38 -05:00
Jeremy Kahn
fcf45db228
fix: upgrade @testing-library/jest-dom from 6.4.2 to 6.4.5 (#300)
Snyk has created this PR to upgrade @testing-library/jest-dom from 6.4.2 to 6.4.5.

See this package in npm:
@testing-library/jest-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-25 09:08:06 -05:00
Jeremy Kahn
3e185b98d5
fix: upgrade react-dom from 18.3.0 to 18.3.1 (#298)
Snyk has created this PR to upgrade react-dom from 18.3.0 to 18.3.1.

See this package in npm:
react-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-18 15:09:13 -05:00
Jeremy Kahn
f7fb092e67
[Snyk] Upgrade: react, react-dom (#293)
* fix: upgrade multiple dependencies with Snyk

Snyk has created this PR to upgrade:
  - react from 18.2.0 to 18.3.0.
    See this package in npm: https://www.npmjs.com/package/react
  - react-dom from 18.2.0 to 18.3.0.
    See this package in npm: https://www.npmjs.com/package/react-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

* chore(deps): use @testing-library/react@15.0.7

* chore(deps): use react@18.3.1

---------

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-17 19:34:04 -05:00
Jeremy Kahn
80407f707d
fix: upgrade @types/react-dom from 18.2.25 to 18.3.0 (#294)
Snyk has created this PR to upgrade @types/react-dom from 18.2.25 to 18.3.0.

See this package in npm:
https://www.npmjs.com/package/@types/react-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-17 08:47:22 -05:00
Jeremy Kahn
978dd74adc
fix: upgrade react-router-dom from 6.22.3 to 6.23.0 (#292)
Snyk has created this PR to upgrade react-router-dom from 6.22.3 to 6.23.0.

See this package in npm:
https://www.npmjs.com/package/react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-14 17:26:37 -05:00
Jeremy Kahn
a029cb7385
fix: upgrade react-qrcode-logo from 2.9.0 to 2.10.0 (#291)
Snyk has created this PR to upgrade react-qrcode-logo from 2.9.0 to 2.10.0.

See this package in npm:
https://www.npmjs.com/package/react-qrcode-logo

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-07 18:58:38 -05:00
Jeremy Kahn
a3e217e69b docs(#288): document FDM compatibility issue 2024-05-07 08:55:44 -05:00
Jeremy Kahn
89830c2611 docs(readme): clarify self-hosting instructions 2024-05-05 10:37:29 -05:00
Jeremy Kahn
263cd8b4e4
fix: upgrade @testing-library/react from 14.3.0 to 14.3.1 (#284)
Snyk has created this PR to upgrade @testing-library/react from 14.3.0 to 14.3.1.

See this package in npm:
https://www.npmjs.com/package/@testing-library/react

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-04 11:56:17 -05:00
Jeremy Kahn
ef514a6d1b
fix: upgrade @types/react-dom from 18.2.24 to 18.2.25 (#283)
Snyk has created this PR to upgrade @types/react-dom from 18.2.24 to 18.2.25.

See this package in npm:
https://www.npmjs.com/package/@types/react-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-04 11:55:16 -05:00
dependabot[bot]
456abaf799
chore(deps-dev): bump ejs from 3.1.9 to 3.1.10 (#281)
Bumps [ejs](https://github.com/mde/ejs) from 3.1.9 to 3.1.10.
- [Release notes](https://github.com/mde/ejs/releases)
- [Commits](https://github.com/mde/ejs/compare/v3.1.9...v3.1.10)

---
updated-dependencies:
- dependency-name: ejs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-05-02 07:47:46 -05:00
Jeremy Kahn
2ac8c2f04e
fix: upgrade typescript from 5.4.4 to 5.4.5 (#279)
Snyk has created this PR to upgrade typescript from 5.4.4 to 5.4.5.

See this package in npm:
https://www.npmjs.com/package/typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-05-02 07:44:06 -05:00
Jeremy Kahn
28e4953570
fix: upgrade @types/node from 18.19.30 to 18.19.31 (#278)
Snyk has created this PR to upgrade @types/node from 18.19.30 to 18.19.31.

See this package in npm:
https://www.npmjs.com/package/@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-30 19:06:04 -05:00
Jeremy Kahn
e36cca6f43
fix: upgrade @testing-library/react from 14.2.2 to 14.3.0 (#277)
Snyk has created this PR to upgrade @testing-library/react from 14.2.2 to 14.3.0.

See this package in npm:
https://www.npmjs.com/package/@testing-library/react

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-29 20:22:10 -05:00
Jeremy Kahn
f64570d6b6
fix: upgrade @types/node from 18.19.29 to 18.19.30 (#275)
Snyk has created this PR to upgrade @types/node from 18.19.29 to 18.19.30.

See this package in npm:
https://www.npmjs.com/package/@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-27 12:08:16 -05:00
Jeremy Kahn
65c4c4890a chore(deps): run npm audit --fix 2024-04-25 21:00:29 -05:00
Jeremy Kahn
7e40e0cd9f chore(deps): [closes #271] use @mui/icons-material@5.15.15 2024-04-25 20:58:42 -05:00
Jeremy Kahn
87f0a185d8
fix: upgrade @mui/material from 5.15.14 to 5.15.15 (#272)
Snyk has created this PR to upgrade @mui/material from 5.15.14 to 5.15.15.

See this package in npm:
https://www.npmjs.com/package/@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-25 21:00:36 -05:00
Jeremy Kahn
4718032415
fix: upgrade typescript from 5.4.3 to 5.4.4 (#273)
Snyk has created this PR to upgrade typescript from 5.4.3 to 5.4.4.

See this package in npm:
https://www.npmjs.com/package/typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-25 20:59:09 -05:00
Jeremy Kahn
b4c86515c0
fix: upgrade @types/react-dom from 18.2.23 to 18.2.24 (#269)
Snyk has created this PR to upgrade @types/react-dom from 18.2.23 to 18.2.24.

See this package in npm:
https://www.npmjs.com/package/@types/react-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-24 20:17:00 -05:00
Jeremy Kahn
367a4e3d62
fix: upgrade @types/node from 18.19.28 to 18.19.29 (#268)
Snyk has created this PR to upgrade @types/node from 18.19.28 to 18.19.29.

See this package in npm:
https://www.npmjs.com/package/@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-24 07:23:11 -05:00
Jeremy Kahn
91ef4ad32f docs(readme): improve formatting 2024-04-21 21:06:19 -05:00
Jeremy Kahn
24ac1f1bcd docs(installation): [closes #267] document cloning, NPM, and WSL 2024-04-21 21:04:04 -05:00
Jeremy Kahn
3adaf41ea0
fix: upgrade @types/node from 18.19.26 to 18.19.28 (#266)
Snyk has created this PR to upgrade @types/node from 18.19.26 to 18.19.28.

See this package in npm:
https://www.npmjs.com/package/@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-21 09:42:52 -05:00
Jeremy Kahn
f4d17d883e
fix: upgrade @emotion/styled from 11.11.0 to 11.11.5 (#264)
Snyk has created this PR to upgrade @emotion/styled from 11.11.0 to 11.11.5.

See this package in npm:
https://www.npmjs.com/package/@emotion/styled

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-19 19:07:00 -05:00
Jeremy Kahn
2dbd29a90a chore(deps): use actions/upload-artifact@v4 2024-04-18 18:23:54 -05:00
Jeremy Kahn
88e47c054f
fix: upgrade @types/react-dom from 18.2.22 to 18.2.23 (#263)
Snyk has created this PR to upgrade @types/react-dom from 18.2.22 to 18.2.23.

See this package in npm:
https://www.npmjs.com/package/@types/react-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-18 18:18:44 -05:00
Jeremy Kahn
c03b4ad50b
fix: upgrade @testing-library/react from 14.2.1 to 14.2.2 (#260)
Snyk has created this PR to upgrade @testing-library/react from 14.2.1 to 14.2.2.

See this package in npm:
https://www.npmjs.com/package/@testing-library/react

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-17 20:34:53 -05:00
Jeremy Kahn
aaa0ed83ad
fix: upgrade @mui/icons-material from 5.15.13 to 5.15.14 (#261)
Snyk has created this PR to upgrade @mui/icons-material from 5.15.13 to 5.15.14.

See this package in npm:
https://www.npmjs.com/package/@mui/icons-material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-17 20:31:49 -05:00
Jeremy Kahn
ffc32fa2bf
fix: upgrade typescript from 5.4.2 to 5.4.3 (#262)
Snyk has created this PR to upgrade typescript from 5.4.2 to 5.4.3.

See this package in npm:
https://www.npmjs.com/package/typescript

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-17 20:30:25 -05:00
Jeremy Kahn
5c4202fa4c chore(deps): [closes #255] use @types/react@18.2.72 2024-04-16 18:57:04 -05:00
Jeremy Kahn
016e4c8161
fix: upgrade detectincognitojs from 1.3.0 to 1.3.5 (#256)
Snyk has created this PR to upgrade detectincognitojs from 1.3.0 to 1.3.5.

See this package in npm:
https://www.npmjs.com/package/detectincognitojs

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-16 18:52:40 -05:00
Jeremy Kahn
bd2ecf202c
fix: upgrade @mui/material from 5.15.12 to 5.15.14 (#257)
Snyk has created this PR to upgrade @mui/material from 5.15.12 to 5.15.14.

See this package in npm:
https://www.npmjs.com/package/@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-16 18:48:43 -05:00
Jeremy Kahn
401ec916bb
fix: upgrade @types/node from 18.19.24 to 18.19.26 (#258)
Snyk has created this PR to upgrade @types/node from 18.19.24 to 18.19.26.

See this package in npm:
https://www.npmjs.com/package/@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-16 18:47:09 -05:00
Jeremy Kahn
aacfed853e
fix: upgrade react-router-dom from 6.22.2 to 6.22.3 (#259)
Snyk has created this PR to upgrade react-router-dom from 6.22.2 to 6.22.3.

See this package in npm:
https://www.npmjs.com/package/react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-04-16 18:46:41 -05:00
Jeremy Kahn
6c434f84ab
refactor(styling): Remove Tailwind (#253)
* refactor(css): replace Tailwind classes with MUI sx
* chore(deps): remove classnames
* refactor(css): inline link baseline style
* feat(css): use modern-normalize
* chore(deps): remove tailwind
* refactor(css): inline all Sass files
* chore(deps): remove sass
2024-04-09 20:54:41 -05:00
Jeremy Kahn
72526ebbbb chore(deps): use node 20 2024-04-07 17:10:29 -05:00
Jeremy Kahn
7556d491f3 chore(deps): update prettier 2024-04-07 16:59:49 -05:00
Jeremy Kahn
3a4a09ce69 docs(readme): update GitHub Pages hosting documentation 2024-04-06 17:43:38 -05:00
Jeremy Kahn
df6d10868e docs(vite): add base hint comment 2024-04-06 17:37:50 -05:00
Marat Zimnurov
3ce8dec639
docs(readme): put step about assets resolving for gh-pages (#252) 2024-04-06 17:32:30 -05:00
dependabot[bot]
533bff5f9f
chore(deps-dev): bump vite from 5.0.12 to 5.0.13 (#250)
Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 5.0.12 to 5.0.13.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v5.0.13/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v5.0.13/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-03 18:07:23 -05:00
Jeremy Kahn
05b4615af9
feat(audio) [closes #230] Screenshare audio control (#248)
* feat(ui): present mic volume icon

* feat(ui): improve mic volume display

* refactor(ui): nest mic audio as a channel

* fix(ui): prevent volume control from reappearing for returning peers

* refactor(audio): update specific audio channel states

* refactor(audio): use enum for audio channel name

* refactor(types): improve audio type names

* feat(audio): wire up screen share audio

* refactor(networking): always provide stream metadata

* fix(audio): remove screen audio when stream ends

* fix(audio): stop audio when removing it

* feat(audio): show appropriate icon for channel

* fix(audio): clean up audio for leaving peers consistently

* fix(audio): use up-to-date peerAudios reference

* refactor(audio): simplify audio state updating

* refactor(audio): use functional setState to update peer list

* refactor(variables): rename peerAudios to peerAudioChannels

* refactor(types): consolidate stream types

* refactor(types): require stream type metadata
2024-04-01 21:25:12 -05:00
Sinisha
89abe718db
chore(networking): update TURN server credentials (#246)
updated server IP address
2024-04-01 17:24:25 -05:00
Jeremy Kahn
547564d921
fix: upgrade @mui/material from 5.15.11 to 5.15.12 (#245)
Snyk has created this PR to upgrade @mui/material from 5.15.11 to 5.15.12.

See this package in npm:
https://www.npmjs.com/package/@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-27 20:24:47 -05:00
Jeremy Kahn
915ea21316
fix: upgrade react-router-dom from 6.22.1 to 6.22.2 (#243)
Snyk has created this PR to upgrade react-router-dom from 6.22.1 to 6.22.2.

See this package in npm:
https://www.npmjs.com/package/react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-21 19:02:57 -05:00
Jeremy Kahn
042c447b55
fix: upgrade @emotion/react from 11.11.3 to 11.11.4 (#242)
Snyk has created this PR to upgrade @emotion/react from 11.11.3 to 11.11.4.

See this package in npm:
https://www.npmjs.com/package/@emotion/react

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-20 20:54:31 -05:00
Jeremy Kahn
7ecb17d0a1
fix: upgrade @mui/material from 5.15.10 to 5.15.11 (#241)
Snyk has created this PR to upgrade @mui/material from 5.15.10 to 5.15.11.

See this package in npm:
https://www.npmjs.com/package/@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-18 21:11:16 -05:00
Jeremy Kahn
0eb5b596dd
fix: upgrade secure-file-transfer from 0.0.7 to 0.0.8 (#240)
Snyk has created this PR to upgrade secure-file-transfer from 0.0.7 to 0.0.8.

See this package in npm:
https://www.npmjs.com/package/secure-file-transfer

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-17 10:12:54 -05:00
Jeremy Kahn
d81e930da6 chore(deps): upgrade to Trystero@0.18.0 2024-03-14 21:35:51 -05:00
Jeremy Kahn
5d4619965c chore(deps): upgrade react, node and MUI icons 2024-03-14 20:55:51 -05:00
Jeremy Kahn
5f2444f9c9
fix: upgrade @types/node from 18.19.10 to 18.19.17 (#233)
Snyk has created this PR to upgrade @types/node from 18.19.10 to 18.19.17.

See this package in npm:
https://www.npmjs.com/package/@types/node

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-14 07:50:16 -05:00
Jeremy Kahn
f9768627ee
fix: upgrade @mui/material from 5.15.6 to 5.15.10 (#235)
Snyk has created this PR to upgrade @mui/material from 5.15.6 to 5.15.10.

See this package in npm:
https://www.npmjs.com/package/@mui/material

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-14 07:48:30 -05:00
Jeremy Kahn
e644b20f4e
fix: upgrade react-router-dom from 6.21.3 to 6.22.1 (#236)
Snyk has created this PR to upgrade react-router-dom from 6.21.3 to 6.22.1.

See this package in npm:
https://www.npmjs.com/package/react-router-dom

See this project in Snyk:
https://app.snyk.io/org/jeremyckahn/project/d52c3b0d-59ff-42e3-89e1-b97ee832bf9a?utm_source=github&utm_medium=referral&page=upgrade-pr

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
2024-03-14 07:47:17 -05:00
Jeremy Kahn
7c5c12178d fix(pwa): use compatible service worker name
This restores backwards compatibility with create-react-app-based builds
2024-03-13 07:27:21 -05:00
Jeremy Kahn
520b863f2a fix(pwa): add upgrade compatibility shim
This is needed to fix automatic PWA updates after ea34058fa7
2024-03-13 07:16:12 -05:00
Jeremy Kahn
ea34058fa7
chore: Migrate from Create React App to Vite (#231)
* chore(vite): use vite

* fix(vite): alias lib directory

* chore(vite): set type: module

* chore: update vite and MUI

* fix(vite): make MUI components load

* fix: use node path resolution

* chore(vite): add svg support

* fix(vite): polyfill global

* fix(vite): use import.meta

* fix(vite): use correct svg module resolution

* chore(vite): migrate to vitest

* fix(vite): remove PUBLIC_URL

* fix(tests): mock audio service

* chore(deps): upgrade to react test library 14

* refactor(tests): simplify room test setup

* refactor(tests): make Date.now() mockable

* refactor(vite): remove bootstrap shim

* chore(deps): drop react-scripts

* chore(deps): remove source-map-explorer

Source maps do not currently work with MUI and Vite:
https://github.com/vitejs/vite/issues/15012

Because of this, source map utilities are currently removed.

* refactor(vite): use TypeScript for Vite config

* chore(actions): update actions config for new paths

* fix(service-worker): use VITE_HOMEPAGE for service worker resolution

* fix(vercel): use quotes for build command

* fix(vite): use import.meta.env.MODE

* fix(service-worker): use correct definition for publicUrl

* feat(vite): use vite-plugin-pwa

* fix(pwa): make update prompt work

* fix(types): use vite/client types

* docs(readme): update building instructions

* refactor(vite): simplify theme loading workaround

* refactor(vite): use manifest object

* docs(readme): update tool references

* chore(deps): run `npm audit fix`

* fix(vite): make syntax highlighter work consistently

See: https://github.com/react-syntax-highlighter/react-syntax-highlighter/issues/513

* fix(pwa): remove manifest.json references

* refactor(deps): remove jest references

* refactor(types): remove react-scripts reference

* chore(deps): use TypeScript 5

* refactor(tests): improve persisted storage mocking
2024-03-12 21:44:43 -05:00
dependabot[bot]
72bc66a340
chore(deps): bump ip from 1.1.8 to 1.1.9 (#229)
Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9.
- [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9)

---
updated-dependencies:
- dependency-name: ip
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-02-20 21:56:51 -06:00
Jeremy Kahn
94a4b2fb2e
refactor(services): Standardize services and lib organization (#226)
* refactor(Notification): use instance methods
* refactor(Audio): move to lib layer
* refactor(EncryptionService): rename instance to encryption
* refactor(ConnectionTest): move to lib
* refactor(FileTransfer): move to lib
* refactor(PeerRoom): move to lib
* refactor(sleep): move to lib
* refactor(type-guards): move to lib
* refactor(SerializationService): use standard instance name
* refactor(SettingsService): use standard instance name
2024-01-28 20:46:59 -06:00
dependabot[bot]
4d6d1482f2
chore(deps): bump follow-redirects from 1.15.3 to 1.15.4 (#224)
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.15.3 to 1.15.4.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.3...v1.15.4)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-01-11 09:26:02 -06:00
105 changed files with 20595 additions and 24592 deletions

View File

@ -1,5 +1,6 @@
{
"extends": ["react-app"],
"extends": ["react-app", "plugin:prettier/recommended"],
"plugins": ["prettier"],
"rules": {
"import/order": [
"error",

25
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: CI
on:
push:
branches:
- '**'
jobs:
test_and_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npm test
- name: 'Build web app artifacts'
run: npm run build
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist

33
.github/workflows/deploy.yml vendored Normal file
View File

@ -0,0 +1,33 @@
name: Deploy to Github Pages
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deployment:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Build
run: |
npm ci
npm run build
- name: Deploy
uses: peaceiris/actions-gh-pages@v4
with:
deploy_key: ${{ secrets.DEPLOY_KEY }}
publish_dir: ./dist
force_orphan: true

634
.gitignore vendored
View File

@ -9,7 +9,9 @@
/coverage
# production
/build
/dist
/dev-dist
/src/config/rtcConfig.ts
# misc
.DS_Store
@ -25,3 +27,633 @@ yarn-error.log*
# Editors
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

2
.nvmrc
View File

@ -1 +1 @@
v18.12.1
v20.12.1

3
.vim/coc-settings.json Normal file
View File

@ -0,0 +1,3 @@
{
"eslint.experimental.useFlatConfig": false
}

View File

@ -125,30 +125,42 @@ As well as the following [standard `<iframe />` attributes](https://developer.mo
- `referrerpolicy`
- `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:
### `npm dev`
#### `npm dev`
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.\
Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
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.
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.
@ -160,16 +172,36 @@ RemnantChat is designed to be forked and self-hosted. If you would like to chang
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
Assuming you are hosting RemnantChat on [GitHub Pages](https://pages.github.com/):
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/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
##### 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).
#### Deployment After Forking
> > > > > > > 811c35b (main: prelim update to documention.)
##### 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
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
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).
@ -190,7 +222,11 @@ RemnantChat works on iOS Safari, but browser-level bugs often prevent peers from
check your `about:config` settings and ensure that `media.peerconnection.enabled` is **enabled**.
##### Security
#### Offered files can't be downloaded from peers
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

View File

@ -1,26 +1,21 @@
<!DOCTYPE html>
<!doctype html>
<html lang="en">
<head>
<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="theme-color" content="#000000" />
<meta
name="description"
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
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.
Notice the use of in the tags above.
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.
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.
Learn how to configure a non-root public URL by running `npm run build`.
-->
@ -69,5 +64,6 @@
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

91
manifest.ts Normal file
View 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',
},
],
},
],
}

42356
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -4,78 +4,73 @@
"homepage": "https://remnant.chat",
"author": "Shiloh",
"license": "GPL-2.0-or-later",
"type": "module",
"dependencies": {
"@emotion/react": "^11.10.0",
"@emotion/styled": "^11.10.0",
"@mui/icons-material": "^5.8.4",
"@mui/material": "^5.14.12",
"@emotion/react": "^11.13.3",
"@emotion/styled": "^11.13.0",
"@mui/icons-material": "^5.16.7",
"@mui/material": "^5.16.7",
"@react-hook/debounce": "^4.0.0",
"@react-hook/window-size": "^3.1.1",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^28.1.6",
"@types/node": "^18.18.4",
"@types/react": "^18.2.25",
"@types/react-dom": "^18.0.6",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^15.0.7",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^18.19.55",
"buffer": "^6.0.3",
"classnames": "^2.3.1",
"detectincognitojs": "^1.1.2",
"detectincognitojs": "^1.3.5",
"fast-memoize": "^2.5.2",
"file-saver": "^2.0.5",
"fun-animal-names": "^0.1.1",
"idb-chunk-store": "^1.0.1",
"localforage": "^1.10.0",
"modern-normalize": "^2.0.0",
"mui-markdown": "^0.5.5",
"querystring": "^0.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-file-reader-input": "^2.0.0",
"react-git-info": "^2.0.1",
"react-markdown": "^8.0.3",
"react-qrcode-logo": "^2.8.0",
"react-router-dom": "^6.16.0",
"react-scripts": "5.0.1",
"react-qrcode-logo": "^2.10.0",
"react-router-dom": "^6.27.0",
"react-syntax-highlighter": "^15.5.0",
"react-youtube": "^10.1.0",
"readable-web-to-node-stream": "^3.0.2",
"remark-gfm": "^3.0.1",
"sass": "^1.54.3",
"sdp": "^3.2.0",
"secure-file-transfer": "^0.0.7",
"secure-file-transfer": "^0.0.8",
"streamsaver": "^2.0.6",
"trystero": "^0.15.0",
"trystero": "^0.20.0",
"typeface-public-sans": "^1.1.13",
"typeface-roboto": "^1.1.13",
"typescript": "^4.9.5",
"typescript": "^5.6.3",
"uuid": "^8.3.2",
"vite-plugin-babel-macros": "^1.0.6",
"web-vitals": "^2.1.4",
"webrtc-adapter": "^8.2.2"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "cross-env REACT_APP_HOMEPAGE=$(npm pkg get homepage) react-scripts start",
"start": "cross-env VITE_HOMEPAGE=$(npm pkg get homepage) vite --port 3000",
"start:tracker": "bittorrent-tracker",
"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:app": "cross-env REACT_APP_HOMEPAGE=$(npm pkg get homepage) react-scripts build",
"build:sdk": "parcel build sdk/sdk.ts --dist-dir build --no-content-hash",
"build:app": "cross-env VITE_HOMEPAGE=$(npm pkg get homepage) vite build",
"build:sdk": "parcel build sdk/sdk.ts --no-content-hash",
"build:sdk:watch": "nodemon --exec \"npm run build:sdk\"",
"test": "react-scripts test",
"test": "vitest",
"prepare": "husky install",
"prettier": "prettier 'src/**/*.js' --write",
"prettier": "prettier \"**/*.{ts,tsx}\" --write",
"lint": "eslint src --max-warnings=0"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
"react-app"
]
},
"engines": {
"node": "18.12.1",
"npm": "8.19.2"
"node": "20.12.1",
"npm": "10.5.0"
},
"browserslist": {
"production": [
@ -92,6 +87,8 @@
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@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-syntax-highlighter": "^15.5.5",
"@types/streamsaver": "^2.0.1",
@ -99,28 +96,34 @@
"@types/webtorrent": "^0.109.3",
"@typescript-eslint/eslint-plugin": "^5.33.0",
"@typescript-eslint/parser": "^5.33.0",
"@vitejs/plugin-react": "^4.2.1",
"autoprefixer": "^10.4.8",
"bittorrent-tracker": "^9.19.0",
"cross-env": "^7.0.3",
"eslint": "^8.21.0",
"eslint-config-prettier": "^9.1.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.1",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"husky": "^8.0.1",
"jsdom": "^24.0.0",
"mprocs": "^0.6.4",
"nodemon": "^3.0.1",
"parcel": "^2.10.0",
"postcss": "^8.4.31",
"prettier": "^2.7.1",
"pretty-quick": "^3.1.3",
"prettier": "^3.2.5",
"pretty-quick": "^4.0.0",
"process": "^0.11.10",
"serve": "^14.1.2",
"source-map-explorer": "^2.5.3",
"tailwindcss": "^3.1.8",
"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": {
"ipfs-core": "npm:dry-uninstall",
@ -134,10 +137,5 @@
"resolve-url-loader": {
"postcss": "8.4.31"
}
},
"jest": {
"transformIgnorePatterns": [
"node_modules/(?!trystero)/"
]
}
}

View File

@ -1,6 +0,0 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}

View File

@ -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

View File

@ -1,9 +1,10 @@
import { vi } from 'vitest'
import { act, render } from '@testing-library/react'
import localforage from 'localforage'
import persistedStorage from 'localforage'
import { PersistedStorageKeys } from 'models/storage'
import {
mockSerializationService,
mockSerialization,
mockSerializedPrivateKey,
mockSerializedPublicKey,
} from 'test-utils/mocks/mockSerializationService'
@ -11,30 +12,16 @@ import { userSettingsStubFactory } from 'test-utils/stubs/userSettings'
import Bootstrap, { BootstrapProps } from './Bootstrap'
const mockPersistedStorage =
jest.createMockFromModule<jest.Mock<typeof localforage>>('localforage')
const mockGetItem = jest.fn()
const mockSetItem = jest.fn()
vi.mock('localforage')
const userSettingsStub = userSettingsStubFactory()
beforeEach(() => {
mockGetItem.mockImplementation(() => Promise.resolve(null))
mockSetItem.mockImplementation((data: any) => Promise.resolve(data))
})
const renderBootstrap = async (overrides: Partial<BootstrapProps> = {}) => {
Object.assign(mockPersistedStorage, {
getItem: mockGetItem,
setItem: mockSetItem,
})
render(
<Bootstrap
persistedStorage={mockPersistedStorage as any as typeof localforage}
persistedStorage={persistedStorage}
initialUserSettings={userSettingsStub}
serializationService={mockSerializationService}
serializationService={mockSerialization}
{...overrides}
/>
)
@ -51,7 +38,9 @@ test('renders', async () => {
test('checks persistedStorage for user settings', async () => {
await renderBootstrap()
expect(mockGetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS)
expect(persistedStorage.getItem).toHaveBeenCalledWith(
PersistedStorageKeys.USER_SETTINGS
)
})
test('updates persisted user settings', async () => {
@ -59,14 +48,17 @@ test('updates persisted user settings', async () => {
initialUserSettings: { ...userSettingsStub, userId: 'abc123' },
})
expect(mockSetItem).toHaveBeenCalledWith(PersistedStorageKeys.USER_SETTINGS, {
colorMode: 'dark',
userId: 'abc123',
customUsername: '',
playSoundOnNewMessage: true,
showNotificationOnNewMessage: true,
showActiveTypingStatus: true,
publicKey: mockSerializedPublicKey,
privateKey: mockSerializedPrivateKey,
})
expect(persistedStorage.setItem).toHaveBeenCalledWith(
PersistedStorageKeys.USER_SETTINGS,
{
colorMode: 'dark',
userId: 'abc123',
customUsername: '',
playSoundOnNewMessage: true,
showNotificationOnNewMessage: true,
showActiveTypingStatus: true,
publicKey: mockSerializedPublicKey,
privateKey: mockSerializedPrivateKey,
}
)
})

View File

@ -6,8 +6,8 @@ import {
Navigate,
} from 'react-router-dom'
import localforage from 'localforage'
import { useRegisterSW } from 'virtual:pwa-register/react'
import * as serviceWorkerRegistration from 'serviceWorkerRegistration'
import { StorageContext } from 'contexts/StorageContext'
import { SettingsContext } from 'contexts/SettingsContext'
import { homepageUrl, routes } from 'config/routes'
@ -27,15 +27,12 @@ import {
PostMessageEvent,
PostMessageEventName,
} from 'models/sdk'
import {
serializationService as serializationServiceInstance,
SerializedUserSettings,
} from 'services/Serialization'
import { serialization, SerializedUserSettings } from 'services/Serialization'
export interface BootstrapProps {
persistedStorage?: typeof localforage
initialUserSettings: UserSettings
serializationService?: typeof serializationServiceInstance
serializationService?: typeof serialization
}
const configListenerTimeout = 3000
@ -82,7 +79,7 @@ const Bootstrap = ({
description: 'Persisted settings data for remnantchat',
}),
initialUserSettings,
serializationService = serializationServiceInstance,
serializationService = serialization,
}: BootstrapProps) => {
const queryParams = useMemo(
() => new URLSearchParams(window.location.search),
@ -90,16 +87,11 @@ const Bootstrap = ({
)
const [persistedStorage] = useState(persistedStorageProp)
const [appNeedsUpdate, setAppNeedsUpdate] = useState(false)
const [hasLoadedSettings, setHasLoadedSettings] = useState(false)
const [userSettings, setUserSettings] =
useState<UserSettings>(initialUserSettings)
const { userId } = userSettings
const handleServiceWorkerUpdate = () => {
setAppNeedsUpdate(true)
}
const persistUserSettings = useCallback(
async (newUserSettings: UserSettings) => {
if (queryParams.has(QueryParamKeys.IS_EMBEDDED)) {
@ -117,9 +109,9 @@ const Bootstrap = ({
[persistedStorageProp, queryParams, serializationService, userSettings]
)
useEffect(() => {
serviceWorkerRegistration.register({ onUpdate: handleServiceWorkerUpdate })
}, [])
const {
needRefresh: [appNeedsUpdate],
} = useRegisterSW()
useEffect(() => {
;(async () => {

View File

@ -3,7 +3,7 @@ import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography'
import { v4 as uuid } from 'uuid'
import { encryptionService } from 'services/Encryption'
import { encryption } from 'services/Encryption'
import {
EnvironmentUnsupportedDialog,
isEnvironmentSupported,
@ -13,8 +13,7 @@ import { ColorMode, UserSettings } from 'models/settings'
import type { BootstrapProps } from './Bootstrap'
// @ts-expect-error
const Bootstrap = lazy(() => import('./Bootstrap.js'))
const Bootstrap = lazy(() => import('./Bootstrap'))
export interface InitProps extends Omit<BootstrapProps, 'initialUserSettings'> {
getUuid?: typeof uuid
@ -32,8 +31,7 @@ const Init = ({ getUuid = uuid, ...props }: InitProps) => {
if (userSettings !== null) return
try {
const { publicKey, privateKey } =
await encryptionService.generateKeyPair()
const { publicKey, privateKey } = await encryption.generateKeyPair()
setUserSettings({
userId: getUuid(),

View File

@ -1,16 +1,25 @@
import { useState, useEffect } from 'react'
import Slider from '@mui/material/Slider'
import Box from '@mui/material/Box'
import Paper from '@mui/material/Paper'
import ListItemIcon from '@mui/material/ListItemIcon'
import VolumeUp from '@mui/icons-material/VolumeUp'
import VolumeDown from '@mui/icons-material/VolumeDown'
import VolumeMute from '@mui/icons-material/VolumeMute'
import VolumeUpIcon from '@mui/icons-material/VolumeUp'
import VolumeDownIcon from '@mui/icons-material/VolumeDown'
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 {
audioEl: HTMLAudioElement
audioChannelName: AudioChannelName
}
export const AudioVolume = ({ audioEl }: AudioVolumeProps) => {
export const AudioVolume = ({
audioEl,
audioChannelName,
}: AudioVolumeProps) => {
const [audioVolume, setAudioVolume] = useState(audioEl.volume)
useEffect(() => {
@ -32,27 +41,48 @@ export const AudioVolume = ({ audioEl }: AudioVolumeProps) => {
const formatLabelValue = () => `${Math.round(audioVolume * 100)}%`
let VolumeIcon = VolumeUp
let VolumeIcon = VolumeUpIcon
if (audioVolume === 0) {
VolumeIcon = VolumeMute
VolumeIcon = VolumeMuteIcon
} else if (audioVolume < 0.5) {
VolumeIcon = VolumeDown
VolumeIcon = VolumeDownIcon
}
return (
<Box sx={{ display: 'flex', pt: 1, pr: 3, alignItems: 'center' }}>
<ListItemIcon>
<VolumeIcon sx={{ cursor: 'pointer' }} onClick={handleIconClick} />
<Paper
sx={{
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>
<Slider
aria-label="Volume"
getAriaValueText={formatLabelValue}
valueLabelFormat={formatLabelValue}
valueLabelDisplay="auto"
onChange={handleSliderChange}
value={audioVolume * 100}
></Slider>
</Box>
<Box display="flex" width={1}>
<Slider
aria-label="Volume"
getAriaValueText={formatLabelValue}
valueLabelFormat={formatLabelValue}
valueLabelDisplay="auto"
onChange={handleSliderChange}
value={audioVolume * 100}
></Slider>
</Box>
</Paper>
)
}

View File

@ -1,5 +1,4 @@
import { HTMLAttributes, useRef, useEffect, useState, useContext } from 'react'
import cx from 'classnames'
import Box from '@mui/material/Box'
import useTheme from '@mui/material/styles/useTheme'
@ -12,11 +11,7 @@ export interface ChatTranscriptProps extends HTMLAttributes<HTMLDivElement> {
userId: string
}
export const ChatTranscript = ({
className,
messageLog,
userId,
}: ChatTranscriptProps) => {
export const ChatTranscript = ({ messageLog, userId }: ChatTranscriptProps) => {
const { showRoomControls } = useContext(ShellContext)
const theme = useTheme()
const boxRef = useRef<HTMLDivElement>(null)
@ -66,11 +61,13 @@ export const ChatTranscript = ({
return (
<Box
ref={boxRef}
className={cx('ChatTranscript', className)}
className="ChatTranscript"
sx={{
display: 'flex',
flexDirection: 'column',
py: transcriptMinPadding,
flexGrow: 1,
overflow: 'auto',
pb: transcriptMinPadding,
pt: showRoomControls ? theme.spacing(10) : theme.spacing(2),
px: `max(${transcriptPaddingX}, ${transcriptMinPadding})`,
transition: `padding-top ${theme.transitions.duration.short}ms ${theme.transitions.easing.easeInOut}`,

View 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')({})

View File

@ -1,9 +1,10 @@
import { useContext, useEffect, useRef, useState } from 'react'
import { TorrentFile } from 'webtorrent'
import Box from '@mui/material/Box'
import CircularProgress from '@mui/material/CircularProgress'
import Typography from '@mui/material/Typography'
import { fileTransfer } from 'services/FileTransfer'
import { fileTransfer } from 'lib/FileTransfer'
import { ShellContext } from 'contexts/ShellContext'
type TorrentFiles = Awaited<ReturnType<typeof fileTransfer.download>>
@ -16,34 +17,65 @@ interface InlineFileProps {
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) => {
const containerRef = useRef(null)
const [didRenderingMediaFail, setDidRenderingMediaFail] = useState(false)
const [isMediaSupported, setIsMediaSupported] = useState(true)
const shellContext = useContext(ShellContext)
useEffect(() => {
;(async () => {
const { current: container } = containerRef
const { current: container } = containerRef
if (!container) return
if (!container) return
try {
file.appendTo(container)
} catch (e) {
console.error(e)
setDidRenderingMediaFail(true)
}
})()
const { name } = file
const fileNameExtension = name.split('.').pop() ?? ''
if (!supportedMediaExtensions.includes(`.${fileNameExtension}`)) {
setIsMediaSupported(false)
return
}
try {
file.appendTo(container)
} catch (e) {
console.error(e)
setDidRenderingMediaFail(true)
}
}, [file, containerRef, shellContext.roomId])
return (
<div ref={containerRef}>
<Box ref={containerRef} sx={{ '& img': { maxWidth: '100%' } }}>
{!isMediaSupported && (
<Typography sx={{ fontStyle: 'italic' }}>
Media preview not supported
</Typography>
)}
{didRenderingMediaFail && (
<Typography sx={{ fontStyle: 'italic' }}>
Media failed to render
</Typography>
)}
</div>
</Box>
)
}

View File

@ -1,3 +0,0 @@
.Message
pre
overflow: auto

View File

@ -5,20 +5,10 @@ import Box from '@mui/material/Box'
import Tooltip from '@mui/material/Tooltip'
import Typography, { TypographyProps } from '@mui/material/Typography'
import Link, { LinkProps } from '@mui/material/Link'
import styled from '@mui/material/styles/styled'
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'
// @ts-ignore
import { CodeProps } from 'react-markdown/lib/ast-to-react'
// @ts-ignore
import remarkGfm from 'remark-gfm'
import {
@ -32,7 +22,7 @@ import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
import { InlineMedia } from './InlineMedia'
import './Message.sass'
const StyledMarkdown = styled(Markdown)({})
export interface MessageProps {
message: IMessage | I_InlineMedia
@ -154,13 +144,26 @@ export const Message = ({ message, showAuthor, userId }: MessageProps) => {
) : isYouTubeLink(message) ? (
<YouTube videoId={getYouTubeVideoId(message.text)} />
) : (
<Markdown
<StyledMarkdown
components={componentMap}
remarkPlugins={[remarkGfm]}
linkTarget="_blank"
sx={{
'& pre': {
overflow: 'auto',
},
'& ol': {
pl: 2,
listStyleType: 'decimal',
},
'& ul': {
pl: 2,
listStyleType: 'disc',
},
}}
>
{message.text}
</Markdown>
</StyledMarkdown>
)}
</Box>
</Tooltip>

View File

@ -14,7 +14,7 @@ import ArrowUpward from '@mui/icons-material/ArrowUpward'
import { messageCharacterSizeLimit } from 'config/messaging'
import { SettingsContext } from 'contexts/SettingsContext'
import classNames from 'classnames'
import { Form } from 'components/Elements'
interface MessageFormProps {
onMessageSubmit: (message: string) => void
@ -76,12 +76,17 @@ export const MessageForm = ({
}
return (
<form
<Form
onSubmit={handleMessageSubmit}
className={classNames({
'pt-4 px-4': showActiveTypingStatus,
'p-4': !showActiveTypingStatus,
})}
sx={{
...(showActiveTypingStatus && {
pt: 2,
px: 2,
}),
...(!showActiveTypingStatus && {
p: 2,
}),
}}
>
<Stack direction="row" spacing={2}>
<FormControl fullWidth>
@ -110,6 +115,6 @@ export const MessageForm = ({
<ArrowUpward />
</Fab>
</Stack>
</form>
</Form>
)
}

View File

@ -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 InputAdornment from '@mui/material/InputAdornment'
import IconButton from '@mui/material/IconButton'
import TextField from '@mui/material/TextField'
import Dialog from '@mui/material/Dialog'
import DialogActions from '@mui/material/DialogActions'
import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
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 {
isOpen: boolean
@ -17,16 +25,36 @@ export const PasswordPrompt = ({
onPasswordEntered,
}: PasswordPromptProps) => {
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>) => {
event.preventDefault()
onPasswordEntered(password)
}
const handleClickShowPassword = () => {
setShowPassword(!showPassword)
}
const handlePasswordChange = (e: ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value)
}
const handleGoBackClick = () => {
navigate(-1)
}
const passwordToggleLabel = showPassword ? 'Hide password' : 'Show password'
return (
<Dialog open={isOpen}>
<form onSubmit={handleFormSubmit}>
@ -38,7 +66,7 @@ export const PasswordPrompt = ({
impossible to know if the password you enter will match the password
entered by other peers.
</DialogContentText>
<DialogContentText>
<DialogContentText sx={{ mb: 2 }}>
If there is a mismatch, you will be in the room but be unable to
connect to others. An error will not be shown.
</DialogContentText>
@ -47,14 +75,33 @@ export const PasswordPrompt = ({
margin="dense"
id="password"
label="Password"
type="password"
type={showPassword ? 'text' : 'password'}
fullWidth
variant="standard"
variant="outlined"
value={password}
onChange={handlePasswordChange}
InputProps={{
endAdornment: (
<InputAdornment position="end">
<Tooltip title={passwordToggleLabel}>
<IconButton
aria-label={passwordToggleLabel}
onClick={handleClickShowPassword}
>
{showPassword ? <Visibility /> : <VisibilityOff />}
</IconButton>
</Tooltip>
</InputAdornment>
),
}}
/>
</DialogContent>
<DialogActions>
{!isEmbedded && (
<Button color="secondary" onClick={handleGoBackClick}>
Go back
</Button>
)}
<Button type="submit" disabled={password.length === 0}>
Submit
</Button>

View File

@ -19,7 +19,7 @@ export const usePeerNameDisplay = () => {
const getCustomUsername = (userId: string) =>
isPeerSelf(userId)
? selfCustomUsername
: getPeer(userId)?.customUsername ?? ''
: (getPeer(userId)?.customUsername ?? '')
const getFriendlyName = (userId: string) => {
const customUsername = getCustomUsername(userId)

View File

@ -2,7 +2,7 @@ import { useEffect, useState } from 'react'
import { materialDark } from 'react-syntax-highlighter/dist/esm/styles/prism'
import { PrismAsyncLight as SyntaxHighlighter } from 'react-syntax-highlighter'
import { CopyableBlock } from 'components/CopyableBlock/CopyableBlock'
import { encryptionService } from 'services/Encryption/Encryption'
import { encryption } from 'services/Encryption/Encryption'
interface PeerPublicKeyProps {
publicKey: CryptoKey
@ -13,7 +13,7 @@ export const PublicKey = ({ publicKey }: PeerPublicKeyProps) => {
useEffect(() => {
;(async () => {
setPublicKeyString(await encryptionService.stringifyCryptoKey(publicKey))
setPublicKeyString(await encryption.stringifyCryptoKey(publicKey))
})()
}, [publicKey])

View File

@ -28,20 +28,20 @@ export const MediaButton = forwardRef<HTMLButtonElement, MediaButtonProps>(
},
}
: isActive
? {
color: theme.palette.common.white,
background: theme.palette.success.main,
'&:hover': {
background: theme.palette.success.dark,
},
}
: {
color: theme.palette.common.black,
background: theme.palette.grey[400],
'&:hover': {
background: theme.palette.grey[500],
},
}
? {
color: theme.palette.common.white,
background: theme.palette.success.main,
'&:hover': {
background: theme.palette.success.dark,
},
}
: {
color: theme.palette.common.black,
background: theme.palette.grey[400],
'&:hover': {
background: theme.palette.grey[500],
},
}
}
/>
)

View File

@ -3,7 +3,7 @@ import Paper from '@mui/material/Paper'
import Tooltip from '@mui/material/Tooltip'
import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { VideoStreamType } from 'models/chat'
import { StreamType } from 'models/chat'
import { SelectedPeerStream } from './RoomVideoDisplay'
@ -13,13 +13,13 @@ interface PeerVideoProps {
numberOfVideos: number
onVideoClick?: (
userId: string,
videoStreamType: VideoStreamType,
streamType: StreamType,
videoStream: MediaStream
) => void
selectedPeerStream: SelectedPeerStream | null
userId: string
videoStream: MediaStream
videoStreamType: VideoStreamType
streamType: StreamType
}
// Adapted from https://www.geeksforgeeks.org/find-the-next-perfect-square-greater-than-a-given-number/
@ -37,7 +37,7 @@ export const PeerVideo = ({
userId,
selectedPeerStream,
videoStream,
videoStreamType,
streamType,
}: PeerVideoProps) => {
const videoRef = useRef<HTMLVideoElement>(null)
@ -47,13 +47,14 @@ export const PeerVideo = ({
video.autoplay = true
video.srcObject = videoStream
video.muted = true
}, [videoRef, videoStream])
const cols = Math.sqrt(nextPerfectSquare(numberOfVideos - 1))
const rows = Math.ceil(numberOfVideos / cols)
const handleVideoClick = () => {
onVideoClick?.(userId, videoStreamType, videoStream)
onVideoClick?.(userId, streamType, videoStream)
}
return (

View File

@ -1,12 +1,13 @@
import { vi } from 'vitest'
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 { MemoryRouter as Router, Route, Routes } from 'react-router-dom'
import { userSettingsContextStubFactory } from 'test-utils/stubs/settingsContext'
import { mockEncryptionService } from 'test-utils/mocks/mockEncryptionService'
import { SettingsContext } from 'contexts/SettingsContext'
import { Time } from 'lib/Time'
import { Room, RoomProps } from './'
@ -17,13 +18,17 @@ const userSettingsStub = userSettingsContextStubFactory({
userId: mockUserId,
})
window.AudioContext = jest.fn().mockImplementation()
const mockGetUuid = jest.fn()
const mockMessagedSender = jest
.fn()
.mockImplementation(() => Promise.resolve([]))
window.AudioContext = vi.fn().mockImplementation(() => {})
const mockGetUuid = vi.fn()
const mockMessagedSender = vi.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: () => ({
makeAction: () => [mockMessagedSender, () => {}, () => {}],
ping: () => Promise.resolve(0),
@ -53,10 +58,14 @@ const RouteStub = ({ children }: PropsWithChildren) => {
)
}
jest.useFakeTimers().setSystemTime(100)
const RoomStub = (props: RoomProps) => {
return <Room encryptionService={mockEncryptionService} {...props} />
return (
<Room
encryptionService={mockEncryptionService}
timeService={mockTimeService}
{...props}
/>
)
}
describe('Room', () => {
@ -89,9 +98,7 @@ describe('Room', () => {
const sendButton = screen.getByLabelText('Send')
const textInput = screen.getByPlaceholderText('Your message')
await waitFor(() => {
userEvent.type(textInput, 'hello')
})
await userEvent.type(textInput, 'hello')
expect(sendButton).not.toBeDisabled()
})
@ -106,13 +113,8 @@ describe('Room', () => {
const sendButton = screen.getByLabelText('Send')
const textInput = screen.getByPlaceholderText('Your message')
await waitFor(() => {
userEvent.type(textInput, 'hello')
})
await waitFor(() => {
userEvent.click(sendButton)
})
await userEvent.type(textInput, 'hello')
await userEvent.click(sendButton)
expect(textInput).toHaveValue('')
})
@ -131,18 +133,13 @@ describe('Room', () => {
const sendButton = screen.getByLabelText('Send')
const textInput = screen.getByPlaceholderText('Your message')
await waitFor(() => {
userEvent.type(textInput, 'hello')
})
await waitFor(() => {
userEvent.click(sendButton)
})
await userEvent.type(textInput, 'hello')
await userEvent.click(sendButton)
expect(mockMessagedSender).toHaveBeenCalledWith({
authorId: mockUserId,
text: 'hello',
timeSent: 100,
timeSent: mockNowTime,
id: 'abc123',
})
})

View File

@ -8,11 +8,12 @@ import { v4 as uuid } from 'uuid'
import { rtcConfig } from 'config/rtcConfig'
import { trackerUrls } from 'config/trackerUrls'
import { time } from 'lib/Time'
import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext'
import { MessageForm } from 'components/MessageForm'
import { ChatTranscript } from 'components/ChatTranscript'
import { encryptionService as encryptionServiceInstance } from 'services/Encryption'
import { encryption } from 'services/Encryption'
import { SettingsContext } from 'contexts/SettingsContext'
import { useRoom } from './useRoom'
@ -30,13 +31,15 @@ export interface RoomProps {
password?: string
roomId: string
userId: string
encryptionService?: typeof encryptionServiceInstance
encryptionService?: typeof encryption
timeService?: typeof time
}
export function Room({
appId = `${encodeURI(window.location.origin)}_${process.env.REACT_APP_NAME}`,
appId = `${encodeURI(window.location.origin)}_${process.env.VITE_NAME}`,
getUuid = uuid,
encryptionService = encryptionServiceInstance,
encryptionService = encryption,
timeService = time,
roomId,
password,
userId,
@ -57,10 +60,10 @@ export function Room({
} = useRoom(
{
appId,
trackerUrls,
relayUrls: trackerUrls,
rtcConfig,
password,
trackerRedundancy: 4,
relayRedundancy: 4,
},
{
roomId,
@ -68,6 +71,7 @@ export function Room({
getUuid,
publicKey,
encryptionService,
timeService,
}
)
@ -153,11 +157,7 @@ export function Room({
height: landscape ? '100%' : '40%',
}}
>
<ChatTranscript
messageLog={messageLog}
userId={userId}
className="grow overflow-auto"
/>
<ChatTranscript messageLog={messageLog} userId={userId} />
<Divider />
<Box>
<MessageForm

View File

@ -9,7 +9,7 @@ import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import Tooltip from '@mui/material/Tooltip'
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
import { PeerRoom } from 'lib/PeerRoom'
import { useRoomAudio } from './useRoomAudio'
import { MediaButton } from './MediaButton'

View File

@ -6,7 +6,9 @@ import Tooltip from '@mui/material/Tooltip'
import CircularProgress from '@mui/material/CircularProgress'
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 { MediaButton } from './MediaButton'
@ -73,12 +75,12 @@ export function RoomFileUploadControls({
px: 1,
}}
>
<input
<Input
multiple
ref={fileInputRef}
type="file"
id="file-upload"
className="hidden"
sx={{ display: 'none' }}
onChange={handleFileSelect}
/>
<Tooltip

View File

@ -3,7 +3,7 @@ import ScreenShare from '@mui/icons-material/ScreenShare'
import StopScreenShare from '@mui/icons-material/StopScreenShare'
import Tooltip from '@mui/material/Tooltip'
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
import { PeerRoom } from 'lib/PeerRoom'
import { useRoomScreenShare } from './useRoomScreenShare'
import { MediaButton } from './MediaButton'

View File

@ -9,7 +9,7 @@ import Menu from '@mui/material/Menu'
import MenuItem from '@mui/material/MenuItem'
import Tooltip from '@mui/material/Tooltip'
import { PeerRoom } from 'services/PeerRoom/PeerRoom'
import { PeerRoom } from 'lib/PeerRoom'
import { useRoomVideo } from './useRoomVideo'
import { MediaButton } from './MediaButton'

View File

@ -4,7 +4,7 @@ import Paper from '@mui/material/Paper'
import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext'
import { Peer, VideoStreamType } from 'models/chat'
import { Peer, StreamType } from 'models/chat'
import { PeerVideo } from './PeerVideo'
@ -16,7 +16,7 @@ interface PeerWithVideo {
export interface SelectedPeerStream {
peerId: string
videoStreamType: VideoStreamType
streamType: StreamType
videoStream: MediaStream
}
@ -105,13 +105,13 @@ export const RoomVideoDisplay = ({
const handleVideoClick = (
peerId: string,
videoStreamType: VideoStreamType,
streamType: StreamType,
videoStream: MediaStream
) => {
if (selectedPeerStream?.videoStream === videoStream) {
setSelectedPeerStream(null)
} else if (numberOfVideos > 1) {
setSelectedPeerStream({ peerId, videoStreamType, videoStream })
setSelectedPeerStream({ peerId, streamType, videoStream })
}
}
@ -139,7 +139,7 @@ export const RoomVideoDisplay = ({
userId={selectedPeerStream.peerId}
selectedPeerStream={selectedPeerStream}
videoStream={selectedPeerStream.videoStream}
videoStreamType={selectedPeerStream.videoStreamType}
streamType={selectedPeerStream.streamType}
/>
</Box>
)}
@ -168,7 +168,7 @@ export const RoomVideoDisplay = ({
userId={userId}
selectedPeerStream={selectedPeerStream}
videoStream={selfVideoStream}
videoStreamType={VideoStreamType.WEBCAM}
streamType={StreamType.WEBCAM}
/>
)}
{selfScreenStream && (
@ -179,7 +179,7 @@ export const RoomVideoDisplay = ({
userId={userId}
selectedPeerStream={selectedPeerStream}
videoStream={selfScreenStream}
videoStreamType={VideoStreamType.SCREEN_SHARE}
streamType={StreamType.SCREEN_SHARE}
/>
)}
{peersWithVideo.map(peerWithVideo => (
@ -191,7 +191,7 @@ export const RoomVideoDisplay = ({
userId={peerWithVideo.peer.userId}
selectedPeerStream={selectedPeerStream}
videoStream={peerWithVideo.videoStream}
videoStreamType={VideoStreamType.WEBCAM}
streamType={StreamType.WEBCAM}
/>
)}
{peerWithVideo.screenStream && (
@ -201,7 +201,7 @@ export const RoomVideoDisplay = ({
userId={peerWithVideo.peer.userId}
selectedPeerStream={selectedPeerStream}
videoStream={peerWithVideo.screenStream}
videoStreamType={VideoStreamType.SCREEN_SHARE}
streamType={StreamType.SCREEN_SHARE}
/>
)}
</Fragment>

View File

@ -1,8 +1,8 @@
import { useCallback, useContext, useEffect, useState } from 'react'
import { ShellContext } from 'contexts/ShellContext'
import { Peer, PeerVerificationState } from 'models/chat'
import { encryptionService as encryptionServiceInstance } from 'services/Encryption'
import { PeerRoom } from 'services/PeerRoom'
import { encryption } from 'services/Encryption'
import { PeerRoom } from 'lib/PeerRoom'
import { PeerActions } from 'models/network'
import { verificationTimeout } from 'config/messaging'
import { usePeerNameDisplay } from 'components/PeerNameDisplay'
@ -10,13 +10,13 @@ import { usePeerNameDisplay } from 'components/PeerNameDisplay'
interface UserPeerVerificationProps {
peerRoom: PeerRoom
privateKey: CryptoKey
encryptionService?: typeof encryptionServiceInstance
encryptionService?: typeof encryption
}
export const usePeerVerification = ({
peerRoom,
privateKey,
encryptionService = encryptionServiceInstance,
encryptionService = encryption,
}: UserPeerVerificationProps) => {
const { updatePeer, peerList, showAlert } = useContext(ShellContext)

View File

@ -1,6 +1,6 @@
import { useContext, useEffect, useMemo, useState } from 'react'
import { BaseRoomConfig } from 'trystero'
import { TorrentRoomConfig } from 'trystero/torrent'
import { RelayConfig } from 'trystero/torrent'
import { v4 as uuid } from 'uuid'
import { useDebounce } from '@react-hook/debounce'
@ -23,16 +23,15 @@ import {
TypingStatus,
Peer,
PeerVerificationState,
AudioChannelName,
} from 'models/chat'
import { getPeerName, usePeerNameDisplay } from 'components/PeerNameDisplay'
import { NotificationService } from 'services/Notification'
import { Audio as AudioService } from 'services/Audio'
import { PeerRoom, PeerHookType } from 'services/PeerRoom'
import { fileTransfer } from 'services/FileTransfer'
import {
AllowedKeyType,
encryptionService as encryptionServiceInstance,
} from 'services/Encryption'
import { Audio } from 'lib/Audio'
import { time } from 'lib/Time'
import { PeerRoom, PeerHookType } from 'lib/PeerRoom'
import { notification } from 'services/Notification'
import { fileTransfer } from 'lib/FileTransfer'
import { AllowedKeyType, encryption } from 'services/Encryption'
import { messageTranscriptSizeLimit } from 'config/messaging'
@ -43,23 +42,25 @@ interface UseRoomConfig {
userId: string
publicKey: CryptoKey
getUuid?: typeof uuid
encryptionService?: typeof encryptionServiceInstance
encryptionService?: typeof encryption
timeService?: typeof time
}
interface UserMetadata {
interface UserMetadata extends Record<string, any> {
userId: string
customUsername: string
publicKeyString: string
}
export function useRoom(
{ password, ...roomConfig }: BaseRoomConfig & TorrentRoomConfig,
{ password, ...roomConfig }: BaseRoomConfig & RelayConfig,
{
roomId,
userId,
publicKey,
getUuid = uuid,
encryptionService = encryptionServiceInstance,
encryptionService = encryption,
timeService = time,
}: UseRoomConfig
) {
const isPrivate = password !== undefined
@ -86,9 +87,7 @@ export function useRoom(
const [messageLog, _setMessageLog] = useState<Array<Message | InlineMedia>>(
[]
)
const [newMessageAudio] = useState(
() => new AudioService(process.env.PUBLIC_URL + '/sounds/new-message.aac')
)
const [newMessageAudio] = useState(() => new Audio('/sounds/new-message.aac'))
const { getDisplayUsername } = usePeerNameDisplay()
@ -240,7 +239,7 @@ export function useRoom(
const unsentMessage: UnsentMessage = {
authorId: userId,
text: message,
timeSent: Date.now(),
timeSent: timeService.now(),
id: getUuid(),
}
@ -251,7 +250,7 @@ export function useRoom(
setMessageLog([
...messageLog,
{ ...unsentMessage, timeReceived: Date.now() },
{ ...unsentMessage, timeReceived: timeService.now() },
])
setIsMessageSending(false)
}
@ -271,7 +270,10 @@ export function useRoom(
userId,
publicKey,
customUsername,
audioState: AudioState.STOPPED,
audioChannelState: {
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
[AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED,
},
videoState: VideoState.STOPPED,
screenShareState: ScreenShareState.NOT_SHARING,
offeredFileId: null,
@ -323,13 +325,14 @@ export function useRoom(
if (userSettings.showNotificationOnNewMessage) {
const displayUsername = getDisplayUsername(message.authorId)
NotificationService.showNotification(
`${displayUsername}: ${message.text}`
)
notification.showNotification(`${displayUsername}: ${message.text}`)
}
}
setMessageLog([...messageLog, { ...message, timeReceived: Date.now() }])
setMessageLog([
...messageLog,
{ ...message, timeReceived: timeService.now() },
])
updatePeer(peerId, { isTyping: false })
})
@ -339,9 +342,8 @@ export function useRoom(
})
;(async () => {
try {
const publicKeyString = await encryptionService.stringifyCryptoKey(
publicKey
)
const publicKeyString =
await encryptionService.stringifyCryptoKey(publicKey)
const promises: Promise<any>[] = [
sendPeerMetadata({ userId, customUsername, publicKeyString }, peerId),
@ -396,7 +398,7 @@ export function useRoom(
const unsentInlineMedia: UnsentInlineMedia = {
authorId: userId,
magnetURI: fileOfferId,
timeSent: Date.now(),
timeSent: timeService.now(),
id: getUuid(),
}
@ -407,7 +409,7 @@ export function useRoom(
setMessageLog([
...messageLog,
{ ...unsentInlineMedia, timeReceived: Date.now() },
{ ...unsentInlineMedia, timeReceived: timeService.now() },
])
setIsMessageSending(false)
}
@ -433,13 +435,16 @@ export function useRoom(
}
if (userSettings.showNotificationOnNewMessage) {
NotificationService.showNotification(
notification.showNotification(
`${getDisplayUsername(inlineMedia.authorId)} shared media`
)
}
}
setMessageLog([...messageLog, { ...inlineMedia, timeReceived: Date.now() }])
setMessageLog([
...messageLog,
{ ...inlineMedia, timeReceived: timeService.now() },
])
})
receiveTypingStatusChange((typingStatus, peerId) => {
@ -449,9 +454,8 @@ export function useRoom(
useEffect(() => {
;(async () => {
const publicKeyString = await encryptionService.stringifyCryptoKey(
publicKey
)
const publicKeyString =
await encryptionService.stringifyCryptoKey(publicKey)
sendPeerMetadata({
customUsername,

View File

@ -2,8 +2,14 @@ import { useContext, useEffect, useCallback, useState } from 'react'
import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network'
import { AudioState, Peer } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
import {
AudioState,
Peer,
AudioChannelName,
PeerAudioChannelState,
StreamType,
} from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
interface UseRoomAudioConfig {
peerRoom: PeerRoom
@ -19,7 +25,7 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
string | null
>(null)
const { peerList, setPeerList, setAudioState, peerAudios, setPeerAudios } =
const { setPeerList, setAudioChannelState, setPeerAudioChannels } =
shellContext
useEffect(() => {
@ -32,29 +38,46 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
})()
}, [audioStream])
const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction<AudioState>(
PeerActions.AUDIO_CHANGE
)
const [sendAudioChange, receiveAudioChange] = peerRoom.makeAction<
Partial<PeerAudioChannelState>
>(PeerActions.AUDIO_CHANGE)
receiveAudioChange((audioState, peerId) => {
const newPeerList = peerList.map(peer => {
const newPeer: Peer = { ...peer }
receiveAudioChange((peerAudioChannelState, peerId) => {
setPeerList(peerList => {
return peerList.map(peer => {
const newPeer: Peer = { ...peer }
if (peer.peerId === peerId) {
newPeer.audioState = audioState
const microphoneAudioChannel =
peerAudioChannelState[AudioChannelName.MICROPHONE]
if (audioState === AudioState.STOPPED) {
deletePeerAudio(peerId)
if (microphoneAudioChannel) {
if (peer.peerId === peerId) {
newPeer.audioChannelState = {
...newPeer.audioChannelState,
...peerAudioChannelState,
}
if (microphoneAudioChannel === AudioState.STOPPED) {
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()
if (audioTracks.length === 0) return
@ -63,7 +86,13 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
audio.srcObject = stream
audio.autoplay = true
setPeerAudios({ ...peerAudios, [peerId]: audio })
setPeerAudioChannels(peerAudioChannels => ({
...peerAudioChannels,
[peerId]: {
...peerAudioChannels[peerId],
[AudioChannelName.MICROPHONE]: audio,
},
}))
})
const cleanupAudio = useCallback(() => {
@ -86,9 +115,19 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
video: false,
})
peerRoom.addStream(newSelfStream)
sendAudioChange(AudioState.PLAYING)
setAudioState(AudioState.PLAYING)
peerRoom.addStream(newSelfStream, null, {
type: StreamType.MICROPHONE,
})
sendAudioChange({
[AudioChannelName.MICROPHONE]: AudioState.PLAYING,
})
setAudioChannelState(prevState => ({
...prevState,
[AudioChannelName.MICROPHONE]: AudioState.PLAYING,
}))
setAudioStream(newSelfStream)
}
} else {
@ -96,8 +135,16 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
cleanupAudio()
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)
}
}
@ -106,11 +153,10 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
audioStream,
cleanupAudio,
isSpeakingToRoom,
peerAudios,
peerRoom,
selectedAudioDeviceId,
sendAudioChange,
setAudioState,
setAudioChannelState,
])
useEffect(() => {
@ -139,27 +185,45 @@ export function useRoomAudio({ peerRoom }: UseRoomAudioConfig) {
video: false,
})
peerRoom.addStream(newSelfStream)
peerRoom.addStream(newSelfStream, null, {
type: StreamType.MICROPHONE,
})
setAudioStream(newSelfStream)
}
const deletePeerAudio = (peerId: string) => {
const newPeerAudios = { ...peerAudios }
delete newPeerAudios[peerId]
setPeerAudios(newPeerAudios)
setPeerAudioChannels(({ ...newPeerAudios }) => {
if (!newPeerAudios[peerId]) {
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) => {
if (audioStream) {
peerRoom.addStream(audioStream, peerId)
peerRoom.addStream(audioStream, peerId, {
type: StreamType.MICROPHONE,
})
}
}
const handleAudioForLeavingPeer = (peerId: string) => {
if (audioStream) {
peerRoom.removeStream(audioStream, peerId)
deletePeerAudio(peerId)
}
deletePeerAudio(peerId)
}
peerRoom.onPeerJoin(PeerHookType.AUDIO, (peerId: string) => {

View File

@ -1,13 +1,12 @@
import { useContext, useEffect, useState } from 'react'
import { sleep } from 'utils'
import { sleep } from 'lib/sleep'
import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network'
import { FileOfferMetadata, Peer } from 'models/chat'
import { PeerRoom, PeerHookType } from 'services/PeerRoom'
import { fileTransfer } from 'services/FileTransfer/index'
import { PeerRoom, PeerHookType } from 'lib/PeerRoom'
import { fileTransfer } from 'lib/FileTransfer'
interface UseRoomFileShareConfig {
onInlineMediaUpload: (files: File[]) => void

View File

@ -1,11 +1,17 @@
import { useContext, useEffect, useCallback, useState } from 'react'
import { isRecord } from 'utils'
import { isRecord } from 'lib/type-guards'
import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network'
import { ScreenShareState, Peer, VideoStreamType } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
import {
ScreenShareState,
Peer,
StreamType,
AudioChannelName,
AudioState,
} from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
interface UseRoomScreenShareConfig {
peerRoom: PeerRoom
@ -16,7 +22,13 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
const roomContext = useContext(RoomContext)
const [isSharingScreen, setIsSharingScreen] = useState(false)
const { peerList, setPeerList, setScreenState } = shellContext
const {
peerList,
setPeerList,
setScreenState,
setAudioChannelState,
setPeerAudioChannels,
} = shellContext
const {
peerScreenStreams,
@ -50,7 +62,7 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
const isScreenShareStream =
isRecord(metadata) &&
'type' in metadata &&
metadata.type === VideoStreamType.SCREEN_SHARE
metadata.type === StreamType.SCREEN_SHARE
if (!isScreenShareStream) return
@ -58,6 +70,33 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
...peerScreenStreams,
[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(() => {
@ -78,8 +117,9 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
})
peerRoom.addStream(displayMedia, null, {
type: VideoStreamType.SCREEN_SHARE,
type: StreamType.SCREEN_SHARE,
})
setSelfScreenStream(displayMedia)
sendScreenShare(ScreenShareState.SHARING)
setScreenState(ScreenShareState.SHARING)
@ -119,15 +159,33 @@ export function useRoomScreenShare({ peerRoom }: UseRoomScreenShareConfig) {
}, [setPeerScreenStreams])
const deletePeerScreen = (peerId: string) => {
const newPeerScreens = { ...peerScreenStreams }
delete newPeerScreens[peerId]
setPeerScreenStreams(newPeerScreens)
setPeerScreenStreams(({ [peerId]: _, ...newPeerScreens }) => {
return 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) => {
if (selfScreenStream) {
peerRoom.addStream(selfScreenStream, peerId, {
type: VideoStreamType.SCREEN_SHARE,
type: StreamType.SCREEN_SHARE,
})
}
}

View File

@ -3,10 +3,9 @@ import { useContext, useEffect, useCallback, useState } from 'react'
import { RoomContext } from 'contexts/RoomContext'
import { ShellContext } from 'contexts/ShellContext'
import { PeerActions } from 'models/network'
import { VideoState, Peer, VideoStreamType } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'services/PeerRoom'
import { isRecord } from 'utils'
import { VideoState, Peer, StreamType } from 'models/chat'
import { PeerRoom, PeerHookType, PeerStreamType } from 'lib/PeerRoom'
import { isRecord } from 'lib/type-guards'
interface UseRoomVideoConfig {
peerRoom: PeerRoom
@ -61,8 +60,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
})
peerRoom.addStream(newSelfStream, null, {
type: VideoStreamType.WEBCAM,
type: StreamType.WEBCAM,
})
setSelfVideoStream(newSelfStream)
}
})()
@ -94,7 +94,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
const isWebcamStream =
isRecord(metadata) &&
'type' in metadata &&
metadata.type === VideoStreamType.WEBCAM
metadata.type === StreamType.WEBCAM
if (!isWebcamStream) return
@ -125,8 +125,9 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
})
peerRoom.addStream(newSelfStream, null, {
type: VideoStreamType.WEBCAM,
type: StreamType.WEBCAM,
})
sendVideoChange(VideoState.PLAYING)
setVideoState(VideoState.PLAYING)
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)
}
@ -207,7 +208,7 @@ export function useRoomVideo({ peerRoom }: UseRoomVideoConfig) {
const handleVideoForNewPeer = (peerId: string) => {
if (selfVideoStream) {
peerRoom.addStream(selfVideoStream, peerId, {
type: VideoStreamType.WEBCAM,
type: StreamType.WEBCAM,
})
}
}

View File

@ -6,7 +6,7 @@ import Circle from '@mui/icons-material/FiberManualRecord'
import { Box } from '@mui/system'
import ReportIcon from '@mui/icons-material/Report'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
import { TrackerConnection } from 'lib/ConnectionTest'
import { ShellContext } from 'contexts/ShellContext'
import { ConnectionTestResults as IConnectionTestResults } from './useConnectionTest'

View File

@ -145,7 +145,9 @@ export const Drawer = ({ isDrawerOpen, onDrawerClose, theme }: DrawerProps) => {
<MuiLink
target="_blank"
rel="noopener"
href={`${process.env.REACT_APP_GITHUB_REPO}/commit/${commit.hash}`}
href={`${import.meta.env.VITE_GITHUB_REPO}/commit/${
commit.hash
}`}
>
{commit.shortHash}
</MuiLink>

View File

@ -12,7 +12,7 @@ const { isSecureContext, RTCDataChannel } = window
const doesSupportWebRtc = RTCDataChannel !== undefined
export const isEnvironmentSupported =
(isSecureContext && doesSupportWebRtc) || process.env.NODE_ENV === 'test'
(isSecureContext && doesSupportWebRtc) || import.meta.env.MODE === 'test'
export const EnvironmentUnsupportedDialog = () => {
const theme = useTheme()

View File

@ -2,12 +2,11 @@ import { forwardRef } from 'react'
import Snackbar from '@mui/material/Snackbar'
import MuiAlert, { AlertProps, AlertColor } from '@mui/material/Alert'
const Alert = forwardRef<HTMLDivElement, AlertProps>(function Alert(
props,
ref
) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />
})
const Alert = forwardRef<HTMLDivElement, AlertProps>(
function Alert(props, ref) {
return <MuiAlert elevation={6} ref={ref} variant="filled" {...props} />
}
)
interface NotificationAreaProps {
alertSeverity: AlertColor

View File

@ -1,3 +0,0 @@
.PeerDownloadFileButton
.MuiCircularProgress-circle
transition: none !important

View File

@ -5,12 +5,11 @@ import Tooltip from '@mui/material/Tooltip'
import Download from '@mui/icons-material/Download'
import CircularProgress from '@mui/material/CircularProgress'
import { isError } from 'utils'
import { fileTransfer } from 'services/FileTransfer/index'
import { isError } from 'lib/type-guards'
import { fileTransfer } from 'lib/FileTransfer'
import { Peer } from 'models/chat'
import { ShellContext } from 'contexts/ShellContext'
import './PeerDownloadFileButton.sass'
import { usePeerNameDisplay } from 'components/PeerNameDisplay/usePeerNameDisplay'
interface PeerDownloadFileButtonProps {
@ -65,6 +64,9 @@ export const PeerDownloadFileButton = ({
<CircularProgress
variant={downloadProgress === null ? 'indeterminate' : 'determinate'}
value={downloadProgress === null ? undefined : downloadProgress}
sx={{
transition: 'none',
}}
/>
) : (
<Tooltip

View File

@ -9,9 +9,15 @@ import Box from '@mui/material/Box'
import CircularProgress from '@mui/material/CircularProgress'
import { UserInfo } from 'components/UserInfo'
import { AudioState, Peer } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom'
import { TrackerConnection } from 'services/ConnectionTest'
import {
AudioState,
Peer,
AudioChannel,
AudioChannelName,
PeerAudioChannelState,
} from 'models/chat'
import { PeerConnectionType } from 'lib/PeerRoom'
import { TrackerConnection } from 'lib/ConnectionTest'
import { PeerListHeader } from './PeerListHeader'
import { PeerListItem } from './PeerListItem'
@ -25,8 +31,8 @@ export interface PeerListProps extends PropsWithChildren {
onPeerListClose: () => void
peerList: Peer[]
peerConnectionTypes: Record<string, PeerConnectionType>
audioState: AudioState
peerAudios: Record<string, HTMLAudioElement>
peerAudioChannelState: PeerAudioChannelState
peerAudioChannels: Record<string, AudioChannel>
connectionTestResults: IConnectionTestResults
}
@ -36,8 +42,8 @@ export const PeerList = ({
onPeerListClose,
peerList,
peerConnectionTypes,
audioState,
peerAudios,
peerAudioChannelState,
peerAudioChannels,
connectionTestResults,
}: PeerListProps) => {
return (
@ -49,7 +55,8 @@ export const PeerList = ({
<Divider />
<List>
<ListItem divider={true}>
{audioState === AudioState.PLAYING && (
{peerAudioChannelState[AudioChannelName.MICROPHONE] ===
AudioState.PLAYING && (
<ListItemIcon>
<VolumeUp />
</ListItemIcon>
@ -63,7 +70,7 @@ export const PeerList = ({
key={peer.peerId}
peer={peer}
peerConnectionTypes={peerConnectionTypes}
peerAudios={peerAudios}
peerAudioChannels={peerAudioChannels}
/>
))}
{peerList.length === 0 &&

View File

@ -18,15 +18,20 @@ import EnhancedEncryptionIcon from '@mui/icons-material/EnhancedEncryption'
import { AudioVolume } from 'components/AudioVolume'
import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { PublicKey } from 'components/PublicKey'
import { Peer, PeerVerificationState } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
import {
Peer,
AudioChannel,
AudioChannelName,
PeerVerificationState,
} from 'models/chat'
import { PeerConnectionType } from 'lib/PeerRoom'
import { PeerDownloadFileButton } from './PeerDownloadFileButton'
interface PeerListItemProps {
peer: Peer
peerConnectionTypes: Record<string, PeerConnectionType>
peerAudios: Record<string, HTMLAudioElement>
peerAudioChannels: Record<string, AudioChannel>
}
const verificationStateDisplayMap = {
@ -52,8 +57,8 @@ const iconRightPadding = 1
export const PeerListItem = ({
peer,
peerConnectionTypes,
peerAudios,
}: PeerListItemProps): JSX.Element => {
peerAudioChannels,
}: PeerListItemProps) => {
const [showPeerDialog, setShowPeerDialog] = useState(false)
const hasPeerConnection = peer.peerId in peerConnectionTypes
@ -69,6 +74,11 @@ export const PeerListItem = ({
setShowPeerDialog(false)
}
const microphoneAudio =
peerAudioChannels[peer.peerId]?.[AudioChannelName.MICROPHONE]
const screenShareAudio =
peerAudioChannels[peer.peerId]?.[AudioChannelName.SCREEN_SHARE]
return (
<>
<ListItem key={peer.peerId} divider={true}>
@ -124,8 +134,17 @@ export const PeerListItem = ({
</Box>
<PeerNameDisplay>{peer.userId}</PeerNameDisplay>
</Box>
{peer.peerId in peerAudios && (
<AudioVolume audioEl={peerAudios[peer.peerId]} />
{microphoneAudio && (
<AudioVolume
audioEl={microphoneAudio}
audioChannelName={AudioChannelName.MICROPHONE}
/>
)}
{screenShareAudio && (
<AudioVolume
audioEl={screenShareAudio}
audioChannelName={AudioChannelName.SCREEN_SHARE}
/>
)}
</ListItemText>
</ListItem>

View File

@ -14,8 +14,8 @@ import CloseIcon from '@mui/icons-material/Close'
import { AlertOptions } from 'models/shell'
import { useEffect, useState, SyntheticEvent } from 'react'
import { sleep } from 'utils'
import { encryptionService } from 'services/Encryption'
import { sleep } from 'lib/sleep'
import { encryption } from 'services/Encryption'
export interface RoomShareDialogProps {
isOpen: boolean
@ -51,10 +51,7 @@ export function RoomShareDialog(props: RoomShareDialogProps) {
const url = window.location.href.split('#')[0]
const copyWithPass = async () => {
const encoded = await encryptionService.encodePassword(
props.roomId,
password
)
const encoded = await encryption.encodePassword(props.roomId, password)
if (encoded === props.password) {
const params = new URLSearchParams()

View File

@ -35,7 +35,7 @@ describe('Shell', () => {
userEvent.click(menuButton)
})
const navigation = screen.getByRole('navigation')
const navigation = screen.getByLabelText('Navigation menu')
await waitFor(() => {
expect(navigation).toBeVisible()
@ -56,7 +56,7 @@ describe('Shell', () => {
userEvent.click(closeMenu)
})
const navigation = screen.getByRole('navigation')
const navigation = screen.getByLabelText('Navigation menu')
await waitFor(() => {
expect(navigation).not.toBeVisible()

View File

@ -8,7 +8,7 @@ import {
useState,
} from 'react'
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 Typography from '@mui/material/Typography'
import { AlertColor } from '@mui/material/Alert'
@ -19,10 +19,17 @@ import { useWindowSize } from '@react-hook/window-size'
import { ShellContext } from 'contexts/ShellContext'
import { SettingsContext } from 'contexts/SettingsContext'
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 { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
import { PeerConnectionType } from 'lib/PeerRoom'
import { Drawer } from './Drawer'
import { UpgradeDialog } from './UpgradeDialog'
@ -38,6 +45,7 @@ import {
EnvironmentUnsupportedDialog,
isEnvironmentSupported,
} from './EnvironmentUnsupportedDialog'
import { useShellTheme } from './useShellTheme'
export interface ShellProps extends PropsWithChildren {
userPeerId: string
@ -50,17 +58,7 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
const { getUserSettings, updateUserSettings } = useContext(SettingsContext)
const isEmbedded = queryParams.get(QueryParamKeys.IS_EMBEDDED) !== null
const { colorMode } = getUserSettings()
const theme = useMemo(
() =>
createTheme({
palette: {
mode: colorMode,
},
}),
[colorMode]
)
const theme = useShellTheme()
const [windowWidth] = useWindowSize()
const defaultSidebarsOpen = windowWidth >= theme.breakpoints.values.lg
@ -87,7 +85,11 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
Record<string, PeerConnectionType>
>({})
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 [screenState, setScreenState] = useState<ScreenShareState>(
ScreenShareState.NOT_SHARING
@ -95,8 +97,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
const [customUsername, setCustomUsername] = useState(
getUserSettings().customUsername
)
const [peerAudios, setPeerAudios] = useState<
Record<string, HTMLAudioElement>
const [peerAudioChannels, setPeerAudioChannels] = useState<
Record<string, AudioChannel>
>({})
const showAlert = useCallback((message: string, options?: AlertOptions) => {
@ -145,14 +147,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
setIsServerConnectionFailureDialogOpen,
peerConnectionTypes,
setPeerConnectionTypes,
audioState,
setAudioState,
audioChannelState,
setAudioChannelState,
videoState,
setVideoState,
screenState,
setScreenState,
peerAudios,
setPeerAudios,
peerAudioChannels,
setPeerAudioChannels,
customUsername,
setCustomUsername,
connectionTestResults,
@ -175,14 +177,14 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
setShowRoomControls,
setTitle,
showAlert,
audioState,
setAudioState,
audioChannelState,
setAudioChannelState,
videoState,
setVideoState,
screenState,
setScreenState,
peerAudios,
setPeerAudios,
peerAudioChannels,
setPeerAudioChannels,
customUsername,
setCustomUsername,
connectionTestResults,
@ -394,8 +396,8 @@ export const Shell = ({ appNeedsUpdate, children, userPeerId }: ShellProps) => {
onPeerListClose={handlePeerListClick}
peerList={peerList}
peerConnectionTypes={peerConnectionTypes}
audioState={audioState}
peerAudios={peerAudios}
peerAudioChannelState={audioChannelState}
peerAudioChannels={peerAudioChannels}
connectionTestResults={connectionTestResults}
/>
{isEmbedded ? (

View File

@ -6,14 +6,17 @@ import DialogContent from '@mui/material/DialogContent'
import DialogContentText from '@mui/material/DialogContentText'
import DialogTitle from '@mui/material/DialogTitle'
import WarningIcon from '@mui/icons-material/Warning'
import { useRegisterSW } from 'virtual:pwa-register/react'
interface UpgradeDialogProps {
appNeedsUpdate: boolean
}
export const UpgradeDialog = ({ appNeedsUpdate }: UpgradeDialogProps) => {
const { updateServiceWorker } = useRegisterSW()
const handleRestartClick = () => {
window.location.reload()
updateServiceWorker(true)
}
return (

View File

@ -1,11 +1,12 @@
import { useEffect, useState } from 'react'
import { sleep } from 'utils'
import { sleep } from 'lib/sleep'
import {
ConnectionTest,
ConnectionTestEvent,
ConnectionTestEvents,
TrackerConnection,
} from 'services/ConnectionTest/ConnectionTest'
} from 'lib/ConnectionTest'
export interface ConnectionTestResults {
hasHost: boolean

View 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
}

View File

@ -0,0 +1 @@
export const communityRoomNames = ['General', 'Prayer']

View File

@ -11,22 +11,32 @@ export const rtcConfig: RTCConfiguration = {
// CONNECTED TO THE INTERNET.
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',
username: 'c386d75b5633456cb3bc13812858098d',
credential: '58fd06d85fe14c0f9f46220748b0f565',
urls: 'stun:stun.relay.metered.ca:80',
},
{
urls: 'turn:188.148.133.173:3478',
username: '0e2f563eacfd4c4a82ea239b04d1d494',
credential: '8179b4b533f240ad9fe590663bef1bc9',
urls: 'turn:global.relay.metered.ca:80',
username: 'cfd3dccc1b9162a136a80482',
credential: '1TgyKEd7B6iUxIn9',
},
{
urls: 'turn:188.148.133.173:3478',
username: 'feab95c3fcd147a2a96a3d3590bf9cda',
credential: '654cafd885424b7fb974e65f631f25f9',
urls: 'turn:global.relay.metered.ca:80?transport=tcp',
username: 'cfd3dccc1b9162a136a80482',
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',
},
],
}

View File

@ -1,6 +1,6 @@
export const streamSaverUrl =
process.env.REACT_APP_STREAMSAVER_URL ??
// If you would like to host your own RemnantChat instance with an
import.meta.env.VITE_STREAMSAVER_URL ??
// If you would like to host your own Chitchatter instance with an
// alternative StreamSaver fork to facilitate file sharing, change this
// string to its respective .mitm.html URL.
//

View File

@ -8,12 +8,12 @@ let trackerUrls: string[] | undefined = [
// 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
// when using the `npm run dev` script. If you are hosting your own RemnantChat
// instance, consider populating the trackerUrls above instead.
if (process.env.REACT_APP_TRACKER_URL) {
trackerUrls.unshift(process.env.REACT_APP_TRACKER_URL)
if (import.meta.env.VITE_TRACKER_URL) {
trackerUrls.unshift(import.meta.env.VITE_TRACKER_URL)
}
// If no tracker URL overrides have been provided, set trackerUrls to undefined

View File

@ -1,7 +1,7 @@
import { createContext } from 'react'
import { ColorMode, UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption'
import { encryption } from 'services/Encryption'
export interface SettingsContextProps {
updateUserSettings: (settings: Partial<UserSettings>) => Promise<void>
@ -17,7 +17,7 @@ export const SettingsContext = createContext<SettingsContextProps>({
playSoundOnNewMessage: true,
showNotificationOnNewMessage: true,
showActiveTypingStatus: true,
publicKey: encryptionService.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub,
publicKey: encryption.cryptoKeyStub,
privateKey: encryption.cryptoKeyStub,
}),
})

View File

@ -1,10 +1,18 @@
import { createContext, Dispatch, SetStateAction } from 'react'
import { AlertOptions } from 'models/shell'
import { AudioState, ScreenShareState, VideoState, Peer } from 'models/chat'
import { PeerConnectionType } from 'services/PeerRoom/PeerRoom'
import {
AudioState,
ScreenShareState,
VideoState,
Peer,
AudioChannel,
PeerAudioChannelState,
AudioChannelName,
} from 'models/chat'
import { PeerConnectionType } from 'lib/PeerRoom'
import { ConnectionTestResults } from 'components/Shell/useConnectionTest'
import { TrackerConnection } from 'services/ConnectionTest/ConnectionTest'
import { TrackerConnection } from 'lib/ConnectionTest'
interface ShellContextProps {
isEmbedded: boolean
@ -27,14 +35,14 @@ interface ShellContextProps {
setPeerConnectionTypes: Dispatch<
SetStateAction<Record<string, PeerConnectionType>>
>
audioState: AudioState
setAudioState: Dispatch<SetStateAction<AudioState>>
audioChannelState: PeerAudioChannelState
setAudioChannelState: Dispatch<SetStateAction<PeerAudioChannelState>>
videoState: VideoState
setVideoState: Dispatch<SetStateAction<VideoState>>
screenState: ScreenShareState
setScreenState: Dispatch<SetStateAction<ScreenShareState>>
peerAudios: Record<string, HTMLAudioElement>
setPeerAudios: Dispatch<SetStateAction<Record<string, HTMLAudioElement>>>
peerAudioChannels: Record<string, AudioChannel>
setPeerAudioChannels: Dispatch<SetStateAction<Record<string, AudioChannel>>>
customUsername: string
setCustomUsername: Dispatch<SetStateAction<string>>
connectionTestResults: ConnectionTestResults
@ -60,14 +68,17 @@ export const ShellContext = createContext<ShellContextProps>({
setIsServerConnectionFailureDialogOpen: () => {},
peerConnectionTypes: {},
setPeerConnectionTypes: () => {},
audioState: AudioState.STOPPED,
setAudioState: () => {},
audioChannelState: {
[AudioChannelName.MICROPHONE]: AudioState.STOPPED,
[AudioChannelName.SCREEN_SHARE]: AudioState.STOPPED,
},
setAudioChannelState: () => {},
videoState: VideoState.STOPPED,
setVideoState: () => {},
screenState: ScreenShareState.NOT_SHARING,
setScreenState: () => {},
peerAudios: {},
setPeerAudios: () => {},
peerAudioChannels: {},
setPeerAudioChannels: () => {},
customUsername: '',
setCustomUsername: () => {},
connectionTestResults: {

20
src/index.css Normal file
View 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;
}

View File

@ -1,9 +0,0 @@
@tailwind base
@tailwind components
@tailwind utilities
ol
@apply pl-4 list-decimal
ul
@apply pl-4 list-disc

View File

@ -2,10 +2,29 @@ import './polyfills'
import ReactDOM from 'react-dom/client'
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 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)
root.render(<Init />)

View File

@ -1,4 +1,4 @@
import { getTrackers } from 'trystero/torrent'
import { getRelaySockets } from 'trystero/torrent'
import { rtcConfig } from 'config/rtcConfig'
import { parseCandidate } from 'sdp'
@ -104,17 +104,15 @@ export class ConnectionTest extends EventTarget {
}
testTrackerConnection() {
const trackers = getTrackers()
const relaySockets = Object.values(getRelaySockets())
const trackerSockets = Object.values(trackers)
if (trackerSockets.length === 0) {
if (relaySockets.length === 0) {
// Trystero has not yet initialized tracker sockets
this.trackerConnection = TrackerConnection.SEARCHING
return this.trackerConnection
}
const readyStates = trackerSockets.map(({ readyState }) => readyState)
const readyStates = relaySockets.map(({ readyState }) => readyState)
const haveAllTrackerConnectionsFailed = readyStates.every(
readyState => readyState === WebSocket.CLOSED

View File

@ -1,7 +1,8 @@
import { joinRoom, Room, BaseRoomConfig } from 'trystero'
import { TorrentRoomConfig } from 'trystero/torrent'
import { joinRoom, Room, BaseRoomConfig, DataPayload } 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 {
NEW_PEER = 'NEW_PEER',
@ -27,7 +28,7 @@ const streamQueueAddDelay = 1000
export class PeerRoom {
private room: Room
private roomConfig: TorrentRoomConfig & BaseRoomConfig
private roomConfig: RelayConfig & BaseRoomConfig
private peerJoinHandlers: Map<
PeerHookType,
@ -48,7 +49,19 @@ export class PeerRoom {
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.room = joinRoom(this.roomConfig, roomId)
@ -155,34 +168,26 @@ export class PeerRoom {
return peerConnections
}
makeAction = <T>(namespace: string) => {
makeAction = <T extends DataPayload>(namespace: string) => {
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
// conditions on the receiver's end where streams and their metadata get
// mixed up.
this.streamQueue.push(
() => Promise.all(this.room.addStream(...args)),
() => Promise.all(this.room.addStream(stream, targetPeers, metadata)),
() => sleep(streamQueueAddDelay)
)
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) => {
return this.room.removeStream(stream, targetPeers)
}

7
src/lib/Time/Time.ts Normal file
View 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
View File

@ -0,0 +1 @@
export * from './Time'

4
src/lib/sleep.ts Normal file
View File

@ -0,0 +1,4 @@
export const sleep = (milliseconds: number): Promise<void> =>
new Promise<void>(res => {
setTimeout(res, milliseconds)
})

View File

@ -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> => {
return (
typeof variable === 'object' &&

View File

@ -1,4 +1,4 @@
export interface UnsentMessage {
export interface UnsentMessage extends Record<string, any> {
id: string
text: string
timeSent: number
@ -31,9 +31,10 @@ export enum VideoState {
STOPPED = 'STOPPED',
}
export enum VideoStreamType {
export enum StreamType {
WEBCAM = 'WEBCAM',
SCREEN_SHARE = 'SCREEN_SHARE',
MICROPHONE = 'MICROPHONE',
}
export enum ScreenShareState {
@ -47,12 +48,21 @@ export enum PeerVerificationState {
VERIFIED,
}
export enum AudioChannelName {
MICROPHONE = 'microphone',
SCREEN_SHARE = 'screen-share',
}
export type AudioChannel = Partial<Record<AudioChannelName, HTMLAudioElement>>
export type PeerAudioChannelState = Record<AudioChannelName, AudioState>
export interface Peer {
peerId: string
userId: string
publicKey: CryptoKey
customUsername: string
audioState: AudioState
audioChannelState: PeerAudioChannelState
videoState: VideoState
screenShareState: ScreenShareState
offeredFileId: string | null
@ -73,11 +83,11 @@ export const isInlineMedia = (
return 'magnetURI' in message
}
export interface FileOfferMetadata {
export interface FileOfferMetadata extends Record<string, any> {
magnetURI: string
isAllInlineMedia: boolean
}
export interface TypingStatus {
export interface TypingStatus extends Record<string, any> {
isTyping: boolean
}

View File

@ -1,6 +1,7 @@
import { useContext, useEffect } from 'react'
import MuiMarkdown from 'mui-markdown'
import Box from '@mui/material/Box'
import useTheme from '@mui/material/styles/useTheme'
import { ShellContext } from 'contexts/ShellContext'
import {
@ -8,8 +9,6 @@ import {
messageCharacterSizeLimit,
} from 'config/messaging'
import './index.sass'
const messageTranscriptSizeLimitFormatted = Intl.NumberFormat().format(
messageTranscriptSizeLimit
)
@ -20,13 +19,24 @@ const messageCharacterSizeLimitFormatted = Intl.NumberFormat().format(
export const About = () => {
const { setTitle } = useContext(ShellContext)
const theme = useTheme()
useEffect(() => {
setTitle('About')
}, [setTitle])
return (
<Box className="About max-w-3xl mx-auto p-4">
<Box
className="About"
sx={{
p: 2,
mx: 'auto',
maxWidth: theme.breakpoints.values.md,
'& p': {
mb: 2,
},
}}
>
<MuiMarkdown>
{`
### User Guide

View File

@ -1,3 +0,0 @@
.About
p
@apply mb-4

View File

@ -1,20 +1,30 @@
import { useContext, useEffect } from 'react'
import Box from '@mui/material/Box'
import MuiMarkdown from 'mui-markdown'
import useTheme from '@mui/material/styles/useTheme'
import { ShellContext } from 'contexts/ShellContext'
import './index.sass'
export const Disclaimer = () => {
const { setTitle } = useContext(ShellContext)
const theme = useTheme()
useEffect(() => {
setTitle('Disclaimer')
}, [setTitle])
return (
<Box className="Disclaimer max-w-3xl mx-auto p-4">
<Box
className="Disclaimer"
sx={{
p: 2,
mx: 'auto',
maxWidth: theme.breakpoints.values.md,
'& p': {
mb: 2,
},
}}
>
<MuiMarkdown>
{`
### Interpretation and Definitions

View File

@ -1,6 +0,0 @@
.Disclaimer
ul
@apply my-4
p
@apply mb-4

View File

@ -0,0 +1,67 @@
import { useState, SyntheticEvent } from 'react'
import { useNavigate } from 'react-router-dom'
import Button from '@mui/material/Button'
import Box from '@mui/material/Box'
import Typography from '@mui/material/Typography'
import TextField from '@mui/material/TextField'
import Autocomplete from '@mui/material/Autocomplete'
import Accordion from '@mui/material/Accordion'
import AccordionSummary from '@mui/material/AccordionSummary'
import AccordionDetails from '@mui/material/AccordionDetails'
import ExpandMoreIcon from '@mui/icons-material/ExpandMore'
import { communityRoomNames } from 'config/communityRooms'
export const CommunityRoomSelector = () => {
const navigate = useNavigate()
const [selectedRoom, setSelectedRoom] = useState<string | null>(null)
const handleRoomNameChange = (
_event: SyntheticEvent<Element, Event>,
roomName: string | null
) => {
setSelectedRoom(roomName)
}
const handleJoinClick = () => {
navigate(`/public/${selectedRoom}`)
}
return (
<Accordion>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1-header"
sx={{
fontWeight: 'bold',
}}
>
Community rooms
</AccordionSummary>
<AccordionDetails>
<Typography variant="body1">
You can also chat in a public community room. You'll be anonymous, but
be careful what information you choose to share.
</Typography>
<Box display="flex" mt={2} gap={1}>
<Autocomplete
disablePortal
options={communityRoomNames}
value={selectedRoom}
renderInput={params => <TextField {...params} label="Room" />}
onChange={handleRoomNameChange}
sx={{ flexGrow: 1 }}
/>
<Button
variant="contained"
disabled={selectedRoom === null}
onClick={handleJoinClick}
>
Join
</Button>
</Box>
</AccordionDetails>
</Accordion>
)
}

View File

@ -10,15 +10,21 @@ import IconButton from '@mui/material/IconButton'
import MuiLink from '@mui/material/Link'
import Cached from '@mui/icons-material/Cached'
import useTheme from '@mui/material/styles/useTheme'
import styled from '@mui/material/styles/styled'
import { v4 as uuid } from 'uuid'
import { routes } from 'config/routes'
import { ShellContext } from 'contexts/ShellContext'
import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { ReactComponent as Logo } from 'img/logo.svg'
import { Form, Main } from 'components/Elements'
import Logo from 'img/logo.svg?react'
import { EmbedCodeDialog } from './EmbedCodeDialog'
import { CommunityRoomSelector } from './CommunityRoomSelector'
const StyledLogo = styled(Logo)({})
interface HomeProps {
userId: string
@ -26,6 +32,7 @@ interface HomeProps {
export function Home({ userId }: HomeProps) {
const { setTitle } = useContext(ShellContext)
const theme = useTheme()
const [roomName, setRoomName] = useState(uuid())
const [showEmbedCode, setShowEmbedCode] = useState(false)
const navigate = useNavigate()
@ -63,11 +70,29 @@ export function Home({ userId }: HomeProps) {
return (
<Box className="Home">
<main className="mt-6 px-4 max-w-3xl text-center mx-auto">
<Main
sx={{
maxWidth: theme.breakpoints.values.md,
mt: 3,
mx: 'auto',
px: 2,
textAlign: 'center',
}}
>
<Link to={routes.ABOUT}>
<Logo className="px-1 pb-4 mx-auto max-w-md" />
<StyledLogo
sx={{
px: 0.5,
pb: 2,
mx: 'auto',
maxWidth: theme.breakpoints.values.sm,
}}
/>
</Link>
<form onSubmit={handleFormSubmit} className="max-w-xl mx-auto">
<Form
onSubmit={handleFormSubmit}
sx={{ maxWidth: theme.breakpoints.values.sm, mx: 'auto' }}
>
<Typography sx={{ mb: 2 }}>
Your username:{' '}
<PeerNameDisplay paragraph={false} sx={{ fontWeight: 'bold' }}>
@ -76,7 +101,7 @@ export function Home({ userId }: HomeProps) {
</Typography>
<FormControl fullWidth>
<TextField
label="Room name (generated client-side)"
label="Room name (generated on your device)"
variant="outlined"
value={roomName}
onChange={handleRoomNameChange}
@ -135,14 +160,28 @@ export function Home({ userId }: HomeProps) {
Get embed code
</Button>
</Box>
</form>
</main>
</Form>
</Main>
<Divider sx={{ my: 2 }} />
<Box className="max-w-3xl text-center mx-auto px-4">
<Box maxWidth={theme.breakpoints.values.sm} mx="auto" px={2}>
<CommunityRoomSelector />
</Box>
<Divider sx={{ my: 2 }} />
<Box
sx={{
maxWidth: theme.breakpoints.values.sm,
mx: 'auto',
textAlign: 'center',
px: 2,
}}
>
<Typography variant="body1">
The secure communication tool that is designed for simplicity,
privacy. All interaction between you and your online peers is
encrypted. Conversation records are dumped once everyone leaves.
WEBRTC settings must be enabled in your browser. Is webrtc{' '}
<MuiLink href="https://webrtc-security.github.io/" target="_blank">
secure
</MuiLink>
? All interaction between you and your online peers is encrypted.
Conversation records are dumped once everyone leaves.
</Typography>
<Box className="max-w-3xl text-center mx-auto my-4 px-4">
{' '}
@ -150,19 +189,7 @@ export function Home({ userId }: HomeProps) {
</Box>
</Box>
<Divider sx={{ my: 2 }} />
<Box className="max-w-3xl text-center mx-auto my-4 px-4">
<Typography variant="body1">Official Public Rooms to Join:</Typography>
<MuiLink
href="/public/general"
target="_blank"
sx={theme => ({
color: theme.palette.text.primary,
})}
>
<Typography variant="body1">&#x2022; General Chat</Typography>
</MuiLink>
</Box>
<Typography variant="body1" sx={{ textAlign: 'center' }}>
<Typography variant="body1" sx={{ textAlign: 'center', mb: 1 }}>
Licensed under{' '}
<MuiLink
href="https://githaven.org/Shiloh/remnantchat/src/branch/main/LICENSE"
@ -175,9 +202,8 @@ export function Home({ userId }: HomeProps) {
href="https://githaven.org/Shiloh/remnantchat/src/branch/main/README.md"
target="_blank"
>
read the docs
read the docs.
</MuiLink>
.
</Typography>
<EmbedCodeDialog
showEmbedCode={showEmbedCode}

View File

@ -3,9 +3,9 @@ import { Room } from 'components/Room'
import { useParams } from 'react-router-dom'
import { ShellContext } from 'contexts/ShellContext'
import { NotificationService } from 'services/Notification'
import { notification } from 'services/Notification'
import { PasswordPrompt } from 'components/PasswordPrompt'
import { encryptionService } from 'services/Encryption'
import { encryption } from 'services/Encryption'
interface PublicRoomProps {
userId: string
@ -22,7 +22,7 @@ export function PrivateRoom({ userId }: PublicRoomProps) {
const [secret, setSecret] = useState(urlParams.get('secret') ?? '')
useEffect(() => {
NotificationService.requestPermission()
notification.requestPermission()
}, [])
useEffect(() => {
@ -31,7 +31,7 @@ export function PrivateRoom({ userId }: PublicRoomProps) {
const handlePasswordEntered = async (password: string) => {
if (password.length !== 0)
setSecret(await encryptionService.encodePassword(roomId, password))
setSecret(await encryption.encodePassword(roomId, password))
}
if (urlParams.has('pwd') && !urlParams.has('secret'))

View File

@ -3,7 +3,7 @@ import { Room } from 'components/Room'
import { useParams } from 'react-router-dom'
import { ShellContext } from 'contexts/ShellContext'
import { NotificationService } from 'services/Notification'
import { notification } from 'services/Notification'
interface PublicRoomProps {
userId: string
@ -14,7 +14,7 @@ export function PublicRoom({ userId }: PublicRoomProps) {
const { setTitle } = useContext(ShellContext)
useEffect(() => {
NotificationService.requestPermission()
notification.requestPermission()
}, [])
useEffect(() => {

View File

@ -10,15 +10,15 @@ import FormControlLabel from '@mui/material/FormControlLabel'
import Paper from '@mui/material/Paper'
import useTheme from '@mui/material/styles/useTheme'
import { settingsService } from 'services/Settings'
import { NotificationService } from 'services/Notification'
import { settings } from 'services/Settings'
import { notification } from 'services/Notification'
import { ShellContext } from 'contexts/ShellContext'
import { StorageContext } from 'contexts/StorageContext'
import { SettingsContext } from 'contexts/SettingsContext'
import { PeerNameDisplay } from 'components/PeerNameDisplay'
import { ConfirmDialog } from 'components/ConfirmDialog'
import { isErrorWithMessage } from '../../utils'
import { isErrorWithMessage } from '../../lib/type-guards'
interface SettingsProps {
userId: string
@ -45,7 +45,7 @@ export const Settings = ({ userId }: SettingsProps) => {
useEffect(() => {
;(async () => {
await NotificationService.requestPermission()
await notification.requestPermission()
// This state needs to be set to cause a rerender so that
// areNotificationsAvailable is up-to-date.
@ -93,7 +93,7 @@ export const Settings = ({ userId }: SettingsProps) => {
const handleExportSettingsClick = async () => {
try {
await settingsService.exportSettings(getUserSettings())
await settings.exportSettings(getUserSettings())
} catch (e) {
if (isErrorWithMessage(e)) {
showAlert(e.message, { severity: 'error' })
@ -103,7 +103,7 @@ export const Settings = ({ userId }: SettingsProps) => {
const handleImportSettingsClick = async ([[, file]]: Result[]) => {
try {
const userSettings = await settingsService.importSettings(file)
const userSettings = await settings.importSettings(file)
updateUserSettings(userSettings)
@ -115,10 +115,10 @@ export const Settings = ({ userId }: SettingsProps) => {
}
}
const areNotificationsAvailable = NotificationService.permission === 'granted'
const areNotificationsAvailable = notification.permission === 'granted'
return (
<Box className="max-w-3xl mx-auto p-4">
<Box sx={{ p: 2, mx: 'auto', maxWidth: theme.breakpoints.values.md }}>
<Typography
variant="h2"
sx={{

View File

@ -1,9 +1 @@
import 'webrtc-adapter'
import { Buffer } from 'buffer'
// @ts-ignore
import process from 'process/browser'
// Polyfill
window.Buffer = Buffer
window.process = process

View File

@ -1 +1 @@
/// <reference types="react-scripts" />
/// <reference types="vite-plugin-svgr/client" />

View File

@ -1,73 +0,0 @@
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core'
import { ExpirationPlugin } from 'workbox-expiration'
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { StaleWhileRevalidate } from 'workbox-strategies'
clientsClaim()
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST)
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$')
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false
} // Return true to signal that we want to use the handler.
return true
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
)
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) =>
url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
)
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', event => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting()
}
})
// Any other custom service worker logic can go here.

View File

@ -1,141 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
)
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return
}
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config)
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://cra.link/PWA'
)
})
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config)
}
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing
if (installingWorker == null) {
return
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://cra.link/PWA.'
)
registration.waiting.postMessage({ type: 'SKIP_WAITING' })
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration)
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.')
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration)
}
}
}
}
}
})
.catch(error => {
console.error('Error during service worker registration:', error)
})
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type')
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload()
})
})
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config)
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
)
})
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister()
})
.catch(error => {
console.error(error.message)
})
}
}

View File

@ -110,4 +110,4 @@ export class EncryptionService {
}
}
export const encryptionService = new EncryptionService()
export const encryption = new EncryptionService()

View File

@ -1,18 +1,17 @@
export class NotificationService {
static permission: NotificationPermission
permission: NotificationPermission = 'default'
static requestPermission = async () => {
if (NotificationService.permission === 'granted') return
requestPermission = async () => {
if (this.permission === 'granted') return
NotificationService.permission = await Notification.requestPermission()
this.permission = await Notification.requestPermission()
}
static showNotification = (
message: string,
options?: NotificationOptions
) => {
if (NotificationService.permission !== 'granted') return
showNotification = (message: string, options?: NotificationOptions) => {
if (this.permission !== 'granted') return
new Notification(message, options)
}
}
export const notification = new NotificationService()

View File

@ -1,5 +1,5 @@
import { ColorMode, UserSettings } from 'models/settings'
import { AllowedKeyType, encryptionService } from 'services/Encryption'
import { AllowedKeyType, encryption } from 'services/Encryption'
export interface SerializedUserSettings
extends Omit<UserSettings, 'publicKey' | 'privateKey'> {
@ -42,13 +42,9 @@ export class SerializationService {
...userSettingsRest
} = userSettings
const publicKey = await encryptionService.stringifyCryptoKey(
publicCryptoKey
)
const publicKey = await encryption.stringifyCryptoKey(publicCryptoKey)
const privateKey = await encryptionService.stringifyCryptoKey(
privateCryptoKey
)
const privateKey = await encryption.stringifyCryptoKey(privateCryptoKey)
return {
...userSettingsRest,
@ -66,11 +62,11 @@ export class SerializationService {
...userSettingsForIndexedDbRest
} = serializedUserSettings
const publicKey = await encryptionService.parseCryptoKeyString(
const publicKey = await encryption.parseCryptoKeyString(
publicCryptoKeyString,
AllowedKeyType.PUBLIC
)
const privateKey = await encryptionService.parseCryptoKeyString(
const privateKey = await encryption.parseCryptoKeyString(
privateCryptoKeyString,
AllowedKeyType.PRIVATE
)
@ -83,4 +79,4 @@ export class SerializationService {
}
}
export const serializationService = new SerializationService()
export const serialization = new SerializationService()

View File

@ -1,10 +1,10 @@
import { saveAs } from 'file-saver'
import { UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption'
import { encryption } from 'services/Encryption'
import {
isSerializedUserSettings,
serializationService,
serialization,
} from 'services/Serialization/Serialization'
class InvalidFileError extends Error {
@ -16,7 +16,7 @@ const encryptionTestTarget = 'remnantchat'
export class SettingsService {
exportSettings = async (userSettings: UserSettings) => {
const serializedUserSettings =
await serializationService.serializeUserSettings(userSettings)
await serialization.serializeUserSettings(userSettings)
const blob = new Blob([JSON.stringify(serializedUserSettings)], {
type: 'application/json;charset=utf-8',
@ -44,14 +44,14 @@ export class SettingsService {
}
const deserializedUserSettings =
await serializationService.deserializeUserSettings(parsedFileResult)
await serialization.deserializeUserSettings(parsedFileResult)
const encryptedString = await encryptionService.encryptString(
const encryptedString = await encryption.encryptString(
deserializedUserSettings.publicKey,
encryptionTestTarget
)
const decryptedString = await encryptionService.decryptString(
const decryptedString = await encryption.decryptString(
deserializedUserSettings.privateKey,
encryptedString
)
@ -77,4 +77,4 @@ export class SettingsService {
}
}
export const settingsService = new SettingsService()
export const settings = new SettingsService()

View File

@ -3,12 +3,16 @@
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom'
import { vi } from 'vitest'
afterEach(() => {
jest.restoreAllMocks()
vi.restoreAllMocks()
})
jest.mock('secure-file-transfer', () => ({
vi.mock('trystero')
vi.mock('trystero/torrent')
vi.mock('secure-file-transfer', () => ({
__esModule: true,
FileTransfer: class FileTransfer {
rescindAll() {}

View File

@ -1,10 +1,11 @@
import { encryptionService } from 'services/Encryption'
import { vi } from 'vitest'
import { encryption } from 'services/Encryption'
export const mockEncryptionService = encryptionService
export const mockEncryptionService = encryption
mockEncryptionService.generateKeyPair = jest.fn(async () => ({
publicKey: encryptionService.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub,
mockEncryptionService.generateKeyPair = vi.fn(async () => ({
publicKey: encryption.cryptoKeyStub,
privateKey: encryption.cryptoKeyStub,
}))
mockEncryptionService.encodePassword = async () => ''
@ -12,4 +13,4 @@ mockEncryptionService.encodePassword = async () => ''
mockEncryptionService.stringifyCryptoKey = async () => ''
mockEncryptionService.parseCryptoKeyString = async () =>
encryptionService.cryptoKeyStub
encryption.cryptoKeyStub

View File

@ -1,16 +1,13 @@
import { UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption'
import {
serializationService,
SerializedUserSettings,
} from 'services/Serialization'
import { encryption } from 'services/Encryption'
import { serialization, SerializedUserSettings } from 'services/Serialization'
export const mockSerializedPublicKey = 'public key'
export const mockSerializedPrivateKey = 'private key'
export const mockSerializationService = serializationService
export const mockSerialization = serialization
mockSerializationService.serializeUserSettings = async (
mockSerialization.serializeUserSettings = async (
userSettings: UserSettings
) => {
const { publicKey, privateKey, ...userSettingsRest } = userSettings
@ -22,14 +19,14 @@ mockSerializationService.serializeUserSettings = async (
}
}
mockSerializationService.deserializeUserSettings = async (
mockSerialization.deserializeUserSettings = async (
serializedUserSettings: SerializedUserSettings
) => {
const { publicKey, privateKey, ...userSettingsRest } = serializedUserSettings
return {
publicKey: encryptionService.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub,
publicKey: encryption.cryptoKeyStub,
privateKey: encryption.cryptoKeyStub,
...userSettingsRest,
}
}

View File

@ -1,6 +1,6 @@
import { SettingsContextProps } from 'contexts/SettingsContext'
import { ColorMode, UserSettings } from 'models/settings'
import { encryptionService } from 'services/Encryption'
import { encryption } from 'services/Encryption'
export const userSettingsContextStubFactory = (
userSettingsOverrides: Partial<UserSettings> = {}
@ -14,8 +14,8 @@ export const userSettingsContextStubFactory = (
playSoundOnNewMessage: true,
showNotificationOnNewMessage: true,
showActiveTypingStatus: true,
publicKey: encryptionService.cryptoKeyStub,
privateKey: encryptionService.cryptoKeyStub,
publicKey: encryption.cryptoKeyStub,
privateKey: encryption.cryptoKeyStub,
...userSettingsOverrides,
}),
}

Some files were not shown because too many files have changed in this diff Show More