@datalayer/lexical-loro 0.1.0 → 0.2.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/README.md +1 -1
- package/package.json +7 -7
- package/lib/App.d.ts +0 -2
- package/lib/App.js +0 -141
- package/lib/Editor.d.ts +0 -2
- package/lib/Editor.js +0 -111
- package/lib/Settings.d.ts +0 -2
- package/lib/Settings.js +0 -57
- package/lib/appSettings.d.ts +0 -36
- package/lib/appSettings.js +0 -44
- package/lib/collab/loro/Bindings.d.ts +0 -41
- package/lib/collab/loro/Bindings.js +0 -95
- package/lib/collab/loro/Debug.d.ts +0 -33
- package/lib/collab/loro/Debug.js +0 -448
- package/lib/collab/loro/LexicalCollaborationContext.d.ts +0 -19
- package/lib/collab/loro/LexicalCollaborationContext.js +0 -48
- package/lib/collab/loro/LexicalCollaborationPlugin.d.ts +0 -24
- package/lib/collab/loro/LexicalCollaborationPlugin.js +0 -83
- package/lib/collab/loro/State.d.ts +0 -53
- package/lib/collab/loro/State.js +0 -90
- package/lib/collab/loro/components/LoroCollaborationUI.d.ts +0 -13
- package/lib/collab/loro/components/LoroCollaborationUI.js +0 -9
- package/lib/collab/loro/components/LoroCollaborators.d.ts +0 -8
- package/lib/collab/loro/components/LoroCollaborators.js +0 -97
- package/lib/collab/loro/components/index.d.ts +0 -2
- package/lib/collab/loro/components/index.js +0 -2
- package/lib/collab/loro/index.d.ts +0 -6
- package/lib/collab/loro/index.js +0 -6
- package/lib/collab/loro/integrators/BaseIntegrator.d.ts +0 -14
- package/lib/collab/loro/integrators/BaseIntegrator.js +0 -1
- package/lib/collab/loro/integrators/CounterIntegrator.d.ts +0 -23
- package/lib/collab/loro/integrators/CounterIntegrator.js +0 -40
- package/lib/collab/loro/integrators/ListIntegrator.d.ts +0 -23
- package/lib/collab/loro/integrators/ListIntegrator.js +0 -49
- package/lib/collab/loro/integrators/MapIntegrator.d.ts +0 -24
- package/lib/collab/loro/integrators/MapIntegrator.js +0 -177
- package/lib/collab/loro/integrators/TextIntegrator.d.ts +0 -25
- package/lib/collab/loro/integrators/TextIntegrator.js +0 -51
- package/lib/collab/loro/integrators/TreeIntegrator.d.ts +0 -25
- package/lib/collab/loro/integrators/TreeIntegrator.js +0 -201
- package/lib/collab/loro/nodes/NodeFactory.d.ts +0 -8
- package/lib/collab/loro/nodes/NodeFactory.js +0 -105
- package/lib/collab/loro/nodes/NodesMapper.d.ts +0 -111
- package/lib/collab/loro/nodes/NodesMapper.js +0 -258
- package/lib/collab/loro/propagators/DecoratorNodePropagator.d.ts +0 -60
- package/lib/collab/loro/propagators/DecoratorNodePropagator.js +0 -302
- package/lib/collab/loro/propagators/ElementNodePropagator.d.ts +0 -62
- package/lib/collab/loro/propagators/ElementNodePropagator.js +0 -335
- package/lib/collab/loro/propagators/LineBreakNodePropagator.d.ts +0 -57
- package/lib/collab/loro/propagators/LineBreakNodePropagator.js +0 -196
- package/lib/collab/loro/propagators/RootNodePropagator.d.ts +0 -55
- package/lib/collab/loro/propagators/RootNodePropagator.js +0 -168
- package/lib/collab/loro/propagators/TextNodePropagator.d.ts +0 -60
- package/lib/collab/loro/propagators/TextNodePropagator.js +0 -434
- package/lib/collab/loro/propagators/index.d.ts +0 -49
- package/lib/collab/loro/propagators/index.js +0 -32
- package/lib/collab/loro/provider/websocket.d.ts +0 -116
- package/lib/collab/loro/provider/websocket.js +0 -907
- package/lib/collab/loro/servers/index.d.ts +0 -0
- package/lib/collab/loro/servers/index.js +0 -0
- package/lib/collab/loro/servers/ws/callback.d.ts +0 -5
- package/lib/collab/loro/servers/ws/callback.js +0 -85
- package/lib/collab/loro/servers/ws/server.d.ts +0 -2
- package/lib/collab/loro/servers/ws/server.js +0 -25
- package/lib/collab/loro/servers/ws/utils.d.ts +0 -40
- package/lib/collab/loro/servers/ws/utils.js +0 -513
- package/lib/collab/loro/sync/SyncCursors.d.ts +0 -32
- package/lib/collab/loro/sync/SyncCursors.js +0 -435
- package/lib/collab/loro/sync/SyncLexicalToLoro.d.ts +0 -4
- package/lib/collab/loro/sync/SyncLexicalToLoro.js +0 -80
- package/lib/collab/loro/sync/SyncLoroToLexical.d.ts +0 -5
- package/lib/collab/loro/sync/SyncLoroToLexical.js +0 -96
- package/lib/collab/loro/types/LexicalNodeData.d.ts +0 -32
- package/lib/collab/loro/types/LexicalNodeData.js +0 -71
- package/lib/collab/loro/useCollaboration.d.ts +0 -12
- package/lib/collab/loro/useCollaboration.js +0 -248
- package/lib/collab/loro/utils/InitialContent.d.ts +0 -64
- package/lib/collab/loro/utils/InitialContent.js +0 -109
- package/lib/collab/loro/utils/LexicalToLoro.d.ts +0 -18
- package/lib/collab/loro/utils/LexicalToLoro.js +0 -96
- package/lib/collab/loro/utils/Utils.d.ts +0 -44
- package/lib/collab/loro/utils/Utils.js +0 -153
- package/lib/collab/loro/wsProvider.d.ts +0 -8
- package/lib/collab/loro/wsProvider.js +0 -31
- package/lib/collab/utils/invariant.d.ts +0 -1
- package/lib/collab/utils/invariant.js +0 -11
- package/lib/collab/utils/simpleDiffWithCursor.d.ts +0 -5
- package/lib/collab/utils/simpleDiffWithCursor.js +0 -31
- package/lib/collab/yjs/Bindings.d.ts +0 -23
- package/lib/collab/yjs/Bindings.js +0 -26
- package/lib/collab/yjs/Debug.d.ts +0 -23
- package/lib/collab/yjs/Debug.js +0 -213
- package/lib/collab/yjs/LexicalCollaborationContext.d.ts +0 -10
- package/lib/collab/yjs/LexicalCollaborationContext.js +0 -37
- package/lib/collab/yjs/LexicalCollaborationPlugin.d.ts +0 -21
- package/lib/collab/yjs/LexicalCollaborationPlugin.js +0 -63
- package/lib/collab/yjs/State.d.ts +0 -51
- package/lib/collab/yjs/State.js +0 -35
- package/lib/collab/yjs/nodes/AnyCollabNode.d.ts +0 -5
- package/lib/collab/yjs/nodes/AnyCollabNode.js +0 -1
- package/lib/collab/yjs/nodes/CollabDecoratorNode.d.ts +0 -22
- package/lib/collab/yjs/nodes/CollabDecoratorNode.js +0 -64
- package/lib/collab/yjs/nodes/CollabElementNode.d.ts +0 -40
- package/lib/collab/yjs/nodes/CollabElementNode.js +0 -462
- package/lib/collab/yjs/nodes/CollabLineBreakNode.d.ts +0 -19
- package/lib/collab/yjs/nodes/CollabLineBreakNode.js +0 -44
- package/lib/collab/yjs/nodes/CollabTextNode.d.ts +0 -25
- package/lib/collab/yjs/nodes/CollabTextNode.js +0 -103
- package/lib/collab/yjs/provider/websocket.d.ts +0 -88
- package/lib/collab/yjs/provider/websocket.js +0 -415
- package/lib/collab/yjs/servers/index.d.ts +0 -0
- package/lib/collab/yjs/servers/index.js +0 -0
- package/lib/collab/yjs/servers/ws/callback.d.ts +0 -5
- package/lib/collab/yjs/servers/ws/callback.js +0 -72
- package/lib/collab/yjs/servers/ws/server.d.ts +0 -2
- package/lib/collab/yjs/servers/ws/server.js +0 -25
- package/lib/collab/yjs/servers/ws/utils.d.ts +0 -49
- package/lib/collab/yjs/servers/ws/utils.js +0 -284
- package/lib/collab/yjs/sync/SyncCursors.d.ts +0 -39
- package/lib/collab/yjs/sync/SyncCursors.js +0 -351
- package/lib/collab/yjs/sync/SyncEditorStates.d.ts +0 -10
- package/lib/collab/yjs/sync/SyncEditorStates.js +0 -200
- package/lib/collab/yjs/useCollaboration.d.ts +0 -12
- package/lib/collab/yjs/useCollaboration.js +0 -255
- package/lib/collab/yjs/utils/Utils.d.ts +0 -25
- package/lib/collab/yjs/utils/Utils.js +0 -402
- package/lib/collab/yjs/wsProvider.d.ts +0 -3
- package/lib/collab/yjs/wsProvider.js +0 -21
- package/lib/commenting/index.d.ts +0 -41
- package/lib/commenting/index.js +0 -324
- package/lib/context/FlashMessageContext.d.ts +0 -7
- package/lib/context/FlashMessageContext.js +0 -24
- package/lib/context/SettingsContext.d.ts +0 -12
- package/lib/context/SettingsContext.js +0 -38
- package/lib/context/SharedHistoryContext.d.ts +0 -11
- package/lib/context/SharedHistoryContext.js +0 -11
- package/lib/context/ToolbarContext.d.ts +0 -65
- package/lib/context/ToolbarContext.js +0 -84
- package/lib/demo.d.ts +0 -12
- package/lib/demo.js +0 -41
- package/lib/hooks/useFlashMessage.d.ts +0 -2
- package/lib/hooks/useFlashMessage.js +0 -4
- package/lib/hooks/useModal.d.ts +0 -5
- package/lib/hooks/useModal.js +0 -26
- package/lib/hooks/useReport.d.ts +0 -1
- package/lib/hooks/useReport.js +0 -46
- package/lib/index.d.ts +0 -1
- package/lib/index.js +0 -1
- package/lib/nodes/AutocompleteNode.d.ts +0 -27
- package/lib/nodes/AutocompleteNode.js +0 -56
- package/lib/nodes/CounterComponent.d.ts +0 -6
- package/lib/nodes/CounterComponent.js +0 -137
- package/lib/nodes/CounterNode.d.ts +0 -23
- package/lib/nodes/CounterNode.js +0 -47
- package/lib/nodes/DateTimeNode/DateTimeComponent.d.ts +0 -8
- package/lib/nodes/DateTimeNode/DateTimeComponent.js +0 -119
- package/lib/nodes/DateTimeNode/DateTimeNode.d.ts +0 -27
- package/lib/nodes/DateTimeNode/DateTimeNode.js +0 -82
- package/lib/nodes/EmojiNode.d.ts +0 -18
- package/lib/nodes/EmojiNode.js +0 -50
- package/lib/nodes/EquationComponent.d.ts +0 -9
- package/lib/nodes/EquationComponent.js +0 -75
- package/lib/nodes/EquationNode.d.ts +0 -26
- package/lib/nodes/EquationNode.js +0 -109
- package/lib/nodes/ExcalidrawNode/ExcalidrawComponent.d.ts +0 -8
- package/lib/nodes/ExcalidrawNode/ExcalidrawComponent.js +0 -110
- package/lib/nodes/ExcalidrawNode/ExcalidrawImage.d.ts +0 -50
- package/lib/nodes/ExcalidrawNode/ExcalidrawImage.js +0 -55
- package/lib/nodes/ExcalidrawNode/index.d.ts +0 -32
- package/lib/nodes/ExcalidrawNode/index.js +0 -117
- package/lib/nodes/FigmaNode.d.ts +0 -20
- package/lib/nodes/FigmaNode.js +0 -52
- package/lib/nodes/ImageComponent.d.ts +0 -16
- package/lib/nodes/ImageComponent.js +0 -272
- package/lib/nodes/ImageNode.d.ts +0 -50
- package/lib/nodes/ImageNode.js +0 -151
- package/lib/nodes/InlineImageNode/InlineImageComponent.d.ts +0 -26
- package/lib/nodes/InlineImageNode/InlineImageComponent.js +0 -161
- package/lib/nodes/InlineImageNode/InlineImageNode.d.ts +0 -59
- package/lib/nodes/InlineImageNode/InlineImageNode.js +0 -162
- package/lib/nodes/KeywordNode.d.ts +0 -14
- package/lib/nodes/KeywordNode.js +0 -33
- package/lib/nodes/LayoutContainerNode.d.ts +0 -24
- package/lib/nodes/LayoutContainerNode.js +0 -91
- package/lib/nodes/LayoutItemNode.d.ts +0 -16
- package/lib/nodes/LayoutItemNode.js +0 -65
- package/lib/nodes/MentionNode.d.ts +0 -20
- package/lib/nodes/MentionNode.js +0 -81
- package/lib/nodes/PageBreakNode/index.d.ts +0 -17
- package/lib/nodes/PageBreakNode/index.js +0 -83
- package/lib/nodes/PlaygroundNodes.d.ts +0 -3
- package/lib/nodes/PlaygroundNodes.js +0 -71
- package/lib/nodes/PollComponent.d.ts +0 -9
- package/lib/nodes/PollComponent.js +0 -85
- package/lib/nodes/PollNode.d.ts +0 -43
- package/lib/nodes/PollNode.js +0 -153
- package/lib/nodes/SpecialTextNode.d.ts +0 -24
- package/lib/nodes/SpecialTextNode.js +0 -50
- package/lib/nodes/StickyComponent.d.ts +0 -10
- package/lib/nodes/StickyComponent.js +0 -162
- package/lib/nodes/StickyNode.d.ts +0 -31
- package/lib/nodes/StickyNode.js +0 -76
- package/lib/nodes/TweetNode.d.ts +0 -21
- package/lib/nodes/TweetNode.js +0 -119
- package/lib/nodes/YouTubeNode.d.ts +0 -22
- package/lib/nodes/YouTubeNode.js +0 -84
- package/lib/plugins/ActionsPlugin/index.d.ts +0 -5
- package/lib/plugins/ActionsPlugin/index.js +0 -168
- package/lib/plugins/AutoEmbedPlugin/index.d.ts +0 -19
- package/lib/plugins/AutoEmbedPlugin/index.js +0 -158
- package/lib/plugins/AutoLinkPlugin/index.d.ts +0 -2
- package/lib/plugins/AutoLinkPlugin/index.js +0 -15
- package/lib/plugins/AutocompletePlugin/index.d.ts +0 -10
- package/lib/plugins/AutocompletePlugin/index.js +0 -2473
- package/lib/plugins/CodeActionMenuPlugin/components/CopyButton/index.d.ts +0 -7
- package/lib/plugins/CodeActionMenuPlugin/components/CopyButton/index.js +0 -42
- package/lib/plugins/CodeActionMenuPlugin/components/PrettierButton/index.d.ts +0 -17
- package/lib/plugins/CodeActionMenuPlugin/components/PrettierButton/index.js +0 -111
- package/lib/plugins/CodeActionMenuPlugin/index.d.ts +0 -5
- package/lib/plugins/CodeActionMenuPlugin/index.js +0 -104
- package/lib/plugins/CodeActionMenuPlugin/utils.d.ts +0 -1
- package/lib/plugins/CodeActionMenuPlugin/utils.js +0 -18
- package/lib/plugins/CodeHighlightPrismPlugin/index.d.ts +0 -2
- package/lib/plugins/CodeHighlightPrismPlugin/index.js +0 -10
- package/lib/plugins/CodeHighlightShikiPlugin/index.d.ts +0 -2
- package/lib/plugins/CodeHighlightShikiPlugin/index.js +0 -10
- package/lib/plugins/CollapsiblePlugin/CollapsibleContainerNode.d.ts +0 -25
- package/lib/plugins/CollapsiblePlugin/CollapsibleContainerNode.js +0 -131
- package/lib/plugins/CollapsiblePlugin/CollapsibleContentNode.d.ts +0 -16
- package/lib/plugins/CollapsiblePlugin/CollapsibleContentNode.js +0 -79
- package/lib/plugins/CollapsiblePlugin/CollapsibleTitleNode.d.ts +0 -16
- package/lib/plugins/CollapsiblePlugin/CollapsibleTitleNode.js +0 -81
- package/lib/plugins/CollapsiblePlugin/CollapsibleUtils.d.ts +0 -2
- package/lib/plugins/CollapsiblePlugin/CollapsibleUtils.js +0 -8
- package/lib/plugins/CollapsiblePlugin/index.d.ts +0 -3
- package/lib/plugins/CollapsiblePlugin/index.js +0 -128
- package/lib/plugins/CommentPlugin/index.d.ts +0 -9
- package/lib/plugins/CommentPlugin/index.js +0 -460
- package/lib/plugins/ComponentPickerPlugin/index.d.ts +0 -2
- package/lib/plugins/ComponentPickerPlugin/index.js +0 -276
- package/lib/plugins/ContextMenuPlugin/index.d.ts +0 -2
- package/lib/plugins/ContextMenuPlugin/index.js +0 -112
- package/lib/plugins/CounterPlugin/index.d.ts +0 -3
- package/lib/plugins/CounterPlugin/index.js +0 -20
- package/lib/plugins/DatalayerPlugin/index.d.ts +0 -2
- package/lib/plugins/DatalayerPlugin/index.js +0 -218
- package/lib/plugins/DateTimePlugin/index.d.ts +0 -8
- package/lib/plugins/DateTimePlugin/index.js +0 -24
- package/lib/plugins/DocsPlugin/index.d.ts +0 -2
- package/lib/plugins/DocsPlugin/index.js +0 -4
- package/lib/plugins/DragDropPastePlugin/index.d.ts +0 -1
- package/lib/plugins/DragDropPastePlugin/index.js +0 -33
- package/lib/plugins/DraggableBlockPlugin/index.d.ts +0 -12
- package/lib/plugins/DraggableBlockPlugin/index.js +0 -36
- package/lib/plugins/EmojiPickerPlugin/index.d.ts +0 -1
- package/lib/plugins/EmojiPickerPlugin/index.js +0 -80
- package/lib/plugins/EmojisPlugin/index.d.ts +0 -2
- package/lib/plugins/EmojisPlugin/index.js +0 -52
- package/lib/plugins/EquationsPlugin/index.d.ts +0 -14
- package/lib/plugins/EquationsPlugin/index.js +0 -34
- package/lib/plugins/ExcalidrawPlugin/index.d.ts +0 -5
- package/lib/plugins/ExcalidrawPlugin/index.js +0 -44
- package/lib/plugins/FigmaPlugin/index.d.ts +0 -4
- package/lib/plugins/FigmaPlugin/index.js +0 -20
- package/lib/plugins/FloatingLinkEditorPlugin/index.d.ts +0 -15
- package/lib/plugins/FloatingLinkEditorPlugin/index.js +0 -280
- package/lib/plugins/FloatingTextFormatToolbarPlugin/index.d.ts +0 -7
- package/lib/plugins/FloatingTextFormatToolbarPlugin/index.js +0 -219
- package/lib/plugins/ImagesPlugin/index.d.ts +0 -24
- package/lib/plugins/ImagesPlugin/index.js +0 -195
- package/lib/plugins/InlineImagePlugin/index.d.ts +0 -17
- package/lib/plugins/InlineImagePlugin/index.js +0 -180
- package/lib/plugins/KeywordsPlugin/index.d.ts +0 -2
- package/lib/plugins/KeywordsPlugin/index.js +0 -31
- package/lib/plugins/LayoutPlugin/InsertLayoutDialog.d.ts +0 -6
- package/lib/plugins/LayoutPlugin/InsertLayoutDialog.js +0 -21
- package/lib/plugins/LayoutPlugin/LayoutPlugin.d.ts +0 -7
- package/lib/plugins/LayoutPlugin/LayoutPlugin.js +0 -131
- package/lib/plugins/LinkPlugin/index.d.ts +0 -6
- package/lib/plugins/LinkPlugin/index.js +0 -11
- package/lib/plugins/MarkdownShortcutPlugin/index.d.ts +0 -2
- package/lib/plugins/MarkdownShortcutPlugin/index.js +0 -6
- package/lib/plugins/MarkdownTransformers/index.d.ts +0 -8
- package/lib/plugins/MarkdownTransformers/index.js +0 -234
- package/lib/plugins/MaxLengthPlugin/index.d.ts +0 -3
- package/lib/plugins/MaxLengthPlugin/index.js +0 -37
- package/lib/plugins/MentionsPlugin/index.d.ts +0 -2
- package/lib/plugins/MentionsPlugin/index.js +0 -564
- package/lib/plugins/PageBreakPlugin/index.d.ts +0 -4
- package/lib/plugins/PageBreakPlugin/index.js +0 -27
- package/lib/plugins/PasteLogPlugin/index.d.ts +0 -2
- package/lib/plugins/PasteLogPlugin/index.js +0 -27
- package/lib/plugins/PollPlugin/index.d.ts +0 -8
- package/lib/plugins/PollPlugin/index.js +0 -38
- package/lib/plugins/ShortcutsPlugin/index.d.ts +0 -6
- package/lib/plugins/ShortcutsPlugin/index.js +0 -112
- package/lib/plugins/ShortcutsPlugin/shortcuts.d.ts +0 -59
- package/lib/plugins/ShortcutsPlugin/shortcuts.js +0 -169
- package/lib/plugins/SpecialTextPlugin/index.d.ts +0 -2
- package/lib/plugins/SpecialTextPlugin/index.js +0 -46
- package/lib/plugins/SpeechToTextPlugin/index.d.ts +0 -5
- package/lib/plugins/SpeechToTextPlugin/index.js +0 -82
- package/lib/plugins/StickyPlugin/index.d.ts +0 -2
- package/lib/plugins/StickyPlugin/index.js +0 -12
- package/lib/plugins/TabFocusPlugin/index.d.ts +0 -1
- package/lib/plugins/TabFocusPlugin/index.js +0 -34
- package/lib/plugins/TableActionMenuPlugin/index.d.ts +0 -5
- package/lib/plugins/TableActionMenuPlugin/index.js +0 -492
- package/lib/plugins/TableCellResizer/index.d.ts +0 -3
- package/lib/plugins/TableCellResizer/index.js +0 -297
- package/lib/plugins/TableHoverActionsPlugin/index.d.ts +0 -4
- package/lib/plugins/TableHoverActionsPlugin/index.js +0 -188
- package/lib/plugins/TableOfContentsPlugin/index.d.ts +0 -2
- package/lib/plugins/TableOfContentsPlugin/index.js +0 -116
- package/lib/plugins/TablePlugin.d.ts +0 -31
- package/lib/plugins/TablePlugin.js +0 -63
- package/lib/plugins/TestRecorderPlugin/index.d.ts +0 -3
- package/lib/plugins/TestRecorderPlugin/index.js +0 -346
- package/lib/plugins/ToolbarPlugin/fontSize.d.ts +0 -9
- package/lib/plugins/ToolbarPlugin/fontSize.js +0 -80
- package/lib/plugins/ToolbarPlugin/index.d.ts +0 -9
- package/lib/plugins/ToolbarPlugin/index.js +0 -500
- package/lib/plugins/ToolbarPlugin/utils.d.ts +0 -26
- package/lib/plugins/ToolbarPlugin/utils.js +0 -243
- package/lib/plugins/TreeViewPlugin/index.d.ts +0 -2
- package/lib/plugins/TreeViewPlugin/index.js +0 -7
- package/lib/plugins/TwitterPlugin/index.d.ts +0 -4
- package/lib/plugins/TwitterPlugin/index.js +0 -20
- package/lib/plugins/TypingPerfPlugin/index.d.ts +0 -2
- package/lib/plugins/TypingPerfPlugin/index.js +0 -93
- package/lib/plugins/YouTubePlugin/index.d.ts +0 -4
- package/lib/plugins/YouTubePlugin/index.js +0 -20
- package/lib/server/validation.d.ts +0 -1
- package/lib/server/validation.js +0 -111
- package/lib/setupEnv.d.ts +0 -2
- package/lib/setupEnv.js +0 -25
- package/lib/themes/CommentEditorTheme.d.ts +0 -4
- package/lib/themes/CommentEditorTheme.js +0 -7
- package/lib/themes/PlaygroundEditorTheme.d.ts +0 -4
- package/lib/themes/PlaygroundEditorTheme.js +0 -120
- package/lib/themes/StickyEditorTheme.d.ts +0 -4
- package/lib/themes/StickyEditorTheme.js +0 -7
- package/lib/tyes.dt.d.ts +0 -12
- package/lib/tyes.dt.js +0 -0
- package/lib/ui/Button.d.ts +0 -12
- package/lib/ui/Button.js +0 -6
- package/lib/ui/ColorPicker.d.ts +0 -14
- package/lib/ui/ColorPicker.js +0 -219
- package/lib/ui/ContentEditable.d.ts +0 -9
- package/lib/ui/ContentEditable.js +0 -6
- package/lib/ui/Dialog.d.ts +0 -10
- package/lib/ui/Dialog.js +0 -8
- package/lib/ui/DropDown.d.ts +0 -18
- package/lib/ui/DropDown.js +0 -133
- package/lib/ui/DropdownColorPicker.d.ts +0 -13
- package/lib/ui/DropdownColorPicker.js +0 -6
- package/lib/ui/EquationEditor.d.ts +0 -8
- package/lib/ui/EquationEditor.js +0 -11
- package/lib/ui/ExcalidrawModal.d.ts +0 -42
- package/lib/ui/ExcalidrawModal.js +0 -103
- package/lib/ui/FileInput.d.ts +0 -10
- package/lib/ui/FileInput.js +0 -5
- package/lib/ui/FlashMessage.d.ts +0 -7
- package/lib/ui/FlashMessage.js +0 -6
- package/lib/ui/ImageResizer.d.ts +0 -17
- package/lib/ui/ImageResizer.js +0 -171
- package/lib/ui/KatexEquationAlterer.d.ts +0 -8
- package/lib/ui/KatexEquationAlterer.js +0 -23
- package/lib/ui/KatexRenderer.d.ts +0 -6
- package/lib/ui/KatexRenderer.js +0 -24
- package/lib/ui/Modal.d.ts +0 -9
- package/lib/ui/Modal.js +0 -48
- package/lib/ui/Select.d.ts +0 -8
- package/lib/ui/Select.js +0 -5
- package/lib/ui/Switch.d.ts +0 -8
- package/lib/ui/Switch.js +0 -6
- package/lib/ui/TextInput.d.ts +0 -13
- package/lib/ui/TextInput.js +0 -7
- package/lib/utils/docSerialization.d.ts +0 -3
- package/lib/utils/docSerialization.js +0 -56
- package/lib/utils/emoji-list.d.ts +0 -20
- package/lib/utils/emoji-list.js +0 -16605
- package/lib/utils/getDOMRangeRect.d.ts +0 -8
- package/lib/utils/getDOMRangeRect.js +0 -22
- package/lib/utils/getSelectedNode.d.ts +0 -2
- package/lib/utils/getSelectedNode.js +0 -24
- package/lib/utils/getThemeSelector.d.ts +0 -2
- package/lib/utils/getThemeSelector.js +0 -10
- package/lib/utils/isMobileWidth.d.ts +0 -7
- package/lib/utils/isMobileWidth.js +0 -7
- package/lib/utils/joinClasses.d.ts +0 -1
- package/lib/utils/joinClasses.js +0 -3
- package/lib/utils/setFloatingElemPosition.d.ts +0 -1
- package/lib/utils/setFloatingElemPosition.js +0 -55
- package/lib/utils/setFloatingElemPositionForLinkEditor.d.ts +0 -1
- package/lib/utils/setFloatingElemPositionForLinkEditor.js +0 -32
- package/lib/utils/swipe.d.ts +0 -4
- package/lib/utils/swipe.js +0 -90
- package/lib/utils/url.d.ts +0 -2
- package/lib/utils/url.js +0 -27
|
@@ -1,907 +0,0 @@
|
|
|
1
|
-
import { EphemeralStore } from 'loro-crdt';
|
|
2
|
-
import { ObservableV2 } from 'lib0/observable';
|
|
3
|
-
import * as bc from 'lib0/broadcastchannel';
|
|
4
|
-
import * as time from 'lib0/time';
|
|
5
|
-
import * as math from 'lib0/math';
|
|
6
|
-
import * as url from 'lib0/url';
|
|
7
|
-
import * as env from 'lib0/environment';
|
|
8
|
-
import { generateClientID, generateRandomClientID } from '../utils/Utils';
|
|
9
|
-
// @todo - this should depend on ephemeral timeout
|
|
10
|
-
const messageReconnectTimeoutMs = 30 * 1000; // 30 seconds
|
|
11
|
-
// Loro message types
|
|
12
|
-
export const messageUpdate = 'update';
|
|
13
|
-
export const messageQuerySnapshot = 'query-snapshot';
|
|
14
|
-
export const messageEphemeral = 'ephemeral';
|
|
15
|
-
export const messageQueryEphemeral = 'query-ephemeral';
|
|
16
|
-
/**
|
|
17
|
-
* Awareness adapter that wraps EphemeralStore to provide awareness-like API
|
|
18
|
-
*/
|
|
19
|
-
class AwarenessAdapter {
|
|
20
|
-
ephemeralStore;
|
|
21
|
-
localClientId;
|
|
22
|
-
eventHandlers = new Map();
|
|
23
|
-
constructor(ephemeralStore, doc) {
|
|
24
|
-
this.ephemeralStore = ephemeralStore;
|
|
25
|
-
// Use the same client ID as the binding for consistency
|
|
26
|
-
this.localClientId = doc ? generateClientID(doc) : generateRandomClientID();
|
|
27
|
-
console.log('🔄 AwarenessAdapter created:', {
|
|
28
|
-
localClientId: this.localClientId,
|
|
29
|
-
docPeerId: doc ? doc.peerId : 'no-doc',
|
|
30
|
-
existingStatesCount: Object.keys(ephemeralStore.getAllStates()).length
|
|
31
|
-
});
|
|
32
|
-
// Subscribe to ephemeral store changes and emit awareness updates
|
|
33
|
-
this.ephemeralStore.subscribe((event) => {
|
|
34
|
-
// Emit update events when ephemeral state changes
|
|
35
|
-
const updateHandlers = this.eventHandlers.get('update') || [];
|
|
36
|
-
updateHandlers.forEach(integrater => integrater());
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
getLocalState() {
|
|
40
|
-
const localKey = this.localClientId.toString();
|
|
41
|
-
try {
|
|
42
|
-
const state = this.ephemeralStore.get(localKey);
|
|
43
|
-
return state ? state : null;
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
console.warn(`[Client] AwarenessAdapter.getLocalState() - ephemeralStore.get() FAILED:`, {
|
|
47
|
-
error: error.message,
|
|
48
|
-
stack: error.stack,
|
|
49
|
-
localKey,
|
|
50
|
-
peerId: this.localClientId,
|
|
51
|
-
storeExists: !!this.ephemeralStore
|
|
52
|
-
});
|
|
53
|
-
throw error;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
getStates() {
|
|
57
|
-
const states = new Map();
|
|
58
|
-
try {
|
|
59
|
-
// Get all states from ephemeral store
|
|
60
|
-
const allStates = this.ephemeralStore.getAllStates();
|
|
61
|
-
// Clean up stale states periodically (very rarely to avoid performance issues)
|
|
62
|
-
if (Math.random() < 0.001) { // ~0.1% chance per call
|
|
63
|
-
this.cleanupStaleStates(allStates);
|
|
64
|
-
}
|
|
65
|
-
// Iterate through all keys and extract user states
|
|
66
|
-
for (const [key, value] of Object.entries(allStates)) {
|
|
67
|
-
// Keys are now direct client ID strings
|
|
68
|
-
const clientId = parseInt(key, 10);
|
|
69
|
-
if (!isNaN(clientId) && value) {
|
|
70
|
-
states.set(clientId, value);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
catch (error) {
|
|
75
|
-
console.warn(`[Client] AwarenessAdapter.getStates() - ERROR:`, error.message);
|
|
76
|
-
// Fallback to just local state
|
|
77
|
-
const localState = this.getLocalState();
|
|
78
|
-
if (localState) {
|
|
79
|
-
states.set(this.localClientId, localState);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return states;
|
|
83
|
-
}
|
|
84
|
-
cleanupStaleStates(allStates) {
|
|
85
|
-
const currentTime = Date.now();
|
|
86
|
-
const staleThreshold = 5 * 60 * 1000; // 5 minutes
|
|
87
|
-
try {
|
|
88
|
-
for (const [key, value] of Object.entries(allStates)) {
|
|
89
|
-
// Keys are now direct client ID strings
|
|
90
|
-
const clientId = parseInt(key, 10);
|
|
91
|
-
if (!isNaN(clientId) && value && typeof value === 'object') {
|
|
92
|
-
const state = value;
|
|
93
|
-
const lastActivity = typeof state.lastActivity === 'number' ? state.lastActivity : 0;
|
|
94
|
-
// Remove states that haven't been active for more than the threshold
|
|
95
|
-
if (typeof lastActivity === 'number' && currentTime - lastActivity > staleThreshold) {
|
|
96
|
-
console.log('Cleaning up stale user state:', key, 'last activity:', new Date(lastActivity).toISOString());
|
|
97
|
-
this.ephemeralStore.delete(key);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
console.warn('Error during stale state cleanup:', error.message);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
setLocalState(state) {
|
|
107
|
-
const localKey = this.localClientId.toString();
|
|
108
|
-
// Add lastActivity timestamp for stale state cleanup
|
|
109
|
-
const stateWithActivity = {
|
|
110
|
-
...state,
|
|
111
|
-
lastActivity: Date.now()
|
|
112
|
-
};
|
|
113
|
-
try {
|
|
114
|
-
this.ephemeralStore.set(localKey, stateWithActivity);
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
console.warn(`[Client] AwarenessAdapter.setLocalState() - ephemeralStore.set() FAILED:`, {
|
|
118
|
-
error: error.message,
|
|
119
|
-
stack: error.stack,
|
|
120
|
-
localKey,
|
|
121
|
-
peerId: this.localClientId,
|
|
122
|
-
stateKeys: Object.keys(state || {}),
|
|
123
|
-
storeExists: !!this.ephemeralStore
|
|
124
|
-
});
|
|
125
|
-
throw error;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
setLocalStateField(field, value) {
|
|
129
|
-
const localState = this.getLocalState() || {
|
|
130
|
-
anchorPos: null,
|
|
131
|
-
awarenessData: {},
|
|
132
|
-
color: '#000000',
|
|
133
|
-
focusPos: null,
|
|
134
|
-
focusing: false,
|
|
135
|
-
name: 'Anonymous',
|
|
136
|
-
};
|
|
137
|
-
localState[field] = value;
|
|
138
|
-
this.setLocalState(localState);
|
|
139
|
-
}
|
|
140
|
-
on(type, cb) {
|
|
141
|
-
if (!this.eventHandlers.has(type)) {
|
|
142
|
-
this.eventHandlers.set(type, []);
|
|
143
|
-
}
|
|
144
|
-
this.eventHandlers.get(type).push(cb);
|
|
145
|
-
}
|
|
146
|
-
off(type, cb) {
|
|
147
|
-
const integrators = this.eventHandlers.get(type);
|
|
148
|
-
if (integrators) {
|
|
149
|
-
const index = integrators.indexOf(cb);
|
|
150
|
-
if (index !== -1) {
|
|
151
|
-
integrators.splice(index, 1);
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// Manual cleanup method for debugging
|
|
156
|
-
forceCleanupStaleStates() {
|
|
157
|
-
try {
|
|
158
|
-
const allStates = this.ephemeralStore.getAllStates();
|
|
159
|
-
console.log('Force cleanup - total keys before:', Object.keys(allStates).length);
|
|
160
|
-
this.cleanupStaleStates(allStates);
|
|
161
|
-
const newStates = this.ephemeralStore.getAllStates();
|
|
162
|
-
console.log('Force cleanup - total keys after:', Object.keys(newStates).length);
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
console.warn('Force cleanup failed:', error.message);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
/**
|
|
170
|
-
* Message integrators for different Loro message types
|
|
171
|
-
*/
|
|
172
|
-
const messageHandlers = {};
|
|
173
|
-
messageHandlers[messageQueryEphemeral] = (provider, message, _emitSynced) => {
|
|
174
|
-
try {
|
|
175
|
-
// Use encodeAll() to encode all ephemeral store data
|
|
176
|
-
const encodedData = provider.ephemeralStore.encodeAll();
|
|
177
|
-
const response = {
|
|
178
|
-
type: 'ephemeral',
|
|
179
|
-
ephemeral: Array.from(encodedData),
|
|
180
|
-
docId: message.docId
|
|
181
|
-
};
|
|
182
|
-
return JSON.stringify(response);
|
|
183
|
-
}
|
|
184
|
-
catch (error) {
|
|
185
|
-
console.warn('Error in messageQueryEphemeral integrater:', error.message);
|
|
186
|
-
return null;
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
messageHandlers[messageEphemeral] = (provider, message, _emitSynced) => {
|
|
190
|
-
try {
|
|
191
|
-
// Validate message data before processing
|
|
192
|
-
if (!message.ephemeral || message.ephemeral.length === 0) {
|
|
193
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - Skipping empty ephemeral data`);
|
|
194
|
-
return null;
|
|
195
|
-
}
|
|
196
|
-
// Reject obviously corrupted data (too small, common corrupt patterns)
|
|
197
|
-
if (message.ephemeral.length < 8) {
|
|
198
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - Rejecting suspiciously small ephemeral data:`, message.ephemeral.length);
|
|
199
|
-
return null;
|
|
200
|
-
}
|
|
201
|
-
// Additional validation - check for specific known bad patterns
|
|
202
|
-
if (message.ephemeral.length === 1) {
|
|
203
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - Rejecting single-byte ephemeral data (likely corrupted):`, message.ephemeral);
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
// Validate ephemeral data format by checking if it starts with reasonable values
|
|
207
|
-
// Ephemeral data should have a structured format - single random bytes are invalid
|
|
208
|
-
const firstBytes = message.ephemeral.slice(0, 4);
|
|
209
|
-
if (firstBytes.every(b => b === 0) || firstBytes.every(b => b === 255)) {
|
|
210
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - Rejecting ephemeral data with suspicious pattern:`, firstBytes);
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
213
|
-
// Apply ephemeral update
|
|
214
|
-
const ephemeralBytes = new Uint8Array(message.ephemeral);
|
|
215
|
-
// Use a try-catch specifically for the apply operation to isolate WASM errors
|
|
216
|
-
try {
|
|
217
|
-
provider.ephemeralStore.apply(ephemeralBytes);
|
|
218
|
-
}
|
|
219
|
-
catch (applyError) {
|
|
220
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - WASM apply() failed:`, {
|
|
221
|
-
error: applyError.message,
|
|
222
|
-
ephemeralLength: ephemeralBytes.length,
|
|
223
|
-
ephemeralSample: Array.from(ephemeralBytes.slice(0, 20))
|
|
224
|
-
});
|
|
225
|
-
// If this is a WASM memory error, don't attempt any more ephemeral operations
|
|
226
|
-
if (applyError.message && applyError.message.includes('memory access out of bounds')) {
|
|
227
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - CRITICAL: WASM memory corruption in apply(). Stopping ephemeral processing.`);
|
|
228
|
-
return null;
|
|
229
|
-
}
|
|
230
|
-
throw applyError; // Re-throw if not a WASM error
|
|
231
|
-
}
|
|
232
|
-
return null;
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
console.warn(`[Client] messageHandlers[messageEphemeral] - ERROR in ephemeral message integrater:`, {
|
|
236
|
-
error: error.message,
|
|
237
|
-
stack: error.stack,
|
|
238
|
-
messageLength: message.ephemeral?.length,
|
|
239
|
-
ephemeralSample: message.ephemeral?.slice(0, 10)
|
|
240
|
-
});
|
|
241
|
-
return null;
|
|
242
|
-
}
|
|
243
|
-
};
|
|
244
|
-
messageHandlers[messageUpdate] = (provider, message, emitSynced) => {
|
|
245
|
-
try {
|
|
246
|
-
// Apply the update to the local document
|
|
247
|
-
const updateBytes = new Uint8Array(message.update);
|
|
248
|
-
// Get document state before applying update for comparison
|
|
249
|
-
// const beforeVersion = provider.doc.version()
|
|
250
|
-
// Import with sender's peerId as origin to mark as remote update
|
|
251
|
-
// We don't know the actual sender's peerId, so use a generic remote identifier
|
|
252
|
-
// The key point is that it's NOT our local peerId
|
|
253
|
-
const importStatus = provider.doc.import(updateBytes);
|
|
254
|
-
const afterVersion = provider.doc.version();
|
|
255
|
-
// Update our last exported version to include the remote changes
|
|
256
|
-
// This ensures we don't re-export remote changes
|
|
257
|
-
provider._lastExportedVersion = afterVersion;
|
|
258
|
-
if (emitSynced && !provider._synced) {
|
|
259
|
-
provider.synced = true;
|
|
260
|
-
}
|
|
261
|
-
return null; // No response needed
|
|
262
|
-
}
|
|
263
|
-
catch (error) {
|
|
264
|
-
console.warn(`❌ [LORO-UPDATE-ERROR] Failed to apply Loro update:`, error);
|
|
265
|
-
return null;
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
/**
|
|
269
|
-
* @param {WebsocketProvider} provider
|
|
270
|
-
* @param {string} reason
|
|
271
|
-
*/
|
|
272
|
-
const permissionDeniedIntegrator = (provider, reason) => console.warn(`Permission denied to access ${provider.url}.\n${reason}`);
|
|
273
|
-
/**
|
|
274
|
-
* Process incoming message (JSON or binary) and return optional response
|
|
275
|
-
*/
|
|
276
|
-
const processMessage = (provider, data, emitSynced) => {
|
|
277
|
-
if (data instanceof ArrayBuffer) {
|
|
278
|
-
try {
|
|
279
|
-
/*
|
|
280
|
-
// Try to decode as UTF-8 string (JSON messages sent as binary)
|
|
281
|
-
const decoder = new TextDecoder()
|
|
282
|
-
const jsonString = decoder.decode(data)
|
|
283
|
-
const message = JSON.parse(jsonString) as LoroWebSocketMessage
|
|
284
|
-
const messageHandler = messageHandlers[message.type]
|
|
285
|
-
if (messageHandler) {
|
|
286
|
-
return messageHandler(provider, message, emitSynced)
|
|
287
|
-
} else {
|
|
288
|
-
console.warn('Unknown message type:', message.type)
|
|
289
|
-
return null
|
|
290
|
-
}
|
|
291
|
-
*/
|
|
292
|
-
// If JSON parsing fails, treat as raw binary Loro update
|
|
293
|
-
const updateBytes = new Uint8Array(data);
|
|
294
|
-
provider.doc.import(updateBytes);
|
|
295
|
-
if (emitSynced && !provider._synced) {
|
|
296
|
-
provider.synced = true;
|
|
297
|
-
}
|
|
298
|
-
return null; // No response needed for binary updates
|
|
299
|
-
}
|
|
300
|
-
catch (error) {
|
|
301
|
-
console.warn('Failed to process binary Loro update:', error);
|
|
302
|
-
return null;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
else if (typeof data === 'string') {
|
|
306
|
-
try {
|
|
307
|
-
const message = JSON.parse(data);
|
|
308
|
-
const messageHandler = messageHandlers[message.type];
|
|
309
|
-
if (messageHandler) {
|
|
310
|
-
return messageHandler(provider, message, emitSynced);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
console.warn('Unknown message type:', message.type);
|
|
314
|
-
return null;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
catch (error) {
|
|
318
|
-
console.warn('Failed to process JSON message:', error);
|
|
319
|
-
return null;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
else if (typeof data === 'object' && data !== null) {
|
|
323
|
-
try {
|
|
324
|
-
const message = data;
|
|
325
|
-
const messageHandler = messageHandlers[message.type];
|
|
326
|
-
if (messageHandler) {
|
|
327
|
-
return messageHandler(provider, message, emitSynced);
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
console.warn('Unknown message type:', message.type);
|
|
331
|
-
return null;
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
catch (error) {
|
|
335
|
-
console.warn('Failed to process object message:', error);
|
|
336
|
-
return null;
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
console.warn('Unknown message format:', typeof data);
|
|
340
|
-
return null;
|
|
341
|
-
};
|
|
342
|
-
/**
|
|
343
|
-
* Outsource this function so that a new websocket connection is created immediately.
|
|
344
|
-
* I suspect that the `ws.onclose` event is not always fired if there are network issues.
|
|
345
|
-
*
|
|
346
|
-
* @param {WebsocketProvider} provider
|
|
347
|
-
* @param {WebSocket} ws
|
|
348
|
-
* @param {CloseEvent | null} event
|
|
349
|
-
*/
|
|
350
|
-
const closeWebsocketConnection = (provider, ws, event) => {
|
|
351
|
-
if (ws === provider.ws) {
|
|
352
|
-
provider.emit('connection-close', [event, provider]);
|
|
353
|
-
provider.ws = null;
|
|
354
|
-
ws.close();
|
|
355
|
-
provider.wsconnecting = false;
|
|
356
|
-
if (provider.wsconnected) {
|
|
357
|
-
provider.wsconnected = false;
|
|
358
|
-
provider.synced = false;
|
|
359
|
-
// Clear local ephemeral state on disconnect
|
|
360
|
-
provider.ephemeralStore.delete('presence');
|
|
361
|
-
provider.ephemeralStore.delete('cursor');
|
|
362
|
-
// Clear user-specific state
|
|
363
|
-
try {
|
|
364
|
-
const peerId = generateClientID(provider.doc);
|
|
365
|
-
const userKey = peerId.toString();
|
|
366
|
-
provider.ephemeralStore.delete(userKey);
|
|
367
|
-
console.log('Disconnect cleanup: removed user key:', userKey);
|
|
368
|
-
}
|
|
369
|
-
catch (error) {
|
|
370
|
-
console.warn('Disconnect cleanup failed:', error.message);
|
|
371
|
-
}
|
|
372
|
-
provider.emit('status', [{
|
|
373
|
-
status: 'disconnected'
|
|
374
|
-
}]);
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
provider.wsUnsuccessfulReconnects++;
|
|
378
|
-
}
|
|
379
|
-
// Start with no reconnect timeout and increase timeout by
|
|
380
|
-
// using exponential backoff starting with 100ms
|
|
381
|
-
setTimeout(setupWS, math.min(math.pow(2, provider.wsUnsuccessfulReconnects) * 100, provider.maxBackoffTime), provider);
|
|
382
|
-
}
|
|
383
|
-
};
|
|
384
|
-
const sendMessage = (ws, message) => {
|
|
385
|
-
if (ws && ws.readyState === ws.OPEN) {
|
|
386
|
-
try {
|
|
387
|
-
const m = JSON.stringify(message);
|
|
388
|
-
ws.send(m);
|
|
389
|
-
}
|
|
390
|
-
catch (error) {
|
|
391
|
-
console.warn('Failed to send message over WebSocket:', error);
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
else {
|
|
395
|
-
console.warn('WebSocket not open, cannot send message');
|
|
396
|
-
}
|
|
397
|
-
};
|
|
398
|
-
/**
|
|
399
|
-
* @param {WebsocketProvider} provider
|
|
400
|
-
*/
|
|
401
|
-
const setupWS = (provider) => {
|
|
402
|
-
if (provider.shouldConnect && provider.ws === null) {
|
|
403
|
-
const ws = new provider._WS(provider.url, provider.protocols);
|
|
404
|
-
ws.binaryType = 'arraybuffer';
|
|
405
|
-
provider.ws = ws;
|
|
406
|
-
provider.wsconnecting = true;
|
|
407
|
-
provider.wsconnected = false;
|
|
408
|
-
provider.synced = false;
|
|
409
|
-
ws.onmessage = (event) => {
|
|
410
|
-
provider.wsLastMessageReceived = time.getUnixTime();
|
|
411
|
-
const response = processMessage(provider, event.data, true);
|
|
412
|
-
if (response) {
|
|
413
|
-
// TODO
|
|
414
|
-
// sendMessage(ws, response)
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
ws.onerror = (event) => {
|
|
418
|
-
provider.emit('connection-error', [event, provider]);
|
|
419
|
-
};
|
|
420
|
-
ws.onclose = (event) => {
|
|
421
|
-
closeWebsocketConnection(provider, ws, event);
|
|
422
|
-
};
|
|
423
|
-
ws.onopen = () => {
|
|
424
|
-
provider.wsLastMessageReceived = time.getUnixTime();
|
|
425
|
-
provider.wsconnecting = false;
|
|
426
|
-
provider.wsconnected = true;
|
|
427
|
-
provider.wsUnsuccessfulReconnects = 0;
|
|
428
|
-
provider.emit('status', [{
|
|
429
|
-
status: 'connected'
|
|
430
|
-
}]);
|
|
431
|
-
console.log('✅ WebSocket connection established, requesting initial data');
|
|
432
|
-
// Since we're in onopen, we know the WebSocket is ready
|
|
433
|
-
// Use sendMessage directly to avoid any race conditions
|
|
434
|
-
// Only request snapshot if we haven't already loaded it
|
|
435
|
-
if (!provider.snapshotLoaded) {
|
|
436
|
-
// First request a snapshot to get the initial document state
|
|
437
|
-
const requestId = Math.random().toString(36).substr(2, 9);
|
|
438
|
-
const clientId = generateClientID(provider.doc).toString();
|
|
439
|
-
const snapshotRequest = {
|
|
440
|
-
type: 'query-snapshot',
|
|
441
|
-
docId: provider.docId,
|
|
442
|
-
clientId: clientId
|
|
443
|
-
};
|
|
444
|
-
console.log(`🔄 Requesting initial snapshot from server (ID: ${requestId}, clientId: ${clientId}):`, snapshotRequest);
|
|
445
|
-
console.log(`🔄 Provider instance ID: ${provider.wsServerUrl}/${provider.docId}, snapshotLoaded: ${provider.snapshotLoaded}`);
|
|
446
|
-
sendMessage(ws, snapshotRequest);
|
|
447
|
-
}
|
|
448
|
-
else {
|
|
449
|
-
console.log('📸 Snapshot already loaded, skipping request');
|
|
450
|
-
}
|
|
451
|
-
// Then request initial ephemeral state from server
|
|
452
|
-
const clientId = generateClientID(provider.doc).toString();
|
|
453
|
-
const ephemeralRequest = {
|
|
454
|
-
type: 'query-ephemeral',
|
|
455
|
-
docId: provider.docId,
|
|
456
|
-
clientId: clientId
|
|
457
|
-
};
|
|
458
|
-
sendMessage(ws, ephemeralRequest);
|
|
459
|
-
// broadcast local ephemeral state if any
|
|
460
|
-
const localState = provider.ephemeralStore.getAllStates();
|
|
461
|
-
if (Object.keys(localState).length > 0) {
|
|
462
|
-
try {
|
|
463
|
-
// Use encodeAll() to encode all ephemeral store data
|
|
464
|
-
const encodedData = provider.ephemeralStore.encodeAll();
|
|
465
|
-
const ephemeralMessage = {
|
|
466
|
-
type: 'ephemeral',
|
|
467
|
-
ephemeral: Array.from(encodedData),
|
|
468
|
-
docId: provider.docId
|
|
469
|
-
};
|
|
470
|
-
sendMessage(ws, ephemeralMessage);
|
|
471
|
-
}
|
|
472
|
-
catch (error) {
|
|
473
|
-
console.warn(`[Client] setupWS - MAJOR ERROR in ephemeral process:`, {
|
|
474
|
-
error: error.message,
|
|
475
|
-
stack: error.stack,
|
|
476
|
-
localStateKeys: Object.keys(localState),
|
|
477
|
-
storeExists: !!provider.ephemeralStore
|
|
478
|
-
});
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
};
|
|
482
|
-
provider.emit('status', [{
|
|
483
|
-
status: 'connecting'
|
|
484
|
-
}]);
|
|
485
|
-
}
|
|
486
|
-
};
|
|
487
|
-
/**
|
|
488
|
-
* Broadcast JSON message to WebSocket and BroadcastChannel
|
|
489
|
-
*/
|
|
490
|
-
const broadcastMessage = (provider, message) => {
|
|
491
|
-
const ws = provider.ws;
|
|
492
|
-
if (provider.wsconnected && ws && ws.readyState === ws.OPEN) {
|
|
493
|
-
sendMessage(ws, message);
|
|
494
|
-
}
|
|
495
|
-
else {
|
|
496
|
-
console.log('❌ [BROADCAST] WebSocket not ready for sending');
|
|
497
|
-
}
|
|
498
|
-
if (provider.bcconnected) {
|
|
499
|
-
bc.publish(provider.bcChannel, JSON.stringify(message), provider);
|
|
500
|
-
}
|
|
501
|
-
else {
|
|
502
|
-
console.log('📻 [BROADCAST] BroadcastChannel not connected');
|
|
503
|
-
}
|
|
504
|
-
};
|
|
505
|
-
/**
|
|
506
|
-
* Websocket Provider for Loro. Creates a websocket connection to sync the shared document.
|
|
507
|
-
* The document name is attached to the provided url. I.e. the following example
|
|
508
|
-
* creates a websocket connection to http://localhost:1235/my-document-name
|
|
509
|
-
*/
|
|
510
|
-
export class WebsocketProvider extends ObservableV2 {
|
|
511
|
-
static globalEphemeralStore = null;
|
|
512
|
-
wsServerUrl = '';
|
|
513
|
-
docId = '';
|
|
514
|
-
doc = null;
|
|
515
|
-
_WS = null;
|
|
516
|
-
protocols = [];
|
|
517
|
-
params = {};
|
|
518
|
-
ephemeralStore = null;
|
|
519
|
-
awareness = null;
|
|
520
|
-
ws = null;
|
|
521
|
-
wsconnected = false;
|
|
522
|
-
wsconnecting = false;
|
|
523
|
-
bcconnected = false;
|
|
524
|
-
disableBc = false;
|
|
525
|
-
bcChannel = '';
|
|
526
|
-
maxBackoffTime = 2500;
|
|
527
|
-
wsUnsuccessfulReconnects = 0;
|
|
528
|
-
messageHandlers = [];
|
|
529
|
-
_synced = false;
|
|
530
|
-
wsLastMessageReceived = 0;
|
|
531
|
-
shouldConnect = false;
|
|
532
|
-
snapshotLoaded = false;
|
|
533
|
-
_checkInterval = null;
|
|
534
|
-
_resyncInterval = null;
|
|
535
|
-
_updateHandler = null;
|
|
536
|
-
_ephemeralUpdateIntegrator = null;
|
|
537
|
-
_exitIntegrator = null;
|
|
538
|
-
_bcSubscriber = null;
|
|
539
|
-
_lastExportedVersion = null; // Track last exported version for incremental updates
|
|
540
|
-
/**
|
|
541
|
-
* @param {string} wsServerUrl
|
|
542
|
-
* @param {string} docId
|
|
543
|
-
* @param {LoroDoc} doc
|
|
544
|
-
* @param {object} opts
|
|
545
|
-
* @param {boolean} [opts.connect]
|
|
546
|
-
* @param {EphemeralStore} [opts.ephemeralStore]
|
|
547
|
-
* @param {Object<string,string>} [opts.params] specify url parameters
|
|
548
|
-
* @param {Array<string>} [opts.protocols] specify websocket protocols
|
|
549
|
-
* @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionally provide a WebSocket polyfill
|
|
550
|
-
* @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds
|
|
551
|
-
* @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff)
|
|
552
|
-
* @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication
|
|
553
|
-
*/
|
|
554
|
-
constructor(wsServerUrl, docId, doc, { connect = true, ephemeralStore = undefined, params = {}, protocols = [], WebSocketPolyfill = WebSocket, resyncInterval = -1, maxBackoffTime = 2500, disableBc = false } = {}) {
|
|
555
|
-
super();
|
|
556
|
-
// ensure that serverUrl does not end with /
|
|
557
|
-
while (wsServerUrl[wsServerUrl.length - 1] === '/') {
|
|
558
|
-
wsServerUrl = wsServerUrl.slice(0, wsServerUrl.length - 1);
|
|
559
|
-
}
|
|
560
|
-
this.wsServerUrl = wsServerUrl;
|
|
561
|
-
this.bcChannel = wsServerUrl + '/' + docId;
|
|
562
|
-
this.maxBackoffTime = maxBackoffTime;
|
|
563
|
-
/**
|
|
564
|
-
* The specified url parameters. This can be safely updated. The changed parameters will be used
|
|
565
|
-
* when a new connection is established.
|
|
566
|
-
* @type {Object<string,string>}
|
|
567
|
-
*/
|
|
568
|
-
this.params = params;
|
|
569
|
-
this.protocols = protocols;
|
|
570
|
-
this.docId = docId;
|
|
571
|
-
this.doc = doc;
|
|
572
|
-
this._WS = WebSocketPolyfill;
|
|
573
|
-
// Create or reuse persistent ephemeral store for the entire user session
|
|
574
|
-
try {
|
|
575
|
-
if (ephemeralStore) {
|
|
576
|
-
// Use provided ephemeral store (already persistent)
|
|
577
|
-
this.ephemeralStore = ephemeralStore;
|
|
578
|
-
}
|
|
579
|
-
else {
|
|
580
|
-
// Create or reuse global ephemeral store for session persistence
|
|
581
|
-
if (!WebsocketProvider.globalEphemeralStore) {
|
|
582
|
-
WebsocketProvider.globalEphemeralStore = new EphemeralStore(300000); // 5 minute timeout
|
|
583
|
-
console.log('🆕 Created new global EphemeralStore');
|
|
584
|
-
}
|
|
585
|
-
else {
|
|
586
|
-
console.log('♻️ Reusing existing global EphemeralStore - cleaning up stale user states');
|
|
587
|
-
// Clean up all existing user states when reusing store to prevent accumulation
|
|
588
|
-
const allStates = WebsocketProvider.globalEphemeralStore.getAllStates();
|
|
589
|
-
Object.keys(allStates).forEach(key => {
|
|
590
|
-
// Keys are now direct client ID strings, check if it's a valid client ID
|
|
591
|
-
const clientId = parseInt(key, 10);
|
|
592
|
-
if (!isNaN(clientId)) {
|
|
593
|
-
WebsocketProvider.globalEphemeralStore.delete(key);
|
|
594
|
-
console.log('🧹 Cleaned up stale user state:', key);
|
|
595
|
-
}
|
|
596
|
-
});
|
|
597
|
-
}
|
|
598
|
-
this.ephemeralStore = WebsocketProvider.globalEphemeralStore;
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
catch (error) {
|
|
602
|
-
console.warn(`[Client] WebsocketProvider constructor - ERROR setting up EphemeralStore:`, {
|
|
603
|
-
error: error.message,
|
|
604
|
-
stack: error.stack,
|
|
605
|
-
docId
|
|
606
|
-
});
|
|
607
|
-
throw error;
|
|
608
|
-
}
|
|
609
|
-
// Create awareness adapter that wraps ephemeral store
|
|
610
|
-
this.awareness = new AwarenessAdapter(this.ephemeralStore, this.doc);
|
|
611
|
-
this.wsconnected = false;
|
|
612
|
-
this.wsconnecting = false;
|
|
613
|
-
this.bcconnected = false;
|
|
614
|
-
this.disableBc = disableBc;
|
|
615
|
-
this.wsUnsuccessfulReconnects = 0;
|
|
616
|
-
/**
|
|
617
|
-
* @type {boolean}
|
|
618
|
-
*/
|
|
619
|
-
this._synced = false;
|
|
620
|
-
/**
|
|
621
|
-
* @type {WebSocket?}
|
|
622
|
-
*/
|
|
623
|
-
this.ws = null;
|
|
624
|
-
this.wsLastMessageReceived = 0;
|
|
625
|
-
/**
|
|
626
|
-
* Whether to connect to other peers or not
|
|
627
|
-
* @type {boolean}
|
|
628
|
-
*/
|
|
629
|
-
this.shouldConnect = connect;
|
|
630
|
-
/**
|
|
631
|
-
* @type {number}
|
|
632
|
-
*/
|
|
633
|
-
this._resyncInterval = 0;
|
|
634
|
-
if (resyncInterval > 0) {
|
|
635
|
-
this._resyncInterval = /** @type {any} */ (setInterval(() => {
|
|
636
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
637
|
-
// Request fresh ephemeral state from server with client ID
|
|
638
|
-
const clientId = generateClientID(this.doc).toString();
|
|
639
|
-
const queryMessage = {
|
|
640
|
-
type: 'query-ephemeral',
|
|
641
|
-
docId: this.docId,
|
|
642
|
-
clientId: clientId
|
|
643
|
-
};
|
|
644
|
-
sendMessage(this.ws, queryMessage);
|
|
645
|
-
}
|
|
646
|
-
}, resyncInterval));
|
|
647
|
-
}
|
|
648
|
-
/**
|
|
649
|
-
* @param {string | object} data
|
|
650
|
-
* @param {any} origin
|
|
651
|
-
*/
|
|
652
|
-
this._bcSubscriber = (data, origin) => {
|
|
653
|
-
if (origin !== this) {
|
|
654
|
-
const response = processMessage(this, data, false);
|
|
655
|
-
if (response) {
|
|
656
|
-
bc.publish(this.bcChannel, response, this);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
};
|
|
660
|
-
/**
|
|
661
|
-
* Listens to Loro Loro updates and sends them to remote peers (ws and broadcastchannel)
|
|
662
|
-
* @param {Uint8Array} update
|
|
663
|
-
* @param {any} origin
|
|
664
|
-
*/
|
|
665
|
-
this._updateHandler = (update) => {
|
|
666
|
-
// This integrater is only called for local changes that need to be broadcast
|
|
667
|
-
const updateMessage = {
|
|
668
|
-
type: 'update',
|
|
669
|
-
update: Array.from(update),
|
|
670
|
-
docId: this.docId
|
|
671
|
-
};
|
|
672
|
-
broadcastMessage(this, updateMessage);
|
|
673
|
-
};
|
|
674
|
-
// Document update integrater - called when Loro emits document change events
|
|
675
|
-
/**
|
|
676
|
-
* @param {EphemeralStoreEvent} event - EphemeralStoreEvent with added, updated, removed arrays
|
|
677
|
-
*/
|
|
678
|
-
this._ephemeralUpdateIntegrator = (event) => {
|
|
679
|
-
// Only broadcast if there are actual changes
|
|
680
|
-
if (event.added.length > 0 || event.updated.length > 0 || event.removed.length > 0) {
|
|
681
|
-
try {
|
|
682
|
-
// Use encodeAll() to encode all ephemeral store data
|
|
683
|
-
const encodedData = this.ephemeralStore.encodeAll();
|
|
684
|
-
const ephemeralMessage = {
|
|
685
|
-
type: 'ephemeral',
|
|
686
|
-
ephemeral: Array.from(encodedData),
|
|
687
|
-
docId: this.docId
|
|
688
|
-
};
|
|
689
|
-
broadcastMessage(this, ephemeralMessage);
|
|
690
|
-
}
|
|
691
|
-
catch (error) {
|
|
692
|
-
console.warn(`[Client] _ephemeralUpdateIntegrator - ERROR:`, error.message);
|
|
693
|
-
// Fallback: skip this update rather than crash
|
|
694
|
-
}
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
this._exitIntegrator = () => {
|
|
698
|
-
// Clear only our local ephemeral state on exit, don't destroy the global store
|
|
699
|
-
if (this.ephemeralStore && this.awareness) {
|
|
700
|
-
try {
|
|
701
|
-
const peerId = generateClientID(this.doc);
|
|
702
|
-
const userKey = peerId.toString();
|
|
703
|
-
this.ephemeralStore.delete(userKey);
|
|
704
|
-
}
|
|
705
|
-
catch (error) {
|
|
706
|
-
console.warn(`[Client] Process exit - Could not clear user state:`, error.message);
|
|
707
|
-
}
|
|
708
|
-
}
|
|
709
|
-
};
|
|
710
|
-
if (env.isNode && typeof process !== 'undefined') {
|
|
711
|
-
process.on('exit', this._exitIntegrator);
|
|
712
|
-
}
|
|
713
|
-
this.ephemeralStore.subscribe(this._ephemeralUpdateIntegrator);
|
|
714
|
-
// Initialize the last exported version to current document version
|
|
715
|
-
this._lastExportedVersion = this.doc.version();
|
|
716
|
-
// Use Loro's native event system to listen for document changes
|
|
717
|
-
try {
|
|
718
|
-
/*
|
|
719
|
-
this.doc.subscribe((event: LoroEventBatch) => {
|
|
720
|
-
try {
|
|
721
|
-
const afterCommitVersion = this.doc.version()
|
|
722
|
-
const update = this.doc.export({
|
|
723
|
-
mode: 'update',
|
|
724
|
-
from: this._lastExportedVersion
|
|
725
|
-
});
|
|
726
|
-
|
|
727
|
-
if (update.length > 0) {
|
|
728
|
-
this._updateHandler(update);
|
|
729
|
-
// Update the last exported version to current version
|
|
730
|
-
this._lastExportedVersion = afterCommitVersion
|
|
731
|
-
} else {
|
|
732
|
-
console.warn(`[WEBSOCKET-PROVIDER] No incremental update available - versions might be the same`);
|
|
733
|
-
}
|
|
734
|
-
} catch (error) {
|
|
735
|
-
console.warn(`[WEBSOCKET-PROVIDER] Error exporting incremental update:`, error);
|
|
736
|
-
}
|
|
737
|
-
});
|
|
738
|
-
*/
|
|
739
|
-
this.doc.subscribeLocalUpdates((update) => {
|
|
740
|
-
try {
|
|
741
|
-
this._updateHandler(update);
|
|
742
|
-
}
|
|
743
|
-
catch (error) {
|
|
744
|
-
console.warn(`[WEBSOCKET-PROVIDER] Error exporting incremental update:`, error);
|
|
745
|
-
}
|
|
746
|
-
});
|
|
747
|
-
}
|
|
748
|
-
catch (error) {
|
|
749
|
-
console.warn(`[Client] ERROR setting up Loro document subscription:`, error);
|
|
750
|
-
}
|
|
751
|
-
this._checkInterval = (setInterval(() => {
|
|
752
|
-
if (this.wsconnected &&
|
|
753
|
-
messageReconnectTimeoutMs <
|
|
754
|
-
time.getUnixTime() - this.wsLastMessageReceived) {
|
|
755
|
-
// no message received in a long time - not even your own ephemeral
|
|
756
|
-
// updates (which are updated every 15 seconds)
|
|
757
|
-
closeWebsocketConnection(this, this.ws, null);
|
|
758
|
-
}
|
|
759
|
-
}, messageReconnectTimeoutMs / 10));
|
|
760
|
-
if (connect) {
|
|
761
|
-
this.connect();
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
get url() {
|
|
765
|
-
const encodedParams = url.encodeQueryParams(this.params);
|
|
766
|
-
return this.wsServerUrl + '/' + this.docId +
|
|
767
|
-
(encodedParams.length === 0 ? '' : '?' + encodedParams);
|
|
768
|
-
}
|
|
769
|
-
/**
|
|
770
|
-
* @type {boolean}
|
|
771
|
-
*/
|
|
772
|
-
get synced() {
|
|
773
|
-
return this._synced;
|
|
774
|
-
}
|
|
775
|
-
set synced(state) {
|
|
776
|
-
if (this._synced !== state) {
|
|
777
|
-
this._synced = state;
|
|
778
|
-
super.emit('synced', [state]);
|
|
779
|
-
super.emit('sync', [state]);
|
|
780
|
-
}
|
|
781
|
-
}
|
|
782
|
-
destroy() {
|
|
783
|
-
if (this._resyncInterval !== 0) {
|
|
784
|
-
clearInterval(this._resyncInterval);
|
|
785
|
-
}
|
|
786
|
-
clearInterval(this._checkInterval);
|
|
787
|
-
this.disconnect();
|
|
788
|
-
if (env.isNode && typeof process !== 'undefined') {
|
|
789
|
-
process.off('exit', this._exitIntegrator);
|
|
790
|
-
}
|
|
791
|
-
// DON'T destroy the ephemeral store - it's shared across the session
|
|
792
|
-
// Only clear our local state from it
|
|
793
|
-
if (this.ephemeralStore && this.awareness) {
|
|
794
|
-
try {
|
|
795
|
-
const peerId = generateClientID(this.doc);
|
|
796
|
-
const userKey = peerId.toString();
|
|
797
|
-
this.ephemeralStore.delete(userKey);
|
|
798
|
-
}
|
|
799
|
-
catch (error) {
|
|
800
|
-
console.warn(`[Client] WebsocketProvider.destroy - Could not clear user state:`, error.message);
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
// Note: LoroDoc doesn't have event listeners to remove
|
|
804
|
-
super.destroy();
|
|
805
|
-
}
|
|
806
|
-
connectBc() {
|
|
807
|
-
if (this.disableBc) {
|
|
808
|
-
return;
|
|
809
|
-
}
|
|
810
|
-
if (!this.bcconnected) {
|
|
811
|
-
bc.subscribe(this.bcChannel, this._bcSubscriber);
|
|
812
|
-
this.bcconnected = true;
|
|
813
|
-
}
|
|
814
|
-
// Note: BroadcastChannel snapshot sharing removed - only WebSocket queries supported
|
|
815
|
-
// Query ephemeral state from other tabs
|
|
816
|
-
const clientId = generateClientID(this.doc).toString();
|
|
817
|
-
const queryMessage = {
|
|
818
|
-
type: 'query-ephemeral',
|
|
819
|
-
docId: this.docId,
|
|
820
|
-
clientId: clientId
|
|
821
|
-
};
|
|
822
|
-
bc.publish(this.bcChannel, JSON.stringify(queryMessage), this);
|
|
823
|
-
// Broadcast local ephemeral state using container approach
|
|
824
|
-
const localState = this.ephemeralStore.getAllStates();
|
|
825
|
-
if (Object.keys(localState).length > 0) {
|
|
826
|
-
try {
|
|
827
|
-
// Use encodeAll() to encode all ephemeral store data
|
|
828
|
-
const encodedData = this.ephemeralStore.encodeAll();
|
|
829
|
-
const ephemeralMessage = {
|
|
830
|
-
type: 'ephemeral',
|
|
831
|
-
ephemeral: Array.from(encodedData),
|
|
832
|
-
docId: this.docId
|
|
833
|
-
};
|
|
834
|
-
bc.publish(this.bcChannel, JSON.stringify(ephemeralMessage), this);
|
|
835
|
-
}
|
|
836
|
-
catch (error) {
|
|
837
|
-
console.warn('Error broadcasting ephemeral state in connectBc:', error.message);
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
disconnectBc() {
|
|
842
|
-
// broadcast message with local ephemeral state cleared (indicating disconnect)
|
|
843
|
-
this.ephemeralStore.delete('presence');
|
|
844
|
-
this.ephemeralStore.delete('cursor');
|
|
845
|
-
// Clear user-specific state
|
|
846
|
-
try {
|
|
847
|
-
const peerId = generateClientID(this.doc);
|
|
848
|
-
const userKey = peerId.toString();
|
|
849
|
-
this.ephemeralStore.delete(userKey);
|
|
850
|
-
console.log('Broadcast disconnect cleanup: removed user key:', userKey);
|
|
851
|
-
}
|
|
852
|
-
catch (error) {
|
|
853
|
-
console.warn('Broadcast disconnect cleanup failed:', error.message);
|
|
854
|
-
}
|
|
855
|
-
try {
|
|
856
|
-
// Use encodeAll() to encode ephemeral store data for disconnect broadcast
|
|
857
|
-
const encodedData = this.ephemeralStore.encodeAll();
|
|
858
|
-
const ephemeralMessage = {
|
|
859
|
-
type: 'ephemeral',
|
|
860
|
-
ephemeral: Array.from(encodedData),
|
|
861
|
-
docId: this.docId
|
|
862
|
-
};
|
|
863
|
-
broadcastMessage(this, ephemeralMessage);
|
|
864
|
-
}
|
|
865
|
-
catch (error) {
|
|
866
|
-
console.warn('Error broadcasting disconnect in disconnectBc:', error.message);
|
|
867
|
-
}
|
|
868
|
-
if (this.bcconnected) {
|
|
869
|
-
bc.unsubscribe(this.bcChannel, this._bcSubscriber);
|
|
870
|
-
this.bcconnected = false;
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
disconnect() {
|
|
874
|
-
this.shouldConnect = false;
|
|
875
|
-
this.disconnectBc();
|
|
876
|
-
if (this.ws !== null) {
|
|
877
|
-
closeWebsocketConnection(this, this.ws, null);
|
|
878
|
-
}
|
|
879
|
-
}
|
|
880
|
-
connect() {
|
|
881
|
-
this.shouldConnect = true;
|
|
882
|
-
if (!this.wsconnected && this.ws === null) {
|
|
883
|
-
setupWS(this);
|
|
884
|
-
this.connectBc();
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
/**
|
|
888
|
-
* Manually send a Loro document update to connected peers
|
|
889
|
-
* Call this method after making changes to the LoroDoc
|
|
890
|
-
* @param {Uint8Array} update The update bytes from LoroDoc
|
|
891
|
-
*/
|
|
892
|
-
sendUpdate(update) {
|
|
893
|
-
this._updateHandler(update, null);
|
|
894
|
-
}
|
|
895
|
-
/**
|
|
896
|
-
* Force cleanup of stale ephemeral states (for debugging)
|
|
897
|
-
* Removes user states that haven't been active for more than 5 minutes
|
|
898
|
-
*/
|
|
899
|
-
cleanupStaleStates() {
|
|
900
|
-
if (this.awareness && typeof this.awareness.forceCleanupStaleStates === 'function') {
|
|
901
|
-
this.awareness.forceCleanupStaleStates();
|
|
902
|
-
}
|
|
903
|
-
else {
|
|
904
|
-
console.warn('Cleanup method not available on awareness provider');
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
}
|