Обмен данными между вкладками с помощью localStorage
JavaScript

Обмен данными между вкладками с помощью localStorage

Оригинал: Cross-tab Communication, Nicolas Bevacqua

Новый SharedWorker API в HTML5 позволяет обмениваться данными между фреймами, вкладками браузера или отдельными окнами. Поддержка SharedWorker API в Chrome появилась несколько лет назад, в Firefox — недавно, но он не поддерживается ни одной версией Safari или IE. Однако существует малоизвестная альтернатива, которая может использоваться уже сегодня. Давайте рассмотрим ее.

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

Можно использовать для этого WebSocket API, но это явно перебор. Я искал низкоуровневую технологию, поэтому стал смотреть в сторону взаимодествия между вкладками. Первым вариантом, который мне попался, было использовование cookie или localStorage и периодическая проверка, залогинен ли пользователь или нет, с помощью setInterval. Мне не поравилось такое решение, т.к. оно тратит слишком много циклов процессора на проверку того, что возможно никогда и не наступит. В тот момент я бы охотнее использовал «comet» (также известный как long-polling), Server-Sent Events или WebSockets.

Я был удивлен, когда понял, что ответ все это время лежал у меня под носом, и это localStorage!

Вы знали, что localStorage порождает события1? А именно, вызываются события при добавлении, удалении или изменении элемента в другом контексте. По сути, это означает, что когда вы изменяете localStorage в одной вкладке, другие вкладки могут узнать об этом, слушая событие storage глобального объекта window2. Например, так:


window.addEventListener('storage', function (event) {
    console.log(event.key, event.newValue);
});

Объект event содержит несколько свойств:

СвойствоОписание
keyЗатронутый ключ в localStorage
newValueЗначение, связанное с этим ключом в данный момент
oldValueЗначение до модицикации
urlURL страницы, на которой произошло изменение

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


var loggedOn;

/* TODO: вызвать этот метод, когда пользователь сменился или разлогинился*/
logonChanged();

window.addEventListener('storage', updateLogon);
window.addEventListener('focus', checkLogon);

function getUsernameOrNull () {
  /* TODO: если пользователь авторизован - возвращает его логин */
}

function logonChanged () {
    var uname = getUsernameOrNull();
    loggedOn = uname;
    localStorage.setItem('logged-on', uname);
}

function updateLogon (event) {
    if (event.key === 'logged-on') {
        loggedOn = event.newValue;
    }
}

function checkLogon () {
    var uname = getUsernameOrNull();
    if (uname !== loggedOn) {
        location.reload();
    }
}

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

Определенно, мы могли бы улучшить этот код, но уже сейчас он отлично служит своей цели. Обратите внимание, что он прекрасно работает и наоборот: когда пользователь авторизуется на одной из вкладок и перейдет на другую, на которой он до этого не был авторизован — приведенный код определит это и перезагрузит страницу, а серверная сторона уже отдаст страницу авторизованного пользователя (опять же, я надеюсь).

Более простой API

Возможно, localStorage API — один из самых простых в использовании API и  довольно хорошо поддерживается браузерами. Однако есть некоторые особенности, такие как, например, исключение QuotaExceededError при попытке вызвать метод setItem в приватном режиме в Safari, отсутствие поддержки JSON из коробки или старые браузеры, не поддерживающие localStorage.

Поэтому я разработал local-storage  — модуль, который предоставляет более простой API для доступа к localStorage и учитывает описанные особенности, в частности, превращается в in-memory хранилище, если localStorage не поддерживается, а также делает более простой работу с событием storage, позволяет навешивать обработчики для определенных ключей.

Методы API local-storage@1.3.1 описаны ниже:

  • ls(key, value?) получает или устанавливает значение по ключу
  • ls.get(key) получает значение по ключу
  • ls.set(key, value) устанавливает значение по ключу
  • ls.remove(key) удаляет ключ
  • ls.on(key, fn(value, old, url)) устанавливает обработчик события изменения значения по ключу key
  • ls.off(key, fn) удаляет обработчик события, до этого зарегистрированного с помощью ls.on

Стоит также отметить, что local-storage регистрирует один обработчик события storage и отслеживает каждый ключ, вместо того, чтобы регистрировать несколько обработчиков событий.

Мне было бы интересно узнать о других случаях применения такого взаимодействия между вкладками! Особенно полезным такой подод кажется при набирающей популярность offline-first разработке, поскольку реализация полной поддержки SharedWorker может занять определенное время, а WebSockets не подходят для offline-взаимодействия.

1 SessionStorage также порождает событиe storage — http://www.w3.org/TR/webstorage/#the-storage-event (прим. переводчика).

2 Имеются в виду вкладки, на которых открыты страницы одного домена (прим. переводчика).

3 Есть тонкий момент, связанный с IE. Согласно спецификации, событие storage возникает во всех вкладках, КРОМЕ той, на которой произошло изменение localStorage. Такое поведение реализовано в Chrome, Firefox и Safari. В IE событие storage возникает ВО ВСЕХ вкладках — https://connect.microsoft.com/IE/feedback/details/774798/localstorage-event-fired-in-source-window (прим. переводчика).

Рассылка
Подпишитесь на рассылку и получайте дайджест новостей и статей.
Никакого спама!
Подписаться