A comprehensive message routing system for parent-child and sibling iframe communication with TypeScript support
npm install @nanohq/postmessage-hubA comprehensive, type-safe message routing system for parent-child and sibling iframe communication. Built with TypeScript for maximum reliability and developer experience.
- โจ 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
``bash`
npm install @nanohq/postmessage-hub
or
`bash`
yarn add @nanohq/postmessage-hub
`typescript
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" },
});
`
`typescript
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!" },
});
`
#### Constructor
`typescript`
new PostMessageHub(config?: PostMessageHubConfig)
Config Options:
- instanceId?: string - Unique identifier for this hub instancedebug?: boolean
- - Enable debug logging (default: false)timeout?: number
- - Timeout for awaiting responses in ms (default: 5000)allowedOrigins?: string[]
- - Whitelist of allowed originssuppressHandshake?: 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).
`typescript`
parentHub.registerIframe(
"iframe-1",
iframe.contentWindow,
"https://example.com"
);
##### unregisterIframe(iframeId: string)
Unregister an iframe.
`typescript`
parentHub.unregisterIframe("iframe-1");
##### connectToParent(parentWindow: Window, parentOrigin: string)
Connect to parent window (iframe only).
`typescript`
iframeHub.connectToParent(window.parent, "https://parent.com");
##### sendToParent(data: PostMessageData)
Send message to parent (iframe only).
`typescript`
iframeHub.sendToParent({
type: "status",
payload: { ready: true },
});
##### sendToIframe(targetId: string, data: PostMessageData)
Send message to specific iframe (parent only).
`typescript`
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).
`typescript`
iframeHub.sendToSibling("iframe-2", {
type: "share",
payload: { data: "shared data" },
});
##### broadcast(data: PostMessageData)
Broadcast message to all registered iframes (parent only).
`typescript`
parentHub.broadcast({
type: "announcement",
payload: { message: "Important update" },
});
##### on(type: string, handler: MessageHandler)
Register a message handler for specific type.
`typescript
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.
`typescript`
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).
`typescript`
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.
`typescript`
hub.on("query", (data, event) => {
hub.respond(data, {
type: "answer",
payload: { status: "ready" },
});
});
##### addAllowedOrigin(origin: string)
Add an allowed origin dynamically.
`typescript`
hub.addAllowedOrigin("https://new-trusted-site.com");
##### getRegisteredIframeIds(): string[]
Get list of all registered iframe IDs.
`typescript`
const iframeIds = parentHub.getRegisteredIframeIds();
console.log("Registered iframes:", iframeIds);
##### destroy()
Clean up event listeners and resources.
`typescript`
hub.destroy();
`typescript
// 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);
});
`
`typescript
// 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);
});
`
`typescript
// 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 },
});
});
`
`typescript
// 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);
});
`
Use this mode when the parent window is NOT using postmessage-hub (vanilla implementation).
`typescript
// 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
);
`
Prevent duplicate messages from being sent (e.g., from React useEffect double-firing).
`typescript
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 } });
`
Full TypeScript definitions are included:
`typescript
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"],
};
`
1. Always specify allowed origins in production:
`typescript`
const hub = new PostMessageHub({
allowedOrigins: ["https://trusted-domain.com"],
});
2. Validate message payloads before processing:
`typescript`
hub.on("sensitive-action", (data) => {
if (!isValidPayload(data.payload)) {
console.error("Invalid payload received");
return;
}
processSensitiveAction(data.payload);
});
3. Use HTTPS for all iframe sources in production.
4. Avoid sending sensitive data directly in messages; use tokens or identifiers instead.
- Chrome/Edge: Latest 2 versions
- Firefox: Latest 2 versions
- Safari: Latest 2 versions
- All browsers supporting postMessage API
`bashInstall dependencies
npm install
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
MIT ยฉ Nano Team
See CHANGELOG.md for version history.
- ๐ง Email: support@getnano.space
- ๐ Issues: GitHub Issues
- ๐ Docs: Full Documentation