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

@nanohq/postmessage-hub

v0.1.0

Published

A comprehensive message routing system for parent-child and sibling iframe communication with TypeScript support

Readme

@nanohq/postmessage-hub

A comprehensive, type-safe message routing system for parent-child and sibling iframe communication. Built with TypeScript for maximum reliability and developer experience.

Features

  • Two-way Communication: Direct parent-child messaging
  • 🔄 Sibling Communication: Route messages between iframes through parent
  • 🎯 Type-Safe: Full TypeScript support with comprehensive type definitions
  • 🔒 Secure: Origin validation and whitelisting
  • 📝 Request-Response Pattern: Async/await support for message exchanges
  • 📡 Broadcasting: Send messages to all registered iframes
  • 🐛 Debug Mode: Built-in logging for development
  • Lightweight: Minimal dependencies, maximum performance
  • 🧪 Well-Tested: Comprehensive test coverage

Installation

npm install @nanohq/postmessage-hub

or

yarn add @nanohq/postmessage-hub

Quick Start

Parent Window Setup

import { PostMessageHub } from "@nanohq/postmessage-hub";

// Initialize the hub in the parent window
const parentHub = new PostMessageHub({
  instanceId: "parent",
  debug: true, // Enable debug logging
  allowedOrigins: [
    "https://iframe1.example.com",
    "https://iframe2.example.com",
  ],
});

// Register iframes
const iframe1 = document.getElementById("iframe-1") as HTMLIFrameElement;
const iframe2 = document.getElementById("iframe-2") as HTMLIFrameElement;

parentHub.registerIframe(
  "iframe-1",
  iframe1.contentWindow!,
  "https://iframe1.example.com"
);
parentHub.registerIframe(
  "iframe-2",
  iframe2.contentWindow!,
  "https://iframe2.example.com"
);

// Listen for messages
parentHub.on("greeting", (data, event) => {
  console.log("Received greeting from:", data.source);
  console.log("Message:", data.payload);
});

// Send message to specific iframe
parentHub.sendToIframe("iframe-1", {
  type: "command",
  payload: { action: "refresh" },
});

// Broadcast to all iframes
parentHub.broadcast({
  type: "notification",
  payload: { message: "System update available" },
});

Iframe Setup

import { PostMessageHub } from "@nanohq/postmessage-hub";

// Initialize the hub in the iframe
const iframeHub = new PostMessageHub({
  instanceId: "iframe-1",
  debug: true,
});

// Connect to parent
iframeHub.connectToParent(window.parent, "https://parent.example.com");

// Listen for messages from parent
iframeHub.on("command", (data, event) => {
  console.log("Received command:", data.payload);
});

// Send message to parent
iframeHub.sendToParent({
  type: "greeting",
  payload: { message: "Hello from iframe-1!" },
});

// Send message to sibling iframe (routed through parent)
iframeHub.sendToSibling("iframe-2", {
  type: "peer-message",
  payload: { text: "Hello sibling!" },
});

API Reference

PostMessageHub

Constructor

new PostMessageHub(config?: PostMessageHubConfig)

Config Options:

  • instanceId?: string - Unique identifier for this hub instance
  • debug?: boolean - Enable debug logging (default: false)
  • timeout?: number - Timeout for awaiting responses in ms (default: 5000)
  • allowedOrigins?: string[] - Whitelist of allowed origins
  • suppressHandshake?: boolean - Disable initial registration handshake (default: false)
  • deduplication?: boolean - Prevent duplicate messages (default: false)
  • deduplicationTimeout?: number - Time window for deduplication in ms (default: 1000)

Methods

registerIframe(iframeId: string, iframeWindow: Window, origin: string)

Register an iframe for communication (parent only).

parentHub.registerIframe(
  "iframe-1",
  iframe.contentWindow,
  "https://example.com"
);
unregisterIframe(iframeId: string)

Unregister an iframe.

parentHub.unregisterIframe("iframe-1");
connectToParent(parentWindow: Window, parentOrigin: string)

Connect to parent window (iframe only).

iframeHub.connectToParent(window.parent, "https://parent.com");
sendToParent(data: PostMessageData)

Send message to parent (iframe only).

iframeHub.sendToParent({
  type: "status",
  payload: { ready: true },
});
sendToIframe(targetId: string, data: PostMessageData)

Send message to specific iframe (parent only).

parentHub.sendToIframe("iframe-1", {
  type: "update",
  payload: { data: "new data" },
});
sendToSibling(targetId: string, data: PostMessageData)

Send message to sibling iframe (iframe only, routed through parent).

iframeHub.sendToSibling("iframe-2", {
  type: "share",
  payload: { data: "shared data" },
});
broadcast(data: PostMessageData)

Broadcast message to all registered iframes (parent only).

parentHub.broadcast({
  type: "announcement",
  payload: { message: "Important update" },
});
on(type: string, handler: MessageHandler)

Register a message handler for specific type.

hub.on("custom-event", (data, event) => {
  console.log("Received custom event:", data.payload);
});

// Wildcard handler for all messages
hub.on("*", (data, event) => {
  console.log("Received any message:", data);
});
off(type: string, handler: MessageHandler)

Unregister a message handler.

const handler = (data) => console.log(data);
hub.on("event", handler);
hub.off("event", handler);
sendAndAwaitResponse(target: string, data: PostMessageData)

Send message and wait for response (Promise-based).

try {
  const response = await parentHub.sendAndAwaitResponse("iframe-1", {
    type: "query",
    payload: { question: "What is your status?" },
  });
  console.log("Response:", response.payload);
} catch (error) {
  console.error("Request timeout or error:", error);
}
respond(originalMessage: PostMessageData, responseData: PostMessageData)

Respond to a message.

hub.on("query", (data, event) => {
  hub.respond(data, {
    type: "answer",
    payload: { status: "ready" },
  });
});
addAllowedOrigin(origin: string)

Add an allowed origin dynamically.

hub.addAllowedOrigin("https://new-trusted-site.com");
getRegisteredIframeIds(): string[]

Get list of all registered iframe IDs.

const iframeIds = parentHub.getRegisteredIframeIds();
console.log("Registered iframes:", iframeIds);
destroy()

Clean up event listeners and resources.

hub.destroy();

Usage Examples

Example 1: Simple Parent-Child Communication

// Parent
const hub = new PostMessageHub({ instanceId: "parent" });
const iframe = document.querySelector("iframe") as HTMLIFrameElement;
hub.registerIframe("child", iframe.contentWindow!, "https://child.com");

hub.on("ready", (data) => {
  console.log("Child is ready!");
  hub.sendToIframe("child", {
    type: "initialize",
    payload: { config: { theme: "dark" } },
  });
});

// Child
const childHub = new PostMessageHub({ instanceId: "child" });
childHub.connectToParent(window.parent, "https://parent.com");

childHub.sendToParent({
  type: "ready",
  payload: { version: "1.0.0" },
});

childHub.on("initialize", (data) => {
  console.log("Received config:", data.payload.config);
});

Example 2: Sibling Communication

// Parent
const hub = new PostMessageHub({ instanceId: "parent", debug: true });
hub.registerIframe(
  "sidebar",
  sidebarIframe.contentWindow!,
  "https://sidebar.com"
);
hub.registerIframe(
  "content",
  contentIframe.contentWindow!,
  "https://content.com"
);

// Sidebar iframe
const sidebarHub = new PostMessageHub({ instanceId: "sidebar" });
sidebarHub.connectToParent(window.parent, "https://parent.com");

// When user clicks navigation
document.querySelector("#nav-item").addEventListener("click", () => {
  sidebarHub.sendToSibling("content", {
    type: "navigate",
    payload: { page: "dashboard" },
  });
});

// Content iframe
const contentHub = new PostMessageHub({ instanceId: "content" });
contentHub.connectToParent(window.parent, "https://parent.com");

contentHub.on("navigate", (data) => {
  console.log("Navigating to:", data.payload.page);
  loadPage(data.payload.page);
});

Example 3: Request-Response Pattern

// Parent
const hub = new PostMessageHub({ instanceId: "parent" });
hub.registerIframe("worker", workerIframe.contentWindow!, "https://worker.com");

async function fetchDataFromWorker() {
  try {
    const response = await hub.sendAndAwaitResponse("worker", {
      type: "fetch-data",
      payload: { userId: 123 },
    });
    console.log("Data received:", response.payload.data);
  } catch (error) {
    console.error("Failed to fetch data:", error);
  }
}

// Worker iframe
const workerHub = new PostMessageHub({ instanceId: "worker" });
workerHub.connectToParent(window.parent, "https://parent.com");

workerHub.on("fetch-data", async (data, event) => {
  const userData = await fetchUser(data.payload.userId);
  workerHub.respond(data, {
    type: "data-response",
    payload: { data: userData },
  });
});

Example 4: Broadcasting Updates

// Parent
const hub = new PostMessageHub({ instanceId: "parent" });
hub.registerIframe(
  "dashboard",
  dashboardIframe.contentWindow!,
  "https://dash.com"
);
hub.registerIframe("chart", chartIframe.contentWindow!, "https://chart.com");
hub.registerIframe("table", tableIframe.contentWindow!, "https://table.com");

// Broadcast real-time updates to all iframes
setInterval(() => {
  hub.broadcast({
    type: "data-update",
    payload: {
      timestamp: Date.now(),
      metrics: fetchLatestMetrics(),
    },
  });
}, 5000);

// Each iframe listens for updates
const dashHub = new PostMessageHub({ instanceId: "dashboard" });
dashHub.connectToParent(window.parent, "https://parent.com");

dashHub.on("data-update", (data) => {
  updateDashboard(data.payload.metrics);
});

Example 5: Standalone Child Mode (Drop-in Replacement)

Use this mode when the parent window is NOT using postmessage-hub (vanilla implementation).

// Child Iframe
const hub = new PostMessageHub({
  instanceId: "child",
  suppressHandshake: true, // 1. Don't send registration handshake
});

hub.connectToParent(window.parent, "*");

// 2. Send raw data that vanilla parent expects
hub.sendToParent(
  {
    type: "my-event",
    payload: { some: "data" },
  },
  { raw: true } // Sends just the payload object
);
  { raw: true } // Sends just the payload object
);

Example 6: Event Deduplication

Prevent duplicate messages from being sent (e.g., from React useEffect double-firing).

const hub = new PostMessageHub({
  instanceId: "child",
  deduplication: true, // Enable deduplication
  deduplicationTimeout: 1000, // Time window in ms (default: 1000)
});

// This will be sent
hub.sendToParent({ type: "init", payload: { id: 1 } });

// This will be BLOCKED if sent within 1000ms
hub.sendToParent({ type: "init", payload: { id: 1 } });

// This will be sent (different payload)
hub.sendToParent({ type: "init", payload: { id: 2 } });

TypeScript Support

Full TypeScript definitions are included:

import {
  PostMessageHub,
  PostMessageData,
  PostMessageHubConfig,
  MessageType,
  ConnectionStatus,
  HubError,
} from "@nanohq/postmessage-hub";

// Type-safe message data
const message: PostMessageData = {
  type: "custom",
  payload: { value: 42 },
  source: "iframe-1",
  target: "iframe-2",
};

// Type-safe config
const config: PostMessageHubConfig = {
  instanceId: "my-hub",
  debug: true,
  timeout: 10000,
  allowedOrigins: ["https://example.com"],
};

Security Best Practices

  1. Always specify allowed origins in production:
const hub = new PostMessageHub({
  allowedOrigins: ["https://trusted-domain.com"],
});
  1. Validate message payloads before processing:
hub.on("sensitive-action", (data) => {
  if (!isValidPayload(data.payload)) {
    console.error("Invalid payload received");
    return;
  }
  processSensitiveAction(data.payload);
});
  1. Use HTTPS for all iframe sources in production.

  2. Avoid sending sensitive data directly in messages; use tokens or identifiers instead.

Browser Support

  • Chrome/Edge: Latest 2 versions
  • Firefox: Latest 2 versions
  • Safari: Latest 2 versions
  • All browsers supporting postMessage API

Development

# Install dependencies
npm install

# Build
npm run build

# Run tests
npm test

# Watch mode
npm run dev

# Run tests with coverage
npm run test:coverage

Contributing

Contributions are welcome! Please read our contributing guidelines before submitting PRs.

License

MIT © Nano Team

Changelog

See CHANGELOG.md for version history.

Support