kritzel-stencil 0.1.87 → 0.1.89

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 (97) hide show
  1. package/dist/cjs/index.cjs.js +524 -2
  2. package/dist/cjs/kritzel-active-users_42.cjs.entry.js +128 -38
  3. package/dist/cjs/loader.cjs.js +1 -1
  4. package/dist/cjs/stencil.cjs.js +1 -1
  5. package/dist/cjs/{workspace.migrations-DPbmUuGp.js → workspace.migrations-B_dbGmyV.js} +822 -335
  6. package/dist/collection/classes/core/core.class.js +41 -1
  7. package/dist/collection/classes/objects/image.class.js +129 -3
  8. package/dist/collection/classes/providers/assets/asset-resolver.class.js +373 -0
  9. package/dist/collection/classes/providers/assets/http-asset-provider.class.js +106 -0
  10. package/dist/collection/classes/providers/assets/indexeddb-asset-provider.class.js +241 -0
  11. package/dist/collection/classes/providers/assets/presigned-asset-provider.class.js +81 -0
  12. package/dist/collection/classes/structures/app-state-map.structure.js +8 -0
  13. package/dist/collection/classes/tools/image-tool.class.js +110 -50
  14. package/dist/collection/components/core/kritzel-editor/kritzel-editor.js +106 -15
  15. package/dist/collection/components/core/kritzel-engine/kritzel-engine.js +49 -3
  16. package/dist/collection/components/ui/kritzel-back-to-content/kritzel-back-to-content.js +1 -1
  17. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.css +12 -0
  18. package/dist/collection/components/ui/kritzel-more-menu/kritzel-more-menu.js +25 -1
  19. package/dist/collection/components/ui/kritzel-settings/kritzel-settings.js +1 -1
  20. package/dist/collection/components/ui/kritzel-share-dialog/kritzel-share-dialog.js +2 -2
  21. package/dist/collection/components/ui/kritzel-utility-panel/kritzel-utility-panel.js +1 -1
  22. package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.css +11 -0
  23. package/dist/collection/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.js +25 -1
  24. package/dist/collection/configs/default-asset-storage.config.js +10 -0
  25. package/dist/collection/configs/default-sync.config.js +2 -2
  26. package/dist/collection/constants/version.js +1 -1
  27. package/dist/collection/helpers/svg-export.helper.js +11 -1
  28. package/dist/collection/index.js +12 -4
  29. package/dist/collection/interfaces/asset-provider.interface.js +1 -0
  30. package/dist/collection/interfaces/asset-storage-config.interface.js +1 -0
  31. package/dist/collection/interfaces/asset.interface.js +1 -0
  32. package/dist/components/index.js +1 -1
  33. package/dist/components/kritzel-back-to-content.js +1 -1
  34. package/dist/components/kritzel-controls.js +1 -1
  35. package/dist/components/kritzel-editor.js +1 -1
  36. package/dist/components/kritzel-engine.js +1 -1
  37. package/dist/components/kritzel-more-menu.js +1 -1
  38. package/dist/components/kritzel-settings.js +1 -1
  39. package/dist/components/kritzel-share-dialog.js +1 -1
  40. package/dist/components/kritzel-tool-config.js +1 -1
  41. package/dist/components/kritzel-utility-panel.js +1 -1
  42. package/dist/components/kritzel-workspace-manager.js +1 -1
  43. package/dist/components/{p-BsQ4E7I8.js → p-BTguWTDZ.js} +1 -1
  44. package/dist/components/{p-BNmTmyJ6.js → p-CKdGsPx9.js} +1 -1
  45. package/dist/components/p-CRdrQOlL.js +1 -0
  46. package/dist/components/p-CYnEIdQL.js +9 -0
  47. package/dist/components/{p-nWFKNhGS.js → p-CaRobExg.js} +1 -1
  48. package/dist/components/p-CowdEK08.js +1 -0
  49. package/dist/components/{p-BIdZCZ_l.js → p-CrCtvLMx.js} +1 -1
  50. package/dist/components/p-DXdAYm-y.js +1 -0
  51. package/dist/components/{p-Di2jiqtv.js → p-TIoiUjzO.js} +1 -1
  52. package/dist/components/{p-DN6xEk5l.js → p-cVQef3Hq.js} +1 -1
  53. package/dist/esm/index.js +520 -2
  54. package/dist/esm/kritzel-active-users_42.entry.js +128 -38
  55. package/dist/esm/loader.js +1 -1
  56. package/dist/esm/stencil.js +1 -1
  57. package/dist/esm/{workspace.migrations-Dfle1xt-.js → workspace.migrations-CvfM8aov.js} +818 -335
  58. package/dist/stencil/index.esm.js +1 -1
  59. package/dist/stencil/p-7fc12473.entry.js +9 -0
  60. package/dist/stencil/p-CvfM8aov.js +1 -0
  61. package/dist/stencil/stencil.esm.js +1 -1
  62. package/dist/types/classes/core/core.class.d.ts +26 -1
  63. package/dist/types/classes/objects/image.class.d.ts +70 -3
  64. package/dist/types/classes/providers/assets/asset-resolver.class.d.ts +103 -0
  65. package/dist/types/classes/providers/assets/http-asset-provider.class.d.ts +75 -0
  66. package/dist/types/classes/providers/assets/indexeddb-asset-provider.class.d.ts +65 -0
  67. package/dist/types/classes/providers/assets/presigned-asset-provider.class.d.ts +65 -0
  68. package/dist/types/classes/providers/{broadcast-sync-provider.class.d.ts → sync/broadcast-sync-provider.class.d.ts} +1 -1
  69. package/dist/types/classes/providers/{hocuspocus-sync-provider.class.d.ts → sync/hocuspocus-sync-provider.class.d.ts} +1 -1
  70. package/dist/types/classes/providers/{indexeddb-sync-provider.class.d.ts → sync/indexeddb-sync-provider.class.d.ts} +1 -1
  71. package/dist/types/classes/providers/{websocket-sync-provider.class.d.ts → sync/websocket-sync-provider.class.d.ts} +1 -1
  72. package/dist/types/classes/structures/app-state-map.structure.d.ts +6 -0
  73. package/dist/types/classes/tools/image-tool.class.d.ts +31 -17
  74. package/dist/types/components/core/kritzel-editor/kritzel-editor.d.ts +6 -0
  75. package/dist/types/components/core/kritzel-engine/kritzel-engine.d.ts +12 -0
  76. package/dist/types/components/ui/kritzel-more-menu/kritzel-more-menu.d.ts +4 -0
  77. package/dist/types/components/ui/kritzel-workspace-manager/kritzel-workspace-manager.d.ts +4 -0
  78. package/dist/types/components.d.ts +73 -5
  79. package/dist/types/configs/default-asset-storage.config.d.ts +8 -0
  80. package/dist/types/configs/default-sync.config.d.ts +1 -1
  81. package/dist/types/constants/version.d.ts +1 -1
  82. package/dist/types/helpers/svg-export.helper.d.ts +9 -0
  83. package/dist/types/index.d.ts +12 -4
  84. package/dist/types/interfaces/asset-provider.interface.d.ts +88 -0
  85. package/dist/types/interfaces/asset-storage-config.interface.d.ts +40 -0
  86. package/dist/types/interfaces/asset.interface.d.ts +43 -0
  87. package/package.json +3 -3
  88. package/dist/components/p-C-ZoFpSf.js +0 -1
  89. package/dist/components/p-C4zfI-dj.js +0 -1
  90. package/dist/components/p-Nq8UyPCc.js +0 -1
  91. package/dist/components/p-eN5E-5M4.js +0 -9
  92. package/dist/stencil/p-Dfle1xt-.js +0 -1
  93. package/dist/stencil/p-adb288d9.entry.js +0 -9
  94. /package/dist/collection/classes/providers/{broadcast-sync-provider.class.js → sync/broadcast-sync-provider.class.js} +0 -0
  95. /package/dist/collection/classes/providers/{hocuspocus-sync-provider.class.js → sync/hocuspocus-sync-provider.class.js} +0 -0
  96. /package/dist/collection/classes/providers/{indexeddb-sync-provider.class.js → sync/indexeddb-sync-provider.class.js} +0 -0
  97. /package/dist/collection/classes/providers/{websocket-sync-provider.class.js → sync/websocket-sync-provider.class.js} +0 -0
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var workspace_migrations = require('./workspace.migrations-DPbmUuGp.js');
3
+ var workspace_migrations = require('./workspace.migrations-B_dbGmyV.js');
4
4
  var Y = require('yjs');
5
5
  var yWebsocket = require('y-websocket');
6
6
  require('y-indexeddb');
@@ -557,19 +557,538 @@ class WebSocketSyncProvider {
557
557
  }
558
558
  }
559
559
 
560
+ /**
561
+ * Hocuspocus sync provider for real-time collaboration
562
+ * Supports multiplexing - multiple documents can share the same WebSocket connection
563
+ */
564
+ class HocuspocusSyncProvider {
565
+ type = 'network';
566
+ provider;
567
+ isConnected = false;
568
+ isSynced = false;
569
+ usesSharedSocket = false;
570
+ isDestroyed = false;
571
+ connectTimeout = null;
572
+ pendingConnectReject = null;
573
+ connectionTimeoutMs;
574
+ _connectionStatus = 'disconnected';
575
+ visibilityHandler = null;
576
+ onlineHandler = null;
577
+ get awareness() {
578
+ return this.provider.awareness;
579
+ }
580
+ get connectionStatus() {
581
+ return this._connectionStatus;
582
+ }
583
+ // Static shared WebSocket instance for multiplexing
584
+ static sharedWebSocketProvider = null;
585
+ constructor(docName, doc, options) {
586
+ const name = options?.name || docName;
587
+ const url = options?.url || 'ws://localhost:1234';
588
+ this.connectionTimeoutMs = options?.connectionTimeout ?? 10000;
589
+ // Use provided websocketProvider or the static shared one
590
+ const websocketProvider = options?.websocketProvider || HocuspocusSyncProvider.sharedWebSocketProvider;
591
+ // Build reconnect config from options
592
+ const reconnectConfig = {};
593
+ if (options?.delay !== undefined)
594
+ reconnectConfig.delay = options.delay;
595
+ if (options?.factor !== undefined)
596
+ reconnectConfig.factor = options.factor;
597
+ if (options?.maxAttempts !== undefined)
598
+ reconnectConfig.maxAttempts = options.maxAttempts;
599
+ if (options?.minDelay !== undefined)
600
+ reconnectConfig.minDelay = options.minDelay;
601
+ if (options?.maxDelay !== undefined)
602
+ reconnectConfig.maxDelay = options.maxDelay;
603
+ const onConnect = () => {
604
+ if (this.isDestroyed) {
605
+ return;
606
+ }
607
+ this.isConnected = true;
608
+ this._connectionStatus = 'connected';
609
+ if (!options?.quiet) {
610
+ console.info(`Hocuspocus connected: ${name}`);
611
+ }
612
+ if (options?.onConnect) {
613
+ options.onConnect();
614
+ }
615
+ };
616
+ const onDisconnect = () => {
617
+ if (this.isDestroyed) {
618
+ return;
619
+ }
620
+ this.isConnected = false;
621
+ this.isSynced = false;
622
+ this._connectionStatus = 'disconnected';
623
+ if (!options?.quiet) {
624
+ console.info(`Hocuspocus disconnected: ${name}`);
625
+ }
626
+ if (options?.onDisconnect) {
627
+ options.onDisconnect();
628
+ }
629
+ };
630
+ const onSynced = () => {
631
+ if (this.isDestroyed) {
632
+ return;
633
+ }
634
+ this.isSynced = true;
635
+ this._connectionStatus = 'synced';
636
+ if (!options?.quiet) {
637
+ console.info(`Hocuspocus synced: ${name}`);
638
+ }
639
+ if (options?.onSynced) {
640
+ options.onSynced();
641
+ }
642
+ };
643
+ const onStatus = (data) => {
644
+ if (this.isDestroyed) {
645
+ return;
646
+ }
647
+ if (data.status === 'connecting') {
648
+ this._connectionStatus = 'connecting';
649
+ }
650
+ if (options?.onStatus) {
651
+ options.onStatus(data);
652
+ }
653
+ };
654
+ if (websocketProvider) {
655
+ // Multiplexing mode - use shared WebSocket connection
656
+ this.usesSharedSocket = true;
657
+ const config = {
658
+ websocketProvider,
659
+ name,
660
+ document: doc,
661
+ token: options?.token || null,
662
+ onStatus,
663
+ onConnect,
664
+ onDisconnect,
665
+ onSynced,
666
+ ...reconnectConfig,
667
+ };
668
+ // Add optional settings
669
+ if (options?.forceSyncInterval !== undefined) {
670
+ config.forceSyncInterval = options.forceSyncInterval;
671
+ }
672
+ if (options?.onAuthenticationFailed) {
673
+ config.onAuthenticationFailed = options.onAuthenticationFailed;
674
+ }
675
+ this.provider = new workspace_migrations.HocuspocusProvider(config);
676
+ // Must call attach() explicitly when using shared socket
677
+ this.provider.attach();
678
+ if (!options?.quiet) {
679
+ console.info(`Hocuspocus Provider initialized (multiplexed): ${name}`);
680
+ }
681
+ }
682
+ else {
683
+ // Standalone mode - create own WebSocket connection
684
+ this.usesSharedSocket = false;
685
+ const config = {
686
+ url,
687
+ name,
688
+ document: doc,
689
+ token: options?.token || null,
690
+ autoConnect: false,
691
+ onStatus,
692
+ onConnect,
693
+ onDisconnect,
694
+ onSynced,
695
+ ...reconnectConfig,
696
+ };
697
+ // Add optional settings
698
+ if (options?.forceSyncInterval !== undefined) {
699
+ config.forceSyncInterval = options.forceSyncInterval;
700
+ }
701
+ if (options?.onAuthenticationFailed) {
702
+ config.onAuthenticationFailed = options.onAuthenticationFailed;
703
+ }
704
+ if (options?.WebSocketPolyfill) {
705
+ config.WebSocketPolyfill = options.WebSocketPolyfill;
706
+ }
707
+ this.provider = new workspace_migrations.HocuspocusProvider(config);
708
+ if (!options?.quiet) {
709
+ console.info(`Hocuspocus Provider initialized: ${url}/${name}`);
710
+ }
711
+ }
712
+ this.setupBrowserEventListeners();
713
+ }
714
+ setupBrowserEventListeners() {
715
+ if (typeof document !== 'undefined') {
716
+ this.visibilityHandler = () => {
717
+ if (document.visibilityState === 'visible' && !this.isConnected && !this.isDestroyed) {
718
+ this.provider.connect();
719
+ }
720
+ };
721
+ document.addEventListener('visibilitychange', this.visibilityHandler);
722
+ }
723
+ if (typeof window !== 'undefined') {
724
+ this.onlineHandler = () => {
725
+ if (!this.isConnected && !this.isDestroyed) {
726
+ this.provider.connect();
727
+ }
728
+ };
729
+ window.addEventListener('online', this.onlineHandler);
730
+ }
731
+ }
732
+ removeBrowserEventListeners() {
733
+ if (this.visibilityHandler && typeof document !== 'undefined') {
734
+ document.removeEventListener('visibilitychange', this.visibilityHandler);
735
+ this.visibilityHandler = null;
736
+ }
737
+ if (this.onlineHandler && typeof window !== 'undefined') {
738
+ window.removeEventListener('online', this.onlineHandler);
739
+ this.onlineHandler = null;
740
+ }
741
+ }
742
+ /**
743
+ * Create a shared WebSocket connection for multiplexing
744
+ * Call this once to create a shared connection that multiple providers can use
745
+ */
746
+ static createSharedWebSocket(options) {
747
+ if (HocuspocusSyncProvider.sharedWebSocketProvider) {
748
+ console.warn('Shared WebSocket already exists. Returning existing instance.');
749
+ return HocuspocusSyncProvider.sharedWebSocketProvider;
750
+ }
751
+ const config = {
752
+ url: options.url,
753
+ };
754
+ if (options.WebSocketPolyfill) {
755
+ config.WebSocketPolyfill = options.WebSocketPolyfill;
756
+ }
757
+ if (options.onConnect) {
758
+ config.onConnect = options.onConnect;
759
+ }
760
+ if (options.onDisconnect) {
761
+ config.onDisconnect = options.onDisconnect;
762
+ }
763
+ if (options.onStatus) {
764
+ config.onStatus = options.onStatus;
765
+ }
766
+ HocuspocusSyncProvider.sharedWebSocketProvider = new workspace_migrations.HocuspocusProviderWebsocket(config);
767
+ console.info(`Shared Hocuspocus WebSocket created: ${options.url}`);
768
+ return HocuspocusSyncProvider.sharedWebSocketProvider;
769
+ }
770
+ /**
771
+ * Destroy the shared WebSocket connection
772
+ * Call this when you're done with all multiplexed providers
773
+ */
774
+ static destroySharedWebSocket() {
775
+ if (HocuspocusSyncProvider.sharedWebSocketProvider) {
776
+ HocuspocusSyncProvider.sharedWebSocketProvider.destroy();
777
+ HocuspocusSyncProvider.sharedWebSocketProvider = null;
778
+ console.info('Shared Hocuspocus WebSocket destroyed');
779
+ }
780
+ }
781
+ /**
782
+ * Get the shared WebSocket provider instance (if it exists)
783
+ */
784
+ static getSharedWebSocket() {
785
+ return HocuspocusSyncProvider.sharedWebSocketProvider;
786
+ }
787
+ /**
788
+ * Static factory method for creating HocuspocusSyncProvider with configuration options
789
+ * Returns a ProviderFactory that can be used in sync configuration
790
+ */
791
+ static with(options) {
792
+ return {
793
+ create: (docName, doc, runtimeOptions) => {
794
+ const mergedOptions = runtimeOptions ? { ...options, ...runtimeOptions } : options;
795
+ return new HocuspocusSyncProvider(docName, doc, mergedOptions);
796
+ },
797
+ };
798
+ }
799
+ async connect() {
800
+ if (this.isSynced || this.isDestroyed) {
801
+ return;
802
+ }
803
+ this._connectionStatus = 'connecting';
804
+ return new Promise((resolve, reject) => {
805
+ // Store reject function so we can cancel the connection if destroyed
806
+ this.pendingConnectReject = reject;
807
+ this.connectTimeout = setTimeout(() => {
808
+ this.pendingConnectReject = null;
809
+ this.connectTimeout = null;
810
+ reject(new Error('Hocuspocus connection timeout'));
811
+ }, this.connectionTimeoutMs);
812
+ const syncHandler = () => {
813
+ if (this.connectTimeout) {
814
+ clearTimeout(this.connectTimeout);
815
+ this.connectTimeout = null;
816
+ }
817
+ this.pendingConnectReject = null;
818
+ this.provider.off('synced', syncHandler);
819
+ if (!this.isDestroyed) {
820
+ resolve();
821
+ }
822
+ };
823
+ this.provider.on('synced', syncHandler);
824
+ // If already synced, resolve immediately
825
+ if (this.provider.isSynced) {
826
+ if (this.connectTimeout) {
827
+ clearTimeout(this.connectTimeout);
828
+ this.connectTimeout = null;
829
+ }
830
+ this.pendingConnectReject = null;
831
+ this.provider.off('synced', syncHandler);
832
+ resolve();
833
+ return;
834
+ }
835
+ // Connect if not already connected (standalone mode only)
836
+ if (!this.isConnected && !this.usesSharedSocket) {
837
+ this.provider.connect();
838
+ }
839
+ });
840
+ }
841
+ async reconnect() {
842
+ this.disconnect();
843
+ return this.connect();
844
+ }
845
+ disconnect() {
846
+ // Cancel any pending connection attempt
847
+ if (this.connectTimeout) {
848
+ clearTimeout(this.connectTimeout);
849
+ this.connectTimeout = null;
850
+ }
851
+ if (this.pendingConnectReject) {
852
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
853
+ }
854
+ if (this.provider) {
855
+ if (this.usesSharedSocket) {
856
+ // Detach from shared socket instead of disconnecting
857
+ this.provider.detach();
858
+ }
859
+ else {
860
+ this.provider.disconnect();
861
+ }
862
+ }
863
+ this.isConnected = false;
864
+ this.isSynced = false;
865
+ this._connectionStatus = 'disconnected';
866
+ }
867
+ destroy() {
868
+ // Mark as destroyed first to prevent any callbacks from doing work
869
+ this.isDestroyed = true;
870
+ // Cancel any pending connection attempt
871
+ if (this.connectTimeout) {
872
+ clearTimeout(this.connectTimeout);
873
+ this.connectTimeout = null;
874
+ }
875
+ if (this.pendingConnectReject) {
876
+ this.pendingConnectReject = null; // Don't reject, just abandon the promise
877
+ }
878
+ this.removeBrowserEventListeners();
879
+ if (this.provider) {
880
+ this.provider.destroy();
881
+ }
882
+ this.isConnected = false;
883
+ this.isSynced = false;
884
+ this._connectionStatus = 'disconnected';
885
+ }
886
+ }
887
+
888
+ /**
889
+ * Remote asset storage provider backed by a plain HTTP endpoint.
890
+ *
891
+ * The host application is responsible for exposing a REST-ish backend
892
+ * that accepts a multipart upload and serves the stored asset at a
893
+ * stable URL. Authentication is injected via `headers`.
894
+ */
895
+ class HttpAssetProvider {
896
+ type = 'remote';
897
+ name = 'HttpAssetProvider';
898
+ _options;
899
+ constructor(options) {
900
+ this._options = options;
901
+ }
902
+ /**
903
+ * Factory helper enabling use as `HttpAssetProvider.with({ ... })` inside
904
+ * a `KritzelAssetStorageConfig.providers` array.
905
+ */
906
+ static with(options) {
907
+ return { create: () => new HttpAssetProvider(options) };
908
+ }
909
+ async init() {
910
+ // No initialization needed for a stateless HTTP provider.
911
+ }
912
+ destroy() {
913
+ // No-op; no resources to release.
914
+ }
915
+ canResolve(_id) {
916
+ // Remote providers don't know without a round-trip; assume yes.
917
+ return true;
918
+ }
919
+ async put(blob, metadata) {
920
+ const id = metadata.id ?? this.generateUuid();
921
+ const fullMetadata = { ...metadata, id };
922
+ const headers = (await this._options.headers?.()) ?? {};
923
+ const url = await this._options.uploadUrl(fullMetadata);
924
+ const uploader = this._options.upload ?? this.defaultUpload;
925
+ const response = await uploader(url, blob, fullMetadata, headers);
926
+ return {
927
+ id: response.id ?? id,
928
+ kind: metadata.kind ?? 'file',
929
+ mimeType: metadata.mimeType,
930
+ size: blob.size,
931
+ createdAt: Date.now(),
932
+ width: metadata.width,
933
+ height: metadata.height,
934
+ durationMs: metadata.durationMs,
935
+ originalFilename: metadata.originalFilename,
936
+ };
937
+ }
938
+ async resolve(id) {
939
+ return this._options.resolveUrl(id);
940
+ }
941
+ async fetch(id) {
942
+ const url = await this._options.resolveUrl(id);
943
+ const headers = (await this._options.headers?.()) ?? {};
944
+ const res = await fetch(url, { headers });
945
+ if (!res.ok) {
946
+ throw new Error(`[HttpAssetProvider] Failed to fetch asset ${id}: ${res.status} ${res.statusText}`);
947
+ }
948
+ return res.blob();
949
+ }
950
+ async delete(id) {
951
+ if (!this._options.deleteUrl)
952
+ return;
953
+ const url = await this._options.deleteUrl(id);
954
+ const headers = (await this._options.headers?.()) ?? {};
955
+ const res = await fetch(url, { method: 'DELETE', headers });
956
+ if (!res.ok) {
957
+ throw new Error(`[HttpAssetProvider] Failed to delete asset ${id}: ${res.status} ${res.statusText}`);
958
+ }
959
+ }
960
+ defaultUpload = async (url, blob, metadata, headers) => {
961
+ const form = new FormData();
962
+ form.append('metadata', JSON.stringify(metadata));
963
+ form.append('file', blob, metadata.originalFilename || `${metadata.id}.${this.extensionFromMime(metadata.mimeType)}`);
964
+ const res = await fetch(url, {
965
+ method: 'POST',
966
+ headers,
967
+ body: form,
968
+ });
969
+ if (!res.ok) {
970
+ throw new Error(`[HttpAssetProvider] Upload failed: ${res.status} ${res.statusText}`);
971
+ }
972
+ // Try to parse a JSON response; tolerate empty/plain-text bodies.
973
+ const contentType = res.headers.get('content-type') || '';
974
+ if (contentType.includes('application/json')) {
975
+ return (await res.json());
976
+ }
977
+ return {};
978
+ };
979
+ extensionFromMime(mime) {
980
+ const slash = mime.indexOf('/');
981
+ return slash >= 0 ? mime.slice(slash + 1) : 'bin';
982
+ }
983
+ generateUuid() {
984
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
985
+ return crypto.randomUUID();
986
+ }
987
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
988
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
989
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
990
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
991
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
992
+ }
993
+ }
994
+
995
+ /**
996
+ * Remote asset storage provider for pre-signed URL uploads (S3, GCS, ...).
997
+ *
998
+ * All auth and URL signing is performed by the host application via the
999
+ * callbacks in {@link PresignedAssetProviderOptions}. The library simply
1000
+ * PUTs/POSTs bytes to the provided URL.
1001
+ */
1002
+ class PresignedAssetProvider {
1003
+ type = 'remote';
1004
+ name = 'PresignedAssetProvider';
1005
+ _options;
1006
+ constructor(options) {
1007
+ this._options = options;
1008
+ }
1009
+ static with(options) {
1010
+ return { create: () => new PresignedAssetProvider(options) };
1011
+ }
1012
+ async init() {
1013
+ // Stateless provider.
1014
+ }
1015
+ destroy() {
1016
+ // No-op.
1017
+ }
1018
+ canResolve(_id) {
1019
+ return true;
1020
+ }
1021
+ async put(blob, metadata) {
1022
+ const descriptor = await this._options.getUploadDescriptor(metadata);
1023
+ const method = descriptor.method ?? 'PUT';
1024
+ const headers = { ...(descriptor.headers ?? {}) };
1025
+ if (!headers['Content-Type'] && !headers['content-type']) {
1026
+ headers['Content-Type'] = metadata.mimeType;
1027
+ }
1028
+ let body;
1029
+ if (method === 'POST' && descriptor.fields) {
1030
+ const form = new FormData();
1031
+ for (const [key, value] of Object.entries(descriptor.fields)) {
1032
+ form.append(key, value);
1033
+ }
1034
+ form.append('file', blob);
1035
+ body = form;
1036
+ // Browser sets multipart boundary; avoid forcing content-type.
1037
+ delete headers['Content-Type'];
1038
+ delete headers['content-type'];
1039
+ }
1040
+ else {
1041
+ body = blob;
1042
+ }
1043
+ const res = await fetch(descriptor.url, { method, headers, body });
1044
+ if (!res.ok) {
1045
+ throw new Error(`[PresignedAssetProvider] Upload failed: ${res.status} ${res.statusText}`);
1046
+ }
1047
+ return {
1048
+ id: descriptor.id,
1049
+ kind: metadata.kind ?? 'file',
1050
+ mimeType: metadata.mimeType,
1051
+ size: blob.size,
1052
+ createdAt: Date.now(),
1053
+ width: metadata.width,
1054
+ height: metadata.height,
1055
+ durationMs: metadata.durationMs,
1056
+ originalFilename: metadata.originalFilename,
1057
+ };
1058
+ }
1059
+ async resolve(id) {
1060
+ return this._options.getDownloadUrl(id);
1061
+ }
1062
+ async fetch(id) {
1063
+ const url = await this._options.getDownloadUrl(id);
1064
+ const res = await fetch(url);
1065
+ if (!res.ok) {
1066
+ throw new Error(`[PresignedAssetProvider] Failed to fetch asset ${id}: ${res.status} ${res.statusText}`);
1067
+ }
1068
+ return res.blob();
1069
+ }
1070
+ async delete(id) {
1071
+ if (!this._options.deleteAsset)
1072
+ return;
1073
+ await this._options.deleteAsset(id);
1074
+ }
1075
+ }
1076
+
560
1077
  exports.APP_STATE_MIGRATIONS = workspace_migrations.APP_STATE_MIGRATIONS;
561
1078
  exports.CURRENT_APP_STATE_SCHEMA_VERSION = workspace_migrations.CURRENT_APP_STATE_SCHEMA_VERSION;
562
1079
  exports.CURRENT_WORKSPACE_SCHEMA_VERSION = workspace_migrations.CURRENT_WORKSPACE_SCHEMA_VERSION;
1080
+ exports.DEFAULT_ASSET_STORAGE_CONFIG = workspace_migrations.DEFAULT_ASSET_STORAGE_CONFIG;
563
1081
  exports.DEFAULT_BRUSH_CONFIG = workspace_migrations.DEFAULT_BRUSH_CONFIG;
564
1082
  exports.DEFAULT_LINE_TOOL_CONFIG = workspace_migrations.DEFAULT_LINE_TOOL_CONFIG;
565
1083
  exports.DEFAULT_TEXT_CONFIG = workspace_migrations.DEFAULT_TEXT_CONFIG;
566
- exports.HocuspocusSyncProvider = workspace_migrations.HocuspocusSyncProvider;
1084
+ exports.IndexedDBAssetProvider = workspace_migrations.IndexedDBAssetProvider;
567
1085
  exports.IndexedDBSyncProvider = workspace_migrations.IndexedDBSyncProvider;
568
1086
  Object.defineProperty(exports, "KritzelAlignment", {
569
1087
  enumerable: true,
570
1088
  get: function () { return workspace_migrations.KritzelAlignment; }
571
1089
  });
572
1090
  exports.KritzelAnchorManager = workspace_migrations.KritzelAnchorManager;
1091
+ exports.KritzelAssetResolver = workspace_migrations.KritzelAssetResolver;
573
1092
  exports.KritzelBrushTool = workspace_migrations.KritzelBrushTool;
574
1093
  exports.KritzelCursorHelper = workspace_migrations.KritzelCursorHelper;
575
1094
  exports.KritzelEraserTool = workspace_migrations.KritzelEraserTool;
@@ -596,4 +1115,7 @@ exports.darkTheme = workspace_migrations.darkTheme;
596
1115
  exports.lightTheme = workspace_migrations.lightTheme;
597
1116
  exports.runMigrations = workspace_migrations.runMigrations;
598
1117
  exports.BroadcastSyncProvider = BroadcastSyncProvider;
1118
+ exports.HocuspocusSyncProvider = HocuspocusSyncProvider;
1119
+ exports.HttpAssetProvider = HttpAssetProvider;
1120
+ exports.PresignedAssetProvider = PresignedAssetProvider;
599
1121
  exports.WebSocketSyncProvider = WebSocketSyncProvider;