From 5b2617d621fd22dfe71be85f4acf1ab16c03a590 Mon Sep 17 00:00:00 2001 From: "soroush.asadi" Date: Fri, 5 Jun 2026 10:36:57 +0330 Subject: [PATCH] fix(studio): crypto.randomUUID crash on non-secure origins (http LAN IP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Build now (and every studio/image editor id generation) called crypto.randomUUID, which is undefined outside a secure context — so over http:// (used because localhost is VPN-hijacked) the click threw 'crypto.randomUUID is not a function' and the spinner hung forever, never reaching the editor. Add lib/uuid.ts (crypto.randomUUID → crypto.getRandomValues → Math.random fallback) and use it in studio-store, image-editor-store, project-defaults, dev-mock-project. Verified headless (Chrome over http://172.28.144.1): Build now → 201 → navigates to /studio/video/ → editor renders Scene 1 with editable title/subtitle fields. Co-Authored-By: Claude Opus 4.8 --- src/lib/dev-mock-project.ts | 3 ++- src/lib/image-editor-store.ts | 3 ++- src/lib/project-defaults.ts | 3 ++- src/lib/studio-store.ts | 3 ++- src/lib/uuid.ts | 37 +++++++++++++++++++++++++++++++++++ 5 files changed, 45 insertions(+), 4 deletions(-) create mode 100644 src/lib/uuid.ts 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("") + ); +}