[Перевод] Общение скриптов из разных вкладок браузера

Мне захотелось наладить общение скриптов из разных вкладок браузера. Будущий API SharedWorker позволяет передавать данные между разными iframe и даже вкладками или окнами. В Chrome он работает давно, в Firefox — Firefox — недавно, а в IE и Safari его не видать. Но существует кроссбраузерная альтернатива, о которой мало кто знает. Давайте разбираться.Представьте, что на одной вкладке человек залогинился, затем открыл другую, и там разлогинился. На первой он вроде как залогинен, но когда он сделает там что-либо, ему выдадут ошибку. Хорошо было бы хотя бы показать ему диалог о том, что он разлогинился и ему надо войти ещё раз.Можно было бы использовать API WebSocket, но это черезчур сложно. Я начал искать другие решения. Первое — сохранить куки и localStorage, и проверять их периодически. Но это нагружало бы процессор достаточно бесполезной задачей — ведь выхода могло и не случиться вообще. Меня больше устроили бы варианты с long-polling (длинные запросы), Server-Sent Events или WebSockets. Удивительно, но в результате оказалось, что ответ лежал в области localStorage!

Знаете ли вы, что localStorage запускают события? Точнее, событие возникает, когда нечто добавляется, меняется или удаляется из хранилища. Это значит, что когда вы касаетесь localStorage в любой вкладке, все остальные могут узнать об этом. Достаточно прослушивать события в объекте window.

window.addEventListener ('storage', function (event) { console.log (event.key, event.newValue); }); У объекта event есть следующие свойства:

key — ключ, который трогали в localStoragenewValue — новое назначенное ему значениеoldValue — значение перед изменениемurl — URL страницы, на которой случилось изменение

Поэтому можно наладить общение между вкладками, просто задавая значения в 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 — один из самых простых интерфейсов. Однако и у него есть особенность — например Safari и QuotaExceededError, нет поддержки JSON и старых браузеров.Для этого я сделал модуль, предоставляющий локальное хранилище с упрощённым API, избавляющий вас от указанных особенностей, работающий с памятью, если вдруг поддержки localStorage нет, и позволяющий проще работать с событиями. Можно регистрировать и удалять слушателей этих событий для заданных ключей.

Вот схема работы с local-storage.

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

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

Наверно можно придумать и другие случаи, когда возможно использовать общение между вкладками. Пока SharedWorker не получил должного распространения, а подход WebSockets ненадёжен, если ваше приложение ориентируется в первую очередь на работу оффлайн.

© Habrahabr.ru