fabricjs-document-engine 0.6.0 → 0.8.0

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 (54) hide show
  1. package/README.md +74 -3
  2. package/dist/assets/asset-pipeline.cjs +3 -1
  3. package/dist/assets/asset-pipeline.js +3 -1
  4. package/dist/engine/create-document-engine.cjs +8 -4
  5. package/dist/engine/create-document-engine.d.cts +5 -0
  6. package/dist/engine/create-document-engine.d.ts +5 -0
  7. package/dist/engine/create-document-engine.js +9 -5
  8. package/dist/engine/errors.d.cts +1 -1
  9. package/dist/engine/errors.d.ts +1 -1
  10. package/dist/export/render-export.cjs +3 -15
  11. package/dist/export/render-export.js +4 -15
  12. package/dist/history/apply-state.cjs +3 -1
  13. package/dist/history/apply-state.js +3 -1
  14. package/dist/history/create-history.cjs +4 -1
  15. package/dist/history/create-history.d.cts +1 -0
  16. package/dist/history/create-history.d.ts +1 -0
  17. package/dist/history/create-history.js +4 -1
  18. package/dist/history/history-stack.cjs +44 -8
  19. package/dist/history/history-stack.js +44 -9
  20. package/dist/index.cjs +6 -0
  21. package/dist/index.d.cts +3 -1
  22. package/dist/index.d.ts +3 -1
  23. package/dist/index.js +3 -1
  24. package/dist/react/engine-context.cjs +12 -0
  25. package/dist/react/engine-context.d.cts +10 -0
  26. package/dist/react/engine-context.d.ts +10 -0
  27. package/dist/react/engine-context.js +11 -0
  28. package/dist/react/use-document-engine.cjs +23 -0
  29. package/dist/react/use-document-engine.d.cts +6 -0
  30. package/dist/react/use-document-engine.d.ts +6 -0
  31. package/dist/react/use-document-engine.js +23 -0
  32. package/dist/react/use-document-event.cjs +12 -0
  33. package/dist/react/use-document-event.d.cts +4 -0
  34. package/dist/react/use-document-event.d.ts +4 -0
  35. package/dist/react/use-document-event.js +12 -0
  36. package/dist/react/use-document-state.cjs +11 -0
  37. package/dist/react/use-document-state.d.cts +5 -0
  38. package/dist/react/use-document-state.d.ts +5 -0
  39. package/dist/react/use-document-state.js +11 -0
  40. package/dist/react.cjs +11 -0
  41. package/dist/react.d.cts +6 -0
  42. package/dist/react.d.ts +6 -0
  43. package/dist/react.js +6 -0
  44. package/dist/recovery/recovery-controller.cjs +29 -3
  45. package/dist/recovery/recovery-controller.js +29 -3
  46. package/dist/security/content-limits.cjs +86 -0
  47. package/dist/security/content-limits.d.cts +11 -0
  48. package/dist/security/content-limits.d.ts +11 -0
  49. package/dist/security/content-limits.js +84 -0
  50. package/dist/state/document-state-store.cjs +114 -0
  51. package/dist/state/document-state-store.d.cts +28 -0
  52. package/dist/state/document-state-store.d.ts +28 -0
  53. package/dist/state/document-state-store.js +114 -0
  54. package/package.json +27 -5
package/dist/index.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_errors = require("./engine/errors.cjs");
3
+ const require_content_limits = require("./security/content-limits.cjs");
3
4
  const require_document_format = require("./document/document-format.cjs");
4
5
  const require_validate_document = require("./document/validate-document.cjs");
5
6
  const require_migrate_document = require("./migrations/migrate-document.cjs");
@@ -7,14 +8,19 @@ const require_create_document_engine = require("./engine/create-document-engine.
7
8
  const require_keyboard_shortcuts = require("./history/keyboard-shortcuts.cjs");
8
9
  const require_unsaved_changes_warning = require("./save/unsaved-changes-warning.cjs");
9
10
  const require_download_export = require("./export/download-export.cjs");
11
+ const require_document_state_store = require("./state/document-state-store.cjs");
10
12
  exports.CURRENT_SCHEMA_VERSION = require_document_format.CURRENT_SCHEMA_VERSION;
11
13
  exports.DocumentEngineError = require_errors.DocumentEngineError;
12
14
  exports.bindKeyboardShortcuts = require_keyboard_shortcuts.bindKeyboardShortcuts;
13
15
  exports.bindUnsavedChangesWarning = require_unsaved_changes_warning.bindUnsavedChangesWarning;
14
16
  exports.createConflictError = require_errors.createConflictError;
15
17
  exports.createDocumentEngine = require_create_document_engine.createDocumentEngine;
18
+ exports.createDocumentStateStore = require_document_state_store.createDocumentStateStore;
16
19
  exports.detectSchemaVersion = require_migrate_document.detectSchemaVersion;
17
20
  exports.downloadExport = require_download_export.downloadExport;
18
21
  exports.isDocumentEngineError = require_errors.isDocumentEngineError;
22
+ exports.isSafeImageUrl = require_content_limits.isSafeImageUrl;
19
23
  exports.migrateDocument = require_migrate_document.migrateDocument;
24
+ exports.refuseUnsafeImageUrls = require_content_limits.refuseUnsafeImageUrls;
25
+ exports.secureDocument = require_content_limits.secureDocument;
20
26
  exports.validateDocument = require_validate_document.validateDocument;
package/dist/index.d.cts CHANGED
@@ -7,6 +7,7 @@ import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/pref
7
7
  import { NewDocumentOptions } from "./document/create-document.cjs";
8
8
  import { CustomObjectDefinition } from "./fabric/object-registry.cjs";
9
9
  import { HistoryOptions, HistoryState } from "./history/create-history.cjs";
10
+ import { ContentLimits, isSafeImageUrl, refuseUnsafeImageUrls, secureDocument } from "./security/content-limits.cjs";
10
11
  import { DocumentVersion, VersionKind, VersionOptions, VersionStorage, VersionSummary } from "./versions/document-version.cjs";
11
12
  import { RecoveryStore } from "./recovery/recovery-store.cjs";
12
13
  import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.cjs";
@@ -22,4 +23,5 @@ import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "
22
23
  import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.cjs";
23
24
  import { DownloadableExport, downloadExport } from "./export/download-export.cjs";
24
25
  import { Migration, MigrationContext, MigrationResult, detectSchemaVersion, migrateDocument } from "./migrations/migrate-document.cjs";
25
- export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type DocumentVersion, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, detectSchemaVersion, downloadExport, isDocumentEngineError, migrateDocument, validateDocument };
26
+ import { DocumentState, DocumentStateStore, createDocumentStateStore } from "./state/document-state-store.cjs";
27
+ export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type ContentLimits, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, isDocumentEngineError, isSafeImageUrl, migrateDocument, refuseUnsafeImageUrls, secureDocument, validateDocument };
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ import { ExportPreflight, ExportProblem, ExportProblemCode } from "./export/pref
7
7
  import { NewDocumentOptions } from "./document/create-document.js";
8
8
  import { CustomObjectDefinition } from "./fabric/object-registry.js";
9
9
  import { HistoryOptions, HistoryState } from "./history/create-history.js";
10
+ import { ContentLimits, isSafeImageUrl, refuseUnsafeImageUrls, secureDocument } from "./security/content-limits.js";
10
11
  import { DocumentVersion, VersionKind, VersionOptions, VersionStorage, VersionSummary } from "./versions/document-version.js";
11
12
  import { RecoveryStore } from "./recovery/recovery-store.js";
12
13
  import { InterruptedLoad, RecoveryOptions, RecoveryRecord } from "./recovery/recovery-controller.js";
@@ -22,4 +23,5 @@ import { KeyboardShortcutOptions, UndoRedoTarget, bindKeyboardShortcuts } from "
22
23
  import { UnsavedChangesSource, bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
23
24
  import { DownloadableExport, downloadExport } from "./export/download-export.js";
24
25
  import { Migration, MigrationContext, MigrationResult, detectSchemaVersion, migrateDocument } from "./migrations/migrate-document.js";
25
- export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentStorage, type DocumentVersion, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, detectSchemaVersion, downloadExport, isDocumentEngineError, migrateDocument, validateDocument };
26
+ import { DocumentState, DocumentStateStore, createDocumentStateStore } from "./state/document-state-store.js";
27
+ export { type AssetManifest, type AssetOptions, type AssetReport, type AssetWarning, type AssetWarningCode, type AutosaveOptions, CURRENT_SCHEMA_VERSION, type ContentLimits, type CustomObjectDefinition, type DocumentCanvas, type DocumentEngine, DocumentEngineError, type DocumentEngineErrorDetails, type DocumentEngineEvents, type DocumentEngineOptions, type DocumentErrorCode, type DocumentInfo, type DocumentIssue, type DocumentState, type DocumentStateStore, type DocumentStorage, type DocumentVersion, type DownloadableExport, type ExportArea, type ExportBackground, type ExportFormat, type ExportOptions, type ExportPreflight, type ExportProblem, type ExportProblemCode, type ExportRect, type ExportResult, type FabricDocument, type FontAsset, type FontLoader, type HistoryOptions, type HistoryState, type ImageAsset, type ImportOptions, type InterruptedLoad, type KeyboardShortcutOptions, type LoadOptions, type Migration, type MigrationContext, type MigrationResult, type NewDocumentOptions, type RecoveryOptions, type RecoveryRecord, type RecoveryStore, type RetryOptions, type SaveContext, type SaveOptions, type SaveResult, type SaveRetryEvent, type SaveState, type SaveStatus, type SerializedFabricObject, type UndoRedoTarget, type UnsavedChangesSource, type Unsubscribe, type UploadRequest, type VersionKind, type VersionOptions, type VersionStorage, type VersionSummary, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, isDocumentEngineError, isSafeImageUrl, migrateDocument, refuseUnsafeImageUrls, secureDocument, validateDocument };
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { DocumentEngineError, createConflictError, isDocumentEngineError } from "./engine/errors.js";
2
+ import { isSafeImageUrl, refuseUnsafeImageUrls, secureDocument } from "./security/content-limits.js";
2
3
  import { CURRENT_SCHEMA_VERSION } from "./document/document-format.js";
3
4
  import { validateDocument } from "./document/validate-document.js";
4
5
  import { detectSchemaVersion, migrateDocument } from "./migrations/migrate-document.js";
@@ -6,4 +7,5 @@ import { createDocumentEngine } from "./engine/create-document-engine.js";
6
7
  import { bindKeyboardShortcuts } from "./history/keyboard-shortcuts.js";
7
8
  import { bindUnsavedChangesWarning } from "./save/unsaved-changes-warning.js";
8
9
  import { downloadExport } from "./export/download-export.js";
9
- export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, detectSchemaVersion, downloadExport, isDocumentEngineError, migrateDocument, validateDocument };
10
+ import { createDocumentStateStore } from "./state/document-state-store.js";
11
+ export { CURRENT_SCHEMA_VERSION, DocumentEngineError, bindKeyboardShortcuts, bindUnsavedChangesWarning, createConflictError, createDocumentEngine, createDocumentStateStore, detectSchemaVersion, downloadExport, isDocumentEngineError, isSafeImageUrl, migrateDocument, refuseUnsafeImageUrls, secureDocument, validateDocument };
@@ -0,0 +1,12 @@
1
+ let react = require("react");
2
+ //#region src/react/engine-context.ts
3
+ const EngineContext = (0, react.createContext)(null);
4
+ function DocumentEngineProvider({ engine, children }) {
5
+ return (0, react.createElement)(EngineContext.Provider, { value: engine }, children);
6
+ }
7
+ function useEngine() {
8
+ return (0, react.useContext)(EngineContext);
9
+ }
10
+ //#endregion
11
+ exports.DocumentEngineProvider = DocumentEngineProvider;
12
+ exports.useEngine = useEngine;
@@ -0,0 +1,10 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.cjs";
2
+ import { ReactNode } from "react";
3
+ //#region src/react/engine-context.d.ts
4
+ export interface DocumentEngineProviderProps {
5
+ engine: DocumentEngine | null;
6
+ children?: ReactNode;
7
+ }
8
+ export declare function DocumentEngineProvider({ engine, children }: DocumentEngineProviderProps): ReactNode;
9
+ export declare function useEngine(): DocumentEngine | null;
10
+ //#endregion
@@ -0,0 +1,10 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/react/engine-context.d.ts
4
+ export interface DocumentEngineProviderProps {
5
+ engine: DocumentEngine | null;
6
+ children?: ReactNode;
7
+ }
8
+ export declare function DocumentEngineProvider({ engine, children }: DocumentEngineProviderProps): ReactNode;
9
+ export declare function useEngine(): DocumentEngine | null;
10
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { createContext, createElement, useContext } from "react";
2
+ //#region src/react/engine-context.ts
3
+ const EngineContext = createContext(null);
4
+ function DocumentEngineProvider({ engine, children }) {
5
+ return createElement(EngineContext.Provider, { value: engine }, children);
6
+ }
7
+ function useEngine() {
8
+ return useContext(EngineContext);
9
+ }
10
+ //#endregion
11
+ export { DocumentEngineProvider, useEngine };
@@ -0,0 +1,23 @@
1
+ const require_create_document_engine = require("../engine/create-document-engine.cjs");
2
+ let react = require("react");
3
+ //#region src/react/use-document-engine.ts
4
+ function useDocumentEngine(canvas, options = {}) {
5
+ const latestOptions = (0, react.useRef)(options);
6
+ latestOptions.current = options;
7
+ const [engine, setEngine] = (0, react.useState)(null);
8
+ (0, react.useEffect)(() => {
9
+ if (!canvas) return void 0;
10
+ const created = require_create_document_engine.createDocumentEngine({
11
+ ...latestOptions.current,
12
+ canvas
13
+ });
14
+ setEngine(created);
15
+ return () => {
16
+ created.destroy();
17
+ setEngine((current) => current === created ? null : current);
18
+ };
19
+ }, [canvas]);
20
+ return engine;
21
+ }
22
+ //#endregion
23
+ exports.useDocumentEngine = useDocumentEngine;
@@ -0,0 +1,6 @@
1
+ import { DocumentEngine, DocumentEngineOptions } from "../engine/create-document-engine.cjs";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/react/use-document-engine.d.ts
4
+ export type ReactEngineOptions = Omit<DocumentEngineOptions, "canvas">;
5
+ export declare function useDocumentEngine(canvas: StaticCanvas | null | undefined, options?: ReactEngineOptions): DocumentEngine | null;
6
+ //#endregion
@@ -0,0 +1,6 @@
1
+ import { DocumentEngine, DocumentEngineOptions } from "../engine/create-document-engine.js";
2
+ import { StaticCanvas } from "fabric";
3
+ //#region src/react/use-document-engine.d.ts
4
+ export type ReactEngineOptions = Omit<DocumentEngineOptions, "canvas">;
5
+ export declare function useDocumentEngine(canvas: StaticCanvas | null | undefined, options?: ReactEngineOptions): DocumentEngine | null;
6
+ //#endregion
@@ -0,0 +1,23 @@
1
+ import { createDocumentEngine } from "../engine/create-document-engine.js";
2
+ import { useEffect, useRef, useState } from "react";
3
+ //#region src/react/use-document-engine.ts
4
+ function useDocumentEngine(canvas, options = {}) {
5
+ const latestOptions = useRef(options);
6
+ latestOptions.current = options;
7
+ const [engine, setEngine] = useState(null);
8
+ useEffect(() => {
9
+ if (!canvas) return void 0;
10
+ const created = createDocumentEngine({
11
+ ...latestOptions.current,
12
+ canvas
13
+ });
14
+ setEngine(created);
15
+ return () => {
16
+ created.destroy();
17
+ setEngine((current) => current === created ? null : current);
18
+ };
19
+ }, [canvas]);
20
+ return engine;
21
+ }
22
+ //#endregion
23
+ export { useDocumentEngine };
@@ -0,0 +1,12 @@
1
+ let react = require("react");
2
+ //#region src/react/use-document-event.ts
3
+ function useDocumentEvent(engine, name, handler) {
4
+ const latestHandler = (0, react.useRef)(handler);
5
+ latestHandler.current = handler;
6
+ (0, react.useEffect)(() => {
7
+ if (!engine) return void 0;
8
+ return engine.on(name, (payload) => latestHandler.current(payload));
9
+ }, [engine, name]);
10
+ }
11
+ //#endregion
12
+ exports.useDocumentEvent = useDocumentEvent;
@@ -0,0 +1,4 @@
1
+ import { DocumentEngine, DocumentEngineEvents } from "../engine/create-document-engine.cjs";
2
+ //#region src/react/use-document-event.d.ts
3
+ export declare function useDocumentEvent<Name extends keyof DocumentEngineEvents>(engine: DocumentEngine | null | undefined, name: Name, handler: (payload: DocumentEngineEvents[Name]) => void): void;
4
+ //#endregion
@@ -0,0 +1,4 @@
1
+ import { DocumentEngine, DocumentEngineEvents } from "../engine/create-document-engine.js";
2
+ //#region src/react/use-document-event.d.ts
3
+ export declare function useDocumentEvent<Name extends keyof DocumentEngineEvents>(engine: DocumentEngine | null | undefined, name: Name, handler: (payload: DocumentEngineEvents[Name]) => void): void;
4
+ //#endregion
@@ -0,0 +1,12 @@
1
+ import { useEffect, useRef } from "react";
2
+ //#region src/react/use-document-event.ts
3
+ function useDocumentEvent(engine, name, handler) {
4
+ const latestHandler = useRef(handler);
5
+ latestHandler.current = handler;
6
+ useEffect(() => {
7
+ if (!engine) return void 0;
8
+ return engine.on(name, (payload) => latestHandler.current(payload));
9
+ }, [engine, name]);
10
+ }
11
+ //#endregion
12
+ export { useDocumentEvent };
@@ -0,0 +1,11 @@
1
+ const require_document_state_store = require("../state/document-state-store.cjs");
2
+ let react = require("react");
3
+ //#region src/react/use-document-state.ts
4
+ const subscribeToNothing = () => () => void 0;
5
+ const nothing = () => null;
6
+ function useDocumentState(engine) {
7
+ const store = (0, react.useMemo)(() => engine ? require_document_state_store.createDocumentStateStore(engine) : null, [engine]);
8
+ return (0, react.useSyncExternalStore)(store ? store.subscribe : subscribeToNothing, store ? store.getSnapshot : nothing, nothing);
9
+ }
10
+ //#endregion
11
+ exports.useDocumentState = useDocumentState;
@@ -0,0 +1,5 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.cjs";
2
+ import { DocumentState } from "../state/document-state-store.cjs";
3
+ //#region src/react/use-document-state.d.ts
4
+ export declare function useDocumentState(engine: DocumentEngine | null | undefined): DocumentState | null;
5
+ //#endregion
@@ -0,0 +1,5 @@
1
+ import { DocumentEngine } from "../engine/create-document-engine.js";
2
+ import { DocumentState } from "../state/document-state-store.js";
3
+ //#region src/react/use-document-state.d.ts
4
+ export declare function useDocumentState(engine: DocumentEngine | null | undefined): DocumentState | null;
5
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { createDocumentStateStore } from "../state/document-state-store.js";
2
+ import { useMemo, useSyncExternalStore } from "react";
3
+ //#region src/react/use-document-state.ts
4
+ const subscribeToNothing = () => () => void 0;
5
+ const nothing = () => null;
6
+ function useDocumentState(engine) {
7
+ const store = useMemo(() => engine ? createDocumentStateStore(engine) : null, [engine]);
8
+ return useSyncExternalStore(store ? store.subscribe : subscribeToNothing, store ? store.getSnapshot : nothing, nothing);
9
+ }
10
+ //#endregion
11
+ export { useDocumentState };
package/dist/react.cjs ADDED
@@ -0,0 +1,11 @@
1
+ "use client";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const require_use_document_engine = require("./react/use-document-engine.cjs");
4
+ const require_use_document_state = require("./react/use-document-state.cjs");
5
+ const require_use_document_event = require("./react/use-document-event.cjs");
6
+ const require_engine_context = require("./react/engine-context.cjs");
7
+ exports.DocumentEngineProvider = require_engine_context.DocumentEngineProvider;
8
+ exports.useDocumentEngine = require_use_document_engine.useDocumentEngine;
9
+ exports.useDocumentEvent = require_use_document_event.useDocumentEvent;
10
+ exports.useDocumentState = require_use_document_state.useDocumentState;
11
+ exports.useEngine = require_engine_context.useEngine;
@@ -0,0 +1,6 @@
1
+ import { DocumentState } from "./state/document-state-store.cjs";
2
+ import { ReactEngineOptions, useDocumentEngine } from "./react/use-document-engine.cjs";
3
+ import { useDocumentState } from "./react/use-document-state.cjs";
4
+ import { useDocumentEvent } from "./react/use-document-event.cjs";
5
+ import { DocumentEngineProvider, DocumentEngineProviderProps, useEngine } from "./react/engine-context.cjs";
6
+ export { DocumentEngineProvider, type DocumentEngineProviderProps, type DocumentState, type ReactEngineOptions, useDocumentEngine, useDocumentEvent, useDocumentState, useEngine };
@@ -0,0 +1,6 @@
1
+ import { DocumentState } from "./state/document-state-store.js";
2
+ import { ReactEngineOptions, useDocumentEngine } from "./react/use-document-engine.js";
3
+ import { useDocumentState } from "./react/use-document-state.js";
4
+ import { useDocumentEvent } from "./react/use-document-event.js";
5
+ import { DocumentEngineProvider, DocumentEngineProviderProps, useEngine } from "./react/engine-context.js";
6
+ export { DocumentEngineProvider, type DocumentEngineProviderProps, type DocumentState, type ReactEngineOptions, useDocumentEngine, useDocumentEvent, useDocumentState, useEngine };
package/dist/react.js ADDED
@@ -0,0 +1,6 @@
1
+ "use client";
2
+ import { useDocumentEngine } from "./react/use-document-engine.js";
3
+ import { useDocumentState } from "./react/use-document-state.js";
4
+ import { useDocumentEvent } from "./react/use-document-event.js";
5
+ import { DocumentEngineProvider, useEngine } from "./react/engine-context.js";
6
+ export { DocumentEngineProvider, useDocumentEngine, useDocumentEvent, useDocumentState, useEngine };
@@ -1,5 +1,25 @@
1
1
  const require_asset_pipeline = require("../assets/asset-pipeline.cjs");
2
2
  //#region src/recovery/recovery-controller.ts
3
+ async function toStoredFiles(files) {
4
+ const entries = await Promise.all(Object.entries(files).map(async ([url, blob]) => [url, {
5
+ type: blob.type,
6
+ bytes: await blob.arrayBuffer()
7
+ }]));
8
+ return Object.fromEntries(entries);
9
+ }
10
+ function fromStoredFiles(files) {
11
+ const restored = {};
12
+ for (const [url, file] of Object.entries(files ?? {})) restored[url] = file instanceof Blob ? file : new Blob([file.bytes], { type: file.type });
13
+ return restored;
14
+ }
15
+ function fromStoredRecord(value) {
16
+ if (!isRecoveryRecord(value)) return void 0;
17
+ const stored = value;
18
+ return {
19
+ ...stored,
20
+ files: fromStoredFiles(stored.files)
21
+ };
22
+ }
3
23
  const documentKeyPrefix = "document:";
4
24
  const unloadKeyPrefix = "unload:";
5
25
  const loadingKey = "loading";
@@ -79,7 +99,10 @@ function createRecoveryController(options) {
79
99
  if (!options.shouldWrite()) return;
80
100
  const document = options.createDocument();
81
101
  const record = createRecord(document, await captureTabOnlyFiles(document));
82
- await store.set(documentKey(document.id), record);
102
+ await store.set(documentKey(document.id), {
103
+ ...record,
104
+ files: await toStoredFiles(record.files)
105
+ });
83
106
  options.onCheckpoint(record);
84
107
  }
85
108
  function writeCheckpointBeforeThePageCloses() {
@@ -91,12 +114,15 @@ function createRecoveryController(options) {
91
114
  return;
92
115
  }
93
116
  const record = createRecord(document, Object.fromEntries(readyFiles));
94
- store.set(documentKey(document.id), record).then(() => options.onCheckpoint(record), options.onError);
117
+ toStoredFiles(record.files).then((files) => store.set(documentKey(document.id), {
118
+ ...record,
119
+ files
120
+ })).then(() => options.onCheckpoint(record), options.onError);
95
121
  }
96
122
  async function read(documentId) {
97
123
  await queue;
98
124
  const [checkpoint, unloadCopy] = await Promise.all([store.get(documentKey(documentId)), store.get(unloadKey(documentId))]);
99
- return newerRecord(isRecoveryRecord(checkpoint) ? checkpoint : void 0, isRecoveryRecord(unloadCopy) ? unloadCopy : void 0);
125
+ return newerRecord(fromStoredRecord(checkpoint), fromStoredRecord(unloadCopy));
100
126
  }
101
127
  function cancel() {
102
128
  if (timer !== void 0) clearTimeout(timer);
@@ -1,5 +1,25 @@
1
1
  import { rewriteUrls } from "../assets/asset-pipeline.js";
2
2
  //#region src/recovery/recovery-controller.ts
3
+ async function toStoredFiles(files) {
4
+ const entries = await Promise.all(Object.entries(files).map(async ([url, blob]) => [url, {
5
+ type: blob.type,
6
+ bytes: await blob.arrayBuffer()
7
+ }]));
8
+ return Object.fromEntries(entries);
9
+ }
10
+ function fromStoredFiles(files) {
11
+ const restored = {};
12
+ for (const [url, file] of Object.entries(files ?? {})) restored[url] = file instanceof Blob ? file : new Blob([file.bytes], { type: file.type });
13
+ return restored;
14
+ }
15
+ function fromStoredRecord(value) {
16
+ if (!isRecoveryRecord(value)) return void 0;
17
+ const stored = value;
18
+ return {
19
+ ...stored,
20
+ files: fromStoredFiles(stored.files)
21
+ };
22
+ }
3
23
  const documentKeyPrefix = "document:";
4
24
  const unloadKeyPrefix = "unload:";
5
25
  const loadingKey = "loading";
@@ -79,7 +99,10 @@ function createRecoveryController(options) {
79
99
  if (!options.shouldWrite()) return;
80
100
  const document = options.createDocument();
81
101
  const record = createRecord(document, await captureTabOnlyFiles(document));
82
- await store.set(documentKey(document.id), record);
102
+ await store.set(documentKey(document.id), {
103
+ ...record,
104
+ files: await toStoredFiles(record.files)
105
+ });
83
106
  options.onCheckpoint(record);
84
107
  }
85
108
  function writeCheckpointBeforeThePageCloses() {
@@ -91,12 +114,15 @@ function createRecoveryController(options) {
91
114
  return;
92
115
  }
93
116
  const record = createRecord(document, Object.fromEntries(readyFiles));
94
- store.set(documentKey(document.id), record).then(() => options.onCheckpoint(record), options.onError);
117
+ toStoredFiles(record.files).then((files) => store.set(documentKey(document.id), {
118
+ ...record,
119
+ files
120
+ })).then(() => options.onCheckpoint(record), options.onError);
95
121
  }
96
122
  async function read(documentId) {
97
123
  await queue;
98
124
  const [checkpoint, unloadCopy] = await Promise.all([store.get(documentKey(documentId)), store.get(unloadKey(documentId))]);
99
- return newerRecord(isRecoveryRecord(checkpoint) ? checkpoint : void 0, isRecoveryRecord(unloadCopy) ? unloadCopy : void 0);
125
+ return newerRecord(fromStoredRecord(checkpoint), fromStoredRecord(unloadCopy));
100
126
  }
101
127
  function cancel() {
102
128
  if (timer !== void 0) clearTimeout(timer);
@@ -0,0 +1,86 @@
1
+ const require_asset_references = require("../assets/asset-references.cjs");
2
+ const require_errors = require("../engine/errors.cjs");
3
+ //#region src/security/content-limits.ts
4
+ const dangerousKeys = /* @__PURE__ */ new Set([
5
+ "__proto__",
6
+ "constructor",
7
+ "prototype"
8
+ ]);
9
+ function isSafeImageUrl(url) {
10
+ const trimmed = url.trim().toLowerCase();
11
+ const scheme = /^([a-z][a-z0-9+.-]*):/.exec(trimmed)?.[1];
12
+ if (scheme === void 0) return true;
13
+ if (scheme === "http" || scheme === "https" || scheme === "blob") return true;
14
+ if (scheme === "data") return trimmed.startsWith("data:image/");
15
+ return false;
16
+ }
17
+ function withoutDangerousKeys(value) {
18
+ return JSON.parse(JSON.stringify(value), (key, nested) => dangerousKeys.has(key) ? void 0 : nested);
19
+ }
20
+ function deepestNesting(value) {
21
+ let deepest = 0;
22
+ const pending = [{
23
+ value,
24
+ depth: 1
25
+ }];
26
+ while (pending.length > 0) {
27
+ const { value: current, depth } = pending.pop();
28
+ if (typeof current !== "object" || current === null) continue;
29
+ deepest = Math.max(deepest, depth);
30
+ for (const child of Object.values(current)) pending.push({
31
+ value: child,
32
+ depth: depth + 1
33
+ });
34
+ }
35
+ return deepest;
36
+ }
37
+ function countObjects(objects) {
38
+ let count = 0;
39
+ const pending = [...objects];
40
+ while (pending.length > 0) {
41
+ const object = pending.pop();
42
+ count += 1;
43
+ if (Array.isArray(object.objects)) pending.push(...object.objects);
44
+ if (object.clipPath && typeof object.clipPath === "object") pending.push(object.clipPath);
45
+ }
46
+ return count;
47
+ }
48
+ function unsafe(path, message) {
49
+ return {
50
+ code: "UNSAFE_DOCUMENT",
51
+ path,
52
+ message
53
+ };
54
+ }
55
+ function secureDocument(input, limits = {}) {
56
+ if (typeof input !== "object" || input === null) return input;
57
+ const maxDepth = limits.maxDepth ?? 100;
58
+ const maxObjects = limits.maxObjects ?? 5e4;
59
+ const document = withoutDangerousKeys(input);
60
+ const issues = [];
61
+ const depth = deepestNesting(document);
62
+ if (depth > maxDepth) issues.push(unsafe("", `the document is nested ${depth} levels deep, the limit is ${maxDepth}`));
63
+ if (Array.isArray(document.objects) && issues.length === 0) {
64
+ const count = countObjects(document.objects.filter((object) => typeof object === "object" && object !== null));
65
+ if (count > maxObjects) issues.push(unsafe("objects", `the document has ${count} objects, the limit is ${maxObjects}`));
66
+ }
67
+ refuseWhenUnsafe(issues);
68
+ return document;
69
+ }
70
+ function refuseWhenUnsafe(issues) {
71
+ if (issues.length === 0) return;
72
+ throw new require_errors.DocumentEngineError("UNSAFE_DOCUMENT", `The document was refused: ${issues.map((issue) => issue.message).join("; ")}`, { issues });
73
+ }
74
+ function refuseUnsafeImageUrls(objects, isAllowedUrl = isSafeImageUrl) {
75
+ const issues = [];
76
+ for (const reference of require_asset_references.findImageReferences(objects)) {
77
+ if (isAllowedUrl(reference.url)) continue;
78
+ const where = reference.objectId ? `object ${reference.objectId}` : "an object";
79
+ issues.push(unsafe("objects", `${where} uses the image address "${reference.url.slice(0, 60)}", which is not allowed`));
80
+ }
81
+ refuseWhenUnsafe(issues);
82
+ }
83
+ //#endregion
84
+ exports.isSafeImageUrl = isSafeImageUrl;
85
+ exports.refuseUnsafeImageUrls = refuseUnsafeImageUrls;
86
+ exports.secureDocument = secureDocument;
@@ -0,0 +1,11 @@
1
+ import { SerializedFabricObject } from "../document/document-format.cjs";
2
+ //#region src/security/content-limits.d.ts
3
+ export interface ContentLimits {
4
+ maxObjects?: number;
5
+ maxDepth?: number;
6
+ isAllowedUrl?: (url: string) => boolean;
7
+ }
8
+ export declare function isSafeImageUrl(url: string): boolean;
9
+ export declare function secureDocument(input: unknown, limits?: ContentLimits): unknown;
10
+ export declare function refuseUnsafeImageUrls(objects: readonly SerializedFabricObject[], isAllowedUrl?: (url: string) => boolean): void;
11
+ //#endregion
@@ -0,0 +1,11 @@
1
+ import { SerializedFabricObject } from "../document/document-format.js";
2
+ //#region src/security/content-limits.d.ts
3
+ export interface ContentLimits {
4
+ maxObjects?: number;
5
+ maxDepth?: number;
6
+ isAllowedUrl?: (url: string) => boolean;
7
+ }
8
+ export declare function isSafeImageUrl(url: string): boolean;
9
+ export declare function secureDocument(input: unknown, limits?: ContentLimits): unknown;
10
+ export declare function refuseUnsafeImageUrls(objects: readonly SerializedFabricObject[], isAllowedUrl?: (url: string) => boolean): void;
11
+ //#endregion
@@ -0,0 +1,84 @@
1
+ import { findImageReferences } from "../assets/asset-references.js";
2
+ import { DocumentEngineError } from "../engine/errors.js";
3
+ //#region src/security/content-limits.ts
4
+ const dangerousKeys = /* @__PURE__ */ new Set([
5
+ "__proto__",
6
+ "constructor",
7
+ "prototype"
8
+ ]);
9
+ function isSafeImageUrl(url) {
10
+ const trimmed = url.trim().toLowerCase();
11
+ const scheme = /^([a-z][a-z0-9+.-]*):/.exec(trimmed)?.[1];
12
+ if (scheme === void 0) return true;
13
+ if (scheme === "http" || scheme === "https" || scheme === "blob") return true;
14
+ if (scheme === "data") return trimmed.startsWith("data:image/");
15
+ return false;
16
+ }
17
+ function withoutDangerousKeys(value) {
18
+ return JSON.parse(JSON.stringify(value), (key, nested) => dangerousKeys.has(key) ? void 0 : nested);
19
+ }
20
+ function deepestNesting(value) {
21
+ let deepest = 0;
22
+ const pending = [{
23
+ value,
24
+ depth: 1
25
+ }];
26
+ while (pending.length > 0) {
27
+ const { value: current, depth } = pending.pop();
28
+ if (typeof current !== "object" || current === null) continue;
29
+ deepest = Math.max(deepest, depth);
30
+ for (const child of Object.values(current)) pending.push({
31
+ value: child,
32
+ depth: depth + 1
33
+ });
34
+ }
35
+ return deepest;
36
+ }
37
+ function countObjects(objects) {
38
+ let count = 0;
39
+ const pending = [...objects];
40
+ while (pending.length > 0) {
41
+ const object = pending.pop();
42
+ count += 1;
43
+ if (Array.isArray(object.objects)) pending.push(...object.objects);
44
+ if (object.clipPath && typeof object.clipPath === "object") pending.push(object.clipPath);
45
+ }
46
+ return count;
47
+ }
48
+ function unsafe(path, message) {
49
+ return {
50
+ code: "UNSAFE_DOCUMENT",
51
+ path,
52
+ message
53
+ };
54
+ }
55
+ function secureDocument(input, limits = {}) {
56
+ if (typeof input !== "object" || input === null) return input;
57
+ const maxDepth = limits.maxDepth ?? 100;
58
+ const maxObjects = limits.maxObjects ?? 5e4;
59
+ const document = withoutDangerousKeys(input);
60
+ const issues = [];
61
+ const depth = deepestNesting(document);
62
+ if (depth > maxDepth) issues.push(unsafe("", `the document is nested ${depth} levels deep, the limit is ${maxDepth}`));
63
+ if (Array.isArray(document.objects) && issues.length === 0) {
64
+ const count = countObjects(document.objects.filter((object) => typeof object === "object" && object !== null));
65
+ if (count > maxObjects) issues.push(unsafe("objects", `the document has ${count} objects, the limit is ${maxObjects}`));
66
+ }
67
+ refuseWhenUnsafe(issues);
68
+ return document;
69
+ }
70
+ function refuseWhenUnsafe(issues) {
71
+ if (issues.length === 0) return;
72
+ throw new DocumentEngineError("UNSAFE_DOCUMENT", `The document was refused: ${issues.map((issue) => issue.message).join("; ")}`, { issues });
73
+ }
74
+ function refuseUnsafeImageUrls(objects, isAllowedUrl = isSafeImageUrl) {
75
+ const issues = [];
76
+ for (const reference of findImageReferences(objects)) {
77
+ if (isAllowedUrl(reference.url)) continue;
78
+ const where = reference.objectId ? `object ${reference.objectId}` : "an object";
79
+ issues.push(unsafe("objects", `${where} uses the image address "${reference.url.slice(0, 60)}", which is not allowed`));
80
+ }
81
+ refuseWhenUnsafe(issues);
82
+ }
83
+ //#endregion
84
+ export { isSafeImageUrl, refuseUnsafeImageUrls, secureDocument };