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