kritzel-stencil 0.0.156 → 0.0.158
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/cjs/app-globals-V2Kpy_OQ.js +8 -0
- package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
- package/dist/cjs/default-text-tool.config-BySzvIox.js +31267 -0
- package/dist/cjs/default-text-tool.config-BySzvIox.js.map +1 -0
- package/dist/cjs/index-Cj__YTlG.js +1650 -0
- package/dist/cjs/index-Cj__YTlG.js.map +1 -0
- package/dist/cjs/index.cjs.js +1465 -0
- package/dist/cjs/index.cjs.js.map +1 -0
- package/dist/cjs/kritzel-brush-style.cjs.entry.js +32 -0
- package/dist/cjs/kritzel-brush-style.entry.cjs.js.map +1 -0
- package/dist/cjs/kritzel-color_22.cjs.entry.js +20974 -0
- package/dist/cjs/loader.cjs.js +14 -0
- package/dist/cjs/loader.cjs.js.map +1 -0
- package/dist/cjs/stencil.cjs.js +26 -0
- package/dist/cjs/stencil.cjs.js.map +1 -0
- package/dist/collection/classes/core/core.class.js +470 -0
- package/dist/collection/classes/core/core.class.js.map +1 -0
- package/dist/collection/classes/core/reviver.class.js +71 -0
- package/dist/collection/classes/core/reviver.class.js.map +1 -0
- package/dist/collection/classes/core/store.class.js +72 -0
- package/dist/collection/classes/core/store.class.js.map +1 -0
- package/dist/collection/classes/core/viewport.class.js +190 -0
- package/dist/collection/classes/core/viewport.class.js.map +1 -0
- package/dist/collection/classes/core/workspace.class.js +41 -0
- package/dist/collection/classes/core/workspace.class.js.map +1 -0
- package/dist/collection/classes/handlers/base.handler.js +8 -0
- package/dist/collection/classes/handlers/base.handler.js.map +1 -0
- package/dist/collection/classes/handlers/context-menu.handler.js +62 -0
- package/dist/collection/classes/handlers/context-menu.handler.js.map +1 -0
- package/dist/collection/classes/handlers/hover.handler.js +19 -0
- package/dist/collection/classes/handlers/hover.handler.js.map +1 -0
- package/dist/collection/classes/handlers/key.handler.js +58 -0
- package/dist/collection/classes/handlers/key.handler.js.map +1 -0
- package/dist/collection/classes/handlers/move.handler.js +149 -0
- package/dist/collection/classes/handlers/move.handler.js.map +1 -0
- package/dist/collection/classes/handlers/resize.handler.js +184 -0
- package/dist/collection/classes/handlers/resize.handler.js.map +1 -0
- package/dist/collection/classes/handlers/rotation.handler.js +116 -0
- package/dist/collection/classes/handlers/rotation.handler.js.map +1 -0
- package/dist/collection/classes/handlers/selection.handler.js +246 -0
- package/dist/collection/classes/handlers/selection.handler.js.map +1 -0
- package/dist/collection/classes/objects/base-object.class.js +232 -0
- package/dist/collection/classes/objects/base-object.class.js.map +1 -0
- package/dist/collection/classes/objects/custom-element.class.js +62 -0
- package/dist/collection/classes/objects/custom-element.class.js.map +1 -0
- package/dist/collection/classes/objects/image.class.js +56 -0
- package/dist/collection/classes/objects/image.class.js.map +1 -0
- package/dist/collection/classes/objects/path.class.js +284 -0
- package/dist/collection/classes/objects/path.class.js.map +1 -0
- package/dist/collection/classes/objects/selection-box.class.js +19 -0
- package/dist/collection/classes/objects/selection-box.class.js.map +1 -0
- package/dist/collection/classes/objects/selection-group.class.js +226 -0
- package/dist/collection/classes/objects/selection-group.class.js.map +1 -0
- package/dist/collection/classes/objects/text.class.js +261 -0
- package/dist/collection/classes/objects/text.class.js.map +1 -0
- package/dist/collection/classes/providers/broadcast-sync-provider.class.js +93 -0
- package/dist/collection/classes/providers/broadcast-sync-provider.class.js.map +1 -0
- package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js +232 -0
- package/dist/collection/classes/providers/hocuspocus-sync-provider.class.js.map +1 -0
- package/dist/collection/classes/providers/indexeddb-sync-provider.class.js +35 -0
- package/dist/collection/classes/providers/indexeddb-sync-provider.class.js.map +1 -0
- package/dist/collection/classes/providers/websocket-sync-provider.class.js +89 -0
- package/dist/collection/classes/providers/websocket-sync-provider.class.js.map +1 -0
- package/dist/{stencil/icon-registry.class-BtT8riKh.js → collection/classes/registries/icon-registry.class.js} +2 -6
- package/dist/collection/classes/registries/icon-registry.class.js.map +1 -0
- package/dist/collection/classes/registries/tool.registry.js +18 -0
- package/dist/collection/classes/registries/tool.registry.js.map +1 -0
- package/dist/collection/classes/structures/app-state-map.structure.js +189 -0
- package/dist/collection/classes/structures/app-state-map.structure.js.map +1 -0
- package/dist/collection/classes/structures/object-map.structure.js +328 -0
- package/dist/collection/classes/structures/object-map.structure.js.map +1 -0
- package/dist/collection/classes/structures/quadtree.structure.js +113 -0
- package/dist/collection/classes/structures/quadtree.structure.js.map +1 -0
- package/dist/collection/classes/tools/base-tool.class.js +38 -0
- package/dist/collection/classes/tools/base-tool.class.js.map +1 -0
- package/dist/collection/classes/tools/brush-tool.class.js +133 -0
- package/dist/collection/classes/tools/brush-tool.class.js.map +1 -0
- package/dist/collection/classes/tools/eraser-tool.class.js +85 -0
- package/dist/collection/classes/tools/eraser-tool.class.js.map +1 -0
- package/dist/collection/classes/tools/image-tool.class.js +83 -0
- package/dist/collection/classes/tools/image-tool.class.js.map +1 -0
- package/dist/collection/classes/tools/selection-tool.class.js +164 -0
- package/dist/collection/classes/tools/selection-tool.class.js.map +1 -0
- package/dist/collection/classes/tools/text-tool.class.js +108 -0
- package/dist/collection/classes/tools/text-tool.class.js.map +1 -0
- package/dist/collection/collection-manifest.json +34 -0
- package/dist/collection/components/core/kritzel-cursor-trail/kritzel-cursor-trail.css +10 -0
- package/dist/collection/components/core/kritzel-cursor-trail/kritzel-cursor-trail.js +153 -0
- package/dist/collection/components/core/kritzel-cursor-trail/kritzel-cursor-trail.js.map +1 -0
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.css +34 -0
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +928 -0
- package/dist/collection/components/core/kritzel-editor/kritzel-editor.js.map +1 -0
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.css +73 -0
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +1630 -0
- package/dist/collection/components/core/kritzel-engine/kritzel-engine.js.map +1 -0
- package/dist/collection/components/shared/kritzel-brush-style/kritzel-brush-style.css +44 -0
- package/dist/collection/components/shared/kritzel-brush-style/kritzel-brush-style.js +98 -0
- package/dist/collection/components/shared/kritzel-brush-style/kritzel-brush-style.js.map +1 -0
- package/dist/collection/components/shared/kritzel-color/kritzel-color.css +21 -0
- package/dist/collection/components/shared/kritzel-color/kritzel-color.js +107 -0
- package/dist/collection/components/shared/kritzel-color/kritzel-color.js.map +1 -0
- package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.css +46 -0
- package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js +145 -0
- package/dist/collection/components/shared/kritzel-color-palette/kritzel-color-palette.js.map +1 -0
- package/dist/collection/components/shared/kritzel-dropdown/kritzel-dropdown.css +53 -0
- package/dist/collection/components/shared/kritzel-dropdown/kritzel-dropdown.js +218 -0
- package/dist/collection/components/shared/kritzel-dropdown/kritzel-dropdown.js.map +1 -0
- package/dist/collection/components/shared/kritzel-font/kritzel-font.css +10 -0
- package/dist/collection/components/shared/kritzel-font/kritzel-font.js +90 -0
- package/dist/collection/components/shared/kritzel-font/kritzel-font.js.map +1 -0
- package/dist/collection/components/shared/kritzel-font-family/kritzel-font-family.css +48 -0
- package/dist/collection/components/shared/kritzel-font-family/kritzel-font-family.js +114 -0
- package/dist/collection/components/shared/kritzel-font-family/kritzel-font-family.js.map +1 -0
- package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.css +30 -0
- package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js +110 -0
- package/dist/collection/components/shared/kritzel-font-size/kritzel-font-size.js.map +1 -0
- package/dist/collection/components/shared/kritzel-icon/kritzel-icon.css +18 -0
- package/dist/collection/components/shared/kritzel-icon/kritzel-icon.js +94 -0
- package/dist/collection/components/shared/kritzel-icon/kritzel-icon.js.map +1 -0
- package/dist/collection/components/shared/kritzel-menu/kritzel-menu.css +27 -0
- package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js +291 -0
- package/dist/collection/components/shared/kritzel-menu/kritzel-menu.js.map +1 -0
- package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.css +145 -0
- package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js +280 -0
- package/dist/collection/components/shared/kritzel-menu-item/kritzel-menu-item.js.map +1 -0
- package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js +310 -0
- package/dist/collection/components/shared/kritzel-portal/kritzel-portal.js.map +1 -0
- package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.css +78 -0
- package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js +406 -0
- package/dist/collection/components/shared/kritzel-split-button/kritzel-split-button.js.map +1 -0
- package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.css +28 -0
- package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js +89 -0
- package/dist/collection/components/shared/kritzel-stroke-size/kritzel-stroke-size.js.map +1 -0
- package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.css +17 -0
- package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js +251 -0
- package/dist/collection/components/shared/kritzel-tooltip/kritzel-tooltip.js.map +1 -0
- package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.css +55 -0
- package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.js +172 -0
- package/dist/collection/components/ui/kritzel-context-menu/kritzel-context-menu.js.map +1 -0
- package/dist/collection/components/ui/kritzel-control-brush-config/kritzel-control-brush-config.css +19 -0
- package/dist/collection/components/ui/kritzel-control-brush-config/kritzel-control-brush-config.js +135 -0
- package/dist/collection/components/ui/kritzel-control-brush-config/kritzel-control-brush-config.js.map +1 -0
- package/dist/collection/components/ui/kritzel-control-text-config/kritzel-control-text-config.css +19 -0
- package/dist/collection/components/ui/kritzel-control-text-config/kritzel-control-text-config.js +115 -0
- package/dist/collection/components/ui/kritzel-control-text-config/kritzel-control-text-config.js.map +1 -0
- package/dist/collection/components/ui/kritzel-controls/kritzel-controls.css +127 -0
- package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js +312 -0
- package/dist/collection/components/ui/kritzel-controls/kritzel-controls.js.map +1 -0
- package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.css +44 -0
- package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +111 -0
- package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js.map +1 -0
- package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.css +5 -0
- package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js +255 -0
- package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js.map +1 -0
- package/dist/collection/configs/default-brush-tool.config.js +60 -0
- package/dist/collection/configs/default-brush-tool.config.js.map +1 -0
- package/dist/collection/configs/default-engine-config.js +53 -0
- package/dist/collection/configs/default-engine-config.js.map +1 -0
- package/dist/collection/configs/default-sync.config.js +10 -0
- package/dist/collection/configs/default-sync.config.js.map +1 -0
- package/dist/collection/configs/default-text-tool.config.js +32 -0
- package/dist/collection/configs/default-text-tool.config.js.map +1 -0
- package/dist/collection/constants/core.constants.js +2 -0
- package/dist/collection/constants/core.constants.js.map +1 -0
- package/dist/collection/constants/engine.constants.js +3 -0
- package/dist/collection/constants/engine.constants.js.map +1 -0
- package/dist/collection/enums/event-button.enum.js +7 -0
- package/dist/collection/enums/event-button.enum.js.map +1 -0
- package/dist/collection/enums/handle-type.enum.js +8 -0
- package/dist/collection/enums/handle-type.enum.js.map +1 -0
- package/dist/collection/helpers/class.helper.js +6 -0
- package/dist/collection/helpers/class.helper.js.map +1 -0
- package/dist/collection/helpers/devices.helper.js +26 -0
- package/dist/collection/helpers/devices.helper.js.map +1 -0
- package/dist/collection/helpers/event.helper.js +51 -0
- package/dist/collection/helpers/event.helper.js.map +1 -0
- package/dist/collection/helpers/geometry.helper.js +53 -0
- package/dist/collection/helpers/geometry.helper.js.map +1 -0
- package/dist/collection/helpers/html.helper.js +64 -0
- package/dist/{stencil/html.helper-C6qB08BS.js.map → collection/helpers/html.helper.js.map} +1 -1
- package/dist/collection/helpers/keyboard.helper.js +49 -0
- package/dist/collection/helpers/keyboard.helper.js.map +1 -0
- package/dist/collection/helpers/math.helper.js +6 -0
- package/dist/collection/helpers/math.helper.js.map +1 -0
- package/dist/collection/helpers/object.helper.js +12 -0
- package/dist/collection/helpers/object.helper.js.map +1 -0
- package/dist/collection/index.js +30 -0
- package/dist/collection/index.js.map +1 -0
- package/dist/collection/interfaces/bounding-box.interface.js +2 -0
- package/dist/collection/interfaces/bounding-box.interface.js.map +1 -0
- package/dist/collection/interfaces/clonable.interface.js +2 -0
- package/dist/collection/interfaces/clonable.interface.js.map +1 -0
- package/dist/collection/interfaces/context-menu-item.interface.js +2 -0
- package/dist/collection/interfaces/context-menu-item.interface.js.map +1 -0
- package/dist/collection/interfaces/debug-info.interface.js +2 -0
- package/dist/collection/interfaces/debug-info.interface.js.map +1 -0
- package/dist/collection/interfaces/engine-state.interface.js +2 -0
- package/dist/collection/interfaces/engine-state.interface.js.map +1 -0
- package/dist/collection/interfaces/menu-item.interface.js +2 -0
- package/dist/collection/interfaces/menu-item.interface.js.map +1 -0
- package/dist/collection/interfaces/object.interface.js +2 -0
- package/dist/collection/interfaces/object.interface.js.map +1 -0
- package/dist/collection/interfaces/path-options.interface.js +2 -0
- package/dist/collection/interfaces/path-options.interface.js.map +1 -0
- package/dist/collection/interfaces/point.interface.js +2 -0
- package/dist/collection/interfaces/point.interface.js.map +1 -0
- package/dist/collection/interfaces/polygon.interface.js +2 -0
- package/dist/collection/interfaces/polygon.interface.js.map +1 -0
- package/dist/collection/interfaces/selection-state.interface.js +2 -0
- package/dist/collection/interfaces/selection-state.interface.js.map +1 -0
- package/dist/collection/interfaces/serializable.interface.js +2 -0
- package/dist/collection/interfaces/serializable.interface.js.map +1 -0
- package/dist/collection/interfaces/shortcut.interface.js +2 -0
- package/dist/collection/interfaces/shortcut.interface.js.map +1 -0
- package/dist/collection/interfaces/sync-config.interface.js +2 -0
- package/dist/collection/interfaces/sync-config.interface.js.map +1 -0
- package/dist/collection/interfaces/sync-provider.interface.js +2 -0
- package/dist/collection/interfaces/sync-provider.interface.js.map +1 -0
- package/dist/collection/interfaces/tool.interface.js +2 -0
- package/dist/collection/interfaces/tool.interface.js.map +1 -0
- package/dist/collection/interfaces/toolbar-control.interface.js +2 -0
- package/dist/collection/interfaces/toolbar-control.interface.js.map +1 -0
- package/dist/collection/interfaces/undo-state.interface.js +2 -0
- package/dist/collection/interfaces/undo-state.interface.js.map +1 -0
- package/dist/collection/types/deep-readonly.type.js +2 -0
- package/dist/collection/types/deep-readonly.type.js.map +1 -0
- package/dist/collection/types/state.types.js +2 -0
- package/dist/collection/types/state.types.js.map +1 -0
- package/dist/components/index.js +1477 -0
- package/dist/components/index.js.map +1 -0
- package/dist/components/kritzel-brush-style.js +67 -0
- package/dist/components/kritzel-brush-style.js.map +1 -0
- package/dist/components/kritzel-color-palette.js +9 -0
- package/dist/components/kritzel-color-palette.js.map +1 -0
- package/dist/components/kritzel-color.js +9 -0
- package/dist/components/kritzel-color.js.map +1 -0
- package/dist/components/kritzel-context-menu.js +9 -0
- package/dist/components/kritzel-context-menu.js.map +1 -0
- package/dist/components/kritzel-control-brush-config.js +9 -0
- package/dist/components/kritzel-control-brush-config.js.map +1 -0
- package/dist/components/kritzel-control-text-config.js +9 -0
- package/dist/components/kritzel-control-text-config.js.map +1 -0
- package/dist/components/kritzel-controls.js +9 -0
- package/dist/components/kritzel-controls.js.map +1 -0
- package/dist/components/kritzel-cursor-trail.js +9 -0
- package/dist/components/kritzel-cursor-trail.js.map +1 -0
- package/dist/components/kritzel-dropdown.js +9 -0
- package/dist/components/kritzel-dropdown.js.map +1 -0
- package/dist/components/kritzel-editor.js +516 -0
- package/dist/components/kritzel-editor.js.map +1 -0
- package/dist/components/kritzel-engine.js +9 -0
- package/dist/components/kritzel-engine.js.map +1 -0
- package/dist/components/kritzel-font-family.js +9 -0
- package/dist/components/kritzel-font-family.js.map +1 -0
- package/dist/components/kritzel-font-size.js +9 -0
- package/dist/components/kritzel-font-size.js.map +1 -0
- package/dist/components/kritzel-font.js +9 -0
- package/dist/components/kritzel-font.js.map +1 -0
- package/dist/components/kritzel-icon.js +9 -0
- package/dist/components/kritzel-icon.js.map +1 -0
- package/dist/components/kritzel-menu-item.js +9 -0
- package/dist/components/kritzel-menu-item.js.map +1 -0
- package/dist/components/kritzel-menu.js +9 -0
- package/dist/components/kritzel-menu.js.map +1 -0
- package/dist/components/kritzel-portal.js +9 -0
- package/dist/components/kritzel-portal.js.map +1 -0
- package/dist/components/kritzel-split-button.js +9 -0
- package/dist/components/kritzel-split-button.js.map +1 -0
- package/dist/components/kritzel-stroke-size.js +9 -0
- package/dist/components/kritzel-stroke-size.js.map +1 -0
- package/dist/components/kritzel-tooltip.js +9 -0
- package/dist/components/kritzel-tooltip.js.map +1 -0
- package/dist/components/kritzel-utility-panel.js +9 -0
- package/dist/components/kritzel-utility-panel.js.map +1 -0
- package/dist/components/kritzel-workspace-manager.js +9 -0
- package/dist/components/kritzel-workspace-manager.js.map +1 -0
- package/dist/{stencil/kritzel-controls.entry.js → components/p-1lIHoOlH.js} +115 -17
- package/dist/components/p-1lIHoOlH.js.map +1 -0
- package/dist/{stencil/object.helper-B0kd2rUI.js → components/p-B0kd2rUI.js} +2 -2
- package/dist/components/p-B0kd2rUI.js.map +1 -0
- package/dist/components/p-B4kxkVe-.js +55 -0
- package/dist/components/p-B4kxkVe-.js.map +1 -0
- package/dist/components/p-BQg4YML7.js +106 -0
- package/dist/components/p-BQg4YML7.js.map +1 -0
- package/dist/{stencil/kritzel-font-family.entry.js → components/p-BgznZoBH.js} +37 -9
- package/dist/components/p-BgznZoBH.js.map +1 -0
- package/dist/components/p-Bhtn9qay.js +98 -0
- package/dist/components/p-Bhtn9qay.js.map +1 -0
- package/dist/{stencil/kritzel-context-menu-GdU9xEKC.js → components/p-C2sWlNsJ.js} +41 -12
- package/dist/components/p-C2sWlNsJ.js.map +1 -0
- package/dist/{stencil/html.helper-C6qB08BS.js → components/p-C6qB08BS.js} +2 -2
- package/dist/components/p-C6qB08BS.js.map +1 -0
- package/dist/{stencil/text-tool.class-C0GbC5zQ.js → components/p-CBYBurdY.js} +25 -729
- package/dist/components/p-CBYBurdY.js.map +1 -0
- package/dist/{stencil/kritzel-dropdown.entry.js → components/p-CIXPLjCu.js} +39 -9
- package/dist/components/p-CIXPLjCu.js.map +1 -0
- package/dist/{stencil/kritzel-workspace-manager.entry.js → components/p-CK6no3mi.js} +68 -13
- package/dist/components/p-CK6no3mi.js.map +1 -0
- package/dist/{stencil/kritzel-cursor-trail.entry.js → components/p-CLt3HMl6.js} +33 -10
- package/dist/components/p-CLt3HMl6.js.map +1 -0
- package/dist/{stencil/kritzel-tooltip.entry.js → components/p-CTP479Lf.js} +39 -11
- package/dist/components/p-CTP479Lf.js.map +1 -0
- package/dist/{stencil/kritzel-menu-item.entry.js → components/p-CsA9M6me.js} +174 -16
- package/dist/components/p-CsA9M6me.js.map +1 -0
- package/dist/components/p-CwkUrTy1.js +1367 -0
- package/dist/components/p-CwkUrTy1.js.map +1 -0
- package/dist/{stencil/kritzel-color-palette.entry.js → components/p-D1uj4A4F.js} +39 -9
- package/dist/components/p-D1uj4A4F.js.map +1 -0
- package/dist/{stencil/kritzel-color.entry.js → components/p-D4yvhd1d.js} +30 -8
- package/dist/components/p-D4yvhd1d.js.map +1 -0
- package/dist/{stencil/kritzel-portal.entry.js → components/p-D5Wq4x4r.js} +37 -11
- package/dist/components/p-D5Wq4x4r.js.map +1 -0
- package/dist/{stencil/event-button.enum-D8W6LE-c.js → components/p-D8W6LE-c.js} +2 -2
- package/dist/components/p-D8W6LE-c.js.map +1 -0
- package/dist/{stencil/kritzel-utility-panel.entry.js → components/p-DAfkuR8U.js} +38 -11
- package/dist/components/p-DAfkuR8U.js.map +1 -0
- package/dist/components/p-DDmSxM5f.js +57 -0
- package/dist/{stencil/kritzel-font-size.entry.esm.js.map → components/p-DDmSxM5f.js.map} +1 -1
- package/dist/components/p-Ddh40W3x.js +103 -0
- package/dist/components/p-Ddh40W3x.js.map +1 -0
- package/dist/components/p-DjTEcPMZ.js +35610 -0
- package/dist/components/p-DjTEcPMZ.js.map +1 -0
- package/dist/{stencil/kritzel-split-button.entry.js → components/p-TdCTkEu0.js} +72 -18
- package/dist/components/p-TdCTkEu0.js.map +1 -0
- package/dist/{stencil/devices.helper-l10It7Nm.js → components/p-l10It7Nm.js} +2 -2
- package/dist/components/p-l10It7Nm.js.map +1 -0
- package/dist/{stencil/workspace.class-n789Y3S-.js → components/p-n789Y3S-.js} +2 -2
- package/dist/components/p-n789Y3S-.js.map +1 -0
- package/dist/components/p-uuRJU2R1.js +46 -0
- package/dist/components/p-uuRJU2R1.js.map +1 -0
- package/dist/esm/app-globals-DQuL1Twl.js +6 -0
- package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
- package/dist/esm/default-text-tool.config-2YFQA3SF.js +31208 -0
- package/dist/esm/default-text-tool.config-2YFQA3SF.js.map +1 -0
- package/dist/esm/index-SGde3HXB.js +1623 -0
- package/dist/esm/index-SGde3HXB.js.map +1 -0
- package/dist/esm/index.js +1449 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/{stencil → esm}/kritzel-brush-style.entry.js +3 -3
- package/dist/esm/kritzel-brush-style.entry.js.map +1 -0
- package/dist/{stencil/kritzel-engine.entry.js → esm/kritzel-color_22.entry.js} +1805 -72
- package/dist/esm/loader.js +12 -0
- package/dist/esm/loader.js.map +1 -0
- package/dist/esm/stencil.js +22 -0
- package/dist/esm/stencil.js.map +1 -0
- package/dist/index.cjs.js +1 -0
- package/dist/index.js +1 -0
- package/dist/stencil/index.esm.js +2 -18
- package/dist/stencil/index.esm.js.map +1 -1
- package/dist/stencil/loader.esm.js.map +1 -1
- package/dist/stencil/p-14102a0c.entry.js +10 -0
- package/dist/stencil/p-14102a0c.entry.js.map +1 -0
- package/dist/stencil/p-2YFQA3SF.js +2 -0
- package/dist/stencil/p-2YFQA3SF.js.map +1 -0
- package/dist/stencil/p-DQuL1Twl.js +2 -0
- package/dist/stencil/p-DQuL1Twl.js.map +1 -0
- package/dist/stencil/p-SGde3HXB.js +3 -0
- package/dist/stencil/p-SGde3HXB.js.map +1 -0
- package/dist/stencil/p-d702c5af.entry.js +2 -0
- package/dist/stencil/p-d702c5af.entry.js.map +1 -0
- package/dist/stencil/stencil.esm.js +2 -48
- package/dist/stencil/stencil.esm.js.map +1 -1
- package/package.json +1 -1
- package/dist/stencil/default-text-tool.config-DKpRP4XR.js +0 -1441
- package/dist/stencil/default-text-tool.config-DKpRP4XR.js.map +0 -1
- package/dist/stencil/devices.helper-l10It7Nm.js.map +0 -1
- package/dist/stencil/engine.constants-DsjjAmnl.js +0 -7
- package/dist/stencil/engine.constants-DsjjAmnl.js.map +0 -1
- package/dist/stencil/event-button.enum-D8W6LE-c.js.map +0 -1
- package/dist/stencil/icon-registry.class-BtT8riKh.js.map +0 -1
- package/dist/stencil/index-DniO_INI.js +0 -4395
- package/dist/stencil/index-DniO_INI.js.map +0 -1
- package/dist/stencil/kritzel-color-palette.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-color.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-context-menu-BYgOEy-i.js +0 -66
- package/dist/stencil/kritzel-context-menu-BYgOEy-i.js.map +0 -1
- package/dist/stencil/kritzel-context-menu-GdU9xEKC.js.map +0 -1
- package/dist/stencil/kritzel-context-menu.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-context-menu.entry.js +0 -3
- package/dist/stencil/kritzel-control-brush-config.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-control-brush-config.entry.js +0 -54
- package/dist/stencil/kritzel-control-text-config.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-control-text-config.entry.js +0 -42
- package/dist/stencil/kritzel-controls.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-cursor-trail.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-dropdown.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-editor.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-editor.entry.js +0 -248
- package/dist/stencil/kritzel-engine.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-font-family.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-font-size.entry.js +0 -28
- package/dist/stencil/kritzel-font.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-font.entry.js +0 -23
- package/dist/stencil/kritzel-icon.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-icon.entry.js +0 -29
- package/dist/stencil/kritzel-menu-item.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-menu.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-menu.entry.js +0 -72
- package/dist/stencil/kritzel-portal.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-split-button.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-stroke-size.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-stroke-size.entry.js +0 -27
- package/dist/stencil/kritzel-tooltip.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-utility-panel.entry.esm.js.map +0 -1
- package/dist/stencil/kritzel-workspace-manager.entry.esm.js.map +0 -1
- package/dist/stencil/object.helper-B0kd2rUI.js.map +0 -1
- package/dist/stencil/sync-config.interface-lKfyG1EN.js +0 -19839
- package/dist/stencil/sync-config.interface-lKfyG1EN.js.map +0 -1
- package/dist/stencil/text-tool.class-C0GbC5zQ.js.map +0 -1
- package/dist/stencil/workspace.class-n789Y3S-.js.map +0 -1
|
@@ -0,0 +1,1477 @@
|
|
|
1
|
+
export { g as getAssetPath, r as render, s as setAssetPath, a as setNonce, b as setPlatformOptions } from './p-CwkUrTy1.js';
|
|
2
|
+
export { b as KritzelBrushTool, a as KritzelPath, K as KritzelText, c as KritzelTextTool } from './p-CBYBurdY.js';
|
|
3
|
+
import { w as writeVarUint, a as writeVarUint8Array, t as toUint8Array, r as readVarUint, b as readVarUint8Array, e as encodeStateAsUpdate, c as applyUpdate, d as encodeStateVector, f as createEncoder, g as createDecoder, s as setIfUndefined, h as create, i as fromBase64, v as varStorage, j as toBase64, o as onChange, k as createUint8ArrayFromArrayBuffer, l as offChange, m as readVarString, O as Observable, n as floor, p as getUnixTime, q as equalityDeep, u as writeVarString, x as map, y as ObservableV2, z as length, A as isNode, B as min, C as pow, H as HocuspocusProvider, D as HocuspocusProviderWebsocket } from './p-DjTEcPMZ.js';
|
|
4
|
+
export { I as IndexedDBSyncProvider, J as KritzelAppStateMap, E as KritzelEraserTool, K as KritzelImage, F as KritzelImageTool, G as KritzelSelectionTool } from './p-DjTEcPMZ.js';
|
|
5
|
+
export { K as KritzelWorkspace } from './p-n789Y3S-.js';
|
|
6
|
+
export { D as DEFAULT_BRUSH_CONFIG, a as DEFAULT_TEXT_CONFIG, KritzelEditor, defineCustomElement as defineCustomElementKritzelEditor } from './kritzel-editor.js';
|
|
7
|
+
export { KritzelBrushStyle, defineCustomElement as defineCustomElementKritzelBrushStyle } from './kritzel-brush-style.js';
|
|
8
|
+
export { KritzelColor, defineCustomElement as defineCustomElementKritzelColor } from './kritzel-color.js';
|
|
9
|
+
export { KritzelColorPalette, defineCustomElement as defineCustomElementKritzelColorPalette } from './kritzel-color-palette.js';
|
|
10
|
+
export { KritzelContextMenu, defineCustomElement as defineCustomElementKritzelContextMenu } from './kritzel-context-menu.js';
|
|
11
|
+
export { KritzelControlBrushConfig, defineCustomElement as defineCustomElementKritzelControlBrushConfig } from './kritzel-control-brush-config.js';
|
|
12
|
+
export { KritzelControlTextConfig, defineCustomElement as defineCustomElementKritzelControlTextConfig } from './kritzel-control-text-config.js';
|
|
13
|
+
export { KritzelControls, defineCustomElement as defineCustomElementKritzelControls } from './kritzel-controls.js';
|
|
14
|
+
export { KritzelCursorTrail, defineCustomElement as defineCustomElementKritzelCursorTrail } from './kritzel-cursor-trail.js';
|
|
15
|
+
export { KritzelDropdown, defineCustomElement as defineCustomElementKritzelDropdown } from './kritzel-dropdown.js';
|
|
16
|
+
export { KritzelEngine, defineCustomElement as defineCustomElementKritzelEngine } from './kritzel-engine.js';
|
|
17
|
+
export { KritzelFont, defineCustomElement as defineCustomElementKritzelFont } from './kritzel-font.js';
|
|
18
|
+
export { KritzelFontFamily, defineCustomElement as defineCustomElementKritzelFontFamily } from './kritzel-font-family.js';
|
|
19
|
+
export { KritzelFontSize, defineCustomElement as defineCustomElementKritzelFontSize } from './kritzel-font-size.js';
|
|
20
|
+
export { KritzelIcon, defineCustomElement as defineCustomElementKritzelIcon } from './kritzel-icon.js';
|
|
21
|
+
export { KritzelMenu, defineCustomElement as defineCustomElementKritzelMenu } from './kritzel-menu.js';
|
|
22
|
+
export { KritzelMenuItem, defineCustomElement as defineCustomElementKritzelMenuItem } from './kritzel-menu-item.js';
|
|
23
|
+
export { KritzelPortal, defineCustomElement as defineCustomElementKritzelPortal } from './kritzel-portal.js';
|
|
24
|
+
export { KritzelSplitButton, defineCustomElement as defineCustomElementKritzelSplitButton } from './kritzel-split-button.js';
|
|
25
|
+
export { KritzelStrokeSize, defineCustomElement as defineCustomElementKritzelStrokeSize } from './kritzel-stroke-size.js';
|
|
26
|
+
export { KritzelTooltip, defineCustomElement as defineCustomElementKritzelTooltip } from './kritzel-tooltip.js';
|
|
27
|
+
export { KritzelUtilityPanel, defineCustomElement as defineCustomElementKritzelUtilityPanel } from './kritzel-utility-panel.js';
|
|
28
|
+
export { KritzelWorkspaceManager, defineCustomElement as defineCustomElementKritzelWorkspaceManager } from './kritzel-workspace-manager.js';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* BroadcastChannel sync provider for cross-tab synchronization
|
|
32
|
+
* This is a lightweight alternative to y-webrtc for browser-tab-only sync
|
|
33
|
+
*/
|
|
34
|
+
class BroadcastSyncProvider {
|
|
35
|
+
doc;
|
|
36
|
+
channel;
|
|
37
|
+
_synced = false;
|
|
38
|
+
constructor(docName, doc, _options) {
|
|
39
|
+
this.doc = doc;
|
|
40
|
+
this.channel = new BroadcastChannel(docName);
|
|
41
|
+
// Handle incoming messages from other tabs
|
|
42
|
+
this.channel.onmessage = (event) => {
|
|
43
|
+
this.handleMessage(event.data);
|
|
44
|
+
};
|
|
45
|
+
// Listen to document updates and broadcast them
|
|
46
|
+
this.doc.on('update', this.handleDocUpdate);
|
|
47
|
+
// Send initial sync request
|
|
48
|
+
this.broadcastSync();
|
|
49
|
+
// Mark as synced after a short delay (to receive any pending updates)
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
this._synced = true;
|
|
52
|
+
}, 100);
|
|
53
|
+
console.info(`BroadcastChannel Provider initialized: ${docName}`);
|
|
54
|
+
}
|
|
55
|
+
handleDocUpdate = (update, origin) => {
|
|
56
|
+
// Don't broadcast updates that came from other tabs (to prevent loops)
|
|
57
|
+
if (origin !== this) {
|
|
58
|
+
const encoder = createEncoder();
|
|
59
|
+
writeVarUint(encoder, 0); // Message type: sync update
|
|
60
|
+
writeVarUint8Array(encoder, update);
|
|
61
|
+
this.channel.postMessage(toUint8Array(encoder));
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
handleMessage(message) {
|
|
65
|
+
const decoder = createDecoder(new Uint8Array(message));
|
|
66
|
+
const messageType = readVarUint(decoder);
|
|
67
|
+
switch (messageType) {
|
|
68
|
+
case 0: // Sync update
|
|
69
|
+
const update = readVarUint8Array(decoder);
|
|
70
|
+
applyUpdate(this.doc, update, this);
|
|
71
|
+
break;
|
|
72
|
+
case 1: // Sync request
|
|
73
|
+
this.broadcastSync();
|
|
74
|
+
break;
|
|
75
|
+
case 2: // Sync response
|
|
76
|
+
const stateVector = readVarUint8Array(decoder);
|
|
77
|
+
const updateResponse = encodeStateAsUpdate(this.doc, stateVector);
|
|
78
|
+
if (updateResponse.length > 0) {
|
|
79
|
+
const encoder = createEncoder();
|
|
80
|
+
writeVarUint(encoder, 0); // Send as regular update
|
|
81
|
+
writeVarUint8Array(encoder, updateResponse);
|
|
82
|
+
this.channel.postMessage(toUint8Array(encoder));
|
|
83
|
+
}
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
broadcastSync() {
|
|
88
|
+
// Broadcast our current state vector to request missing updates
|
|
89
|
+
const encoder = createEncoder();
|
|
90
|
+
writeVarUint(encoder, 2); // Message type: sync response
|
|
91
|
+
writeVarUint8Array(encoder, encodeStateVector(this.doc));
|
|
92
|
+
this.channel.postMessage(toUint8Array(encoder));
|
|
93
|
+
}
|
|
94
|
+
async connect() {
|
|
95
|
+
// Wait for initial sync to complete
|
|
96
|
+
if (this._synced) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
const checkSync = () => {
|
|
101
|
+
if (this._synced) {
|
|
102
|
+
resolve();
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
setTimeout(checkSync, 50);
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
checkSync();
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
disconnect() {
|
|
112
|
+
// BroadcastChannel doesn't have explicit disconnect
|
|
113
|
+
}
|
|
114
|
+
destroy() {
|
|
115
|
+
this.doc.off('update', this.handleDocUpdate);
|
|
116
|
+
this.channel.close();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/* eslint-env browser */
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* @typedef {Object} Channel
|
|
125
|
+
* @property {Set<function(any, any):any>} Channel.subs
|
|
126
|
+
* @property {any} Channel.bc
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* @type {Map<string, Channel>}
|
|
131
|
+
*/
|
|
132
|
+
const channels = new Map();
|
|
133
|
+
|
|
134
|
+
/* c8 ignore start */
|
|
135
|
+
class LocalStoragePolyfill {
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} room
|
|
138
|
+
*/
|
|
139
|
+
constructor (room) {
|
|
140
|
+
this.room = room;
|
|
141
|
+
/**
|
|
142
|
+
* @type {null|function({data:ArrayBuffer}):void}
|
|
143
|
+
*/
|
|
144
|
+
this.onmessage = null;
|
|
145
|
+
/**
|
|
146
|
+
* @param {any} e
|
|
147
|
+
*/
|
|
148
|
+
this._onChange = e => e.key === room && this.onmessage !== null && this.onmessage({ data: fromBase64(e.newValue || '') });
|
|
149
|
+
onChange(this._onChange);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* @param {ArrayBuffer} buf
|
|
154
|
+
*/
|
|
155
|
+
postMessage (buf) {
|
|
156
|
+
varStorage.setItem(this.room, toBase64(createUint8ArrayFromArrayBuffer(buf)));
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
close () {
|
|
160
|
+
offChange(this._onChange);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/* c8 ignore stop */
|
|
164
|
+
|
|
165
|
+
// Use BroadcastChannel or Polyfill
|
|
166
|
+
/* c8 ignore next */
|
|
167
|
+
const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel;
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* @param {string} room
|
|
171
|
+
* @return {Channel}
|
|
172
|
+
*/
|
|
173
|
+
const getChannel = room =>
|
|
174
|
+
setIfUndefined(channels, room, () => {
|
|
175
|
+
const subs = create();
|
|
176
|
+
const bc = new BC(room);
|
|
177
|
+
/**
|
|
178
|
+
* @param {{data:ArrayBuffer}} e
|
|
179
|
+
*/
|
|
180
|
+
/* c8 ignore next */
|
|
181
|
+
bc.onmessage = e => subs.forEach(sub => sub(e.data, 'broadcastchannel'));
|
|
182
|
+
return {
|
|
183
|
+
bc, subs
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Subscribe to global `publish` events.
|
|
189
|
+
*
|
|
190
|
+
* @function
|
|
191
|
+
* @param {string} room
|
|
192
|
+
* @param {function(any, any):any} f
|
|
193
|
+
*/
|
|
194
|
+
const subscribe = (room, f) => {
|
|
195
|
+
getChannel(room).subs.add(f);
|
|
196
|
+
return f
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Unsubscribe from `publish` global events.
|
|
201
|
+
*
|
|
202
|
+
* @function
|
|
203
|
+
* @param {string} room
|
|
204
|
+
* @param {function(any, any):any} f
|
|
205
|
+
*/
|
|
206
|
+
const unsubscribe = (room, f) => {
|
|
207
|
+
const channel = getChannel(room);
|
|
208
|
+
const unsubscribed = channel.subs.delete(f);
|
|
209
|
+
if (unsubscribed && channel.subs.size === 0) {
|
|
210
|
+
channel.bc.close();
|
|
211
|
+
channels.delete(room);
|
|
212
|
+
}
|
|
213
|
+
return unsubscribed
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Publish data to all subscribers (including subscribers on this tab)
|
|
218
|
+
*
|
|
219
|
+
* @function
|
|
220
|
+
* @param {string} room
|
|
221
|
+
* @param {any} data
|
|
222
|
+
* @param {any} [origin]
|
|
223
|
+
*/
|
|
224
|
+
const publish = (room, data, origin = null) => {
|
|
225
|
+
const c = getChannel(room);
|
|
226
|
+
c.bc.postMessage(data);
|
|
227
|
+
c.subs.forEach(sub => sub(data, origin));
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* @module sync-protocol
|
|
232
|
+
*/
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* @typedef {Map<number, number>} StateMap
|
|
237
|
+
*/
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Core Yjs defines two message types:
|
|
241
|
+
* • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2.
|
|
242
|
+
* • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the client is assured that it
|
|
243
|
+
* received all information from the remote client.
|
|
244
|
+
*
|
|
245
|
+
* In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection
|
|
246
|
+
* with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both
|
|
247
|
+
* SyncStep2 and SyncDone, it is assured that it is synced to the remote client.
|
|
248
|
+
*
|
|
249
|
+
* In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1.
|
|
250
|
+
* When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies
|
|
251
|
+
* with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the
|
|
252
|
+
* client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can
|
|
253
|
+
* easily be implemented on top of http and websockets. 2. The server should only reply to requests, and not initiate them.
|
|
254
|
+
* Therefore it is necessary that the client initiates the sync.
|
|
255
|
+
*
|
|
256
|
+
* Construction of a message:
|
|
257
|
+
* [messageType : varUint, message definition..]
|
|
258
|
+
*
|
|
259
|
+
* Note: A message does not include information about the room name. This must to be handled by the upper layer protocol!
|
|
260
|
+
*
|
|
261
|
+
* stringify[messageType] stringifies a message definition (messageType is already read from the bufffer)
|
|
262
|
+
*/
|
|
263
|
+
|
|
264
|
+
const messageYjsSyncStep1 = 0;
|
|
265
|
+
const messageYjsSyncStep2 = 1;
|
|
266
|
+
const messageYjsUpdate = 2;
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Create a sync step 1 message based on the state of the current shared document.
|
|
270
|
+
*
|
|
271
|
+
* @param {encoding.Encoder} encoder
|
|
272
|
+
* @param {Y.Doc} doc
|
|
273
|
+
*/
|
|
274
|
+
const writeSyncStep1 = (encoder, doc) => {
|
|
275
|
+
writeVarUint(encoder, messageYjsSyncStep1);
|
|
276
|
+
const sv = encodeStateVector(doc);
|
|
277
|
+
writeVarUint8Array(encoder, sv);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* @param {encoding.Encoder} encoder
|
|
282
|
+
* @param {Y.Doc} doc
|
|
283
|
+
* @param {Uint8Array} [encodedStateVector]
|
|
284
|
+
*/
|
|
285
|
+
const writeSyncStep2 = (encoder, doc, encodedStateVector) => {
|
|
286
|
+
writeVarUint(encoder, messageYjsSyncStep2);
|
|
287
|
+
writeVarUint8Array(encoder, encodeStateAsUpdate(doc, encodedStateVector));
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Read SyncStep1 message and reply with SyncStep2.
|
|
292
|
+
*
|
|
293
|
+
* @param {decoding.Decoder} decoder The reply to the received message
|
|
294
|
+
* @param {encoding.Encoder} encoder The received message
|
|
295
|
+
* @param {Y.Doc} doc
|
|
296
|
+
*/
|
|
297
|
+
const readSyncStep1 = (decoder, encoder, doc) =>
|
|
298
|
+
writeSyncStep2(encoder, doc, readVarUint8Array(decoder));
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Read and apply Structs and then DeleteStore to a y instance.
|
|
302
|
+
*
|
|
303
|
+
* @param {decoding.Decoder} decoder
|
|
304
|
+
* @param {Y.Doc} doc
|
|
305
|
+
* @param {any} transactionOrigin
|
|
306
|
+
*/
|
|
307
|
+
const readSyncStep2 = (decoder, doc, transactionOrigin) => {
|
|
308
|
+
try {
|
|
309
|
+
applyUpdate(doc, readVarUint8Array(decoder), transactionOrigin);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
// This catches errors that are thrown by event handlers
|
|
312
|
+
console.error('Caught error while handling a Yjs update', error);
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {encoding.Encoder} encoder
|
|
318
|
+
* @param {Uint8Array} update
|
|
319
|
+
*/
|
|
320
|
+
const writeUpdate = (encoder, update) => {
|
|
321
|
+
writeVarUint(encoder, messageYjsUpdate);
|
|
322
|
+
writeVarUint8Array(encoder, update);
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Read and apply Structs and then DeleteStore to a y instance.
|
|
327
|
+
*
|
|
328
|
+
* @param {decoding.Decoder} decoder
|
|
329
|
+
* @param {Y.Doc} doc
|
|
330
|
+
* @param {any} transactionOrigin
|
|
331
|
+
*/
|
|
332
|
+
const readUpdate = readSyncStep2;
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* @param {decoding.Decoder} decoder A message received from another client
|
|
336
|
+
* @param {encoding.Encoder} encoder The reply message. Does not need to be sent if empty.
|
|
337
|
+
* @param {Y.Doc} doc
|
|
338
|
+
* @param {any} transactionOrigin
|
|
339
|
+
*/
|
|
340
|
+
const readSyncMessage = (decoder, encoder, doc, transactionOrigin) => {
|
|
341
|
+
const messageType = readVarUint(decoder);
|
|
342
|
+
switch (messageType) {
|
|
343
|
+
case messageYjsSyncStep1:
|
|
344
|
+
readSyncStep1(decoder, encoder, doc);
|
|
345
|
+
break
|
|
346
|
+
case messageYjsSyncStep2:
|
|
347
|
+
readSyncStep2(decoder, doc, transactionOrigin);
|
|
348
|
+
break
|
|
349
|
+
case messageYjsUpdate:
|
|
350
|
+
readUpdate(decoder, doc, transactionOrigin);
|
|
351
|
+
break
|
|
352
|
+
default:
|
|
353
|
+
throw new Error('Unknown message type')
|
|
354
|
+
}
|
|
355
|
+
return messageType
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const messagePermissionDenied = 0;
|
|
359
|
+
|
|
360
|
+
/**
|
|
361
|
+
* @callback PermissionDeniedHandler
|
|
362
|
+
* @param {any} y
|
|
363
|
+
* @param {string} reason
|
|
364
|
+
*/
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
*
|
|
368
|
+
* @param {decoding.Decoder} decoder
|
|
369
|
+
* @param {Y.Doc} y
|
|
370
|
+
* @param {PermissionDeniedHandler} permissionDeniedHandler
|
|
371
|
+
*/
|
|
372
|
+
const readAuthMessage = (decoder, y, permissionDeniedHandler) => {
|
|
373
|
+
switch (readVarUint(decoder)) {
|
|
374
|
+
case messagePermissionDenied: permissionDeniedHandler(y, readVarString(decoder));
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* @module awareness-protocol
|
|
380
|
+
*/
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
const outdatedTimeout = 30000;
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* @typedef {Object} MetaClientState
|
|
387
|
+
* @property {number} MetaClientState.clock
|
|
388
|
+
* @property {number} MetaClientState.lastUpdated unix timestamp
|
|
389
|
+
*/
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* The Awareness class implements a simple shared state protocol that can be used for non-persistent data like awareness information
|
|
393
|
+
* (cursor, username, status, ..). Each client can update its own local state and listen to state changes of
|
|
394
|
+
* remote clients. Every client may set a state of a remote peer to `null` to mark the client as offline.
|
|
395
|
+
*
|
|
396
|
+
* Each client is identified by a unique client id (something we borrow from `doc.clientID`). A client can override
|
|
397
|
+
* its own state by propagating a message with an increasing timestamp (`clock`). If such a message is received, it is
|
|
398
|
+
* applied if the known state of that client is older than the new state (`clock < newClock`). If a client thinks that
|
|
399
|
+
* a remote client is offline, it may propagate a message with
|
|
400
|
+
* `{ clock: currentClientClock, state: null, client: remoteClient }`. If such a
|
|
401
|
+
* message is received, and the known clock of that client equals the received clock, it will override the state with `null`.
|
|
402
|
+
*
|
|
403
|
+
* Before a client disconnects, it should propagate a `null` state with an updated clock.
|
|
404
|
+
*
|
|
405
|
+
* Awareness states must be updated every 30 seconds. Otherwise the Awareness instance will delete the client state.
|
|
406
|
+
*
|
|
407
|
+
* @extends {Observable<string>}
|
|
408
|
+
*/
|
|
409
|
+
class Awareness extends Observable {
|
|
410
|
+
/**
|
|
411
|
+
* @param {Y.Doc} doc
|
|
412
|
+
*/
|
|
413
|
+
constructor (doc) {
|
|
414
|
+
super();
|
|
415
|
+
this.doc = doc;
|
|
416
|
+
/**
|
|
417
|
+
* @type {number}
|
|
418
|
+
*/
|
|
419
|
+
this.clientID = doc.clientID;
|
|
420
|
+
/**
|
|
421
|
+
* Maps from client id to client state
|
|
422
|
+
* @type {Map<number, Object<string, any>>}
|
|
423
|
+
*/
|
|
424
|
+
this.states = new Map();
|
|
425
|
+
/**
|
|
426
|
+
* @type {Map<number, MetaClientState>}
|
|
427
|
+
*/
|
|
428
|
+
this.meta = new Map();
|
|
429
|
+
this._checkInterval = /** @type {any} */ (setInterval(() => {
|
|
430
|
+
const now = getUnixTime();
|
|
431
|
+
if (this.getLocalState() !== null && (outdatedTimeout / 2 <= now - /** @type {{lastUpdated:number}} */ (this.meta.get(this.clientID)).lastUpdated)) {
|
|
432
|
+
// renew local clock
|
|
433
|
+
this.setLocalState(this.getLocalState());
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* @type {Array<number>}
|
|
437
|
+
*/
|
|
438
|
+
const remove = [];
|
|
439
|
+
this.meta.forEach((meta, clientid) => {
|
|
440
|
+
if (clientid !== this.clientID && outdatedTimeout <= now - meta.lastUpdated && this.states.has(clientid)) {
|
|
441
|
+
remove.push(clientid);
|
|
442
|
+
}
|
|
443
|
+
});
|
|
444
|
+
if (remove.length > 0) {
|
|
445
|
+
removeAwarenessStates(this, remove, 'timeout');
|
|
446
|
+
}
|
|
447
|
+
}, floor(outdatedTimeout / 10)));
|
|
448
|
+
doc.on('destroy', () => {
|
|
449
|
+
this.destroy();
|
|
450
|
+
});
|
|
451
|
+
this.setLocalState({});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
destroy () {
|
|
455
|
+
this.emit('destroy', [this]);
|
|
456
|
+
this.setLocalState(null);
|
|
457
|
+
super.destroy();
|
|
458
|
+
clearInterval(this._checkInterval);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* @return {Object<string,any>|null}
|
|
463
|
+
*/
|
|
464
|
+
getLocalState () {
|
|
465
|
+
return this.states.get(this.clientID) || null
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* @param {Object<string,any>|null} state
|
|
470
|
+
*/
|
|
471
|
+
setLocalState (state) {
|
|
472
|
+
const clientID = this.clientID;
|
|
473
|
+
const currLocalMeta = this.meta.get(clientID);
|
|
474
|
+
const clock = currLocalMeta === undefined ? 0 : currLocalMeta.clock + 1;
|
|
475
|
+
const prevState = this.states.get(clientID);
|
|
476
|
+
if (state === null) {
|
|
477
|
+
this.states.delete(clientID);
|
|
478
|
+
} else {
|
|
479
|
+
this.states.set(clientID, state);
|
|
480
|
+
}
|
|
481
|
+
this.meta.set(clientID, {
|
|
482
|
+
clock,
|
|
483
|
+
lastUpdated: getUnixTime()
|
|
484
|
+
});
|
|
485
|
+
const added = [];
|
|
486
|
+
const updated = [];
|
|
487
|
+
const filteredUpdated = [];
|
|
488
|
+
const removed = [];
|
|
489
|
+
if (state === null) {
|
|
490
|
+
removed.push(clientID);
|
|
491
|
+
} else if (prevState == null) {
|
|
492
|
+
if (state != null) {
|
|
493
|
+
added.push(clientID);
|
|
494
|
+
}
|
|
495
|
+
} else {
|
|
496
|
+
updated.push(clientID);
|
|
497
|
+
if (!equalityDeep(prevState, state)) {
|
|
498
|
+
filteredUpdated.push(clientID);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
|
|
502
|
+
this.emit('change', [{ added, updated: filteredUpdated, removed }, 'local']);
|
|
503
|
+
}
|
|
504
|
+
this.emit('update', [{ added, updated, removed }, 'local']);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
/**
|
|
508
|
+
* @param {string} field
|
|
509
|
+
* @param {any} value
|
|
510
|
+
*/
|
|
511
|
+
setLocalStateField (field, value) {
|
|
512
|
+
const state = this.getLocalState();
|
|
513
|
+
if (state !== null) {
|
|
514
|
+
this.setLocalState({
|
|
515
|
+
...state,
|
|
516
|
+
[field]: value
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* @return {Map<number,Object<string,any>>}
|
|
523
|
+
*/
|
|
524
|
+
getStates () {
|
|
525
|
+
return this.states
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Mark (remote) clients as inactive and remove them from the list of active peers.
|
|
531
|
+
* This change will be propagated to remote clients.
|
|
532
|
+
*
|
|
533
|
+
* @param {Awareness} awareness
|
|
534
|
+
* @param {Array<number>} clients
|
|
535
|
+
* @param {any} origin
|
|
536
|
+
*/
|
|
537
|
+
const removeAwarenessStates = (awareness, clients, origin) => {
|
|
538
|
+
const removed = [];
|
|
539
|
+
for (let i = 0; i < clients.length; i++) {
|
|
540
|
+
const clientID = clients[i];
|
|
541
|
+
if (awareness.states.has(clientID)) {
|
|
542
|
+
awareness.states.delete(clientID);
|
|
543
|
+
if (clientID === awareness.clientID) {
|
|
544
|
+
const curMeta = /** @type {MetaClientState} */ (awareness.meta.get(clientID));
|
|
545
|
+
awareness.meta.set(clientID, {
|
|
546
|
+
clock: curMeta.clock + 1,
|
|
547
|
+
lastUpdated: getUnixTime()
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
removed.push(clientID);
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
if (removed.length > 0) {
|
|
554
|
+
awareness.emit('change', [{ added: [], updated: [], removed }, origin]);
|
|
555
|
+
awareness.emit('update', [{ added: [], updated: [], removed }, origin]);
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* @param {Awareness} awareness
|
|
561
|
+
* @param {Array<number>} clients
|
|
562
|
+
* @return {Uint8Array}
|
|
563
|
+
*/
|
|
564
|
+
const encodeAwarenessUpdate = (awareness, clients, states = awareness.states) => {
|
|
565
|
+
const len = clients.length;
|
|
566
|
+
const encoder = createEncoder();
|
|
567
|
+
writeVarUint(encoder, len);
|
|
568
|
+
for (let i = 0; i < len; i++) {
|
|
569
|
+
const clientID = clients[i];
|
|
570
|
+
const state = states.get(clientID) || null;
|
|
571
|
+
const clock = /** @type {MetaClientState} */ (awareness.meta.get(clientID)).clock;
|
|
572
|
+
writeVarUint(encoder, clientID);
|
|
573
|
+
writeVarUint(encoder, clock);
|
|
574
|
+
writeVarString(encoder, JSON.stringify(state));
|
|
575
|
+
}
|
|
576
|
+
return toUint8Array(encoder)
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* @param {Awareness} awareness
|
|
581
|
+
* @param {Uint8Array} update
|
|
582
|
+
* @param {any} origin This will be added to the emitted change event
|
|
583
|
+
*/
|
|
584
|
+
const applyAwarenessUpdate = (awareness, update, origin) => {
|
|
585
|
+
const decoder = createDecoder(update);
|
|
586
|
+
const timestamp = getUnixTime();
|
|
587
|
+
const added = [];
|
|
588
|
+
const updated = [];
|
|
589
|
+
const filteredUpdated = [];
|
|
590
|
+
const removed = [];
|
|
591
|
+
const len = readVarUint(decoder);
|
|
592
|
+
for (let i = 0; i < len; i++) {
|
|
593
|
+
const clientID = readVarUint(decoder);
|
|
594
|
+
let clock = readVarUint(decoder);
|
|
595
|
+
const state = JSON.parse(readVarString(decoder));
|
|
596
|
+
const clientMeta = awareness.meta.get(clientID);
|
|
597
|
+
const prevState = awareness.states.get(clientID);
|
|
598
|
+
const currClock = clientMeta === undefined ? 0 : clientMeta.clock;
|
|
599
|
+
if (currClock < clock || (currClock === clock && state === null && awareness.states.has(clientID))) {
|
|
600
|
+
if (state === null) {
|
|
601
|
+
// never let a remote client remove this local state
|
|
602
|
+
if (clientID === awareness.clientID && awareness.getLocalState() != null) {
|
|
603
|
+
// remote client removed the local state. Do not remote state. Broadcast a message indicating
|
|
604
|
+
// that this client still exists by increasing the clock
|
|
605
|
+
clock++;
|
|
606
|
+
} else {
|
|
607
|
+
awareness.states.delete(clientID);
|
|
608
|
+
}
|
|
609
|
+
} else {
|
|
610
|
+
awareness.states.set(clientID, state);
|
|
611
|
+
}
|
|
612
|
+
awareness.meta.set(clientID, {
|
|
613
|
+
clock,
|
|
614
|
+
lastUpdated: timestamp
|
|
615
|
+
});
|
|
616
|
+
if (clientMeta === undefined && state !== null) {
|
|
617
|
+
added.push(clientID);
|
|
618
|
+
} else if (clientMeta !== undefined && state === null) {
|
|
619
|
+
removed.push(clientID);
|
|
620
|
+
} else if (state !== null) {
|
|
621
|
+
if (!equalityDeep(state, prevState)) {
|
|
622
|
+
filteredUpdated.push(clientID);
|
|
623
|
+
}
|
|
624
|
+
updated.push(clientID);
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (added.length > 0 || filteredUpdated.length > 0 || removed.length > 0) {
|
|
629
|
+
awareness.emit('change', [{
|
|
630
|
+
added, updated: filteredUpdated, removed
|
|
631
|
+
}, origin]);
|
|
632
|
+
}
|
|
633
|
+
if (added.length > 0 || updated.length > 0 || removed.length > 0) {
|
|
634
|
+
awareness.emit('update', [{
|
|
635
|
+
added, updated, removed
|
|
636
|
+
}, origin]);
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
/**
|
|
641
|
+
* Utility module to work with urls.
|
|
642
|
+
*
|
|
643
|
+
* @module url
|
|
644
|
+
*/
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
/**
|
|
648
|
+
* @param {Object<string,string>} params
|
|
649
|
+
* @return {string}
|
|
650
|
+
*/
|
|
651
|
+
const encodeQueryParams = params =>
|
|
652
|
+
map(params, (val, key) => `${encodeURIComponent(key)}=${encodeURIComponent(val)}`).join('&');
|
|
653
|
+
|
|
654
|
+
/**
|
|
655
|
+
* @module provider/websocket
|
|
656
|
+
*/
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
const messageSync = 0;
|
|
660
|
+
const messageQueryAwareness = 3;
|
|
661
|
+
const messageAwareness = 1;
|
|
662
|
+
const messageAuth = 2;
|
|
663
|
+
|
|
664
|
+
/**
|
|
665
|
+
* encoder, decoder, provider, emitSynced, messageType
|
|
666
|
+
* @type {Array<function(encoding.Encoder, decoding.Decoder, WebsocketProvider, boolean, number):void>}
|
|
667
|
+
*/
|
|
668
|
+
const messageHandlers = [];
|
|
669
|
+
|
|
670
|
+
messageHandlers[messageSync] = (
|
|
671
|
+
encoder,
|
|
672
|
+
decoder,
|
|
673
|
+
provider,
|
|
674
|
+
emitSynced,
|
|
675
|
+
_messageType
|
|
676
|
+
) => {
|
|
677
|
+
writeVarUint(encoder, messageSync);
|
|
678
|
+
const syncMessageType = readSyncMessage(
|
|
679
|
+
decoder,
|
|
680
|
+
encoder,
|
|
681
|
+
provider.doc,
|
|
682
|
+
provider
|
|
683
|
+
);
|
|
684
|
+
if (
|
|
685
|
+
emitSynced && syncMessageType === messageYjsSyncStep2 &&
|
|
686
|
+
!provider.synced
|
|
687
|
+
) {
|
|
688
|
+
provider.synced = true;
|
|
689
|
+
}
|
|
690
|
+
};
|
|
691
|
+
|
|
692
|
+
messageHandlers[messageQueryAwareness] = (
|
|
693
|
+
encoder,
|
|
694
|
+
_decoder,
|
|
695
|
+
provider,
|
|
696
|
+
_emitSynced,
|
|
697
|
+
_messageType
|
|
698
|
+
) => {
|
|
699
|
+
writeVarUint(encoder, messageAwareness);
|
|
700
|
+
writeVarUint8Array(
|
|
701
|
+
encoder,
|
|
702
|
+
encodeAwarenessUpdate(
|
|
703
|
+
provider.awareness,
|
|
704
|
+
Array.from(provider.awareness.getStates().keys())
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
};
|
|
708
|
+
|
|
709
|
+
messageHandlers[messageAwareness] = (
|
|
710
|
+
_encoder,
|
|
711
|
+
decoder,
|
|
712
|
+
provider,
|
|
713
|
+
_emitSynced,
|
|
714
|
+
_messageType
|
|
715
|
+
) => {
|
|
716
|
+
applyAwarenessUpdate(
|
|
717
|
+
provider.awareness,
|
|
718
|
+
readVarUint8Array(decoder),
|
|
719
|
+
provider
|
|
720
|
+
);
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
messageHandlers[messageAuth] = (
|
|
724
|
+
_encoder,
|
|
725
|
+
decoder,
|
|
726
|
+
provider,
|
|
727
|
+
_emitSynced,
|
|
728
|
+
_messageType
|
|
729
|
+
) => {
|
|
730
|
+
readAuthMessage(
|
|
731
|
+
decoder,
|
|
732
|
+
provider.doc,
|
|
733
|
+
(_ydoc, reason) => permissionDeniedHandler(provider, reason)
|
|
734
|
+
);
|
|
735
|
+
};
|
|
736
|
+
|
|
737
|
+
// @todo - this should depend on awareness.outdatedTime
|
|
738
|
+
const messageReconnectTimeout = 30000;
|
|
739
|
+
|
|
740
|
+
/**
|
|
741
|
+
* @param {WebsocketProvider} provider
|
|
742
|
+
* @param {string} reason
|
|
743
|
+
*/
|
|
744
|
+
const permissionDeniedHandler = (provider, reason) =>
|
|
745
|
+
console.warn(`Permission denied to access ${provider.url}.\n${reason}`);
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* @param {WebsocketProvider} provider
|
|
749
|
+
* @param {Uint8Array} buf
|
|
750
|
+
* @param {boolean} emitSynced
|
|
751
|
+
* @return {encoding.Encoder}
|
|
752
|
+
*/
|
|
753
|
+
const readMessage = (provider, buf, emitSynced) => {
|
|
754
|
+
const decoder = createDecoder(buf);
|
|
755
|
+
const encoder = createEncoder();
|
|
756
|
+
const messageType = readVarUint(decoder);
|
|
757
|
+
const messageHandler = provider.messageHandlers[messageType];
|
|
758
|
+
if (/** @type {any} */ (messageHandler)) {
|
|
759
|
+
messageHandler(encoder, decoder, provider, emitSynced, messageType);
|
|
760
|
+
} else {
|
|
761
|
+
console.error('Unable to compute message');
|
|
762
|
+
}
|
|
763
|
+
return encoder
|
|
764
|
+
};
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Outsource this function so that a new websocket connection is created immediately.
|
|
768
|
+
* I suspect that the `ws.onclose` event is not always fired if there are network issues.
|
|
769
|
+
*
|
|
770
|
+
* @param {WebsocketProvider} provider
|
|
771
|
+
* @param {WebSocket} ws
|
|
772
|
+
* @param {CloseEvent | null} event
|
|
773
|
+
*/
|
|
774
|
+
const closeWebsocketConnection = (provider, ws, event) => {
|
|
775
|
+
if (ws === provider.ws) {
|
|
776
|
+
provider.emit('connection-close', [event, provider]);
|
|
777
|
+
provider.ws = null;
|
|
778
|
+
ws.close();
|
|
779
|
+
provider.wsconnecting = false;
|
|
780
|
+
if (provider.wsconnected) {
|
|
781
|
+
provider.wsconnected = false;
|
|
782
|
+
provider.synced = false;
|
|
783
|
+
// update awareness (all users except local left)
|
|
784
|
+
removeAwarenessStates(
|
|
785
|
+
provider.awareness,
|
|
786
|
+
Array.from(provider.awareness.getStates().keys()).filter((client) =>
|
|
787
|
+
client !== provider.doc.clientID
|
|
788
|
+
),
|
|
789
|
+
provider
|
|
790
|
+
);
|
|
791
|
+
provider.emit('status', [{
|
|
792
|
+
status: 'disconnected'
|
|
793
|
+
}]);
|
|
794
|
+
} else {
|
|
795
|
+
provider.wsUnsuccessfulReconnects++;
|
|
796
|
+
}
|
|
797
|
+
// Start with no reconnect timeout and increase timeout by
|
|
798
|
+
// using exponential backoff starting with 100ms
|
|
799
|
+
setTimeout(
|
|
800
|
+
setupWS,
|
|
801
|
+
min(
|
|
802
|
+
pow(2, provider.wsUnsuccessfulReconnects) * 100,
|
|
803
|
+
provider.maxBackoffTime
|
|
804
|
+
),
|
|
805
|
+
provider
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
/**
|
|
811
|
+
* @param {WebsocketProvider} provider
|
|
812
|
+
*/
|
|
813
|
+
const setupWS = (provider) => {
|
|
814
|
+
if (provider.shouldConnect && provider.ws === null) {
|
|
815
|
+
const websocket = new provider._WS(provider.url, provider.protocols);
|
|
816
|
+
websocket.binaryType = 'arraybuffer';
|
|
817
|
+
provider.ws = websocket;
|
|
818
|
+
provider.wsconnecting = true;
|
|
819
|
+
provider.wsconnected = false;
|
|
820
|
+
provider.synced = false;
|
|
821
|
+
|
|
822
|
+
websocket.onmessage = (event) => {
|
|
823
|
+
provider.wsLastMessageReceived = getUnixTime();
|
|
824
|
+
const encoder = readMessage(provider, new Uint8Array(event.data), true);
|
|
825
|
+
if (length(encoder) > 1) {
|
|
826
|
+
websocket.send(toUint8Array(encoder));
|
|
827
|
+
}
|
|
828
|
+
};
|
|
829
|
+
websocket.onerror = (event) => {
|
|
830
|
+
provider.emit('connection-error', [event, provider]);
|
|
831
|
+
};
|
|
832
|
+
websocket.onclose = (event) => {
|
|
833
|
+
closeWebsocketConnection(provider, websocket, event);
|
|
834
|
+
};
|
|
835
|
+
websocket.onopen = () => {
|
|
836
|
+
provider.wsLastMessageReceived = getUnixTime();
|
|
837
|
+
provider.wsconnecting = false;
|
|
838
|
+
provider.wsconnected = true;
|
|
839
|
+
provider.wsUnsuccessfulReconnects = 0;
|
|
840
|
+
provider.emit('status', [{
|
|
841
|
+
status: 'connected'
|
|
842
|
+
}]);
|
|
843
|
+
// always send sync step 1 when connected
|
|
844
|
+
const encoder = createEncoder();
|
|
845
|
+
writeVarUint(encoder, messageSync);
|
|
846
|
+
writeSyncStep1(encoder, provider.doc);
|
|
847
|
+
websocket.send(toUint8Array(encoder));
|
|
848
|
+
// broadcast local awareness state
|
|
849
|
+
if (provider.awareness.getLocalState() !== null) {
|
|
850
|
+
const encoderAwarenessState = createEncoder();
|
|
851
|
+
writeVarUint(encoderAwarenessState, messageAwareness);
|
|
852
|
+
writeVarUint8Array(
|
|
853
|
+
encoderAwarenessState,
|
|
854
|
+
encodeAwarenessUpdate(provider.awareness, [
|
|
855
|
+
provider.doc.clientID
|
|
856
|
+
])
|
|
857
|
+
);
|
|
858
|
+
websocket.send(toUint8Array(encoderAwarenessState));
|
|
859
|
+
}
|
|
860
|
+
};
|
|
861
|
+
provider.emit('status', [{
|
|
862
|
+
status: 'connecting'
|
|
863
|
+
}]);
|
|
864
|
+
}
|
|
865
|
+
};
|
|
866
|
+
|
|
867
|
+
/**
|
|
868
|
+
* @param {WebsocketProvider} provider
|
|
869
|
+
* @param {ArrayBuffer} buf
|
|
870
|
+
*/
|
|
871
|
+
const broadcastMessage = (provider, buf) => {
|
|
872
|
+
const ws = provider.ws;
|
|
873
|
+
if (provider.wsconnected && ws && ws.readyState === ws.OPEN) {
|
|
874
|
+
ws.send(buf);
|
|
875
|
+
}
|
|
876
|
+
if (provider.bcconnected) {
|
|
877
|
+
publish(provider.bcChannel, buf, provider);
|
|
878
|
+
}
|
|
879
|
+
};
|
|
880
|
+
|
|
881
|
+
/**
|
|
882
|
+
* Websocket Provider for Yjs. Creates a websocket connection to sync the shared document.
|
|
883
|
+
* The document name is attached to the provided url. I.e. the following example
|
|
884
|
+
* creates a websocket connection to http://localhost:1234/my-document-name
|
|
885
|
+
*
|
|
886
|
+
* @example
|
|
887
|
+
* import * as Y from 'yjs'
|
|
888
|
+
* import { WebsocketProvider } from 'y-websocket'
|
|
889
|
+
* const doc = new Y.Doc()
|
|
890
|
+
* const provider = new WebsocketProvider('http://localhost:1234', 'my-document-name', doc)
|
|
891
|
+
*
|
|
892
|
+
* @extends {ObservableV2<{ 'connection-close': (event: CloseEvent | null, provider: WebsocketProvider) => any, 'status': (event: { status: 'connected' | 'disconnected' | 'connecting' }) => any, 'connection-error': (event: Event, provider: WebsocketProvider) => any, 'sync': (state: boolean) => any }>}
|
|
893
|
+
*/
|
|
894
|
+
class WebsocketProvider extends ObservableV2 {
|
|
895
|
+
/**
|
|
896
|
+
* @param {string} serverUrl
|
|
897
|
+
* @param {string} roomname
|
|
898
|
+
* @param {Y.Doc} doc
|
|
899
|
+
* @param {object} opts
|
|
900
|
+
* @param {boolean} [opts.connect]
|
|
901
|
+
* @param {awarenessProtocol.Awareness} [opts.awareness]
|
|
902
|
+
* @param {Object<string,string>} [opts.params] specify url parameters
|
|
903
|
+
* @param {Array<string>} [opts.protocols] specify websocket protocols
|
|
904
|
+
* @param {typeof WebSocket} [opts.WebSocketPolyfill] Optionall provide a WebSocket polyfill
|
|
905
|
+
* @param {number} [opts.resyncInterval] Request server state every `resyncInterval` milliseconds
|
|
906
|
+
* @param {number} [opts.maxBackoffTime] Maximum amount of time to wait before trying to reconnect (we try to reconnect using exponential backoff)
|
|
907
|
+
* @param {boolean} [opts.disableBc] Disable cross-tab BroadcastChannel communication
|
|
908
|
+
*/
|
|
909
|
+
constructor (serverUrl, roomname, doc, {
|
|
910
|
+
connect = true,
|
|
911
|
+
awareness = new Awareness(doc),
|
|
912
|
+
params = {},
|
|
913
|
+
protocols = [],
|
|
914
|
+
WebSocketPolyfill = WebSocket,
|
|
915
|
+
resyncInterval = -1,
|
|
916
|
+
maxBackoffTime = 2500,
|
|
917
|
+
disableBc = false
|
|
918
|
+
} = {}) {
|
|
919
|
+
super();
|
|
920
|
+
// ensure that serverUrl does not end with /
|
|
921
|
+
while (serverUrl[serverUrl.length - 1] === '/') {
|
|
922
|
+
serverUrl = serverUrl.slice(0, serverUrl.length - 1);
|
|
923
|
+
}
|
|
924
|
+
this.serverUrl = serverUrl;
|
|
925
|
+
this.bcChannel = serverUrl + '/' + roomname;
|
|
926
|
+
this.maxBackoffTime = maxBackoffTime;
|
|
927
|
+
/**
|
|
928
|
+
* The specified url parameters. This can be safely updated. The changed parameters will be used
|
|
929
|
+
* when a new connection is established.
|
|
930
|
+
* @type {Object<string,string>}
|
|
931
|
+
*/
|
|
932
|
+
this.params = params;
|
|
933
|
+
this.protocols = protocols;
|
|
934
|
+
this.roomname = roomname;
|
|
935
|
+
this.doc = doc;
|
|
936
|
+
this._WS = WebSocketPolyfill;
|
|
937
|
+
this.awareness = awareness;
|
|
938
|
+
this.wsconnected = false;
|
|
939
|
+
this.wsconnecting = false;
|
|
940
|
+
this.bcconnected = false;
|
|
941
|
+
this.disableBc = disableBc;
|
|
942
|
+
this.wsUnsuccessfulReconnects = 0;
|
|
943
|
+
this.messageHandlers = messageHandlers.slice();
|
|
944
|
+
/**
|
|
945
|
+
* @type {boolean}
|
|
946
|
+
*/
|
|
947
|
+
this._synced = false;
|
|
948
|
+
/**
|
|
949
|
+
* @type {WebSocket?}
|
|
950
|
+
*/
|
|
951
|
+
this.ws = null;
|
|
952
|
+
this.wsLastMessageReceived = 0;
|
|
953
|
+
/**
|
|
954
|
+
* Whether to connect to other peers or not
|
|
955
|
+
* @type {boolean}
|
|
956
|
+
*/
|
|
957
|
+
this.shouldConnect = connect;
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* @type {number}
|
|
961
|
+
*/
|
|
962
|
+
this._resyncInterval = 0;
|
|
963
|
+
if (resyncInterval > 0) {
|
|
964
|
+
this._resyncInterval = /** @type {any} */ (setInterval(() => {
|
|
965
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
966
|
+
// resend sync step 1
|
|
967
|
+
const encoder = createEncoder();
|
|
968
|
+
writeVarUint(encoder, messageSync);
|
|
969
|
+
writeSyncStep1(encoder, doc);
|
|
970
|
+
this.ws.send(toUint8Array(encoder));
|
|
971
|
+
}
|
|
972
|
+
}, resyncInterval));
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* @param {ArrayBuffer} data
|
|
977
|
+
* @param {any} origin
|
|
978
|
+
*/
|
|
979
|
+
this._bcSubscriber = (data, origin) => {
|
|
980
|
+
if (origin !== this) {
|
|
981
|
+
const encoder = readMessage(this, new Uint8Array(data), false);
|
|
982
|
+
if (length(encoder) > 1) {
|
|
983
|
+
publish(this.bcChannel, toUint8Array(encoder), this);
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
/**
|
|
988
|
+
* Listens to Yjs updates and sends them to remote peers (ws and broadcastchannel)
|
|
989
|
+
* @param {Uint8Array} update
|
|
990
|
+
* @param {any} origin
|
|
991
|
+
*/
|
|
992
|
+
this._updateHandler = (update, origin) => {
|
|
993
|
+
if (origin !== this) {
|
|
994
|
+
const encoder = createEncoder();
|
|
995
|
+
writeVarUint(encoder, messageSync);
|
|
996
|
+
writeUpdate(encoder, update);
|
|
997
|
+
broadcastMessage(this, toUint8Array(encoder));
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
this.doc.on('update', this._updateHandler);
|
|
1001
|
+
/**
|
|
1002
|
+
* @param {any} changed
|
|
1003
|
+
* @param {any} _origin
|
|
1004
|
+
*/
|
|
1005
|
+
this._awarenessUpdateHandler = ({ added, updated, removed }, _origin) => {
|
|
1006
|
+
const changedClients = added.concat(updated).concat(removed);
|
|
1007
|
+
const encoder = createEncoder();
|
|
1008
|
+
writeVarUint(encoder, messageAwareness);
|
|
1009
|
+
writeVarUint8Array(
|
|
1010
|
+
encoder,
|
|
1011
|
+
encodeAwarenessUpdate(awareness, changedClients)
|
|
1012
|
+
);
|
|
1013
|
+
broadcastMessage(this, toUint8Array(encoder));
|
|
1014
|
+
};
|
|
1015
|
+
this._exitHandler = () => {
|
|
1016
|
+
removeAwarenessStates(
|
|
1017
|
+
this.awareness,
|
|
1018
|
+
[doc.clientID],
|
|
1019
|
+
'app closed'
|
|
1020
|
+
);
|
|
1021
|
+
};
|
|
1022
|
+
if (isNode && typeof process !== 'undefined') {
|
|
1023
|
+
process.on('exit', this._exitHandler);
|
|
1024
|
+
}
|
|
1025
|
+
awareness.on('update', this._awarenessUpdateHandler);
|
|
1026
|
+
this._checkInterval = /** @type {any} */ (setInterval(() => {
|
|
1027
|
+
if (
|
|
1028
|
+
this.wsconnected &&
|
|
1029
|
+
messageReconnectTimeout <
|
|
1030
|
+
getUnixTime() - this.wsLastMessageReceived
|
|
1031
|
+
) {
|
|
1032
|
+
// no message received in a long time - not even your own awareness
|
|
1033
|
+
// updates (which are updated every 15 seconds)
|
|
1034
|
+
closeWebsocketConnection(this, /** @type {WebSocket} */ (this.ws), null);
|
|
1035
|
+
}
|
|
1036
|
+
}, messageReconnectTimeout / 10));
|
|
1037
|
+
if (connect) {
|
|
1038
|
+
this.connect();
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
get url () {
|
|
1043
|
+
const encodedParams = encodeQueryParams(this.params);
|
|
1044
|
+
return this.serverUrl + '/' + this.roomname +
|
|
1045
|
+
(encodedParams.length === 0 ? '' : '?' + encodedParams)
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/**
|
|
1049
|
+
* @type {boolean}
|
|
1050
|
+
*/
|
|
1051
|
+
get synced () {
|
|
1052
|
+
return this._synced
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
set synced (state) {
|
|
1056
|
+
if (this._synced !== state) {
|
|
1057
|
+
this._synced = state;
|
|
1058
|
+
// @ts-ignore
|
|
1059
|
+
this.emit('synced', [state]);
|
|
1060
|
+
this.emit('sync', [state]);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
destroy () {
|
|
1065
|
+
if (this._resyncInterval !== 0) {
|
|
1066
|
+
clearInterval(this._resyncInterval);
|
|
1067
|
+
}
|
|
1068
|
+
clearInterval(this._checkInterval);
|
|
1069
|
+
this.disconnect();
|
|
1070
|
+
if (isNode && typeof process !== 'undefined') {
|
|
1071
|
+
process.off('exit', this._exitHandler);
|
|
1072
|
+
}
|
|
1073
|
+
this.awareness.off('update', this._awarenessUpdateHandler);
|
|
1074
|
+
this.doc.off('update', this._updateHandler);
|
|
1075
|
+
super.destroy();
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
connectBc () {
|
|
1079
|
+
if (this.disableBc) {
|
|
1080
|
+
return
|
|
1081
|
+
}
|
|
1082
|
+
if (!this.bcconnected) {
|
|
1083
|
+
subscribe(this.bcChannel, this._bcSubscriber);
|
|
1084
|
+
this.bcconnected = true;
|
|
1085
|
+
}
|
|
1086
|
+
// send sync step1 to bc
|
|
1087
|
+
// write sync step 1
|
|
1088
|
+
const encoderSync = createEncoder();
|
|
1089
|
+
writeVarUint(encoderSync, messageSync);
|
|
1090
|
+
writeSyncStep1(encoderSync, this.doc);
|
|
1091
|
+
publish(this.bcChannel, toUint8Array(encoderSync), this);
|
|
1092
|
+
// broadcast local state
|
|
1093
|
+
const encoderState = createEncoder();
|
|
1094
|
+
writeVarUint(encoderState, messageSync);
|
|
1095
|
+
writeSyncStep2(encoderState, this.doc);
|
|
1096
|
+
publish(this.bcChannel, toUint8Array(encoderState), this);
|
|
1097
|
+
// write queryAwareness
|
|
1098
|
+
const encoderAwarenessQuery = createEncoder();
|
|
1099
|
+
writeVarUint(encoderAwarenessQuery, messageQueryAwareness);
|
|
1100
|
+
publish(
|
|
1101
|
+
this.bcChannel,
|
|
1102
|
+
toUint8Array(encoderAwarenessQuery),
|
|
1103
|
+
this
|
|
1104
|
+
);
|
|
1105
|
+
// broadcast local awareness state
|
|
1106
|
+
const encoderAwarenessState = createEncoder();
|
|
1107
|
+
writeVarUint(encoderAwarenessState, messageAwareness);
|
|
1108
|
+
writeVarUint8Array(
|
|
1109
|
+
encoderAwarenessState,
|
|
1110
|
+
encodeAwarenessUpdate(this.awareness, [
|
|
1111
|
+
this.doc.clientID
|
|
1112
|
+
])
|
|
1113
|
+
);
|
|
1114
|
+
publish(
|
|
1115
|
+
this.bcChannel,
|
|
1116
|
+
toUint8Array(encoderAwarenessState),
|
|
1117
|
+
this
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
disconnectBc () {
|
|
1122
|
+
// broadcast message with local awareness state set to null (indicating disconnect)
|
|
1123
|
+
const encoder = createEncoder();
|
|
1124
|
+
writeVarUint(encoder, messageAwareness);
|
|
1125
|
+
writeVarUint8Array(
|
|
1126
|
+
encoder,
|
|
1127
|
+
encodeAwarenessUpdate(this.awareness, [
|
|
1128
|
+
this.doc.clientID
|
|
1129
|
+
], new Map())
|
|
1130
|
+
);
|
|
1131
|
+
broadcastMessage(this, toUint8Array(encoder));
|
|
1132
|
+
if (this.bcconnected) {
|
|
1133
|
+
unsubscribe(this.bcChannel, this._bcSubscriber);
|
|
1134
|
+
this.bcconnected = false;
|
|
1135
|
+
}
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
disconnect () {
|
|
1139
|
+
this.shouldConnect = false;
|
|
1140
|
+
this.disconnectBc();
|
|
1141
|
+
if (this.ws !== null) {
|
|
1142
|
+
closeWebsocketConnection(this, this.ws, null);
|
|
1143
|
+
}
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1146
|
+
connect () {
|
|
1147
|
+
this.shouldConnect = true;
|
|
1148
|
+
if (!this.wsconnected && this.ws === null) {
|
|
1149
|
+
setupWS(this);
|
|
1150
|
+
this.connectBc();
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
/**
|
|
1156
|
+
* WebSocket sync provider for real-time collaboration
|
|
1157
|
+
*/
|
|
1158
|
+
class WebSocketSyncProvider {
|
|
1159
|
+
provider;
|
|
1160
|
+
isConnected = false;
|
|
1161
|
+
constructor(docName, doc, options) {
|
|
1162
|
+
const url = options?.url || 'ws://localhost:1234';
|
|
1163
|
+
const roomName = options?.roomName || docName;
|
|
1164
|
+
this.provider = new WebsocketProvider(url, roomName, doc, {
|
|
1165
|
+
params: options?.params,
|
|
1166
|
+
protocols: options?.protocols,
|
|
1167
|
+
WebSocketPolyfill: options?.WebSocketPolyfill,
|
|
1168
|
+
awareness: options?.awareness,
|
|
1169
|
+
maxBackoffTime: options?.maxBackoffTime,
|
|
1170
|
+
disableBc: true,
|
|
1171
|
+
});
|
|
1172
|
+
this.setupEventListeners();
|
|
1173
|
+
console.info(`WebSocket Provider initialized: ${url}/${roomName}`);
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Static factory method for creating WebSocketSyncProvider with configuration options
|
|
1177
|
+
* Returns a ProviderFactory that can be used in sync configuration
|
|
1178
|
+
*/
|
|
1179
|
+
static with(options) {
|
|
1180
|
+
return {
|
|
1181
|
+
create: (docName, doc) => {
|
|
1182
|
+
return new WebSocketSyncProvider(docName, doc, options);
|
|
1183
|
+
},
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
setupEventListeners() {
|
|
1187
|
+
this.provider.on('status', ({ status }) => {
|
|
1188
|
+
if (status === 'connected') {
|
|
1189
|
+
this.isConnected = true;
|
|
1190
|
+
console.info('WebSocket connected');
|
|
1191
|
+
}
|
|
1192
|
+
else if (status === 'disconnected') {
|
|
1193
|
+
this.isConnected = false;
|
|
1194
|
+
console.info('WebSocket disconnected');
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
this.provider.on('sync', (synced) => {
|
|
1198
|
+
if (synced) {
|
|
1199
|
+
console.info('WebSocket synced');
|
|
1200
|
+
}
|
|
1201
|
+
});
|
|
1202
|
+
}
|
|
1203
|
+
async connect() {
|
|
1204
|
+
if (this.isConnected) {
|
|
1205
|
+
return;
|
|
1206
|
+
}
|
|
1207
|
+
return new Promise((resolve, reject) => {
|
|
1208
|
+
const timeout = setTimeout(() => {
|
|
1209
|
+
reject(new Error('WebSocket connection timeout'));
|
|
1210
|
+
}, 10000); // 10 second timeout
|
|
1211
|
+
const statusHandler = ({ status }) => {
|
|
1212
|
+
if (status === 'connected') {
|
|
1213
|
+
clearTimeout(timeout);
|
|
1214
|
+
this.provider.off('status', statusHandler);
|
|
1215
|
+
this.isConnected = true;
|
|
1216
|
+
resolve();
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
this.provider.on('status', statusHandler);
|
|
1220
|
+
// If already connected, resolve immediately
|
|
1221
|
+
if (this.provider.wsconnected) {
|
|
1222
|
+
clearTimeout(timeout);
|
|
1223
|
+
this.provider.off('status', statusHandler);
|
|
1224
|
+
this.isConnected = true;
|
|
1225
|
+
resolve();
|
|
1226
|
+
}
|
|
1227
|
+
});
|
|
1228
|
+
}
|
|
1229
|
+
disconnect() {
|
|
1230
|
+
if (this.provider) {
|
|
1231
|
+
this.provider.disconnect();
|
|
1232
|
+
}
|
|
1233
|
+
this.isConnected = false;
|
|
1234
|
+
}
|
|
1235
|
+
destroy() {
|
|
1236
|
+
if (this.provider) {
|
|
1237
|
+
this.provider.destroy();
|
|
1238
|
+
}
|
|
1239
|
+
this.isConnected = false;
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
/**
|
|
1244
|
+
* Hocuspocus sync provider for real-time collaboration
|
|
1245
|
+
* Supports multiplexing - multiple documents can share the same WebSocket connection
|
|
1246
|
+
*/
|
|
1247
|
+
class HocuspocusSyncProvider {
|
|
1248
|
+
provider;
|
|
1249
|
+
isConnected = false;
|
|
1250
|
+
isSynced = false;
|
|
1251
|
+
usesSharedSocket = false;
|
|
1252
|
+
// Static shared WebSocket instance for multiplexing
|
|
1253
|
+
static sharedWebSocketProvider = null;
|
|
1254
|
+
constructor(docName, doc, options) {
|
|
1255
|
+
const name = options?.name || docName;
|
|
1256
|
+
const url = options?.url || 'ws://localhost:1234';
|
|
1257
|
+
// Use provided websocketProvider or the static shared one
|
|
1258
|
+
const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
1259
|
+
if (websocketProvider) {
|
|
1260
|
+
// Multiplexing mode - use shared WebSocket connection
|
|
1261
|
+
this.usesSharedSocket = true;
|
|
1262
|
+
const config = {
|
|
1263
|
+
websocketProvider,
|
|
1264
|
+
name,
|
|
1265
|
+
document: doc,
|
|
1266
|
+
token: options?.token || null,
|
|
1267
|
+
onConnect: () => {
|
|
1268
|
+
this.isConnected = true;
|
|
1269
|
+
if (!options?.quiet) {
|
|
1270
|
+
console.info(`Hocuspocus connected: ${name}`);
|
|
1271
|
+
}
|
|
1272
|
+
if (options?.onConnect) {
|
|
1273
|
+
options.onConnect();
|
|
1274
|
+
}
|
|
1275
|
+
},
|
|
1276
|
+
onDisconnect: () => {
|
|
1277
|
+
this.isConnected = false;
|
|
1278
|
+
this.isSynced = false;
|
|
1279
|
+
if (!options?.quiet) {
|
|
1280
|
+
console.info(`Hocuspocus disconnected: ${name}`);
|
|
1281
|
+
}
|
|
1282
|
+
if (options?.onDisconnect) {
|
|
1283
|
+
options.onDisconnect();
|
|
1284
|
+
}
|
|
1285
|
+
},
|
|
1286
|
+
onSynced: () => {
|
|
1287
|
+
this.isSynced = true;
|
|
1288
|
+
if (!options?.quiet) {
|
|
1289
|
+
console.info(`Hocuspocus synced: ${name}`);
|
|
1290
|
+
}
|
|
1291
|
+
if (options?.onSynced) {
|
|
1292
|
+
options.onSynced();
|
|
1293
|
+
}
|
|
1294
|
+
},
|
|
1295
|
+
};
|
|
1296
|
+
// Add optional settings
|
|
1297
|
+
if (options?.forceSyncInterval !== undefined) {
|
|
1298
|
+
config.forceSyncInterval = options.forceSyncInterval;
|
|
1299
|
+
}
|
|
1300
|
+
if (options?.onAuthenticationFailed) {
|
|
1301
|
+
config.onAuthenticationFailed = options.onAuthenticationFailed;
|
|
1302
|
+
}
|
|
1303
|
+
if (options?.onStatus) {
|
|
1304
|
+
config.onStatus = options.onStatus;
|
|
1305
|
+
}
|
|
1306
|
+
this.provider = new HocuspocusProvider(config);
|
|
1307
|
+
// Must call attach() explicitly when using shared socket
|
|
1308
|
+
this.provider.attach();
|
|
1309
|
+
if (!options?.quiet) {
|
|
1310
|
+
console.info(`Hocuspocus Provider initialized (multiplexed): ${name}`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
else {
|
|
1314
|
+
// Standalone mode - create own WebSocket connection
|
|
1315
|
+
this.usesSharedSocket = false;
|
|
1316
|
+
const config = {
|
|
1317
|
+
url,
|
|
1318
|
+
name,
|
|
1319
|
+
document: doc,
|
|
1320
|
+
token: options?.token || null,
|
|
1321
|
+
onConnect: () => {
|
|
1322
|
+
this.isConnected = true;
|
|
1323
|
+
if (!options?.quiet) {
|
|
1324
|
+
console.info(`Hocuspocus connected: ${name}`);
|
|
1325
|
+
}
|
|
1326
|
+
if (options?.onConnect) {
|
|
1327
|
+
options.onConnect();
|
|
1328
|
+
}
|
|
1329
|
+
},
|
|
1330
|
+
onDisconnect: () => {
|
|
1331
|
+
this.isConnected = false;
|
|
1332
|
+
this.isSynced = false;
|
|
1333
|
+
if (!options?.quiet) {
|
|
1334
|
+
console.info(`Hocuspocus disconnected: ${name}`);
|
|
1335
|
+
}
|
|
1336
|
+
if (options?.onDisconnect) {
|
|
1337
|
+
options.onDisconnect();
|
|
1338
|
+
}
|
|
1339
|
+
},
|
|
1340
|
+
onSynced: () => {
|
|
1341
|
+
this.isSynced = true;
|
|
1342
|
+
if (!options?.quiet) {
|
|
1343
|
+
console.info(`Hocuspocus synced: ${name}`);
|
|
1344
|
+
}
|
|
1345
|
+
if (options?.onSynced) {
|
|
1346
|
+
options.onSynced();
|
|
1347
|
+
}
|
|
1348
|
+
},
|
|
1349
|
+
};
|
|
1350
|
+
// Add optional settings
|
|
1351
|
+
if (options?.forceSyncInterval !== undefined) {
|
|
1352
|
+
config.forceSyncInterval = options.forceSyncInterval;
|
|
1353
|
+
}
|
|
1354
|
+
if (options?.onAuthenticationFailed) {
|
|
1355
|
+
config.onAuthenticationFailed = options.onAuthenticationFailed;
|
|
1356
|
+
}
|
|
1357
|
+
if (options?.onStatus) {
|
|
1358
|
+
config.onStatus = options.onStatus;
|
|
1359
|
+
}
|
|
1360
|
+
if (options?.WebSocketPolyfill) {
|
|
1361
|
+
config.WebSocketPolyfill = options.WebSocketPolyfill;
|
|
1362
|
+
}
|
|
1363
|
+
this.provider = new HocuspocusProvider(config);
|
|
1364
|
+
if (!options?.quiet) {
|
|
1365
|
+
console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Create a shared WebSocket connection for multiplexing
|
|
1371
|
+
* Call this once to create a shared connection that multiple providers can use
|
|
1372
|
+
*/
|
|
1373
|
+
static createSharedWebSocket(options) {
|
|
1374
|
+
if (HocuspocusSyncProvider.sharedWebSocketProvider) {
|
|
1375
|
+
console.warn('Shared WebSocket already exists. Returning existing instance.');
|
|
1376
|
+
return HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
1377
|
+
}
|
|
1378
|
+
const config = {
|
|
1379
|
+
url: options.url,
|
|
1380
|
+
};
|
|
1381
|
+
if (options.WebSocketPolyfill) {
|
|
1382
|
+
config.WebSocketPolyfill = options.WebSocketPolyfill;
|
|
1383
|
+
}
|
|
1384
|
+
if (options.onConnect) {
|
|
1385
|
+
config.onConnect = options.onConnect;
|
|
1386
|
+
}
|
|
1387
|
+
if (options.onDisconnect) {
|
|
1388
|
+
config.onDisconnect = options.onDisconnect;
|
|
1389
|
+
}
|
|
1390
|
+
if (options.onStatus) {
|
|
1391
|
+
config.onStatus = options.onStatus;
|
|
1392
|
+
}
|
|
1393
|
+
HocuspocusSyncProvider.sharedWebSocketProvider = new HocuspocusProviderWebsocket(config);
|
|
1394
|
+
console.info(`Shared Hocuspocus WebSocket created: ${options.url}`);
|
|
1395
|
+
return HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
1396
|
+
}
|
|
1397
|
+
/**
|
|
1398
|
+
* Destroy the shared WebSocket connection
|
|
1399
|
+
* Call this when you're done with all multiplexed providers
|
|
1400
|
+
*/
|
|
1401
|
+
static destroySharedWebSocket() {
|
|
1402
|
+
if (HocuspocusSyncProvider.sharedWebSocketProvider) {
|
|
1403
|
+
HocuspocusSyncProvider.sharedWebSocketProvider.destroy();
|
|
1404
|
+
HocuspocusSyncProvider.sharedWebSocketProvider = null;
|
|
1405
|
+
console.info('Shared Hocuspocus WebSocket destroyed');
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
/**
|
|
1409
|
+
* Get the shared WebSocket provider instance (if it exists)
|
|
1410
|
+
*/
|
|
1411
|
+
static getSharedWebSocket() {
|
|
1412
|
+
return HocuspocusSyncProvider.sharedWebSocketProvider;
|
|
1413
|
+
}
|
|
1414
|
+
/**
|
|
1415
|
+
* Static factory method for creating HocuspocusSyncProvider with configuration options
|
|
1416
|
+
* Returns a ProviderFactory that can be used in sync configuration
|
|
1417
|
+
*/
|
|
1418
|
+
static with(options) {
|
|
1419
|
+
return {
|
|
1420
|
+
create: (docName, doc) => {
|
|
1421
|
+
return new HocuspocusSyncProvider(docName, doc, options);
|
|
1422
|
+
},
|
|
1423
|
+
};
|
|
1424
|
+
}
|
|
1425
|
+
async connect() {
|
|
1426
|
+
if (this.isSynced) {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
return new Promise((resolve, reject) => {
|
|
1430
|
+
const timeout = setTimeout(() => {
|
|
1431
|
+
reject(new Error('Hocuspocus connection timeout'));
|
|
1432
|
+
}, 10000); // 10 second timeout
|
|
1433
|
+
const syncHandler = () => {
|
|
1434
|
+
clearTimeout(timeout);
|
|
1435
|
+
this.provider.off('synced', syncHandler);
|
|
1436
|
+
resolve();
|
|
1437
|
+
};
|
|
1438
|
+
this.provider.on('synced', syncHandler);
|
|
1439
|
+
// If already synced, resolve immediately
|
|
1440
|
+
if (this.provider.isSynced) {
|
|
1441
|
+
clearTimeout(timeout);
|
|
1442
|
+
this.provider.off('synced', syncHandler);
|
|
1443
|
+
resolve();
|
|
1444
|
+
return;
|
|
1445
|
+
}
|
|
1446
|
+
// Connect if not already connected (standalone mode only)
|
|
1447
|
+
if (!this.isConnected && !this.usesSharedSocket) {
|
|
1448
|
+
this.provider.connect();
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
}
|
|
1452
|
+
disconnect() {
|
|
1453
|
+
if (this.provider) {
|
|
1454
|
+
if (this.usesSharedSocket) {
|
|
1455
|
+
// Detach from shared socket instead of disconnecting
|
|
1456
|
+
this.provider.detach();
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.provider.disconnect();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
this.isConnected = false;
|
|
1463
|
+
this.isSynced = false;
|
|
1464
|
+
}
|
|
1465
|
+
destroy() {
|
|
1466
|
+
if (this.provider) {
|
|
1467
|
+
this.provider.destroy();
|
|
1468
|
+
}
|
|
1469
|
+
this.isConnected = false;
|
|
1470
|
+
this.isSynced = false;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
export { BroadcastSyncProvider, HocuspocusSyncProvider, WebSocketSyncProvider };
|
|
1475
|
+
//# sourceMappingURL=index.js.map
|
|
1476
|
+
|
|
1477
|
+
//# sourceMappingURL=index.js.map
|