Мне захотелось наладить общение скриптов из разных вкладок браузера. Будущий 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