Without the fallback, every Copy button is dead on HTTP. The execCommand approach is deprecated but works in all current browsers and does not require a secure context.
4.3 KiB
Fix HTTP/Non-Secure Context Compatibility
Date: 2026-04-04 Issues: #4, #5
Problem
crypto.randomUUID() and navigator.clipboard.writeText() are secure-context-only Web APIs. They throw or are undefined when the app is accessed over plain HTTP on non-localhost addresses (e.g., http://192.168.1.x:1349). This breaks all tool operations and copy-to-clipboard functionality.
Stirling-Image is a self-hosted tool where many users deploy on NAS/homelab devices over plain HTTP. This must be a first-class supported deployment mode.
Solution
Part 1: generateId() utility
Add to apps/web/src/lib/utils.ts:
export function generateId(): string {
const bytes = new Uint8Array(16);
crypto.getRandomValues(bytes);
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant 10
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
}
Produces a standard UUID v4 string using crypto.getRandomValues(), which is available in all modern browsers regardless of secure context.
6 call sites replaced (crypto.randomUUID() -> generateId()):
| File | Line | Purpose |
|---|---|---|
apps/web/src/hooks/use-tool-processor.ts |
91 | Single-file job correlation ID |
apps/web/src/hooks/use-tool-processor.ts |
260 | Batch job correlation ID |
apps/web/src/components/tools/ocr-settings.tsx |
52 | OCR job ID |
apps/web/src/components/tools/erase-object-settings.tsx |
52 | Erase object job ID |
apps/web/src/components/tools/pipeline-builder.tsx |
104 | Pipeline step ID |
apps/web/src/pages/automate-page.tsx |
147 | Automation step ID |
Part 2: copyToClipboard() utility
Add to apps/web/src/lib/utils.ts:
export async function copyToClipboard(text: string): Promise<boolean> {
try {
await navigator.clipboard.writeText(text);
return true;
} catch {
// Fallback for non-secure contexts (HTTP on LAN)
// document.execCommand is deprecated but works in all current browsers
// and does not require a secure context
try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
const ok = document.execCommand("copy");
document.body.removeChild(textarea);
return ok;
} catch {
return false;
}
}
}
Tries the Clipboard API first (works on HTTPS/localhost). Falls back to document.execCommand("copy") which is deprecated but works in all current browsers including non-secure contexts. This is the standard clipboard compatibility pattern used by GitHub, Stack Overflow, etc. Returns true/false so callers can decide whether to show a "Copied!" confirmation. Only returns false if both approaches fail.
4 call sites replaced (navigator.clipboard.writeText() -> copyToClipboard()):
| File | Line | Current error handling |
|---|---|---|
apps/web/src/components/settings/settings-dialog.tsx |
1148 | None - needs wrapping |
apps/web/src/components/tools/color-palette-settings.tsx |
47 | Has try-catch, simplify |
apps/web/src/components/tools/ocr-settings.tsx |
120 | None - needs wrapping |
apps/web/src/components/tools/barcode-read-settings.tsx |
48 | Has try-catch, simplify |
What does NOT change
- Server-side code: Node's
crypto.randomUUID()works fine outside browsers vitest.config.ts: Runs in Node, not a browser- No new dependencies added
- No config file changes
Testing
pnpm typecheck- verify all imports resolvepnpm lint- verify Biome formattingpnpm test- catch regressions- Manual: access over HTTP on a non-localhost address, confirm tools work
Alternatives considered
uuidnpm package - rejected, adds a dependency for 5 lines of code that does the same thing internally- Try-catch fallback wrapper (try native
randomUUID(), fall back togetRandomValues()) - rejected, two code paths for zero meaningful performance benefit