/** * mountProductView — sandboxed iframe container for Polkadot product dApps. * * Works in web, Electron, and Tauri environments. The iframe is given * `sandbox="allow-scripts"` — no same-origin access, no forms, no popups. * Direct network access is blocked by default, with optional host-approved * exact HTTPS origin exceptions. All communication happens through postMessage. * * @example * ```ts * const handle = mountProductView({ * container: document.getElementById("app-frame")!, * productId: "com.example.myapp", * assets: assetsMap, * sdk, * delegate: { * onMessage(data, productId) { * const outcome = sdk.handleMessage(data, productId); * // ... handle outcome * }, * }, * }); * // Later: * handle.sendResponse(responseBytes); * handle.destroy(); * ``` */ import type { ProductViewSdk } from "./sdk-interfaces.js"; import type { TraceContext, AnalyticsEvent, AnalyticsRouter } from "./telemetry.js"; /** A sandbox violation event captured by the monitor or interactive script. */ export interface SandboxViolation { /** The blocked API that was accessed (e.g. "WebSocket", "localStorage"). */ api: string; /** Any available context (URL, storage key, etc). */ details: Record; /** * Unix epoch milliseconds when the violation occurred (from inside the iframe). * * **Untrusted:** this value is supplied by product JavaScript and can be * falsified. Use `hostTimestamp` for ordering and rate-limiting. */ timestamp: number; /** * Unix epoch milliseconds when the host received this violation. * * Populated by the host on receipt — not influenced by product code. * Prefer this field for audit logging, rate-limiting, and ordering. */ hostTimestamp?: number; /** * Whether the host can grant runtime permission for this API. * * - `"promptable"`: the API can be granted at runtime (fetch/XHR to an HTTPS origin). * The host should prompt the user and call `grantNetworkOrigin()` if approved. * - `"always_blocked"`: the API is unconditionally blocked (storage, workers, etc.). * - `undefined`: not set (monitor mode, backward compat). */ disposition?: "promptable" | "always_blocked"; } export interface ProductViewDelegate { /** Raw binary message from the product. Call sdk.handleMessage() to process. */ onMessage(data: Uint8Array, productId: string): void; /** Product requested navigation to a URL (may be a .dot address). */ onNavigate?(url: string, productId: string): void; /** Load state changed. */ onLoadStateChanged?(state: ProductLoadState): void; /** An error occurred in the product container. */ onError?(error: Error): void; /** * A sandbox violation was detected by the monitor script. * Only called when `enableViolationMonitor` is true in mount options. */ onSandboxViolation?(violation: SandboxViolation, productId: string): void; /** * A product-side analytics event was received. * * Only called when `traceContext` is provided in mount options with * `sampled: true`. Use this to forward events to an {@link AnalyticsRouter} * or handle them directly. */ onProductAnalytics?(event: AnalyticsEvent, productId: string): void; } export type ProductLoadState = "idle" | "loading" | "ready" | "failed"; export interface ProductViewHandle { /** Send a response back to the product (e.g. sign result, RPC response). */ sendResponse(data: Uint8Array): void; /** The stable product instance identifier. */ readonly productId: string; /** Remove the iframe and clean up. */ destroy(): void; } /** * Extended handle returned when `enableInteractiveSandbox` is true. * Adds the ability to grant network origins at runtime. */ export interface InteractiveProductViewHandle extends ProductViewHandle { /** * Grant a product access to a specific HTTPS origin at runtime. * * The origin is validated and sent to the iframe's sandbox script via * the private MessageChannel port. Future fetch/XHR requests to this * origin will be allowed without throwing. * * Invalid origins (non-HTTPS, containing paths/wildcards) are silently * dropped. * * **Recovery pattern:** after a `SANDBOX_VIOLATION` with * `disposition: "promptable"`, the host prompts the user. If approved, * call this method. The product must retry the failed request — the * original call has already thrown `SecurityError`. * * **Lifecycle:** grants are session-scoped and cleared when `destroy()` * is called. To persist across reloads, the host should merge runtime * grants into `approvedNetworkOrigins` at the next `mountProductView()`. */ grantNetworkOrigin(origin: string): void; /** Grant a product access to a specific WSS origin at runtime. */ grantWebSocketOrigin(origin: string): void; /** Grant a product access to a specific STUN/TURN server URL at runtime. */ grantWebRtcServerUrl(url: string): void; } /** * Host-side adapter for the stable `window.host` bridge surface. * * When provided to `mountProductView`, bridge method calls from the product * guest are routed to this adapter. Methods not implemented on the adapter * reject with ERR_UNSUPPORTED_CAPABILITY. */ export interface StableBridgeAdapter { getAddress?(): Promise; storage?: { get(key: string): Promise; set(key: string, value: string): Promise; remove(key: string): Promise; }; statements?: { subscribe(channel: string): Promise; write(channel: string, data: string): Promise; status(): Promise<{ storeRunning: boolean; storeHealth: string; subscribers: number; pendingEvents: number; lastError: string; msSinceHealthy: number; }>; }; identity?: { resolveUsername(username: string): Promise; }; } export interface MountProductViewOptions { /** DOM element to mount the iframe into. */ container: HTMLElement; /** Product identifier (used for bridge correlation and storage scoping). */ productId: string; /** Pre-fetched product assets. Key is the file path (e.g. "index.html"). */ assets: Map; /** Product-view bridge surface used to generate injection scripts. */ sdk: ProductViewSdk; /** Delegate receiving messages from the product. */ delegate: ProductViewDelegate; /** * Exact HTTPS origins that this product instance may access directly. * * Invalid values are ignored and the sandbox remains fail-closed. */ approvedNetworkOrigins?: string[]; /** Exact WSS origins that this product instance may access directly. */ approvedWebSocketOrigins?: string[]; /** Exact STUN/TURN server URLs that this product instance may use for WebRTC. */ approvedWebRtcServerUrls?: string[]; /** * Use the violation monitor instead of the silent lockdown (development only). * * When enabled, every blocked API call emits a `SANDBOX_VIOLATION` postMessage * before throwing. The parent window catches these events and forwards them to * `delegate.onSandboxViolation`. Do NOT enable in production — the monitor * script is more verbose and surfaces internal product error attempts to the * parent frame, which could be a privacy concern. * * Mutually exclusive with `enableInteractiveSandbox`. */ enableViolationMonitor?: boolean; /** * Enable interactive sandbox mode with runtime consent-based network access. * * When enabled: * - Remote APIs (fetch/XHR, WebSocket, WebRTC ICE servers) are wrapped with * mutable allowlists. * - Unapproved requests throw `SecurityError` and emit a `SANDBOX_VIOLATION` * with `disposition: "promptable"`. The host can then prompt the user. * - The host grants access via the interactive handle grant methods. * - Non-network APIs (storage, workers, wallet) remain hard-blocked with * `disposition: "always_blocked"`. * - CSP uses `connect-src https: wss:` — the JS proxy is the primary enforcement. * * Mutually exclusive with `enableViolationMonitor`. * * Returns an {@link InteractiveProductViewHandle} instead of the base handle. */ enableInteractiveSandbox?: boolean; /** * Trace context to inject into the product iframe. * * When provided with `sampled: true`, the product can post analytics and * timing events back to the host via `delegate.onProductAnalytics`. * When absent or `sampled: false`, the guest-side telemetry functions * are no-ops and no analytics postMessages are accepted. */ traceContext?: TraceContext; /** * Analytics router for automatic event dispatch. * * When provided alongside `traceContext`, incoming analytics events from * the product are automatically dispatched through this router instead of * (or in addition to) calling `delegate.onProductAnalytics`. */ analyticsRouter?: AnalyticsRouter; /** * Host-side adapter for the stable `window.host` bridge surface. * * When provided, bridge method calls from the guest (`getAddress()`, * `storage.*`, `statements.*`, `identity.*`) are routed to the adapter * methods. Unimplemented adapter methods reject with * `ERR_UNSUPPORTED_CAPABILITY`. When absent, all stable bridge calls * reject with `ERR_HOST_RUNTIME_UNAVAILABLE`. * * The capabilities advertised in `window.host.__bridge.capabilities` are * derived from which adapter namespaces are present. */ bridgeAdapter?: StableBridgeAdapter; } /** * Prepare a product asset bundle for URL-backed iframe hosting. * * The returned map keeps the original bundle structure, but rewrites * `index.html` so the host bridge and sandbox scripts are injected before * any product code runs. Hosts can then serve the prepared assets from a * real URL space (for example via a service worker or custom protocol). */ export interface PrepareProductViewAssetsOptions { /** Pre-fetched product assets. Key is the file path (e.g. "index.html"). */ assets: Map; /** Product-view bridge surface used to generate injection scripts. */ sdk: ProductViewSdk; /** Exact HTTPS origins that this product instance may access directly. */ approvedNetworkOrigins?: string[]; /** Exact WSS origins that this product instance may access directly. */ approvedWebSocketOrigins?: string[]; /** Exact STUN/TURN server URLs that this product instance may use for WebRTC. */ approvedWebRtcServerUrls?: string[]; /** Use the violation monitor instead of the silent lockdown. */ enableViolationMonitor?: boolean; /** Enable interactive sandbox mode with runtime consent-based network access. */ enableInteractiveSandbox?: boolean; /** Trace context to inject into the product iframe. */ traceContext?: TraceContext; /** * Absolute path prefix under which the prepared product will be served. * * Example: `/__hostsdk_product__/browse.dot/` */ routeBasePath: string; /** * Host-side adapter for the stable `window.host` bridge surface. * * When provided, the capability flags baked into the prepared `index.html` * (`window.host.__bridge.capabilities`) reflect which namespaces are * implemented. Use the same adapter value when calling `mountProductViewUrl` * so capabilities and runtime behaviour stay in sync. */ bridgeAdapter?: StableBridgeAdapter; } /** * Mount a sandboxed ProductView from a host-controlled URL. * * URL-backed ProductView uses a same-origin iframe so service-worker-backed * archive assets and module scripts can load correctly under a real route. * This is a distinct security profile from the stricter srcdoc path. */ export interface MountProductViewUrlOptions { /** DOM element to mount the iframe into. */ container: HTMLElement; /** Product identifier (used for bridge correlation and storage scoping). */ productId: string; /** URL for the prepared product document (typically `${routeBasePath}index.html`). */ frameUrl: string; /** Delegate receiving messages from the product. */ delegate: ProductViewDelegate; /** Whether the prepared product uses monitor mode. */ enableViolationMonitor?: boolean; /** Whether the prepared product uses interactive sandbox mode. */ enableInteractiveSandbox?: boolean; /** Trace context used by the host when routing analytics events. */ traceContext?: TraceContext; /** Analytics router for automatic event dispatch. */ analyticsRouter?: AnalyticsRouter; /** * Host-side adapter for the stable `window.host` bridge surface. * * Must match the adapter used when calling `prepareProductViewAssets` so * that the capability flags in the pre-baked HTML are consistent with the * methods actually available at runtime. */ bridgeAdapter?: StableBridgeAdapter; } /** * Build the web iframe bridge script. * * Uses `WEB_BRIDGE_SCRIPT` which creates a shim `window.__HOST_API_PORT__` * that communicates with the host via `parent.postMessage` directly (raw * Uint8Array, no base64 encoding, no MessageChannel indirection). * * Security note: `'*'` is unavoidable in the iframe-to-host direction * because the sandboxed iframe (no allow-same-origin) has an opaque `null` * origin and cannot know its parent's origin. The host-side security * boundary is the `event.source === iframe.contentWindow` check in * mountProductView's `onMessage` handler. * * For host-to-iframe replies the host uses `'null'` as the targetOrigin * (srcdoc) or the real frame origin (URL-backed). This restricts delivery * to the correct iframe. * * @internal Exported for testing only -- not part of the public API. */ export declare function buildIframeBridgeScript(interactive?: boolean): string; /** * Build a script that installs the stable `window.host` bridge surface in the * sandboxed iframe. Each method routes through a JSON postMessage channel to * the host frame. * * Must be injected AFTER `buildIframeBridgeScript()` (which creates * `window.host`) and BEFORE `buildBridgeMetadataScript()` and the sandbox * lockdown script. * * @internal Exported for testing only — not part of the public API. */ export declare function buildStableBridgeScript(): string; /** * Build a bootstrap script that exposes immutable `window.host.__bridge` * metadata before product code and extension scripts run. * * The current web `srcdoc` profile still uses the binary host-api port for its * core transport, so only extension capabilities are advertised here. * * @internal Exported for testing only — not part of the public API. */ export declare function buildBridgeMetadataScript(extensionInjectScript: string, profile?: "web-srcdoc" | "web-relay", coreCapabilities?: { storage?: boolean; statements?: boolean; identity?: boolean; }): string; /** * Build a script that injects `window.__HOST_TRACE_CONTEXT__` into the iframe. * * The object is frozen to prevent product code from tampering with it. * Only injected when the host provides a `traceContext` in mount options. * * @internal Exported for testing only — not part of the public API. */ export declare function buildTraceContextScript(ctx: TraceContext): string; /** Sandbox attribute applied to the product iframe. */ export declare const IFRAME_SANDBOX_ATTR = "allow-scripts"; export declare const URL_BACKED_IFRAME_SANDBOX_ATTR = "allow-scripts allow-same-origin"; export declare function buildIframeCsp(approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[]): string; export declare const IFRAME_CSP: string; export declare function buildUrlBackedIframeCsp(approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[]): string; /** * Production lockdown script: neutralise browser APIs that could exfiltrate * data or establish out-of-band connections. Silently blocks without reporting. * * Mirrors `SANDBOX_LOCKDOWN_SCRIPT` from `host-product-view/src/security.rs`. */ export declare function buildLockdownScript(approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[], approvedWebRtcServerUrls?: readonly string[]): string; export declare const LOCKDOWN_SCRIPT: string; /** * Development monitor script: wraps every blocked API with an instrumented stub * that sends a SANDBOX_VIOLATION postMessage before throwing SecurityError. * * Use this instead of LOCKDOWN_SCRIPT during development by passing * `enableViolationMonitor: true` to `mountProductView`. * * Mirrors `SANDBOX_MONITOR_SCRIPT` from `host-product-view/src/security.rs`. */ export declare function buildMonitorScript(approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[], approvedWebRtcServerUrls?: readonly string[]): string; export declare const MONITOR_SCRIPT: string; /** * Interactive sandbox script: wraps network APIs with a mutable allowlist. * * - fetch/XHR: checked against an origin allowlist. Unapproved requests * throw SecurityError and emit SANDBOX_VIOLATION with `disposition: "promptable"`. * - Grant delivery: the host sends `{ type: "sandbox-grant", origin, kind: "network" }` * over a dedicated MessagePort created by the iframe bridge. The sandbox * re-validates and adds the origin, then deletes the temporary window handle * before product code runs. * - Non-network APIs: blocked + reported with `disposition: "always_blocked"`. * - fetch/XHR wrappers use `configurable: false, writable: false` to prevent bypass. * - The allowlist Set is frozen after each mutation to prevent product manipulation. * * Mirrors `generate_interactive_sandbox_script()` from `host-product-view/src/security.rs`. * * @internal Exported for testing only. */ export declare function buildInteractiveScript(approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[], approvedWebRtcServerUrls?: readonly string[]): string; export declare const INTERACTIVE_SCRIPT: string; /** Content-Security-Policy for interactive sandbox mode (connect-src https: wss:). */ export declare const IFRAME_CSP_INTERACTIVE: string; export declare const IFRAME_CSP_URL_BACKED_INTERACTIVE: string; /** * Build the full srcdoc string for the sandboxed iframe. * * Preserves the product's original `` structure (meta tags, CSS links, * module scripts, import maps) and injects bridge/lockdown scripts before * any product content. Bundle-local CSS and JS assets referenced by the HTML * are inlined since srcdoc iframes cannot reliably fetch relative paths. * * @internal Exported for testing only — not part of the public API. */ export declare function buildSrcdoc(assets: Map, bridgeScript: string, sandboxScript: string, extensionInjectScript: string, approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[], cspOverride?: string, coreCapabilities?: { storage?: boolean; statements?: boolean; identity?: boolean; }): string; /** * Build a URL-backed product document. * * Unlike `buildSrcdoc()`, this path preserves the bundle's file structure and * expects the host to serve the returned assets from `routeBasePath`. */ export declare function buildUrlBackedDocument(assets: Map, bridgeScript: string, sandboxScript: string, extensionInjectScript: string, routeBasePath: string, approvedNetworkOrigins?: readonly string[], approvedWebSocketOrigins?: readonly string[], cspOverride?: string, coreCapabilities?: { storage?: boolean; statements?: boolean; identity?: boolean; }): string; export declare function prepareProductViewAssets(options: PrepareProductViewAssetsOptions): Map; /** * Mount a sandboxed iframe containing a Polkadot product dApp. * * The iframe is appended to `options.container`. All product code is inlined * from the `assets` map — no external resources are loaded. * * @returns A {@link ProductViewHandle} to send responses and clean up. */ /** * Mount with `enableInteractiveSandbox: true` to get an `InteractiveProductViewHandle`. */ export declare function mountProductView(options: MountProductViewOptions & { enableInteractiveSandbox: true; }): InteractiveProductViewHandle; /** * Mount with default or monitor mode to get a standard `ProductViewHandle`. */ export declare function mountProductView(options: MountProductViewOptions): ProductViewHandle; export declare function mountProductViewUrl(options: MountProductViewUrlOptions & { enableInteractiveSandbox: true; }): InteractiveProductViewHandle; export declare function mountProductViewUrl(options: MountProductViewUrlOptions): ProductViewHandle; //# sourceMappingURL=product-view.d.ts.map