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

Original author: Nicolas Bevacqua
  • Translation
Мне захотелось наладить общение скриптов из разных вкладок браузера. Будущий API SharedWorker позволяет передавать данные между разными iframe и даже вкладками или окнами. В Chrome он работает давно, в 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 ненадёжен, если ваше приложение ориентируется в первую очередь на работу оффлайн.
Ads
AdBlock has stolen the banner, but banners are not teeth — they will be back

More

Comments 15

    –17
    В Chrome он работает давно, в Firefox — Firefox – недавно, а в IE и Safari его не видать.

    А в опере? Вышеперечисленные браузеры не интересуют.
      +15
      А опера не интересует практически никого.
        0
        Ээээ, вы из параллельной вселенной пишите или как? IE самый первый (начиная IE5!), кто дал подобную возможность, называлась она UserData, с IE8 появилась поддержка LocalStorage. FF с 3.5 (30 June 2009), а Safari c 4 (11 июня 2008).

        Так что Хром, был «последний», кто это внедрил.
          –2
          Во-первых — мимо.
          Во-вторых — это перевод.
            –1
            Мимо что? И причем тут перевод, я обращался к qwerty135.
              0
              qwerty135 первым предложением процитировал статью (но кто бы их читал..), а не утверждал что-либо.
                0
                А, ну тогда я неправ, там про SharedWorker, а я про LocalStorage, ай-ай, как себе минус поставить?
        +1
        Думаю стоит указать эту ссылку github.com/bevacqua/local-storage из оригинала.
          0
          Библиотека ни о чем, для корректной работы между влкадками через localStorage, нужна как минимум очередь событий, чтобы при изменении ключа в цикле не пропустить события. Опять же, ничего не сказано как работать между вкладками разных доменов и других нюансах.

          > Наверно можно придумать и другие случаи, когда возможно использовать общение между вкладками.

          Можно, например реализация Master/Slave для WebSocket, чтобы снизить нагрузку на сервер.
            +2
            Вот за location.reload() надо руки, простите, укорачивать. Что если в соседней вкладке статья писалась? Я понимаю что это лишь пример, но пример этот вредный.
              0
              Совсем уж не однозначно. Если я разлогинился на сайте, то хочу что бы это произошло и на других вкладках: разлогинился раз, ушел, и никто не увидет личную информацию на других вкладках. А что бы статья или любая другая информация оставалась, то нужны черновики и автосохранение. Хотя последнее нужно и во всех других случаях.
                0
                Да вы правы — черновики и автосохранение решают проблему, если они есть. И я не говорил, что сам процесс разлогина вреден. Но в статье не помешало бы в псевдокод добавить функцию checkThatAllUserDataSaved() хотя бы. Да и разлогинивание не обязательно делать с помощью location.reload()
              +1
              Писал об подобном на хабре год назад: вот здесь.
                0
                И я тоже писал. Замечу что IE до сих по работает некорректно с событием storage. Насчет сафари с его приватными вкладками — похоже на гипотезу, потому что я лично подтвердить её не смог. Возможно старый safari так делал, который на Windows сейчас прекратили обновлять.
                0
                Как я понимаю, при каждой записи в localStorage мы будем слышать «похрустывание» винта?
                Т.е. если вкладки будут «общаться» через события изменения localStorage достаточно активно — треск винта будет очень даже заметен?

                Only users with full accounts can post comments. Log in, please.