Bidirectional Channel for JavaScript
BIDC is a JavaScript library designed for establishing robust, asynchronous, bidirectional communication channels between different JavaScript execution contexts, such as web workers, iframes, and service workers. Unlike traditional `postMessage` APIs, BIDC abstracts away the complexities of message passing, providing full support for promises, async functions, and a wide range of complex data types (e.g., Date, RegExp, Map, Set, ArrayBuffer). It features an automatic handshake mechanism to establish and re-establish connections seamlessly, even if one side reloads, and buffers messages until the recipient is ready. Currently at version 0.0.4, the library is in an early development stage, focusing on foundational features for secure and efficient cross-context communication. Its primary differentiators are automatic connection management, comprehensive data type serialization, and first-class async/await support, streamlining RPC-style interactions. It ships with TypeScript types, enhancing developer experience and type safety.
Common errors
-
TypeError: createChannel is not a function
cause Attempting to use CommonJS `require` syntax with an ESM-only package or a misconfigured bundler.fixUse ESM import syntax: `import { createChannel } from 'bidc'`. Ensure your project's `package.json` specifies `"type": "module"` or your build system correctly handles ESM. -
Uncaught (in promise) DOMException: Failed to execute 'postMessage' on 'Window': An object could not be cloned.
cause While BIDC generally handles complex types, certain non-clonable or non-transferable objects (e.g., DOM elements, functions not wrapped by BIDC's async support) might still fail if directly sent in a payload without proper serialization or remote execution context.fixEnsure that the data being sent directly as a payload consists of clonable JavaScript primitives or objects that BIDC explicitly supports for serialization. For DOM elements or functions, consider an RPC pattern where the function is executed remotely or only send serializable identifiers.
Warnings
- breaking As of version 0.0.4, BIDC is in early development. While the core API (`createChannel`, `send`, `receive`) is stable, minor versions may introduce breaking changes to less common features or internal protocols. Major version 1.0.0 is expected to stabilize the API.
- gotcha When using `createChannel()` without specifying a target, it defaults to `window.parent`. This behavior is useful for iframes/workers communicating with their creators but can lead to unexpected connections if used in a top-level window without explicit targeting.
- gotcha Ensure both ends of the channel are calling either `send` or `receive` (or both) to establish and maintain communication. If only one side attempts to `send` without a `receive` handler on the other, messages will be buffered but never processed.
Install
-
npm install bidc -
yarn add bidc -
pnpm add bidc
Imports
- createChannel
const { createChannel } = require('bidc')import { createChannel } from 'bidc' - Channel
import { Channel } from 'bidc'import type { Channel } from 'bidc' - Payload
import type { Payload } from 'bidc'
Quickstart
import { createChannel } from 'bidc';
// --- Parent window code ---
async function parentContext() {
const iframe = document.createElement('iframe');
document.body.appendChild(iframe);
// Wait for iframe to load for contentWindow to be available
await new Promise(resolve => iframe.onload = resolve);
const { send } = createChannel(iframe.contentWindow);
// Send a simple message to the iframe and await its response
const result = await send({ value: 'Hello, iframe from parent!' });
console.log('Parent received:', result);
console.assert(result === 'HELLO, IFRAME FROM PARENT!');
}
// --- Inside the iframe (hypothetical, or simulated for demo) ---
// In a real scenario, this would be in the iframe's HTML or JS file.
function iframeContext() {
// Omitting the target here will create a channel to the parent window by default
const { receive } = createChannel(); // Equivalent to createChannel(window.parent)
// Handle incoming messages from the parent and return a response
receive((payload) => {
console.log('Iframe received:', payload);
if (typeof payload.value === 'string') {
return payload.value.toUpperCase();
}
return 'Invalid payload';
});
}
// For demonstration, call both, but in reality they run in separate contexts.
parentContext();
iframeContext(); // This would typically be loaded by the iframe itself.