
Protocol for communication between iframe and main browser window
Many developers periodically need to establish communication between several browser tabs: the ability to send messages from one to another and receive a response. Such a task arose before us.
There are standard solutions like BroadcastChannel, but browser support now leaves much to be desired , so we decided to implement our library. When the library was ready, it turned out that such functionality was no longer needed, but another task appeared: it was necessary to communicate between the iframe and the main window.
Upon closer inspection, it turned out that two-thirds of the library can not be changed at the same time, you just need to refactor the code a little. The library is rather a communication PROTOCOL that can work with text data. It can be used in all cases if it is possible to transfer text (iframe, window.open, worker, browser tabs, WebSocket).
How it works
At the moment, the protocol has two functions: sending a message and subscribing to events. Any message in the protocol is an object with data. The main field of this object is the type field , which tells us what kind of message it is. The type field is enum with values:
- 0 - send message
- 1 - sending request
- 2 - receiving a response.
Message sending
Sending a message does not imply a response. To send an event, we construct an object with fields:
- type - event type 0
- name - name of the user event
- data - user data (JSON-like).
When we receive a message on the other side with the type = 0 field, we know that this is an event and that there is an event name and data. All that remains is to fire the event (an almost regular EventEmitter pattern ).
Scheme of working with events:
Request Submission
Sending a request implies that a request ID is generated inside the library, the library will wait for a response with this ID, and after a successful response service fields will be deleted from it, and the response will be returned to the user. In addition, you can set the maximum response time.
With the request, everything is somewhat more complicated. To respond to a request, you must declare methods that are available in our protocol. This is done using the registerRequestHandler method . It accepts the name of the request to which it will respond, and the function that returns the response. To create a request, we need an id , and in general, you can use timestamp , but it is very inconvenient to debug. Therefore, this is the id of the class that sends the request + serial number of the request + string constant. Next, we construct an object with fields id , type - with a value of 1, name - the name of the request, data - user data (JSON-like).
Upon receipt of the request, we check whether we have an API to respond to this request, if there is no API, we return an error. If there is an API, we return the result of the function from registerRequestHandler , with the corresponding request name.
An object is formed for the response with type fields - with a value of 2, id - id of the message to which we are responding, status - a field that says whether this response is an error (if there is no API, or an error occurred in the user exit, or the user returned Rejected Promise, other errors (serialize)), content - response data.
Thus, we described the operation of the protocol itself, which implements the Bus class , but did not describe how to actually send and receive messages. For this we need adapters - a class with 3 methods:
- send - a method that is actually responsible for sending a message
- addListener - a method for subscribing to events
- destroy - to destroy subscriptions when destroying Bus.
Adapters Protocol implementation.
To start all this, at the moment only the adapter for working with iframe / window is ready. It works on postMessage and addEventListener . Everything is quite simple here: you need to send a message to postMessage with the correct origin and listen to messages through addEventListener on the event "message".
Small subtleties that we encountered:
- You should always listen to the answers on YOUR window, and send them to someone else (iframe, opener, parent, worker, ...).
The fact is that when you try to listen to a message on someone else's window, if origin differs from the current one, an error will occur. - When you receive a message, make sure it is sent to you (a bunch of messages from analytics,
WebStrom (if you use it), other people's iframes work on the window , so make sure that the event is in our protocol for us too. - You cannot return Promise with an instance of Window , since Promise, when returning the result, tries to check if the result has a then method , and if you do not have access to the window (a window with a different origin, for example), an error will occur (although not in all browsers ) To avoid this problem, just wrap the window in an object and put in the Promise object in which there is a link to the desired window.
Examples of using:
The library can be installed using your favorite package manager - @ waves / waves-browser-bus
To establish two-way communication with an iframe, just write the code:
import { Bus, WindowAdapter } from '@waves/waves-browser-bus';
const url = 'https://some-iframe-content-url.com';
const iframe = document.createElement('iframe');
WindowAdapter.createSimpleWindowAdapter(iframe).then(adapter => {
const bus = new Bus(adapter);
bus.once('ready', () => {
// Получено сообщение от iframe
});
});
iframe.src = url; // Предпочтительно присваивать url после вызова WindowAdapter.createSimpleWindowAdapter
document.body.appendChild(iframe);
And inside the iframe:
import { Bus, WindowAdapter } from '@waves/waves-browser-bus';
WindowAdapter.createSimpleWindowAdapter().then(adapter => {
const bus = new Bus(adapter);
bus.dispatchEvent('ready', null); // Отправили сообщение в родительское окно
});
What's next?
It turned out a flexible and universal protocol that can be used in any situation.
Now I plan to separate the adapters from the protocol and put them in separate npm packages, add adapters for working with worker and browser tabs. I would like to write adapters that implement the protocol for any other needs, it was as simple as possible.
If you have a desire to join the development or ideas on the functionality of the library - you are welcome to the repository .