Способы передачи сообщений между вкладками и окнами браузера
Существует целый ряд задач, для решения которых необходимо передавать состояние между разными контекстами браузера. Контекстами браузера могут быть вкладки, окна и встроенные на страницу фреймы. В этой статье я хотел бы рассмотреть способы решать задачи, которые требуют передачи сообщений между вкладками и окнами в пределах одного и того же ресурса (same-origin
).
Примеры подобных задач:
- обновление аутентификационного токена (auth token) во всех контекстах при обновлении токена в одном из них;
- разлогин пользователя во всех контекстах, когда завершается сессия в одном из них;
- синхронизация клиентского состояния между контекстами или изменение клиентского состояния в других контекстах как реакция на событие в одном из них;
- оптимистичное обновление UI во всех открытых контекстах без необходимости синхронизации с сервером (через механизм поллинга или вебсокеты);
- остановка действия во всех контекстах, кроме активного (например, остановка проигрывания аудио в приложении веб-плеера в одной вкладке, если проигрывание было запущено в другой).
Давайте рассмотрим самые популярные способы решать подобные задачи:
Web Storage API
Web Storage API предоставляет интерфейсы для работы с хранилищами localStorage
и sessionStorage
. Эти интерфейсы позволяют добавить строковую пару ключ-значение в объект Storage
, после чего будет сгенерировано событие storage
во всех вкладках, кроме той, через которую вносилось изменение. Внутри обработчика события storage
можно получить доступ не только к новому, но и к предыдущему значению элемента, который был изменён.
👀 Пример использования:
Инициация события storage
:
localStorage.setItem("key", "value");
Обработка события storage
:
window.addEventListener("storage", (e) => {
console.log(e.newValue); // "value"
});
Плюсы:
- простой API
- самая широкая поддержка в браузерах
Минусы:
- не подходит для синхронизации чувствительной информации типа auth tokens
- имеет ограничения по размеру хранимой информации
- неудобно для работы со сложной логикой
- работает синхронно, поэтому может заблокировать основной UI-поток при передаче больших чанков информации
Shared Worker
Shared Worker — это скрипт, который могут переиспользовать разные контексты браузера в пределах одного ресурса. Скрипт работает в отдельном потоке и не влияет на основной поток выполнения кода приложения. Shared Worker
может получать сообщения от вкладок, которые к нему подключены, хранить и обрабатывать эти сообщения, а также уведомлять остальные вкладки о результатах обработки таких сообщений.
👀 Пример использования:
Скрипт, который умеет принимать сообщения и отправлять их обратно в приложение:
// shared-worker.js
self.addEventListener("connect", (e) => {
e.source.addEventListener("message", (event) => {
port.postMessage(event.data);
});
e.source.start();
});
Подключение приложения к Shared Worker
и получение/отправка сообщений:
const worker = new SharedWorker("shared-worker.js");
worker.port.addEventListener("message", (e) => {
console.log(e.data);
});
worker.port.start();
worker.port.postMessage("message");
Плюсы:
- работает в отдельном потоке, подходит для независимого выполнения тяжёлых вычислений
- может содержать сложную логику и работать с разными структурами данных
- предоставляет контроль над источниками и потребителями сообщений
Минусы:
- не самая широкая поддержка браузерами
- высокий порог вхождения для решения простых кейсов
Broadcast Channel API
Broadcast Channel API — это нативный браузерный интерфейс, который придуман специально для коммуникации между вкладками и окнами одного и того же приложения. Синтаксис Broadcast Channel API
очень простой и позволяет гибко настраивать синхронизацию между контекстами, а также безопасен для передачи чувствительной информации.
👀 Пример использования:
Инициация события отправки сообщения:
const bc = new BroadcastChannel("channel");
bc.postMessage("message");
bc.close();
Получение и обработка сообщения в других вкладках:
const bc = new BroadcastChannel("channel");
bc.addEventListener("message", (e) => {
console.log(e.data);
});
bc.close();
Плюсы:
- простой API
- хорошо поддерживается браузерами на сегодняшний день
- подходит для передачи чувствительных данных, требующих защиты от cross-site-scripting (XSS) атак
Минусы:
- меньше контроля над источниками и получателями сообщений, сообщения транслируются сразу всем вкладкам
В заключение
Учитывая простоту, удобство использования и широкую поддержку современными браузерами, Broadcast Channel API
будет лучшим решением в подавляющем количестве кейсов, в которых необходимо настроить общение между вкладками в пределах одного ресурса.
Shared Worker
стоит использовать только для очень сложных задач, в которых каждая вкладка обрабатывается как отдельный источник/потребитель данных со своей уникальной логикой. В этом случае воркер выступает хранилищем общего состояния всего приложения, когда как вкладки становятся независимыми потребителями этого состояния. Возможно, что для решения таких сложных задач пригодится ещё более мощный инструмент — Service Worker
, который не имеет своего состояния, но позволяет тонко настраивать логику работы с внешними ресурсами и синхронизировать результаты этой работы между вкладками через встроенный механизм postMessage
.
localStorage
(sessionStorage
) и событие storage
можно использовать для совсем простых кейсов и передачи очень небольших чанков информации, так как передача больших чанков может приводить к блокировкам потока, отвечающего за работу с UI. Web Storage API
поддерживается даже IE, так что его можно использовать в целях поддержания кроссбраузерности вашего решения.
В этой статье я не рассматривал глобальный метод window.postMessage
, так как он работает только с теми вкладками и окнами, которые были созданы методами window.open()
или document.open()
, требуя указания ссылки на созданные таким образом контексты. Лучше всего использовать этот способ для общения между фреймами, которые были подключены на одной странице и живут в разных ресурсах (cross-origin
).
Для более глубокого погружения в материал советую посмотреть уроки от Tom Gobich с разбором примеров использования каждого способа:
- How To Do Cross-Tab Communication In JavaScript With LocalStorage
- Cross-Tab Communication in JavaScript using a SharedWorker
- Cross-Tab Communication in JavaScript using a BroadcastChannel
Приглашаю вас подписаться на мой телеграм-канал: https://t.me/alexgriss , где я пишу о фронтенде, архитектуре, UX/UI, продакт-мышлении, стартапах и лидерстве, а так же о том, как оставаться собой в профессии и создавать то, что имеет значение.