@jitsi/js-utils
Version:
Utilities for Jitsi JS projects
129 lines (128 loc) • 5.4 kB
TypeScript
import type { ITransportBackend } from './types';
/**
* Options for MessageChannelTransportBackend.
*/
export interface IMessageChannelTransportBackendOptions {
/**
* The origin to use for postMessage calls and origin checks.
* When not provided, the wildcard origin ('*') is used for the initial port transfer,
* which means any origin can receive the port. For production use with cross-origin
* iframes, always specify the expected origin to prevent port interception.
*/
origin?: string;
/**
* A scope identifier used to namespace channel initialization messages,
* allowing multiple independent channels on the same window.
*/
scope?: string;
/**
* Whether this side should create the MessageChannel.
* When true, creates a new MessageChannel and sends port2 to the target window.
* When false, listens for an incoming channel initialization message.
*/
shouldCreateChannel?: boolean;
/**
* The target window to send the initial channel port to.
* Defaults to window.parent.
*/
targetWindow?: Window;
}
/**
* Implements message transport using the MessageChannel API.
*
* When shouldCreateChannel is true, a new MessageChannel is created and port2 is
* transferred to the target window via postMessage. When false, the backend listens
* for an incoming init_channel message carrying the port.
*
* Outgoing messages sent before the port is established (receiver side) are buffered and
* flushed automatically once the handshake completes. Incoming messages are buffered
* natively by the MessagePort until a receive callback is wired via setReceiveCallback,
* since assigning onmessage implicitly starts the port.
*
* Re-handshake: on the receiver side the init_channel listener stays attached for the
* lifetime of the backend. If the creator (e.g. an iframe) self-navigates or reloads
* and posts a fresh init_channel, the receiver closes the previous (now-dead) port and
* adopts the new one transparently. MessageChannel exposes no liveness signal, so this
* "always re-adopt the latest matching port" policy is the only way to keep working
* across in-creator reloads without cooperation from the creator side.
*/
export default class MessageChannelTransportBackend implements ITransportBackend {
/**
* The underlying MessageChannel instance used when this side creates the channel.
*/
private channel?;
/**
* The expected origin for postMessage calls and origin validation.
* When set, incoming messages from other origins are rejected.
*/
private origin?;
/**
* Messages buffered before the port was established on the receiver side.
* Flushed in order once the port becomes available.
*/
private pendingMessages;
/**
* The MessagePort used for sending and receiving messages.
*/
private port?;
/**
* A scope identifier used to namespace channel initialization messages.
*/
private scope?;
/**
* Callback function for receiving messages. Undefined until setReceiveCallback is called;
* while undefined the MessagePort remains unstarted and buffers incoming messages natively.
*/
private receiveCallback?;
/**
* Creates a new MessageChannelTransportBackend instance.
*
* @param {IMessageChannelTransportBackendOptions} options - Configuration options for the transport backend.
*/
constructor({ shouldCreateChannel, targetWindow, origin, scope }: IMessageChannelTransportBackendOptions);
/**
* Handles the initial postMessage event to receive the MessagePort from the channel creator.
*
* The listener remains attached for the lifetime of the backend so that if the creator
* self-reloads (its previous document and port1 are gone, leaving the receiver with an
* inert port2 that the platform never signals), a fresh init_channel can replace the
* dead port without cooperation from the creator.
*
* @param {MessageEvent} event - The incoming message event.
*/
private initialMessageHandler;
/**
* Forwards messages from the MessagePort to the current receive callback.
*
* @param {MessageEvent} event - The incoming port message event.
*/
private handlePortMessage;
/**
* Flushes any messages that were buffered before the port was established.
*/
private flushPendingMessages;
/**
* Disposes the allocated resources.
*
* @returns {void}
*/
dispose(): void;
/**
* Sends the passed message. If the port has not yet been established (receiver side),
* the message is buffered and will be sent once the handshake completes.
*
* @param {any} message - The message to be sent.
* @param {Transferable[]} [transfer] - Optional array of transferable objects.
* @returns {void}
*/
send(message: any, transfer?: Transferable[]): void;
/**
* Sets the callback for receiving data. If the MessagePort is available and has not
* yet been started, this also starts it (by assigning onmessage), flushing any messages
* buffered natively by the port since the handshake completed.
*
* @param {Function} callback - The new callback.
* @returns {void}
*/
setReceiveCallback(callback: (message: any) => void): void;
}