fix: generateId() fallback for insecure-context origins
Problem The phone-preview path (http://192.168.1.167:5173) is a non-secure origin. `window.crypto.randomUUID` is gated behind secure contexts (HTTPS / localhost / 127.0.0.1 / file://) so it's undefined there, and every optimistic-id call site crashed handleAdd with `TypeError: crypto.randomUUID is not a function`. Fix apps/web/src/lib/utils/id.ts — generateId() uses crypto.randomUUID() when available, otherwise falls back to crypto.getRandomValues() + RFC 4122 §4.4 byte assembly. getRandomValues IS exposed on insecure contexts (unlike subtle), so the fallback is v4-compliant and cryptographically acceptable for client-side optimistic ids. Replaced crypto.randomUUID() at every call site: stores/lists.ts, stores/tasks.ts, sync/queue.ts, sync/undoQueue.ts routes/(app)/lists/[id]/+page.svelte routes/(app)/tasks/[id]/+page.svelte CLAUDE.md gotcha added: never call crypto.randomUUID() directly — always import generateId from $lib/utils/id. Tests (written first, confirmed failing on main) src/lib/utils/id.test.ts (4 tests) U-01 returns UUID v4 when randomUUID is available U-02 falls back when randomUUID is undefined U-03 50 fallback-generated ids are all unique U-04 prefers randomUUID when present (doesn't drop into fallback) tests/e2e/insecure-context.test.ts (1 test) INS-01 stubs crypto.randomUUID = undefined via addInitScript, drives the add-item flow, confirms the row renders (if the old call site were still there, handleAdd would throw). Verification just test-all → exit 0, 233 green, 2 skipped: 34 pgTAP (unchanged) 140 Vitest integration + 2 skipped presence (unchanged) 15 Vitest unit (was 11, +4 generateId) 44 Playwright (was 43, +1 INS-01) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
33
apps/web/src/lib/utils/id.ts
Normal file
33
apps/web/src/lib/utils/id.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Generate a RFC 4122 v4 UUID.
|
||||
*
|
||||
* `crypto.randomUUID()` is only exposed on secure contexts (HTTPS, localhost,
|
||||
* 127.0.0.1, file://). The app is served over bare LAN IPs for on-device
|
||||
* phone previews (e.g. http://192.168.1.167:5173) — a non-secure origin —
|
||||
* where `randomUUID` is undefined and every optimistic-id call site crashed
|
||||
* with `TypeError: crypto.randomUUID is not a function`.
|
||||
*
|
||||
* `crypto.getRandomValues` IS available on insecure contexts (unlike
|
||||
* `crypto.subtle` which is gated), so the fallback here is v4-compliant and
|
||||
* cryptographically acceptable for client-side optimistic ids.
|
||||
*
|
||||
* Background: https://developer.mozilla.org/en-US/docs/Web/API/Crypto/randomUUID
|
||||
*/
|
||||
export function generateId(): string {
|
||||
// Secure-context fast path.
|
||||
if (typeof globalThis.crypto?.randomUUID === 'function') {
|
||||
return globalThis.crypto.randomUUID();
|
||||
}
|
||||
|
||||
// Fallback: 16 random bytes → format per RFC 4122 §4.4.
|
||||
const bytes = new Uint8Array(16);
|
||||
globalThis.crypto.getRandomValues(bytes);
|
||||
|
||||
// Set version (4) and variant (10xx) bits.
|
||||
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
||||
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
||||
|
||||
const hex: string[] = [];
|
||||
for (let i = 0; i < 16; i++) hex.push(bytes[i].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('')}`;
|
||||
}
|
||||
Reference in New Issue
Block a user