Error Codes
Every error from the SDK is an instance of IframeBridgeError — a typed error class that carries a machine-readable code, a human-readable message, and optional details. This page catalogs all 27 error codes, explains what triggers each one, and what you should do about it.
The same error class and codes are used by the parent SDK and the child iframe SDK where the failure category applies. Child-specific notes below call out iframe-helper-sdk/child behavior without adding separate child-only codes.
If you're stuck on a specific problem, start with the Troubleshooting page for diagnostic flowcharts. For the config options that trigger validation errors, see Configuration.
IframeBridgeError
import { IframeBridgeError } from 'iframe-helper-sdk';
class IframeBridgeError extends Error {
readonly code: IframeBridgeErrorCode;
readonly details?: unknown;
}
nameis always'IframeBridgeError'— you caninstanceof-check it.codeis a string from theIframeBridgeErrorCodeunion. Use it to programmatically branch your error handling.messageis a human-readable description of what went wrong.detailsmay carry extra context — for remote errors it includes the normalizedremoteErrorobject from the iframe, for config errors it may include the invalid value.
Config validation errors are thrown synchronously by createIframeBridge() and createIframeChildBridge(). They happen before the parent iframe is created or before the child bridge starts, so wrapping the factory call in try/catch is sufficient.
Operation errors (timeouts, aborts, remote errors) and lifecycle errors (not ready, destroyed, handshake failed) are delivered as rejected promises from methods such as request(), sendEvent(), waitForEvent(), whenReady(), and child whenConnected().
Config Errors
Config errors are thrown synchronously by createIframeBridge(), createTypedIframeBridge(), and createIframeChildBridge() when the provided configuration is invalid or unsafe. You won't have a bridge instance yet — fix the config and try again.
| Code | Typical cause | Recovery |
|---|---|---|
CONFIG_INVALID_CONTAINER | container is missing, not an Element, is an invalid selector string, or the selector returned no match. | Pass a valid DOM element or a selector that resolves to an element in the document. |
CONFIG_INVALID_SRC | src is missing, cannot be parsed as an absolute URL, contains embedded credentials, or uses an unsupported scheme (javascript:, data:, blob:). | Use an absolute HTTPS URL without credentials. For localhost HTTP during development, enable allowInsecureLocalhost. |
CONFIG_INVALID_QUEUE | queue.maxSize is not a positive integer. | Set queue.maxSize to an integer ≥ 1, or omit it to use the default of 50. |
CONFIG_INVALID_RESIZE | resizePlugin() axis is invalid, a resize bound or offset is invalid, onResize is not a function, a minimum bound is greater than its matching maximum, or strict mode is missing max bounds for active resize axes. | Use a valid axis, finite integer offsets, non-negative integer bounds, minimum values that do not exceed maximum values, and max bounds for every active axis in strict mode. |
CONFIG_INVALID_SECURITY_PROFILE | securityProfile is set to a value other than 'development' or 'strict'. | Use 'strict' for production fail-fast behavior, 'development' for warnings, or omit the option entirely. |
CONFIG_INVALID_TIMEOUT | bootstrap.handshakeTimeoutMs, child bootstrap.connectionTimeoutMs, or timeouts.operationTimeoutMs is not a positive integer. | Set timeout values to integers ≥ 1, or omit them to use the defaults. |
CONFIG_UNSAFE_ORIGIN | A configured origin (src, targetOrigin, allowedOrigin, bootstrap.parentOrigin.value, or child allowedParentOrigins) is empty, wildcard-based, non-HTTPS (outside localhost development), or contains a path/query/hash. | Use exact HTTPS origins. For localhost HTTP during development, set allowInsecureLocalhost: true on the parent side. Child allowedParentOrigins must be omitted, null, or a non-empty exact-origin array. |
CONFIG_UNSAFE_PERMISSIONS_POLICY | iframeAttributes.allow uses a wildcard (*) grant in securityProfile: 'strict'. | Replace * with an explicit feature list scoped to the features your iframe actually needs (e.g., "fullscreen 'self'"). |
CONFIG_UNSAFE_SANDBOX | sandbox combines allow-scripts with allow-same-origin in securityProfile: 'strict'. This combination effectively removes sandbox isolation. | Remove one of the conflicting tokens, or switch to securityProfile: 'development' after a documented security review. |
DIAGNOSTICS_INVALID_MAX_ENTRIES | maxEntries passed to createDiagnosticRecorder() is not a positive integer. | Pass an integer ≥ 1, or omit the option to use the default of 100. |
These errors are thrown during config normalization, before any iframe is created or mounted. Your createIframeBridge() or createIframeChildBridge() call itself is the throwing boundary — wrap it in try/catch.
Handshake Errors
Handshake errors occur during the bridge readiness phase — between iframe mount and the first valid bridge:ready message. For the full handshake sequence and protocol rules, see Wire Protocol.
For child SDK integrations, the same mismatch categories apply while the child waits for bridge:connected from the accepted parent origin. The session id is still correlation metadata only, not auth or proof of trust.
| Code | Typical cause | Recovery |
|---|---|---|
HANDSHAKE_TIMEOUT | The iframe did not send a valid bridge:ready within bootstrap.handshakeTimeoutMs (default: 10 seconds). | Confirm the iframe reads the configured session and parent origin params, sends bridge:ready to the exact parent origin, and the iframe is loaded from the expected origin. Check that the iframe URL resolves and returns HTML. |
HANDSHAKE_ORIGIN_MISMATCH | An incoming message that looked like a handshake came from an unexpected origin. The SDK rejects it rather than processing a potentially deceptive message. | Verify allowedOrigin matches the iframe's actual origin. If the iframe redirects to a different origin, set allowedOrigin and targetOrigin explicitly. |
HANDSHAKE_SOURCE_MISMATCH | An incoming message that looked like a handshake came from a window other than the owned iframe. | This usually indicates another window or iframe on the page is sending messages. Verify only one iframe is configured per bridge instance. |
HANDSHAKE_SESSION_MISMATCH | An incoming message that looked like a handshake carried a sessionId that doesn't match this bridge instance. | Check that the iframe echoes the same session parameter the SDK appended to the URL. If you renamed bootstrap.session.paramName, make sure the iframe reads the new parameter name. |
HANDSHAKE_PROTOCOL_MISMATCH | An incoming message that looked like a handshake did not declare protocol: 'iframe-bridge'. | The iframe must include protocol: 'iframe-bridge' in every envelope. This is part of the wire protocol contract. |
HANDSHAKE_VERSION_MISMATCH | An incoming message that looked like a handshake declared a protocol version other than 1. | The iframe must set version: 1. Future SDK versions may support version negotiation, but MVP requires exact match. |
Child context: HANDSHAKE_ORIGIN_MISMATCH can indicate that bridge:connected came from an origin other than the accepted parent origin. A bootstrap parent origin that is not in configured allowedParentOrigins is a config-time CONFIG_UNSAFE_ORIGIN failure. If allowedParentOrigins is omitted or null, the child accepts the bootstrap parent origin and relies on iframe-side controls such as CSP frame-ancestors to restrict embedding.
HANDSHAKE_ORIGIN_MISMATCH, HANDSHAKE_SOURCE_MISMATCH, HANDSHAKE_SESSION_MISMATCH, HANDSHAKE_PROTOCOL_MISMATCH, and HANDSHAKE_VERSION_MISMATCH are surfaced through diagnostics when logger hooks are configured. The lifecycle outcome when no valid ready message is accepted is always HANDSHAKE_TIMEOUT. Enable debug diagnostics with createDiagnosticRecorder to see detailed rejection reasons during integration. See Debugging & Diagnostics.
Operation Errors
Operation errors occur during active bridge communication — parent request(), parent or child sendEvent(), and parent waitForEvent() calls. These are delivered as rejected promises.
| Code | Typical cause | Recovery |
|---|---|---|
OPERATION_INVALID_TIMEOUT | Per-operation timeoutMs is not a positive integer. | Pass timeoutMs as an integer ≥ 1 in the options argument. Omit it to use the global timeouts.operationTimeoutMs default. |
OPERATION_ABORTED | The AbortSignal passed in options.signal was already aborted, or was aborted while the operation was queued or pending. | This is caller-initiated cancellation, not an error condition. Create a new AbortController for a retry. Note: aborting a posted sendEvent cannot undo remote processing. |
REQUEST_TIMEOUT | A request() was sent but the iframe did not respond with a matching bridge:response before the operation timeout. | Check that the iframe request handler is registered and responding. Increase the timeout only if the remote work legitimately takes longer than the default 5 seconds. |
REQUEST_REMOTE_ERROR | The iframe returned a bridge:response with an error object. | Inspect the remote error via error.details.remoteError — it contains code, message, and optional data from the iframe. |
EVENT_WAIT_TIMEOUT | waitForEvent() did not receive a matching inbound event before the operation timeout. | Register the waiter before triggering the remote action that should produce the event. Confirm the iframe sends the expected event name. |
Child context: REQUEST_TIMEOUT is a parent-side error. The child SDK has no request() method and does not initiate bridge:request; child handleRequest() handlers only respond to parent requests.
Accessing remote errors
When REQUEST_REMOTE_ERROR is thrown, the iframe's error is available in details:
try {
await bridge.request('user:get', { id: '123' });
} catch (error) {
if (error instanceof IframeBridgeError && error.code === 'REQUEST_REMOTE_ERROR') {
const remote = error.details as {
remoteError: { code: string; message: string; data?: unknown };
};
console.error('Iframe error:', remote.remoteError.code, remote.remoteError.message);
}
}
Or use the normalizeBridgeRemoteError helper:
import { normalizeBridgeRemoteError, IframeBridgeError } from 'iframe-helper-sdk';
try {
await bridge.request('user:get', { id: '123' });
} catch (error) {
if (error instanceof IframeBridgeError && error.code === 'REQUEST_REMOTE_ERROR') {
const remote = normalizeBridgeRemoteError(error.details);
console.error(remote.code, remote.message);
}
}
Lifecycle Errors
Lifecycle errors are about the bridge's state — what it can and can't do at a given moment.
| Code | Typical cause | Recovery |
|---|---|---|
BRIDGE_NOT_READY | An operation was called before readiness and the pre-ready queue is disabled (queue.enabled: false). | Either await bridge.whenReady() before communicating, or enable the queue by setting queue.enabled: true (the default). |
BRIDGE_DESTROYED | An operation was attempted after bridge.destroy() was called, or whenReady() awaited a bridge that was destroyed before readiness. | destroy() is terminal — create a new bridge with createIframeBridge() or call bridge.remount(). |
QUEUE_LIMIT_EXCEEDED | Too many operations were queued before readiness, exceeding queue.maxSize (default: 50). | Reduce pre-ready activity, increase queue.maxSize deliberately, or await readiness before queuing more operations. |
QUEUE_CLOSED | An operation attempted to enter the queue, but the queue has already closed because handshake failed or the bridge was destroyed. | Check bridge.state to understand why the queue closed. If the handshake failed, fix the handshake and call bridge.remount(). |
Child context: BRIDGE_DESTROYED can also reject child operations after createIframeChildBridge() instances are destroyed. Use whenConnected() as the child-side readiness gate.
whenReady() to avoid lifecycle errorsThe safest pattern: await bridge.whenReady() before any communication. This avoids BRIDGE_NOT_READY entirely and lets the queue handle the window between factory creation and readiness.
const bridge = createIframeBridge({ container: '#root', src: 'https://partner.example/app' });
await bridge.whenReady();
// Now safe to communicate — bridge is ready.
const data = await bridge.request('data:get', {});
Message Errors
Message errors occur at the transport/envelope level — before a message reaches application logic.
| Code | Typical cause | Recovery |
|---|---|---|
MESSAGE_INVALID_ENVELOPE | A message matched the transport (correct origin, session, and source) but failed envelope validation — missing type, missing name on an event/request, missing requestId on a request/response, or an invalid error shape on a response. | Check that both parent and child messages follow the envelope spec: protocol: 'iframe-bridge', version: 1, a non-empty sessionId, a known type, and required fields per type. See Wire Protocol. |
MESSAGE_TARGET_MISMATCH | The SDK could not post a message to the iframe — the iframe window or target origin was unavailable. | Confirm the iframe is still mounted in the DOM and targetOrigin is the correct exact origin. This can happen after the iframe navigates to a different origin or is removed from the DOM. |
Child context: MESSAGE_TARGET_MISMATCH can also occur if the child cannot post to an available parent window. The child still targets the accepted exact parent origin; it should not fall back to '*' in normal operation.
Error Handling Patterns
Catch and branch by code
The code property lets you handle different errors differently:
import { createIframeBridge, IframeBridgeError } from 'iframe-helper-sdk';
try {
const bridge = createIframeBridge({
container: '#partner-frame',
src: 'https://partner.example/app',
});
await bridge.whenReady();
const user = await bridge.request('user:get', { id: '123' });
console.log('User:', user.name);
} catch (error) {
if (!(error instanceof IframeBridgeError)) {
// Unexpected error — rethrow
throw error;
}
switch (error.code) {
case 'CONFIG_UNSAFE_ORIGIN':
console.error('Origin config is invalid. Use exact HTTPS origins.', error.message);
break;
case 'HANDSHAKE_TIMEOUT':
console.error('Iframe did not respond. Check the iframe integration and URL.', error.message);
// Maybe show a fallback UI
break;
case 'REQUEST_TIMEOUT':
console.error('Request timed out. The iframe may be slow or unresponsive.', error.message);
// Retry with a longer timeout, or show an error state
break;
case 'REQUEST_REMOTE_ERROR': {
const remote = error.details as { remoteError: { code: string; message: string } };
console.error(
`Iframe returned error ${remote.remoteError.code}: ${remote.remoteError.message}`,
);
break;
}
case 'OPERATION_ABORTED':
// Caller cancelled — do nothing
break;
default:
console.error('Bridge error:', error.code, error.message, error.details);
}
}
Handling AbortSignal cancellation
The OPERATION_ABORTED code is used for caller-initiated cancellation via AbortController. It's not a true error — treat it as "the caller changed its mind."
const controller = new AbortController();
const bridge = createIframeBridge({
container: '#root',
src: 'https://partner.example/app',
});
await bridge.whenReady();
// Start a request that can be cancelled
const requestPromise = bridge.request(
'search',
{ query: 'term' },
{
signal: controller.signal,
},
);
// Cancel after 2 seconds if the user navigates away
setTimeout(() => controller.abort(), 2000);
try {
const result = await requestPromise;
console.log('Result:', result);
} catch (error) {
if (error instanceof IframeBridgeError && error.code === 'OPERATION_ABORTED') {
console.log('Request was cancelled.');
return;
}
throw error;
}
Config validation pattern
Since config errors are synchronous, wrap only the factory call:
import { createIframeBridge, IframeBridgeError } from 'iframe-helper-sdk';
let bridge;
try {
bridge = createIframeBridge({
container: '#partner-frame',
src: 'https://partner.example/app',
securityProfile: 'strict',
sandbox: ['allow-scripts'],
});
} catch (error) {
if (error instanceof IframeBridgeError) {
console.error(`Config error [${error.code}]:`, error.message);
return;
}
throw error;
}
// Config validated — now await readiness
try {
await bridge.whenReady();
} catch (error) {
if (error instanceof IframeBridgeError) {
console.error(`Bridge error [${error.code}]:`, error.message);
return;
}
throw error;
}
Quick Reference
All 27 error codes in one table with the most common recovery action.
| Error code | Category | Recovery |
|---|---|---|
CONFIG_INVALID_CONTAINER | Config | Pass a valid DOM element or selector. |
CONFIG_INVALID_SRC | Config | Use an absolute HTTPS URL without credentials. |
CONFIG_INVALID_QUEUE | Config | Set queue.maxSize to a positive integer. |
CONFIG_INVALID_RESIZE | Config | Use a valid resize axis, offsets, callback, and min/max bounds. |
CONFIG_INVALID_SECURITY_PROFILE | Config | Use 'strict' or 'development'. |
CONFIG_INVALID_TIMEOUT | Config | Set timeout values to integers ≥ 1. |
CONFIG_UNSAFE_ORIGIN | Config | Use exact HTTPS origins; enable allowInsecureLocalhost for dev. |
CONFIG_UNSAFE_PERMISSIONS_POLICY | Config | Replace wildcard allow with explicit feature grants. |
CONFIG_UNSAFE_SANDBOX | Config | Remove allow-same-origin or switch to development profile. |
DIAGNOSTICS_INVALID_MAX_ENTRIES | Config | Pass maxEntries as a positive integer. |
HANDSHAKE_TIMEOUT | Handshake | Verify iframe reads bootstrap params and sends bridge:ready. |
HANDSHAKE_ORIGIN_MISMATCH | Handshake | Match allowedOrigin to iframe's actual origin. |
HANDSHAKE_SOURCE_MISMATCH | Handshake | Ensure only the owned iframe sends bridge messages. |
HANDSHAKE_SESSION_MISMATCH | Handshake | Check iframe echoes the correct session parameter. |
HANDSHAKE_PROTOCOL_MISMATCH | Handshake | Iframe must use protocol: 'iframe-bridge'. |
HANDSHAKE_VERSION_MISMATCH | Handshake | Iframe must use version: 1. |
OPERATION_INVALID_TIMEOUT | Operation | Pass timeoutMs as a positive integer. |
OPERATION_ABORTED | Operation | Caller cancelled — create a new AbortController for retry. |
REQUEST_TIMEOUT | Operation | Check iframe request handling; increase timeout if needed. |
REQUEST_REMOTE_ERROR | Operation | Inspect error.details.remoteError for the iframe's error. |
EVENT_WAIT_TIMEOUT | Operation | Register the waiter before triggering the remote action. |
BRIDGE_NOT_READY | Lifecycle | Await bridge.whenReady() or enable the pre-ready queue. |
BRIDGE_DESTROYED | Lifecycle | Create a new bridge or call bridge.remount(). |
QUEUE_LIMIT_EXCEEDED | Lifecycle | Reduce pre-ready activity or increase queue.maxSize. |
QUEUE_CLOSED | Lifecycle | Fix the handshake and call bridge.remount(). |
MESSAGE_INVALID_ENVELOPE | Message | Check envelope shape against the wire protocol spec. |
MESSAGE_TARGET_MISMATCH | Message | Confirm the iframe is mounted and targetOrigin is correct. |
Next Steps
- Troubleshooting — Diagnostic flowcharts for common integration problems.
- API Reference — Hand-written reference for every public export.
- Configuration — Complete reference for every
IframeBridgeConfigoption. - Security — Security model, profiles, CSP guidance, and production checklist.
- Wire Protocol — The envelope specification for iframe-side integrations.
- Debugging & Diagnostics — Diagnostic recorder workflows and logger hooks.