WIP voice channels
This commit is contained in:
@@ -18,5 +18,8 @@ VITE_DEFAULT_RELAYS=relay.damus.io,relay.primal.net,nostr.mom
|
|||||||
VITE_DEFAULT_MESSAGING_RELAYS=auth.nostr1.com,nostr-01.uid.ovh,relay.keychat.io,relay.0xchat.com
|
VITE_DEFAULT_MESSAGING_RELAYS=auth.nostr1.com,nostr-01.uid.ovh,relay.keychat.io,relay.0xchat.com
|
||||||
VITE_SIGNER_RELAYS=relay.nsec.app,ephemeral.snowflare.cc,bucket.coracle.social
|
VITE_SIGNER_RELAYS=relay.nsec.app,ephemeral.snowflare.cc,bucket.coracle.social
|
||||||
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
|
VITE_VAPID_PUBLIC_KEY=BIt2D4BdgdbCowD_0d3Np6GbrIGHxd7aIEUeZNe3hQuRlHz02OhzvDaai0XSFoJYVzSzdMjdyW-QhvW9_yq8j4Y
|
||||||
|
VITE_LIVEKIT_URL=
|
||||||
|
VITE_LIVEKIT_API_KEY=
|
||||||
|
VITE_LIVEKIT_API_SECRET=
|
||||||
VITE_GLITCHTIP_API_KEY=
|
VITE_GLITCHTIP_API_KEY=
|
||||||
GLITCHTIP_AUTH_TOKEN=
|
GLITCHTIP_AUTH_TOKEN=
|
||||||
|
|||||||
@@ -83,6 +83,8 @@
|
|||||||
"fuse.js": "^7.1.0",
|
"fuse.js": "^7.1.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"idb": "^8.0.3",
|
"idb": "^8.0.3",
|
||||||
|
"jose": "^6.1.3",
|
||||||
|
"livekit-client": "^2.17.2",
|
||||||
"nostr-signer-capacitor-plugin": "github:coracle-social/nostr-signer-capacitor-plugin#main",
|
"nostr-signer-capacitor-plugin": "github:coracle-social/nostr-signer-capacitor-plugin#main",
|
||||||
"nostr-tools": "^2.19.4",
|
"nostr-tools": "^2.19.4",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.14",
|
"prettier-plugin-tailwindcss": "^0.6.14",
|
||||||
|
|||||||
Generated
+97
@@ -134,6 +134,12 @@ importers:
|
|||||||
idb:
|
idb:
|
||||||
specifier: ^8.0.3
|
specifier: ^8.0.3
|
||||||
version: 8.0.3
|
version: 8.0.3
|
||||||
|
jose:
|
||||||
|
specifier: ^6.1.3
|
||||||
|
version: 6.1.3
|
||||||
|
livekit-client:
|
||||||
|
specifier: ^2.17.2
|
||||||
|
version: 2.17.2(@types/dom-mediacapture-record@1.0.22)
|
||||||
nostr-signer-capacitor-plugin:
|
nostr-signer-capacitor-plugin:
|
||||||
specifier: github:coracle-social/nostr-signer-capacitor-plugin#main
|
specifier: github:coracle-social/nostr-signer-capacitor-plugin#main
|
||||||
version: https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1)
|
version: https://codeload.github.com/coracle-social/nostr-signer-capacitor-plugin/tar.gz/be4bb90a1a15c8eec0934f2f66ce9e82ecc72d51(@capacitor/core@8.0.1)
|
||||||
@@ -737,6 +743,9 @@ packages:
|
|||||||
'@braintree/sanitize-url@7.1.1':
|
'@braintree/sanitize-url@7.1.1':
|
||||||
resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==}
|
resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==}
|
||||||
|
|
||||||
|
'@bufbuild/protobuf@1.10.1':
|
||||||
|
resolution: {integrity: sha512-wJ8ReQbHxsAfXhrf9ixl0aYbZorRuOWpBNzm8pL8ftmSxQx/wnJD5Eg861NwJU/czy2VXFIebCeZnZrI9rktIQ==}
|
||||||
|
|
||||||
'@canvas/image-data@1.1.0':
|
'@canvas/image-data@1.1.0':
|
||||||
resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==}
|
resolution: {integrity: sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==}
|
||||||
|
|
||||||
@@ -1283,6 +1292,12 @@ packages:
|
|||||||
'@jridgewell/trace-mapping@0.3.9':
|
'@jridgewell/trace-mapping@0.3.9':
|
||||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||||
|
|
||||||
|
'@livekit/mutex@1.1.1':
|
||||||
|
resolution: {integrity: sha512-EsshAucklmpuUAfkABPxJNhzj9v2sG7JuzFDL4ML1oJQSV14sqrpTYnsaOudMAw9yOaW53NU3QQTlUQoRs4czw==}
|
||||||
|
|
||||||
|
'@livekit/protocol@1.44.0':
|
||||||
|
resolution: {integrity: sha512-/vfhDUGcUKO8Q43r6i+5FrDhl5oZjm/X3U4x2Iciqvgn5C8qbj+57YPcWSJ1kyIZm5Cm6AV2nAPjMm3ETD/iyg==}
|
||||||
|
|
||||||
'@noble/ciphers@0.5.3':
|
'@noble/ciphers@0.5.3':
|
||||||
resolution: {integrity: sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==}
|
resolution: {integrity: sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==}
|
||||||
|
|
||||||
@@ -1823,6 +1838,9 @@ packages:
|
|||||||
'@types/cookie@0.6.0':
|
'@types/cookie@0.6.0':
|
||||||
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||||
|
|
||||||
|
'@types/dom-mediacapture-record@1.0.22':
|
||||||
|
resolution: {integrity: sha512-mUMZLK3NvwRLcAAT9qmcK+9p7tpU2FHdDsntR3YI4+GY88XrgG4XiE7u1Q2LAN2/FZOz/tdMDC3GQCR4T8nFuw==}
|
||||||
|
|
||||||
'@types/eslint@9.6.1':
|
'@types/eslint@9.6.1':
|
||||||
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
|
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
|
||||||
|
|
||||||
@@ -3334,6 +3352,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
jose@6.1.3:
|
||||||
|
resolution: {integrity: sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==}
|
||||||
|
|
||||||
js-base64@3.7.8:
|
js-base64@3.7.8:
|
||||||
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
||||||
|
|
||||||
@@ -3438,6 +3459,11 @@ packages:
|
|||||||
linkifyjs@4.3.2:
|
linkifyjs@4.3.2:
|
||||||
resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
|
resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==}
|
||||||
|
|
||||||
|
livekit-client@2.17.2:
|
||||||
|
resolution: {integrity: sha512-+67y2EtAWZabARlY7kANl/VT1Uu1EJYR5a8qwpT2ub/uBCltsEgEDOxCIMwE9HFR5w+z41HR6GL9hyEvW/y6CQ==}
|
||||||
|
peerDependencies:
|
||||||
|
'@types/dom-mediacapture-record': ^1
|
||||||
|
|
||||||
load-json-file@4.0.0:
|
load-json-file@4.0.0:
|
||||||
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
|
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -3472,6 +3498,10 @@ packages:
|
|||||||
lodash@4.17.23:
|
lodash@4.17.23:
|
||||||
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
||||||
|
|
||||||
|
loglevel@1.9.2:
|
||||||
|
resolution: {integrity: sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg==}
|
||||||
|
engines: {node: '>= 0.6.0'}
|
||||||
|
|
||||||
lru-cache@10.4.3:
|
lru-cache@10.4.3:
|
||||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||||
|
|
||||||
@@ -4277,6 +4307,9 @@ packages:
|
|||||||
run-parallel@1.2.0:
|
run-parallel@1.2.0:
|
||||||
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
|
||||||
|
|
||||||
|
rxjs@7.8.2:
|
||||||
|
resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
|
||||||
|
|
||||||
sade@1.8.1:
|
sade@1.8.1:
|
||||||
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -4306,6 +4339,13 @@ packages:
|
|||||||
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
|
resolution: {integrity: sha512-1n3r/tGXO6b6VXMdFT54SHzT9ytu9yr7TaELowdYpMqY/Ao7EnlQGmAQ1+RatX7Tkkdm6hONI2owqNx2aZj5Sw==}
|
||||||
engines: {node: '>=11.0.0'}
|
engines: {node: '>=11.0.0'}
|
||||||
|
|
||||||
|
sdp-transform@2.15.0:
|
||||||
|
resolution: {integrity: sha512-KrOH82c/W+GYQ0LHqtr3caRpM3ITglq3ljGUIb8LTki7ByacJZ9z+piSGiwZDsRyhQbYBOBJgr2k6X4BZXi3Kw==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
sdp@3.2.1:
|
||||||
|
resolution: {integrity: sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==}
|
||||||
|
|
||||||
semver@5.7.2:
|
semver@5.7.2:
|
||||||
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
@@ -4641,6 +4681,9 @@ packages:
|
|||||||
peerDependencies:
|
peerDependencies:
|
||||||
typescript: '>=4.8.4'
|
typescript: '>=4.8.4'
|
||||||
|
|
||||||
|
ts-debounce@4.0.0:
|
||||||
|
resolution: {integrity: sha512-+1iDGY6NmOGidq7i7xZGA4cm8DAa6fqdYcvO5Z6yBevH++Bdo9Qt/mN0TzHUgcCcKv1gmh9+W5dHqz8pMWbCbg==}
|
||||||
|
|
||||||
ts-interface-checker@0.1.13:
|
ts-interface-checker@0.1.13:
|
||||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||||
|
|
||||||
@@ -4707,6 +4750,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
typed-emitter@2.1.0:
|
||||||
|
resolution: {integrity: sha512-g/KzbYKbH5C2vPkaXGu8DJlHrGKHLsM25Zg9WuC9pMGfuvT+X25tZQWo5fK1BjBm8+UrVE9LDCvaY0CQk+fXDA==}
|
||||||
|
|
||||||
typescript-eslint@8.53.1:
|
typescript-eslint@8.53.1:
|
||||||
resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==}
|
resolution: {integrity: sha512-gB+EVQfP5RDElh9ittfXlhZJdjSU4jUSTyE2+ia8CYyNvet4ElfaLlAIqDvQV9JPknKx0jQH1racTYe/4LaLSg==}
|
||||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||||
@@ -4851,6 +4897,10 @@ packages:
|
|||||||
webidl-conversions@4.0.2:
|
webidl-conversions@4.0.2:
|
||||||
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
|
||||||
|
|
||||||
|
webrtc-adapter@9.0.4:
|
||||||
|
resolution: {integrity: sha512-5ZZY1+lGq8LEKuDlg9M2RPJHlH3R7OVwyHqMcUsLKCgd9Wvf+QrFTCItkXXYPmrJn8H6gRLXbSgxLLdexiqHxw==}
|
||||||
|
engines: {node: '>=6.0.0', npm: '>=3.10.0'}
|
||||||
|
|
||||||
whatwg-url@5.0.0:
|
whatwg-url@5.0.0:
|
||||||
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
|
||||||
|
|
||||||
@@ -5737,6 +5787,8 @@ snapshots:
|
|||||||
|
|
||||||
'@braintree/sanitize-url@7.1.1': {}
|
'@braintree/sanitize-url@7.1.1': {}
|
||||||
|
|
||||||
|
'@bufbuild/protobuf@1.10.1': {}
|
||||||
|
|
||||||
'@canvas/image-data@1.1.0': {}
|
'@canvas/image-data@1.1.0': {}
|
||||||
|
|
||||||
'@capacitor-community/safe-area@8.0.1(@capacitor/core@8.0.1)':
|
'@capacitor-community/safe-area@8.0.1(@capacitor/core@8.0.1)':
|
||||||
@@ -6302,6 +6354,12 @@ snapshots:
|
|||||||
'@jridgewell/resolve-uri': 3.1.2
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
'@jridgewell/sourcemap-codec': 1.5.5
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@livekit/mutex@1.1.1': {}
|
||||||
|
|
||||||
|
'@livekit/protocol@1.44.0':
|
||||||
|
dependencies:
|
||||||
|
'@bufbuild/protobuf': 1.10.1
|
||||||
|
|
||||||
'@noble/ciphers@0.5.3': {}
|
'@noble/ciphers@0.5.3': {}
|
||||||
|
|
||||||
'@noble/ciphers@1.3.0': {}
|
'@noble/ciphers@1.3.0': {}
|
||||||
@@ -6863,6 +6921,8 @@ snapshots:
|
|||||||
|
|
||||||
'@types/cookie@0.6.0': {}
|
'@types/cookie@0.6.0': {}
|
||||||
|
|
||||||
|
'@types/dom-mediacapture-record@1.0.22': {}
|
||||||
|
|
||||||
'@types/eslint@9.6.1':
|
'@types/eslint@9.6.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/estree': 1.0.8
|
'@types/estree': 1.0.8
|
||||||
@@ -8534,6 +8594,8 @@ snapshots:
|
|||||||
|
|
||||||
jiti@1.21.7: {}
|
jiti@1.21.7: {}
|
||||||
|
|
||||||
|
jose@6.1.3: {}
|
||||||
|
|
||||||
js-base64@3.7.8: {}
|
js-base64@3.7.8: {}
|
||||||
|
|
||||||
js-tokens@4.0.0: {}
|
js-tokens@4.0.0: {}
|
||||||
@@ -8609,6 +8671,20 @@ snapshots:
|
|||||||
|
|
||||||
linkifyjs@4.3.2: {}
|
linkifyjs@4.3.2: {}
|
||||||
|
|
||||||
|
livekit-client@2.17.2(@types/dom-mediacapture-record@1.0.22):
|
||||||
|
dependencies:
|
||||||
|
'@livekit/mutex': 1.1.1
|
||||||
|
'@livekit/protocol': 1.44.0
|
||||||
|
'@types/dom-mediacapture-record': 1.0.22
|
||||||
|
events: 3.3.0
|
||||||
|
jose: 6.1.3
|
||||||
|
loglevel: 1.9.2
|
||||||
|
sdp-transform: 2.15.0
|
||||||
|
ts-debounce: 4.0.0
|
||||||
|
tslib: 2.8.1
|
||||||
|
typed-emitter: 2.1.0
|
||||||
|
webrtc-adapter: 9.0.4
|
||||||
|
|
||||||
load-json-file@4.0.0:
|
load-json-file@4.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-fs: 4.2.11
|
graceful-fs: 4.2.11
|
||||||
@@ -8641,6 +8717,8 @@ snapshots:
|
|||||||
|
|
||||||
lodash@4.17.23: {}
|
lodash@4.17.23: {}
|
||||||
|
|
||||||
|
loglevel@1.9.2: {}
|
||||||
|
|
||||||
lru-cache@10.4.3: {}
|
lru-cache@10.4.3: {}
|
||||||
|
|
||||||
lru-cache@11.2.4: {}
|
lru-cache@11.2.4: {}
|
||||||
@@ -9440,6 +9518,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
queue-microtask: 1.2.3
|
queue-microtask: 1.2.3
|
||||||
|
|
||||||
|
rxjs@7.8.2:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
sade@1.8.1:
|
sade@1.8.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
mri: 1.2.0
|
mri: 1.2.0
|
||||||
@@ -9471,6 +9554,10 @@ snapshots:
|
|||||||
|
|
||||||
sax@1.4.4: {}
|
sax@1.4.4: {}
|
||||||
|
|
||||||
|
sdp-transform@2.15.0: {}
|
||||||
|
|
||||||
|
sdp@3.2.1: {}
|
||||||
|
|
||||||
semver@5.7.2: {}
|
semver@5.7.2: {}
|
||||||
|
|
||||||
semver@6.3.1: {}
|
semver@6.3.1: {}
|
||||||
@@ -9920,6 +10007,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
ts-debounce@4.0.0: {}
|
||||||
|
|
||||||
ts-interface-checker@0.1.13: {}
|
ts-interface-checker@0.1.13: {}
|
||||||
|
|
||||||
ts-node@10.9.2(@types/node@25.0.10)(typescript@5.9.3):
|
ts-node@10.9.2(@types/node@25.0.10)(typescript@5.9.3):
|
||||||
@@ -9995,6 +10084,10 @@ snapshots:
|
|||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
reflect.getprototypeof: 1.0.10
|
reflect.getprototypeof: 1.0.10
|
||||||
|
|
||||||
|
typed-emitter@2.1.0:
|
||||||
|
optionalDependencies:
|
||||||
|
rxjs: 7.8.2
|
||||||
|
|
||||||
typescript-eslint@8.53.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
typescript-eslint@8.53.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
'@typescript-eslint/eslint-plugin': 8.53.1(@typescript-eslint/parser@8.53.1(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||||
@@ -10103,6 +10196,10 @@ snapshots:
|
|||||||
|
|
||||||
webidl-conversions@4.0.2: {}
|
webidl-conversions@4.0.2: {}
|
||||||
|
|
||||||
|
webrtc-adapter@9.0.4:
|
||||||
|
dependencies:
|
||||||
|
sdp: 3.2.1
|
||||||
|
|
||||||
whatwg-url@5.0.0:
|
whatwg-url@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
tr46: 0.0.3
|
tr46: 0.0.3
|
||||||
|
|||||||
@@ -38,6 +38,8 @@
|
|||||||
import SpaceReports from "@app/components/SpaceReports.svelte"
|
import SpaceReports from "@app/components/SpaceReports.svelte"
|
||||||
import RoomCreate from "@app/components/RoomCreate.svelte"
|
import RoomCreate from "@app/components/RoomCreate.svelte"
|
||||||
import SpaceMenuRoomItem from "@app/components/SpaceMenuRoomItem.svelte"
|
import SpaceMenuRoomItem from "@app/components/SpaceMenuRoomItem.svelte"
|
||||||
|
import VoiceRoomItem from "@app/components/VoiceRoomItem.svelte"
|
||||||
|
import VoiceWidget from "@app/components/VoiceWidget.svelte"
|
||||||
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
|
import SocketStatusIndicator from "@app/components/SocketStatusIndicator.svelte"
|
||||||
import {
|
import {
|
||||||
ENABLE_ZAPS,
|
ENABLE_ZAPS,
|
||||||
@@ -257,6 +259,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each $userRooms as h, i (h)}
|
{#each $userRooms as h, i (h)}
|
||||||
<SpaceMenuRoomItem notify {replaceState} {url} {h} />
|
<SpaceMenuRoomItem notify {replaceState} {url} {h} />
|
||||||
|
<VoiceRoomItem {url} {h} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if $otherRooms.length > 0}
|
{#if $otherRooms.length > 0}
|
||||||
<div class="h-2"></div>
|
<div class="h-2"></div>
|
||||||
@@ -276,6 +279,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#each $roomSearch.searchValues(term) as h, i (h)}
|
{#each $roomSearch.searchValues(term) as h, i (h)}
|
||||||
<SpaceMenuRoomItem {replaceState} {url} {h} />
|
<SpaceMenuRoomItem {replaceState} {url} {h} />
|
||||||
|
<VoiceRoomItem {url} {h} />
|
||||||
{/each}
|
{/each}
|
||||||
{#if $canCreateRoom}
|
{#if $canCreateRoom}
|
||||||
<SecondaryNavItem {replaceState} onclick={addRoom}>
|
<SecondaryNavItem {replaceState} onclick={addRoom}>
|
||||||
@@ -286,7 +290,8 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</SecondaryNavSection>
|
</SecondaryNavSection>
|
||||||
<div class="flex flex-col gap-2 pb-2 p-4 pt-0">
|
<div class="flex flex-col gap-2 p-4 pb-2 pt-0">
|
||||||
|
<VoiceWidget />
|
||||||
<Button class="btn btn-neutral btn-sm h-10" onclick={showDetail}>
|
<Button class="btn btn-neutral btn-sm h-10" onclick={showDetail}>
|
||||||
<SocketStatusIndicator {url} />
|
<SocketStatusIndicator {url} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -0,0 +1,46 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {loadProfile, displayProfileByPubkey} from "@welshman/app"
|
||||||
|
import Volume from "@assets/icons/volume.svg?dataurl"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import SecondaryNavItem from "@lib/components/SecondaryNavItem.svelte"
|
||||||
|
import ProfileCircle from "@app/components/ProfileCircle.svelte"
|
||||||
|
import RoomName from "@app/components/RoomName.svelte"
|
||||||
|
import {deriveVoiceParticipants, joinVoiceRoom, currentVoiceSession} from "@app/voice"
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
url: string
|
||||||
|
h: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const {url, h}: Props = $props()
|
||||||
|
|
||||||
|
const participants = deriveVoiceParticipants(url, h)
|
||||||
|
const isActive = $derived($currentVoiceSession?.url === url && $currentVoiceSession?.h === h)
|
||||||
|
|
||||||
|
const handleClick = () => joinVoiceRoom(url, h)
|
||||||
|
|
||||||
|
$effect(() => {
|
||||||
|
for (const pk of $participants) {
|
||||||
|
loadProfile(pk)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<SecondaryNavItem onclick={handleClick} class={isActive ? "!bg-base-100 !text-base-content" : ""}>
|
||||||
|
<Icon icon={Volume} size={4} class="opacity-70" />
|
||||||
|
<RoomName {url} {h} />
|
||||||
|
</SecondaryNavItem>
|
||||||
|
{#if $participants.length > 0}
|
||||||
|
<div class="flex flex-col gap-1 pb-1 pl-10">
|
||||||
|
{#each $participants as pk (pk)}
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<ProfileCircle pubkey={pk} size={5} class="h-5 w-5" />
|
||||||
|
<span class="ellipsize text-xs opacity-70">
|
||||||
|
{displayProfileByPubkey(pk)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import {displayRelayUrl} from "@welshman/util"
|
||||||
|
import Microphone from "@assets/icons/microphone.svg?dataurl"
|
||||||
|
import VolumeCross from "@assets/icons/volume-cross.svg?dataurl"
|
||||||
|
import PhoneRounded from "@assets/icons/phone-rounded.svg?dataurl"
|
||||||
|
import Icon from "@lib/components/Icon.svelte"
|
||||||
|
import Button from "@lib/components/Button.svelte"
|
||||||
|
import {displayRoom} from "@app/core/state"
|
||||||
|
import {currentVoiceSession, leaveVoiceRoom, toggleMute} from "@app/voice"
|
||||||
|
|
||||||
|
const roomName = $derived(
|
||||||
|
$currentVoiceSession ? displayRoom($currentVoiceSession.url, $currentVoiceSession.h) : "",
|
||||||
|
)
|
||||||
|
const spaceName = $derived($currentVoiceSession ? displayRelayUrl($currentVoiceSession.url) : "")
|
||||||
|
|
||||||
|
const handleDisconnect = () => leaveVoiceRoom()
|
||||||
|
const handleToggleMute = () => toggleMute()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if $currentVoiceSession}
|
||||||
|
<div class="flex flex-col gap-2 rounded-box bg-base-100 p-3">
|
||||||
|
<div class="flex flex-col gap-0.5">
|
||||||
|
<span class="text-sm font-semibold text-success">Voice Connected</span>
|
||||||
|
<span class="ellipsize text-xs opacity-70">
|
||||||
|
{roomName} / {spaceName}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-1">
|
||||||
|
<Button
|
||||||
|
class="btn btn-sm btn-square {$currentVoiceSession.muted ? 'btn-error' : 'btn-ghost'}"
|
||||||
|
onclick={handleToggleMute}>
|
||||||
|
<Icon icon={$currentVoiceSession.muted ? VolumeCross : Microphone} size={4} />
|
||||||
|
</Button>
|
||||||
|
<Button class="btn btn-sm btn-square btn-error" onclick={handleDisconnect}>
|
||||||
|
<Icon icon={PhoneRounded} size={4} />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
@@ -55,6 +55,7 @@ import {
|
|||||||
loadFeedsForPubkey,
|
loadFeedsForPubkey,
|
||||||
} from "@app/core/state"
|
} from "@app/core/state"
|
||||||
import {hasBlossomSupport} from "@app/core/commands"
|
import {hasBlossomSupport} from "@app/core/commands"
|
||||||
|
import {LIVE_ACTIVITY, ROOM_PRESENCE} from "@app/voice"
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
|
|
||||||
@@ -316,6 +317,12 @@ const syncSpace = (url: string, rooms: string[]) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pullAndListen({
|
||||||
|
url,
|
||||||
|
signal: controller.signal,
|
||||||
|
filters: [{kinds: [LIVE_ACTIVITY, ROOM_PRESENCE]}],
|
||||||
|
})
|
||||||
|
|
||||||
return () => controller.abort()
|
return () => controller.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
import {derived, get, writable} from "svelte/store"
|
||||||
|
import * as nip19 from "nostr-tools/nip19"
|
||||||
|
import {SignJWT} from "jose"
|
||||||
|
import {Room, RoomEvent} from "livekit-client"
|
||||||
|
import {now} from "@welshman/lib"
|
||||||
|
import {makeEvent, normalizeRelayUrl, getTag} from "@welshman/util"
|
||||||
|
import {pubkey, publishThunk} from "@welshman/app"
|
||||||
|
import {deriveEventsForUrl, displayRoom} from "@app/core/state"
|
||||||
|
|
||||||
|
export const LIVE_ACTIVITY = 30311
|
||||||
|
export const ROOM_PRESENCE = 10312
|
||||||
|
|
||||||
|
const LIVEKIT_URL = import.meta.env.VITE_LIVEKIT_URL || ""
|
||||||
|
const LIVEKIT_API_KEY = import.meta.env.VITE_LIVEKIT_API_KEY || ""
|
||||||
|
const LIVEKIT_API_SECRET = import.meta.env.VITE_LIVEKIT_API_SECRET || ""
|
||||||
|
const PRESENCE_INTERVAL_MS = 60_000
|
||||||
|
const PRESENCE_EXPIRY_S = 300
|
||||||
|
|
||||||
|
export type VoiceSession = {
|
||||||
|
url: string
|
||||||
|
h: string
|
||||||
|
room: Room
|
||||||
|
muted: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const currentVoiceSession = writable<VoiceSession | undefined>(undefined)
|
||||||
|
|
||||||
|
const makeLivekitRoomName = (url: string, h: string) =>
|
||||||
|
`${normalizeRelayUrl(url)}:${h}`.replace(/[^a-zA-Z0-9_-]/g, "_")
|
||||||
|
|
||||||
|
const generateToken = async (roomName: string, identity: string) => {
|
||||||
|
const secret = new TextEncoder().encode(LIVEKIT_API_SECRET)
|
||||||
|
const jwt = await new SignJWT({
|
||||||
|
video: {roomJoin: true, room: roomName, canPublish: true, canSubscribe: true},
|
||||||
|
sub: identity,
|
||||||
|
iss: LIVEKIT_API_KEY,
|
||||||
|
jti: identity,
|
||||||
|
})
|
||||||
|
.setProtectedHeader({alg: "HS256"})
|
||||||
|
.setIssuedAt()
|
||||||
|
.setExpirationTime("6h")
|
||||||
|
.sign(secret)
|
||||||
|
|
||||||
|
return jwt
|
||||||
|
}
|
||||||
|
|
||||||
|
export const deriveVoiceParticipants = (url: string, h: string) =>
|
||||||
|
derived(deriveEventsForUrl(url, [{kinds: [ROOM_PRESENCE]}]), $events => {
|
||||||
|
const cutoff = now() - PRESENCE_EXPIRY_S
|
||||||
|
const pubkeys: string[] = []
|
||||||
|
|
||||||
|
for (const event of $events) {
|
||||||
|
if (event.created_at < cutoff) continue
|
||||||
|
|
||||||
|
const aTag = getTag("a", event.tags)
|
||||||
|
if (!aTag) continue
|
||||||
|
|
||||||
|
const [, , dTag] = aTag[1].split(":")
|
||||||
|
if (dTag === h) {
|
||||||
|
pubkeys.push(event.pubkey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubkeys
|
||||||
|
})
|
||||||
|
|
||||||
|
const publishLiveActivity = (url: string, h: string, status: "live" | "ended") => {
|
||||||
|
const pk = get(pubkey)!
|
||||||
|
const title = displayRoom(url, h)
|
||||||
|
const event = makeEvent(LIVE_ACTIVITY, {
|
||||||
|
tags: [
|
||||||
|
["d", h],
|
||||||
|
["h", h],
|
||||||
|
["title", title],
|
||||||
|
["service", LIVEKIT_URL],
|
||||||
|
["status", status],
|
||||||
|
["starts", String(now())],
|
||||||
|
["p", pk, "", "Host"],
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
return publishThunk({event, relays: [url]})
|
||||||
|
}
|
||||||
|
|
||||||
|
const publishPresence = (url: string, h: string) => {
|
||||||
|
const pk = get(pubkey)!
|
||||||
|
const aTag = `${LIVE_ACTIVITY}:${pk}:${h}`
|
||||||
|
const event = makeEvent(ROOM_PRESENCE, {
|
||||||
|
tags: [["a", aTag, url, "root"]],
|
||||||
|
})
|
||||||
|
|
||||||
|
return publishThunk({event, relays: [url]})
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletePresence = (url: string) => {
|
||||||
|
const event = makeEvent(ROOM_PRESENCE, {tags: []})
|
||||||
|
|
||||||
|
return publishThunk({event, relays: [url]})
|
||||||
|
}
|
||||||
|
|
||||||
|
let presenceInterval: ReturnType<typeof setInterval> | undefined
|
||||||
|
|
||||||
|
const startPresenceHeartbeat = (url: string, h: string) => {
|
||||||
|
stopPresenceHeartbeat()
|
||||||
|
publishPresence(url, h)
|
||||||
|
presenceInterval = setInterval(() => publishPresence(url, h), PRESENCE_INTERVAL_MS)
|
||||||
|
}
|
||||||
|
|
||||||
|
const stopPresenceHeartbeat = () => {
|
||||||
|
if (presenceInterval) {
|
||||||
|
clearInterval(presenceInterval)
|
||||||
|
presenceInterval = undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const joinVoiceRoom = async (url: string, h: string) => {
|
||||||
|
const session = get(currentVoiceSession)
|
||||||
|
|
||||||
|
if (session) {
|
||||||
|
if (session.url === url && session.h === h) return
|
||||||
|
await leaveVoiceRoom()
|
||||||
|
}
|
||||||
|
|
||||||
|
const pk = get(pubkey)!
|
||||||
|
const identity = nip19.npubEncode(pk)
|
||||||
|
const roomName = makeLivekitRoomName(url, h)
|
||||||
|
const token = await generateToken(roomName, identity)
|
||||||
|
|
||||||
|
const room = new Room({
|
||||||
|
adaptiveStream: true,
|
||||||
|
dynacast: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
room.on(RoomEvent.Disconnected, () => {
|
||||||
|
currentVoiceSession.set(undefined)
|
||||||
|
stopPresenceHeartbeat()
|
||||||
|
})
|
||||||
|
|
||||||
|
await room.connect(LIVEKIT_URL, token)
|
||||||
|
await room.localParticipant.setMicrophoneEnabled(true)
|
||||||
|
|
||||||
|
currentVoiceSession.set({url, h, room, muted: false})
|
||||||
|
|
||||||
|
publishLiveActivity(url, h, "live")
|
||||||
|
startPresenceHeartbeat(url, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const leaveVoiceRoom = async () => {
|
||||||
|
const session = get(currentVoiceSession)
|
||||||
|
if (!session) return
|
||||||
|
|
||||||
|
stopPresenceHeartbeat()
|
||||||
|
session.room.disconnect()
|
||||||
|
deletePresence(session.url)
|
||||||
|
publishLiveActivity(session.url, session.h, "ended")
|
||||||
|
currentVoiceSession.set(undefined)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleMute = () => {
|
||||||
|
const session = get(currentVoiceSession)
|
||||||
|
if (!session) return
|
||||||
|
|
||||||
|
const muted = !session.muted
|
||||||
|
session.room.localParticipant.setMicrophoneEnabled(!muted)
|
||||||
|
currentVoiceSession.set({...session, muted})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const toggleDeafen = () => {
|
||||||
|
const session = get(currentVoiceSession)
|
||||||
|
if (!session) return
|
||||||
|
|
||||||
|
for (const participant of session.room.remoteParticipants.values()) {
|
||||||
|
for (const pub of participant.audioTrackPublications.values()) {
|
||||||
|
pub.setEnabled(!pub.isEnabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user