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

в 22:56, , рубрики: javascript, sharedworker, websockets, браузеры, вкладки

Мне захотелось наладить общение скриптов из разных вкладок браузера. Будущий 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 – ключ, который трогали в localStorage
newValue – новое назначенное ему значение
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)) слушает изменения в других вкладках, запускает fn
ls.off(key, fn) удаляет слушателя, который был зарегистрирован через ls.on

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

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

Автор: SLY_G

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js