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, however support in browsers 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 examination, it turned out that two thirds of the library could not be changed, it was only necessary to re-factor the code a little. The library is rather a communication PROTOCOL, which can work with text data. It can be used in all cases if it is possible to transmit text (iframe, window.open, worker, browser tabs, WebSocket).
At the moment, the protocol has two functionalities: 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 the message is. The type field is enum with values:
Sending a message does not imply a response. To send an event, we construct an object with the fields:
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. It remains only to start the event (almost the usual EventEmitter pattern).
The scheme of work with events:
Sending a request implies that a request ID is generated inside the library, the library will wait for a response with the given ID, and after a successful response, the service fields will be removed from it, and the answer will be returned to the user. In addition, you can set the maximum time to wait for a response.
With the query, everything is somewhat more complicated. To respond to a request, you must declare the methods that are available in our protocol. This is done using the registerRequestHandler method. It takes 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 the timestamp , but this is not very convenient to debug. Therefore, it is the id of the class that sends the request + request sequence number + string constant. Next, we construct an object with the fields id , type - with the value 1, name - the name of the request, data - the user data (JSON-like).
When we receive a request, we check if 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 execution from registerRequestHandler , with the corresponding request name.
For the response, an object is formed with type fields — with a value of 2, id — the message id to which it is answered, status — a field that indicates whether the response is an error (if there is no API, or an error occurred in the user’s handler, or the user returned 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 you need adapters - a class with 3 methods:
To start all this, at the moment only the adapter is ready to work with iframe / window. It works on postMessage and addEventListener . Everything is simple enough: you need to send a message to postMessage with the correct origin and listen to messages via addEventListener at the event "message".
Small subtleties that we encountered:
You can install the library using your favorite package manager - @ waves / waves-browser-bus
To establish a two-way connection with the 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); // });
The result is 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 into separate npm packages, add adapters to work with the worker and browser tabs. I want 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 .
Source: https://habr.com/ru/post/455942/
All Articles