
Security News
Axios Maintainer Confirms Social Engineering Attack Behind npm Compromise
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.
@knowledgecode/messenger
Advanced tools
Type-safe Request/Reply and Pub/Sub messaging library for browser applications
A type-safe Request/Reply and Pub/Sub messaging library for cross-context communication in browsers, enabling seamless message exchange between the main window, iframes, and web workers.
npm i @knowledgecode/messenger
import { MessengerClient, MessengerServer } from '@knowledgecode/messenger';
For legacy environments without ES module support:
<script src="./node_modules/@knowledgecode/messenger/dist/messenger.js"></script>
<script>
const { MessengerClient, MessengerServer } = self.messenger;
</script>
For workers in legacy environments:
// worker.ts (legacy)
importScripts('./node_modules/@knowledgecode/messenger/dist/messenger.js');
const { MessengerServer } = self.messenger;
Note: The examples below are written in TypeScript. Build them to JavaScript files using TypeScript compiler or a bundler before running in the browser.
import { MessengerClient } from '@knowledgecode/messenger';
const messenger = new MessengerClient();
const worker = new Worker('./worker.js', { type: 'module' });
(async () => {
// Connect to the worker's server named 'calculator'
await messenger.connect('calculator', worker);
// Request/Reply: Send a request and wait for response
const result = await messenger.req<number>('add', { x: 2, y: 3 });
console.log(result); // => 5
// Send: Fire and forget
messenger.send('close');
messenger.disconnect();
})();
import { MessengerServer } from '@knowledgecode/messenger';
interface AddRequest {
x: number;
y: number;
}
const messenger = new MessengerServer('calculator', self);
// Bind handler for 'add' topic
messenger.bind<AddRequest>('add', (data) => {
if (!data) {
return 0;
}
return data.x + data.y;
});
// Bind handler for 'close' topic
messenger.bind<void>('close', () => {
messenger.close();
self.close();
});
import { MessengerClient } from '@knowledgecode/messenger';
interface StatusUpdate {
status: string;
timestamp: number;
}
interface User {
id: number;
name: string;
email: string;
}
const messenger = new MessengerClient();
const iframe = document.querySelector('iframe');
(async () => {
// Connect to iframe's server with targetOrigin for security
await messenger.connect('iframe-app', iframe!.contentWindow!, {
targetOrigin: 'https://example.com', // Use '*' only for development
timeout: 5000
});
// Subscribe to messages published from the iframe
messenger.subscribe<StatusUpdate>('status-update', (data) => {
if (data) {
console.log('Status:', data.status);
}
});
// Send request to iframe
const userData = await messenger.req<User>('get-user', { id: 123 });
console.log(userData);
})();
import { MessengerServer } from '@knowledgecode/messenger';
interface GetUserRequest {
id: number;
}
interface User {
id: number;
name: string;
email: string;
}
const messenger = new MessengerServer('iframe-app', self);
messenger.bind<GetUserRequest>('get-user', async (data) => {
if (!data) {
throw new Error('User ID is required');
}
const response = await fetch(`/api/users/${data.id}`);
return await response.json() as User;
});
// Publish status updates to subscribers
setInterval(() => {
messenger.publish('status-update', { status: 'running', timestamp: Date.now() });
}, 1000);
You can use Messenger for communication between components within the same window or worker context.
import { MessengerServer } from '@knowledgecode/messenger';
interface DataItem {
id: number;
value: string;
}
const messenger = new MessengerServer('data-service', self);
messenger.bind<void>('get-data', (): DataItem[] => {
return [
{ id: 1, value: 'Item 1' },
{ id: 2, value: 'Item 2' },
{ id: 3, value: 'Item 3' }
];
});
import { MessengerClient } from '@knowledgecode/messenger';
interface DataItem {
id: number;
value: string;
}
const messenger = new MessengerClient();
(async () => {
// Connect to the data service in the same context
await messenger.connect('data-service', self);
// Request data from the service
const items = await messenger.req<DataItem[]>('get-data');
console.log('Received items:', items);
messenger.disconnect();
})();
Client for connecting to a MessengerServer and sending messages.
constructor()Creates a new MessengerClient instance.
const messenger = new MessengerClient();
connect(name, endpoint, options)Establishes a connection to a MessengerServer.
Parameters:
name (string): Unique name of the MessengerServer to connect toendpoint (Window | Worker, optional): Target context that has postMessage() method. Defaults to selfoptions (object, optional): Connection options
targetOrigin (string, optional): Target origin for security (iframe only). Defaults to '*'. For production, always specify the exact origintimeout (number, optional): Connection timeout in milliseconds. If omitted, waits indefinitelyReturns: Promise<void> - Resolves when connection is established
Throws:
Error if endpoint doesn't have postMessage() methodError if connection times outExamples:
// Connect to iframe with security and timeout
const iframe = document.querySelector('iframe');
await messenger.connect('my-iframe', iframe!.contentWindow!, {
targetOrigin: 'https://trusted-domain.com',
timeout: 5000
});
// Connect to worker
const worker = new Worker('./worker.js', { type: 'module' });
await messenger.connect('my-worker', worker, { timeout: 3000 });
// Connect from within a worker to parent
await messenger.connect('main', self);
disconnect()Disconnects from the server, clears all subscriptions, and cleans up resources.
messenger.disconnect();
send(topic, data)Sends a one-way message to a topic. Does not wait for a response.
Parameters:
topic (string): Topic namedata (unknown): Data to sendThrows: Error if not connected
messenger.send('log', { level: 'info', message: 'Task completed' });
req<T>(topic, data, timeout)Sends a request to a topic and waits for a response.
Type Parameters:
T (optional): The expected response type. Defaults to unknownParameters:
topic (string): Topic namedata (unknown, optional): Data to sendtimeout (number, optional): Request timeout in milliseconds. If omitted, waits indefinitelyReturns: Promise<T> - Resolves with the response data of type T
Throws:
Error if not connectedError if request times outError if the topic is not bound on the serverExamples:
// Simple request with type inference
const result = await messenger.req<number>('calculate', { operation: 'add', values: [1, 2, 3] });
// Request with timeout and type safety
interface DataResponse {
id: number;
value: string;
}
try {
const data = await messenger.req<DataResponse>('fetch-data', { id: 123 }, 5000);
console.log(data.value); // TypeScript knows about the 'value' property
} catch (error) {
console.error('Request failed:', (error as Error).message);
}
subscribe<T>(topic, listener)Subscribes to messages published on a topic.
Type Parameters:
T (optional): The expected message data type. Defaults to unknownParameters:
topic (string): Topic namelistener (function): Callback function invoked when messages are published
(data?: T) => voidThrows: Error if not connected
interface Notification {
title: string;
message: string;
timestamp: number;
}
messenger.subscribe<Notification>('notifications', (data) => {
if (data) {
console.log('Notification received:', data.title);
}
});
unsubscribe(topic, listener)Unsubscribes from a topic.
Parameters:
topic (string, optional): Topic name. If omitted, clears all subscriptionslistener (function, optional): Specific listener to remove. If omitted, removes all listeners for the topic// Remove specific listener
interface UpdateData {
version: string;
}
const listener = (data?: UpdateData) => {
if (data) {
console.log(data.version);
}
};
messenger.subscribe<UpdateData>('updates', listener);
messenger.unsubscribe('updates', listener);
// Remove all listeners for a topic
messenger.unsubscribe('updates');
// Remove all subscriptions
messenger.unsubscribe();
Server for accepting client connections and handling messages.
constructor(name, endpoint)Creates a new MessengerServer instance.
Parameters:
name (string): Unique name for this server. Clients use this name to connectendpoint (Window | Worker, optional): Context to listen on. Defaults to self// In a worker
const messenger = new MessengerServer('my-worker', self);
// In an iframe
const messenger = new MessengerServer('my-iframe', self);
// In main window (listening for messages from a specific worker)
const worker = new Worker('./worker.js', { type: 'module' });
const messenger = new MessengerServer('worker-listener', worker);
bind<T>(topic, listener)Binds a handler to a topic for receiving messages.
Type Parameters:
T (optional): The expected message data type. Defaults to unknownParameters:
topic (string): Topic name (must be unique per server)listener (function): Handler function
(data?: T) => unknownsend() messages: return value is ignoredreq() messages: return value (or resolved Promise value) is sent back to clientReturns: boolean - true if bound successfully, false if topic already bound
Examples:
// Handle one-way messages
interface LogMessage {
level: string;
message: string;
}
messenger.bind<LogMessage>('log', (data) => {
if (data) {
console.log(`[${data.level}] ${data.message}`);
}
});
// Handle requests (synchronous)
interface AddRequest {
x: number;
y: number;
}
messenger.bind<AddRequest>('add', (data) => {
if (!data) {
return 0;
}
return data.x + data.y;
});
// Handle requests (asynchronous)
interface FetchUserRequest {
id: number;
}
interface User {
id: number;
name: string;
}
messenger.bind<FetchUserRequest>('fetch-user', async (data) => {
if (!data) {
throw new Error('User ID is required');
}
const response = await fetch(`/api/users/${data.id}`);
return await response.json() as User;
});
// Check binding result
if (!messenger.bind<void>('duplicate-topic', () => {})) {
console.error('Topic already bound');
}
unbind(topic)Removes the handler for a topic.
Parameters:
topic (string): Topic namemessenger.unbind('old-topic');
publish(topic, data)Publishes a message to all subscribed clients on a topic.
Parameters:
topic (string): Topic namedata (unknown, optional): Data to publishThis method does not wait for responses and succeeds even if there are no subscribers.
// Notify all subscribers
messenger.publish('status-change', { status: 'ready', timestamp: Date.now() });
// Broadcast to all clients
setInterval(() => {
messenger.publish('heartbeat', { timestamp: Date.now() });
}, 1000);
close()Closes all client connections, removes all handlers and subscriptions, and shuts down the server.
messenger.close();
When connecting to iframes from different origins, always specify the exact targetOrigin instead of using '*':
// ❌ Insecure - allows any origin
await messenger.connect('iframe', iframe!.contentWindow!, { targetOrigin: '*' });
// ✅ Secure - restricts to specific origin
await messenger.connect('iframe', iframe!.contentWindow!, {
targetOrigin: 'https://trusted-domain.com'
});
This library is written in TypeScript and provides full type safety with generic support.
import { MessengerClient } from '@knowledgecode/messenger';
interface User {
id: number;
name: string;
}
const messenger = new MessengerClient();
// Type-safe request - result is typed as User
const user = await messenger.req<User>('get-user', { id: 123 });
console.log(user.name); // TypeScript knows user has a name property
The listener receives data as T | undefined because messages may be published without data.
interface StatusUpdate {
status: 'online' | 'offline';
timestamp: number;
}
// Type-safe subscribe - data parameter is StatusUpdate | undefined
messenger.subscribe<StatusUpdate>('status-change', (data) => {
if (data) {
console.log(`Status: ${data.status} at ${data.timestamp}`);
}
});
Handlers receive data as T | undefined because clients may send requests or messages without data.
interface CalculateRequest {
operation: 'add' | 'subtract' | 'multiply' | 'divide';
a: number;
b: number;
}
interface CalculateResponse {
result: number;
}
const messenger = new MessengerServer('calculator', self);
// Type-safe handler - data parameter is CalculateRequest | undefined
messenger.bind<CalculateRequest>('calculate', (data) => {
if (!data) {
return { result: 0 };
}
switch (data.operation) {
case 'add':
return { result: data.a + data.b };
case 'subtract':
return { result: data.a - data.b };
case 'multiply':
return { result: data.a * data.b };
case 'divide':
return { result: data.a / data.b };
}
});
// Client side - response is typed as CalculateResponse
const client = new MessengerClient();
await client.connect('calculator', self);
const response = await client.req<CalculateResponse>('calculate', {
operation: 'add',
a: 5,
b: 3
});
console.log(response.result); // TypeScript knows about result property
MIT
FAQs
Type-safe Request/Reply and Pub/Sub messaging library for browser applications
The npm package @knowledgecode/messenger receives a total of 3 weekly downloads. As such, @knowledgecode/messenger popularity was classified as not popular.
We found that @knowledgecode/messenger demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.