MessagePortDispatcher is extended API for cross-origin communication. It utilizes MessagePort API available on window
object to send custom events into/from <IFRAME/> or other target that implements MessagePort interface. MessagePortDispatcher uses two EventDispatcher's for incoming and outgoing events internally.
Demo with two <iframe/>'s talking to each other
Easy to install with npm package manager
npm install --save @actualwave/messageport-dispatcher
with yarn package manager
yarn add @actualwave/messageport-dispatcher
Note: MessagePortDispatcher distribution package contains
dist/
folder with package wrapped into UMD wrapper, so it can be used with any AMD module loader, nodejsrequire()
or without any.
To start using EventDispatcher, just instantiate it
const dispatcher = new MessagePortDispatcher(iframe.contentWindow);
As first argument its constructor accepts object that implements messaging methods of MessagePort interface.
- postMessage(message:Object)
- addEventDispatcher(type:String, handler:Function)
- removeEventDispatcher(type:String, handler:Function)
Window object or Dedicated Worker can be used, to communicate with other side of communication channel(send event to script in IFRAME or from IFRAME or to Worker). To have custom events working on both sides, MessagePortDispatcher instances should be created from both sides of communication channel. In outer document pass IFRAME's window object
const frameDispatcher = new MessagePortDispatcher(iframeNode.contentWindow);
frameDispatcher.addEventListener('initialized', () => {
console.log('Ok, we can start communication.');
});
In IFRAME use window.self
const dispatcher = MessagePortDispatcher.self();
dispatcher.dispatchEvent('initialized');
Instances returned from MessagePortDispatcher.self()
, MessagePortDispatcher.parent()
and MessagePortDispatcher.top()
are cached internally, so will always return same instance.
Its possible to write an adapter for any object and pass it into MessagePortDispatcher
const target = {
postMessage: (data, origin) => {
console.log('Message sent', data);
window.postMessage(data, origin);
},
addEventListener: (eventType, handler) => {
console.log('Event listener added to ', eventType);
window.addEventListener(eventType, handler);
},
removeEventListener: (eventType, handler) => {
console.log('Event listener removed from ', eventType);
window.removeEventListener(eventType, handler);
}
};
const dispatcher = new MessagePortDispatcher(target);
Once its instance was created, you can send events into iframe
dispatcher.dispatchEvent('someEvent', {someData: 'anything here'});
and catch it on other side
dispatcher.addEventListener('someEvent', (event) => {
console.log('Data received', event.data);
});
When MessagePortDispatcher.dispatchEvent()
called, it actually calls postMessage()
method to pass message to other side. So instead of using postMessage
and listening to message
event, with MessagePortDispatcher you can send and receive custom events.
When MessagePortDispatcher instantiated, it creates two EventDispatcher's, one for incoming events and second for outgoing. Since Window object fires same message
event for both sides, under the hood MessagePortDispatcher adds own ID to each event and if received event has same ID, it will be fired viasender
(outgoing event) EventDispatcher, otherwise via receiver
(incoming event).
This will not work, event someEvent
will be fired on other side but not for this dispatcher:
dispatcher.addEventListener('someEvent', () => {
console.log('Some Event Received!');
});
dispatcher.dispatchEvent('someEvent');
If you want to listen for outgoing events, use sender
:
dispatcher.sender.addEventListener('someEvent', () => {
console.log('Some Event Received!');
});
dispatcher.dispatchEvent('someEvent');
Using same event types on both sides of communication channel will not mix them, since they will be fired from different dispatchers.
MessagePortDispatcher has exposed methods from receiver
EventDispatcher for easier usage and custom dispatchEvent()
method that sends events using MessagePort.postMessage()
.
These two calls are equivalent:
dispatcher.addEventListener('someEvent', () => {});
dispatcher.receiver.addEventListener('someEvent', () => {});
But these lines do different things:
dispatcher.dispatchEvent('someEvent');
dispatcher.sender.dispatchEvent('someEvent');
sender.dispatchEvent()
will just fire event from sender EventDispatcher, but MessagePortDispatcher.dispatchEvent()
will actually send message to other side via postMessage()
.
Since MessagePortDispatcher passes data between origins, it can send only simple data(i.e. nothing can be sent by reference) that can be converted to JSON. Before sending event, it checks its data property value. If this value has method toJSON()
, it will use it and send returned data as is. In other case the value will be converted to JSON string before being sent and converted back when received. When using toJSON()
method its developer's responsibility to look for nested data objects and convert everything to transferable simple objects.
Project contains example in example
folder, it shows how to use MessagePortDispatcher when communicating with frames.
- target:Object - Requred, target object, should have postMessage(), addEventListener(), removeEventListener() methods, asdescribed in MessagePort docs.
- customPostMessageHandler:Function - will be used to call
target.postMessage()
- receiverEventPreprocessor:Function - Optional, allows pre-processing of events and their data before firing event.
- senderEventPreprocessor:Function - Optional, , allows pre-processing of events and their data before passing them to
postMessage
orcustomPostMessageHandler
.
- targetOrigin:String
- sender:EventDispatcher - fires outgoing events that are passed to
postMessage()
- receiver:EventDispatcher - fires incoming events received from other origin
- target:Object - target object that is used for communication
- dispatcherId:String - unique ID of current MessagePortDispatcher instance
- addEventListener(eventType:String, listener:Function):void - method copied from
receiver
EventDispatcher for easier access - hasEventListener(eventType:String):Boolean - method copied from
receiver
EventDispatcher for easier access - removeEventListener(eventType:String, listener:Function):void - method copied from
receiver
EventDispatcher for easier access - removeAllEventListeners(eventType:String):void - method copied from
receiver
EventDispatcher for easier access - dispatchEvent(event:Object):void - does not fire event, it sends event to
postMessage()
. Can be used with two arguments: -
- dispatchEvent(eventType:String, data?:Object):void
A class that is used as a surrogate target for MessagePortDispatcher, it is useful when you have two objects for sending and receiving messages. For example, when you have an iframe with content from another origin and you can set iframe.contentWindow as sender object and own window as receiver. Sender object must contain postMessage()
method and receiver object -- addEventListener()
and removeEventListener()
methods. Pass both sender and receiver into MessagePortTarget constructor, then it's instance can be provided for MessagePortDispatcher.
const frameDispatcher = new MessagePortDispatcher(new MessagePortTarget(iframeNode.contentWindow, window));
It also accepts lists of senders and receivers for mass sending and receiving.
const frameDispatcher = new MessagePortDispatcher(new MessagePortTarget([
iframe1.contentWindow,
iframe2.contentWindow,
iframe3.contentWindow
], window));