@dxos/plugin-thread 0.8.2-staging.7ac8446 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/browser/ChannelContainer-LGYSQMC7.mjs +15 -0
- package/dist/lib/browser/ChatContainer-JWRI73OG.mjs +14 -0
- package/dist/lib/browser/app-graph-builder-K3CFBN7V.mjs +171 -0
- package/dist/lib/browser/app-graph-builder-K3CFBN7V.mjs.map +7 -0
- package/dist/lib/browser/call-manager-APQAZWHT.mjs +23 -0
- package/dist/lib/browser/call-manager-APQAZWHT.mjs.map +7 -0
- package/dist/lib/browser/chunk-4H4A264L.mjs +1708 -0
- package/dist/lib/browser/chunk-4H4A264L.mjs.map +7 -0
- package/dist/lib/browser/{chunk-QDJ3Y6WF.mjs → chunk-5P5BH5GE.mjs} +57 -35
- package/dist/lib/browser/chunk-5P5BH5GE.mjs.map +7 -0
- package/dist/lib/browser/chunk-5PGLGDIP.mjs +983 -0
- package/dist/lib/browser/chunk-5PGLGDIP.mjs.map +7 -0
- package/dist/lib/browser/chunk-CF2MXRCH.mjs +21 -0
- package/dist/lib/browser/chunk-CF2MXRCH.mjs.map +7 -0
- package/dist/lib/browser/chunk-G3TS64FP.mjs +351 -0
- package/dist/lib/browser/chunk-G3TS64FP.mjs.map +7 -0
- package/dist/lib/browser/{chunk-WBJLUUOK.mjs → chunk-IIUTBDVB.mjs} +4 -4
- package/dist/lib/browser/chunk-IIUTBDVB.mjs.map +7 -0
- package/dist/lib/browser/chunk-JKQUJOTX.mjs +31 -0
- package/dist/lib/browser/chunk-JKQUJOTX.mjs.map +7 -0
- package/dist/lib/browser/chunk-KMVGJG23.mjs +545 -0
- package/dist/lib/browser/chunk-KMVGJG23.mjs.map +7 -0
- package/dist/lib/browser/{chunk-EP6MRLY7.mjs → chunk-R5MDLUPC.mjs} +5 -3
- package/dist/lib/browser/chunk-R5MDLUPC.mjs.map +7 -0
- package/dist/lib/browser/chunk-WDOQXTI2.mjs +126 -0
- package/dist/lib/browser/chunk-WDOQXTI2.mjs.map +7 -0
- package/dist/lib/browser/index.mjs +107 -41
- package/dist/lib/browser/index.mjs.map +4 -4
- package/dist/lib/browser/{intent-resolver-QAY4FLLM.mjs → intent-resolver-YUYZCTLF.mjs} +102 -74
- package/dist/lib/browser/intent-resolver-YUYZCTLF.mjs.map +7 -0
- package/dist/lib/browser/markdown-4EC3OCF2.mjs +169 -0
- package/dist/lib/browser/markdown-4EC3OCF2.mjs.map +7 -0
- package/dist/lib/browser/meta.json +1 -1
- package/dist/lib/browser/react-root-EECCWHCW.mjs +27 -0
- package/dist/lib/browser/react-root-EECCWHCW.mjs.map +7 -0
- package/dist/lib/browser/react-surface-5QLZAFDL.mjs +108 -0
- package/dist/lib/browser/react-surface-5QLZAFDL.mjs.map +7 -0
- package/dist/lib/browser/{settings-4FEC7EAX.mjs → settings-KVBQQSO5.mjs} +5 -5
- package/dist/lib/browser/settings-KVBQQSO5.mjs.map +7 -0
- package/dist/lib/browser/{state-PNMSLKAH.mjs → state-RKL5MSDP.mjs} +7 -7
- package/dist/lib/browser/state-RKL5MSDP.mjs.map +7 -0
- package/dist/lib/browser/types/index.mjs +16 -0
- package/dist/lib/browser/types/index.mjs.map +7 -0
- package/dist/lib/node/ChannelContainer-5EKNEH56.cjs +36 -0
- package/dist/lib/node/ChannelContainer-5EKNEH56.cjs.map +7 -0
- package/dist/lib/node/ChatContainer-EE3FPSYO.cjs +35 -0
- package/dist/lib/node/ChatContainer-EE3FPSYO.cjs.map +7 -0
- package/dist/lib/node/app-graph-builder-IRE3GYKE.cjs +179 -0
- package/dist/lib/node/app-graph-builder-IRE3GYKE.cjs.map +7 -0
- package/dist/lib/node/call-manager-HV2XJW5O.cjs +37 -0
- package/dist/lib/node/call-manager-HV2XJW5O.cjs.map +7 -0
- package/dist/lib/node/chunk-44I33JDT.cjs +1731 -0
- package/dist/lib/node/chunk-44I33JDT.cjs.map +7 -0
- package/dist/lib/node/chunk-4B3PPXA6.cjs +371 -0
- package/dist/lib/node/chunk-4B3PPXA6.cjs.map +7 -0
- package/dist/lib/node/{chunk-NQ5DTPOY.cjs → chunk-4YSNVPWU.cjs} +10 -8
- package/dist/lib/node/chunk-4YSNVPWU.cjs.map +7 -0
- package/dist/lib/node/chunk-6B3M6XRE.cjs +984 -0
- package/dist/lib/node/chunk-6B3M6XRE.cjs.map +7 -0
- package/dist/lib/node/{chunk-AKINTFBH.cjs → chunk-FUXJAVFZ.cjs} +50 -29
- package/dist/lib/node/chunk-FUXJAVFZ.cjs.map +7 -0
- package/dist/lib/node/{chunk-ONLWOUYZ.cjs → chunk-HLMCEE5K.cjs} +7 -7
- package/dist/lib/node/{chunk-ONLWOUYZ.cjs.map → chunk-HLMCEE5K.cjs.map} +2 -2
- package/dist/lib/node/chunk-JLHENFGK.cjs +549 -0
- package/dist/lib/node/chunk-JLHENFGK.cjs.map +7 -0
- package/dist/lib/node/chunk-LR5NENBE.cjs +54 -0
- package/dist/lib/node/chunk-LR5NENBE.cjs.map +7 -0
- package/dist/lib/node/chunk-TKHJVC2C.cjs +147 -0
- package/dist/lib/node/chunk-TKHJVC2C.cjs.map +7 -0
- package/dist/lib/node/{chunk-S2A6BXEA.cjs → chunk-U7NOIXN6.cjs} +16 -10
- package/dist/lib/node/chunk-U7NOIXN6.cjs.map +7 -0
- package/dist/lib/node/index.cjs +142 -80
- package/dist/lib/node/index.cjs.map +4 -4
- package/dist/lib/node/{intent-resolver-S6FE5OR7.cjs → intent-resolver-MZ3HADNY.cjs} +108 -82
- package/dist/lib/node/intent-resolver-MZ3HADNY.cjs.map +7 -0
- package/dist/lib/node/markdown-NJDPXIKD.cjs +178 -0
- package/dist/lib/node/markdown-NJDPXIKD.cjs.map +7 -0
- package/dist/lib/node/meta.json +1 -1
- package/dist/lib/node/react-root-IAYQ2DBO.cjs +51 -0
- package/dist/lib/node/react-root-IAYQ2DBO.cjs.map +7 -0
- package/dist/lib/node/react-surface-AVC5VVIS.cjs +122 -0
- package/dist/lib/node/react-surface-AVC5VVIS.cjs.map +7 -0
- package/dist/lib/node/{settings-IXCPE55J.cjs → settings-H45P5YO2.cjs} +9 -9
- package/dist/lib/node/settings-H45P5YO2.cjs.map +7 -0
- package/dist/lib/node/{state-AT73KPGZ.cjs → state-IY5XX3H2.cjs} +10 -10
- package/dist/lib/node/state-IY5XX3H2.cjs.map +7 -0
- package/dist/lib/node/{types.cjs → types/index.cjs} +12 -6
- package/dist/lib/node/types/index.cjs.map +7 -0
- package/dist/lib/node-esm/ChannelContainer-KFS2RWWW.mjs +16 -0
- package/dist/lib/node-esm/ChannelContainer-KFS2RWWW.mjs.map +7 -0
- package/dist/lib/node-esm/ChatContainer-UJYPQMLV.mjs +15 -0
- package/dist/lib/node-esm/ChatContainer-UJYPQMLV.mjs.map +7 -0
- package/dist/lib/node-esm/app-graph-builder-I5VMPLE4.mjs +172 -0
- package/dist/lib/node-esm/app-graph-builder-I5VMPLE4.mjs.map +7 -0
- package/dist/lib/node-esm/call-manager-UUYXLF4T.mjs +24 -0
- package/dist/lib/node-esm/call-manager-UUYXLF4T.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-42F2DTMM.mjs → chunk-ESQC6SYE.mjs} +4 -4
- package/dist/lib/node-esm/chunk-ESQC6SYE.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-J323VQ4P.mjs +127 -0
- package/dist/lib/node-esm/chunk-J323VQ4P.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-WLY2HMDB.mjs → chunk-K4KAUOKT.mjs} +5 -3
- package/dist/lib/node-esm/chunk-K4KAUOKT.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-MNJ3V4XM.mjs +546 -0
- package/dist/lib/node-esm/chunk-MNJ3V4XM.mjs.map +7 -0
- package/dist/lib/node-esm/{chunk-J56FORYF.mjs → chunk-O3V45YJP.mjs} +57 -35
- package/dist/lib/node-esm/chunk-O3V45YJP.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-ODUB4GAB.mjs +23 -0
- package/dist/lib/node-esm/chunk-ODUB4GAB.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-PGRP3O4O.mjs +33 -0
- package/dist/lib/node-esm/chunk-PGRP3O4O.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-POKXRTII.mjs +352 -0
- package/dist/lib/node-esm/chunk-POKXRTII.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-WXNPD542.mjs +1710 -0
- package/dist/lib/node-esm/chunk-WXNPD542.mjs.map +7 -0
- package/dist/lib/node-esm/chunk-X6AUQDTE.mjs +984 -0
- package/dist/lib/node-esm/chunk-X6AUQDTE.mjs.map +7 -0
- package/dist/lib/node-esm/index.mjs +107 -41
- package/dist/lib/node-esm/index.mjs.map +4 -4
- package/dist/lib/node-esm/{intent-resolver-OURFRSSF.mjs → intent-resolver-CU22OPAH.mjs} +102 -74
- package/dist/lib/node-esm/intent-resolver-CU22OPAH.mjs.map +7 -0
- package/dist/lib/node-esm/markdown-HRGGO7IA.mjs +170 -0
- package/dist/lib/node-esm/markdown-HRGGO7IA.mjs.map +7 -0
- package/dist/lib/node-esm/meta.json +1 -1
- package/dist/lib/node-esm/react-root-NQMUEI6Z.mjs +28 -0
- package/dist/lib/node-esm/react-root-NQMUEI6Z.mjs.map +7 -0
- package/dist/lib/node-esm/react-surface-Z4X5VLMY.mjs +109 -0
- package/dist/lib/node-esm/react-surface-Z4X5VLMY.mjs.map +7 -0
- package/dist/lib/node-esm/{settings-ZUJQ4LWI.mjs → settings-IJGUOU3F.mjs} +5 -5
- package/dist/lib/node-esm/settings-IJGUOU3F.mjs.map +7 -0
- package/dist/lib/node-esm/{state-SSDX5O67.mjs → state-6AUTNROZ.mjs} +7 -7
- package/dist/lib/node-esm/state-6AUTNROZ.mjs.map +7 -0
- package/dist/lib/node-esm/types/index.mjs +17 -0
- package/dist/lib/node-esm/types/index.mjs.map +7 -0
- package/dist/types/src/ThreadPlugin.d.ts.map +1 -1
- package/dist/types/src/calls/call-manager.d.ts +77 -0
- package/dist/types/src/calls/call-manager.d.ts.map +1 -0
- package/dist/types/src/calls/call-swarm-synchronizer.d.ts +66 -0
- package/dist/types/src/calls/call-swarm-synchronizer.d.ts.map +1 -0
- package/dist/types/src/calls/index.d.ts +7 -0
- package/dist/types/src/calls/index.d.ts.map +1 -0
- package/dist/types/src/calls/media-manager.d.ts +60 -0
- package/dist/types/src/calls/media-manager.d.ts.map +1 -0
- package/dist/types/src/calls/speaking-monitor.d.ts +24 -0
- package/dist/types/src/calls/speaking-monitor.d.ts.map +1 -0
- package/dist/types/src/calls/types.d.ts +45 -0
- package/dist/types/src/calls/types.d.ts.map +1 -0
- package/dist/types/src/calls/util/audio.d.ts +6 -0
- package/dist/types/src/calls/util/audio.d.ts.map +1 -0
- package/dist/types/src/calls/util/calls-service.d.ts +72 -0
- package/dist/types/src/calls/util/calls-service.d.ts.map +1 -0
- package/dist/types/src/calls/util/history-cache.d.ts +11 -0
- package/dist/types/src/calls/util/history-cache.d.ts.map +1 -0
- package/dist/types/src/calls/util/index.d.ts +5 -0
- package/dist/types/src/calls/util/index.d.ts.map +1 -0
- package/dist/types/src/calls/util/media.d.ts +6 -0
- package/dist/types/src/calls/util/media.d.ts.map +1 -0
- package/dist/types/src/calls/util/stub-media-streams.d.ts +18 -0
- package/dist/types/src/calls/util/stub-media-streams.d.ts.map +1 -0
- package/dist/types/src/calls/util/task-scheduling.d.ts +17 -0
- package/dist/types/src/calls/util/task-scheduling.d.ts.map +1 -0
- package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
- package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
- package/dist/types/src/capabilities/call-manager.d.ts +5 -0
- package/dist/types/src/capabilities/call-manager.d.ts.map +1 -0
- package/dist/types/src/capabilities/capabilities.d.ts +13 -1
- package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
- package/dist/types/src/capabilities/index.d.ts +8 -179
- package/dist/types/src/capabilities/index.d.ts.map +1 -1
- package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
- package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
- package/dist/types/src/capabilities/markdown.d.ts +2 -2
- package/dist/types/src/capabilities/markdown.d.ts.map +1 -1
- package/dist/types/src/capabilities/react-root.d.ts +7 -0
- package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
- package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
- package/dist/types/src/components/Call/Call.d.ts +17 -0
- package/dist/types/src/components/Call/Call.d.ts.map +1 -0
- package/dist/types/src/components/Call/Toolbar.d.ts +16 -0
- package/dist/types/src/components/Call/Toolbar.d.ts.map +1 -0
- package/dist/types/src/components/Call/index.d.ts +3 -0
- package/dist/types/src/components/Call/index.d.ts.map +1 -0
- package/dist/types/src/components/CallDebugPanel.d.ts +8 -0
- package/dist/types/src/components/CallDebugPanel.d.ts.map +1 -0
- package/dist/types/src/components/CallSidebar.d.ts +4 -0
- package/dist/types/src/components/CallSidebar.d.ts.map +1 -0
- package/dist/types/src/components/ChannelContainer.d.ts +14 -0
- package/dist/types/src/components/ChannelContainer.d.ts.map +1 -0
- package/dist/types/src/components/ChannelContainer.stories.d.ts +10 -0
- package/dist/types/src/components/ChannelContainer.stories.d.ts.map +1 -0
- package/dist/types/src/components/Chat.stories.d.ts.map +1 -1
- package/dist/types/src/components/ChatContainer.d.ts +9 -7
- package/dist/types/src/components/ChatContainer.d.ts.map +1 -1
- package/dist/types/src/components/CommentContainer.d.ts +11 -2
- package/dist/types/src/components/CommentContainer.d.ts.map +1 -1
- package/dist/types/src/components/Comments.stories.d.ts +1 -1
- package/dist/types/src/components/Comments.stories.d.ts.map +1 -1
- package/dist/types/src/components/CommentsContainer.d.ts +5 -9
- package/dist/types/src/components/CommentsContainer.d.ts.map +1 -1
- package/dist/types/src/components/Lobby/Lobby.d.ts +15 -0
- package/dist/types/src/components/Lobby/Lobby.d.ts.map +1 -0
- package/dist/types/src/components/Lobby/index.d.ts +2 -0
- package/dist/types/src/components/Lobby/index.d.ts.map +1 -0
- package/dist/types/src/components/Media/AudioStream.d.ts +6 -0
- package/dist/types/src/components/Media/AudioStream.d.ts.map +1 -0
- package/dist/types/src/components/Media/VideoObject.d.ts +15 -0
- package/dist/types/src/components/Media/VideoObject.d.ts.map +1 -0
- package/dist/types/src/components/Media/VideoObject.stories.d.ts +8 -0
- package/dist/types/src/components/Media/VideoObject.stories.d.ts.map +1 -0
- package/dist/types/src/components/Media/index.d.ts +3 -0
- package/dist/types/src/components/Media/index.d.ts.map +1 -0
- package/dist/types/src/components/MessageContainer.d.ts +5 -4
- package/dist/types/src/components/MessageContainer.d.ts.map +1 -1
- package/dist/types/src/components/Participant/Participant.d.ts +6 -0
- package/dist/types/src/components/Participant/Participant.d.ts.map +1 -0
- package/dist/types/src/components/Participant/ParticipantGrid.d.ts +11 -0
- package/dist/types/src/components/Participant/ParticipantGrid.d.ts.map +1 -0
- package/dist/types/src/components/Participant/index.d.ts +3 -0
- package/dist/types/src/components/Participant/index.d.ts.map +1 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.d.ts +12 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.d.ts.map +1 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts +10 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveContainer.stories.d.ts.map +1 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.d.ts +32 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.d.ts.map +1 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts +23 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveGrid.stories.d.ts.map +1 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts +21 -0
- package/dist/types/src/components/ResponsiveGrid/ResponsiveGridItem.d.ts.map +1 -0
- package/dist/types/src/components/ResponsiveGrid/index.d.ts +4 -0
- package/dist/types/src/components/ResponsiveGrid/index.d.ts.map +1 -0
- package/dist/types/src/components/ThreadComplementary.d.ts.map +1 -1
- package/dist/types/src/components/ThreadSettings.d.ts.map +1 -1
- package/dist/types/src/components/index.d.ts +5 -1
- package/dist/types/src/components/index.d.ts.map +1 -1
- package/dist/types/src/components/testing.d.ts +101 -54
- package/dist/types/src/components/testing.d.ts.map +1 -1
- package/dist/types/src/extensions/threads.d.ts.map +1 -1
- package/dist/types/src/hooks/analytics.d.ts +3 -3
- package/dist/types/src/hooks/analytics.d.ts.map +1 -1
- package/dist/types/src/hooks/index.d.ts +2 -1
- package/dist/types/src/hooks/index.d.ts.map +1 -1
- package/dist/types/src/hooks/useDebugMode.d.ts +2 -0
- package/dist/types/src/hooks/useDebugMode.d.ts.map +1 -0
- package/dist/types/src/hooks/useStatus.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/meta.d.ts.map +1 -1
- package/dist/types/src/peers.stories.d.ts +11 -0
- package/dist/types/src/peers.stories.d.ts.map +1 -0
- package/dist/types/src/testing/index.d.ts +2 -0
- package/dist/types/src/testing/index.d.ts.map +1 -0
- package/dist/types/src/testing/stories.d.ts +2 -0
- package/dist/types/src/testing/stories.d.ts.map +1 -0
- package/dist/types/src/translations.d.ts +35 -56
- package/dist/types/src/translations.d.ts.map +1 -1
- package/dist/types/src/types/index.d.ts +3 -0
- package/dist/types/src/types/index.d.ts.map +1 -0
- package/dist/types/src/types/schema.d.ts +317 -0
- package/dist/types/src/types/schema.d.ts.map +1 -0
- package/dist/types/src/types/types.d.ts +1015 -0
- package/dist/types/src/types/types.d.ts.map +1 -0
- package/dist/types/src/util.d.ts +2 -0
- package/dist/types/src/util.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +60 -40
- package/src/ThreadPlugin.tsx +33 -14
- package/src/calls/call-manager.ts +242 -0
- package/src/calls/call-swarm-synchronizer.ts +350 -0
- package/src/calls/index.ts +10 -0
- package/src/calls/media-manager.ts +342 -0
- package/src/calls/speaking-monitor.ts +85 -0
- package/src/calls/types.ts +70 -0
- package/src/calls/util/audio.ts +58 -0
- package/src/calls/util/calls-service.ts +527 -0
- package/src/calls/util/history-cache.ts +27 -0
- package/src/calls/util/index.ts +8 -0
- package/src/calls/util/media.ts +52 -0
- package/src/calls/util/stub-media-streams.ts +73 -0
- package/src/calls/util/task-scheduling.ts +92 -0
- package/src/capabilities/app-graph-builder.ts +171 -25
- package/src/capabilities/call-manager.ts +19 -0
- package/src/capabilities/capabilities.ts +15 -1
- package/src/capabilities/index.ts +2 -0
- package/src/capabilities/intent-resolver.ts +77 -56
- package/src/capabilities/markdown.ts +54 -4
- package/src/capabilities/react-root.tsx +17 -0
- package/src/capabilities/react-surface.tsx +52 -27
- package/src/capabilities/settings.ts +2 -2
- package/src/capabilities/state.ts +3 -3
- package/src/components/Call/Call.tsx +88 -0
- package/src/components/Call/Toolbar.tsx +182 -0
- package/src/components/Call/index.ts +6 -0
- package/src/components/CallDebugPanel.tsx +115 -0
- package/src/components/CallSidebar.tsx +17 -0
- package/src/components/ChannelContainer.stories.tsx +58 -0
- package/src/components/ChannelContainer.tsx +203 -0
- package/src/components/Chat.stories.tsx +11 -12
- package/src/components/ChatContainer.tsx +37 -25
- package/src/components/CommentContainer.tsx +56 -61
- package/src/components/Comments.stories.tsx +20 -16
- package/src/components/CommentsContainer.tsx +20 -36
- package/src/components/Lobby/Lobby.tsx +119 -0
- package/src/components/Lobby/index.ts +5 -0
- package/src/components/Media/AudioStream.tsx +67 -0
- package/src/components/Media/VideoObject.stories.tsx +52 -0
- package/src/components/Media/VideoObject.tsx +58 -0
- package/src/components/Media/index.ts +6 -0
- package/src/components/MessageContainer.tsx +31 -50
- package/src/components/Participant/Participant.tsx +57 -0
- package/src/components/Participant/ParticipantGrid.tsx +88 -0
- package/src/components/Participant/index.ts +6 -0
- package/src/components/ResponsiveGrid/ResponsiveContainer.stories.tsx +54 -0
- package/src/components/ResponsiveGrid/ResponsiveContainer.tsx +31 -0
- package/src/components/ResponsiveGrid/ResponsiveGrid.stories.tsx +156 -0
- package/src/components/ResponsiveGrid/ResponsiveGrid.tsx +341 -0
- package/src/components/ResponsiveGrid/ResponsiveGridItem.tsx +131 -0
- package/src/components/ResponsiveGrid/index.ts +7 -0
- package/src/components/ThreadComplementary.tsx +37 -42
- package/src/components/index.ts +4 -0
- package/src/components/testing.ts +6 -5
- package/src/extensions/threads.ts +42 -24
- package/src/hooks/analytics.ts +4 -4
- package/src/hooks/index.ts +2 -1
- package/src/hooks/useDebugMode.ts +25 -0
- package/src/index.ts +1 -1
- package/src/meta.ts +4 -3
- package/src/peers.stories.tsx +189 -0
- package/src/testing/index.ts +5 -0
- package/src/testing/stories.ts +50 -0
- package/src/translations.ts +46 -5
- package/src/types/index.ts +6 -0
- package/src/types/schema.ts +29 -0
- package/src/types/types.ts +110 -0
- package/src/typings/peermetrics-webrtc-stats.d.ts +30 -0
- package/src/util.ts +12 -0
- package/dist/lib/browser/ChatContainer-IOOAQW6S.mjs +0 -138
- package/dist/lib/browser/ChatContainer-IOOAQW6S.mjs.map +0 -7
- package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs +0 -41
- package/dist/lib/browser/app-graph-builder-6W4UKJDI.mjs.map +0 -7
- package/dist/lib/browser/chunk-EP6MRLY7.mjs.map +0 -7
- package/dist/lib/browser/chunk-QDJ3Y6WF.mjs.map +0 -7
- package/dist/lib/browser/chunk-S5ZR4FYW.mjs +0 -17
- package/dist/lib/browser/chunk-S5ZR4FYW.mjs.map +0 -7
- package/dist/lib/browser/chunk-TFVPUPOO.mjs +0 -207
- package/dist/lib/browser/chunk-TFVPUPOO.mjs.map +0 -7
- package/dist/lib/browser/chunk-WBJLUUOK.mjs.map +0 -7
- package/dist/lib/browser/chunk-WOG72DWE.mjs +0 -93
- package/dist/lib/browser/chunk-WOG72DWE.mjs.map +0 -7
- package/dist/lib/browser/intent-resolver-QAY4FLLM.mjs.map +0 -7
- package/dist/lib/browser/markdown-7KXBXCUS.mjs +0 -111
- package/dist/lib/browser/markdown-7KXBXCUS.mjs.map +0 -7
- package/dist/lib/browser/react-surface-MT3OWULR.mjs +0 -437
- package/dist/lib/browser/react-surface-MT3OWULR.mjs.map +0 -7
- package/dist/lib/browser/settings-4FEC7EAX.mjs.map +0 -7
- package/dist/lib/browser/state-PNMSLKAH.mjs.map +0 -7
- package/dist/lib/browser/types.mjs +0 -10
- package/dist/lib/node/ChatContainer-4FNDRF3G.cjs +0 -163
- package/dist/lib/node/ChatContainer-4FNDRF3G.cjs.map +0 -7
- package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs +0 -57
- package/dist/lib/node/app-graph-builder-HZNWT4QU.cjs.map +0 -7
- package/dist/lib/node/chunk-46BSFPBV.cjs +0 -113
- package/dist/lib/node/chunk-46BSFPBV.cjs.map +0 -7
- package/dist/lib/node/chunk-AKINTFBH.cjs.map +0 -7
- package/dist/lib/node/chunk-E7Z3BKRQ.cjs +0 -232
- package/dist/lib/node/chunk-E7Z3BKRQ.cjs.map +0 -7
- package/dist/lib/node/chunk-NQ5DTPOY.cjs.map +0 -7
- package/dist/lib/node/chunk-S2A6BXEA.cjs.map +0 -7
- package/dist/lib/node/intent-resolver-S6FE5OR7.cjs.map +0 -7
- package/dist/lib/node/markdown-L2WMCQKM.cjs +0 -121
- package/dist/lib/node/markdown-L2WMCQKM.cjs.map +0 -7
- package/dist/lib/node/react-surface-Z45BYECE.cjs +0 -439
- package/dist/lib/node/react-surface-Z45BYECE.cjs.map +0 -7
- package/dist/lib/node/settings-IXCPE55J.cjs.map +0 -7
- package/dist/lib/node/state-AT73KPGZ.cjs.map +0 -7
- package/dist/lib/node/types.cjs.map +0 -7
- package/dist/lib/node-esm/ChatContainer-NYLCL6QA.mjs +0 -139
- package/dist/lib/node-esm/ChatContainer-NYLCL6QA.mjs.map +0 -7
- package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs +0 -42
- package/dist/lib/node-esm/app-graph-builder-M3KK5QJH.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-42F2DTMM.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-ADTVMESP.mjs +0 -208
- package/dist/lib/node-esm/chunk-ADTVMESP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-E446MGLP.mjs +0 -94
- package/dist/lib/node-esm/chunk-E446MGLP.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-J56FORYF.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-WLY2HMDB.mjs.map +0 -7
- package/dist/lib/node-esm/chunk-WSE23GSG.mjs +0 -19
- package/dist/lib/node-esm/chunk-WSE23GSG.mjs.map +0 -7
- package/dist/lib/node-esm/intent-resolver-OURFRSSF.mjs.map +0 -7
- package/dist/lib/node-esm/markdown-ZKU23UVD.mjs +0 -112
- package/dist/lib/node-esm/markdown-ZKU23UVD.mjs.map +0 -7
- package/dist/lib/node-esm/react-surface-KIJ767JK.mjs +0 -438
- package/dist/lib/node-esm/react-surface-KIJ767JK.mjs.map +0 -7
- package/dist/lib/node-esm/settings-ZUJQ4LWI.mjs.map +0 -7
- package/dist/lib/node-esm/state-SSDX5O67.mjs.map +0 -7
- package/dist/lib/node-esm/types.mjs +0 -11
- package/dist/types/src/components/types.d.ts +0 -28
- package/dist/types/src/components/types.d.ts.map +0 -1
- package/dist/types/src/events.d.ts +0 -4
- package/dist/types/src/events.d.ts.map +0 -1
- package/dist/types/src/types.d.ts +0 -125
- package/dist/types/src/types.d.ts.map +0 -1
- package/src/components/types.ts +0 -32
- package/src/events.ts +0 -11
- package/src/types.ts +0 -100
- /package/dist/lib/browser/{types.mjs.map → ChannelContainer-LGYSQMC7.mjs.map} +0 -0
- /package/dist/lib/{node-esm/types.mjs.map → browser/ChatContainer-JWRI73OG.mjs.map} +0 -0
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import md5Hex from 'md5-hex';
|
|
6
|
+
|
|
7
|
+
import { DeferredTask, Event, scheduleTaskInterval, synchronized } from '@dxos/async';
|
|
8
|
+
import { type Identity } from '@dxos/client/halo';
|
|
9
|
+
import { Context, Resource } from '@dxos/context';
|
|
10
|
+
import { generateName } from '@dxos/display-name';
|
|
11
|
+
import { invariant } from '@dxos/invariant';
|
|
12
|
+
import { PublicKey } from '@dxos/keys';
|
|
13
|
+
import { log } from '@dxos/log';
|
|
14
|
+
import { buf } from '@dxos/protocols/buf';
|
|
15
|
+
import { ActivitySchema } from '@dxos/protocols/buf/dxos/edge/calls_pb';
|
|
16
|
+
import { type Device, type NetworkService } from '@dxos/protocols/proto/dxos/client/services';
|
|
17
|
+
import { ConnectionState, type SwarmResponse } from '@dxos/protocols/proto/dxos/edge/messenger';
|
|
18
|
+
import { isNonNullable } from '@dxos/util';
|
|
19
|
+
|
|
20
|
+
import { codec, type ActivityState, type UserState } from './types';
|
|
21
|
+
|
|
22
|
+
export type CallState = {
|
|
23
|
+
/**
|
|
24
|
+
* Room id.
|
|
25
|
+
*/
|
|
26
|
+
roomId?: string;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* All users states in the swarm.
|
|
30
|
+
*/
|
|
31
|
+
users?: UserState[];
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Self state inside swarm.
|
|
35
|
+
*/
|
|
36
|
+
self?: UserState;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Swarm synchronized CRDT transcription. Last write wins.
|
|
40
|
+
*/
|
|
41
|
+
activities?: Record<string, ActivityState>;
|
|
42
|
+
|
|
43
|
+
raisedHand?: boolean;
|
|
44
|
+
speaking?: boolean;
|
|
45
|
+
joined?: boolean;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Tracks pushed by self to calls service.
|
|
49
|
+
*/
|
|
50
|
+
tracks?: UserState['tracks'];
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export type CallSwarmSynchronizerParams = { networkService: NetworkService };
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Period for peer to reconnect to the call swarm gracefully if connection was lost abruptly.
|
|
57
|
+
*/
|
|
58
|
+
const DISCONNECTED_ABRUPT_TIMEOUT = 10_000; // [ms]
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Sends and receives state to/from Swarm network.
|
|
62
|
+
*/
|
|
63
|
+
export class CallSwarmSynchronizer extends Resource {
|
|
64
|
+
public readonly stateUpdated = new Event<CallState>();
|
|
65
|
+
|
|
66
|
+
private readonly _state: CallState = { activities: {} };
|
|
67
|
+
private readonly _networkService: NetworkService;
|
|
68
|
+
private _lastSwarmEvent?: SwarmResponse = undefined;
|
|
69
|
+
private _reconcileSwarmStateTask?: DeferredTask = undefined;
|
|
70
|
+
|
|
71
|
+
private _identityKey?: string = undefined;
|
|
72
|
+
private _deviceKey?: string = undefined;
|
|
73
|
+
private _displayName?: string = undefined;
|
|
74
|
+
|
|
75
|
+
private _swarmCtx?: Context = undefined;
|
|
76
|
+
|
|
77
|
+
private _sendStateTask?: DeferredTask = undefined;
|
|
78
|
+
|
|
79
|
+
constructor({ networkService }: CallSwarmSynchronizerParams) {
|
|
80
|
+
super();
|
|
81
|
+
this._networkService = networkService;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* @internal
|
|
86
|
+
*/
|
|
87
|
+
_getState(): CallState {
|
|
88
|
+
return this._state;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
setRaisedHand(raisedHand: boolean): void {
|
|
92
|
+
if (this._state.raisedHand === raisedHand) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
this._state.raisedHand = raisedHand;
|
|
97
|
+
this._notifyAndSchedule();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
setSpeaking(speaking: boolean): void {
|
|
101
|
+
if (this._state.speaking === speaking) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
this._state.speaking = speaking;
|
|
106
|
+
this._notifyAndSchedule();
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
setJoined(joined: boolean): void {
|
|
110
|
+
if (this._state.joined === joined) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this._state.joined = joined;
|
|
115
|
+
this._notifyAndSchedule();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setTracks(tracks: UserState['tracks']): void {
|
|
119
|
+
this._state.tracks = { ...this._state.tracks, ...tracks };
|
|
120
|
+
this._notifyAndSchedule();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
setActivity(key: string, payload: ActivityState['payload']): void {
|
|
124
|
+
const lamportTimestamp = LamportTimestampCrdt.increment(
|
|
125
|
+
this._state.activities?.[key]?.lamportTimestamp ?? { id: this._deviceKey },
|
|
126
|
+
);
|
|
127
|
+
this._state.activities![key] = { lamportTimestamp, payload };
|
|
128
|
+
|
|
129
|
+
this._notifyAndSchedule();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* @internal
|
|
134
|
+
*/
|
|
135
|
+
_setIdentity(identity: Identity): void {
|
|
136
|
+
this._identityKey = identity.identityKey.toHex();
|
|
137
|
+
this._displayName = identity.profile?.displayName ?? generateName(identity.identityKey.toHex());
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
_setDevice(device: Device): void {
|
|
144
|
+
this._deviceKey = device.deviceKey.toHex();
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @internal
|
|
149
|
+
*/
|
|
150
|
+
_setRoomId(roomId?: string): void {
|
|
151
|
+
if (this._swarmCtx) {
|
|
152
|
+
log.verbose('joined to a different room', { roomId });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
this._state.roomId = roomId;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
protected override async _open(): Promise<void> {
|
|
160
|
+
this._sendStateTask = new DeferredTask(this._ctx, async () => {
|
|
161
|
+
await this._sendState();
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
this._reconcileSwarmStateTask = new DeferredTask(this._ctx, async () => {
|
|
165
|
+
await this._reconcileSwarmState();
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
protected override async _close(): Promise<void> {
|
|
170
|
+
this._sendStateTask = undefined;
|
|
171
|
+
this._reconcileSwarmStateTask = undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@synchronized
|
|
175
|
+
async join(): Promise<void> {
|
|
176
|
+
invariant(this._state.roomId);
|
|
177
|
+
invariant(!this._swarmCtx, 'Already joined');
|
|
178
|
+
|
|
179
|
+
this._swarmCtx = new Context();
|
|
180
|
+
const topic = getTopic(this._state.roomId);
|
|
181
|
+
log('joining swarm', { topic, peer: { identityKey: this._identityKey, peerKey: this._deviceKey } });
|
|
182
|
+
const stream = this._networkService.subscribeSwarmState({ topic });
|
|
183
|
+
stream.subscribe((event) => this._processSwarmEvent(event));
|
|
184
|
+
|
|
185
|
+
const cleanup = () => {
|
|
186
|
+
void stream.close();
|
|
187
|
+
if (topic && this._identityKey && this._deviceKey) {
|
|
188
|
+
log('leaving swarm', { topic, peer: { identityKey: this._identityKey, peerKey: this._deviceKey } });
|
|
189
|
+
void this._networkService
|
|
190
|
+
.leaveSwarm({
|
|
191
|
+
topic,
|
|
192
|
+
peer: { identityKey: this._identityKey, peerKey: this._deviceKey },
|
|
193
|
+
})
|
|
194
|
+
.catch((err) => log.catch(err));
|
|
195
|
+
}
|
|
196
|
+
window.removeEventListener('beforeunload', cleanup);
|
|
197
|
+
};
|
|
198
|
+
window.addEventListener('beforeunload', cleanup);
|
|
199
|
+
this._swarmCtx.onDispose(cleanup);
|
|
200
|
+
|
|
201
|
+
this._notifyAndSchedule();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
@synchronized
|
|
205
|
+
async leave(): Promise<void> {
|
|
206
|
+
if (!this._swarmCtx) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await this._swarmCtx.dispose();
|
|
211
|
+
this._swarmCtx = undefined;
|
|
212
|
+
|
|
213
|
+
this._state.users = undefined;
|
|
214
|
+
this._state.self = undefined;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async querySwarm(roomId: string) {
|
|
218
|
+
const topic = getTopic(roomId);
|
|
219
|
+
const swarm = await this._networkService.querySwarm({ topic });
|
|
220
|
+
return swarm.peers ?? [];
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Notify and schedule send state task.
|
|
225
|
+
*/
|
|
226
|
+
private _notifyAndSchedule(): void {
|
|
227
|
+
this.stateUpdated.emit(this._state);
|
|
228
|
+
if (this._state.joined) {
|
|
229
|
+
this._sendStateTask?.schedule();
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
private async _sendState(): Promise<void> {
|
|
234
|
+
if (!this._state.roomId || !this._identityKey || !this._deviceKey || !this._state.joined) {
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const state: UserState = {
|
|
239
|
+
...this._state,
|
|
240
|
+
id: this._deviceKey,
|
|
241
|
+
name: this._displayName,
|
|
242
|
+
activities: this._state.activities
|
|
243
|
+
? Object.fromEntries(
|
|
244
|
+
Object.entries(this._state.activities).map(([key, activity]) => [
|
|
245
|
+
key,
|
|
246
|
+
buf.create(ActivitySchema, activity),
|
|
247
|
+
]),
|
|
248
|
+
)
|
|
249
|
+
: {},
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
await this._networkService.joinSwarm({
|
|
253
|
+
topic: getTopic(this._state.roomId),
|
|
254
|
+
peer: {
|
|
255
|
+
identityKey: this._identityKey,
|
|
256
|
+
peerKey: this._deviceKey,
|
|
257
|
+
state: codec.encode(state),
|
|
258
|
+
},
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
private _processSwarmEvent(swarmEvent: SwarmResponse): void {
|
|
263
|
+
if (!this._state.joined) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
this._lastSwarmEvent = swarmEvent;
|
|
268
|
+
this._reconcileSwarmStateTask!.schedule();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
private async _reconcileSwarmState(): Promise<void> {
|
|
272
|
+
const swarmEvent = this._lastSwarmEvent;
|
|
273
|
+
invariant(swarmEvent);
|
|
274
|
+
// We include inactive peers that were disconnected abruptly and have not yet reconnected to not drop them from call.
|
|
275
|
+
// Websocket connection could be unstable and we include graceful period for peers to reconnect.
|
|
276
|
+
const peers = [
|
|
277
|
+
...(swarmEvent.peers ?? []),
|
|
278
|
+
...(swarmEvent.inactivePeers ?? []).filter(
|
|
279
|
+
({ disconnected, connectionState }) =>
|
|
280
|
+
connectionState === ConnectionState.DISCONNECTED_ABRUPT &&
|
|
281
|
+
disconnected &&
|
|
282
|
+
disconnected > Date.now() - DISCONNECTED_ABRUPT_TIMEOUT,
|
|
283
|
+
),
|
|
284
|
+
].sort((peer1, peer2) => peer1.peerKey.localeCompare(peer2.peerKey));
|
|
285
|
+
const users = peers
|
|
286
|
+
.map((peer) => {
|
|
287
|
+
try {
|
|
288
|
+
return codec.decode(peer.state!);
|
|
289
|
+
} catch (error) {
|
|
290
|
+
log.error('error decoding peer state', { error, peer });
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
.filter(isNonNullable);
|
|
295
|
+
this._state.users = users;
|
|
296
|
+
this._state.self = users.find((user) => user.id === this._deviceKey);
|
|
297
|
+
log('reconciling swarm state', { users });
|
|
298
|
+
if (users.length === 0) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const activityKeys = new Set<string>(users.flatMap((user) => Object.keys(user.activities)));
|
|
303
|
+
[...activityKeys].forEach((key) => {
|
|
304
|
+
const activities = users.map((user) => user.activities?.[key]).filter(isNonNullable);
|
|
305
|
+
const lastActivity = LamportTimestampCrdt.getLastState(activities);
|
|
306
|
+
if (
|
|
307
|
+
lastActivity &&
|
|
308
|
+
lastActivity.lamportTimestamp!.version! > (this._state.activities![key]?.lamportTimestamp?.version ?? 0)
|
|
309
|
+
) {
|
|
310
|
+
this._state.activities![key] = lastActivity;
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
this.stateUpdated.emit(this._state);
|
|
314
|
+
|
|
315
|
+
// Schedule next reconcile if there are inactive peer to drop them after timeout.
|
|
316
|
+
if (peers.some((peer) => peer.connectionState !== ConnectionState.CONNECTED)) {
|
|
317
|
+
scheduleTaskInterval(
|
|
318
|
+
this._ctx,
|
|
319
|
+
async () => this._reconcileSwarmStateTask!.schedule(),
|
|
320
|
+
Math.min(DISCONNECTED_ABRUPT_TIMEOUT / 2, 1000),
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
type LamportTimestamp = ActivityState['lamportTimestamp'];
|
|
327
|
+
|
|
328
|
+
// TODO(mykola): Factor out.
|
|
329
|
+
class LamportTimestampCrdt {
|
|
330
|
+
static getLastState<T extends { lamportTimestamp?: LamportTimestamp }>(states: T[]): T | undefined {
|
|
331
|
+
const maxTimestamp = Math.max(...states.map((state) => state.lamportTimestamp?.version ?? 0));
|
|
332
|
+
const sortedStates = states
|
|
333
|
+
.filter(
|
|
334
|
+
(state) =>
|
|
335
|
+
state.lamportTimestamp && state.lamportTimestamp.version === maxTimestamp && state.lamportTimestamp.id,
|
|
336
|
+
)
|
|
337
|
+
.sort((a, b) => a.lamportTimestamp!.id!.localeCompare(b.lamportTimestamp!.id!));
|
|
338
|
+
return sortedStates[0];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
static increment(timestamp: LamportTimestamp): LamportTimestamp {
|
|
342
|
+
return {
|
|
343
|
+
...(timestamp ?? {}),
|
|
344
|
+
version: (timestamp?.version ?? 0) + 1,
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// TODO(mykola): Delete once we refactor swarm to accept string as topic.
|
|
350
|
+
const getTopic = (roomId: string): PublicKey => PublicKey.fromHex(md5Hex(roomId));
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
//
|
|
2
|
+
// Copyright 2025 DXOS.org
|
|
3
|
+
//
|
|
4
|
+
|
|
5
|
+
import { DeferredTask, Event, sleep, synchronized } from '@dxos/async';
|
|
6
|
+
import { cancelWithContext, type Context, Resource } from '@dxos/context';
|
|
7
|
+
import { log } from '@dxos/log';
|
|
8
|
+
|
|
9
|
+
import { SpeakingMonitor } from './speaking-monitor';
|
|
10
|
+
import { TrackNameCodec, type EncodedTrackName, type TrackObject } from './types';
|
|
11
|
+
import {
|
|
12
|
+
type CallsServiceConfig,
|
|
13
|
+
CallsServicePeer,
|
|
14
|
+
createBlackCanvasStreamTrack,
|
|
15
|
+
createInaudibleAudioStreamTrack,
|
|
16
|
+
getScreenshare,
|
|
17
|
+
getUserMediaTrack,
|
|
18
|
+
} from './util';
|
|
19
|
+
|
|
20
|
+
export type MediaState = {
|
|
21
|
+
audioDeviceId?: string;
|
|
22
|
+
audioEnabled?: boolean;
|
|
23
|
+
audioTrack?: MediaStreamTrack;
|
|
24
|
+
|
|
25
|
+
videoDeviceId?: string;
|
|
26
|
+
videoEnabled?: boolean;
|
|
27
|
+
videoTrack?: MediaStreamTrack;
|
|
28
|
+
videoStream?: MediaStream;
|
|
29
|
+
|
|
30
|
+
screenshareEnabled?: boolean;
|
|
31
|
+
screenshareTrack?: MediaStreamTrack;
|
|
32
|
+
screenshareVideoStream?: MediaStream;
|
|
33
|
+
|
|
34
|
+
pushedVideoTrack?: TrackObject;
|
|
35
|
+
pushedAudioTrack?: TrackObject;
|
|
36
|
+
pushedScreenshareTrack?: TrackObject;
|
|
37
|
+
peer?: CallsServicePeer;
|
|
38
|
+
|
|
39
|
+
pulledAudioTracks: Record<EncodedTrackName, { track: MediaStreamTrack; ctx: Context }>;
|
|
40
|
+
/**
|
|
41
|
+
* Includes video feeds and screenshare feeds of other participants.
|
|
42
|
+
*/
|
|
43
|
+
pulledVideoStreams: Record<EncodedTrackName, { stream: MediaStream; ctx: Context }>;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
// TOOD(burdon): Hard coded.
|
|
47
|
+
const VIDEO_WIDTH = 1280;
|
|
48
|
+
const VIDEO_HEIGHT = 720;
|
|
49
|
+
const MAX_WEB_CAM_FRAMERATE = 24;
|
|
50
|
+
const MAX_WEB_CAM_BITRATE = 120_0000;
|
|
51
|
+
const RETRY_INTERVAL = 500;
|
|
52
|
+
|
|
53
|
+
export type MediaManagerParams = {
|
|
54
|
+
serviceConfig: CallsServiceConfig;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
export class MediaManager extends Resource {
|
|
58
|
+
public readonly stateUpdated = new Event<MediaState>();
|
|
59
|
+
private readonly _state: MediaState = { pulledVideoStreams: {}, pulledAudioTracks: {} };
|
|
60
|
+
private _speakingMonitor?: SpeakingMonitor = undefined;
|
|
61
|
+
|
|
62
|
+
private _trackToReconcile: EncodedTrackName[] = [];
|
|
63
|
+
private _blackCanvasStreamTrack?: MediaStreamTrack = undefined;
|
|
64
|
+
private _inaudibleAudioStreamTrack?: MediaStreamTrack = undefined;
|
|
65
|
+
private _pushTracksTask?: DeferredTask = undefined;
|
|
66
|
+
private _pullTracksTask?: DeferredTask = undefined;
|
|
67
|
+
|
|
68
|
+
get isSpeaking() {
|
|
69
|
+
return this._speakingMonitor?.isSpeaking;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @internal
|
|
74
|
+
*/
|
|
75
|
+
_getState(): MediaState {
|
|
76
|
+
return this._state;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
protected override async _open(): Promise<void> {
|
|
80
|
+
this._blackCanvasStreamTrack = await createBlackCanvasStreamTrack({
|
|
81
|
+
ctx: this._ctx,
|
|
82
|
+
width: VIDEO_WIDTH,
|
|
83
|
+
height: VIDEO_HEIGHT,
|
|
84
|
+
});
|
|
85
|
+
this._state.videoTrack = this._blackCanvasStreamTrack;
|
|
86
|
+
this._state.videoStream = new MediaStream();
|
|
87
|
+
this._state.videoStream.addTrack(this._state.videoTrack);
|
|
88
|
+
|
|
89
|
+
this._inaudibleAudioStreamTrack = await createInaudibleAudioStreamTrack({ ctx: this._ctx });
|
|
90
|
+
this._state.audioTrack = this._inaudibleAudioStreamTrack;
|
|
91
|
+
|
|
92
|
+
this._pushTracksTask = new DeferredTask(this._ctx, async () => {
|
|
93
|
+
await this._pushTracks();
|
|
94
|
+
});
|
|
95
|
+
this._pullTracksTask = new DeferredTask(this._ctx, async () => {
|
|
96
|
+
await this._reconcilePulledMedia();
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
protected override async _close(): Promise<void> {
|
|
101
|
+
void this._speakingMonitor?.close();
|
|
102
|
+
this._state.videoTrack && this._state.videoStream?.removeTrack(this._state.videoTrack);
|
|
103
|
+
this._state.audioTrack?.stop();
|
|
104
|
+
this._state.videoTrack?.stop();
|
|
105
|
+
this._state.screenshareTrack?.stop();
|
|
106
|
+
this._pushTracksTask = undefined;
|
|
107
|
+
this._pullTracksTask = undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@synchronized
|
|
111
|
+
async join(serviceConfig: CallsServiceConfig): Promise<void> {
|
|
112
|
+
this._state.peer = new CallsServicePeer(serviceConfig);
|
|
113
|
+
await this._state.peer!.open();
|
|
114
|
+
this._pushTracksTask!.schedule();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
@synchronized
|
|
118
|
+
async leave(): Promise<void> {
|
|
119
|
+
await Promise.all(Object.values(this._state.pulledAudioTracks).map(({ ctx }) => ctx.dispose()));
|
|
120
|
+
await Promise.all(Object.values(this._state.pulledVideoStreams).map(({ ctx }) => ctx.dispose()));
|
|
121
|
+
this._trackToReconcile = [];
|
|
122
|
+
await this._state.peer?.close();
|
|
123
|
+
this._state.peer = undefined;
|
|
124
|
+
this._state.pushedVideoTrack = undefined;
|
|
125
|
+
this._state.pushedAudioTrack = undefined;
|
|
126
|
+
this._state.pushedScreenshareTrack = undefined;
|
|
127
|
+
this._state.pulledAudioTracks = {};
|
|
128
|
+
this._state.pulledVideoStreams = {};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async turnVideoOn(): Promise<void> {
|
|
132
|
+
this._state.videoStream!.removeTrack(this._state.videoTrack!);
|
|
133
|
+
this._state.videoTrack = await getUserMediaTrack('videoinput', { width: VIDEO_WIDTH, height: VIDEO_HEIGHT });
|
|
134
|
+
this._state.videoStream!.addTrack(this._state.videoTrack);
|
|
135
|
+
this._state.videoEnabled = true;
|
|
136
|
+
this.stateUpdated.emit(this._state);
|
|
137
|
+
this._pushTracksTask!.schedule();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async turnVideoOff(): Promise<void> {
|
|
141
|
+
if (this._state.videoTrack !== this._blackCanvasStreamTrack) {
|
|
142
|
+
this._state.videoStream!.removeTrack(this._state.videoTrack!);
|
|
143
|
+
this._state.videoTrack?.stop();
|
|
144
|
+
this._state.videoTrack = this._blackCanvasStreamTrack;
|
|
145
|
+
this._state.videoStream!.addTrack(this._state.videoTrack!);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
this._state.videoEnabled = false;
|
|
149
|
+
this.stateUpdated.emit(this._state);
|
|
150
|
+
this._pushTracksTask!.schedule();
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// TODO(mykola): Change to `setAudioEnabled(enabled: boolean)`.
|
|
154
|
+
async turnAudioOn(): Promise<void> {
|
|
155
|
+
void this._speakingMonitor?.close();
|
|
156
|
+
this._state.audioEnabled = true;
|
|
157
|
+
this._state.audioTrack = await getUserMediaTrack('audioinput', {
|
|
158
|
+
echoCancellation: true,
|
|
159
|
+
noiseSuppression: true,
|
|
160
|
+
autoGainControl: true,
|
|
161
|
+
sampleRate: 16_000,
|
|
162
|
+
});
|
|
163
|
+
this.stateUpdated.emit(this._state);
|
|
164
|
+
this._pushTracksTask!.schedule();
|
|
165
|
+
this._speakingMonitor = new SpeakingMonitor(this._state.audioTrack!);
|
|
166
|
+
this._speakingMonitor.speakingChanged.on(this._ctx, () => this.stateUpdated.emit(this._state));
|
|
167
|
+
void this._speakingMonitor.open();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async turnAudioOff(): Promise<void> {
|
|
171
|
+
void this._speakingMonitor?.close();
|
|
172
|
+
this._speakingMonitor = undefined;
|
|
173
|
+
if (this._state.audioTrack !== this._inaudibleAudioStreamTrack) {
|
|
174
|
+
this._state.audioTrack?.stop();
|
|
175
|
+
this._state.audioTrack = this._inaudibleAudioStreamTrack;
|
|
176
|
+
}
|
|
177
|
+
this._state.audioEnabled = false;
|
|
178
|
+
this.stateUpdated.emit(this._state);
|
|
179
|
+
this._pushTracksTask!.schedule();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async turnScreenshareOn(): Promise<void> {
|
|
183
|
+
const ms = await getScreenshare({ contentHint: 'text' });
|
|
184
|
+
this._state.screenshareVideoStream = ms;
|
|
185
|
+
this._state.screenshareTrack = ms.getVideoTracks()[0];
|
|
186
|
+
this._state.screenshareEnabled = true;
|
|
187
|
+
this.stateUpdated.emit(this._state);
|
|
188
|
+
this._pushTracksTask!.schedule();
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async turnScreenshareOff(): Promise<void> {
|
|
192
|
+
this._state.screenshareEnabled = false;
|
|
193
|
+
this._state.screenshareTrack?.stop();
|
|
194
|
+
this._state.screenshareTrack = undefined;
|
|
195
|
+
this.stateUpdated.emit(this._state);
|
|
196
|
+
this._pushTracksTask!.schedule();
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* @internal
|
|
201
|
+
*/
|
|
202
|
+
_schedulePullTracks(tracks?: EncodedTrackName[]): void {
|
|
203
|
+
if (
|
|
204
|
+
!tracks ||
|
|
205
|
+
(this._trackToReconcile.every((name) => tracks.includes(name)) && this._trackToReconcile.length === tracks.length)
|
|
206
|
+
) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
this._trackToReconcile = tracks;
|
|
211
|
+
this._pullTracksTask!.schedule();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private async _reconcilePulledMedia(): Promise<void> {
|
|
215
|
+
log('reconciling tracks');
|
|
216
|
+
// TODO(mykola): Currently cloudflare fails if you try to pull track immediately after pushing it.
|
|
217
|
+
// Add retry logic, remove sleep.
|
|
218
|
+
// Wait for cloudflare to process the track.
|
|
219
|
+
await cancelWithContext(this._ctx, sleep(800));
|
|
220
|
+
const trackNames = this._trackToReconcile;
|
|
221
|
+
|
|
222
|
+
const tracksToPull = trackNames.filter(
|
|
223
|
+
(name) => !this._state.pulledAudioTracks[name] && !this._state.pulledVideoStreams[name],
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const audioTracksToClose = Object.entries(this._state.pulledAudioTracks).filter(
|
|
227
|
+
([key]) => !trackNames.some((name) => name === key),
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
const videoStreamsToClose = Object.entries(this._state.pulledVideoStreams).filter(
|
|
231
|
+
([key]) => !trackNames.some((name) => name === key),
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
log('reconciling tracks', {
|
|
235
|
+
trackNames,
|
|
236
|
+
tracksToPull,
|
|
237
|
+
videoStreamsToClose,
|
|
238
|
+
audioTracksToClose,
|
|
239
|
+
currentAudioTracks: Object.keys(this._state.pulledAudioTracks),
|
|
240
|
+
currentVideoStreams: Object.keys(this._state.pulledVideoStreams),
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// Pull new tracks.
|
|
244
|
+
await Promise.all(tracksToPull.map((name) => this._pullTrack(name)));
|
|
245
|
+
|
|
246
|
+
// Close old tracks.
|
|
247
|
+
await Promise.all([...audioTracksToClose, ...videoStreamsToClose].map(([_, { ctx }]) => ctx.dispose()));
|
|
248
|
+
|
|
249
|
+
log('reconciled tracks', {
|
|
250
|
+
currentAudioTracks: Object.keys(this._state.pulledAudioTracks),
|
|
251
|
+
currentVideoStreams: Object.keys(this._state.pulledVideoStreams),
|
|
252
|
+
});
|
|
253
|
+
this.stateUpdated.emit(this._state);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
private async _pushTracks(): Promise<void> {
|
|
257
|
+
if (!this._state.peer?.isOpen) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
let updated = false;
|
|
262
|
+
const [pushedVideoTrack, pushedAudioTrack, pushedScreenshareTrack] = await Promise.all([
|
|
263
|
+
this._pushTrack(this._state.videoTrack, this._state.pushedVideoTrack, [
|
|
264
|
+
{ maxFramerate: MAX_WEB_CAM_FRAMERATE, maxBitrate: MAX_WEB_CAM_BITRATE },
|
|
265
|
+
]),
|
|
266
|
+
this._pushTrack(this._state.audioTrack, this._state.pushedAudioTrack, [{ networkPriority: 'high' }]),
|
|
267
|
+
this._pushTrack(this._state.screenshareTrack, this._state.pushedScreenshareTrack),
|
|
268
|
+
]);
|
|
269
|
+
|
|
270
|
+
if (pushedVideoTrack !== this._state.pushedVideoTrack) {
|
|
271
|
+
this._state.pushedVideoTrack = pushedVideoTrack;
|
|
272
|
+
updated = true;
|
|
273
|
+
}
|
|
274
|
+
if (pushedAudioTrack !== this._state.pushedAudioTrack) {
|
|
275
|
+
this._state.pushedAudioTrack = pushedAudioTrack;
|
|
276
|
+
updated = true;
|
|
277
|
+
}
|
|
278
|
+
if (pushedScreenshareTrack !== this._state.pushedScreenshareTrack) {
|
|
279
|
+
this._state.pushedScreenshareTrack = pushedScreenshareTrack;
|
|
280
|
+
updated = true;
|
|
281
|
+
}
|
|
282
|
+
if (!updated) {
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
this.stateUpdated.emit(this._state);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
private async _pullTrack(name: EncodedTrackName): Promise<void> {
|
|
290
|
+
const ctx = this._ctx.derive();
|
|
291
|
+
try {
|
|
292
|
+
const trackData = TrackNameCodec.decode(name);
|
|
293
|
+
const track = await this._state.peer!.pullTrack({ trackData, ctx });
|
|
294
|
+
if (track?.readyState === 'ended') {
|
|
295
|
+
throw new Error('Pulled track ended immediately');
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
switch (track?.kind) {
|
|
299
|
+
case 'audio': {
|
|
300
|
+
this._state.pulledAudioTracks[name] = { track, ctx };
|
|
301
|
+
ctx.onDispose(() => delete this._state.pulledAudioTracks[name]);
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
case 'video': {
|
|
305
|
+
const mediaStream = new MediaStream();
|
|
306
|
+
mediaStream.addTrack(track);
|
|
307
|
+
this._state.pulledVideoStreams[name] = { stream: mediaStream, ctx };
|
|
308
|
+
ctx.onDispose(() => {
|
|
309
|
+
mediaStream.removeTrack(track);
|
|
310
|
+
track.stop();
|
|
311
|
+
delete this._state.pulledVideoStreams[name];
|
|
312
|
+
});
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
default:
|
|
316
|
+
throw new Error(`Invalid track kind: ${track?.kind}`);
|
|
317
|
+
}
|
|
318
|
+
} catch (err) {
|
|
319
|
+
log.verbose('failed to pull track', { err, name });
|
|
320
|
+
void ctx.dispose();
|
|
321
|
+
await cancelWithContext(this._ctx, sleep(RETRY_INTERVAL));
|
|
322
|
+
log.verbose('retrying pull track', { name });
|
|
323
|
+
this._pullTracksTask!.schedule();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async _pushTrack(
|
|
328
|
+
track?: MediaStreamTrack,
|
|
329
|
+
previousTrack?: TrackObject,
|
|
330
|
+
encodings?: RTCRtpEncodingParameters[],
|
|
331
|
+
): Promise<TrackObject | undefined> {
|
|
332
|
+
if (!track && !previousTrack) {
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
return this._state.peer!.pushTrack({
|
|
337
|
+
track: track ?? null,
|
|
338
|
+
previousTrack,
|
|
339
|
+
encodings,
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
}
|