kritzel-stencil 0.4.4 → 0.4.6

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.
Files changed (128) hide show
  1. package/dist/cjs/kritzel-active-users_47.cjs.entry.js +79 -13
  2. package/dist/cjs/loader.cjs.js +1 -1
  3. package/dist/cjs/stencil.cjs.js +1 -1
  4. package/dist/collection/classes/core/viewport.class.js +14 -1
  5. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +48 -8
  6. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +84 -3
  7. package/dist/collection/constants/version.js +1 -1
  8. package/dist/components/index.js +1226 -1
  9. package/dist/components/kritzel-active-users.js +6 -1
  10. package/dist/components/kritzel-avatar.js +6 -1
  11. package/dist/components/kritzel-awareness-cursors.js +6 -1
  12. package/dist/components/kritzel-back-to-content.js +6 -1
  13. package/dist/components/kritzel-brush-style.js +64 -1
  14. package/dist/components/kritzel-button.js +6 -1
  15. package/dist/components/kritzel-color-palette.js +6 -1
  16. package/dist/components/kritzel-color.js +6 -1
  17. package/dist/components/kritzel-context-menu.js +6 -1
  18. package/dist/components/kritzel-controls.js +6 -1
  19. package/dist/components/kritzel-current-user-dialog.js +6 -1
  20. package/dist/components/kritzel-current-user.js +6 -1
  21. package/dist/components/kritzel-cursor-trail.js +6 -1
  22. package/dist/components/kritzel-dialog.js +6 -1
  23. package/dist/components/kritzel-dropdown.js +6 -1
  24. package/dist/components/kritzel-editor.js +4404 -1
  25. package/dist/components/kritzel-engine.js +6 -1
  26. package/dist/components/kritzel-export.js +6 -1
  27. package/dist/components/kritzel-font-family.js +6 -1
  28. package/dist/components/kritzel-font-size.js +6 -1
  29. package/dist/components/kritzel-font.js +6 -1
  30. package/dist/components/kritzel-icon.js +6 -1
  31. package/dist/components/kritzel-input.js +6 -1
  32. package/dist/components/kritzel-line-endings.js +6 -1
  33. package/dist/components/kritzel-loading-overlay.js +6 -1
  34. package/dist/components/kritzel-login-dialog.js +6 -1
  35. package/dist/components/kritzel-master-detail.js +6 -1
  36. package/dist/components/kritzel-menu-item.js +6 -1
  37. package/dist/components/kritzel-menu.js +6 -1
  38. package/dist/components/kritzel-more-menu.js +6 -1
  39. package/dist/components/kritzel-notification-card.js +6 -1
  40. package/dist/components/kritzel-numeric-input.js +6 -1
  41. package/dist/components/kritzel-opacity-slider.js +6 -1
  42. package/dist/components/kritzel-pill-tabs.js +6 -1
  43. package/dist/components/kritzel-portal.js +6 -1
  44. package/dist/components/kritzel-settings.js +6 -1
  45. package/dist/components/kritzel-shape-fill.js +6 -1
  46. package/dist/components/kritzel-share-dialog.js +6 -1
  47. package/dist/components/kritzel-slide-toggle.js +6 -1
  48. package/dist/components/kritzel-split-button.js +6 -1
  49. package/dist/components/kritzel-stroke-size.js +6 -1
  50. package/dist/components/kritzel-sync-indicator.js +6 -1
  51. package/dist/components/kritzel-tool-config.js +6 -1
  52. package/dist/components/kritzel-tooltip.js +6 -1
  53. package/dist/components/kritzel-utility-panel.js +6 -1
  54. package/dist/components/kritzel-watermark.js +6 -1
  55. package/dist/components/kritzel-workspace-manager.js +6 -1
  56. package/dist/components/kritzel-zoom-panel.js +6 -1
  57. package/dist/components/p-894OnDQV.js +39 -1
  58. package/dist/components/p-B43upypT.js +1500 -1
  59. package/dist/components/p-B5oHmPJb.js +97 -1
  60. package/dist/components/p-B6t5q-V4.js +63 -1
  61. package/dist/components/p-B8Xi4Clg.js +43 -1
  62. package/dist/components/p-BBmP8lVY.js +51726 -0
  63. package/dist/components/p-BDzlx2hB.js +135 -1
  64. package/dist/components/p-BFTU3MAI.js +7 -1
  65. package/dist/components/p-BFYtCsZu.js +56 -1
  66. package/dist/components/p-BI2ng9I_.js +62 -1
  67. package/dist/components/p-BTzlrIxe.js +194 -1
  68. package/dist/components/p-BmyTdQGy.js +1090 -1
  69. package/dist/components/p-BsANVili.js +150 -1
  70. package/dist/components/p-BxpKq94F.js +290 -1
  71. package/dist/components/p-C38P-Vd0.js +446 -1
  72. package/dist/components/p-C4NrrUfN.js +77 -1
  73. package/dist/components/p-CW-VyJgK.js +6 -1
  74. package/dist/components/p-CWRSGYOE.js +57 -1
  75. package/dist/components/p-CXpv9Rxe.js +106 -1
  76. package/dist/components/p-Ca-igU1f.js +320 -1
  77. package/dist/components/p-CdR76C4L.js +165 -1
  78. package/dist/components/p-Ch42cOeY.js +320 -0
  79. package/dist/components/p-ClFprFvz.js +209 -1
  80. package/dist/components/p-Cm33td2O.js +122 -1
  81. package/dist/components/p-CqLgB8JK.js +91 -1
  82. package/dist/components/p-Cr682JI_.js +156 -1
  83. package/dist/components/p-CsvMgk_f.js +62 -1
  84. package/dist/components/p-CwJwsRR3.js +277 -1
  85. package/dist/components/p-Cymum41a.js +88 -1
  86. package/dist/components/p-D09kgc5Q.js +94 -1
  87. package/dist/components/p-D30zs0kA.js +121 -1
  88. package/dist/components/p-D3y_Hauo.js +85 -1
  89. package/dist/components/p-D5jf8wAu.js +142 -1
  90. package/dist/components/p-D82VaHce.js +53 -1
  91. package/dist/components/p-DAwPPDMS.js +101 -1
  92. package/dist/components/p-DEBwmRTC.js +55 -1
  93. package/dist/components/p-DIazmg6w.js +52 -1
  94. package/dist/components/p-DJ88BrIW.js +70 -1
  95. package/dist/components/p-DJkKlUyZ.js +182 -1
  96. package/dist/components/p-DUYmwrWV.js +3267 -1
  97. package/dist/components/p-DX2nriaq.js +486 -1
  98. package/dist/components/p-Dd-pMvlr.js +20 -1
  99. package/dist/components/p-Df3BwVGy.js +299 -1
  100. package/dist/components/p-DgJ0ufzr.js +43 -1
  101. package/dist/components/p-DkUb2czk.js +70 -1
  102. package/dist/components/p-Dlp84thc.js +57 -1
  103. package/dist/components/p-JYj3Facr.js +431 -1
  104. package/dist/components/p-Ka7OWbnk.js +2304 -1
  105. package/dist/components/p-PSFAfAyd.js +49 -1
  106. package/dist/components/p-WUdQqYnu.js +118 -1
  107. package/dist/components/p-djJSnjAX.js +84 -1
  108. package/dist/components/p-e6Zpc9Vp.js +143 -1
  109. package/dist/components/p-jGOpkGDl.js +30 -1
  110. package/dist/components/p-pagW8YF7.js +99 -1
  111. package/dist/esm/kritzel-active-users_47.entry.js +79 -13
  112. package/dist/esm/loader.js +1 -1
  113. package/dist/esm/stencil.js +1 -1
  114. package/dist/stencil/index.esm.js +1172 -1
  115. package/dist/stencil/p-5b969a42.entry.js +36109 -0
  116. package/dist/stencil/p-DQuL1Twl.js +3 -1
  117. package/dist/stencil/p-Dhio9uis.js +1717 -2
  118. package/dist/stencil/p-fb32cd8f.entry.js +29 -1
  119. package/dist/stencil/p-uC7_X6yr.js +30737 -1
  120. package/dist/stencil/stencil.esm.js +21 -1
  121. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +2 -0
  122. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +7 -0
  123. package/dist/types/components.d.ts +10 -0
  124. package/dist/types/constants/version.d.ts +1 -1
  125. package/package.json +1 -1
  126. package/dist/components/p--Oazz-BP.js +0 -9
  127. package/dist/components/p-Xsiz1D5Q.js +0 -1
  128. package/dist/stencil/p-9abc21b9.entry.js +0 -9
@@ -1 +1,1172 @@
1
- import{H as a,a as T}from"./p-uC7_X6yr.js";export{O as APP_STATE_MIGRATIONS,A as AssetNotFoundError,Q as CURRENT_APP_STATE_SCHEMA_VERSION,R as CURRENT_WORKSPACE_SCHEMA_VERSION,C as DEFAULT_ASSET_STORAGE_CONFIG,D as DEFAULT_BRUSH_CONFIG,B as DEFAULT_LINE_TOOL_CONFIG,z as DEFAULT_TEXT_CONFIG,J as DE_LOCALE,G as EN_LOCALE,L as FR_LOCALE,I as IndexedDBAssetProvider,M as KritzelAlignment,t as KritzelAnchorManager,r as KritzelAssetResolver,K as KritzelBaseObject,i as KritzelBaseTool,j as KritzelBrushTool,p as KritzelCursorHelper,h as KritzelCustomElement,x as KritzelCustomElementRendererRegistry,l as KritzelEraserTool,y as KritzelFontRegistry,f as KritzelGroup,d as KritzelImage,m as KritzelImageTool,w as KritzelLicenseManager,e as KritzelLine,k as KritzelLineTool,v as KritzelLocalizationManager,c as KritzelPath,q as KritzelSelectionTool,g as KritzelShape,o as KritzelShapeTool,b as KritzelText,n as KritzelTextTool,u as KritzelThemeManager,s as KritzelWorkspace,S as ShapeType,W as WORKSPACE_EXPORT_VERSION,P as WORKSPACE_MIGRATIONS,F as darkTheme,E as lightTheme,N as runMigrations}from"./p-uC7_X6yr.js";import*as $ from"yjs";import{IndexeddbPersistence as _}from"y-indexeddb";import{WebsocketProvider as U}from"y-websocket";const H=Math.floor,X=127,V=Number.MAX_SAFE_INTEGER;class Y{constructor(){this.cpos=0,this.cbuf=new Uint8Array(100),this.bufs=[]}}const Z=()=>new Y,tt=t=>{const e=new Uint8Array((t=>{let e=t.cpos;for(let s=0;s<t.bufs.length;s++)e+=t.bufs[s].length;return e})(t));let s=0;for(let i=0;i<t.bufs.length;i++){const n=t.bufs[i];e.set(n,s),s+=n.length}return e.set(new Uint8Array(t.cbuf.buffer,0,t.cpos),s),e},et=(t,e)=>{const s=t.cbuf.length;t.cpos===s&&(t.bufs.push(t.cbuf),t.cbuf=new Uint8Array(2*s),t.cpos=0),t.cbuf[t.cpos++]=e},st=(t,e)=>{for(;e>X;)et(t,128|X&e),e=H(e/128);et(t,X&e)},it=(t,e)=>{st(t,e.byteLength),((t,e)=>{const s=t.cbuf.length,i=t.cpos,n=((t,e)=>t<e?t:e)(s-i,e.length),o=e.length-n;t.cbuf.set(e.subarray(0,n),i),t.cpos+=n,o>0&&(t.bufs.push(t.cbuf),t.cbuf=new Uint8Array(((t,e)=>t>e?t:e)(2*s,o)),t.cbuf.set(e.subarray(n)),t.cpos=o)})(t,e)},nt=t=>new Error(t),ot=nt("Unexpected end of array"),rt=nt("Integer out of Range");class ct{constructor(t){this.arr=t,this.pos=0}}const at=t=>((t,e)=>{const s=new Uint8Array(t.arr.buffer,t.pos+t.arr.byteOffset,e);return t.pos+=e,s})(t,ht(t)),ht=t=>{let e=0,s=1;const i=t.arr.length;for(;t.pos<i;){const i=t.arr[t.pos++];if(e+=(i&X)*s,s*=128,i<128)return e;if(e>V)throw rt}throw ot};class lt{type="local";doc;channel;_synced=!1;constructor(t,e,s){this.doc=e,this.channel=new BroadcastChannel(t),this.channel.onmessage=t=>{this.handleMessage(t.data)},this.doc.on("update",this.handleDocUpdate),this.broadcastSync(),setTimeout((()=>{this._synced=!0}),100),s?.quiet||console.info(`BroadcastChannel Provider initialized: ${t}`)}handleDocUpdate=(t,e)=>{if(e!==this){const e=Z();st(e,0),it(e,t),this.channel.postMessage(tt(e))}};handleMessage(t){const e=(s=new Uint8Array(t),new ct(s));var s;switch(ht(e)){case 0:const t=at(e);$.applyUpdate(this.doc,t,this);break;case 1:this.broadcastSync();break;case 2:const s=at(e),i=$.encodeStateAsUpdate(this.doc,s);if(i.length>0){const t=Z();st(t,0),it(t,i),this.channel.postMessage(tt(t))}}}broadcastSync(){const t=Z();st(t,2),it(t,$.encodeStateVector(this.doc)),this.channel.postMessage(tt(t))}async connect(){if(!this._synced)return new Promise((t=>{const e=()=>{this._synced?t():setTimeout(e,50)};e()}))}disconnect(){}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.doc.off("update",this.handleDocUpdate),this.channel.close()}}const dt=new Map;class ut{type="local";doc;cacheKey;isConnected=!1;constructor(t,e,s){this.doc=e,this.cacheKey=s?.name??t}handleUpdate=(t,e)=>{e!==this&&dt.set(this.cacheKey,$.encodeStateAsUpdate(this.doc))};async connect(){if(this.isConnected)return;const t=dt.get(this.cacheKey);t&&$.applyUpdate(this.doc,t,this),this.doc.on("update",this.handleUpdate),this.isConnected=!0}disconnect(){this.isConnected=!1}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.doc.off("update",this.handleUpdate),this.isConnected=!1}static clear(t){void 0!==t?dt.delete(t):dt.clear()}}class yt{type="local";provider;isConnected=!1;constructor(t,e,s){this.provider=new _(s?.name||t,e)}async connect(){if(!this.isConnected)return new Promise((t=>{this.provider.on("synced",(()=>{this.isConnected=!0,t()}))}))}disconnect(){this.isConnected=!1}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.provider&&this.provider.destroy(),this.isConnected=!1}}class wt{type="network";provider;isConnected=!1;_quiet=!1;get awareness(){return this.provider.awareness}constructor(t,e,s){const i=s?.url||"ws://localhost:1234",n=s?.roomName||t;this.provider=new U(i,n,e,{params:s?.params,protocols:s?.protocols,WebSocketPolyfill:s?.WebSocketPolyfill,awareness:s?.awareness,maxBackoffTime:s?.maxBackoffTime,disableBc:!0}),this._quiet=s?.quiet??!1,this.setupEventListeners(),this._quiet||console.info(`WebSocket Provider initialized: ${i}/${n}`)}static with(t){return{create:(e,s,i)=>{const n=i?{...t,...i}:t;return new wt(e,s,n)}}}setupEventListeners(){this.provider.on("status",(({status:t})=>{"connected"===t?(this.isConnected=!0,this._quiet||console.info("WebSocket connected")):"disconnected"===t&&(this.isConnected=!1,this._quiet||console.info("WebSocket disconnected"))})),this.provider.on("sync",(t=>{t&&!this._quiet&&console.info("WebSocket synced")}))}async connect(){if(!this.isConnected)return new Promise(((t,e)=>{const s=setTimeout((()=>{e(new Error("WebSocket connection timeout"))}),1e4),i=({status:e})=>{"connected"===e&&(clearTimeout(s),this.provider.off("status",i),this.isConnected=!0,t())};this.provider.on("status",i),this.provider.wsconnected&&(clearTimeout(s),this.provider.off("status",i),this.isConnected=!0,t())}))}disconnect(){this.provider&&this.provider.disconnect(),this.isConnected=!1}async reconnect(){return this.disconnect(),this.connect()}destroy(){this.provider&&this.provider.destroy(),this.isConnected=!1}}class pt{type="network";provider;isConnected=!1;isSynced=!1;usesSharedSocket=!1;isDestroyed=!1;connectTimeout=null;pendingConnectReject=null;connectionTimeoutMs;_connectionStatus="disconnected";visibilityHandler=null;onlineHandler=null;get awareness(){return this.provider.awareness}get connectionStatus(){return this._connectionStatus}static sharedWebSocketProvider=null;constructor(t,e,s){const i=s?.name||t,n=s?.url||"ws://localhost:1234";this.connectionTimeoutMs=s?.connectionTimeout??1e4;const o=s?.websocketProvider||pt.sharedWebSocketProvider,r={};void 0!==s?.delay&&(r.delay=s.delay),void 0!==s?.factor&&(r.factor=s.factor),void 0!==s?.maxAttempts&&(r.maxAttempts=s.maxAttempts),void 0!==s?.minDelay&&(r.minDelay=s.minDelay),void 0!==s?.maxDelay&&(r.maxDelay=s.maxDelay);const c=()=>{this.isDestroyed||(this.isConnected=!0,this._connectionStatus="connected",s?.quiet||console.info(`Hocuspocus connected: ${i}`),s?.onConnect&&s.onConnect())},h=()=>{this.isDestroyed||(this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected",s?.quiet||console.info(`Hocuspocus disconnected: ${i}`),s?.onDisconnect&&s.onDisconnect())},l=()=>{this.isDestroyed||(this.isSynced=!0,this._connectionStatus="synced",s?.quiet||console.info(`Hocuspocus synced: ${i}`),s?.onSynced&&s.onSynced())},d=t=>{this.isDestroyed||("connecting"===t.status&&(this._connectionStatus="connecting"),s?.onStatus&&s.onStatus(t))};if(o){this.usesSharedSocket=!0;const t={websocketProvider:o,name:i,document:e,token:s?.token||null,onStatus:d,onConnect:c,onDisconnect:h,onSynced:l,...r};void 0!==s?.forceSyncInterval&&(t.forceSyncInterval=s.forceSyncInterval),s?.onAuthenticationFailed&&(t.onAuthenticationFailed=s.onAuthenticationFailed),this.provider=new a(t),this.provider.attach(),s?.quiet||console.info(`Hocuspocus Provider initialized (multiplexed): ${i}`)}else{this.usesSharedSocket=!1;const t={url:n,name:i,document:e,token:s?.token||null,autoConnect:!1,onStatus:d,onConnect:c,onDisconnect:h,onSynced:l,...r};void 0!==s?.forceSyncInterval&&(t.forceSyncInterval=s.forceSyncInterval),s?.onAuthenticationFailed&&(t.onAuthenticationFailed=s.onAuthenticationFailed),s?.WebSocketPolyfill&&(t.WebSocketPolyfill=s.WebSocketPolyfill),this.provider=new a(t),s?.quiet||console.info(`Hocuspocus Provider initialized: ${n}/${i}`)}this.setupBrowserEventListeners()}setupBrowserEventListeners(){"undefined"!=typeof document&&(this.visibilityHandler=()=>{"visible"!==document.visibilityState||this.isConnected||this.isDestroyed||this.provider.connect()},document.addEventListener("visibilitychange",this.visibilityHandler)),"undefined"!=typeof window&&(this.onlineHandler=()=>{this.isConnected||this.isDestroyed||this.provider.connect()},window.addEventListener("online",this.onlineHandler))}removeBrowserEventListeners(){this.visibilityHandler&&"undefined"!=typeof document&&(document.removeEventListener("visibilitychange",this.visibilityHandler),this.visibilityHandler=null),this.onlineHandler&&"undefined"!=typeof window&&(window.removeEventListener("online",this.onlineHandler),this.onlineHandler=null)}static createSharedWebSocket(t){if(pt.sharedWebSocketProvider)return console.warn("Shared WebSocket already exists. Returning existing instance."),pt.sharedWebSocketProvider;const e={url:t.url};return t.WebSocketPolyfill&&(e.WebSocketPolyfill=t.WebSocketPolyfill),t.onConnect&&(e.onConnect=t.onConnect),t.onDisconnect&&(e.onDisconnect=t.onDisconnect),t.onStatus&&(e.onStatus=t.onStatus),pt.sharedWebSocketProvider=new T(e),console.info(`Shared Hocuspocus WebSocket created: ${t.url}`),pt.sharedWebSocketProvider}static destroySharedWebSocket(){pt.sharedWebSocketProvider&&(pt.sharedWebSocketProvider.destroy(),pt.sharedWebSocketProvider=null,console.info("Shared Hocuspocus WebSocket destroyed"))}static getSharedWebSocket(){return pt.sharedWebSocketProvider}static with(t){return{create:(e,s,i)=>{const n=i?{...t,...i}:t;return new pt(e,s,n)}}}async connect(){if(!this.isSynced&&!this.isDestroyed)return this._connectionStatus="connecting",new Promise(((t,e)=>{this.pendingConnectReject=e,this.connectTimeout=setTimeout((()=>{this.pendingConnectReject=null,this.connectTimeout=null,e(new Error("Hocuspocus connection timeout"))}),this.connectionTimeoutMs);const s=()=>{this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject=null,this.provider.off("synced",s),this.isDestroyed||t()};if(this.provider.on("synced",s),this.provider.isSynced)return this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject=null,this.provider.off("synced",s),void t();this.isConnected||this.usesSharedSocket||this.provider.connect()}))}async reconnect(){return this.disconnect(),this.connect()}disconnect(){this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject&&(this.pendingConnectReject=null),this.provider&&(this.usesSharedSocket?this.provider.detach():this.provider.disconnect()),this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected"}destroy(){this.isDestroyed=!0,this.connectTimeout&&(clearTimeout(this.connectTimeout),this.connectTimeout=null),this.pendingConnectReject&&(this.pendingConnectReject=null),this.removeBrowserEventListeners(),this.provider&&this.provider.destroy(),this.isConnected=!1,this.isSynced=!1,this._connectionStatus="disconnected"}}class mt{type="remote";name="HttpAssetProvider";_options;constructor(t){this._options=t}static with(t){return{create:()=>new mt(t)}}async init(){this._options.quiet||console.info("HttpAssetProvider initialized")}destroy(){}canResolve(t){return!0}async put(t,e){const s=e.id??this.generateUuid(),i={...e,id:s},n=await(this._options.headers?.())??{},o=await this._options.uploadUrl(i),r=this._options.upload??this.defaultUpload;return{id:(await r(o,t,i,n)).id??s,kind:e.kind??"file",mimeType:e.mimeType,size:t.size,createdAt:Date.now(),width:e.width,height:e.height,durationMs:e.durationMs,originalFilename:e.originalFilename}}async resolve(t){return this._options.resolveUrl(t)}async fetch(t){const e=await this._options.resolveUrl(t),s=await(this._options.headers?.())??{},i=await fetch(e,{headers:s});if(!i.ok)throw new Error(`[HttpAssetProvider] Failed to fetch asset ${t}: ${i.status} ${i.statusText}`);return i.blob()}async delete(t){if(!this._options.deleteUrl)return;const e=await this._options.deleteUrl(t),s=await(this._options.headers?.())??{},i=await fetch(e,{method:"DELETE",headers:s});if(!i.ok)throw new Error(`[HttpAssetProvider] Failed to delete asset ${t}: ${i.status} ${i.statusText}`);this._options.quiet||console.info(`HttpAssetProvider: deleted asset ${t}`)}defaultUpload=async(t,e,s,i)=>{const n=new FormData;n.append("metadata",JSON.stringify(s)),n.append("file",e,s.originalFilename||`${s.id}.${this.extensionFromMime(s.mimeType)}`);const o=await fetch(t,{method:"POST",headers:i,body:n});if(!o.ok)throw new Error(`[HttpAssetProvider] Upload failed: ${o.status} ${o.statusText}`);return(o.headers.get("content-type")||"").includes("application/json")?await o.json():{}};extensionFromMime(t){const e=t.indexOf("/");return e>=0?t.slice(e+1):"bin"}generateUuid(){if("undefined"!=typeof crypto&&"function"==typeof crypto.randomUUID)return crypto.randomUUID();const t=crypto.getRandomValues(new Uint8Array(16));t[6]=15&t[6]|64,t[8]=63&t[8]|128;const e=Array.from(t,(t=>t.toString(16).padStart(2,"0"))).join("");return`${e.slice(0,8)}-${e.slice(8,12)}-${e.slice(12,16)}-${e.slice(16,20)}-${e.slice(20)}`}}class ft{type="remote";name="PresignedAssetProvider";_options;constructor(t){this._options=t}static with(t){return{create:()=>new ft(t)}}async init(){this._options.quiet||console.info("PresignedAssetProvider initialized")}destroy(){}canResolve(t){return!0}async put(t,e){const s=await this._options.getUploadDescriptor(e),i=s.method??"PUT",n={...s.headers??{}};let o;if(n["Content-Type"]||n["content-type"]||(n["Content-Type"]=e.mimeType),"POST"===i&&s.fields){const e=new FormData;for(const[t,i]of Object.entries(s.fields))e.append(t,i);e.append("file",t),o=e,delete n["Content-Type"],delete n["content-type"]}else o=t;const r=await fetch(s.url,{method:i,headers:n,body:o});if(!r.ok)throw new Error(`[PresignedAssetProvider] Upload failed: ${r.status} ${r.statusText}`);return{id:s.id,kind:e.kind??"file",mimeType:e.mimeType,size:t.size,createdAt:Date.now(),width:e.width,height:e.height,durationMs:e.durationMs,originalFilename:e.originalFilename}}async resolve(t){return this._options.getDownloadUrl(t)}async fetch(t){const e=await this._options.getDownloadUrl(t),s=await fetch(e);if(!s.ok)throw new Error(`[PresignedAssetProvider] Failed to fetch asset ${t}: ${s.status} ${s.statusText}`);return s.blob()}async delete(t){this._options.deleteAsset&&(await this._options.deleteAsset(t),this._options.quiet||console.info(`PresignedAssetProvider: deleted asset ${t}`))}}export{lt as BroadcastSyncProvider,pt as HocuspocusSyncProvider,mt as HttpAssetProvider,ut as InMemorySyncProvider,yt as IndexedDBSyncProvider,ft as PresignedAssetProvider,wt as WebSocketSyncProvider}
1
+ import { H as HocuspocusProvider, a as HocuspocusProviderWebsocket } from './p-uC7_X6yr.js';
2
+ export { O as APP_STATE_MIGRATIONS, A as AssetNotFoundError, Q as CURRENT_APP_STATE_SCHEMA_VERSION, R as CURRENT_WORKSPACE_SCHEMA_VERSION, C as DEFAULT_ASSET_STORAGE_CONFIG, D as DEFAULT_BRUSH_CONFIG, B as DEFAULT_LINE_TOOL_CONFIG, z as DEFAULT_TEXT_CONFIG, J as DE_LOCALE, G as EN_LOCALE, L as FR_LOCALE, I as IndexedDBAssetProvider, M as KritzelAlignment, t as KritzelAnchorManager, r as KritzelAssetResolver, K as KritzelBaseObject, i as KritzelBaseTool, j as KritzelBrushTool, p as KritzelCursorHelper, h as KritzelCustomElement, x as KritzelCustomElementRendererRegistry, l as KritzelEraserTool, y as KritzelFontRegistry, f as KritzelGroup, d as KritzelImage, m as KritzelImageTool, w as KritzelLicenseManager, e as KritzelLine, k as KritzelLineTool, v as KritzelLocalizationManager, c as KritzelPath, q as KritzelSelectionTool, g as KritzelShape, o as KritzelShapeTool, b as KritzelText, n as KritzelTextTool, u as KritzelThemeManager, s as KritzelWorkspace, S as ShapeType, W as WORKSPACE_EXPORT_VERSION, P as WORKSPACE_MIGRATIONS, F as darkTheme, E as lightTheme, N as runMigrations } from './p-uC7_X6yr.js';
3
+ import * as Y from 'yjs';
4
+ import { IndexeddbPersistence } from 'y-indexeddb';
5
+ import { WebsocketProvider } from 'y-websocket';
6
+
7
+ /**
8
+ * Common Math expressions.
9
+ *
10
+ * @module math
11
+ */
12
+
13
+ const floor = Math.floor;
14
+
15
+ /**
16
+ * @function
17
+ * @param {number} a
18
+ * @param {number} b
19
+ * @return {number} The smaller element of a and b
20
+ */
21
+ const min = (a, b) => a < b ? a : b;
22
+
23
+ /**
24
+ * @function
25
+ * @param {number} a
26
+ * @param {number} b
27
+ * @return {number} The bigger element of a and b
28
+ */
29
+ const max = (a, b) => a > b ? a : b;
30
+
31
+ /* eslint-env browser */
32
+
33
+ const BIT8 = 128;
34
+ const BITS7 = 127;
35
+
36
+ /**
37
+ * Utility helpers for working with numbers.
38
+ *
39
+ * @module number
40
+ */
41
+
42
+
43
+ const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER;
44
+
45
+ /**
46
+ * Efficient schema-less binary encoding with support for variable length encoding.
47
+ *
48
+ * Use [lib0/encoding] with [lib0/decoding]. Every encoding function has a corresponding decoding function.
49
+ *
50
+ * Encodes numbers in little-endian order (least to most significant byte order)
51
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
52
+ * which is also used in Protocol Buffers.
53
+ *
54
+ * ```js
55
+ * // encoding step
56
+ * const encoder = encoding.createEncoder()
57
+ * encoding.writeVarUint(encoder, 256)
58
+ * encoding.writeVarString(encoder, 'Hello world!')
59
+ * const buf = encoding.toUint8Array(encoder)
60
+ * ```
61
+ *
62
+ * ```js
63
+ * // decoding step
64
+ * const decoder = decoding.createDecoder(buf)
65
+ * decoding.readVarUint(decoder) // => 256
66
+ * decoding.readVarString(decoder) // => 'Hello world!'
67
+ * decoding.hasContent(decoder) // => false - all data is read
68
+ * ```
69
+ *
70
+ * @module encoding
71
+ */
72
+
73
+
74
+ /**
75
+ * A BinaryEncoder handles the encoding to an Uint8Array.
76
+ */
77
+ class Encoder {
78
+ constructor () {
79
+ this.cpos = 0;
80
+ this.cbuf = new Uint8Array(100);
81
+ /**
82
+ * @type {Array<Uint8Array>}
83
+ */
84
+ this.bufs = [];
85
+ }
86
+ }
87
+
88
+ /**
89
+ * @function
90
+ * @return {Encoder}
91
+ */
92
+ const createEncoder = () => new Encoder();
93
+
94
+ /**
95
+ * The current length of the encoded data.
96
+ *
97
+ * @function
98
+ * @param {Encoder} encoder
99
+ * @return {number}
100
+ */
101
+ const length = encoder => {
102
+ let len = encoder.cpos;
103
+ for (let i = 0; i < encoder.bufs.length; i++) {
104
+ len += encoder.bufs[i].length;
105
+ }
106
+ return len
107
+ };
108
+
109
+ /**
110
+ * Transform to Uint8Array.
111
+ *
112
+ * @function
113
+ * @param {Encoder} encoder
114
+ * @return {Uint8Array<ArrayBuffer>} The created ArrayBuffer.
115
+ */
116
+ const toUint8Array = encoder => {
117
+ const uint8arr = new Uint8Array(length(encoder));
118
+ let curPos = 0;
119
+ for (let i = 0; i < encoder.bufs.length; i++) {
120
+ const d = encoder.bufs[i];
121
+ uint8arr.set(d, curPos);
122
+ curPos += d.length;
123
+ }
124
+ uint8arr.set(new Uint8Array(encoder.cbuf.buffer, 0, encoder.cpos), curPos);
125
+ return uint8arr
126
+ };
127
+
128
+ /**
129
+ * Write one byte to the encoder.
130
+ *
131
+ * @function
132
+ * @param {Encoder} encoder
133
+ * @param {number} num The byte that is to be encoded.
134
+ */
135
+ const write = (encoder, num) => {
136
+ const bufferLen = encoder.cbuf.length;
137
+ if (encoder.cpos === bufferLen) {
138
+ encoder.bufs.push(encoder.cbuf);
139
+ encoder.cbuf = new Uint8Array(bufferLen * 2);
140
+ encoder.cpos = 0;
141
+ }
142
+ encoder.cbuf[encoder.cpos++] = num;
143
+ };
144
+
145
+ /**
146
+ * Write a variable length unsigned integer. Max encodable integer is 2^53.
147
+ *
148
+ * @function
149
+ * @param {Encoder} encoder
150
+ * @param {number} num The number that is to be encoded.
151
+ */
152
+ const writeVarUint = (encoder, num) => {
153
+ while (num > BITS7) {
154
+ write(encoder, BIT8 | (BITS7 & num));
155
+ num = floor(num / 128); // shift >>> 7
156
+ }
157
+ write(encoder, BITS7 & num);
158
+ };
159
+
160
+ /**
161
+ * Append fixed-length Uint8Array to the encoder.
162
+ *
163
+ * @function
164
+ * @param {Encoder} encoder
165
+ * @param {Uint8Array} uint8Array
166
+ */
167
+ const writeUint8Array = (encoder, uint8Array) => {
168
+ const bufferLen = encoder.cbuf.length;
169
+ const cpos = encoder.cpos;
170
+ const leftCopyLen = min(bufferLen - cpos, uint8Array.length);
171
+ const rightCopyLen = uint8Array.length - leftCopyLen;
172
+ encoder.cbuf.set(uint8Array.subarray(0, leftCopyLen), cpos);
173
+ encoder.cpos += leftCopyLen;
174
+ if (rightCopyLen > 0) {
175
+ // Still something to write, write right half..
176
+ // Append new buffer
177
+ encoder.bufs.push(encoder.cbuf);
178
+ // must have at least size of remaining buffer
179
+ encoder.cbuf = new Uint8Array(max(bufferLen * 2, rightCopyLen));
180
+ // copy array
181
+ encoder.cbuf.set(uint8Array.subarray(leftCopyLen));
182
+ encoder.cpos = rightCopyLen;
183
+ }
184
+ };
185
+
186
+ /**
187
+ * Append an Uint8Array to Encoder.
188
+ *
189
+ * @function
190
+ * @param {Encoder} encoder
191
+ * @param {Uint8Array} uint8Array
192
+ */
193
+ const writeVarUint8Array = (encoder, uint8Array) => {
194
+ writeVarUint(encoder, uint8Array.byteLength);
195
+ writeUint8Array(encoder, uint8Array);
196
+ };
197
+
198
+ /**
199
+ * Error helpers.
200
+ *
201
+ * @module error
202
+ */
203
+
204
+ /**
205
+ * @param {string} s
206
+ * @return {Error}
207
+ */
208
+ /* c8 ignore next */
209
+ const create = s => new Error(s);
210
+
211
+ /**
212
+ * Efficient schema-less binary decoding with support for variable length encoding.
213
+ *
214
+ * Use [lib0/decoding] with [lib0/encoding]. Every encoding function has a corresponding decoding function.
215
+ *
216
+ * Encodes numbers in little-endian order (least to most significant byte order)
217
+ * and is compatible with Golang's binary encoding (https://golang.org/pkg/encoding/binary/)
218
+ * which is also used in Protocol Buffers.
219
+ *
220
+ * ```js
221
+ * // encoding step
222
+ * const encoder = encoding.createEncoder()
223
+ * encoding.writeVarUint(encoder, 256)
224
+ * encoding.writeVarString(encoder, 'Hello world!')
225
+ * const buf = encoding.toUint8Array(encoder)
226
+ * ```
227
+ *
228
+ * ```js
229
+ * // decoding step
230
+ * const decoder = decoding.createDecoder(buf)
231
+ * decoding.readVarUint(decoder) // => 256
232
+ * decoding.readVarString(decoder) // => 'Hello world!'
233
+ * decoding.hasContent(decoder) // => false - all data is read
234
+ * ```
235
+ *
236
+ * @module decoding
237
+ */
238
+
239
+
240
+ const errorUnexpectedEndOfArray = create('Unexpected end of array');
241
+ const errorIntegerOutOfRange = create('Integer out of Range');
242
+
243
+ /**
244
+ * A Decoder handles the decoding of an Uint8Array.
245
+ * @template {ArrayBufferLike} [Buf=ArrayBufferLike]
246
+ */
247
+ class Decoder {
248
+ /**
249
+ * @param {Uint8Array<Buf>} uint8Array Binary data to decode
250
+ */
251
+ constructor (uint8Array) {
252
+ /**
253
+ * Decoding target.
254
+ *
255
+ * @type {Uint8Array<Buf>}
256
+ */
257
+ this.arr = uint8Array;
258
+ /**
259
+ * Current decoding position.
260
+ *
261
+ * @type {number}
262
+ */
263
+ this.pos = 0;
264
+ }
265
+ }
266
+
267
+ /**
268
+ * @function
269
+ * @template {ArrayBufferLike} Buf
270
+ * @param {Uint8Array<Buf>} uint8Array
271
+ * @return {Decoder<Buf>}
272
+ */
273
+ const createDecoder = uint8Array => new Decoder(uint8Array);
274
+
275
+ /**
276
+ * Create an Uint8Array view of the next `len` bytes and advance the position by `len`.
277
+ *
278
+ * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
279
+ * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
280
+ *
281
+ * @function
282
+ * @template {ArrayBufferLike} Buf
283
+ * @param {Decoder<Buf>} decoder The decoder instance
284
+ * @param {number} len The length of bytes to read
285
+ * @return {Uint8Array<Buf>}
286
+ */
287
+ const readUint8Array = (decoder, len) => {
288
+ const view = new Uint8Array(decoder.arr.buffer, decoder.pos + decoder.arr.byteOffset, len);
289
+ decoder.pos += len;
290
+ return view
291
+ };
292
+
293
+ /**
294
+ * Read variable length Uint8Array.
295
+ *
296
+ * Important: The Uint8Array still points to the underlying ArrayBuffer. Make sure to discard the result as soon as possible to prevent any memory leaks.
297
+ * Use `buffer.copyUint8Array` to copy the result into a new Uint8Array.
298
+ *
299
+ * @function
300
+ * @template {ArrayBufferLike} Buf
301
+ * @param {Decoder<Buf>} decoder
302
+ * @return {Uint8Array<Buf>}
303
+ */
304
+ const readVarUint8Array = decoder => readUint8Array(decoder, readVarUint(decoder));
305
+
306
+ /**
307
+ * Read unsigned integer (32bit) with variable length.
308
+ * 1/8th of the storage is used as encoding overhead.
309
+ * * numbers < 2^7 is stored in one bytlength
310
+ * * numbers < 2^14 is stored in two bylength
311
+ *
312
+ * @function
313
+ * @param {Decoder} decoder
314
+ * @return {number} An unsigned integer.length
315
+ */
316
+ const readVarUint = decoder => {
317
+ let num = 0;
318
+ let mult = 1;
319
+ const len = decoder.arr.length;
320
+ while (decoder.pos < len) {
321
+ const r = decoder.arr[decoder.pos++];
322
+ // num = num | ((r & binary.BITS7) << len)
323
+ num = num + (r & BITS7) * mult; // shift $r << (7*#iterations) and add it to num
324
+ mult *= 128; // next iteration, shift 7 "more" to the left
325
+ if (r < BIT8) {
326
+ return num
327
+ }
328
+ /* c8 ignore start */
329
+ if (num > MAX_SAFE_INTEGER) {
330
+ throw errorIntegerOutOfRange
331
+ }
332
+ /* c8 ignore stop */
333
+ }
334
+ throw errorUnexpectedEndOfArray
335
+ };
336
+
337
+ /**
338
+ * BroadcastChannel sync provider for cross-tab synchronization
339
+ * This is a lightweight alternative to y-webrtc for browser-tab-only sync
340
+ */
341
+ class BroadcastSyncProvider {
342
+ type = 'local';
343
+ doc;
344
+ channel;
345
+ _synced = false;
346
+ constructor(docName, doc, _options) {
347
+ this.doc = doc;
348
+ this.channel = new BroadcastChannel(docName);
349
+ // Handle incoming messages from other tabs
350
+ this.channel.onmessage = (event) => {
351
+ this.handleMessage(event.data);
352
+ };
353
+ // Listen to document updates and broadcast them
354
+ this.doc.on('update', this.handleDocUpdate);
355
+ // Send initial sync request
356
+ this.broadcastSync();
357
+ // Mark as synced after a short delay (to receive any pending updates)
358
+ setTimeout(() => {
359
+ this._synced = true;
360
+ }, 100);
361
+ if (!_options?.quiet) {
362
+ console.info(`BroadcastChannel Provider initialized: ${docName}`);
363
+ }
364
+ }
365
+ handleDocUpdate = (update, origin) => {
366
+ // Don't broadcast updates that came from other tabs (to prevent loops)
367
+ if (origin !== this) {
368
+ const encoder = createEncoder();
369
+ writeVarUint(encoder, 0); // Message type: sync update
370
+ writeVarUint8Array(encoder, update);
371
+ this.channel.postMessage(toUint8Array(encoder));
372
+ }
373
+ };
374
+ handleMessage(message) {
375
+ const decoder = createDecoder(new Uint8Array(message));
376
+ const messageType = readVarUint(decoder);
377
+ switch (messageType) {
378
+ case 0: // Sync update
379
+ const update = readVarUint8Array(decoder);
380
+ Y.applyUpdate(this.doc, update, this);
381
+ break;
382
+ case 1: // Sync request
383
+ this.broadcastSync();
384
+ break;
385
+ case 2: // Sync response
386
+ const stateVector = readVarUint8Array(decoder);
387
+ const updateResponse = Y.encodeStateAsUpdate(this.doc, stateVector);
388
+ if (updateResponse.length > 0) {
389
+ const encoder = createEncoder();
390
+ writeVarUint(encoder, 0); // Send as regular update
391
+ writeVarUint8Array(encoder, updateResponse);
392
+ this.channel.postMessage(toUint8Array(encoder));
393
+ }
394
+ break;
395
+ }
396
+ }
397
+ broadcastSync() {
398
+ // Broadcast our current state vector to request missing updates
399
+ const encoder = createEncoder();
400
+ writeVarUint(encoder, 2); // Message type: sync response
401
+ writeVarUint8Array(encoder, Y.encodeStateVector(this.doc));
402
+ this.channel.postMessage(toUint8Array(encoder));
403
+ }
404
+ async connect() {
405
+ // Wait for initial sync to complete
406
+ if (this._synced) {
407
+ return;
408
+ }
409
+ return new Promise((resolve) => {
410
+ const checkSync = () => {
411
+ if (this._synced) {
412
+ resolve();
413
+ }
414
+ else {
415
+ setTimeout(checkSync, 50);
416
+ }
417
+ };
418
+ checkSync();
419
+ });
420
+ }
421
+ disconnect() {
422
+ // BroadcastChannel doesn't have explicit disconnect
423
+ }
424
+ async reconnect() {
425
+ this.disconnect();
426
+ return this.connect();
427
+ }
428
+ destroy() {
429
+ this.doc.off('update', this.handleDocUpdate);
430
+ this.channel.close();
431
+ }
432
+ }
433
+
434
+ /**
435
+ * Module-scoped cache that survives component removal and remounting
436
+ * within the same JS runtime. Cleared on page reload.
437
+ */
438
+ const sessionCache = new Map();
439
+ /**
440
+ * In-memory sync provider for session-scoped persistence.
441
+ *
442
+ * State survives the component being removed from the DOM and later
443
+ * re-added within the same page session, but is lost on page reload.
444
+ * Intended for documentation demos where durable IndexedDB persistence
445
+ * is not desired.
446
+ */
447
+ class InMemorySyncProvider {
448
+ type = 'local';
449
+ doc;
450
+ cacheKey;
451
+ isConnected = false;
452
+ constructor(docName, doc, options) {
453
+ this.doc = doc;
454
+ this.cacheKey = options?.name ?? docName;
455
+ }
456
+ handleUpdate = (_update, origin) => {
457
+ // Skip updates that originated from connect() rehydration to avoid a
458
+ // redundant re-encode of the state we just applied.
459
+ if (origin === this) {
460
+ return;
461
+ }
462
+ sessionCache.set(this.cacheKey, Y.encodeStateAsUpdate(this.doc));
463
+ };
464
+ async connect() {
465
+ if (this.isConnected) {
466
+ return;
467
+ }
468
+ const cached = sessionCache.get(this.cacheKey);
469
+ if (cached) {
470
+ Y.applyUpdate(this.doc, cached, this);
471
+ }
472
+ this.doc.on('update', this.handleUpdate);
473
+ this.isConnected = true;
474
+ }
475
+ disconnect() {
476
+ this.isConnected = false;
477
+ }
478
+ async reconnect() {
479
+ this.disconnect();
480
+ return this.connect();
481
+ }
482
+ destroy() {
483
+ this.doc.off('update', this.handleUpdate);
484
+ this.isConnected = false;
485
+ }
486
+ /**
487
+ * Clears the in-memory cache.
488
+ *
489
+ * @param name - If provided, only the entry with this cache key is removed.
490
+ * If omitted, all entries are cleared.
491
+ */
492
+ static clear(name) {
493
+ if (name !== undefined) {
494
+ sessionCache.delete(name);
495
+ }
496
+ else {
497
+ sessionCache.clear();
498
+ }
499
+ }
500
+ }
501
+
502
+ /**
503
+ * IndexedDB sync provider for local persistence
504
+ */
505
+ class IndexedDBSyncProvider {
506
+ type = 'local';
507
+ provider;
508
+ isConnected = false;
509
+ constructor(docName, doc, options) {
510
+ const dbName = options?.name || docName;
511
+ this.provider = new IndexeddbPersistence(dbName, doc);
512
+ }
513
+ async connect() {
514
+ if (this.isConnected) {
515
+ return;
516
+ }
517
+ return new Promise(resolve => {
518
+ this.provider.on('synced', () => {
519
+ this.isConnected = true;
520
+ resolve();
521
+ });
522
+ });
523
+ }
524
+ disconnect() {
525
+ // IndexedDB doesn't need explicit disconnect
526
+ this.isConnected = false;
527
+ }
528
+ async reconnect() {
529
+ this.disconnect();
530
+ return this.connect();
531
+ }
532
+ destroy() {
533
+ if (this.provider) {
534
+ this.provider.destroy();
535
+ }
536
+ this.isConnected = false;
537
+ }
538
+ }
539
+
540
+ /**
541
+ * WebSocket sync provider for real-time collaboration
542
+ */
543
+ class WebSocketSyncProvider {
544
+ type = 'network';
545
+ provider;
546
+ isConnected = false;
547
+ _quiet = false;
548
+ get awareness() {
549
+ return this.provider.awareness;
550
+ }
551
+ constructor(docName, doc, options) {
552
+ const url = options?.url || 'ws://localhost:1234';
553
+ const roomName = options?.roomName || docName;
554
+ this.provider = new WebsocketProvider(url, roomName, doc, {
555
+ params: options?.params,
556
+ protocols: options?.protocols,
557
+ WebSocketPolyfill: options?.WebSocketPolyfill,
558
+ awareness: options?.awareness,
559
+ maxBackoffTime: options?.maxBackoffTime,
560
+ disableBc: true,
561
+ });
562
+ this._quiet = options?.quiet ?? false;
563
+ this.setupEventListeners();
564
+ if (!this._quiet) {
565
+ console.info(`WebSocket Provider initialized: ${url}/${roomName}`);
566
+ }
567
+ }
568
+ /**
569
+ * Static factory method for creating WebSocketSyncProvider with configuration options
570
+ * Returns a ProviderFactory that can be used in sync configuration
571
+ */
572
+ static with(options) {
573
+ return {
574
+ create: (docName, doc, runtimeOptions) => {
575
+ const mergedOptions = runtimeOptions ? { ...options, ...runtimeOptions } : options;
576
+ return new WebSocketSyncProvider(docName, doc, mergedOptions);
577
+ },
578
+ };
579
+ }
580
+ setupEventListeners() {
581
+ this.provider.on('status', ({ status }) => {
582
+ if (status === 'connected') {
583
+ this.isConnected = true;
584
+ if (!this._quiet) {
585
+ console.info('WebSocket connected');
586
+ }
587
+ }
588
+ else if (status === 'disconnected') {
589
+ this.isConnected = false;
590
+ if (!this._quiet) {
591
+ console.info('WebSocket disconnected');
592
+ }
593
+ }
594
+ });
595
+ this.provider.on('sync', (synced) => {
596
+ if (synced && !this._quiet) {
597
+ console.info('WebSocket synced');
598
+ }
599
+ });
600
+ }
601
+ async connect() {
602
+ if (this.isConnected) {
603
+ return;
604
+ }
605
+ return new Promise((resolve, reject) => {
606
+ const timeout = setTimeout(() => {
607
+ reject(new Error('WebSocket connection timeout'));
608
+ }, 10000); // 10 second timeout
609
+ const statusHandler = ({ status }) => {
610
+ if (status === 'connected') {
611
+ clearTimeout(timeout);
612
+ this.provider.off('status', statusHandler);
613
+ this.isConnected = true;
614
+ resolve();
615
+ }
616
+ };
617
+ this.provider.on('status', statusHandler);
618
+ // If already connected, resolve immediately
619
+ if (this.provider.wsconnected) {
620
+ clearTimeout(timeout);
621
+ this.provider.off('status', statusHandler);
622
+ this.isConnected = true;
623
+ resolve();
624
+ }
625
+ });
626
+ }
627
+ disconnect() {
628
+ if (this.provider) {
629
+ this.provider.disconnect();
630
+ }
631
+ this.isConnected = false;
632
+ }
633
+ async reconnect() {
634
+ this.disconnect();
635
+ return this.connect();
636
+ }
637
+ destroy() {
638
+ if (this.provider) {
639
+ this.provider.destroy();
640
+ }
641
+ this.isConnected = false;
642
+ }
643
+ }
644
+
645
+ /**
646
+ * Hocuspocus sync provider for real-time collaboration
647
+ * Supports multiplexing - multiple documents can share the same WebSocket connection
648
+ */
649
+ class HocuspocusSyncProvider {
650
+ type = 'network';
651
+ provider;
652
+ isConnected = false;
653
+ isSynced = false;
654
+ usesSharedSocket = false;
655
+ isDestroyed = false;
656
+ connectTimeout = null;
657
+ pendingConnectReject = null;
658
+ connectionTimeoutMs;
659
+ _connectionStatus = 'disconnected';
660
+ visibilityHandler = null;
661
+ onlineHandler = null;
662
+ get awareness() {
663
+ return this.provider.awareness;
664
+ }
665
+ get connectionStatus() {
666
+ return this._connectionStatus;
667
+ }
668
+ // Static shared WebSocket instance for multiplexing
669
+ static sharedWebSocketProvider = null;
670
+ constructor(docName, doc, options) {
671
+ const name = options?.name || docName;
672
+ const url = options?.url || 'ws://localhost:1234';
673
+ this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
674
+ // Use provided websocketProvider or the static shared one
675
+ const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
676
+ // Build reconnect config from options
677
+ const reconnectConfig = {};
678
+ if (options?.delay !== undefined)
679
+ reconnectConfig.delay = options.delay;
680
+ if (options?.factor !== undefined)
681
+ reconnectConfig.factor = options.factor;
682
+ if (options?.maxAttempts !== undefined)
683
+ reconnectConfig.maxAttempts = options.maxAttempts;
684
+ if (options?.minDelay !== undefined)
685
+ reconnectConfig.minDelay = options.minDelay;
686
+ if (options?.maxDelay !== undefined)
687
+ reconnectConfig.maxDelay = options.maxDelay;
688
+ const onConnect = () => {
689
+ if (this.isDestroyed) {
690
+ return;
691
+ }
692
+ this.isConnected = true;
693
+ this._connectionStatus = 'connected';
694
+ if (!options?.quiet) {
695
+ console.info(`Hocuspocus connected: ${name}`);
696
+ }
697
+ if (options?.onConnect) {
698
+ options.onConnect();
699
+ }
700
+ };
701
+ const onDisconnect = () => {
702
+ if (this.isDestroyed) {
703
+ return;
704
+ }
705
+ this.isConnected = false;
706
+ this.isSynced = false;
707
+ this._connectionStatus = 'disconnected';
708
+ if (!options?.quiet) {
709
+ console.info(`Hocuspocus disconnected: ${name}`);
710
+ }
711
+ if (options?.onDisconnect) {
712
+ options.onDisconnect();
713
+ }
714
+ };
715
+ const onSynced = () => {
716
+ if (this.isDestroyed) {
717
+ return;
718
+ }
719
+ this.isSynced = true;
720
+ this._connectionStatus = 'synced';
721
+ if (!options?.quiet) {
722
+ console.info(`Hocuspocus synced: ${name}`);
723
+ }
724
+ if (options?.onSynced) {
725
+ options.onSynced();
726
+ }
727
+ };
728
+ const onStatus = (data) => {
729
+ if (this.isDestroyed) {
730
+ return;
731
+ }
732
+ if (data.status === 'connecting') {
733
+ this._connectionStatus = 'connecting';
734
+ }
735
+ if (options?.onStatus) {
736
+ options.onStatus(data);
737
+ }
738
+ };
739
+ if (websocketProvider) {
740
+ // Multiplexing mode - use shared WebSocket connection
741
+ this.usesSharedSocket = true;
742
+ const config = {
743
+ websocketProvider,
744
+ name,
745
+ document: doc,
746
+ token: options?.token || null,
747
+ onStatus,
748
+ onConnect,
749
+ onDisconnect,
750
+ onSynced,
751
+ ...reconnectConfig,
752
+ };
753
+ // Add optional settings
754
+ if (options?.forceSyncInterval !== undefined) {
755
+ config.forceSyncInterval = options.forceSyncInterval;
756
+ }
757
+ if (options?.onAuthenticationFailed) {
758
+ config.onAuthenticationFailed = options.onAuthenticationFailed;
759
+ }
760
+ this.provider = new HocuspocusProvider(config);
761
+ // Must call attach() explicitly when using shared socket
762
+ this.provider.attach();
763
+ if (!options?.quiet) {
764
+ console.info(`Hocuspocus Provider initialized (multiplexed): ${name}`);
765
+ }
766
+ }
767
+ else {
768
+ // Standalone mode - create own WebSocket connection
769
+ this.usesSharedSocket = false;
770
+ const config = {
771
+ url,
772
+ name,
773
+ document: doc,
774
+ token: options?.token || null,
775
+ autoConnect: false,
776
+ onStatus,
777
+ onConnect,
778
+ onDisconnect,
779
+ onSynced,
780
+ ...reconnectConfig,
781
+ };
782
+ // Add optional settings
783
+ if (options?.forceSyncInterval !== undefined) {
784
+ config.forceSyncInterval = options.forceSyncInterval;
785
+ }
786
+ if (options?.onAuthenticationFailed) {
787
+ config.onAuthenticationFailed = options.onAuthenticationFailed;
788
+ }
789
+ if (options?.WebSocketPolyfill) {
790
+ config.WebSocketPolyfill = options.WebSocketPolyfill;
791
+ }
792
+ this.provider = new HocuspocusProvider(config);
793
+ if (!options?.quiet) {
794
+ console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
795
+ }
796
+ }
797
+ this.setupBrowserEventListeners();
798
+ }
799
+ setupBrowserEventListeners() {
800
+ if (typeof document !== 'undefined') {
801
+ this.visibilityHandler = () => {
802
+ if (document.visibilityState === 'visible' && !this.isConnected && !this.isDestroyed) {
803
+ this.provider.connect();
804
+ }
805
+ };
806
+ document.addEventListener('visibilitychange', this.visibilityHandler);
807
+ }
808
+ if (typeof window !== 'undefined') {
809
+ this.onlineHandler = () => {
810
+ if (!this.isConnected && !this.isDestroyed) {
811
+ this.provider.connect();
812
+ }
813
+ };
814
+ window.addEventListener('online', this.onlineHandler);
815
+ }
816
+ }
817
+ removeBrowserEventListeners() {
818
+ if (this.visibilityHandler && typeof document !== 'undefined') {
819
+ document.removeEventListener('visibilitychange', this.visibilityHandler);
820
+ this.visibilityHandler = null;
821
+ }
822
+ if (this.onlineHandler && typeof window !== 'undefined') {
823
+ window.removeEventListener('online', this.onlineHandler);
824
+ this.onlineHandler = null;
825
+ }
826
+ }
827
+ /**
828
+ * Create a shared WebSocket connection for multiplexing
829
+ * Call this once to create a shared connection that multiple providers can use
830
+ */
831
+ static createSharedWebSocket(options) {
832
+ if (HocuspocusSyncProvider.sharedWebSocketProvider) {
833
+ console.warn('Shared WebSocket already exists. Returning existing instance.');
834
+ return HocuspocusSyncProvider.sharedWebSocketProvider;
835
+ }
836
+ const config = {
837
+ url: options.url,
838
+ };
839
+ if (options.WebSocketPolyfill) {
840
+ config.WebSocketPolyfill = options.WebSocketPolyfill;
841
+ }
842
+ if (options.onConnect) {
843
+ config.onConnect = options.onConnect;
844
+ }
845
+ if (options.onDisconnect) {
846
+ config.onDisconnect = options.onDisconnect;
847
+ }
848
+ if (options.onStatus) {
849
+ config.onStatus = options.onStatus;
850
+ }
851
+ HocuspocusSyncProvider.sharedWebSocketProvider = new HocuspocusProviderWebsocket(config);
852
+ console.info(`Shared Hocuspocus WebSocket created: ${options.url}`);
853
+ return HocuspocusSyncProvider.sharedWebSocketProvider;
854
+ }
855
+ /**
856
+ * Destroy the shared WebSocket connection
857
+ * Call this when you're done with all multiplexed providers
858
+ */
859
+ static destroySharedWebSocket() {
860
+ if (HocuspocusSyncProvider.sharedWebSocketProvider) {
861
+ HocuspocusSyncProvider.sharedWebSocketProvider.destroy();
862
+ HocuspocusSyncProvider.sharedWebSocketProvider = null;
863
+ console.info('Shared Hocuspocus WebSocket destroyed');
864
+ }
865
+ }
866
+ /**
867
+ * Get the shared WebSocket provider instance (if it exists)
868
+ */
869
+ static getSharedWebSocket() {
870
+ return HocuspocusSyncProvider.sharedWebSocketProvider;
871
+ }
872
+ /**
873
+ * Static factory method for creating HocuspocusSyncProvider with configuration options
874
+ * Returns a ProviderFactory that can be used in sync configuration
875
+ */
876
+ static with(options) {
877
+ return {
878
+ create: (docName, doc, runtimeOptions) => {
879
+ const mergedOptions = runtimeOptions ? { ...options, ...runtimeOptions } : options;
880
+ return new HocuspocusSyncProvider(docName, doc, mergedOptions);
881
+ },
882
+ };
883
+ }
884
+ async connect() {
885
+ if (this.isSynced || this.isDestroyed) {
886
+ return;
887
+ }
888
+ this._connectionStatus = 'connecting';
889
+ return new Promise((resolve, reject) => {
890
+ // Store reject function so we can cancel the connection if destroyed
891
+ this.pendingConnectReject = reject;
892
+ this.connectTimeout = setTimeout(() => {
893
+ this.pendingConnectReject = null;
894
+ this.connectTimeout = null;
895
+ reject(new Error('Hocuspocus connection timeout'));
896
+ }, this.connectionTimeoutMs);
897
+ const syncHandler = () => {
898
+ if (this.connectTimeout) {
899
+ clearTimeout(this.connectTimeout);
900
+ this.connectTimeout = null;
901
+ }
902
+ this.pendingConnectReject = null;
903
+ this.provider.off('synced', syncHandler);
904
+ if (!this.isDestroyed) {
905
+ resolve();
906
+ }
907
+ };
908
+ this.provider.on('synced', syncHandler);
909
+ // If already synced, resolve immediately
910
+ if (this.provider.isSynced) {
911
+ if (this.connectTimeout) {
912
+ clearTimeout(this.connectTimeout);
913
+ this.connectTimeout = null;
914
+ }
915
+ this.pendingConnectReject = null;
916
+ this.provider.off('synced', syncHandler);
917
+ resolve();
918
+ return;
919
+ }
920
+ // Connect if not already connected (standalone mode only)
921
+ if (!this.isConnected && !this.usesSharedSocket) {
922
+ this.provider.connect();
923
+ }
924
+ });
925
+ }
926
+ async reconnect() {
927
+ this.disconnect();
928
+ return this.connect();
929
+ }
930
+ disconnect() {
931
+ // Cancel any pending connection attempt
932
+ if (this.connectTimeout) {
933
+ clearTimeout(this.connectTimeout);
934
+ this.connectTimeout = null;
935
+ }
936
+ if (this.pendingConnectReject) {
937
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
938
+ }
939
+ if (this.provider) {
940
+ if (this.usesSharedSocket) {
941
+ // Detach from shared socket instead of disconnecting
942
+ this.provider.detach();
943
+ }
944
+ else {
945
+ this.provider.disconnect();
946
+ }
947
+ }
948
+ this.isConnected = false;
949
+ this.isSynced = false;
950
+ this._connectionStatus = 'disconnected';
951
+ }
952
+ destroy() {
953
+ // Mark as destroyed first to prevent any callbacks from doing work
954
+ this.isDestroyed = true;
955
+ // Cancel any pending connection attempt
956
+ if (this.connectTimeout) {
957
+ clearTimeout(this.connectTimeout);
958
+ this.connectTimeout = null;
959
+ }
960
+ if (this.pendingConnectReject) {
961
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
962
+ }
963
+ this.removeBrowserEventListeners();
964
+ if (this.provider) {
965
+ this.provider.destroy();
966
+ }
967
+ this.isConnected = false;
968
+ this.isSynced = false;
969
+ this._connectionStatus = 'disconnected';
970
+ }
971
+ }
972
+
973
+ /**
974
+ * Remote asset storage provider backed by a plain HTTP endpoint.
975
+ *
976
+ * The host application is responsible for exposing a REST-ish backend
977
+ * that accepts a multipart upload and serves the stored asset at a
978
+ * stable URL. Authentication is injected via `headers`.
979
+ */
980
+ class HttpAssetProvider {
981
+ type = 'remote';
982
+ name = 'HttpAssetProvider';
983
+ _options;
984
+ constructor(options) {
985
+ this._options = options;
986
+ }
987
+ /**
988
+ * Factory helper enabling use as `HttpAssetProvider.with({ ... })` inside
989
+ * a `KritzelAssetStorageConfig.providers` array.
990
+ */
991
+ static with(options) {
992
+ return { create: () => new HttpAssetProvider(options) };
993
+ }
994
+ async init() {
995
+ if (!this._options.quiet) {
996
+ console.info('HttpAssetProvider initialized');
997
+ }
998
+ }
999
+ destroy() {
1000
+ // No-op; no resources to release.
1001
+ }
1002
+ canResolve(_id) {
1003
+ // Remote providers don't know without a round-trip; assume yes.
1004
+ return true;
1005
+ }
1006
+ async put(blob, metadata) {
1007
+ const id = metadata.id ?? this.generateUuid();
1008
+ const fullMetadata = { ...metadata, id };
1009
+ const headers = (await this._options.headers?.()) ?? {};
1010
+ const url = await this._options.uploadUrl(fullMetadata);
1011
+ const uploader = this._options.upload ?? this.defaultUpload;
1012
+ const response = await uploader(url, blob, fullMetadata, headers);
1013
+ return {
1014
+ id: response.id ?? id,
1015
+ kind: metadata.kind ?? 'file',
1016
+ mimeType: metadata.mimeType,
1017
+ size: blob.size,
1018
+ createdAt: Date.now(),
1019
+ width: metadata.width,
1020
+ height: metadata.height,
1021
+ durationMs: metadata.durationMs,
1022
+ originalFilename: metadata.originalFilename,
1023
+ };
1024
+ }
1025
+ async resolve(id) {
1026
+ return this._options.resolveUrl(id);
1027
+ }
1028
+ async fetch(id) {
1029
+ const url = await this._options.resolveUrl(id);
1030
+ const headers = (await this._options.headers?.()) ?? {};
1031
+ const res = await fetch(url, { headers });
1032
+ if (!res.ok) {
1033
+ throw new Error(`[HttpAssetProvider] Failed to fetch asset ${id}: ${res.status} ${res.statusText}`);
1034
+ }
1035
+ return res.blob();
1036
+ }
1037
+ async delete(id) {
1038
+ if (!this._options.deleteUrl)
1039
+ return;
1040
+ const url = await this._options.deleteUrl(id);
1041
+ const headers = (await this._options.headers?.()) ?? {};
1042
+ const res = await fetch(url, { method: 'DELETE', headers });
1043
+ if (!res.ok) {
1044
+ throw new Error(`[HttpAssetProvider] Failed to delete asset ${id}: ${res.status} ${res.statusText}`);
1045
+ }
1046
+ if (!this._options.quiet) {
1047
+ console.info(`HttpAssetProvider: deleted asset ${id}`);
1048
+ }
1049
+ }
1050
+ defaultUpload = async (url, blob, metadata, headers) => {
1051
+ const form = new FormData();
1052
+ form.append('metadata', JSON.stringify(metadata));
1053
+ form.append('file', blob, metadata.originalFilename || `${metadata.id}.${this.extensionFromMime(metadata.mimeType)}`);
1054
+ const res = await fetch(url, {
1055
+ method: 'POST',
1056
+ headers,
1057
+ body: form,
1058
+ });
1059
+ if (!res.ok) {
1060
+ throw new Error(`[HttpAssetProvider] Upload failed: ${res.status} ${res.statusText}`);
1061
+ }
1062
+ // Try to parse a JSON response; tolerate empty/plain-text bodies.
1063
+ const contentType = res.headers.get('content-type') || '';
1064
+ if (contentType.includes('application/json')) {
1065
+ return (await res.json());
1066
+ }
1067
+ return {};
1068
+ };
1069
+ extensionFromMime(mime) {
1070
+ const slash = mime.indexOf('/');
1071
+ return slash >= 0 ? mime.slice(slash + 1) : 'bin';
1072
+ }
1073
+ generateUuid() {
1074
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
1075
+ return crypto.randomUUID();
1076
+ }
1077
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
1078
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
1079
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
1080
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
1081
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
1082
+ }
1083
+ }
1084
+
1085
+ /**
1086
+ * Remote asset storage provider for pre-signed URL uploads (S3, GCS, ...).
1087
+ *
1088
+ * All auth and URL signing is performed by the host application via the
1089
+ * callbacks in {@link PresignedAssetProviderOptions}. The library simply
1090
+ * PUTs/POSTs bytes to the provided URL.
1091
+ */
1092
+ class PresignedAssetProvider {
1093
+ type = 'remote';
1094
+ name = 'PresignedAssetProvider';
1095
+ _options;
1096
+ constructor(options) {
1097
+ this._options = options;
1098
+ }
1099
+ static with(options) {
1100
+ return { create: () => new PresignedAssetProvider(options) };
1101
+ }
1102
+ async init() {
1103
+ if (!this._options.quiet) {
1104
+ console.info('PresignedAssetProvider initialized');
1105
+ }
1106
+ }
1107
+ destroy() {
1108
+ // No-op.
1109
+ }
1110
+ canResolve(_id) {
1111
+ return true;
1112
+ }
1113
+ async put(blob, metadata) {
1114
+ const descriptor = await this._options.getUploadDescriptor(metadata);
1115
+ const method = descriptor.method ?? 'PUT';
1116
+ const headers = { ...(descriptor.headers ?? {}) };
1117
+ if (!headers['Content-Type'] && !headers['content-type']) {
1118
+ headers['Content-Type'] = metadata.mimeType;
1119
+ }
1120
+ let body;
1121
+ if (method === 'POST' && descriptor.fields) {
1122
+ const form = new FormData();
1123
+ for (const [key, value] of Object.entries(descriptor.fields)) {
1124
+ form.append(key, value);
1125
+ }
1126
+ form.append('file', blob);
1127
+ body = form;
1128
+ // Browser sets multipart boundary; avoid forcing content-type.
1129
+ delete headers['Content-Type'];
1130
+ delete headers['content-type'];
1131
+ }
1132
+ else {
1133
+ body = blob;
1134
+ }
1135
+ const res = await fetch(descriptor.url, { method, headers, body });
1136
+ if (!res.ok) {
1137
+ throw new Error(`[PresignedAssetProvider] Upload failed: ${res.status} ${res.statusText}`);
1138
+ }
1139
+ return {
1140
+ id: descriptor.id,
1141
+ kind: metadata.kind ?? 'file',
1142
+ mimeType: metadata.mimeType,
1143
+ size: blob.size,
1144
+ createdAt: Date.now(),
1145
+ width: metadata.width,
1146
+ height: metadata.height,
1147
+ durationMs: metadata.durationMs,
1148
+ originalFilename: metadata.originalFilename,
1149
+ };
1150
+ }
1151
+ async resolve(id) {
1152
+ return this._options.getDownloadUrl(id);
1153
+ }
1154
+ async fetch(id) {
1155
+ const url = await this._options.getDownloadUrl(id);
1156
+ const res = await fetch(url);
1157
+ if (!res.ok) {
1158
+ throw new Error(`[PresignedAssetProvider] Failed to fetch asset ${id}: ${res.status} ${res.statusText}`);
1159
+ }
1160
+ return res.blob();
1161
+ }
1162
+ async delete(id) {
1163
+ if (!this._options.deleteAsset)
1164
+ return;
1165
+ await this._options.deleteAsset(id);
1166
+ if (!this._options.quiet) {
1167
+ console.info(`PresignedAssetProvider: deleted asset ${id}`);
1168
+ }
1169
+ }
1170
+ }
1171
+
1172
+ export { BroadcastSyncProvider, HocuspocusSyncProvider, HttpAssetProvider, InMemorySyncProvider, IndexedDBSyncProvider, PresignedAssetProvider, WebSocketSyncProvider };