npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

iframe-helper-sdk

v0.2.1

Published

TypeScript SDK for secure cross-domain iframe embeds with a strict postMessage bridge, origin validation, handshakes, RPC-style requests, events, and typed contracts.

Readme

Iframe Helper SDK

npm version docs types bundle size license

Secure, structured communication between a parent page and cross-domain iframes.

iframe-helper-sdk is a zero-dependency TypeScript SDK for iframe integrations. It wraps window.postMessage with exact origin validation, ready-first handshakes, session routing, bounded queueing, request/response timeouts, fire-and-forget events, opt-in iframe resizing, diagnostics, optional compile-time typed contracts, and an optional child runtime for iframe apps.

Use it when you need a small, auditable bridge between your application and an embedded iframe without building a custom protocol from scratch.

Install

npm install iframe-helper-sdk
pnpm add iframe-helper-sdk
yarn add iframe-helper-sdk

Types are included. No separate @types/* package is required.

Quick Start

Parent Page

The SDK runs in the parent page. Give it a container and an iframe URL, wait for the iframe to complete the handshake, then start communicating.

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

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

try {
  await bridge.whenReady();

  const user = await bridge.request<{ id: string }, { name: string }>('user:get', {
    id: '123',
  });

  console.log(user.name);
} catch (error) {
  if (error instanceof IframeBridgeError) {
    console.error('Bridge failed:', error.code, error.details);
  } else {
    throw error;
  }
}

Iframe Application

The iframe application does not need to install this SDK. It implements the wire protocol directly by reading the bootstrap parameters and replying with protocol envelopes.

const params = new URLSearchParams(window.location.search);
const sessionId = params.get('__iframeBridgeSessionId');
const parentOrigin = params.get('__iframeBridgeParentOrigin');

if (!sessionId || !parentOrigin) {
  throw new Error('Missing iframe bridge bootstrap parameters.');
}

window.parent.postMessage(
  {
    protocol: 'iframe-bridge',
    version: 1,
    sessionId,
    type: 'bridge:ready',
  },
  parentOrigin,
);

window.addEventListener('message', (event) => {
  if (event.origin !== parentOrigin) return;

  const message = event.data;
  if (message?.protocol !== 'iframe-bridge' || message?.version !== 1) return;
  if (message.sessionId !== sessionId) return;

  if (message.type === 'bridge:request' && message.name === 'user:get') {
    window.parent.postMessage(
      {
        protocol: 'iframe-bridge',
        version: 1,
        sessionId,
        type: 'bridge:response',
        requestId: message.requestId,
        payload: { name: 'Ada Lovelace' },
      },
      parentOrigin,
    );
  }
});

See the full Wire Protocol for every envelope type and validation rule.

Iframe Application With Child SDK

If the iframe app can install the package, use the child SDK instead of hand-writing the protocol boilerplate:

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();

allowedParentOrigins?: readonly string[] | null may be omitted or set to null to accept the bootstrap parent origin. Non-empty arrays require exact origin match; empty arrays are invalid. If you omit the allowlist, rely on server-side/browser embedding controls such as CSP frame-ancestors.

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

Why This SDK

Raw postMessage is intentionally low-level. Every window, iframe, tab, and extension can send messages on the same channel. A production iframe integration needs more structure than a message event listener.

| Concern | Raw postMessage | Iframe Helper SDK | | ----------------- | ----------------------- | --------------------------------------------- | | Origin validation | Manual and easy to miss | Exact origin enforced per bridge | | Handshake | None or custom | Ready-first handshake with timeout | | Message format | Ad hoc JSON | Versioned iframe-bridge envelopes | | Request queueing | Not built in | Bounded pre-ready queue | | Timeouts | Manual timers | Configurable operation timeouts | | Typed contracts | Manual type assertions | Compile-time narrowing via contract maps | | Error handling | Generic errors | IframeBridgeError with code-based branching | | Diagnostics | Console logging | Opt-in diagnostic recorder and logger hooks |

Features

  • Strict origin enforcement - exact targetOrigin and allowedOrigin checks. Wildcard origins are rejected.
  • Ready-first handshake - the parent waits for bridge:ready, validates the sender, then responds with bridge:connected.
  • Iframe app SDK - optional iframe-helper-sdk/child runtime for child-side handshake, events, and parent request handlers.
  • Session-scoped routing - every bridge gets a session id so messages from unrelated iframes are ignored.
  • Bounded pre-ready queue - operations called before readiness are queued and flushed after handshake, with a configurable limit.
  • Request/response API - bridge.request() sends a method call and resolves with the iframe response.
  • Event API - bridge.sendEvent(), bridge.on(), and bridge.waitForEvent() cover one-way and inbound event flows.
  • Iframe resize - opt into child-driven width and height updates with parent-side bounds, offsets, and an applied-resize callback.
  • Typed contracts - createTypedIframeBridge narrows method names, payloads, and responses at compile time with no runtime cost.
  • Structured errors - every SDK error is an IframeBridgeError with a stable code and optional details.
  • Diagnostics - use createDiagnosticRecorder or a custom logger to observe lifecycle, handshake, queue, and filtering decisions.
  • Small runtime - zero runtime dependencies, tree-shakable package output, and a compact browser payload.

Communication Patterns

| You want to | Use | | -------------------------------------------- | ----------------------------------------------- | | Wait until the iframe is ready | bridge.whenReady() | | Ask the iframe for a result | bridge.request(method, payload) | | Notify the iframe without waiting for a body | bridge.sendEvent(name, payload) | | Subscribe to iframe events | bridge.on(name, handler) | | Wait for one matching iframe event | bridge.waitForEvent(name, options?) | | Let the iframe resize itself | resizePlugin() + iframe-bridge:resize event | | Recreate a failed bridge attempt | bridge.remount() | | Remove listeners, timers, and the iframe | bridge.destroy() |

Child-Driven Resize

Resize is opt-in and tree-shakable. Import the resize plugin from the iframe-helper-sdk/resize subpath, register it on the parent, then have the iframe send the reserved iframe-bridge:resize event after the bridge connects.

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({
        minWidthPx: 320,
        maxWidthPx: 1200,
        minHeightPx: 240,
        maxHeightPx: 900,
        offsetHeightPx: 16,
        onResize({ width, height }) {
          console.log('iframe resized to', width, height);
        },
      }),
    ],
  },
);

The iframe sends the requested dimensions through a standard bridge:event named iframe-bridge:resize. The parent still validates origin, source window, session id, protocol, version, and envelope shape before applying dimensions to the owned iframe element.

Iframe apps that use the child SDK can import the tree-shakable child resize plugin from iframe-helper-sdk/child/resize:

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.

offsetWidthPx and offsetHeightPx add fixed parent-side pixels before min/max bounds are applied. In development mode, the SDK warns if resize is enabled without max bounds for every active axis; in securityProfile: 'strict', missing active max bounds throw CONFIG_INVALID_RESIZE.

Full guide: Resize Plugin.

Type-Safe Bridge

Use createTypedIframeBridge when your integration has enough methods or events that repeated generics become noisy. The contract exists only at compile time. It does not validate runtime payloads and is not a security boundary.

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

type PartnerContract = {
  requests: {
    'user:get': {
      payload: { id: string };
      response: { name: string; email: string };
    };
    'order:create': {
      payload: { productId: string; quantity: number };
      response: { orderId: string };
    };
  };
  outboundEvents: {
    'analytics:track': { action: string; label?: string };
  };
  inboundEvents: {
    'cart:changed': { itemCount: number };
  };
};

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

await bridge.whenReady();

const user = await bridge.request('user:get', { id: '123' });
// user is { name: string; email: string }

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

bridge.on('cart:changed', (payload) => {
  console.log(payload.itemCount);
});

Read more in the Type-Safe Bridge guide.

Security Model

This SDK is a transport and lifecycle layer, not a complete application security solution. It reduces common postMessage mistakes, but authentication, authorization, payload validation, CSRF protection, and server-side business rules remain your responsibility.

Enforced by the SDK:

  • Parent-to-iframe messages always use an exact target origin.
  • Inbound messages are validated against origin, source window, session id, protocol name, protocol version, and envelope shape.
  • HTTPS iframe URLs are required by default. HTTP is only allowed for explicit localhost development mode.
  • Unsafe URL schemes such as javascript:, data:, blob:, and srcdoc are rejected.
  • securityProfile: 'strict' turns risky production settings into config errors.
  • destroy() removes SDK-owned listeners, timers, pending requests, event waits, and the owned iframe.

Production checklist:

  • Use HTTPS for both parent and iframe origins.
  • Set securityProfile: 'strict' once local development is complete.
  • Keep targetOrigin, allowedOrigin, and child allowedParentOrigins exact. Do not use wildcard origins.
  • Add parent-side CSP such as frame-src https://partner.example.
  • Add iframe-side CSP such as frame-ancestors https://host.example.
  • Review iframe sandbox and allow attributes before enabling browser capabilities.
  • Validate critical payloads in your application layer or backend.

Read the full Security guide before shipping a cross-domain integration.

API Surface

Import parent public APIs from the package root. Child APIs and optional plugins use documented subpath exports such as iframe-helper-sdk/child, iframe-helper-sdk/resize, and iframe-helper-sdk/child/resize; never import from internal src or dist paths.

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

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

| Export | Kind | Purpose | | ---------------------------- | -------- | ------------------------------------------------------- | | createIframeBridge | Function | Create a parent-side iframe bridge | | createTypedIframeBridge | Function | Create the same bridge with contract-narrowed types | | createIframeChildBridge | Function | Create a child-side bridge inside an iframe app | | createDiagnosticRecorder | Function | Capture diagnostic events in a bounded recorder | | IframeBridgeError | Class | SDK error with code, message, and details | | BRIDGE_MESSAGE_TYPES | Constant | Tuple of protocol message type strings | | BRIDGE_PROTOCOL_NAME | Constant | Protocol name, currently 'iframe-bridge' | | BRIDGE_PROTOCOL_VERSION | Constant | Protocol version, currently 1 | | isBridgeEnvelope | Function | Type guard for bridge envelopes | | validateBridgeEnvelope | Function | Validate and return a typed bridge envelope | | normalizeBridgeRemoteError | Function | Normalize iframe-side error responses | | resizePlugin | Function | Optional child-driven resize plugin from ./resize | | childResizePlugin | Function | Optional child-side resize plugin from ./child/resize |

Bridge Instance

type IframeBridge = {
  readonly iframe: HTMLIFrameElement;
  readonly state:
    | 'created'
    | 'mounting'
    | 'waiting_for_handshake'
    | 'ready'
    | 'handshake_failed'
    | 'destroyed';
  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;
};

Full reference: API Reference.

Error Handling

All SDK errors use IframeBridgeError. Branch on error.code instead of parsing strings.

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

try {
  await bridge.whenReady();
  const result = await bridge.request('report:generate', payload, {
    timeoutMs: 30_000,
  });
  console.log(result);
} catch (error) {
  if (error instanceof IframeBridgeError) {
    switch (error.code) {
      case 'HANDSHAKE_TIMEOUT':
        console.error('The iframe did not complete the handshake.');
        break;
      case 'REQUEST_TIMEOUT':
        console.error('The iframe did not respond in time.');
        break;
      case 'REQUEST_REMOTE_ERROR':
        console.error('The iframe returned an error:', error.details);
        break;
      default:
        console.error(error.code, error.message, error.details);
    }
  } else {
    throw error;
  }
}

Common codes:

| Code | Meaning | | ---------------------- | -------------------------------------------------------------- | | HANDSHAKE_TIMEOUT | The iframe did not send a valid bridge:ready in time | | REQUEST_TIMEOUT | A request was sent, but no matching response arrived in time | | REQUEST_REMOTE_ERROR | The iframe responded with an explicit error object | | EVENT_WAIT_TIMEOUT | waitForEvent() did not receive the expected event in time | | OPERATION_ABORTED | The provided AbortSignal cancelled the operation | | BRIDGE_NOT_READY | Queueing is disabled and an operation ran before readiness | | BRIDGE_DESTROYED | The bridge was destroyed before the operation could complete | | QUEUE_LIMIT_EXCEEDED | Too many operations were queued before the iframe became ready |

See the Error Codes reference for the complete list and recovery actions.

Diagnostics

Diagnostics are opt-in. Use the built-in recorder for local debugging or plug your own logger into monitoring.

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);

Diagnostics are sanitized by design and do not include raw application payloads by default. See Debugging & Diagnostics.

Documentation

Full documentation: furkankaynak.github.io/iframe-helper-sdk

| Section | Pages | | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Introduction | Home, Getting Started, Core Concepts | | Guides | Configuration, Type-Safe Bridge, Wire Protocol, Security, Use Cases, Debugging | | Child Iframe SDK | Overview, Security, Events And Requests, Plugins, Resize | | Reference | API Reference, Error Codes | | Help | Troubleshooting, FAQ |

Documentation source lives in documentation/docs.

Examples

The repository includes a working parent and iframe playground under playground/manual.

npm run build

Then run the parent and iframe dev servers in separate terminals:

npm run example:manual:parent
npm run example:manual:iframe

Open http://127.0.0.1:5173/. The parent server runs on port 5173, and the iframe server runs on port 5174.

Compatibility

  • Runtime: Browser environments with window, document, HTMLIFrameElement, and postMessage.
  • SSR: Browser-only. Create bridges inside client-only lifecycle hooks such as React useEffect, Vue onMounted, or Svelte onMount.
  • Build target: ES2020 browser output.
  • Package formats: ESM and CommonJS, with TypeScript declarations for both import styles.
  • Node.js: >=18 for development, build, and package tooling.
  • Dependencies: Zero runtime dependencies.

Development

npm install
npm run build
npm run typecheck
npm run test
npm run lint
npm run format:check

| Script | Description | | ---------------------- | ---------------------------------------------------------------------------- | | npm run build | Production build with Vite, TypeScript declarations, and CJS type prep | | npm run test | Vitest test suite | | npm run typecheck | TypeScript type checking with tsc --noEmit | | npm run lint | ESLint | | npm run format | Format source, tests, playground, docs, config, and README files | | npm run format:check | Prettier format check | | npm run verify | Full release gate: typecheck, test, lint, format, build, publint, attw, pack | | npm run docs:dev | Start the Docusaurus documentation server | | npm run docs:build | Build the documentation site |

Contributing

Issues and pull requests are welcome on GitHub. For changes to the public API, update the documentation and README examples in the same pull request.

Before opening a PR, run:

npm run verify
npm run docs:build

License

MIT