Давным-давно в далёкой галактике появилась задача по синхронизации вкладок браузера для веб-плеера, наподобие VK: нужно было организовать обмен данными между вкладками, отслеживать их количество и назначать задачи некоторым из них. Всю реализацию нужно было выполнить на клиенте. Информации собрано много, и набралось на целую статью.
Ниже опишу различные способы решения подобных задач.
Рассмотрим наиболее популярные способы синхронизации вкладок браузера в порядке увеличения сложности.
Local Storage
localStorage – локальное хранилище, свойство объекта window, позволяет получить доступ к локальному Storage объекту. В нем можно хранить данные между сессиями пользователя. Есть аналогичное свойство – sessionStorage, но оно хранит данные только в течение сессии страницы.
Данные в storage добавляются с помощью метода setItem.
localStorage.setItem('key', 'value');
Событие storage идеально подходит для синхронизации данных между вкладками, оно генерируется при изменении значения элемента localStorage или sessionStorage.
window.addEventListener('storage', function(event) { console.log(event.key); });
Событие не работает на вкладке, которая вносит изменения, но срабатывает на остальных вкладках домена в браузере.

Генерация события storage
Браузеры имеют различный уровень объема хранилищ для localStorage и sessionStorage:
- Chrome, FireFox и Opera ~ 5 МБ.
- IE ~ 4,8 МБ.
- iOS Safari, OS X Safari ~ 2,5 МБ.
- Android ~ 5 МБ.
Из недостатков можно отметить – объем хранилища браузера, а при его переполнении новый объект не будет записан.
Метод работает во всех браузерах, кроме Opera mini.
Post Message
postMessage — это метод, который позволяет безопасно отправлять кросс-доменные запросы, то есть общаться друг с другом окнам и iframes с разных доменов.
Он очень удобен для взаимодействия внешних виджетов и сервисов, подключенных через iframe с основной страницы.
Передача сообщения:
const win = window.frames.target; win.postMessage('send message', 'http://javascript.ru');
Передаваемые данные могут быть любым объектом, который поддерживает клонирование (строка, объект, массив, Map, Date …). Но IE поддерживает только строки.
Url указывает, что получать сообщения можно только окнам с данного источника.
Чтобы получать сообщения, окно должно подписаться на событие onmessage.
window.addEventListener('message', function(event) { if (event.origin != 'http://javascript.ru') { return; } console.log(event.data); });
Любое окно может получить доступ к этому методу, для отправки ему сообщения независимо от местоположения документа в окне. Поэтому обязательно нужно проверять origin.
В браузере IE интерфейс postMessage работает только с iframes и не работает между вкладками и окнами.
Broadcast Channel API
Broadcast Channel API обеспечивает простую связь между контекстом просмотра (окна, вкладки). Объект BroadcastChannel создает общий канал, который позволяет получить любое сообщение, отправленное в него. Вкладки, окна, iframes могут подписаться на канал и установить с ним связь.
const bc = new BroadcastChannel('test_channel');
Метод postMessage публикует сообщение в канале. Аргументом является тип, который поддерживает клонирование.
bc.postMessage('This is a test message.');
Когда сообщение публикуется, message событие будет отправлено каждому объекту, подключенному к этому каналу.
bc.addEventListener('message', function (e) { console.log(e); })

Публикация сообщения в канале для разных контекстов.
API довольно простое, его можно рассматривать как простую шину сообщений. Но у способа есть весомый недостаток: нет поддержки Safari и IE.
На первый взгляд можно найти несколько похожих методов передачи данных (например MessageChannel, WebSocket), но каждый из них служит определенной цели — их сравнение.
Web Workers
Это механизм, который позволяет скрипту выполняться в фоновом потоке, который отделен от основного потока веб-приложения. Он реализован с использованием js-файлов, которые включаются в страницу с применением асинхронного HTTP-запроса.
Воркеры отлично подходят для того, чтобы выполнять тяжелые вычислительные операции, не замедляя работу пользовательского интерфейса.
Но с синхронизацией помочь могут только два вида воркеров.
Shared Worker
Это особый вид воркера, к которому можно получить доступ из нескольких контекстов браузера. Напишем общий js-файл для вкладок, например shared-worker.js.
const worker = new SharedWorker('shared-worker.js');
Каждая вкладка может связываться с воркером через worker.port. Скрипт воркера также имеет доступ к своим портам. Каждый раз, когда вкладка подключается к воркеру, в сценарии запускается событие connect.
// shared-worker.js const connections = []; onconnect = function(e) { const port = e.ports[0]; connections.push(port); };
Метод postMessage создан для отправки данных вкладки на общий воркер.
worker.port.postMessage('test message');
Получить данные воркера можно с помощью события message.
worker.port.onmessage = function (e) { console.log(e.data); };
В SharedWorker API есть событие connect, но нет события disconnect, и поэтому данные не смогут самоочищаться в закрытых вкладках — они будут продолжать считаться открытыми. Это не приведет к ошибкам, но можно считать это недоработкой или фичей API.
Работает только в Chrome и FF.
Service Worker
Это событийно-управляемый воркер, который может контролировать, перехватывать и модифицировать сетевые запросы и кешировать их.
Регистрация воркера:
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('service-worker.js') .then(function() { return navigator.serviceWorker.ready; }) .catch(function(error) { console.error('registration error : ', error); }); }
С помощью события message вкладки могут получить данные из js-файла воркера, а функция syncTabState используется для обработки сообщения.
self.addEventListener('message', function(e){ const data = e.data; const tabId = e.source.id self.syncTabState(data, tabId); });
Функция sendTabState создана для отправки сообщений вкладкам.
self.sendTabState = function(client, data){ client.postMessage(data); }
Подробное использование и множество примеров тут.
У всех веб-воркеров нет доступа к объектам window и document.
Service worker не работает в IE и Opera mini.
Библиотеки синхронизации
Это способ для тех, кто не хочет велосипедить и готов рассмотреть уже имеющиеся решения.
- tabs-router, использует Сookie и Local Storage;
- Hermes, использует SharedWorker;
- Visibility, использует Page Visibility API;
- Duel, использует Local Storage и WebSocket;
- __SE__, использует Worker и localStorage;
Их недостаток в том, что библиотеки в основном универсальные, поэтому не всегда подходят для узких решений.
Итог
Чтобы подвести окончательные итоги, сравним методы по поддержке браузерами наглядно.

Используйте LocalStorage, BroadcastChannel и PostMessage для простых случаев, когда вам нужно отправить сообщение потенциально нескольким окнам/вкладкам или iframes.
Для управления блокировками совместного состояния и совместных файлов наиболее подходящим решением является Shared Workers и Service Worker.
А для задачи с веб-плеером был выбран LocalStorage, так как есть поддержка IE.
Надеюсь, что статья помогла вам в выборе подходящего способа синхронизации.
Благодарю команду Афиши за помощь и поддержку!
Используемые статьи:
- Messaging Between Tabs Using Service Worker
- Sending data across different browser tabs
- BroadcastChannel API: A Message Bus for the Web
- How JavaScript works: The building blocks of Web Workers + 5 cases when you should use them
- Задача коммуникации между вкладками и выявления активной вкладки
- Библиотека для обмена событиями, данными и задачами между вкладками браузера
ссылка на оригинал статьи https://habr.com/post/422545/
Добавить комментарий