rx-postmessenger
Minimal RxJS adapter for the Window # postMessage
API for request-response streams and notification streams across frame windows.
In short
An RxPostmessenger class instance establishes one end of a connection between 2 window objects, using the Window # postMessage
API. Each instance provides methods to initiate outgoing messages and handle incoming messages, both categorized by channel name.
Each instance targets one single Window
object. It propagates incoming MessageEvent
s only from that specific window object while it's serving documents from one single origin.
RxJS Interoperability
RxPostmessenger v1.x | RxPostmessenger v2.x | RxPostmessenger v3.x | |
---|---|---|---|
RxJS v5.x | |||
RxJS v6.x | |||
RxJS v7.x |
Installation
$ npm install rx-postmessenger --save
Contents / API
Static methods
Method | Description |
---|---|
connect() |
Connect Window objects by creating messenger instances. |
Messenger
Instance methods
Method | Description |
---|---|
notify() |
Send notifications to the connected window. |
notifications() |
Listen for inbound notifications. |
request() |
Send requests to the connected window. |
requests() |
Listen for inbound requests. |
Request
Instance methods
Method | Description |
---|---|
respond() |
Respond to the request with a certain payload. |
Usage
import RxPostmessenger from 'rx-postmessenger';
Static methods
Connecting 2 Window objects
RxPostmessenger.connect(otherWindow: Window, origin: string): RxPostmessenger.Messenger
Both ends of the connection should implement this package. One in a parent project (that implements the iframe), and one in a child project (that's being served by the iframe). Creating a new messenger is straightforward:
At parent window - https://parent-project.com
const childMessenger = RxPostmessenger.connect(
someIFrame.contentWindow,
'https://child-project.com'
);
At child window - https://child-project.com
const parentMessenger = RxPostmessenger.connect(
window.parent,
'https://parent-project.com'
);
Messenger
Instance methods
Sending notifications
Messenger.notify<T>(channel: string, payload?: T): void
The messenger instances give you a way to send notifications to the other Window
through the notify()
method.
The notify method is void -- notifications are fire-and-forget.
Use request()
instead if you require data back.
Consider an example where we want to notify a child window of price changes:
childMessenger.notify('price-changed', {
oldPrice: 12.50,
newPrice: 14.50,
});
Listening for inbound notifications
Messenger.notifications<T = any>(channel: string): Observable<T>
The child project can request an Observable stream for a certain notification channel.
In this case we're interested in 'price-changed'
events, but only the ones where the price increased.
The ability to use RxJS operators can help us out:
parentMessenger.notifications('price-changed').pipe(
filter(({ oldPrice, newPrice }) => newPrice > oldPrice),
map(({ oldPrice, newPrice }) => newPrice - oldPrice),
).subscribe((increase) => console.log(`Price increased with €${increase}!`));
// > 'Price increased with €2!'
Sending requests
Messenger.request<T = any, U = any>(channel: string, payload?: T): Observable<U>
RxPostmessenger also supports request - response communication.
At the requester side a request is initiated by calling the request()
method with 1 or 2 arguments.
The first is a request alias (actually just another channel) of our choice.
A notification-channel and a request-channel can both have the same channel name without any problem.
An observable is returned that emits the response when arrived, and then completes.
Let's request a greeting from the child window, and tell it to localize the response to 'en'
:
const greetingResponse$ = childMessenger.request('greeting', {
language: 'en',
});
We can then subscribe to the greeting response stream. Provided that the greeting says something nice, we'll log it for everyone to see:
greetingResponse$.pipe(
filter((greeting) => isNiceGreeting(greeting)),
).subscribe(console.log);
// > 'Hi parent!'
Listening for inbound requests
Messenger.requests<T = any, U = any>(channel: string): Observable<RxPostmessenger.Request<T, U>>
No greeting would ever be received by parentMessenger
when the child project does not listen
for requests to handle and respond to. Let's not be rude and create a request stream for
'greeting'
requests, and subscribe to it. We'll pass the RxPostmessenger.Request
objects
that the subscription receives into a function handleGreetingRequest()
:
parentMessenger
.requests('greeting')
.subscribe(handleGreetingRequest);
Request
instance methods
Sending request responses
RxPostmessenger.Request<T, U> ~ respond(payload: U): void
The requests
method returns an observable of RxPostmessenger.Request
objects.
They provide a single method respond
that accepts one argument: the response payload.
Let's use the method on the requests we give to handleGreetingRequest
:
const handleGreetingRequest = (request) => {
// The data that was sent along with the request
const requestPayload = request.payload;
// A hypothetical greeting translator
const localizedGreeting = translateGreeting(
'Hi parent!',
requestPayload.language
);
// Eventually respond to the request with some data (payload)
request.respond(localizedGreeting);
};
License
The MIT License (MIT). See license file for more information.