Skip to main content

API Reference

This page is a hand-written reference for every public API surface exported by iframe-helper-sdk. It is not auto-generated — every signature, behavior note, and example has been reviewed against the implemented SDK.

If you're looking for conceptual explanations, see Core Concepts. For step-by-step guidance, start with Getting Started.


Quick Reference

ExportKindDescription
createIframeBridgeFunctionCreate a bridge instance for a cross-domain iframe
createTypedIframeBridgeFunctionCreate a contract-typed bridge instance
createIframeChildBridgeFunctionCreate a child-side bridge inside an iframe app
createDiagnosticRecorderFunctionRecord diagnostic events for debugging
IframeBridgeErrorClassTyped SDK error with code, message, and details
BRIDGE_MESSAGE_TYPESConstantTuple of all bridge message type strings
BRIDGE_PROTOCOL_NAMEConstantProtocol name string: 'iframe-bridge'
BRIDGE_PROTOCOL_VERSIONConstantProtocol version number: 1
isBridgeEnvelopeFunctionType guard: checks if a value is a bridge envelope
validateBridgeEnvelopeFunctionValidates and returns a typed bridge envelope
normalizeBridgeRemoteErrorFunctionNormalizes a remote error into a standard shape
childResizePluginFunctionOptional child-side resize plugin
All exported types

IframeBridge, IframeBridgeConfig, IframeBridgeOptions, IframeBridgeContract, IframeBridgeErrorCode, IframeBridgeErrorOptions, IframeBridgeEventHandler, IframeBridgeIframeAttributes, IframeBridgeBootstrapConfig, IframeBridgeBootstrapSessionConfig, IframeBridgeBootstrapParentOriginConfig, IframeBridgeQueueConfig, IframeBridgeResizeAxis, IframeBridgeResizeCallback, IframeBridgeResizeConfig, IframeBridgeResizeEvent, IframeBridgeResizePayload, IframeBridgeTimeoutConfig, IframeBridgeDiagnosticsConfig, IframeBridgeLogger, IframeBridgeSecurityProfile, IframeBridgeRequestContract, TypedIframeBridge, OperationOptions, LifecycleState, ChildLifecycleState, IframeChildBridge, IframeChildBridgeConfig, IframeChildBridgeOptions, IframeChildBridgeEventHandler, IframeChildBridgeRequestHandler, IframeChildOperationOptions, IframeChildBootstrapConfig, IframeChildBootstrapSessionConfig, IframeChildBootstrapParentOriginConfig, IframeChildBridgePlugin, IframeChildBridgePluginSetupContext, IframeChildBridgePluginHandle, DiagnosticEvent, DiagnosticLevel, DiagnosticRecorder, DiagnosticRecorderEntry, DiagnosticRecorderOptions, BridgePlugin, BridgePluginContext, BridgePluginHandle, BridgePluginSetupContext, BridgeEnvelope, BridgeReadyEnvelope, BridgeConnectedEnvelope, BridgeEventEnvelope, BridgeRequestEnvelope, BridgeResponseEnvelope, BridgeEnvelopeBase, BridgeEnvelopeError, BridgeMessageType, BridgeProtocolName, BridgeProtocolVersion, BootstrapParamLocation


Importing

Import the parent bridge from the package root. The child iframe SDK and optional plugins use public subpath exports, such as iframe-helper-sdk/child, iframe-helper-sdk/resize, and iframe-helper-sdk/child/resize. Do not import from internal paths — they are not part of the public API.

import {
BRIDGE_MESSAGE_TYPES,
BRIDGE_PROTOCOL_NAME,
BRIDGE_PROTOCOL_VERSION,
IframeBridgeError,
createDiagnosticRecorder,
createIframeBridge,
createTypedIframeBridge,
isBridgeEnvelope,
normalizeBridgeRemoteError,
validateBridgeEnvelope,
} from 'iframe-helper-sdk';

import type {
BridgeEnvelope,
ChildLifecycleState,
DiagnosticEvent,
DiagnosticRecorder,
IframeChildBridge,
IframeChildBridgeConfig,
IframeChildBridgeOptions,
IframeBridge,
IframeBridgeConfig,
IframeBridgeContract,
IframeBridgeOptions,
IframeBridgeErrorCode,
IframeBridgeResizeConfig,
IframeBridgeResizePayload,
IframeBridgeSecurityProfile,
OperationOptions,
TypedIframeBridge,
} from 'iframe-helper-sdk';

import { createIframeChildBridge } from 'iframe-helper-sdk/child';
import { resizePlugin } from 'iframe-helper-sdk/resize';
import { childResizePlugin } from 'iframe-helper-sdk/child/resize';

Factories

createIframeBridge(config, options?)

function createIframeBridge(
config: IframeBridgeConfig,
options?: IframeBridgeOptions,
): IframeBridge;

Creates and returns a new bridge instance. The factory performs these steps synchronously before returning:

  1. Validates every option in config — throws IframeBridgeError on invalid values.
  2. Creates an HTMLIFrameElement and assigns configured attributes.
  3. Appends bootstrap parameters (session id, parent origin) to the iframe URL.
  4. Installs a message event listener on window.
  5. Starts the handshake timeout timer.
  6. Mounts the iframe into config.container.

The returned bridge is in waiting_for_handshake state and begins listening for bridge:ready from the iframe.

import { createIframeBridge } from 'iframe-helper-sdk';

const bridge = createIframeBridge({
container: '#partner-frame',
src: 'https://partner.example/app',
iframeAttributes: { title: 'Partner application' },
});

// Operations called here are queued until ready
const user = await bridge.request('user:get', { id: '123' });

Parameter: config: IframeBridgeConfig — see Configuration for every option, its type, default, and validation rules.

Parameter: options?: IframeBridgeOptions — optional runtime plugins, such as resizePlugin() from iframe-helper-sdk/resize.

Returns: IframeBridge — the bridge instance. Use this object for all communication with the iframe.

Throws: IframeBridgeError synchronously when config validation fails. Common codes:

  • CONFIG_INVALID_CONTAINER — invalid or missing container
  • CONFIG_INVALID_SRC — missing, unparseable, or unsupported URL scheme
  • CONFIG_UNSAFE_ORIGIN — HTTP non-localhost origin without allowInsecureLocalhost
  • CONFIG_INVALID_TIMEOUT — invalid handshakeTimeoutMs or operationTimeoutMs
  • CONFIG_INVALID_QUEUE — invalid queue.maxSize
  • CONFIG_INVALID_RESIZE — invalid resizePlugin() axis, bounds, offsets, callback, or missing strict-mode max bounds
  • CONFIG_INVALID_SECURITY_PROFILEsecurityProfile is not 'development' or 'strict'

createTypedIframeBridge(config, options?)

function createTypedIframeBridge<TContract extends IframeBridgeContract>(
config: IframeBridgeConfig,
options?: IframeBridgeOptions,
): TypedIframeBridge<TContract>;

Creates a bridge instance with compile-time type narrowing. The runtime behavior is identical to createIframeBridge — the generic contract parameter only affects TypeScript types. See Type-Safe Bridge for the full contract API and examples.

import { createTypedIframeBridge } from 'iframe-helper-sdk';

const bridge = createTypedIframeBridge<PartnerContract>({
container: '#partner-frame',
src: 'https://partner.example/app',
});

// Method and payload types are narrowed by the contract — no manual generics
const user = await bridge.request('user:get', { id: '123' });

Parameter: config: IframeBridgeConfig — same as createIframeBridge.

Returns: TypedIframeBridge<TContract> — bridge instance with contract-narrowed method signatures.


createIframeChildBridge(config?, options?)

function createIframeChildBridge(
config?: IframeChildBridgeConfig,
options?: IframeChildBridgeOptions,
): IframeChildBridge;

Creates a child-side bridge inside the iframe application. The child bridge reads the bootstrap session id and parent origin from the iframe URL, validates the parent origin, sends bridge:ready, waits for bridge:connected, and then routes events and parent requests.

import { createIframeChildBridge } from 'iframe-helper-sdk/child';

const bridge = createIframeChildBridge({
allowedParentOrigins: ['https://host.example'],
});

await bridge.whenConnected();
await bridge.sendEvent('cart:changed', { itemCount: 3 });
bridge.on('theme:changed', (payload) => {});
bridge.handleRequest('user:get', async (payload) => ({ name: 'Ada' }));
bridge.destroy();

Parameter: config?: IframeChildBridgeConfig — child runtime configuration. allowedParentOrigins?: readonly string[] | null is omitted or null by default, which accepts the bootstrap parent origin. Non-empty arrays require exact origin match. Empty arrays are invalid.

Parameter: options?: IframeChildBridgeOptions — optional child plugins, such as childResizePlugin() from iframe-helper-sdk/child/resize.

Returns: IframeChildBridge — the child-side bridge instance.

note

The child bridge does not expose request(). Child request handlers respond to parent bridge:request; the child does not initiate bridge:request.


Parent Bridge Instance API

Every bridge instance (IframeBridge) exposes the properties and methods below. For the contract-typed variant (TypedIframeBridge), see Type-Safe Bridge.

type IframeBridge = {
readonly iframe: HTMLIFrameElement;
readonly state: LifecycleState;
request<TPayload, TResponse>(
method: string,
payload: TPayload,
options?: OperationOptions,
): Promise<TResponse>;
sendEvent<TPayload>(name: string, payload: TPayload, options?: OperationOptions): Promise<void>;
waitForEvent<TPayload>(name: string, options?: OperationOptions): Promise<TPayload>;
on<TPayload>(name: string, handler: (payload: TPayload) => void): () => void;
whenReady(): Promise<void>;
remount(): IframeBridge;
destroy(): void;
};

bridge.iframe

readonly iframe: HTMLIFrameElement

The HTMLIFrameElement owned by this bridge instance. Available immediately after the factory returns. Use it for direct DOM access — scrolling into view, observing dimensions, or integrating with framework refs.

note

Do not modify iframe.src, call iframe.remove(), or change sandbox/allow attributes directly. Use the bridge methods (remount(), destroy()) or the config options instead. Direct DOM manipulation can break the bridge lifecycle.


bridge.state

readonly state: LifecycleState

The current lifecycle state of the bridge. Read-only. Changes are monotonic — the bridge never goes backwards.

type LifecycleState =
| 'created'
| 'mounting'
| 'waiting_for_handshake'
| 'ready'
| 'handshake_failed'
| 'destroyed';
StateDescriptionValid operations
createdConfig validated, bridge object returned. Iframe not yet created.destroy()
mountingIframe element built. Listeners not yet installed.destroy()
waiting_for_handshakeListener installed, iframe loading. Handshake timer ticking.request(), sendEvent(), waitForEvent(), on(), whenReady(), destroy()
readyValid bridge:ready received and bridge:connected sent. Queue flushed.All operations
handshake_failedHandshake timeout elapsed without a valid ready.destroy(), remount()
destroyedBridge destroyed. Listeners removed, iframe detached.None — all calls reject with BRIDGE_DESTROYED

bridge.request(method, payload, options?)

request<TPayload = unknown, TResponse = unknown>(
method: string,
payload: TPayload,
options?: OperationOptions,
): Promise<TResponse>

Sends a request to the iframe and waits for a matching response. This is the primary mechanism for request/response communication — use it when the parent needs data or an action result from the iframe.

// Basic usage — type parameters are optional but recommended
const user = await bridge.request<{ id: string }, { name: string }>('user:get', { id: '123' });

// With a per-operation timeout override
const report = await bridge.request('report:generate', params, {
timeoutMs: 30000,
});

// With an AbortSignal for cancellation
const controller = new AbortController();
const promise = bridge.request('slow:task', payload, {
signal: controller.signal,
});
controller.abort(); // promise rejects with OPERATION_ABORTED

Parameters:

ParameterTypeDescription
methodstringThe request method name. Must be non-empty. The iframe uses this to route the request.
payloadTPayloadThe request payload. Must be structured-cloneable data (no functions, DOM nodes, class instances).
options.timeoutMsnumber (optional)Per-operation timeout in milliseconds. Overrides timeouts.operationTimeoutMs. Must be an integer ≥ 1.
options.signalAbortSignal (optional)Abort controller signal for cancelling the request.

Returns: Promise<TResponse> — resolves with the iframe's response payload when a matching bridge:response arrives.

Behavior:

ScenarioOutcome
Called before ready, queue enabledEnters the pre-ready queue. Flushes and executes after readiness.
Called before ready, queue disabledRejects with BRIDGE_NOT_READY
Called when queue is fullRejects with QUEUE_LIMIT_EXCEEDED
Handshake succeedsRequest is posted and timeout started
Handshake fails while queuedRejects with handshake error (HANDSHAKE_TIMEOUT)
Iframe returns a response with errorRejects with REQUEST_REMOTE_ERROR — the remote error is in error.details.remoteError
Iframe returns multiple responses for the same requestIdOnly the first is accepted; duplicates are ignored
signal aborts while queued or pendingRejects with OPERATION_ABORTED; timers and listeners cleaned up
timeoutMs elapses before responseRejects with REQUEST_TIMEOUT
Invalid timeoutMs providedRejects with OPERATION_INVALID_TIMEOUT
Bridge is destroyed while pendingRejects with BRIDGE_DESTROYED
tip

The operation timeout starts after the request leaves the pre-ready queue and is posted. If the handshake takes 8 seconds and the operation timeout is 5 seconds, the request gets the full 5 seconds — it is not penalized by the handshake wait.


bridge.sendEvent(name, payload, options?)

sendEvent<TPayload = unknown>(
name: string,
payload: TPayload,
options?: OperationOptions,
): Promise<void>

Sends a fire-and-forget event to the iframe. Resolves after the event is posted — it does not wait for the iframe to process the event or acknowledge receipt. If you need confirmation from the iframe, use request() instead.

await bridge.sendEvent('analytics:track', { action: 'opened' });

await bridge.sendEvent('ui:resize', { width: 800, height: 600 });

// With abort signal — prevents posting if cancelled before flush
const controller = new AbortController();
const promise = bridge.sendEvent('analytics:track', data, {
signal: controller.signal,
});
controller.abort(); // rejects with OPERATION_ABORTED

Parameters:

ParameterTypeDescription
namestringThe event name. Must be non-empty.
payloadTPayloadThe event payload. Must be structured-cloneable data.
options.timeoutMsnumber (optional)Per-operation timeout. Used only during the queue phase; the event resolves immediately after posting.
options.signalAbortSignal (optional)Aborts before the event is posted. Once posted, abort is a no-op — the iframe has already received (or will receive) the message.

Returns: Promise<void> — resolves after the event is posted to the iframe via postMessage.

Behavior:

ScenarioOutcome
Called before ready, queue enabledEnters the pre-ready queue. Resolves after flush and post.
Called before ready, queue disabledRejects with BRIDGE_NOT_READY
signal aborts before postRejects with OPERATION_ABORTED
signal aborts after postNo effect — the event was already sent
Invalid timeoutMsRejects with OPERATION_INVALID_TIMEOUT
note

sentEvent resolves when the message leaves the parent. It does not mean the iframe received, processed, or acknowledged the event. For guaranteed processing, use request() and have the iframe respond when done.


bridge.waitForEvent(name, options?)

waitForEvent<TPayload = unknown>(
name: string,
options?: OperationOptions,
): Promise<TPayload>

Waits for the next matching inbound event from the iframe and resolves with its payload. Use this for one-shot event scenarios — such as waiting for an initialization signal, a user action confirmation, or a status update.

// Wait for a one-time status event
const { ready } = await bridge.waitForEvent<{ ready: boolean }>('app:status');

// With a custom timeout
const data = await bridge.waitForEvent('data:loaded', { timeoutMs: 10000 });

// With cancellation
const controller = new AbortController();
const promise = bridge.waitForEvent('transaction:complete', {
signal: controller.signal,
});

Parameters:

ParameterTypeDescription
namestringThe event name to wait for. Must match the name field on the iframe's bridge:event envelope.
options.timeoutMsnumber (optional)How long to wait for the event. Overrides timeouts.operationTimeoutMs.
options.signalAbortSignal (optional)Cancels the wait.

Returns: Promise<TPayload> — resolves with the event payload from the first matching inbound event received after the waiter becomes active.

Behavior:

ScenarioOutcome
Called before ready, queue enabledRegistration is queued. Timeout starts after flush.
Called before ready, queue disabledRejects with BRIDGE_NOT_READY
Timeout elapses before matching eventRejects with EVENT_WAIT_TIMEOUT
signal aborts while queued or waitingRejects with OPERATION_ABORTED; waiter removed
Bridge destroyed before resolutionRejects with BRIDGE_DESTROYED
Invalid timeoutMsRejects with OPERATION_INVALID_TIMEOUT
warning

waitForEvent only resolves for events that arrive after the waiter becomes active. If the iframe sends the event before you call waitForEvent, it will not be matched. Register the waiter before triggering the remote action.


bridge.on(name, handler)

on<TPayload = unknown>(
name: string,
handler: (payload: TPayload) => void,
): () => void

Registers a continuous event listener for inbound events from the iframe. Returns an unsubscribe function. Use this for persistent subscriptions — state changes, real-time data, or UI updates.

const unsubscribe = bridge.on<{ itemCount: number }>('cart:changed', (payload) => {
console.log('Cart now has', payload.itemCount, 'items');
});

// Later, stop listening
unsubscribe();

Parameters:

ParameterTypeDescription
namestringThe event name to subscribe to.
handler(payload: TPayload) => voidCallback invoked with the event payload when a matching event arrives.

Returns: () => void — an unsubscribe function. Call it to remove the listener.

Behavior:

ScenarioOutcome
Registered before readyListener installed. Events are dispatched only when the bridge is ready.
Registered after readyListener installed immediately. Receives future matching events.
Registered after destroyed or handshake_failedThrows — listeners cannot be added to a non-operational bridge.
Bridge destroyedAll listeners removed. No further calls.
Handler throwsDiagnostics emit EVENT_LISTENER_ERROR (if logger configured). Other listeners continue. Bridge operation is not interrupted.
note

on() has no timeout semantics. It fires for every matching event until unsubscribed or the bridge is destroyed. For one-shot waiting, use waitForEvent().


bridge.whenReady()

whenReady(): Promise<void>

Returns a promise that resolves when the bridge enters the ready state — after a valid bridge:ready is received from the iframe and validated. This does not send any protocol message; it is purely a lifecycle observation tool.

try {
await bridge.whenReady();
console.log('Bridge is ready — communication open');
} catch (error) {
if (error instanceof IframeBridgeError) {
console.error('Bridge failed:', error.code);
}
}

Returns: Promise<void> — resolves on ready, rejects on failure.

Behavior:

ScenarioOutcome
Bridge already readyResolves immediately
Bridge becomes readyResolves when the first valid bridge:ready is accepted
Handshake timeout elapsesRejects with HANDSHAKE_TIMEOUT
Bridge destroyed before readyRejects with BRIDGE_DESTROYED
tip

Use whenReady() as a gate before calling operations when queueing is disabled. When queueing is enabled (the default), you can skip it and call operations immediately — they'll flush after readiness.


bridge.remount()

remount(): IframeBridge

Destroys the current bridge, detaches its iframe, and creates a fresh bridge attempt from the same configuration. Returns the new bridge instance — discard the old one and use the returned instance for all future communication.

// After a handshake failure, try again
if (bridge.state === 'handshake_failed') {
bridge = bridge.remount();
await bridge.whenReady();
}

Returns: IframeBridge — a new bridge instance created from the original config.

Behavior:

  • Destroys the current bridge (same guarantees as destroy() — timer cleanup, listener removal, pending rejection, iframe detach).
  • Creates a fresh iframe, fresh session id, fresh handshake timer, fresh message listener.
  • If bootstrap.session.paramValue was explicitly set, that value is reused. For a new session id per attempt, leave it unset so the SDK generates one.
  • Does not run automatically. Call it only when the host application intentionally wants a new attempt.

bridge.destroy()

destroy(): void

Destroys the bridge instance. Idempotent — calling it multiple times is safe and has no additional effect.

bridge.destroy();
// Subsequent calls are no-ops
bridge.destroy(); // safe

Behavior:

  • Removes the message event listener from window.
  • Clears all active timers (handshake timeout, operation timeouts).
  • Rejects all queued operations with BRIDGE_DESTROYED.
  • Rejects all pending requests with BRIDGE_DESTROYED.
  • Rejects all pending waitForEvent calls with BRIDGE_DESTROYED.
  • Unsubscribes all continuous on() listeners.
  • Detaches the owned iframe from its container (removes from DOM).
  • Transitions state to destroyed.
  • All future bridge method calls reject with BRIDGE_DESTROYED.

Child Bridge Instance API

The child bridge (IframeChildBridge) runs inside the iframe application. It has a smaller API than the parent bridge and intentionally does not include request().

type IframeChildBridge = {
readonly parentOrigin: string;
readonly sessionId: string;
readonly state: ChildLifecycleState;
sendEvent<TPayload = unknown>(
name: string,
payload: TPayload,
options?: IframeChildOperationOptions,
): Promise<void>;
on<TPayload = unknown>(
name: string,
handler: IframeChildBridgeEventHandler<TPayload>,
): () => void;
handleRequest<TPayload = unknown, TResponse = unknown>(
name: string,
handler: IframeChildBridgeRequestHandler<TPayload, TResponse>,
): () => void;
whenConnected(): Promise<void>;
destroy(): void;
};

Child lifecycle state

type ChildLifecycleState =
| 'created'
| 'connecting'
| 'connected'
| 'connection_failed'
| 'destroyed';
StateDescription
createdChild bridge object created; bootstrap validation has not completed.
connectingChild bridge is sending bridge:ready and waiting for bridge:connected.
connectedParent acknowledged the handshake. Events and parent request handlers are active.
connection_failedThe child could not complete the connection.
destroyedThe child bridge has been destroyed and will not process more messages.

childBridge.parentOrigin

readonly parentOrigin: string

The accepted parent origin. The child SDK uses this exact origin for postMessage() calls. No wildcard target origin is used in normal operation.


childBridge.sessionId

readonly sessionId: string

The bootstrap session id used to correlate protocol messages for this bridge instance. It is correlation metadata, not auth, a token, or a secret.


childBridge.whenConnected()

whenConnected(): Promise<void>

Resolves when the child bridge receives a valid bridge:connected acknowledgement from the accepted parent origin.

await bridge.whenConnected();
await bridge.sendEvent('app:ready', {});

childBridge.sendEvent(name, payload, options?)

sendEvent<TPayload = unknown>(
name: string,
payload: TPayload,
options?: IframeChildOperationOptions,
): Promise<void>

Sends a child-to-parent bridge:event. The promise resolves after the event is posted; it does not mean the parent processed the event.

await bridge.sendEvent('cart:changed', { itemCount: 3 });

childBridge.on(name, handler)

on<TPayload = unknown>(name: string, handler: (payload: TPayload) => void): () => void

Registers a listener for parent-to-child bridge:event messages with the matching name. Returns an unsubscribe function.

const unsubscribe = bridge.on('theme:changed', (payload) => {
console.log(payload);
});

childBridge.handleRequest(name, handler)

handleRequest<TPayload = unknown, TResponse = unknown>(
name: string,
handler: (payload: TPayload) => TResponse | Promise<TResponse>,
): () => void

Registers a persistent handler for parent bridge:request messages and sends a matching bridge:response when the handler resolves or rejects. The returned cleanup function removes this handler; call it only when the child app should stop handling that request name, such as route cleanup, component unmount, or handler replacement.

const stopHandlingUserGet = bridge.handleRequest('user:get', async (payload) => ({ name: 'Ada' }));

// Later, only when this handler should no longer respond:
stopHandlingUserGet();

Child request handlers respond to parent bridge:request; the child does not initiate bridge:request.


childBridge.destroy()

destroy(): void

Destroys the child bridge and removes SDK-owned listeners, handlers, and plugin resources. Treat the destroyed instance as terminal.


Child Config And Plugin Types

type IframeChildBridgeConfig = {
allowedParentOrigins?: readonly string[] | null;
bootstrap?: IframeChildBootstrapConfig;
diagnostics?: IframeBridgeDiagnosticsConfig;
};

type IframeChildBridgeOptions = {
readonly plugins?: readonly IframeChildBridgePlugin[];
};

type IframeChildOperationOptions = {
signal?: AbortSignal;
};

allowedParentOrigins?: readonly string[] | null controls parent origin acceptance:

  • Omitted or null accepts the bootstrap parent origin.
  • Non-empty arrays require exact origin match.
  • Empty arrays are invalid.
  • Omitted allowlist relies on server-side/browser embedding controls such as CSP frame-ancestors.

Child plugins are optional and registered through the second argument:

import { createIframeChildBridge } from 'iframe-helper-sdk/child';
import { childResizePlugin } from 'iframe-helper-sdk/child/resize';

const bridge = createIframeChildBridge(
{ allowedParentOrigins: ['https://host.example'] },
{ plugins: [childResizePlugin({ axis: 'both' })] },
);

Child resize must be imported from iframe-helper-sdk/child/resize, not iframe-helper-sdk/child.


Type-Safe Bridge

TypedIframeBridge<TContract> is a compile-time-only variant that narrows method names, payloads, and response types based on a contract map. At runtime, it behaves identically to IframeBridge — there is no runtime payload validation.

Contract Shape

type IframeBridgeContract = {
readonly requests?: Record<string, IframeBridgeRequestContract>;
readonly outboundEvents?: Record<string, unknown>;
readonly inboundEvents?: Record<string, unknown>;
};

type IframeBridgeRequestContract = {
readonly payload: unknown;
readonly response: unknown;
};

Each contract field defines a map from method/event name to its payload type:

Contract fieldControls
requestsNarrows request() — method name, payload type, and response type
outboundEventsNarrows sendEvent() — event name and payload type
inboundEventsNarrows on() and waitForEvent() — event name and payload type

TypedIframeBridge API

type TypedIframeBridge<TContract extends IframeBridgeContract> = Omit<
IframeBridge,
'on' | 'remount' | 'request' | 'sendEvent' | 'waitForEvent'
> & {
request<TName extends /* keys of TContract['requests'] */>(
method: TName,
payload: /* TContract['requests'][TName]['payload'] */,
options?: OperationOptions,
): Promise</* TContract['requests'][TName]['response'] */>;

sendEvent<TName extends /* keys of TContract['outboundEvents'] */>(
name: TName,
payload: /* TContract['outboundEvents'][TName] */,
options?: OperationOptions,
): Promise<void>;

waitForEvent<TName extends /* keys of TContract['inboundEvents'] */>(
name: TName,
options?: OperationOptions,
): Promise</* TContract['inboundEvents'][TName] */>;

on<TName extends /* keys of TContract['inboundEvents'] */>(
name: TName,
handler: (payload: /* TContract['inboundEvents'][TName] */) => void,
): () => void;

remount(): TypedIframeBridge<TContract>;
};

Example

type PartnerContract = {
requests: {
'user:get': {
payload: { id: string };
response: { name: string; email: string };
};
'report:generate': {
payload: { start: string; end: string };
response: { url: string };
};
};
outboundEvents: {
'analytics:track': { action: string; label?: string };
'ui:resize': { width: number; height: number };
};
inboundEvents: {
'cart:changed': { itemCount: number };
'app:status': { ready: boolean; version: string };
};
};

const bridge = createTypedIframeBridge<PartnerContract>({
container: '#partner-frame',
src: 'https://partner.example/app',
});

// ✅ Method names are narrowed — autocomplete works
const user = await bridge.request('user:get', { id: '123' });
// ^? { name: string; email: string }

// ✅ Payload is narrowed — wrong shape is a compile error
await bridge.sendEvent('analytics:track', { action: 'opened' });

// ✅ Event name and handler payload are narrowed
bridge.on('cart:changed', (payload) => {
// ^? { itemCount: number }
console.log(payload.itemCount);
});

// ❌ Compile error: 'unknown:method' is not a valid request method
// bridge.request('unknown:method', {});
note

The contract is type-level only. There is no runtime validation of method names or payload shapes. If the iframe sends a cart:changed event with a different shape, the TypeScript types will not catch it at runtime. For runtime validation, add your own checks inside the handler.


OperationOptions

type OperationOptions = {
timeoutMs?: number;
signal?: AbortSignal;
};

Passed as the optional third argument to request(), sendEvent(), and waitForEvent().

OptionTypeDefaultDescription
timeoutMsnumbertimeouts.operationTimeoutMs (5000)Per-operation timeout in milliseconds. Overrides the default. Must be an integer ≥ 1.
signalAbortSignalAn AbortController signal for cancelling the operation.

Validation: If timeoutMs is invalid (not an integer, or < 1), the operation rejects with OPERATION_INVALID_TIMEOUT.

Per-method behavior:

MethodtimeoutMs effectsignal effect
request()Starts after post; rejects with REQUEST_TIMEOUTRejects with OPERATION_ABORTED if aborted while queued or pending
sendEvent()Rejects with OPERATION_INVALID_TIMEOUT if set (fire-and-forget should not have a timeout)Rejects with OPERATION_ABORTED if aborted before post; no-op after post
waitForEvent()Starts after waiter active; rejects with EVENT_WAIT_TIMEOUTRejects with OPERATION_ABORTED; waiter removed

Resize Types

type IframeBridgeResizeAxis = 'width' | 'height' | 'both';

type IframeBridgeResizeConfig = {
enabled?: boolean;
axis?: IframeBridgeResizeAxis;
minWidthPx?: number;
maxWidthPx?: number;
minHeightPx?: number;
maxHeightPx?: number;
offsetWidthPx?: number;
offsetHeightPx?: number;
onResize?: IframeBridgeResizeCallback;
};

type IframeBridgeResizeEvent = {
readonly width?: number;
readonly height?: number;
readonly requestedWidth?: number;
readonly requestedHeight?: number;
};

type IframeBridgeResizeCallback = (event: IframeBridgeResizeEvent) => void;

type IframeBridgeResizePayload =
| { width: number; height?: number }
| { width?: number; height: number };

IframeBridgeResizeConfig is passed to resizePlugin(config) from iframe-helper-sdk/resize. The config object is optional; resizePlugin() enables both axes with no bounds. Unbounded active axes warn in development mode and throw CONFIG_INVALID_RESIZE in strict mode. IframeBridgeResizePayload documents the reserved iframe-side event payload for iframe-bridge:resize.

import { createIframeBridge } from 'iframe-helper-sdk';
import { resizePlugin } from 'iframe-helper-sdk/resize';

const bridge = createIframeBridge(
{
container: '#partner-frame',
src: 'https://partner.example/app',
},
{
plugins: [
resizePlugin({
axis: 'both',
minWidthPx: 320,
maxWidthPx: 1200,
minHeightPx: 240,
maxHeightPx: 900,
offsetHeightPx: 16,
onResize({ width, height, requestedWidth, requestedHeight }) {
console.log({ width, height, requestedWidth, requestedHeight });
},
}),
],
},
);

Offsets are added before min/max bounds are applied. onResize is called after the SDK applies the final dimensions; if it throws, the resize remains applied and the SDK emits a RESIZE_CALLBACK_ERROR diagnostic warning.

Full usage, iframe-side events, security behavior, and diagnostics are covered in Resize Plugin.


IframeBridgeError

class IframeBridgeError extends Error {
readonly code: IframeBridgeErrorCode;
readonly details?: unknown;
constructor(code: IframeBridgeErrorCode, message: string, options?: IframeBridgeErrorOptions);
}

Every error thrown by the SDK is an instance of IframeBridgeError. Use instanceof to distinguish SDK errors from other exceptions in catch blocks.

import { IframeBridgeError } from 'iframe-helper-sdk';

try {
const result = await bridge.request('user:get', { id: '123' });
} catch (error) {
if (error instanceof IframeBridgeError) {
console.error('Bridge error:', error.code, error.message, error.details);
} else {
throw error; // re-throw non-SDK errors
}
}

Properties:

PropertyTypeDescription
codeIframeBridgeErrorCodeThe error code. See Error Codes for every code, its cause, and recovery actions.
messagestringHuman-readable description of what went wrong.
detailsunknownOptional additional context. For REQUEST_REMOTE_ERROR, contains the normalized remote error.
causeunknownThe original error that triggered this one, when available (standard Error.cause).

IframeBridgeErrorCode — the full union of all error codes:

type IframeBridgeErrorCode =
| 'CONFIG_INVALID_CONTAINER'
| 'CONFIG_INVALID_SRC'
| 'CONFIG_INVALID_QUEUE'
| 'CONFIG_INVALID_RESIZE'
| 'CONFIG_INVALID_SECURITY_PROFILE'
| 'CONFIG_INVALID_TIMEOUT'
| 'CONFIG_UNSAFE_ORIGIN'
| 'CONFIG_UNSAFE_PERMISSIONS_POLICY'
| 'CONFIG_UNSAFE_SANDBOX'
| 'DIAGNOSTICS_INVALID_MAX_ENTRIES'
| 'HANDSHAKE_TIMEOUT'
| 'HANDSHAKE_ORIGIN_MISMATCH'
| 'HANDSHAKE_SOURCE_MISMATCH'
| 'HANDSHAKE_SESSION_MISMATCH'
| 'HANDSHAKE_PROTOCOL_MISMATCH'
| 'HANDSHAKE_VERSION_MISMATCH'
| 'BRIDGE_NOT_READY'
| 'BRIDGE_DESTROYED'
| 'QUEUE_LIMIT_EXCEEDED'
| 'QUEUE_CLOSED'
| 'OPERATION_INVALID_TIMEOUT'
| 'OPERATION_ABORTED'
| 'REQUEST_TIMEOUT'
| 'REQUEST_REMOTE_ERROR'
| 'EVENT_WAIT_TIMEOUT'
| 'MESSAGE_INVALID_ENVELOPE'
| 'MESSAGE_TARGET_MISMATCH';

For the complete error reference — what each code means, common causes, and recovery actions — see Error Codes.


createDiagnosticRecorder(options?)

function createDiagnosticRecorder(options?: DiagnosticRecorderOptions): DiagnosticRecorder;

Creates a diagnostic recorder for collecting bridge events during development and debugging. Pass its logger to the bridge's diagnostics.logger config, then inspect recorder.entries after operations complete.

import { createDiagnosticRecorder, createIframeBridge } from 'iframe-helper-sdk';

const recorder = createDiagnosticRecorder({ maxEntries: 100 });

const bridge = createIframeBridge({
container: '#partner-frame',
src: 'https://partner.example/app',
diagnostics: {
debug: true,
logger: recorder.logger,
},
});

await bridge.whenReady();
console.table(recorder.entries);

Parameters:

type DiagnosticRecorderOptions = {
readonly maxEntries?: number; // default: Infinity
readonly now?: () => number; // default: Date.now
};

Returns:

type DiagnosticRecorder = {
readonly entries: readonly DiagnosticRecorderEntry[];
readonly logger: Required<IframeBridgeLogger>;
clear(): void;
};

type DiagnosticRecorderEntry = Readonly<
DiagnosticEvent & {
level: DiagnosticLevel;
sequence: number;
timestamp: number;
}
>;
Property / MethodDescription
entriesArray of recorded diagnostic events, most recent first. Each entry extends DiagnosticEvent with level, sequence, and timestamp.
loggerA debug/warn/error logger object that routes bridge diagnostics into entries. Pass this to diagnostics.logger.
clear()Empties the recorded entries array.
note

The recorder does not capture raw postMessage data or application payloads. Events contain sanitized metadata — message type, session id, error codes, and lifecycle transitions. Application payload data is never included to avoid exposing PII or secrets in diagnostic logs.


Protocol Exports

These constants and utility functions are for iframe-side integrations and advanced parent-side message inspection. Most parent applications only need the factories and bridge instance API. If you're building an iframe application that speaks the bridge protocol, see Wire Protocol.

Constants

import {
BRIDGE_MESSAGE_TYPES,
BRIDGE_PROTOCOL_NAME,
BRIDGE_PROTOCOL_VERSION,
} from 'iframe-helper-sdk';
ConstantTypeValue
BRIDGE_PROTOCOL_NAME'iframe-bridge'The protocol name used in every envelope's protocol field.
BRIDGE_PROTOCOL_VERSION1The protocol version. Envelopes with other versions are rejected.
BRIDGE_MESSAGE_TYPESreadonly ['bridge:ready', 'bridge:connected', 'bridge:event', 'bridge:request', 'bridge:response']Tuple of all valid bridge message type strings.

Validation Functions

import {
isBridgeEnvelope,
validateBridgeEnvelope,
normalizeBridgeRemoteError,
} from 'iframe-helper-sdk';

isBridgeEnvelope(value)

function isBridgeEnvelope(value: unknown): value is BridgeEnvelope;

Type guard that checks whether a value matches the bridge envelope shape. Returns true if the value has the correct protocol, version, sessionId, and type fields with expected types.

window.addEventListener('message', (event) => {
if (isBridgeEnvelope(event.data)) {
// event.data is now typed as BridgeEnvelope
console.log(event.data.type);
}
});

validateBridgeEnvelope(value)

function validateBridgeEnvelope(value: unknown): BridgeEnvelope;

Validates a value against the bridge envelope shape and returns it typed. Throws if the value is not a valid envelope — use isBridgeEnvelope first for conditionals, or validateBridgeEnvelope when you expect a valid envelope and want the error thrown on mismatch.

try {
const envelope = validateBridgeEnvelope(event.data);
} catch {
// Not a valid bridge envelope
}

Validation rules:

  • protocol must be 'iframe-bridge'
  • version must be 1
  • sessionId must be a non-empty string
  • type must be one of BRIDGE_MESSAGE_TYPES
  • name must be non-empty for bridge:event and bridge:request
  • requestId must be non-empty for bridge:request and bridge:response
  • Remote errors must have non-empty code and message

normalizeBridgeRemoteError(error)

function normalizeBridgeRemoteError(error: unknown): BridgeEnvelopeError;

Normalizes a remote error value into the standard { code, message, data? } shape. If the input is already a valid BridgeEnvelopeError, it is returned as-is. Otherwise, a fallback error with code 'REMOTE_ERROR' and the original value as data is returned.

const envelope = validateBridgeEnvelope(event.data);
if (envelope.type === 'bridge:response' && envelope.error) {
const { code, message, data } = normalizeBridgeRemoteError(envelope.error);
console.error(`Remote error ${code}: ${message}`, data);
}

Full Type Reference

IframeBridgeConfig
type IframeBridgeConfig = {
// ── Required ──────────────────────────────
container: Element | string;
src: string | URL;

// ── Iframe presentation ───────────────────
iframeAttributes?: {
title?: string;
className?: string;
id?: string;
name?: string;
allow?: string;
allowFullscreen?: boolean;
loading?: 'eager' | 'lazy';
referrerPolicy?: ReferrerPolicy;
};

// ── Security ──────────────────────────────
sandbox?: string | readonly string[];
replaceContainerContent?: boolean;
targetOrigin?: string;
allowedOrigin?: string;
allowInsecureLocalhost?: boolean;
securityProfile?: 'development' | 'strict';

// ── Bootstrap ─────────────────────────────
bootstrap?: {
session?: {
paramName?: string;
paramValue?: string;
location?: 'query' | 'hash';
};
parentOrigin?: {
enabled?: boolean;
paramName?: string;
value?: string;
location?: 'query' | 'hash';
};
handshakeTimeoutMs?: number;
};

// ── Queue ─────────────────────────────────
queue?: {
enabled?: boolean;
maxSize?: number;
};

// ── Timeouts ──────────────────────────────
timeouts?: {
operationTimeoutMs?: number;
};

// ── Diagnostics ───────────────────────────
diagnostics?: {
debug?: boolean;
logger?: {
debug?(event: DiagnosticEvent): void;
warn?(event: DiagnosticEvent): void;
error?(event: DiagnosticEvent): void;
};
};
};

type IframeBridgeOptions = {
plugins?: readonly BridgePlugin[];
};

type BridgePlugin = (ctx: BridgePluginSetupContext) => BridgePluginHandle;

type BridgePluginSetupContext = {
readonly securityProfile: 'development' | 'strict';
readonly sessionId: string;
readonly warn: (event: DiagnosticEvent) => void;
};

type BridgePluginHandle = {
readonly events: readonly string[];
onEvent(envelope: BridgeEventEnvelope, ctx: BridgePluginContext): void;
};

type BridgePluginContext = {
readonly iframe: HTMLIFrameElement;
readonly sessionId: string;
readonly warn: (event: DiagnosticEvent) => void;
};
IframeBridge
type IframeBridge = {
readonly iframe: HTMLIFrameElement;
readonly state: LifecycleState;
request<TPayload = unknown, TResponse = unknown>(
method: string,
payload: TPayload,
options?: OperationOptions,
): Promise<TResponse>;
sendEvent<TPayload = unknown>(
name: string,
payload: TPayload,
options?: OperationOptions,
): Promise<void>;
waitForEvent<TPayload = unknown>(name: string, options?: OperationOptions): Promise<TPayload>;
on<TPayload = unknown>(name: string, handler: (payload: TPayload) => void): () => void;
whenReady(): Promise<void>;
remount(): IframeBridge;
destroy(): void;
};
IframeBridgeContract and TypedIframeBridge
type IframeBridgeContract = {
readonly requests?: Record<string, IframeBridgeRequestContract>;
readonly outboundEvents?: Record<string, unknown>;
readonly inboundEvents?: Record<string, unknown>;
};

type IframeBridgeRequestContract = {
readonly payload: unknown;
readonly response: unknown;
};

type TypedIframeBridge<TContract extends IframeBridgeContract> = Omit<
IframeBridge,
'on' | 'remount' | 'request' | 'sendEvent' | 'waitForEvent'
> & {
request: /* narrowed by TContract['requests'] */;
sendEvent: /* narrowed by TContract['outboundEvents'] */;
waitForEvent: /* narrowed by TContract['inboundEvents'] */;
on: /* narrowed by TContract['inboundEvents'] */;
remount(): TypedIframeBridge<TContract>;
};
LifecycleState
type LifecycleState =
| 'created'
| 'mounting'
| 'waiting_for_handshake'
| 'ready'
| 'handshake_failed'
| 'destroyed';
OperationOptions
type OperationOptions = {
timeoutMs?: number;
signal?: AbortSignal;
};
IframeBridgeError and related types
class IframeBridgeError extends Error {
readonly code: IframeBridgeErrorCode;
readonly details?: unknown;
constructor(code: IframeBridgeErrorCode, message: string, options?: IframeBridgeErrorOptions);
}

type IframeBridgeErrorOptions = {
details?: unknown;
cause?: unknown;
};

type IframeBridgeErrorCode =
| 'CONFIG_INVALID_CONTAINER'
| 'CONFIG_INVALID_SRC'
| 'CONFIG_INVALID_QUEUE'
| 'CONFIG_INVALID_RESIZE'
| 'CONFIG_INVALID_SECURITY_PROFILE'
| 'CONFIG_INVALID_TIMEOUT'
| 'CONFIG_UNSAFE_ORIGIN'
| 'CONFIG_UNSAFE_PERMISSIONS_POLICY'
| 'CONFIG_UNSAFE_SANDBOX'
| 'DIAGNOSTICS_INVALID_MAX_ENTRIES'
| 'HANDSHAKE_TIMEOUT'
| 'HANDSHAKE_ORIGIN_MISMATCH'
| 'HANDSHAKE_SOURCE_MISMATCH'
| 'HANDSHAKE_SESSION_MISMATCH'
| 'HANDSHAKE_PROTOCOL_MISMATCH'
| 'HANDSHAKE_VERSION_MISMATCH'
| 'BRIDGE_NOT_READY'
| 'BRIDGE_DESTROYED'
| 'QUEUE_LIMIT_EXCEEDED'
| 'QUEUE_CLOSED'
| 'OPERATION_INVALID_TIMEOUT'
| 'OPERATION_ABORTED'
| 'REQUEST_TIMEOUT'
| 'REQUEST_REMOTE_ERROR'
| 'EVENT_WAIT_TIMEOUT'
| 'MESSAGE_INVALID_ENVELOPE'
| 'MESSAGE_TARGET_MISMATCH';
DiagnosticEvent, DiagnosticRecorder, and logger types
type DiagnosticLevel = 'debug' | 'warn' | 'error';

type DiagnosticEvent = {
message: string;
code?: string;
details?: unknown;
level?: DiagnosticLevel;
sessionId?: string;
};

type IframeBridgeLogger = {
debug?(event: DiagnosticEvent): void;
warn?(event: DiagnosticEvent): void;
error?(event: DiagnosticEvent): void;
};

type IframeBridgeDiagnosticsConfig = {
debug?: boolean;
logger?: IframeBridgeLogger;
};

type DiagnosticRecorderOptions = {
readonly maxEntries?: number;
readonly now?: () => number;
};

type DiagnosticRecorderEntry = Readonly<
DiagnosticEvent & {
level: DiagnosticLevel;
sequence: number;
timestamp: number;
}
>;

type DiagnosticRecorder = {
readonly entries: readonly DiagnosticRecorderEntry[];
readonly logger: Required<IframeBridgeLogger>;
clear(): void;
};
BridgeEnvelope and protocol types
type BridgeProtocolName = 'iframe-bridge';
type BridgeProtocolVersion = 1;

type BridgeMessageType =
| 'bridge:ready'
| 'bridge:connected'
| 'bridge:event'
| 'bridge:request'
| 'bridge:response';

type BridgeEnvelopeError = {
code: string;
message: string;
data?: unknown;
};

type BridgeEnvelopeBase<TType extends BridgeMessageType> = {
protocol: BridgeProtocolName;
version: BridgeProtocolVersion;
sessionId: string;
type: TType;
};

type BridgeReadyEnvelope = BridgeEnvelopeBase<'bridge:ready'>;
type BridgeConnectedEnvelope = BridgeEnvelopeBase<'bridge:connected'>;

type BridgeEventEnvelope<TPayload = unknown> = BridgeEnvelopeBase<'bridge:event'> & {
name: string;
payload?: TPayload;
};

type BridgeRequestEnvelope<TPayload = unknown> = BridgeEnvelopeBase<'bridge:request'> & {
requestId: string;
name: string;
payload?: TPayload;
};

type BridgeResponseEnvelope<TPayload = unknown> = BridgeEnvelopeBase<'bridge:response'> & {
requestId: string;
payload?: TPayload;
error?: BridgeEnvelopeError;
};

type BridgeEnvelope<TPayload = unknown> =
| BridgeReadyEnvelope
| BridgeConnectedEnvelope
| BridgeEventEnvelope<TPayload>
| BridgeRequestEnvelope<TPayload>
| BridgeResponseEnvelope<TPayload>;

Next Steps

  • Error Codes — Every error code with common causes and recovery actions.
  • Configuration — Complete reference for every IframeBridgeConfig option.
  • Plugins — Optional tree-shakable parent-side bridge extensions.
  • Resize Plugin — Parent setup, iframe event payload, bounds, and diagnostics.
  • Type-Safe Bridge — Deep-dive on contract maps and typed communication.
  • Wire Protocol — The envelope specification for iframe-side integrations.
  • Security — Security model, profiles, CSP guidance, and production checklist.
  • Debugging & Diagnostics — Diagnostic recorder workflows and logger hooks.
  • Troubleshooting — Diagnostic flowcharts for common problems.