@kritzel/engine 0.4.31

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 (373) hide show
  1. package/LICENSE.md +50 -0
  2. package/dist/cjs/app-globals-V2Kpy_OQ.js +5 -0
  3. package/dist/cjs/engine.constants-CakmdKMW.js +28206 -0
  4. package/dist/cjs/index-BbbWU2aN.js +1742 -0
  5. package/dist/cjs/index.cjs.js +4232 -0
  6. package/dist/cjs/kritzel-awareness-cursors_5.cjs.entry.js +28004 -0
  7. package/dist/cjs/loader.cjs.js +13 -0
  8. package/dist/cjs/stencil.cjs.js +25 -0
  9. package/dist/collection/classes/core/core.class.js +1389 -0
  10. package/dist/collection/classes/core/reviver.class.js +124 -0
  11. package/dist/collection/classes/core/store.class.js +337 -0
  12. package/dist/collection/classes/core/viewport.class.js +794 -0
  13. package/dist/collection/classes/core/workspace.class.js +112 -0
  14. package/dist/collection/classes/handlers/base.handler.js +20 -0
  15. package/dist/collection/classes/handlers/context-menu.handler.js +170 -0
  16. package/dist/collection/classes/handlers/hover.handler.js +35 -0
  17. package/dist/collection/classes/handlers/key.handler.js +162 -0
  18. package/dist/collection/classes/handlers/line-handle.handler.js +475 -0
  19. package/dist/collection/classes/handlers/move.handler.js +295 -0
  20. package/dist/collection/classes/handlers/resize.handler.js +327 -0
  21. package/dist/collection/classes/handlers/rotation.handler.js +174 -0
  22. package/dist/collection/classes/handlers/selection.handler.js +526 -0
  23. package/dist/collection/classes/managers/anchor.manager.js +1476 -0
  24. package/dist/collection/classes/managers/browser-permission.manager.js +64 -0
  25. package/dist/collection/classes/managers/clipboard.manager.js +346 -0
  26. package/dist/collection/classes/managers/clipboard.types.js +1 -0
  27. package/dist/collection/classes/managers/cursor.manager.js +139 -0
  28. package/dist/collection/classes/managers/license.manager.js +341 -0
  29. package/dist/collection/classes/managers/localization.manager.js +227 -0
  30. package/dist/collection/classes/managers/theme.manager.js +206 -0
  31. package/dist/collection/classes/objects/base-object.class.js +462 -0
  32. package/dist/collection/classes/objects/dynamic-object.class.js +437 -0
  33. package/dist/collection/classes/objects/group.class.js +590 -0
  34. package/dist/collection/classes/objects/image.class.js +500 -0
  35. package/dist/collection/classes/objects/line.class.js +789 -0
  36. package/dist/collection/classes/objects/path.class.js +578 -0
  37. package/dist/collection/classes/objects/selection-box.class.js +32 -0
  38. package/dist/collection/classes/objects/selection-group.class.js +752 -0
  39. package/dist/collection/classes/objects/shape.class.js +628 -0
  40. package/dist/collection/classes/objects/text.class.js +461 -0
  41. package/dist/collection/classes/providers/assets/asset-resolver.class.js +536 -0
  42. package/dist/collection/classes/providers/assets/http-asset-provider.class.js +111 -0
  43. package/dist/collection/classes/providers/assets/indexeddb-asset-provider.class.js +241 -0
  44. package/dist/collection/classes/providers/assets/presigned-asset-provider.class.js +86 -0
  45. package/dist/collection/classes/providers/paste-intent/event-paste-intent.provider.js +48 -0
  46. package/dist/collection/classes/providers/paste-intent/ios-paste-intent.provider.js +16 -0
  47. package/dist/collection/classes/providers/paste-intent/navigator-paste-intent.provider.js +86 -0
  48. package/dist/collection/classes/providers/sync/broadcast-sync-provider.class.js +100 -0
  49. package/dist/collection/classes/providers/sync/hocuspocus-sync-provider.class.js +333 -0
  50. package/dist/collection/classes/providers/sync/in-memory-sync-provider.class.js +69 -0
  51. package/dist/collection/classes/providers/sync/indexeddb-sync-provider.class.js +39 -0
  52. package/dist/collection/classes/providers/sync/websocket-sync-provider.class.js +110 -0
  53. package/dist/collection/classes/registries/dynamic-object-renderer.registry.js +52 -0
  54. package/dist/collection/classes/registries/font-registry.class.js +161 -0
  55. package/dist/collection/classes/registries/icon-registry.class.js +118 -0
  56. package/dist/collection/classes/registries/paste-intent-provider.registry.js +26 -0
  57. package/dist/collection/classes/registries/tool.registry.js +69 -0
  58. package/dist/collection/classes/structures/app-state-map.structure.js +461 -0
  59. package/dist/collection/classes/structures/object-map.structure.js +1103 -0
  60. package/dist/collection/classes/structures/quadtree.structure.js +251 -0
  61. package/dist/collection/classes/tools/base-tool.class.js +88 -0
  62. package/dist/collection/classes/tools/brush-tool.class.js +236 -0
  63. package/dist/collection/classes/tools/eraser-tool.class.js +133 -0
  64. package/dist/collection/classes/tools/image-tool.class.js +152 -0
  65. package/dist/collection/classes/tools/line-tool.class.js +249 -0
  66. package/dist/collection/classes/tools/selection-tool.class.js +611 -0
  67. package/dist/collection/classes/tools/shape-tool.class.js +251 -0
  68. package/dist/collection/classes/tools/text-tool.class.js +179 -0
  69. package/dist/collection/collection-manifest.json +17 -0
  70. package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.css +115 -0
  71. package/dist/collection/components/kritzel-awareness-cursors/kritzel-awareness-cursors.js +380 -0
  72. package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.css +10 -0
  73. package/dist/collection/components/kritzel-cursor-trail/kritzel-cursor-trail.js +153 -0
  74. package/dist/collection/components/kritzel-engine/kritzel-engine.css +81 -0
  75. package/dist/collection/components/kritzel-engine/kritzel-engine.js +6345 -0
  76. package/dist/collection/components/kritzel-icon/kritzel-icon.css +18 -0
  77. package/dist/collection/components/kritzel-icon/kritzel-icon.js +93 -0
  78. package/dist/collection/components/kritzel-watermark/kritzel-watermark.css +29 -0
  79. package/dist/collection/components/kritzel-watermark/kritzel-watermark.js +83 -0
  80. package/dist/collection/configs/default-asset-storage.config.js +9 -0
  81. package/dist/collection/configs/default-brush-tool.config.js +9 -0
  82. package/dist/collection/configs/default-engine-config.js +73 -0
  83. package/dist/collection/configs/default-line-tool.config.js +12 -0
  84. package/dist/collection/configs/default-shape-tool.config.js +15 -0
  85. package/dist/collection/configs/default-sync.config.js +6 -0
  86. package/dist/collection/configs/default-text-tool.config.js +12 -0
  87. package/dist/collection/constants/color-palette.constants.js +40 -0
  88. package/dist/collection/constants/core.constants.js +1 -0
  89. package/dist/collection/constants/engine.constants.js +4 -0
  90. package/dist/collection/constants/license.constants.js +25 -0
  91. package/dist/collection/constants/schema.constants.js +4 -0
  92. package/dist/collection/constants/storage.constants.js +1 -0
  93. package/dist/collection/constants/stroke-size.constants.js +2 -0
  94. package/dist/collection/constants/text-tool-fonts.constants.js +18 -0
  95. package/dist/collection/constants/version.js +6 -0
  96. package/dist/collection/enums/alignment.enum.js +9 -0
  97. package/dist/collection/enums/event-button.enum.js +6 -0
  98. package/dist/collection/enums/handle-type.enum.js +7 -0
  99. package/dist/collection/enums/shape-type.enum.js +6 -0
  100. package/dist/collection/events/active-workspace-change.event.js +1 -0
  101. package/dist/collection/events/editor-is-ready.event.js +1 -0
  102. package/dist/collection/events/login.event.js +1 -0
  103. package/dist/collection/helpers/alignment.helper.js +73 -0
  104. package/dist/collection/helpers/class.helper.js +5 -0
  105. package/dist/collection/helpers/color.helper.js +106 -0
  106. package/dist/collection/helpers/cursor.helper.js +57 -0
  107. package/dist/collection/helpers/devices.helper.js +28 -0
  108. package/dist/collection/helpers/event.helper.js +58 -0
  109. package/dist/collection/helpers/geometry.helper.js +149 -0
  110. package/dist/collection/helpers/html.helper.js +116 -0
  111. package/dist/collection/helpers/image-file.helper.js +6 -0
  112. package/dist/collection/helpers/keyboard.helper.js +55 -0
  113. package/dist/collection/helpers/localization.helper.js +25 -0
  114. package/dist/collection/helpers/math.helper.js +8 -0
  115. package/dist/collection/helpers/migration.helper.js +45 -0
  116. package/dist/collection/helpers/object.helper.js +29 -0
  117. package/dist/collection/helpers/svg-export.helper.js +655 -0
  118. package/dist/collection/helpers/theme.helper.js +77 -0
  119. package/dist/collection/helpers/tool-config.helper.js +63 -0
  120. package/dist/collection/helpers/wheel.helper.js +113 -0
  121. package/dist/collection/index.js +107 -0
  122. package/dist/collection/interfaces/anchor.interface.js +1 -0
  123. package/dist/collection/interfaces/arrow-head.interface.js +1 -0
  124. package/dist/collection/interfaces/asset-provider.interface.js +1 -0
  125. package/dist/collection/interfaces/asset-storage-config.interface.js +1 -0
  126. package/dist/collection/interfaces/asset.interface.js +1 -0
  127. package/dist/collection/interfaces/bounding-box.interface.js +1 -0
  128. package/dist/collection/interfaces/clonable.interface.js +1 -0
  129. package/dist/collection/interfaces/context-menu-item.interface.js +1 -0
  130. package/dist/collection/interfaces/context-menu-state.interface.js +1 -0
  131. package/dist/collection/interfaces/debug-info.interface.js +1 -0
  132. package/dist/collection/interfaces/dialog.interface.js +1 -0
  133. package/dist/collection/interfaces/document-metadata.interface.js +1 -0
  134. package/dist/collection/interfaces/dynamic-object-renderer.interface.js +1 -0
  135. package/dist/collection/interfaces/engine-state.interface.js +1 -0
  136. package/dist/collection/interfaces/font.interface.js +1 -0
  137. package/dist/collection/interfaces/localization.interface.js +1 -0
  138. package/dist/collection/interfaces/login-config.interface.js +1 -0
  139. package/dist/collection/interfaces/master-detail.interface.js +1 -0
  140. package/dist/collection/interfaces/menu-item.interface.js +1 -0
  141. package/dist/collection/interfaces/migration.interface.js +1 -0
  142. package/dist/collection/interfaces/notification.interface.js +1 -0
  143. package/dist/collection/interfaces/object-change-event.interface.js +1 -0
  144. package/dist/collection/interfaces/object-export.interface.js +1 -0
  145. package/dist/collection/interfaces/object.interface.js +1 -0
  146. package/dist/collection/interfaces/paste-intent-provider.interface.js +1 -0
  147. package/dist/collection/interfaces/point.interface.js +1 -0
  148. package/dist/collection/interfaces/polygon.interface.js +1 -0
  149. package/dist/collection/interfaces/remote-cursor.interface.js +1 -0
  150. package/dist/collection/interfaces/selection-state.interface.js +1 -0
  151. package/dist/collection/interfaces/serializable.interface.js +1 -0
  152. package/dist/collection/interfaces/settings.interface.js +1 -0
  153. package/dist/collection/interfaces/share.interface.js +1 -0
  154. package/dist/collection/interfaces/shortcut.interface.js +1 -0
  155. package/dist/collection/interfaces/sync-config.interface.js +21 -0
  156. package/dist/collection/interfaces/sync-provider.interface.js +1 -0
  157. package/dist/collection/interfaces/theme.interface.js +1 -0
  158. package/dist/collection/interfaces/tool-config.interface.js +1 -0
  159. package/dist/collection/interfaces/tool-type.interface.js +1 -0
  160. package/dist/collection/interfaces/tool.interface.js +1 -0
  161. package/dist/collection/interfaces/toolbar-item.interface.js +1 -0
  162. package/dist/collection/interfaces/undo-state.interface.js +1 -0
  163. package/dist/collection/interfaces/user.interface.js +1 -0
  164. package/dist/collection/interfaces/viewport-state.interface.js +1 -0
  165. package/dist/collection/locales/de-locale.js +119 -0
  166. package/dist/collection/locales/en-locale.js +120 -0
  167. package/dist/collection/locales/fr-locale.js +119 -0
  168. package/dist/collection/migrations/app-state.migrations.js +47 -0
  169. package/dist/collection/migrations/workspace.migrations.js +67 -0
  170. package/dist/collection/themes/dark-theme.js +340 -0
  171. package/dist/collection/themes/light-theme.js +346 -0
  172. package/dist/collection/types/deep-readonly.type.js +1 -0
  173. package/dist/collection/types/state.types.js +1 -0
  174. package/dist/components/index.d.ts +47 -0
  175. package/dist/components/index.js +4143 -0
  176. package/dist/components/kritzel-awareness-cursors.d.ts +11 -0
  177. package/dist/components/kritzel-awareness-cursors.js +6 -0
  178. package/dist/components/kritzel-cursor-trail.d.ts +11 -0
  179. package/dist/components/kritzel-cursor-trail.js +6 -0
  180. package/dist/components/kritzel-engine.d.ts +11 -0
  181. package/dist/components/kritzel-engine.js +52641 -0
  182. package/dist/components/kritzel-icon.d.ts +11 -0
  183. package/dist/components/kritzel-icon.js +6 -0
  184. package/dist/components/kritzel-watermark.d.ts +11 -0
  185. package/dist/components/kritzel-watermark.js +6 -0
  186. package/dist/components/p-B2Htv9ou.js +49 -0
  187. package/dist/components/p-BvRGQvVI.js +3456 -0
  188. package/dist/components/p-CfSx6HU_.js +1469 -0
  189. package/dist/components/p-YP8wAcF4.js +122 -0
  190. package/dist/components/p-fx6msYHJ.js +167 -0
  191. package/dist/esm/app-globals-DQuL1Twl.js +3 -0
  192. package/dist/esm/engine.constants-Bl5-pbq-.js +28143 -0
  193. package/dist/esm/index-BU9Mv7y0.js +1715 -0
  194. package/dist/esm/index.js +4134 -0
  195. package/dist/esm/kritzel-awareness-cursors_5.entry.js +27978 -0
  196. package/dist/esm/loader.js +11 -0
  197. package/dist/esm/stencil.js +21 -0
  198. package/dist/index.cjs.js +1 -0
  199. package/dist/index.js +1 -0
  200. package/dist/stencil/index.esm.js +4134 -0
  201. package/dist/stencil/p-BU9Mv7y0.js +1702 -0
  202. package/dist/stencil/p-Bl5-pbq-.js +28143 -0
  203. package/dist/stencil/p-DQuL1Twl.js +3 -0
  204. package/dist/stencil/p-f0a8b7af.entry.js +27978 -0
  205. package/dist/stencil/stencil.esm.js +21 -0
  206. package/dist/types/classes/core/core.class.d.ts +532 -0
  207. package/dist/types/classes/core/reviver.class.d.ts +32 -0
  208. package/dist/types/classes/core/store.class.d.ts +198 -0
  209. package/dist/types/classes/core/viewport.class.d.ts +256 -0
  210. package/dist/types/classes/core/workspace.class.d.ts +95 -0
  211. package/dist/types/classes/handlers/base.handler.d.ts +18 -0
  212. package/dist/types/classes/handlers/context-menu.handler.d.ts +67 -0
  213. package/dist/types/classes/handlers/hover.handler.d.ts +23 -0
  214. package/dist/types/classes/handlers/key.handler.d.ts +44 -0
  215. package/dist/types/classes/handlers/line-handle.handler.d.ts +115 -0
  216. package/dist/types/classes/handlers/move.handler.d.ts +94 -0
  217. package/dist/types/classes/handlers/resize.handler.d.ts +95 -0
  218. package/dist/types/classes/handlers/rotation.handler.d.ts +50 -0
  219. package/dist/types/classes/handlers/selection.handler.d.ts +153 -0
  220. package/dist/types/classes/managers/anchor.manager.d.ts +513 -0
  221. package/dist/types/classes/managers/browser-permission.manager.d.ts +41 -0
  222. package/dist/types/classes/managers/clipboard.manager.d.ts +71 -0
  223. package/dist/types/classes/managers/clipboard.types.d.ts +23 -0
  224. package/dist/types/classes/managers/cursor.manager.d.ts +66 -0
  225. package/dist/types/classes/managers/license.manager.d.ts +147 -0
  226. package/dist/types/classes/managers/localization.manager.d.ts +145 -0
  227. package/dist/types/classes/managers/theme.manager.d.ts +112 -0
  228. package/dist/types/classes/objects/base-object.class.d.ts +266 -0
  229. package/dist/types/classes/objects/dynamic-object.class.d.ts +148 -0
  230. package/dist/types/classes/objects/group.class.d.ts +231 -0
  231. package/dist/types/classes/objects/image.class.d.ts +246 -0
  232. package/dist/types/classes/objects/line.class.d.ts +285 -0
  233. package/dist/types/classes/objects/path.class.d.ts +220 -0
  234. package/dist/types/classes/objects/selection-box.class.d.ts +19 -0
  235. package/dist/types/classes/objects/selection-group.class.d.ts +267 -0
  236. package/dist/types/classes/objects/shape.class.d.ts +322 -0
  237. package/dist/types/classes/objects/text.class.d.ts +197 -0
  238. package/dist/types/classes/providers/assets/asset-resolver.class.d.ts +141 -0
  239. package/dist/types/classes/providers/assets/http-asset-provider.class.d.ts +79 -0
  240. package/dist/types/classes/providers/assets/indexeddb-asset-provider.class.d.ts +69 -0
  241. package/dist/types/classes/providers/assets/presigned-asset-provider.class.d.ts +69 -0
  242. package/dist/types/classes/providers/paste-intent/event-paste-intent.provider.d.ts +8 -0
  243. package/dist/types/classes/providers/paste-intent/ios-paste-intent.provider.d.ts +6 -0
  244. package/dist/types/classes/providers/paste-intent/navigator-paste-intent.provider.d.ts +9 -0
  245. package/dist/types/classes/providers/sync/broadcast-sync-provider.class.d.ts +23 -0
  246. package/dist/types/classes/providers/sync/hocuspocus-sync-provider.class.d.ts +163 -0
  247. package/dist/types/classes/providers/sync/in-memory-sync-provider.class.d.ts +39 -0
  248. package/dist/types/classes/providers/sync/indexeddb-sync-provider.class.d.ts +25 -0
  249. package/dist/types/classes/providers/sync/websocket-sync-provider.class.d.ts +63 -0
  250. package/dist/types/classes/registries/dynamic-object-renderer.registry.d.ts +32 -0
  251. package/dist/types/classes/registries/font-registry.class.d.ts +19 -0
  252. package/dist/types/classes/registries/icon-registry.class.d.ts +39 -0
  253. package/dist/types/classes/registries/paste-intent-provider.registry.d.ts +10 -0
  254. package/dist/types/classes/registries/tool.registry.d.ts +53 -0
  255. package/dist/types/classes/structures/app-state-map.structure.d.ts +171 -0
  256. package/dist/types/classes/structures/object-map.structure.d.ts +403 -0
  257. package/dist/types/classes/structures/quadtree.structure.d.ts +128 -0
  258. package/dist/types/classes/tools/base-tool.class.d.ts +72 -0
  259. package/dist/types/classes/tools/brush-tool.class.d.ts +68 -0
  260. package/dist/types/classes/tools/eraser-tool.class.d.ts +38 -0
  261. package/dist/types/classes/tools/image-tool.class.d.ts +74 -0
  262. package/dist/types/classes/tools/line-tool.class.d.ts +63 -0
  263. package/dist/types/classes/tools/selection-tool.class.d.ts +188 -0
  264. package/dist/types/classes/tools/shape-tool.class.d.ts +90 -0
  265. package/dist/types/classes/tools/text-tool.class.d.ts +66 -0
  266. package/dist/types/components/kritzel-awareness-cursors/kritzel-awareness-cursors.d.ts +29 -0
  267. package/dist/types/components/kritzel-cursor-trail/kritzel-cursor-trail.d.ts +19 -0
  268. package/dist/types/components/kritzel-engine/kritzel-engine.d.ts +695 -0
  269. package/dist/types/components/kritzel-icon/kritzel-icon.d.ts +6 -0
  270. package/dist/types/components/kritzel-watermark/kritzel-watermark.d.ts +20 -0
  271. package/dist/types/components.d.ts +981 -0
  272. package/dist/types/configs/default-asset-storage.config.d.ts +8 -0
  273. package/dist/types/configs/default-brush-tool.config.d.ts +2 -0
  274. package/dist/types/configs/default-engine-config.d.ts +2 -0
  275. package/dist/types/configs/default-line-tool.config.d.ts +2 -0
  276. package/dist/types/configs/default-shape-tool.config.d.ts +2 -0
  277. package/dist/types/configs/default-sync.config.d.ts +5 -0
  278. package/dist/types/configs/default-text-tool.config.d.ts +2 -0
  279. package/dist/types/constants/color-palette.constants.d.ts +31 -0
  280. package/dist/types/constants/core.constants.d.ts +0 -0
  281. package/dist/types/constants/engine.constants.d.ts +4 -0
  282. package/dist/types/constants/license.constants.d.ts +25 -0
  283. package/dist/types/constants/schema.constants.d.ts +4 -0
  284. package/dist/types/constants/storage.constants.d.ts +1 -0
  285. package/dist/types/constants/stroke-size.constants.d.ts +2 -0
  286. package/dist/types/constants/text-tool-fonts.constants.d.ts +3 -0
  287. package/dist/types/constants/version.d.ts +6 -0
  288. package/dist/types/enums/alignment.enum.d.ts +8 -0
  289. package/dist/types/enums/event-button.enum.d.ts +5 -0
  290. package/dist/types/enums/handle-type.enum.d.ts +6 -0
  291. package/dist/types/enums/shape-type.enum.d.ts +5 -0
  292. package/dist/types/events/active-workspace-change.event.d.ts +7 -0
  293. package/dist/types/events/editor-is-ready.event.d.ts +18 -0
  294. package/dist/types/events/login.event.d.ts +3 -0
  295. package/dist/types/helpers/alignment.helper.d.ts +21 -0
  296. package/dist/types/helpers/class.helper.d.ts +3 -0
  297. package/dist/types/helpers/color.helper.d.ts +34 -0
  298. package/dist/types/helpers/cursor.helper.d.ts +22 -0
  299. package/dist/types/helpers/devices.helper.d.ts +8 -0
  300. package/dist/types/helpers/event.helper.d.ts +6 -0
  301. package/dist/types/helpers/geometry.helper.d.ts +38 -0
  302. package/dist/types/helpers/html.helper.d.ts +8 -0
  303. package/dist/types/helpers/image-file.helper.d.ts +3 -0
  304. package/dist/types/helpers/keyboard.helper.d.ts +6 -0
  305. package/dist/types/helpers/localization.helper.d.ts +18 -0
  306. package/dist/types/helpers/math.helper.d.ts +4 -0
  307. package/dist/types/helpers/migration.helper.d.ts +30 -0
  308. package/dist/types/helpers/object.helper.d.ts +9 -0
  309. package/dist/types/helpers/svg-export.helper.d.ts +231 -0
  310. package/dist/types/helpers/theme.helper.d.ts +42 -0
  311. package/dist/types/helpers/tool-config.helper.d.ts +4 -0
  312. package/dist/types/helpers/wheel.helper.d.ts +9 -0
  313. package/dist/types/index.d.ts +109 -0
  314. package/dist/types/interfaces/anchor.interface.d.ts +147 -0
  315. package/dist/types/interfaces/arrow-head.interface.d.ts +27 -0
  316. package/dist/types/interfaces/asset-provider.interface.d.ts +88 -0
  317. package/dist/types/interfaces/asset-storage-config.interface.d.ts +40 -0
  318. package/dist/types/interfaces/asset.interface.d.ts +43 -0
  319. package/dist/types/interfaces/bounding-box.interface.d.ts +8 -0
  320. package/dist/types/interfaces/clonable.interface.d.ts +3 -0
  321. package/dist/types/interfaces/context-menu-item.interface.d.ts +25 -0
  322. package/dist/types/interfaces/context-menu-state.interface.d.ts +16 -0
  323. package/dist/types/interfaces/debug-info.interface.d.ts +7 -0
  324. package/dist/types/interfaces/dialog.interface.d.ts +4 -0
  325. package/dist/types/interfaces/document-metadata.interface.d.ts +6 -0
  326. package/dist/types/interfaces/dynamic-object-renderer.interface.d.ts +40 -0
  327. package/dist/types/interfaces/engine-state.interface.d.ts +82 -0
  328. package/dist/types/interfaces/font.interface.d.ts +15 -0
  329. package/dist/types/interfaces/localization.interface.d.ts +143 -0
  330. package/dist/types/interfaces/login-config.interface.d.ts +24 -0
  331. package/dist/types/interfaces/master-detail.interface.d.ts +14 -0
  332. package/dist/types/interfaces/menu-item.interface.d.ts +27 -0
  333. package/dist/types/interfaces/migration.interface.d.ts +16 -0
  334. package/dist/types/interfaces/notification.interface.d.ts +10 -0
  335. package/dist/types/interfaces/object-change-event.interface.d.ts +31 -0
  336. package/dist/types/interfaces/object-export.interface.d.ts +20 -0
  337. package/dist/types/interfaces/object.interface.d.ts +53 -0
  338. package/dist/types/interfaces/paste-intent-provider.interface.d.ts +10 -0
  339. package/dist/types/interfaces/point.interface.d.ts +4 -0
  340. package/dist/types/interfaces/polygon.interface.d.ts +7 -0
  341. package/dist/types/interfaces/remote-cursor.interface.d.ts +18 -0
  342. package/dist/types/interfaces/selection-state.interface.d.ts +7 -0
  343. package/dist/types/interfaces/serializable.interface.d.ts +5 -0
  344. package/dist/types/interfaces/settings.interface.d.ts +25 -0
  345. package/dist/types/interfaces/share.interface.d.ts +9 -0
  346. package/dist/types/interfaces/shortcut.interface.d.ts +10 -0
  347. package/dist/types/interfaces/sync-config.interface.d.ts +41 -0
  348. package/dist/types/interfaces/sync-provider.interface.d.ts +66 -0
  349. package/dist/types/interfaces/theme.interface.d.ts +553 -0
  350. package/dist/types/interfaces/tool-config.interface.d.ts +24 -0
  351. package/dist/types/interfaces/tool-type.interface.d.ts +1 -0
  352. package/dist/types/interfaces/tool.interface.d.ts +8 -0
  353. package/dist/types/interfaces/toolbar-item.interface.d.ts +66 -0
  354. package/dist/types/interfaces/undo-state.interface.d.ts +6 -0
  355. package/dist/types/interfaces/user.interface.d.ts +22 -0
  356. package/dist/types/interfaces/viewport-state.interface.d.ts +7 -0
  357. package/dist/types/locales/de-locale.d.ts +5 -0
  358. package/dist/types/locales/en-locale.d.ts +6 -0
  359. package/dist/types/locales/fr-locale.d.ts +5 -0
  360. package/dist/types/migrations/app-state.migrations.d.ts +11 -0
  361. package/dist/types/migrations/workspace.migrations.d.ts +11 -0
  362. package/dist/types/stencil-public-runtime.d.ts +1873 -0
  363. package/dist/types/themes/dark-theme.d.ts +5 -0
  364. package/dist/types/themes/light-theme.d.ts +5 -0
  365. package/dist/types/types/deep-readonly.type.d.ts +7 -0
  366. package/dist/types/types/state.types.d.ts +3 -0
  367. package/loader/cdn.js +1 -0
  368. package/loader/index.cjs.js +1 -0
  369. package/loader/index.d.ts +24 -0
  370. package/loader/index.es2017.js +1 -0
  371. package/loader/index.js +2 -0
  372. package/package.json +101 -0
  373. package/readme.md +11 -0
@@ -0,0 +1,536 @@
1
+ import { IndexedDBAssetProvider } from "./indexeddb-asset-provider.class";
2
+ const BASE_RETRY_DELAY_MS = 2_000;
3
+ const MAX_RETRY_DELAY_MS = 5 * 60_000; // 5 minutes
4
+ /**
5
+ * Thrown when an asset id cannot be located in any configured provider.
6
+ *
7
+ * This is distinct from transport / network errors: callers (e.g.
8
+ * {@link KritzelImage.ensureResolved}) use the type to decide whether a
9
+ * failure is permanent (asset truly missing) or transient (network blip,
10
+ * remote temporarily unreachable) and should be retried without
11
+ * burning the per-page-load attempt budget.
12
+ */
13
+ export class AssetNotFoundError extends Error {
14
+ assetId;
15
+ constructor(assetId, message) {
16
+ super(message ?? `[KritzelAssetResolver] Asset ${assetId} not found in any configured provider.`);
17
+ this.name = 'AssetNotFoundError';
18
+ this.assetId = assetId;
19
+ }
20
+ }
21
+ /**
22
+ * Orchestrates asset storage across one or more providers.
23
+ *
24
+ * Responsibilities:
25
+ * - Instantiates providers from a {@link KritzelAssetStorageConfig}.
26
+ * - Always ensures an {@link IndexedDBAssetProvider} is present as the
27
+ * local cache / optimistic write buffer.
28
+ * - Implements a write-through upload pipeline: bytes are written to the
29
+ * local provider immediately (so UI renders instantly), and remote
30
+ * providers receive a copy via an in-memory retry loop with
31
+ * exponential backoff.
32
+ * - Resolves asset ids to URLs by walking providers in order; the local
33
+ * provider serves as a cache for remote assets.
34
+ *
35
+ * Crash / reload resilience: the "needs upload" state is stored directly
36
+ * on the asset's metadata record in the local IndexedDB provider (as
37
+ * `KritzelAsset.pendingRemotes`). On `init()` the resolver scans for any
38
+ * such records and re-enqueues them, so uploads that were interrupted
39
+ * by a reload eventually complete without requiring a separate queue DB.
40
+ */
41
+ export class KritzelAssetResolver {
42
+ _providers = [];
43
+ _local = null;
44
+ /** Remote providers ordered as configured. */
45
+ _remoteProviders = [];
46
+ _initialized = false;
47
+ /**
48
+ * Promise that resolves when the resolver is ready to serve requests.
49
+ *
50
+ * Created lazily so that callers issuing `resolve()` / `fetchBlob()` /
51
+ * `put()` before {@link init} has been invoked are queued instead of
52
+ * rejected. This eliminates a race on reload where the renderer
53
+ * triggers `KritzelImage.ensureResolved()` before the engine has
54
+ * finished wiring up the asset storage layer.
55
+ */
56
+ _readyPromise = null;
57
+ _resolveReady = null;
58
+ _rejectReady = null;
59
+ /** In-memory retry state keyed by `${id}::${providerName}`. */
60
+ _pending = new Map();
61
+ _flushTimer = null;
62
+ _destroyed = false;
63
+ _quiet = true;
64
+ /** Dedupes concurrent resolves of the same id. */
65
+ _resolvePromises = new Map();
66
+ /** Enables or disables resolver diagnostic logging. */
67
+ setShowInfo(show) {
68
+ this._quiet = !show;
69
+ }
70
+ /**
71
+ * Whether any remote provider is configured. Used by the engine to
72
+ * decide whether to warn about a missing remote provider when Yjs is
73
+ * configured for network sync.
74
+ */
75
+ get hasRemoteProvider() {
76
+ return this._remoteProviders.length > 0;
77
+ }
78
+ /** Returns the local (IndexedDB) provider instance, if available. */
79
+ get localProvider() {
80
+ return this._local;
81
+ }
82
+ /**
83
+ * Initializes the resolver with the given configuration. Idempotent.
84
+ *
85
+ * @param config - User-supplied provider list. An `IndexedDBAssetProvider`
86
+ * is automatically prepended when the list contains none,
87
+ * so local caching is always available.
88
+ */
89
+ async init(config) {
90
+ if (this._initialized)
91
+ return;
92
+ // Ensure the deferred ready promise exists before any async work so
93
+ // concurrent callers awaiting `ensureReady()` are unblocked together.
94
+ this.getReadyPromise();
95
+ const providers = this.instantiateProviders(config?.providers ?? []);
96
+ // Ensure a local provider always exists so uploads can be optimistic
97
+ // and remote misses can be cached for later offline access.
98
+ const hasLocal = providers.some(p => p.type === 'local');
99
+ if (!hasLocal) {
100
+ providers.unshift(new IndexedDBAssetProvider());
101
+ }
102
+ // Normalize order: local providers first so resolution always
103
+ // hits the local cache before making network requests.
104
+ providers.sort((a, b) => {
105
+ if (a.type === 'local' && b.type !== 'local')
106
+ return -1;
107
+ if (a.type !== 'local' && b.type === 'local')
108
+ return 1;
109
+ return 0;
110
+ });
111
+ this._providers = providers;
112
+ // Use type + name instead of `instanceof` to find the local provider.
113
+ // `instanceof` can fail across module boundaries when the external app
114
+ // bundles its own copy of IndexedDBAssetProvider.
115
+ this._local = providers.find(p => p.type === 'local' && p.name === 'IndexedDBAssetProvider') ?? null;
116
+ this._remoteProviders = providers.filter(p => p.type === 'remote');
117
+ // Initialize the local provider first (must succeed), then remote
118
+ // providers individually so a failing remote does not block init.
119
+ if (this._local) {
120
+ await this._local.init();
121
+ }
122
+ for (const provider of this._remoteProviders) {
123
+ try {
124
+ await provider.init();
125
+ }
126
+ catch (err) {
127
+ if (this.canLogInfo()) {
128
+ console.warn(`[Kritzel] Asset resolver remote provider "${provider.name}" failed to initialize. ` +
129
+ `Assets will be served from the local provider until the remote becomes available.`, err);
130
+ }
131
+ }
132
+ }
133
+ // Re-enqueue any uploads that were interrupted before completing.
134
+ await this.recoverPendingUploads();
135
+ this._initialized = true;
136
+ // Release any callers that were queued behind init.
137
+ if (this._resolveReady) {
138
+ this._resolveReady();
139
+ this._resolveReady = null;
140
+ this._rejectReady = null;
141
+ }
142
+ if (this.canLogInfo()) {
143
+ this.logProviderConfiguration();
144
+ }
145
+ if (this._pending.size > 0) {
146
+ this.scheduleFlush(0);
147
+ }
148
+ }
149
+ /** Tears down the resolver and all providers. */
150
+ destroy() {
151
+ if (this.canLogInfo()) {
152
+ console.info('[Kritzel] Asset resolver destroyed');
153
+ }
154
+ this._destroyed = true;
155
+ if (this._flushTimer) {
156
+ clearTimeout(this._flushTimer);
157
+ this._flushTimer = null;
158
+ }
159
+ for (const provider of this._providers) {
160
+ try {
161
+ provider.destroy();
162
+ }
163
+ catch {
164
+ /* ignore */
165
+ }
166
+ }
167
+ this._providers = [];
168
+ this._local = null;
169
+ this._remoteProviders = [];
170
+ this._pending.clear();
171
+ this._resolvePromises.clear();
172
+ this._initialized = false;
173
+ // Reject any callers still waiting on init so they don't hang forever.
174
+ if (this._rejectReady) {
175
+ this._rejectReady(new Error('[KritzelAssetResolver] Destroyed before initialization completed.'));
176
+ }
177
+ this._readyPromise = null;
178
+ this._resolveReady = null;
179
+ this._rejectReady = null;
180
+ }
181
+ /**
182
+ * Uploads a blob through the write-through pipeline.
183
+ *
184
+ * 1. The local provider stores the bytes immediately and assigns the id.
185
+ * 2. Remote providers are marked pending on the local metadata and
186
+ * scheduled via the in-memory retry loop.
187
+ *
188
+ * Callers receive the asset descriptor synchronously after the local
189
+ * write completes; remote replication happens in the background.
190
+ */
191
+ async put(blob, metadata) {
192
+ await this.ensureReady();
193
+ const id = metadata.id ?? this.generateUuid();
194
+ const fullMetadata = { ...metadata, id };
195
+ // Always write to the local provider first so the UI can render
196
+ // immediately and offline usage is supported.
197
+ let asset = null;
198
+ if (this._local) {
199
+ asset = await this._local.put(blob, fullMetadata);
200
+ }
201
+ if (this._remoteProviders.length > 0) {
202
+ const providerNames = this._remoteProviders.map(p => p.name);
203
+ // Persist the pending list on the metadata so a reload can
204
+ // rediscover in-flight uploads.
205
+ if (this._local) {
206
+ await this._local.setPendingRemotes(id, providerNames);
207
+ }
208
+ for (const name of providerNames) {
209
+ this._pending.set(this.pendingKey(id, name), {
210
+ id,
211
+ providerName: name,
212
+ attempts: 0,
213
+ nextAttemptAt: Date.now(),
214
+ });
215
+ }
216
+ }
217
+ if (!asset) {
218
+ // No local provider? Upload synchronously to the first remote.
219
+ if (this._remoteProviders.length === 0) {
220
+ throw new Error('[KritzelAssetResolver] No providers configured to store asset.');
221
+ }
222
+ asset = await this._remoteProviders[0].put(blob, fullMetadata);
223
+ }
224
+ this.scheduleFlush(0);
225
+ return asset;
226
+ }
227
+ /**
228
+ * Resolves an asset id to a URL usable as `<img src>`. Providers are
229
+ * tried in order; the first successful response wins.
230
+ *
231
+ * Remote hits are cached into the local provider for future offline use.
232
+ */
233
+ async resolve(id) {
234
+ await this.ensureReady();
235
+ const inflight = this._resolvePromises.get(id);
236
+ if (inflight)
237
+ return inflight;
238
+ const promise = (async () => {
239
+ // Fast path: local cache.
240
+ if (this._local && this._local.canResolve(id)) {
241
+ return this._local.resolve(id);
242
+ }
243
+ // Walk remote providers.
244
+ let lastError;
245
+ let remoteAttempts = 0;
246
+ for (const provider of this._providers) {
247
+ if (provider === this._local)
248
+ continue;
249
+ remoteAttempts++;
250
+ try {
251
+ // Prefer `fetch` so we can cache the bytes locally; fall back
252
+ // to `resolve` if fetch is unavailable or rejects.
253
+ const blob = await provider.fetch(id);
254
+ if (this._local) {
255
+ try {
256
+ await this._local.put(blob, {
257
+ id,
258
+ mimeType: blob.type || 'application/octet-stream',
259
+ kind: 'file',
260
+ });
261
+ }
262
+ catch {
263
+ /* caching failure is non-fatal */
264
+ }
265
+ return this._local.resolve(id);
266
+ }
267
+ return URL.createObjectURL(blob);
268
+ }
269
+ catch (err) {
270
+ lastError = err;
271
+ // Try next provider.
272
+ }
273
+ }
274
+ // No remote providers were tried (only local exists and missed):
275
+ // the asset is definitively absent locally and nowhere else to
276
+ // look. Surface a typed error so callers can distinguish a true
277
+ // miss from a transient network failure.
278
+ if (remoteAttempts === 0) {
279
+ throw new AssetNotFoundError(id);
280
+ }
281
+ // Remotes were tried and all failed. Without HTTP-status visibility
282
+ // we cannot tell "truly missing" from "network blip", so surface a
283
+ // generic error and let the caller treat it as transient.
284
+ throw new Error(`[KritzelAssetResolver] Failed to resolve asset ${id}: ${String(lastError)}`);
285
+ })();
286
+ this._resolvePromises.set(id, promise);
287
+ try {
288
+ return await promise;
289
+ }
290
+ finally {
291
+ this._resolvePromises.delete(id);
292
+ }
293
+ }
294
+ /**
295
+ * Returns the raw bytes for an asset. Used for export flows that embed
296
+ * the bytes inline (e.g. SVG export).
297
+ */
298
+ async fetchBlob(id) {
299
+ await this.ensureReady();
300
+ let lastError;
301
+ let localTried = false;
302
+ let localMissed = false;
303
+ let remoteAttempts = 0;
304
+ // Prefer local to avoid network round-trips.
305
+ const ordered = this._local
306
+ ? [this._local, ...this._providers.filter(p => p !== this._local)]
307
+ : this._providers;
308
+ for (const provider of ordered) {
309
+ if (provider === this._local) {
310
+ localTried = true;
311
+ }
312
+ else {
313
+ remoteAttempts++;
314
+ }
315
+ try {
316
+ return await provider.fetch(id);
317
+ }
318
+ catch (err) {
319
+ if (provider === this._local) {
320
+ localMissed = true;
321
+ }
322
+ lastError = err;
323
+ }
324
+ }
325
+ // Same logic as resolve(): only flag as definitively missing when no
326
+ // remote providers were available to mask a transient failure.
327
+ if (remoteAttempts === 0 && (localTried ? localMissed : true)) {
328
+ throw new AssetNotFoundError(id);
329
+ }
330
+ throw new Error(`[KritzelAssetResolver] Failed to fetch blob for ${id}: ${String(lastError)}`);
331
+ }
332
+ /**
333
+ * Uploads a legacy data-URL and returns the new asset. Used by the
334
+ * migration step for existing `KritzelImage.src` payloads.
335
+ */
336
+ async putDataUrl(dataUrl, metadata) {
337
+ const blob = await this.dataUrlToBlob(dataUrl);
338
+ return this.put(blob, {
339
+ ...metadata,
340
+ mimeType: metadata.mimeType ?? blob.type ?? 'application/octet-stream',
341
+ });
342
+ }
343
+ // ------------------------------------------------------------------
344
+ // Internals
345
+ // ------------------------------------------------------------------
346
+ logProviderConfiguration() {
347
+ const lines = [
348
+ `[Kritzel] Asset resolver initialized with ${this._providers.length} provider(s):`,
349
+ ];
350
+ for (const provider of this._providers) {
351
+ const role = provider === this._local ? 'local (primary write target)' : provider.type;
352
+ lines.push(` - ${provider.name} [${role}]`);
353
+ }
354
+ if (this._local) {
355
+ lines.push(`Write strategy: local-first (${this._local.name} → immediate, remote → background queue)`);
356
+ }
357
+ if (this._remoteProviders.length > 0) {
358
+ lines.push(`Remote replication: ${this._remoteProviders.map(p => p.name).join(', ')}`);
359
+ }
360
+ if (this._pending.size > 0) {
361
+ lines.push(`Pending uploads recovered: ${this._pending.size}`);
362
+ }
363
+ console.info(lines.join('\n'));
364
+ }
365
+ instantiateProviders(configs) {
366
+ return configs.map(config => {
367
+ // A class constructor is a function; anything else is a factory
368
+ // object with a `create` method.
369
+ if (typeof config === 'function') {
370
+ return new config();
371
+ }
372
+ return config.create();
373
+ });
374
+ }
375
+ pendingKey(id, providerName) {
376
+ return `${id}::${providerName}`;
377
+ }
378
+ /**
379
+ * Reads pending uploads back from the local provider's metadata and
380
+ * seeds the in-memory retry map. Only uploads targeting remote
381
+ * providers that are still configured are re-enqueued.
382
+ */
383
+ async recoverPendingUploads() {
384
+ if (!this._local || this._remoteProviders.length === 0)
385
+ return;
386
+ const pending = await this._local.listAssetsWithPendingRemotes();
387
+ const configured = new Set(this._remoteProviders.map(p => p.name));
388
+ const now = Date.now();
389
+ let recoveredCount = 0;
390
+ for (const asset of pending) {
391
+ const names = (asset.pendingRemotes ?? []).filter(n => configured.has(n));
392
+ if (names.length === 0) {
393
+ // None of the previously pending providers are still configured.
394
+ await this._local.setPendingRemotes(asset.id, []);
395
+ continue;
396
+ }
397
+ for (const name of names) {
398
+ this._pending.set(this.pendingKey(asset.id, name), {
399
+ id: asset.id,
400
+ providerName: name,
401
+ attempts: 0,
402
+ nextAttemptAt: now,
403
+ });
404
+ recoveredCount++;
405
+ }
406
+ }
407
+ if (this.canLogInfo() && recoveredCount > 0) {
408
+ console.info(`[Kritzel] Asset resolver recovered ${recoveredCount} pending upload(s)`);
409
+ }
410
+ }
411
+ scheduleFlush(delayMs) {
412
+ if (this._destroyed)
413
+ return;
414
+ if (this._flushTimer)
415
+ clearTimeout(this._flushTimer);
416
+ this._flushTimer = setTimeout(() => {
417
+ this._flushTimer = null;
418
+ void this.flushQueue();
419
+ }, Math.max(0, delayMs));
420
+ }
421
+ async flushQueue() {
422
+ if (this._destroyed)
423
+ return;
424
+ if (!this._local)
425
+ return; // Cannot flush without local source of truth.
426
+ const now = Date.now();
427
+ let nextRetryIn = Number.POSITIVE_INFINITY;
428
+ for (const [key, job] of Array.from(this._pending.entries())) {
429
+ if (job.nextAttemptAt > now) {
430
+ nextRetryIn = Math.min(nextRetryIn, job.nextAttemptAt - now);
431
+ continue;
432
+ }
433
+ const provider = this._remoteProviders.find(p => p.name === job.providerName);
434
+ if (!provider) {
435
+ this._pending.delete(key);
436
+ continue;
437
+ }
438
+ let blob;
439
+ let metadata;
440
+ try {
441
+ [blob, metadata] = await Promise.all([
442
+ this._local.fetch(job.id),
443
+ this._local.getMetadata(job.id),
444
+ ]);
445
+ }
446
+ catch {
447
+ // Bytes no longer available locally (asset deleted?). Drop job.
448
+ this._pending.delete(key);
449
+ continue;
450
+ }
451
+ try {
452
+ await provider.put(blob, {
453
+ id: job.id,
454
+ mimeType: metadata?.mimeType ?? blob.type ?? 'application/octet-stream',
455
+ kind: metadata?.kind,
456
+ width: metadata?.width,
457
+ height: metadata?.height,
458
+ durationMs: metadata?.durationMs,
459
+ originalFilename: metadata?.originalFilename,
460
+ });
461
+ this._pending.delete(key);
462
+ await this.clearPendingRemote(job.id, job.providerName);
463
+ if (this.canLogInfo()) {
464
+ console.info(`[Kritzel] Asset resolver uploaded ${job.id} to ${provider.name}`);
465
+ }
466
+ }
467
+ catch (err) {
468
+ job.attempts += 1;
469
+ const delay = Math.min(MAX_RETRY_DELAY_MS, BASE_RETRY_DELAY_MS * Math.pow(2, job.attempts - 1));
470
+ job.nextAttemptAt = Date.now() + delay;
471
+ nextRetryIn = Math.min(nextRetryIn, delay);
472
+ if (this.canLogInfo()) {
473
+ console.warn(`[Kritzel] Asset resolver upload to ${provider.name} failed for ${job.id} (attempt ${job.attempts}). ` +
474
+ `Retrying in ${Math.round(delay / 1000)}s.`, err);
475
+ }
476
+ }
477
+ }
478
+ if (this._pending.size > 0 && Number.isFinite(nextRetryIn)) {
479
+ this.scheduleFlush(nextRetryIn);
480
+ }
481
+ }
482
+ /**
483
+ * Removes `providerName` from the `pendingRemotes` list on the asset's
484
+ * metadata record. Clears the field entirely when no providers remain.
485
+ */
486
+ async clearPendingRemote(id, providerName) {
487
+ if (!this._local)
488
+ return;
489
+ const meta = await this._local.getMetadata(id);
490
+ if (!meta)
491
+ return;
492
+ const remaining = (meta.pendingRemotes ?? []).filter(n => n !== providerName);
493
+ await this._local.setPendingRemotes(id, remaining);
494
+ }
495
+ /**
496
+ * Returns a promise that resolves once {@link init} has completed.
497
+ *
498
+ * Callers issuing requests before initialization are queued via this
499
+ * promise instead of being rejected. If the resolver is destroyed
500
+ * before init completes, the promise rejects so callers do not hang.
501
+ */
502
+ ensureReady() {
503
+ if (this._initialized)
504
+ return Promise.resolve();
505
+ if (this._destroyed) {
506
+ return Promise.reject(new Error('[KritzelAssetResolver] Resolver was destroyed.'));
507
+ }
508
+ return this.getReadyPromise();
509
+ }
510
+ getReadyPromise() {
511
+ if (!this._readyPromise) {
512
+ this._readyPromise = new Promise((resolve, reject) => {
513
+ this._resolveReady = resolve;
514
+ this._rejectReady = reject;
515
+ });
516
+ }
517
+ return this._readyPromise;
518
+ }
519
+ generateUuid() {
520
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
521
+ return crypto.randomUUID();
522
+ }
523
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
524
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
525
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
526
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
527
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
528
+ }
529
+ async dataUrlToBlob(dataUrl) {
530
+ const res = await fetch(dataUrl);
531
+ return res.blob();
532
+ }
533
+ canLogInfo() {
534
+ return !this._quiet;
535
+ }
536
+ }
@@ -0,0 +1,111 @@
1
+ /**
2
+ * Remote asset storage provider backed by a plain HTTP endpoint.
3
+ *
4
+ * The host application is responsible for exposing a REST-ish backend
5
+ * that accepts a multipart upload and serves the stored asset at a
6
+ * stable URL. Authentication is injected via `headers`.
7
+ */
8
+ export class HttpAssetProvider {
9
+ type = 'remote';
10
+ name = 'HttpAssetProvider';
11
+ _options;
12
+ constructor(options) {
13
+ this._options = options;
14
+ }
15
+ /**
16
+ * Factory helper enabling use as `HttpAssetProvider.with({ ... })` inside
17
+ * a `KritzelAssetStorageConfig.providers` array.
18
+ */
19
+ static with(options) {
20
+ return { create: () => new HttpAssetProvider(options) };
21
+ }
22
+ async init() {
23
+ if (!this._options.quiet) {
24
+ console.info('HttpAssetProvider initialized');
25
+ }
26
+ }
27
+ destroy() {
28
+ // No-op; no resources to release.
29
+ }
30
+ canResolve(_id) {
31
+ // Remote providers don't know without a round-trip; assume yes.
32
+ return true;
33
+ }
34
+ async put(blob, metadata) {
35
+ const id = metadata.id ?? this.generateUuid();
36
+ const fullMetadata = { ...metadata, id };
37
+ const headers = (await this._options.headers?.()) ?? {};
38
+ const url = await this._options.uploadUrl(fullMetadata);
39
+ const uploader = this._options.upload ?? this.defaultUpload;
40
+ const response = await uploader(url, blob, fullMetadata, headers);
41
+ return {
42
+ id: response.id ?? id,
43
+ kind: metadata.kind ?? 'file',
44
+ mimeType: metadata.mimeType,
45
+ size: blob.size,
46
+ createdAt: Date.now(),
47
+ width: metadata.width,
48
+ height: metadata.height,
49
+ durationMs: metadata.durationMs,
50
+ originalFilename: metadata.originalFilename,
51
+ };
52
+ }
53
+ async resolve(id) {
54
+ return this._options.resolveUrl(id);
55
+ }
56
+ async fetch(id) {
57
+ const url = await this._options.resolveUrl(id);
58
+ const headers = (await this._options.headers?.()) ?? {};
59
+ const res = await fetch(url, { headers });
60
+ if (!res.ok) {
61
+ throw new Error(`[HttpAssetProvider] Failed to fetch asset ${id}: ${res.status} ${res.statusText}`);
62
+ }
63
+ return res.blob();
64
+ }
65
+ async delete(id) {
66
+ if (!this._options.deleteUrl)
67
+ return;
68
+ const url = await this._options.deleteUrl(id);
69
+ const headers = (await this._options.headers?.()) ?? {};
70
+ const res = await fetch(url, { method: 'DELETE', headers });
71
+ if (!res.ok) {
72
+ throw new Error(`[HttpAssetProvider] Failed to delete asset ${id}: ${res.status} ${res.statusText}`);
73
+ }
74
+ if (!this._options.quiet) {
75
+ console.info(`HttpAssetProvider: deleted asset ${id}`);
76
+ }
77
+ }
78
+ defaultUpload = async (url, blob, metadata, headers) => {
79
+ const form = new FormData();
80
+ form.append('metadata', JSON.stringify(metadata));
81
+ form.append('file', blob, metadata.originalFilename || `${metadata.id}.${this.extensionFromMime(metadata.mimeType)}`);
82
+ const res = await fetch(url, {
83
+ method: 'POST',
84
+ headers,
85
+ body: form,
86
+ });
87
+ if (!res.ok) {
88
+ throw new Error(`[HttpAssetProvider] Upload failed: ${res.status} ${res.statusText}`);
89
+ }
90
+ // Try to parse a JSON response; tolerate empty/plain-text bodies.
91
+ const contentType = res.headers.get('content-type') || '';
92
+ if (contentType.includes('application/json')) {
93
+ return (await res.json());
94
+ }
95
+ return {};
96
+ };
97
+ extensionFromMime(mime) {
98
+ const slash = mime.indexOf('/');
99
+ return slash >= 0 ? mime.slice(slash + 1) : 'bin';
100
+ }
101
+ generateUuid() {
102
+ if (typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function') {
103
+ return crypto.randomUUID();
104
+ }
105
+ const bytes = crypto.getRandomValues(new Uint8Array(16));
106
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
107
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
108
+ const hex = Array.from(bytes, b => b.toString(16).padStart(2, '0')).join('');
109
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
110
+ }
111
+ }