diff --git a/src/lib/dev-mock-project.ts b/src/lib/dev-mock-project.ts index 4323776..0080e2d 100644 --- a/src/lib/dev-mock-project.ts +++ b/src/lib/dev-mock-project.ts @@ -1,4 +1,5 @@ import type { ProjectRow, ProjectType } from "@/lib/projects"; +import { uuid } from "@/lib/uuid"; export function buildMockProjectRow(input: { name: string; @@ -7,7 +8,7 @@ export function buildMockProjectRow(input: { }): ProjectRow { const now = new Date().toISOString(); return { - id: crypto.randomUUID(), + id: uuid(), user_id: "dev-local-user", name: input.name, type: input.type, diff --git a/src/lib/image-editor-store.ts b/src/lib/image-editor-store.ts index 6f00b7a..35a5576 100644 --- a/src/lib/image-editor-store.ts +++ b/src/lib/image-editor-store.ts @@ -26,9 +26,10 @@ import { buildImageSceneDataPayload, parseImageSceneData, } from "@/lib/image-scene-data"; +import { uuid } from "@/lib/uuid"; function createId(): string { - return crypto.randomUUID(); + return uuid(); } function createLayer( diff --git a/src/lib/project-defaults.ts b/src/lib/project-defaults.ts index 1fe70c6..38e823b 100644 --- a/src/lib/project-defaults.ts +++ b/src/lib/project-defaults.ts @@ -1,7 +1,8 @@ import type { ProjectType } from "@/lib/projects"; +import { uuid } from "@/lib/uuid"; function createId(): string { - return crypto.randomUUID(); + return uuid(); } export function createDefaultSceneData(type: ProjectType): Record { diff --git a/src/lib/studio-store.ts b/src/lib/studio-store.ts index 0723233..c16d3f7 100644 --- a/src/lib/studio-store.ts +++ b/src/lib/studio-store.ts @@ -39,9 +39,10 @@ import { captureSceneThumbnailFromStage, scheduleSceneThumbnailCapture, } from "@/lib/studio-scene-thumbnail"; +import { uuid } from "@/lib/uuid"; function createId(): string { - return crypto.randomUUID(); + return uuid(); } function defaultPropsForType(type: LayerType): LayerProps { diff --git a/src/lib/uuid.ts b/src/lib/uuid.ts new file mode 100644 index 0000000..c1e96de --- /dev/null +++ b/src/lib/uuid.ts @@ -0,0 +1,37 @@ +/** + * RFC4122 v4 UUID that also works in a NON-secure context (plain `http://` on a LAN + * IP, e.g. when localhost is unavailable), where `crypto.randomUUID` is undefined. + * + * Order of preference: crypto.randomUUID (secure contexts) → crypto.getRandomValues + * (available over http too) → Math.random fallback. + */ +export function uuid(): string { + const c: Crypto | undefined = + typeof globalThis !== "undefined" ? (globalThis.crypto as Crypto | undefined) : undefined; + + if (c && typeof c.randomUUID === "function") { + return c.randomUUID(); + } + + const bytes = new Uint8Array(16); + if (c && typeof c.getRandomValues === "function") { + c.getRandomValues(bytes); + } else { + for (let i = 0; i < 16; i++) bytes[i] = Math.floor(Math.random() * 256); + } + bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4 + bytes[8] = (bytes[8] & 0x3f) | 0x80; // RFC4122 variant + + const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")); + return ( + hex.slice(0, 4).join("") + + "-" + + hex.slice(4, 6).join("") + + "-" + + hex.slice(6, 8).join("") + + "-" + + hex.slice(8, 10).join("") + + "-" + + hex.slice(10, 16).join("") + ); +}