На протяжении многих лет возможности браузеров постоянно увеличивались в ответ на растущие потребности веб-приложений. И теперь у нас есть множество способов получения одной и той же (или схожей) функциональности. На такую особенность браузеров, как возможность обмена данными между вкладками, редко обращают внимание. Рассмотрим несколько сценариев, в которых она может потребоваться:

  • Изменение темы (например, тёмная или светлая тема) приложения распространяется на уже открытые вкладки браузера.
  • Получение последнего токена для аутентификации и использование его во всех вкладках браузера.
  • Синхронизация состояния приложения во всех вкладках браузера.

На момент написания этой статьи существовало несколько достойных внимания подходов, в соответствии с которыми осуществляется обмен данными между вкладками браузеров. У каждого из этих подходов свои сильные и слабые стороны, поэтому остановимся на них подробнее, чтобы вы могли найти подходящий для вас вариант.

1. «Events» («События») в локальном хранилище данных

Возможно, вы уже использовали LocalStorage, который доступен на разных вкладках в рамках одного и того же приложения-источника. Но знаете ли вы, что LocalStorage поддерживает события? Эту функцию можно использовать для обмена данными между вкладками браузера: хранилище обновляется, после чего событие получат другие вкладки.

Например, в одной вкладке выполняется следующий код JavaScript:

window.localStorage.setItem("loggedIn", "true");

И другие, прослушивающие событие вкладки получат это событие:

window.addEventListener('storage', (event) => {
 if (event.storageArea != localStorage) return;
 if (event.key === 'loggedIn') {
   // Делаем что-нибудь с «event.newValue»
 }
});

Но здесь есть несколько ограничений:

  • Событие не срабатывает для вкладки, на которой выполняется действие по вводу значений в хранилище.
  • Этот подход имеет негативные последствия для большого объёма данных: из-за синхронности выполняемых в LocalStorage действий основной поток пользовательского интерфейса может быть заблокирован.

Более подробная информация содержится в разделе «Storage Events» документации MDN.

2. API широковещательного канала

API широковещательного канала позволяет осуществлять обмен данными между вкладками, окнами, фреймами, Iframes и веб-воркерами. Одна вкладка создаёт что-то и опубликовывает это на канале:

const channel = new BroadcastChannel('app-data');
channel.postMessage(data);

А другие вкладки прослушивают канал:

const channel = new BroadcastChannel('app-data');

channel.addEventListener ('message', (event) => {
 console.log(event.data);
});
const channel = new BroadcastChannel('app-data');

И таким образом происходит обмен данными между контекстами браузера (окнами, вкладками, фреймами или Iframes). Такой способ обмена данными между вкладками браузера очень удобен. Тем не менее safari и IE его не поддерживают. Более подробная информация содержится в разделе «BroadcastChannel» документации MDN.

Подсказка: выкладывайте на Bit (Github) переиспользуемые компоненты для своих проектов. Здесь очень просто размещать, документировать и организовывать независимые компоненты из любого проекта.

Задействуйте этот ресурс для максимально многократного использования кода и совместной работы над независимыми компонентами, а также для создания масштабируемых приложений.

Bit поддерживает Node, TypeScript, React, Vue, Angular и много других инструментов.

Пример: просмотр повторно используемых компонентов React, выложенных на Bit.dev

3. Сервис-воркеры для отправки сообщений

Здесь у вас может возникнуть вопрос: «Откуда взялись тут сервис-воркеры?». Вообще-то они тоже поддерживают отправку сообщений, поэтому сервис-воркеры можно использовать для обмена данными между вкладками браузера.

Вот как отправляется сообщение с помощью сервис-воркеров:

navigator.serviceWorker.controller.postMessage({
 broadcast: data
});

А принимающий воркер на другой вкладке браузера прослушивает это событие:

addEventListener('message', async (event) => {
 if ('boadcast' in event.data ) {
  const allClients = await clients.matchAll();
  for (const client of allClients) {
   client.postMessage(event.broadcast);
  }
 }
});

Это надежный способ передачи сообщений, предоставляющий больше контроля. Но для внедрения сервис-воркеров требуются дополнительные знания об API Service Worker и чуть больше работы. Так что, если другие подходы не работают, стоит обратить внимание на этот. Более подробная информация содержится в разделе «Service Worker API» документации MDN, а весь пример доступен по этой ссылке.

4. Отправка сообщений между окнами

Подход Window.postMessage()  —  один из традиционных способов обмена данными между вкладками браузера. Сообщение отправляется так:

targetWindow.postMessage(message, targetOrigin)

А целевое окно прослушивает события:

 рwindow.addEventListener("message", (event) => {
  if (event.origin !== "http://localhost:8080")
    return;
  // Делаем что-нибудь
}, false);

Одно из преимуществ этого подхода перед другими  —  возможность поддержки обмена данными между разными источниками. Но есть и ограничение: необходима ссылка на другую вкладку браузера. Поэтому этот подход только для вкладок браузера, открытых через window.open() или document.open(). Более подробная информация содержится в документации MDN.

Заключение

Надеюсь, статья была для познавательной и рассмотренные в ней подходы будут полезны для ваших веб-приложений. Каждый подход уникален и имеет свои варианты использования.

Кроме этих четырёх подходов, для обмена данными между вкладками браузера и даже между устройствами в режиме реального времени применяются Websockets («веб-сокеты») и Server-Sent Events («события, посылаемые сервером»). Но для этого понадобится веб-сервер. А вот рассмотренные в статье подходы не зависят от веб-сервера, и их применение позволяет осуществлять обмен данными в браузере без него и делать это быстро.

Спасибо за внимание!

А теперь приступайте к работе над собственным кодом!!! ❤️️

Читайте также:

Читайте нас в Telegram, VK и Яндекс.Дзен


Перевод статьи Dilantha Prasanjith: 4 Ways to Communicate Across Browser Tabs in Realtime

Предыдущая статьяUX-текст - как он формирует продукт?
Следующая статьяNo-code и сферы его применения