npm package: @nanohq/postmessage-hub
Description: A comprehensive message routing system for parent-child and sibling iframe communication with TypeScript support
Author: Nano Team
Latest version: 0.1.0

# @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

```bash
npm install @nanohq/postmessage-hub
```

or

```bash
yarn add @nanohq/postmessage-hub
```

## Quick Start

### Parent Window Setup

```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" },
});
```

### Iframe Setup

```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!" },
});
```

## API Reference

### PostMessageHub

#### Constructor

```typescript
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).

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

## Usage Examples

### Example 1: Simple Parent-Child Communication

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

### Example 2: Sibling Communication

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

### Example 3: Request-Response Pattern

```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 },
  });
});
```

### Example 4: Broadcasting Updates

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

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

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

### Example 6: Event Deduplication

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

## TypeScript Support

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"],
};
```

## Security Best Practices

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.

## Browser Support

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

## Development

```bash
# 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](./CHANGELOG.md) for version history.

## Support

- 📧 Email: support@getnano.space
- 🐛 Issues: [GitHub Issues](https://github.com/nanohq/postmessage-hub/issues)
- 📖 Docs: [Full Documentation](https://docs.getnano.space/postmessage-hub)