Files
eranos/src/lib/iframeSubdomain.ts
T
Alex Gleason c4454ee2a1 Refactor iframe.diy usage into unified SandboxFrame component
Extract duplicated sandbox protocol logic from NsitePreviewDialog and
Webxdc into a single SandboxFrame component. Shared utilities (MIME
types, base64, HTML injection, JSON-RPC types) move to src/lib/sandbox/.

Add configurable sandboxDomain to AppConfig so the iframe.diy domain
can be overridden via ditto.json, preparing for native Capacitor
implementations.

Strip unused console/navigation/error RPC from previewInjectedScript,
leaving only the /index.html path normalization.
2026-04-08 16:41:23 -05:00

45 lines
1.6 KiB
TypeScript

import { hmac } from '@noble/hashes/hmac';
import { sha256 } from '@noble/hashes/sha256';
import { bytesToHex } from '@noble/hashes/utils';
import { hexToBase36 } from '@/lib/nsiteSubdomain';
const SEED_STORAGE_KEY = 'ditto:seed';
/**
* Get or create a device-local random seed persisted in localStorage.
* This is a general-purpose secret used to derive private identifiers
* (e.g. sandbox frame subdomains) that must not be predictable by third parties.
*/
function getSeed(): string {
const stored = localStorage.getItem(SEED_STORAGE_KEY);
if (stored) return stored;
const seed = crypto.randomUUID();
localStorage.setItem(SEED_STORAGE_KEY, seed);
return seed;
}
/**
* Derive a stable, private subdomain label for a sandbox frame.
*
* Uses HMAC-SHA256 with the device-local seed as the key and
* `prefix|identifier` as the message. Because the seed is secret to
* this device, a third party cannot predict or collide with another
* app's subdomain, preventing cross-app localStorage/IndexedDB access
* on the sandbox domain.
*
* The `prefix` acts as a domain separator so that different use-cases
* (e.g. "webxdc", "nsite") produce distinct subdomains even for the
* same identifier.
*
* The result is a 50-character base36 string (256 bits of entropy) that
* fits within the 63-character subdomain label limit.
*/
export function deriveIframeSubdomain(prefix: string, identifier: string): string {
const seed = getSeed();
const enc = new TextEncoder();
const mac = hmac(sha256, enc.encode(seed), enc.encode(`${prefix}|${identifier}`));
return hexToBase36(bytesToHex(mac));
}