Files
SnapOtter/docs/superpowers/specs/2026-04-04-http-compatibility-design.md
T
Siddharth Kumar Sah 2422baec30 docs: add execCommand fallback to clipboard utility in design spec
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.
2026-04-04 16:17:11 +08:00

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 resolve
  • pnpm lint - verify Biome formatting
  • pnpm test - catch regressions
  • Manual: access over HTTP on a non-localhost address, confirm tools work

Alternatives considered

  1. uuid npm package - rejected, adds a dependency for 5 lines of code that does the same thing internally
  2. Try-catch fallback wrapper (try native randomUUID(), fall back to getRandomValues()) - rejected, two code paths for zero meaningful performance benefit