Switchboard is a library to make it easier to communicate across browser windows using the MessageChannel API
npx @tessl/cli install tessl/npm-superset-ui--switchboard@0.18.0Switchboard is a TypeScript library that provides secure communication between iframe windows and their parent windows using the MessageChannel API. It enables structured method calls across window boundaries with support for both synchronous (get) and asynchronous (emit) patterns, specifically designed for the Superset embedded SDK.
npm install @superset-ui/switchboardimport { Switchboard, type Params } from "@superset-ui/switchboard";For CommonJS:
const { Switchboard } = require("@superset-ui/switchboard");import { Switchboard } from "@superset-ui/switchboard";
// Create MessageChannel for communication
const channel = new MessageChannel();
// Parent window switchboard
const parentBoard = new Switchboard({
port: channel.port1,
name: 'parent',
debug: true
});
// Child window switchboard (inside iframe)
const childBoard = new Switchboard({
port: channel.port2,
name: 'child'
});
// Define method on child side
childBoard.defineMethod('getData', (args) => {
return { data: 'hello', timestamp: Date.now() };
});
// Start listening
childBoard.start();
// Call method from parent side
const result = await parentBoard.get('getData', { query: 'users' });
console.log(result); // { data: 'hello', timestamp: 1638360000000 }
// Fire-and-forget call
parentBoard.emit('logEvent', { event: 'user_action' });Switchboard is built around the MessageChannel API and provides:
get() method calls with Promise-based responsesemit() method calls without waiting for responsesMain class for managing iframe-parent window communication using MessageChannel.
/**
* A utility for communications between an iframe and its parent, used by the Superset embedded SDK.
* This builds useful patterns on top of the basic functionality offered by MessageChannel.
*/
class Switchboard {
/** The MessagePort used for communication */
port: MessagePort;
/** Instance name for debugging */
name: string;
/** Registry of callable methods */
methods: Record<string, Method<any, unknown>>;
/** Internal message ID counter */
incrementor: number;
/** Debug mode flag */
debugMode: boolean;
constructor(params: Params);
}Creates a new Switchboard instance with the specified configuration.
/**
* Creates a new Switchboard instance
* @param params - Configuration parameters
*/
constructor(params: Params);Defines a method that can be called from the other side of the communication channel.
/**
* Defines a method that can be "called" from the other side by sending an event
* @param methodName - Name of the method to register
* @param executor - Function to execute when method is called
*/
defineMethod<A = any, R = any>(methodName: string, executor: Method<A, R>): void;Usage Example:
switchboard.defineMethod('authenticate', async (credentials) => {
const { username, password } = credentials;
const token = await authenticateUser(username, password);
return { token, userId: 123, expires: Date.now() + 3600000 };
});Calls a method registered on the other side and returns the result via Promise.
/**
* Calls a method registered on the other side, and returns the result.
* Sends a "get" message, waits for a "reply" message with the result.
*
* @param method - Name of the method to call
* @param args - Arguments to pass (must be serializable)
* @returns Promise resolving to the method's return value
* @throws Error if method not found or execution fails
*/
get<T = unknown>(method: string, args?: unknown): Promise<T>;Usage Example:
// Call a method and wait for response
try {
const userData = await switchboard.get('fetchUserData', { userId: 123 });
console.log('User:', userData.name);
} catch (error) {
console.error('Failed to fetch user data:', error.message);
}Calls a method on the other side without waiting for a response (fire-and-forget).
/**
* Emit calls a method on the other side just like get does.
* But emit doesn't wait for a response, it just sends and forgets.
*
* @param method - Name of the method to call
* @param args - Arguments to pass (must be serializable)
*/
emit(method: string, args?: unknown): void;Usage Example:
// Fire-and-forget method call
switchboard.emit('trackEvent', {
event: 'dashboard_viewed',
dashboardId: 456,
timestamp: Date.now()
});Starts the MessagePort to begin listening for messages.
/**
* Starts the MessagePort to begin listening for messages
*/
start(): void;Configuration parameters for Switchboard constructor.
/**
* Configuration parameters for Switchboard constructor
*/
interface Params {
/** MessagePort for communication (required) */
port: MessagePort;
/** Name for debugging purposes (default: 'switchboard') */
name?: string;
/** Enable debug logging (default: false) */
debug?: boolean;
}Function signature for methods that can be registered with defineMethod.
/**
* Function signature for methods that can be registered with defineMethod
* @template A - Type of arguments object
* @template R - Return type
*/
type Method<A extends {}, R> = (args: A) => R | Promise<R>;Switchboard provides built-in error handling for method calls:
Error Handling Example:
try {
const result = await switchboard.get('nonexistentMethod');
} catch (error) {
// Error message will be: "[switchboard_name] Method "nonexistentMethod" is not defined"
console.error('Method call failed:', error.message);
}defineMethod()start() to begin processing messagesget() for responses or emit() for fire-and-forget calls