- PVSM.RU - https://www.pvsm.ru -
Многим разработчикам периодически требуется наладить общение между несколькими вкладками браузера: возможность посылать сообщения из одной в другую и получать ответ. Такая задача встала и перед нами.
Существуют стандартные решения вроде BroadcastChannel, однако поддержка в браузерах сейчас оставляет желать лучшего [1], поэтому мы решили реализовать свою библиотеку. Когда библиотека была готова, выяснилось, что такая функциональность уже не нужна, зато появилась другая задача: нужно было общаться между iframe и основным окном.
При ближайшем рассмотрении выяснилось, что две трети библиотеки при этом можно не менять, необходимо только немного порефакторить код. Библиотека представляет из себя скорей ПРОТОКОЛ общения, который может работать с текстовыми данными. Его можно применять во всех случаях, если есть возможность передавать текст (iframe, window.open, worker, вкладки браузера, WebSocket).
На данный момент в протоколе есть две функциональности: отправка сообщения и подписка на события. Любое сообщение в протоколе — это объект с данными. Главное поле этого объекта — поле type, которое говорит нам, что это за сообщение. Поле type — это enum [2] со значениями:
Отправка сообщения не подразумевает ответа. Для отправки события мы конструируем объект с полями:
При получении сообщения на другой стороне с полем type = 0 мы знаем, что это — событие и что есть имя события и данные. Остается лишь запустить событие (почти обычный паттерн EventEmitter [3]).
Схема работы с событиями:
Отправка запроса подразумевает, что внутри библиотеки формируется ID запроса, библиотека будет ожидать ответа с данным ID, и после успешного ответа из него будут удалены служебные поля, а ответ вернется пользователю. Кроме того, можно установить максимальное время ожидания ответа.
С запросом все обстоит несколько сложнее. Чтобы ответить на запрос, необходимо объявить методы, которые доступны в нашем протоколе. Это делается с помощью метода registerRequestHandler. Он принимает имя запроса, на который будет отвечать, и функцию, которая возвращает ответ. Для создания запроса нам нужен id, и в общем-то можно использовать timestamp, но это очень не удобно отлаживать. Поэтому это id класса который отправляет запрос + порядковый номер запроса + строковая константа. Далее мы конструируем объект с полями id, type — со значением 1, name — наименование запроса, data — данные пользователя (JSON-like).
При получении запроса мы проверяем, есть ли у нас API для ответа на данный запрос, если API нет — возвращаем ошибку. Если API есть — возвращаем результат выполнения функции из registerRequestHandler, с соответствующим именем запроса.
Для ответа формируется объект с полями type — со значением 2, id — id сообщения на которое отвечаем, status — поле, которое говорит, является ли данный ответ ошибкой (если нет API, или в обработчике пользователя произошла ошибка, или пользователь вернул Rejected Promise, другие ошибки (serialize)), content — данные ответа.
Таким образом мы описали работу самого протокола, который реализует класс Bus, но не описали, как собственно отправлять и получать сообщения. Для этого нужны адаптеры — класс с 3 методами:
Чтобы запустить все это, на данный момент готов только адаптер для работы с iframe/window. Работает он на postMessage [4] и addEventListener [5]. Тут все достаточно просто: нужно отправить сообщение в postMessage с правильным origin и слушать сообщения через addEventListener на событии "message".
Небольшие тонкости, с которыми мы столкнулись:
Библиотеку можно установить с помощью своего любимого пакетного менеджера — @waves/waves-browser-bus [8]
Чтобы установить двустороннюю связь с iframe, достаточно написать код:
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);
И внутри iframe:
import { Bus, WindowAdapter } from '@waves/waves-browser-bus';
WindowAdapter.createSimpleWindowAdapter().then(adapter => {
const bus = new Bus(adapter);
bus.dispatchEvent('ready', null); // Отправили сообщение в родительское окно
});
Получился гибкий и универсальный протокол, который можно использовать в любой ситуации.
Теперь я планирую отделить адаптеры от протокола и вынести их в отдельные npm-пакеты, добавить адаптеры для работы с worker и вкладками браузера. Хочется, чтобы писать адаптеры, реализующие протокол для любых других нужд, было максимально просто.
Если у вас есть желание присоединиться к разработке или идеи по функционалу библиотеки — милости прошу в репозиторий [9].
Автор: TsDaniil
Источник [10]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/320719
Ссылки в тексте:
[1] оставляет желать лучшего: https://caniuse.com/#search=BroadcastChannel
[2] enum: https://en.wikipedia.org/wiki/Enumerated_type
[3] EventEmitter: https://tproger.ru/translations/event-emitter-javascript/
[4] postMessage: https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage
[5] addEventListener: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
[6] Promise: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
[7] Window: https://developer.mozilla.org/en-US/docs/Web/API/Window
[8] @waves/waves-browser-bus: https://www.npmjs.com/package/@waves/waves-browser-bus
[9] репозиторий: https://github.com/wavesplatform/waves-browser-bus
[10] Источник: https://habr.com/ru/post/455942/?utm_campaign=455942&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.