Skip to content

burdiuz/js-messageport-event-dispatcher

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

68 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MessagePortDispatcher

MessagePortDispatcher is an extended API for cross-origin communication. It wraps the MessagePort API available on window, Worker, and other targets to send and receive typed custom events across <iframe> boundaries, Web Workers, and any object that implements the MessagePort interface. Internally it uses two EventDispatcher instances — one for incoming events and one for outgoing.

Demo with two <iframe>'s talking to each other

Installation

npm install @actualwave/messageport-dispatcher
yarn add @actualwave/messageport-dispatcher

Usage

Instantiate with any object that implements the MessagePort interface (postMessage, addEventListener, removeEventListener):

const dispatcher = new MessagePortDispatcher(iframe.contentWindow);

Communicating across an iframe boundary

In the outer document, pass the iframe's contentWindow:

import { MessagePortDispatcher } from '@actualwave/messageport-dispatcher';

const frameDispatcher = new MessagePortDispatcher(iframeNode.contentWindow);
frameDispatcher.addEventListener('initialized', () => {
  console.log('Communication channel is open.');
});

Inside the iframe, use getForSelf():

import { getForSelf } from '@actualwave/messageport-dispatcher';

const dispatcher = getForSelf();
dispatcher.dispatchEvent('initialized');

getForSelf(), getForParent(), and getForTop() return cached singletons, so they always return the same instance.

Sending and receiving events

// Send to the other side
dispatcher.dispatchEvent('someEvent', { someData: 'anything here' });

// Receive on the other side
dispatcher.addEventListener('someEvent', (event) => {
  console.log('Data received', event.data);
});

dispatchEvent() serialises the event and calls postMessage() — it does not fire the event locally on receiver. To observe sent events locally, listen on sender:

dispatcher.sender.addEventListener('someEvent', () => {
  console.log('Outgoing event observed');
});
dispatcher.dispatchEvent('someEvent');

Custom adapter target

const target = {
  postMessage: (data, origin) => {
    console.log('Message sent', data);
    window.postMessage(data, origin);
  },
  addEventListener: (eventType, handler) => {
    window.addEventListener(eventType, handler);
  },
  removeEventListener: (eventType, handler) => {
    window.removeEventListener(eventType, handler);
  },
};
const dispatcher = new MessagePortDispatcher(target);

MessagePortTarget

MessagePortTarget is a convenience wrapper for cases where sending and receiving are handled by different objects — for example, an iframe's contentWindow for sending and your own window for receiving:

import { MessagePortDispatcher, MessagePortTarget } from '@actualwave/messageport-dispatcher';

const frameDispatcher = new MessagePortDispatcher(
  new MessagePortTarget(iframeNode.contentWindow, window),
);

It also accepts arrays for broadcasting to or receiving from multiple targets:

const frameDispatcher = new MessagePortDispatcher(
  new MessagePortTarget(
    [iframe1.contentWindow, iframe2.contentWindow, iframe3.contentWindow],
    window,
  ),
);

Data serialisation

Because events cross origin boundaries, only serialisable data can be transferred. Before sending, dispatchEvent checks the event's data value:

  • If the value has a toJSON() method, its return value is sent as-is (structured clone). The developer is responsible for converting nested objects.
  • Otherwise the value is JSON.stringify-d, then parsed back on the receiving side.

Dispatcher ID and echo suppression

Each MessagePortDispatcher instance generates a unique dispatcherId. When a sent event is echoed back (which happens with window.postMessage), the dispatcher detects its own ID and routes the echo to the sender EventDispatcher instead of receiver, preventing false local dispatch.

API

MessagePortDispatcher constructor

Parameter Type Description
target MessagePortLike | null Object with postMessage and addEventListener. Defaults to self.
customPostMessageHandler Function | null Replaces the default target.postMessage() call.
receiverEventPreprocessor EventProcessor | null Transforms incoming events before listeners are called.
senderEventPreprocessor EventProcessor | null Transforms outgoing events before postMessage is called.

MessagePortDispatcher instance members

Member Type Description
dispatcherId string Unique ID for this instance.
targetOrigin string Passed to postMessage as the target origin. Defaults to '*'.
target MessagePortLike The underlying message port object.
sender IEventDispatcher Fires outgoing events (echoes of sent messages).
receiver IEventDispatcher Fires incoming events received from the other side.
addEventListener(type, listener, priority?) void Delegates to receiver.addEventListener.
hasEventListener(type) boolean Delegates to receiver.hasEventListener.
removeEventListener(type, listener) void Delegates to receiver.removeEventListener.
removeAllEventListeners(type) void Delegates to receiver.removeAllEventListeners.
dispatchEvent(eventType, data?, transferList?) void Serialises and sends the event via postMessage.

Factory functions

Function Description
getForSelf() Cached dispatcher for self (current window / worker).
getForParent() Cached dispatcher for parent window.
getForTop() Cached dispatcher for top window.
createMessagePortDispatcher(target?, ...) Creates a new MessagePortDispatcher instance.

MessagePortTarget

Member Description
constructor(sender?, receiver?) Accepts single objects or arrays of objects for each role.
postMessage(...args) Calls postMessage on all senders.
addEventListener(type, handler) Calls addEventListener on all receivers.
removeEventListener(type, handler) Calls removeEventListener on all receivers.

TypeScript

The package ships with TypeScript declarations. Types from @actualwave/event-dispatcher are also available for event typing:

import {
  MessagePortDispatcher,
  MessagePortTarget,
  MessagePortEvent,
  createMessagePortDispatcher,
  getForSelf,
  getForParent,
  getForTop,
} from '@actualwave/messageport-dispatcher';

import type { EventListener, EventProcessor } from '@actualwave/event-dispatcher';

const preprocessor: EventProcessor = (event) => {
  return { ...event, data: { ...(event.data as object), timestamp: Date.now() } };
};

const dispatcher = new MessagePortDispatcher(iframe.contentWindow, null, preprocessor);

const handler: EventListener = (event) => {
  console.log(event.type, event.data);
};

dispatcher.addEventListener('myEvent', handler);
dispatcher.dispatchEvent('myEvent', { payload: 42 });

About

Cross-domain EventDispatcher for MessagePort interface

Topics

Resources

License

Stars

0 stars

Watchers

1 watching

Forks

Packages

 
 
 

Contributors