perf: multiple peerRoom implementation

This commit is contained in:
molvqingtai 2024-09-24 15:02:12 +08:00
parent ec62b1155e
commit e373993899
15 changed files with 628 additions and 276 deletions

View file

@ -56,8 +56,11 @@
"@radix-ui/react-scroll-area": "^1.1.0", "@radix-ui/react-scroll-area": "^1.1.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-switch": "^1.1.0", "@radix-ui/react-switch": "^1.1.0",
"@resreq/event-hub": "^1.6.0",
"@resreq/timer": "^1.1.5", "@resreq/timer": "^1.1.5",
"@rtco/client": "^0.2.17",
"@tailwindcss/typography": "^0.5.15", "@tailwindcss/typography": "^0.5.15",
"@webext-core/proxy-service": "^1.2.0",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",

View file

@ -44,12 +44,21 @@ importers:
'@radix-ui/react-switch': '@radix-ui/react-switch':
specifier: ^1.1.0 specifier: ^1.1.0
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.7)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@resreq/event-hub':
specifier: ^1.6.0
version: 1.6.0
'@resreq/timer': '@resreq/timer':
specifier: ^1.1.5 specifier: ^1.1.5
version: 1.1.5 version: 1.1.5
'@rtco/client':
specifier: ^0.2.17
version: 0.2.17
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.15 specifier: ^0.5.15
version: 0.5.15(tailwindcss@3.4.12) version: 0.5.15(tailwindcss@3.4.12)
'@webext-core/proxy-service':
specifier: ^1.2.0
version: 1.2.0(@webext-core/messaging@1.4.0)(webextension-polyfill@0.12.0)
class-variance-authority: class-variance-authority:
specifier: ^0.7.0 specifier: ^0.7.0
version: 0.7.0 version: 0.7.0
@ -115,7 +124,7 @@ importers:
version: 2.5.2 version: 2.5.2
trystero: trystero:
specifier: ^0.20.0 specifier: ^0.20.0
version: 0.20.0(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/proto@0.0.7)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20) version: 0.20.0(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/proto@0.0.7)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20)(bufferutil@4.0.8)(utf-8-validate@6.0.4)
type-fest: type-fest:
specifier: ^4.26.1 specifier: ^4.26.1
version: 4.26.1 version: 4.26.1
@ -227,7 +236,7 @@ importers:
version: 6.0.1 version: 6.0.1
wxt: wxt:
specifier: ^0.19.9 specifier: ^0.19.9
version: 0.19.9(@types/node@22.5.5)(rollup@4.21.3) version: 0.19.9(@types/node@22.5.5)(bufferutil@4.0.8)(rollup@4.21.3)(utf-8-validate@6.0.4)
packages: packages:
@ -1741,6 +1750,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@rtco/client@0.2.17':
resolution: {integrity: sha512-nV/KJGBh/j0fK069uADXNr30JBbWe7CZ0xe0cEx1JjuBQIkH10Ny5mQm4WZd/vID+EF7l+tqkCG6QUyCAW5PFg==}
'@rtco/peer@0.2.17':
resolution: {integrity: sha512-jxKQzAIMiofkJ5UHIbeq2JUl+fBOCnWRxgxemzuI7TKw96pbkfaMowr3fj+ElXnKPWwCi5WRX3hwitqjfNkwFQ==}
'@sec-ant/readable-stream@0.4.1': '@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
@ -1756,6 +1771,9 @@ packages:
resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
'@supabase/auth-js@2.65.0': '@supabase/auth-js@2.65.0':
resolution: {integrity: sha512-+wboHfZufAE2Y612OsKeVP4rVOeGZzzMLD/Ac3HrTQkkY4qXNjI6Af9gtmxwccE5nFvTiF114FEbIQ1hRq5uUw==} resolution: {integrity: sha512-+wboHfZufAE2Y612OsKeVP4rVOeGZzzMLD/Ac3HrTQkkY4qXNjI6Af9gtmxwccE5nFvTiF114FEbIQ1hRq5uUw==}
@ -2105,6 +2123,15 @@ packages:
'@webext-core/match-patterns@1.0.3': '@webext-core/match-patterns@1.0.3':
resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==} resolution: {integrity: sha512-NY39ACqCxdKBmHgw361M9pfJma8e4AZo20w9AY+5ZjIj1W2dvXC8J31G5fjfOGbulW9w4WKpT8fPooi0mLkn9A==}
'@webext-core/messaging@1.4.0':
resolution: {integrity: sha512-gzXQ13HfKR3Yrn9TnrvTC/5seA7uPFvaqxqNFBsFOOdSZa5LyXt58Rhym8BYXarkWUGp+fh8f6AYM3RYuNbS+A==}
'@webext-core/proxy-service@1.2.0':
resolution: {integrity: sha512-MCUadVakeb7L47AvdtlbJfBUDjFdejr5t4E2WrwZagnev3a5I/xh2wHCkE+G0ihO/VUt/m0R1MPX+y4YVFRyPA==}
peerDependencies:
'@webext-core/messaging': '>=1.3.1'
webextension-polyfill: ^0.10.0
'@xobotyi/scrollbar-width@1.9.5': '@xobotyi/scrollbar-width@1.9.5':
resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==} resolution: {integrity: sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ==}
@ -2311,6 +2338,10 @@ packages:
buffer@6.0.3: buffer@6.0.3:
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
bufferutil@4.0.8:
resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==}
engines: {node: '>=6.14.2'}
bundle-name@3.0.0: bundle-name@3.0.0:
resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==}
engines: {node: '>=12'} engines: {node: '>=12'}
@ -2825,6 +2856,13 @@ packages:
end-of-stream@1.4.4: end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
engine.io-client@6.6.1:
resolution: {integrity: sha512-aYuoak7I+R83M/BBPIOs2to51BmFIpC1wZe6zZzMrT2llVsHy5cvcmdsJgP2Qz6smHu+sD9oexiSUAVd8OfBPw==}
engine.io-parser@5.2.3:
resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
engines: {node: '>=10.0.0'}
entities@4.5.0: entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'} engines: {node: '>=0.12'}
@ -3271,6 +3309,10 @@ packages:
resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==} resolution: {integrity: sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
get-value@3.0.1:
resolution: {integrity: sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA==}
engines: {node: '>=6.0'}
giget@1.2.3: giget@1.2.3:
resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==}
hasBin: true hasBin: true
@ -4415,6 +4457,10 @@ packages:
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
engines: {node: '>= 6.13.0'} engines: {node: '>= 6.13.0'}
node-gyp-build@4.8.2:
resolution: {integrity: sha512-IRUxE4BVsHWXkV/SFOut4qTlagw2aM8T5/vnTsmrHJvVoKueJHRc/JaFND7QDDc61kLYUJ6qlZM3sqTSyx2dTw==}
hasBin: true
node-notifier@10.0.1: node-notifier@10.0.1:
resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==} resolution: {integrity: sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==}
@ -5128,6 +5174,10 @@ packages:
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
serialize-error@11.0.3:
resolution: {integrity: sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==}
engines: {node: '>=14.16'}
serialize-error@9.1.1: serialize-error@9.1.1:
resolution: {integrity: sha512-6uZQLGyUkNA4N+Zii9fYukmNu9PEA1F5rqcwXzN/3LtBjwl2dFBbVZ1Zyn08/CGkB4H440PIemdOQBt1Wvjbrg==} resolution: {integrity: sha512-6uZQLGyUkNA4N+Zii9fYukmNu9PEA1F5rqcwXzN/3LtBjwl2dFBbVZ1Zyn08/CGkB4H440PIemdOQBt1Wvjbrg==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@ -5211,6 +5261,14 @@ packages:
resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==}
engines: {node: '>=18'} engines: {node: '>=18'}
socket.io-client@4.8.0:
resolution: {integrity: sha512-C0jdhD5yQahMws9alf/yvtsMGTaIDBnZ8Rb5HU56svyq0l5LIrGzIDZZD5pHQlmzxLuU91Gz+VpQMKgCTNYtkw==}
engines: {node: '>=10.0.0'}
socket.io-parser@4.2.4:
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
engines: {node: '>=10.0.0'}
sonner@1.5.0: sonner@1.5.0:
resolution: {integrity: sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==} resolution: {integrity: sha512-FBjhG/gnnbN6FY0jaNnqZOMmB73R+5IiyYAw8yBj7L54ER7HB3fOSE5OFiQiE2iXWxeXKvg6fIP4LtVppHEdJA==}
peerDependencies: peerDependencies:
@ -5756,6 +5814,10 @@ packages:
peerDependencies: peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 react: ^16.8.0 || ^17.0.0 || ^18.0.0
utf-8-validate@6.0.4:
resolution: {integrity: sha512-xu9GQDeFp+eZ6LnCywXN/zBancWvOpUMzgjLPSjy4BRHSmTelvn2E0DG0o1sTiw5hkCKBHo8rwSKncfRfv2EEQ==}
engines: {node: '>=6.14.2'}
utf8-bytes@0.0.1: utf8-bytes@0.0.1:
resolution: {integrity: sha512-GifWmJAx2qAXT+lZLhbkWhBsy7pr6xWHiPWlVToDiELdWgZwt4Ogjf9tlgvKuALzTFR/d+EPQQI9ogJV3957Jg==} resolution: {integrity: sha512-GifWmJAx2qAXT+lZLhbkWhBsy7pr6xWHiPWlVToDiELdWgZwt4Ogjf9tlgvKuALzTFR/d+EPQQI9ogJV3957Jg==}
@ -5849,6 +5911,9 @@ packages:
webext-bridge@6.0.1: webext-bridge@6.0.1:
resolution: {integrity: sha512-GruIrN+vNwbxVCi8UW4Dqk5YkcGA9V0ZfJ57jXP9JXHbrsDs5k2N6NNYQR5e+wSCnQpGYOGAGihwUpKlhg8QIw==} resolution: {integrity: sha512-GruIrN+vNwbxVCi8UW4Dqk5YkcGA9V0ZfJ57jXP9JXHbrsDs5k2N6NNYQR5e+wSCnQpGYOGAGihwUpKlhg8QIw==}
webextension-polyfill@0.10.0:
resolution: {integrity: sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==}
webextension-polyfill@0.12.0: webextension-polyfill@0.12.0:
resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==} resolution: {integrity: sha512-97TBmpoWJEE+3nFBQ4VocyCdLKfw54rFaJ6EVQYLBCXqCIpLSZkwGgASpv4oPt9gdKCJ80RJlcmNzNn008Ag6Q==}
@ -5941,6 +6006,18 @@ packages:
write-file-atomic@3.0.3: write-file-atomic@3.0.3:
resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==} resolution: {integrity: sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==}
ws@8.17.1:
resolution: {integrity: sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: '>=5.0.2'
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
ws@8.18.0: ws@8.18.0:
resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==} resolution: {integrity: sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -5969,6 +6046,10 @@ packages:
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==} resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
xmlhttprequest-ssl@2.1.1:
resolution: {integrity: sha512-ptjR8YSJIXoA3Mbv5po7RtSYHO6mZr8s7i5VGmEk7QY2pQWyT1o0N+W1gKbOyJPUCGXGnuw0wqe8f0L6Y0ny7g==}
engines: {node: '>=0.4.0'}
y18n@5.0.8: y18n@5.0.8:
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -7186,7 +7267,7 @@ snapshots:
uint8arraylist: 2.4.8 uint8arraylist: 2.4.8
uint8arrays: 5.1.0 uint8arrays: 5.1.0
'@libp2p/websockets@8.2.0': '@libp2p/websockets@8.2.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
dependencies: dependencies:
'@libp2p/interface': 1.7.0 '@libp2p/interface': 1.7.0
'@libp2p/utils': 5.4.9 '@libp2p/utils': 5.4.9
@ -7194,12 +7275,12 @@ snapshots:
'@multiformats/multiaddr': 12.3.1 '@multiformats/multiaddr': 12.3.1
'@multiformats/multiaddr-to-uri': 10.1.0 '@multiformats/multiaddr-to-uri': 10.1.0
'@types/ws': 8.5.12 '@types/ws': 8.5.12
it-ws: 6.1.5 it-ws: 6.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.4)
p-defer: 4.0.1 p-defer: 4.0.1
progress-events: 1.0.1 progress-events: 1.0.1
race-signal: 1.1.0 race-signal: 1.1.0
wherearewe: 2.0.1 wherearewe: 2.0.1
ws: 8.18.0 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
@ -7781,6 +7862,21 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.21.3': '@rollup/rollup-win32-x64-msvc@4.21.3':
optional: true optional: true
'@rtco/client@0.2.17':
dependencies:
'@rtco/peer': 0.2.17
bufferutil: 4.0.8
eventemitter3: 5.0.1
nanoid: 5.0.7
socket.io-client: 4.8.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
utf-8-validate: 6.0.4
transitivePeerDependencies:
- supports-color
'@rtco/peer@0.2.17':
dependencies:
eventemitter3: 5.0.1
'@sec-ant/readable-stream@0.4.1': {} '@sec-ant/readable-stream@0.4.1': {}
'@sindresorhus/fnv1a@3.1.0': {} '@sindresorhus/fnv1a@3.1.0': {}
@ -7789,6 +7885,8 @@ snapshots:
'@sindresorhus/merge-streams@4.0.0': {} '@sindresorhus/merge-streams@4.0.0': {}
'@socket.io/component-emitter@3.1.2': {}
'@supabase/auth-js@2.65.0': '@supabase/auth-js@2.65.0':
dependencies: dependencies:
'@supabase/node-fetch': 2.6.15 '@supabase/node-fetch': 2.6.15
@ -7805,12 +7903,12 @@ snapshots:
dependencies: dependencies:
'@supabase/node-fetch': 2.6.15 '@supabase/node-fetch': 2.6.15
'@supabase/realtime-js@2.10.2': '@supabase/realtime-js@2.10.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
dependencies: dependencies:
'@supabase/node-fetch': 2.6.15 '@supabase/node-fetch': 2.6.15
'@types/phoenix': 1.6.5 '@types/phoenix': 1.6.5
'@types/ws': 8.5.12 '@types/ws': 8.5.12
ws: 8.18.0 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
@ -7819,13 +7917,13 @@ snapshots:
dependencies: dependencies:
'@supabase/node-fetch': 2.6.15 '@supabase/node-fetch': 2.6.15
'@supabase/supabase-js@2.45.4': '@supabase/supabase-js@2.45.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
dependencies: dependencies:
'@supabase/auth-js': 2.65.0 '@supabase/auth-js': 2.65.0
'@supabase/functions-js': 2.4.1 '@supabase/functions-js': 2.4.1
'@supabase/node-fetch': 2.6.15 '@supabase/node-fetch': 2.6.15
'@supabase/postgrest-js': 1.16.1 '@supabase/postgrest-js': 1.16.1
'@supabase/realtime-js': 2.10.2 '@supabase/realtime-js': 2.10.2(bufferutil@4.0.8)(utf-8-validate@6.0.4)
'@supabase/storage-js': 2.7.0 '@supabase/storage-js': 2.7.0
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
@ -8213,13 +8311,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@waku/sdk@0.0.26(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20)': '@waku/sdk@0.0.26(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20)(bufferutil@4.0.8)(utf-8-validate@6.0.4)':
dependencies: dependencies:
'@chainsafe/libp2p-noise': 14.1.0 '@chainsafe/libp2p-noise': 14.1.0
'@libp2p/identify': 1.0.21 '@libp2p/identify': 1.0.21
'@libp2p/mplex': 10.1.5 '@libp2p/mplex': 10.1.5
'@libp2p/ping': 1.1.6 '@libp2p/ping': 1.1.6
'@libp2p/websockets': 8.2.0 '@libp2p/websockets': 8.2.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
'@noble/hashes': 1.5.0 '@noble/hashes': 1.5.0
'@waku/core': 0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4) '@waku/core': 0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4)
'@waku/discovery': 0.0.3(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20) '@waku/discovery': 0.0.3(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20)
@ -8267,6 +8365,17 @@ snapshots:
'@webext-core/match-patterns@1.0.3': {} '@webext-core/match-patterns@1.0.3': {}
'@webext-core/messaging@1.4.0':
dependencies:
serialize-error: 11.0.3
webextension-polyfill: 0.10.0
'@webext-core/proxy-service@1.2.0(@webext-core/messaging@1.4.0)(webextension-polyfill@0.12.0)':
dependencies:
'@webext-core/messaging': 1.4.0
get-value: 3.0.1
webextension-polyfill: 0.12.0
'@xobotyi/scrollbar-width@1.9.5': {} '@xobotyi/scrollbar-width@1.9.5': {}
JSONStream@1.3.5: JSONStream@1.3.5:
@ -8482,6 +8591,10 @@ snapshots:
base64-js: 1.5.1 base64-js: 1.5.1
ieee754: 1.2.1 ieee754: 1.2.1
bufferutil@4.0.8:
dependencies:
node-gyp-build: 4.8.2
bundle-name@3.0.0: bundle-name@3.0.0:
dependencies: dependencies:
run-applescript: 5.0.0 run-applescript: 5.0.0
@ -9011,6 +9124,20 @@ snapshots:
dependencies: dependencies:
once: 1.4.0 once: 1.4.0
engine.io-client@6.6.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
engine.io-parser: 5.2.3
ws: 8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
xmlhttprequest-ssl: 2.1.1
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
engine.io-parser@5.2.3: {}
entities@4.5.0: {} entities@4.5.0: {}
env-paths@2.2.1: {} env-paths@2.2.1: {}
@ -9669,6 +9796,10 @@ snapshots:
es-errors: 1.3.0 es-errors: 1.3.0
get-intrinsic: 1.2.4 get-intrinsic: 1.2.4
get-value@3.0.1:
dependencies:
isobject: 3.0.1
giget@1.2.3: giget@1.2.3:
dependencies: dependencies:
citty: 0.1.6 citty: 0.1.6
@ -10209,13 +10340,13 @@ snapshots:
it-take@3.0.6: {} it-take@3.0.6: {}
it-ws@6.1.5: it-ws@6.1.5(bufferutil@4.0.8)(utf-8-validate@6.0.4):
dependencies: dependencies:
'@types/ws': 8.5.12 '@types/ws': 8.5.12
event-iterator: 2.0.0 event-iterator: 2.0.0
it-stream-types: 2.0.2 it-stream-types: 2.0.2
uint8arrays: 5.1.0 uint8arrays: 5.1.0
ws: 8.18.0 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- utf-8-validate - utf-8-validate
@ -10932,7 +11063,7 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
mqtt@5.10.1: mqtt@5.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
dependencies: dependencies:
'@types/readable-stream': 4.0.15 '@types/readable-stream': 4.0.15
'@types/ws': 8.5.12 '@types/ws': 8.5.12
@ -10949,7 +11080,7 @@ snapshots:
rfdc: 1.4.1 rfdc: 1.4.1
split2: 4.2.0 split2: 4.2.0
worker-timers: 7.1.8 worker-timers: 7.1.8
ws: 8.18.0 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
- supports-color - supports-color
@ -11044,6 +11175,8 @@ snapshots:
node-forge@1.3.1: {} node-forge@1.3.1: {}
node-gyp-build@4.8.2: {}
node-notifier@10.0.1: node-notifier@10.0.1:
dependencies: dependencies:
growly: 1.3.0 growly: 1.3.0
@ -11863,6 +11996,10 @@ snapshots:
semver@7.6.3: {} semver@7.6.3: {}
serialize-error@11.0.3:
dependencies:
type-fest: 2.19.0
serialize-error@9.1.1: serialize-error@9.1.1:
dependencies: dependencies:
type-fest: 2.19.0 type-fest: 2.19.0
@ -11945,6 +12082,24 @@ snapshots:
ansi-styles: 6.2.1 ansi-styles: 6.2.1
is-fullwidth-code-point: 5.0.0 is-fullwidth-code-point: 5.0.0
socket.io-client@4.8.0(bufferutil@4.0.8)(utf-8-validate@6.0.4):
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
engine.io-client: 6.6.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
socket.io-parser: 4.2.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.7
transitivePeerDependencies:
- supports-color
sonner@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): sonner@1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
@ -12246,16 +12401,16 @@ snapshots:
trough@2.2.0: {} trough@2.2.0: {}
trystero@0.20.0(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/proto@0.0.7)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20): trystero@0.20.0(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/proto@0.0.7)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20)(bufferutil@4.0.8)(utf-8-validate@6.0.4):
dependencies: dependencies:
'@noble/curves': 1.6.0 '@noble/curves': 1.6.0
'@supabase/supabase-js': 2.45.4 '@supabase/supabase-js': 2.45.4(bufferutil@4.0.8)(utf-8-validate@6.0.4)
'@thaunknown/simple-peer': 10.0.10 '@thaunknown/simple-peer': 10.0.10
'@waku/discovery': 0.0.3(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20) '@waku/discovery': 0.0.3(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20)
'@waku/sdk': 0.0.26(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20) '@waku/sdk': 0.0.26(@libp2p/interface@1.7.0)(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/enr@0.0.21)(@waku/interfaces@0.0.22)(@waku/message-hash@0.1.16)(@waku/relay@0.0.11(@waku/core@0.0.27(@multiformats/multiaddr@12.3.1)(libp2p@1.9.4))(@waku/interfaces@0.0.22)(@waku/proto@0.0.7)(@waku/utils@0.0.20))(@waku/utils@0.0.20)(bufferutil@4.0.8)(utf-8-validate@6.0.4)
firebase: 10.13.1 firebase: 10.13.1
libp2p: 1.9.4 libp2p: 1.9.4
mqtt: 5.10.1 mqtt: 5.10.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@libp2p/bootstrap' - '@libp2p/bootstrap'
- '@libp2p/interface' - '@libp2p/interface'
@ -12541,6 +12696,10 @@ snapshots:
dependencies: dependencies:
react: 18.3.1 react: 18.3.1
utf-8-validate@6.0.4:
dependencies:
node-gyp-build: 4.8.2
utf8-bytes@0.0.1: {} utf8-bytes@0.0.1: {}
utf8-codec@1.0.0: {} utf8-codec@1.0.0: {}
@ -12614,7 +12773,7 @@ snapshots:
ms: 3.0.0-canary.1 ms: 3.0.0-canary.1
supports-color: 9.4.0 supports-color: 9.4.0
web-ext-run@0.2.1: web-ext-run@0.2.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
dependencies: dependencies:
'@babel/runtime': 7.24.7 '@babel/runtime': 7.24.7
'@devicefarmer/adbkit': 3.2.6 '@devicefarmer/adbkit': 3.2.6
@ -12638,7 +12797,7 @@ snapshots:
tmp: 0.2.3 tmp: 0.2.3
update-notifier: 6.0.2 update-notifier: 6.0.2
watchpack: 2.4.1 watchpack: 2.4.1
ws: 8.18.0 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4)
zip-dir: 2.0.0 zip-dir: 2.0.0
transitivePeerDependencies: transitivePeerDependencies:
- bufferutil - bufferutil
@ -12653,6 +12812,8 @@ snapshots:
tiny-uid: 1.1.2 tiny-uid: 1.1.2
webextension-polyfill: 0.9.0 webextension-polyfill: 0.9.0
webextension-polyfill@0.10.0: {}
webextension-polyfill@0.12.0: {} webextension-polyfill@0.12.0: {}
webextension-polyfill@0.9.0: {} webextension-polyfill@0.9.0: {}
@ -12768,9 +12929,17 @@ snapshots:
signal-exit: 3.0.7 signal-exit: 3.0.7
typedarray-to-buffer: 3.1.5 typedarray-to-buffer: 3.1.5
ws@8.18.0: {} ws@8.17.1(bufferutil@4.0.8)(utf-8-validate@6.0.4):
optionalDependencies:
bufferutil: 4.0.8
utf-8-validate: 6.0.4
wxt@0.19.9(@types/node@22.5.5)(rollup@4.21.3): ws@8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4):
optionalDependencies:
bufferutil: 4.0.8
utf-8-validate: 6.0.4
wxt@0.19.9(@types/node@22.5.5)(bufferutil@4.0.8)(rollup@4.21.3)(utf-8-validate@6.0.4):
dependencies: dependencies:
'@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.21.3) '@aklinker1/rollup-plugin-visualizer': 5.12.0(rollup@4.21.3)
'@types/chrome': 0.0.269 '@types/chrome': 0.0.269
@ -12814,7 +12983,7 @@ snapshots:
unimport: 3.12.0(rollup@4.21.3) unimport: 3.12.0(rollup@4.21.3)
vite: 5.4.5(@types/node@22.5.5) vite: 5.4.5(@types/node@22.5.5)
vite-node: 2.1.1(@types/node@22.5.5) vite-node: 2.1.1(@types/node@22.5.5)
web-ext-run: 0.2.1 web-ext-run: 0.2.1(bufferutil@4.0.8)(utf-8-validate@6.0.4)
webextension-polyfill: 0.12.0 webextension-polyfill: 0.12.0
transitivePeerDependencies: transitivePeerDependencies:
- '@types/node' - '@types/node'
@ -12840,6 +13009,8 @@ snapshots:
xmlbuilder@11.0.1: {} xmlbuilder@11.0.1: {}
xmlhttprequest-ssl@2.1.1: {}
y18n@5.0.8: {} y18n@5.0.8: {}
yallist@3.1.1: {} yallist@3.1.1: {}

View file

@ -1,3 +1,4 @@
import { EVENT } from '@/constants/event'
import { browser } from 'wxt/browser' import { browser } from 'wxt/browser'
import { defineBackground } from 'wxt/sandbox' import { defineBackground } from 'wxt/sandbox'
@ -7,8 +8,10 @@ export default defineBackground({
type: 'module', type: 'module',
main() { main() {
browser.runtime.onMessage.addListener(async () => { browser.runtime.onMessage.addListener(async (event: EVENT) => {
browser.runtime.openOptionsPage() if (event === EVENT.OPEN_OPTIONS_PAGE) {
browser.runtime.openOptionsPage()
}
}) })
} }
}) })

View file

@ -5,15 +5,12 @@ import AppButton from '@/app/content/views/AppButton'
import AppContainer from '@/app/content/views/AppContainer' import AppContainer from '@/app/content/views/AppContainer'
import { useRemeshDomain, useRemeshQuery, useRemeshSend } from 'remesh-react' import { useRemeshDomain, useRemeshQuery, useRemeshSend } from 'remesh-react'
import RoomDomain from '@/domain/Room' import RoomDomain from '@/domain/Room'
import { stringToHex } from '@/utils'
import { Toaster } from '@/components/ui/Sonner' import { Toaster } from '@/components/ui/Sonner'
import UserInfoDomain from '@/domain/UserInfo' import UserInfoDomain from '@/domain/UserInfo'
import Setup from '@/app/content/views/Setup' import Setup from '@/app/content/views/Setup'
import MessageListDomain from '@/domain/MessageList' import MessageListDomain from '@/domain/MessageList'
import { useEffect } from 'react' import { useEffect } from 'react'
const hostRoomId = stringToHex(document.location.host)
export default function App() { export default function App() {
const send = useRemeshSend() const send = useRemeshSend()
const roomDomain = useRemeshDomain(RoomDomain()) const roomDomain = useRemeshDomain(RoomDomain())
@ -29,7 +26,7 @@ export default function App() {
useEffect(() => { useEffect(() => {
if (userInfoFinished) { if (userInfoFinished) {
if (userInfo) { if (userInfo) {
!roomFinished && send(roomDomain.command.JoinRoomCommand(hostRoomId)) !roomFinished && send(roomDomain.command.JoinRoomCommand())
} else { } else {
send(messageListDomain.command.ClearListCommand()) send(messageListDomain.command.ClearListCommand())
} }

View file

@ -12,7 +12,7 @@ export interface PromptItemProps {
const PromptItem: FC<PromptItemProps> = ({ data, className }) => { const PromptItem: FC<PromptItemProps> = ({ data, className }) => {
return ( return (
<div className={cn('flex justify-center py-1', className)}> <div className={cn('flex justify-center py-1 px-4', className)}>
<Badge variant="secondary" className="gap-x-2 rounded-full font-medium text-slate-400"> <Badge variant="secondary" className="gap-x-2 rounded-full font-medium text-slate-400">
<Avatar className="size-4"> <Avatar className="size-4">
<AvatarImage src={data.userAvatar} alt="avatar" /> <AvatarImage src={data.userAvatar} alt="avatar" />

View file

@ -8,7 +8,8 @@ import { createShadowRootUi } from 'wxt/client'
import App from './App' import App from './App'
import { IndexDBStorageImpl, BrowserSyncStorageImpl } from '@/domain/impls/Storage' import { IndexDBStorageImpl, BrowserSyncStorageImpl } from '@/domain/impls/Storage'
import { PeerRoomImpl } from '@/domain/impls/PeerRoom' // import { PeerRoomImpl } from '@/domain/impls/PeerRoom'
import { PeerRoomImpl } from '@/domain/impls/PeerRoom2'
import '@/assets/styles/tailwind.css' import '@/assets/styles/tailwind.css'
import { createElement } from '@/utils' import { createElement } from '@/utils'
import { ToastImpl } from '@/domain/impls/Toast' import { ToastImpl } from '@/domain/impls/Toast'
@ -19,7 +20,7 @@ export default defineContentScript({
async main(ctx) { async main(ctx) {
const store = Remesh.store({ const store = Remesh.store({
externs: [IndexDBStorageImpl, BrowserSyncStorageImpl, PeerRoomImpl, ToastImpl], externs: [IndexDBStorageImpl, BrowserSyncStorageImpl, PeerRoomImpl, ToastImpl],
inspectors: !__DEV__ ? [RemeshLogger()] : [] inspectors: __DEV__ ? [RemeshLogger()] : []
}) })
const ui = await createShadowRootUi(ctx, { const ui = await createShadowRootUi(ctx, {

View file

@ -6,13 +6,12 @@ import { Button } from '@/components/ui/Button'
import { getSiteInfo } from '@/utils' import { getSiteInfo } from '@/utils'
import { useRemeshDomain, useRemeshQuery } from 'remesh-react' import { useRemeshDomain, useRemeshQuery } from 'remesh-react'
import RoomDomain from '@/domain/Room' import RoomDomain from '@/domain/Room'
import { selfId } from 'trystero'
const Header: FC = () => { const Header: FC = () => {
const siteInfo = getSiteInfo() const siteInfo = getSiteInfo()
const roomDomain = useRemeshDomain(RoomDomain()) const roomDomain = useRemeshDomain(RoomDomain())
const userList = useRemeshQuery(roomDomain.query.UserListQuery()) const userList = useRemeshQuery(roomDomain.query.UserListQuery())
console.log('userList', [...userList], userList.length) const peerId = useRemeshQuery(roomDomain.query.PeerIdQuery())
return ( return (
<div className="z-10 grid h-12 grid-flow-col items-center justify-between gap-x-4 rounded-t-xl bg-white px-4 backdrop-blur-lg"> <div className="z-10 grid h-12 grid-flow-col items-center justify-between gap-x-4 rounded-t-xl bg-white px-4 backdrop-blur-lg">
@ -27,7 +26,7 @@ const Header: FC = () => {
<Button className="overflow-hidden" variant="link"> <Button className="overflow-hidden" variant="link">
<span className="truncate text-lg font-medium text-slate-600"> <span className="truncate text-lg font-medium text-slate-600">
{/* {siteInfo.hostname.replace(/^www\./i, '')} */} {/* {siteInfo.hostname.replace(/^www\./i, '')} */}
{selfId} {peerId}
</span> </span>
</Button> </Button>
</HoverCardTrigger> </HoverCardTrigger>

View file

@ -21,8 +21,8 @@ const AvatarSelect = React.forwardRef<HTMLInputElement, AvatarSelectProps>(
const handleChange = async (e: ChangeEvent<HTMLInputElement>) => { const handleChange = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0] const file = e.target.files?.[0]
if (file) { if (file) {
if (!/image\/(png|jpeg)/.test(file.type)) { if (!/image\/(png|jpeg|webp)/.test(file.type)) {
onWarning?.(new Error('Only PNG and JPEG image are supported.')) onWarning?.(new Error('Only PNG, JPEG and WebP image are supported.'))
return return
} }

View file

@ -42,7 +42,7 @@ const formSchema = v.object({
avatar: v.pipe( avatar: v.pipe(
v.string(), v.string(),
v.notLength(0, 'Please select your avatar.'), v.notLength(0, 'Please select your avatar.'),
v.maxBytes(8 * 1024, 'Your avatar cannot exceed 8kb.') v.maxBytes(8 * 1024, `Your avatar cannot exceed 8kb.`)
), ),
themeMode: v.pipe( themeMode: v.pipe(
v.string(), v.string(),
@ -92,32 +92,24 @@ const ProfileForm = () => {
control={form.control} control={form.control}
name="avatar" name="avatar"
render={({ field }) => ( render={({ field }) => (
<FormItem className="absolute left-1/2 top-0 grid -translate-x-1/2 -translate-y-1/2 justify-items-center"> <FormItem className="absolute inset-x-1 top-0 mx-auto grid w-fit -translate-y-1/2 justify-items-center">
<FormControl> <FormControl>
<div className="grid justify-items-center gap-y-2"> <AvatarSelect
<AvatarSelect compressSize={MAX_AVATAR_SIZE}
compressSize={MAX_AVATAR_SIZE} onError={handleError}
onError={handleError} onWarning={handleWarning}
onWarning={handleWarning} className="shadow-lg"
className="shadow-lg" {...field}
{...field} ></AvatarSelect>
></AvatarSelect>
<Button
type="button"
size="xs"
className="mx-auto flex items-center gap-x-2"
onClick={handleRefreshAvatar}
>
<RefreshCcwIcon size={14} />
Ugly Avatar
</Button>
</div>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
)} )}
/> />
<Button type="button" size="xs" className="mx-auto flex items-center gap-x-2" onClick={handleRefreshAvatar}>
<RefreshCcwIcon size={14} />
Ugly Avatar
</Button>
<FormField <FormField
control={form.control} control={form.control}
name="name" name="name"

View file

@ -194,4 +194,4 @@ export const STORAGE_NAME = 'WEB_CHAT' as const
* Image is encoded as base64, and the size is increased by about 33%. * Image is encoded as base64, and the size is increased by about 33%.
* 8kb * (1 - 0.33) = 5488 bytes * 8kb * (1 - 0.33) = 5488 bytes
*/ */
export const MAX_AVATAR_SIZE = 5488 as const export const MAX_AVATAR_SIZE = 5120 as const

View file

@ -1,5 +1,5 @@
import { Remesh } from 'remesh' import { Remesh } from 'remesh'
import { map, merge, switchMap, tap, of, EMPTY, mergeMap } from 'rxjs' import { map, merge, of, EMPTY, mergeMap } from 'rxjs'
import { NormalMessage, type MessageUser } from './MessageList' import { NormalMessage, type MessageUser } from './MessageList'
import { PeerRoomExtern } from '@/domain/externs/PeerRoom' import { PeerRoomExtern } from '@/domain/externs/PeerRoom'
import MessageListDomain, { MessageType } from '@/domain/MessageList' import MessageListDomain, { MessageType } from '@/domain/MessageList'
@ -51,6 +51,18 @@ const RoomDomain = Remesh.domain({
const userInfoDomain = domain.getDomain(UserInfoDomain()) const userInfoDomain = domain.getDomain(UserInfoDomain())
const peerRoom = domain.getExtern(PeerRoomExtern) const peerRoom = domain.getExtern(PeerRoomExtern)
const PeerIdState = domain.state<string>({
name: 'Room.PeerIdState',
default: peerRoom.peerId
})
const PeerIdQuery = domain.query({
name: 'Room.PeerIdQuery',
impl: ({ get }) => {
return get(PeerIdState())
}
})
const MessageListQuery = messageListDomain.query.ListQuery const MessageListQuery = messageListDomain.query.ListQuery
const RoomStatusModule = StatusModule(domain, { const RoomStatusModule = StatusModule(domain, {
@ -71,16 +83,17 @@ const RoomDomain = Remesh.domain({
const JoinRoomCommand = domain.command({ const JoinRoomCommand = domain.command({
name: 'RoomJoinRoomCommand', name: 'RoomJoinRoomCommand',
impl: ({ get }, roomId: string) => { impl: ({ get }) => {
peerRoom.joinRoom(roomId) peerRoom.joinRoom()
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())! const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
return [ return [
JoinRoomEvent(roomId),
RoomStatusModule.command.SetFinishedCommand(),
UpdateUserListCommand({ UpdateUserListCommand({
type: 'create', type: 'create',
user: { peerId: peerRoom.selfId, joinTime: Date.now(), userId, username, userAvatar } user: { peerId: peerRoom.peerId, joinTime: Date.now(), userId, username, userAvatar }
}) }),
RoomStatusModule.command.SetFinishedCommand(),
JoinRoomEvent(peerRoom.roomId)
] ]
} }
}) })
@ -91,12 +104,12 @@ const RoomDomain = Remesh.domain({
peerRoom.leaveRoom() peerRoom.leaveRoom()
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())! const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
return [ return [
LeaveRoomEvent(roomId),
RoomStatusModule.command.SetInitialCommand(),
UpdateUserListCommand({ UpdateUserListCommand({
type: 'delete', type: 'delete',
user: { peerId: peerRoom.selfId, joinTime: Date.now(), userId, username, userAvatar } user: { peerId: peerRoom.peerId, joinTime: Date.now(), userId, username, userAvatar }
}) }),
RoomStatusModule.command.SetInitialCommand(),
LeaveRoomEvent(roomId)
] ]
} }
}) })
@ -105,22 +118,26 @@ const RoomDomain = Remesh.domain({
name: 'RoomSendTextMessageCommand', name: 'RoomSendTextMessageCommand',
impl: ({ get }, message: string) => { impl: ({ get }, message: string) => {
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())! const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
const id = nanoid()
const date = Date.now() const textMessage: TextMessage = {
return [ id: nanoid(),
messageListDomain.command.CreateItemCommand({ type: SendType.Text,
id, body: message,
type: MessageType.Normal, userId,
body: message, username,
date, userAvatar
userId, }
username,
userAvatar, const listMessage: NormalMessage = {
likeUsers: [], ...textMessage,
hateUsers: [] type: MessageType.Normal,
}), date: Date.now(),
SendTextMessageEvent({ id, body: message, userId, username, userAvatar, type: SendType.Text }) likeUsers: [],
] hateUsers: []
}
peerRoom.sendMessage<RoomMessage>(textMessage)
return [messageListDomain.command.CreateItemCommand(listMessage), SendTextMessageEvent(textMessage)]
} }
}) })
@ -128,28 +145,23 @@ const RoomDomain = Remesh.domain({
name: 'RoomSendLikeMessageCommand', name: 'RoomSendLikeMessageCommand',
impl: ({ get }, messageId: string) => { impl: ({ get }, messageId: string) => {
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())! const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
const _message = get(messageListDomain.query.ItemQuery(messageId)) as NormalMessage const localMessage = get(messageListDomain.query.ItemQuery(messageId)) as NormalMessage
return [
messageListDomain.command.UpdateItemCommand({ const likeMessage: LikeMessage = {
..._message, id: messageId,
likeUsers: desert( userId,
_message.likeUsers, username,
{ userAvatar,
userId, type: SendType.Like
username, }
userAvatar const listMessage: NormalMessage = {
}, ...localMessage,
'userId' type: MessageType.Normal,
) date: Date.now(),
}), likeUsers: desert(localMessage.likeUsers, likeMessage, 'userId')
SendLikeMessageEvent({ }
id: messageId, peerRoom.sendMessage<RoomMessage>(likeMessage)
userId, return [messageListDomain.command.UpdateItemCommand(listMessage), SendLikeMessageEvent(likeMessage)]
username,
userAvatar,
type: SendType.Like
})
]
} }
}) })
@ -157,47 +169,55 @@ const RoomDomain = Remesh.domain({
name: 'RoomSendHateMessageCommand', name: 'RoomSendHateMessageCommand',
impl: ({ get }, messageId: string) => { impl: ({ get }, messageId: string) => {
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())! const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
const _message = get(messageListDomain.query.ItemQuery(messageId)) as NormalMessage const localMessage = get(messageListDomain.query.ItemQuery(messageId)) as NormalMessage
return [ const hateMessage: HateMessage = {
messageListDomain.command.UpdateItemCommand({ id: messageId,
..._message, userId,
hateUsers: desert( username,
_message.hateUsers, userAvatar,
{ type: SendType.Hate
userId, }
username, const listMessage: NormalMessage = {
userAvatar ...localMessage,
}, type: MessageType.Normal,
'userId' date: Date.now(),
) hateUsers: desert(localMessage.hateUsers, hateMessage, 'userId')
}), }
SendHateMessageEvent({ id: messageId, userId, username, userAvatar, type: SendType.Hate }) peerRoom.sendMessage<RoomMessage>(hateMessage)
] return [messageListDomain.command.UpdateItemCommand(listMessage), SendHateMessageEvent(hateMessage)]
} }
}) })
const SendUserSyncMessageCommand = domain.command({ const SendUserSyncMessageCommand = domain.command({
name: 'RoomSendUserSyncMessageCommand', name: 'RoomSendUserSyncMessageCommand',
impl: ({ get }, targetPeerId: string) => { impl: ({ get }, targetPeerId: string) => {
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())! const self = get(UserListQuery()).find((user) => user.peerId === peerRoom.peerId)!
const joinTime = get(UserListQuery()).find((u) => u.peerId === peerRoom.selfId)?.joinTime || Date.now()
return [ const syncUserMessage: SyncUserMessage = {
SendUserSyncMessageEvent({ ...self,
id: nanoid(), id: nanoid(),
peerId: peerRoom.selfId, type: SendType.UserSync
targetPeerId, }
userId,
joinTime, peerRoom.sendMessage<RoomMessage>(syncUserMessage, targetPeerId)
username, return [SendUserSyncMessageEvent(syncUserMessage)]
userAvatar,
type: SendType.UserSync
})
]
} }
}) })
const SendUserSyncMessageEvent = domain.event<SyncUserMessage & { targetPeerId: string }>({ const UpdateUserListCommand = domain.command({
name: 'RoomUpdateUserListCommand',
impl: ({ get }, action: { type: 'create' | 'delete'; user: RoomUser }) => {
const userList = get(UserListState())
if (action.type === 'create') {
return [UserListState().new(upsert(userList, action.user, 'peerId'))]
} else {
return [UserListState().new(userList.filter(({ peerId }) => peerId !== action.user.peerId))]
}
}
})
const SendUserSyncMessageEvent = domain.event<SyncUserMessage>({
name: 'RoomSendUserSyncMessageEvent' name: 'RoomSendUserSyncMessageEvent'
}) })
@ -233,94 +253,47 @@ const RoomDomain = Remesh.domain({
name: 'RoomOnLeaveRoomEvent' name: 'RoomOnLeaveRoomEvent'
}) })
const UpdateUserListCommand = domain.command({
name: 'RoomUpdateUserListCommand',
impl: ({ get }, action: { type: 'create' | 'delete'; user: RoomUser }) => {
const userList = get(UserListState())
if (action.type === 'create') {
return [UserListState().new(upsert(userList, action.user, 'peerId'))]
} else {
return [UserListState().new(userList.filter(({ peerId }) => peerId !== action.user.peerId))]
}
}
})
domain.effect({ domain.effect({
name: 'RoomSendTextMessageEffect', name: 'RoomOnJoinRoomEffect',
impl: ({ fromEvent }) => { impl: () => {
const sendMessage$ = fromEvent(SendTextMessageEvent).pipe( const onJoinRoom$ = callbackToObservable<string>(peerRoom.onJoinRoom).pipe(
tap(async (message) => { mergeMap((peerId) => {
peerRoom.sendMessage<RoomMessage>(message) console.log('onJoinRoom', peerId)
if (peerRoom.peerId === peerId) {
return [OnJoinRoomEvent(peerId)]
} else {
return [SendUserSyncMessageCommand(peerId), OnJoinRoomEvent(peerId)]
}
}) })
) )
return merge(sendMessage$).pipe(map(() => null)) return onJoinRoom$
}
})
domain.effect({
name: 'RoomSendLikeMessageEffect',
impl: ({ fromEvent }) => {
const likeMessage$ = fromEvent(SendLikeMessageEvent).pipe(
tap(async (message) => {
return peerRoom.sendMessage<RoomMessage>(message)
})
)
return merge(likeMessage$).pipe(map(() => null))
}
})
domain.effect({
name: 'RoomSendHateMessageEffect',
impl: ({ fromEvent }) => {
const hateMessage$ = fromEvent(SendHateMessageEvent).pipe(
tap(async (message) => {
peerRoom.sendMessage<RoomMessage>(message)
})
)
return merge(hateMessage$).pipe(map(() => null))
}
})
domain.effect({
name: 'RoomSendUserSyncMessageEffect',
impl: ({ fromEvent }) => {
const userSyncMessage$ = fromEvent(SendUserSyncMessageEvent).pipe(
tap(async (message) => {
console.log('sendMessage', message)
peerRoom.sendMessage<RoomMessage>(message, message.targetPeerId)
})
)
return merge(userSyncMessage$).pipe(map(() => null))
} }
}) })
domain.effect({ domain.effect({
name: 'RoomOnMessageEffect', name: 'RoomOnMessageEffect',
impl: ({ fromEvent, get }) => { impl: ({ get }) => {
const onMessage$ = fromEvent(JoinRoomEvent).pipe( const onMessage$ = callbackToObservable<RoomMessage>(peerRoom.onMessage).pipe(
switchMap(() => callbackToObservable<RoomMessage>(peerRoom.onMessage.bind(peerRoom))),
mergeMap((message) => { mergeMap((message) => {
console.log('onMessage', message) console.log('onMessage', message)
const messageEvent$ = of(OnMessageEvent(message)) const messageEvent$ = of(OnMessageEvent(message))
const commandEvent$ = (() => { const commandEvent$ = (() => {
switch (message.type) { switch (message.type) {
case SendType.UserSync: { case SendType.UserSync: {
const self = get(UserListQuery()).find((user) => user.peerId === peerRoom.selfId)! const self = get(UserListQuery()).find((user) => user.peerId === peerRoom.peerId)!
if (self.joinTime > message.joinTime) { const isJoining = self.joinTime < message.joinTime
return EMPTY
}
return of( return of(
UpdateUserListCommand({ type: 'create', user: message }), UpdateUserListCommand({ type: 'create', user: message }),
messageListDomain.command.CreateItemCommand({ isJoining
...message, ? messageListDomain.command.CreateItemCommand({
id: nanoid(), ...message,
body: `"${message.username}" joined the chat`, id: nanoid(),
type: MessageType.Prompt, body: `"${message.username}" joined the chat`,
date: Date.now() type: MessageType.Prompt,
}) date: Date.now()
})
: null
) )
} }
case SendType.Text: case SendType.Text:
@ -356,7 +329,7 @@ const RoomDomain = Remesh.domain({
) )
} }
default: default:
console.warn('未知消息类型', message) console.warn('Unsupported message type', message)
return EMPTY return EMPTY
} }
})() })()
@ -367,36 +340,14 @@ const RoomDomain = Remesh.domain({
} }
}) })
domain.effect({
name: 'RoomOnJoinRoomEffect',
impl: ({ fromEvent, get }) => {
const onJoinRoom$ = fromEvent(JoinRoomEvent).pipe(
switchMap(() => callbackToObservable<string>(peerRoom.onJoinRoom.bind(peerRoom))),
mergeMap((peerId) => {
console.log('onJoinRoom', peerId)
const { id: userId, name: username, avatar: userAvatar } = get(userInfoDomain.query.UserInfoQuery())!
return [
SendUserSyncMessageCommand(peerId),
UpdateUserListCommand({
type: 'create',
user: { peerId, joinTime: Date.now(), userId, username, userAvatar }
}),
OnJoinRoomEvent(peerId)
]
})
)
return onJoinRoom$
}
})
domain.effect({ domain.effect({
name: 'RoomOnLeaveRoomEffect', name: 'RoomOnLeaveRoomEffect',
impl: ({ fromEvent, get }) => { impl: ({ get }) => {
const onLeaveRoom$ = fromEvent(JoinRoomEvent).pipe( const onLeaveRoom$ = callbackToObservable<string>(peerRoom.onLeaveRoom).pipe(
switchMap(() => callbackToObservable<string>(peerRoom.onLeaveRoom.bind(peerRoom))),
map((peerId) => { map((peerId) => {
console.log('onLeaveRoom', peerId) console.log('onLeaveRoom', peerId)
const user = get(UserListQuery()).find((user) => user.peerId === peerId) const user = get(UserListQuery()).find((user) => user.peerId === peerId)
if (user) { if (user) {
return [ return [
UpdateUserListCommand({ type: 'delete', user }), UpdateUserListCommand({ type: 'delete', user }),
@ -420,6 +371,7 @@ const RoomDomain = Remesh.domain({
return { return {
query: { query: {
PeerIdQuery,
UserListQuery, UserListQuery,
MessageListQuery, MessageListQuery,
...RoomStatusModule.query ...RoomStatusModule.query

View file

@ -1,25 +1,27 @@
import { Remesh } from 'remesh' import { Remesh } from 'remesh'
import { type Promisable } from 'type-fest'
export type PeerMessage = object | Blob | ArrayBuffer | ArrayBufferView export type PeerMessage = object | Blob | ArrayBuffer | ArrayBufferView
export interface PeerRoom { export interface PeerRoom {
readonly selfId: string readonly peerId: string
joinRoom: (roomId: string) => Promise<any> readonly roomId: string
sendMessage: <T extends PeerMessage>(message: T, id?: string) => Promise<any> joinRoom: () => PeerRoom
onMessage: <T extends PeerMessage>(callback: (message: T) => void) => Promisable<void> sendMessage: <T extends PeerMessage>(message: T, id?: string) => PeerRoom
leaveRoom: () => Promisable<void> onMessage: <T extends PeerMessage>(callback: (message: T) => void) => PeerRoom
onJoinRoom: (callback: (id: string) => void) => Promisable<void> leaveRoom: () => PeerRoom
onLeaveRoom: (callback: (id: string) => void) => Promisable<void> onJoinRoom: (callback: (id: string) => void) => PeerRoom
onLeaveRoom: (callback: (id: string) => void) => PeerRoom
onError: (callback: (error: Error) => void) => PeerRoom
} }
export const PeerRoomExtern = Remesh.extern<PeerRoom>({ export const PeerRoomExtern = Remesh.extern<PeerRoom>({
default: { default: {
selfId: '', peerId: '',
joinRoom: async () => { roomId: '',
joinRoom: () => {
throw new Error('"joinRoom" not implemented.') throw new Error('"joinRoom" not implemented.')
}, },
sendMessage: async () => { sendMessage: () => {
throw new Error('"sendMessage" not implemented.') throw new Error('"sendMessage" not implemented.')
}, },
onMessage: () => { onMessage: () => {
@ -33,6 +35,9 @@ export const PeerRoomExtern = Remesh.extern<PeerRoom>({
}, },
onLeaveRoom: () => { onLeaveRoom: () => {
throw new Error('"onLeaveRoom" not implemented.') throw new Error('"onLeaveRoom" not implemented.')
},
onError: () => {
throw new Error('"onError" not implemented.')
} }
} }
}) })

View file

@ -1,64 +1,151 @@
import { type DataPayload, type Room, joinRoom, selfId } from 'trystero' import { type DataPayload, type Room, joinRoom, selfId } from 'trystero'
// import { joinRoom } from 'trystero/firebase' // import { joinRoom } from 'trystero/firebase'
import { PeerRoomExtern, type PeerMessage } from '@/domain/externs/PeerRoom' import { PeerRoomExtern, type PeerMessage } from '@/domain/externs/PeerRoom'
import { stringToHex } from '@/utils' import { stringToHex } from '@/utils'
import EventHub from '@resreq/event-hub'
export interface Config { export interface Config {
appId: string peerId?: string
roomId: string
} }
class PeerRoom { class PeerRoom extends EventHub {
readonly appId: string readonly appId: string
room: Room | null private room?: Room
readonly selfId: string readonly roomId: string
readonly peerId: string
constructor(config: Config) { constructor(config: Config) {
this.appId = config.appId super()
this.room = null this.appId = __NAME__
this.selfId = selfId this.roomId = config.roomId
this.peerId = selfId
this.joinRoom = this.joinRoom.bind(this)
this.sendMessage = this.sendMessage.bind(this)
this.onMessage = this.onMessage.bind(this)
this.onJoinRoom = this.onJoinRoom.bind(this)
this.onLeaveRoom = this.onLeaveRoom.bind(this)
this.leaveRoom = this.leaveRoom.bind(this)
this.onError = this.onError.bind(this)
} }
async joinRoom(roomId: string) { joinRoom() {
this.room = joinRoom({ appId: this.appId }, roomId) this.room = joinRoom({ appId: this.appId }, this.roomId)
/**
return this.room * If we wait to join, it will result in not being able to listen to our own join event.
* This might be related to the fact that:
* (If called more than once, only the latest callback registered is ever called.)
* Multiple listeners may overwrite each other.
* @see: https://github.com/dmotz/trystero?tab=readme-ov-file#onpeerjoincallback
*/
// this.room.onPeerJoin(() => this.emit('action'))
this.emit('action')
return this
} }
async sendMessage<T extends PeerMessage>(message: T, id?: string) { sendMessage<T extends PeerMessage>(message: T, id?: string) {
if (!this.room) { if (!this.room) {
throw new Error('Room not joined') this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
const [send] = this.room.makeAction('MESSAGE')
send(message as DataPayload, id)
})
} else {
const [send] = this.room.makeAction('MESSAGE')
send(message as DataPayload, id)
} }
const [send] = this.room!.makeAction('MESSAGE')
return await send(message as DataPayload, id) return this
} }
onMessage<T extends PeerMessage>(callback: (message: T) => void) { onMessage<T extends PeerMessage>(callback: (message: T) => void) {
if (!this.room) { if (!this.room) {
throw new Error('Room not joined') this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
const [, on] = this.room.makeAction('MESSAGE')
on((message) => callback(message as T))
})
} else {
const [, on] = this.room.makeAction('MESSAGE')
on((message) => callback(message as T))
} }
const [, on] = this.room!.makeAction('MESSAGE') return this
on((message) => callback(message as T))
} }
onJoinRoom(callback: (id: string) => void) { onJoinRoom(callback: (id: string) => void) {
if (!this.room) { if (!this.room) {
throw new Error('Room not joined') this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.onPeerJoin((peerId) => {
callback(peerId)
})
})
} else {
this.room.onPeerJoin((peerId) => {
callback(peerId)
})
} }
this.room.onPeerJoin((peerId) => callback(peerId)) return this
} }
onLeaveRoom(callback: (id: string) => void) { onLeaveRoom(callback: (id: string) => void) {
if (!this.room) { if (!this.room) {
throw new Error('Room not joined') this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.onPeerLeave((peerId) => callback(peerId))
})
} else {
this.room.onPeerLeave((peerId) => callback(peerId))
} }
this.room.onPeerLeave((peerId) => callback(peerId)) return this
} }
async leaveRoom() { leaveRoom() {
return await this.room?.leave() if (!this.room) {
this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.leave()
this.room = undefined
})
} else {
this.room.leave()
this.room = undefined
}
return this
}
onError(callback: (error: Error) => void) {
this.on('error', (error: Error) => callback(error))
return this
} }
} }
const peerRoom = new PeerRoom({ appId: stringToHex(__NAME__) }) const hostRoomId = stringToHex(document.location.host)
const peerRoom = new PeerRoom({ roomId: hostRoomId })
export const PeerRoomImpl = PeerRoomExtern.impl(peerRoom) export const PeerRoomImpl = PeerRoomExtern.impl(peerRoom)
// https://github.com/w3c/webextensions/issues/72
// https://issues.chromium.org/issues/40251342
// https://github.com/w3c/webrtc-extensions/issues/77

View file

@ -0,0 +1,142 @@
import { Artico, Room } from '@rtco/client'
import { PeerRoomExtern, type PeerMessage } from '@/domain/externs/PeerRoom'
import { stringToHex } from '@/utils'
import { nanoid } from 'nanoid'
import EventHub from '@resreq/event-hub'
export interface Config {
peerId?: string
roomId: string
}
class PeerRoom extends EventHub {
readonly roomId: string
private rtco?: Artico
readonly peerId: string
private room?: Room
constructor(config: Config) {
super()
this.roomId = config.roomId
this.peerId = config.peerId || nanoid()
this.joinRoom = this.joinRoom.bind(this)
this.sendMessage = this.sendMessage.bind(this)
this.onMessage = this.onMessage.bind(this)
this.onJoinRoom = this.onJoinRoom.bind(this)
this.onLeaveRoom = this.onLeaveRoom.bind(this)
this.leaveRoom = this.leaveRoom.bind(this)
this.onError = this.onError.bind(this)
}
joinRoom() {
if (!this.rtco) {
this.rtco = new Artico({ id: this.peerId })
}
if (this.room) {
this.room = this.rtco.join(this.roomId)
} else {
this.rtco!.on('open', () => {
this.room = this.rtco!.join(this.roomId)
this.emit('action')
})
}
return this
}
sendMessage<T extends PeerMessage>(message: T, id?: string) {
if (!this.room) {
this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.send(JSON.stringify(message), id)
})
} else {
this.room.send(JSON.stringify(message), id)
}
return this
}
onMessage<T extends PeerMessage>(callback: (message: T) => void) {
if (!this.room) {
this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.on('message', (message) => callback(JSON.parse(message) as T))
})
} else {
this.room.on('message', (message) => callback(JSON.parse(message) as T))
}
return this
}
onJoinRoom(callback: (id: string) => void) {
if (!this.room) {
this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.on('join', (id) => callback(id))
})
} else {
this.room.on('join', (id) => callback(id))
}
return this
}
onLeaveRoom(callback: (id: string) => void) {
if (!this.room) {
this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.on('leave', (id) => callback(id))
})
} else {
this.room.on('leave', (id) => callback(id))
}
return this
}
leaveRoom() {
if (!this.room) {
this.once('action', () => {
if (!this.room) {
const error = new Error('Room not joined')
this.emit('error', error)
throw error
}
this.room.leave()
this.room = undefined
})
} else {
this.room.leave()
this.room = undefined
}
return this
}
onError(callback: (error: Error) => void) {
this.rtco?.on('error', (error) => callback(error))
this.on('error', (error: Error) => callback(error))
return this
}
}
const hostRoomId = stringToHex(document.location.host)
const peerRoom = new PeerRoom({ roomId: hostRoomId })
export const PeerRoomImpl = PeerRoomExtern.impl(peerRoom)
// https://github.com/w3c/webextensions/issues/72
// https://issues.chromium.org/issues/40251342
// https://github.com/w3c/webrtc-extensions/issues/77

View file

@ -1,11 +1,11 @@
import generateUglyAvatar from '@/lib/uglyAvatar' import generateUglyAvatar from '@/lib/uglyAvatar'
import compressImage from './compressImage' import compressImage from './compressImage'
const generateRandomAvatar = async (idealSize: number) => { const generateRandomAvatar = async (targetSize: number) => {
const svgBlob = generateUglyAvatar() const svgBlob = generateUglyAvatar()
// compressImage can't directly compress svg, need to convert to jpeg first // compressImage can't directly compress svg, need to convert to jpeg first
const jpegBlob = await new Promise<Blob>((resolve, reject) => { const imageBlob = await new Promise<Blob>((resolve, reject) => {
const image = new Image() const image = new Image()
image.onload = async () => { image.onload = async () => {
const canvas = new OffscreenCanvas(image.width, image.height) const canvas = new OffscreenCanvas(image.width, image.height)
@ -17,7 +17,7 @@ const generateRandomAvatar = async (idealSize: number) => {
image.onerror = () => reject(new Error('Failed to load SVG')) image.onerror = () => reject(new Error('Failed to load SVG'))
image.src = URL.createObjectURL(svgBlob) image.src = URL.createObjectURL(svgBlob)
}) })
const miniAvatarBlob = await compressImage({ input: jpegBlob, targetSize: idealSize }) const miniAvatarBlob = await compressImage({ input: imageBlob, targetSize })
const miniAvatarBase64 = await new Promise<string>((resolve, reject) => { const miniAvatarBase64 = await new Promise<string>((resolve, reject) => {
const reader = new FileReader() const reader = new FileReader()
reader.onload = (e) => resolve(e.target?.result as string) reader.onload = (e) => resolve(e.target?.result as string)