Связь между вкладками или окнами
Я искал способ связи между несколькими вкладками или окнами в браузере (в одном домене, а не в CORS), не оставляя следов. Было несколько решений:
Первое, вероятно, худшее решение - вам нужно открыть окно из текущего окна, и тогда вы сможете общаться только при условии, что окна открыты. Если вы перезагрузите страницу в любом из окон, вы, скорее всего, потеряли связь.
Второй подход, использующий postMessage, возможно, обеспечивает связь между источниками, но сталкивается с той же проблемой, что и первый подход. Вам нужно поддерживать объект окна.
Третий способ, используя куки, хранить некоторые данные в браузере, что может выглядеть как отправка сообщения всем окнам в одном домене, но проблема в том, что вы никогда не узнаете, читали ли все вкладки "сообщение" или нет ранее. убираться. Вы должны установить какое-то время ожидания для периодического чтения куки. Кроме того, вы ограничены максимальной длиной куки, которая составляет 4 КБ.
Четвертое решение, использующее localStorage, казалось, преодолевает ограничения файлов cookie, и его можно даже прослушивать, используя события. Как использовать это описано в принятом ответе.
Edit 2018: принятый ответ все еще работает, но для современных браузеров существует более новое решение - использовать BroadcastChannel. Смотрите другой ответ для простого примера, описывающего, как легко передавать сообщения между вкладками с помощью BroadcastChannel.
8 ответов
Редактировать 2018: Вы можете лучше использовать BroadcastChannel для этой цели, см. Другие ответы ниже. Тем не менее, если вы все еще предпочитаете использовать localalstorage для связи между вкладками, сделайте это следующим образом:
Чтобы получать уведомления, когда вкладка отправляет сообщение на другие вкладки, вам просто нужно привязать событие "хранилище". Во всех вкладках сделайте это:
$(window).on('storage', message_receive);
Функция message_receive
будет вызываться каждый раз, когда вы устанавливаете любое значение localStorage на любой другой вкладке. Слушатель событий также содержит данные, недавно установленные в localStorage, поэтому вам даже не нужно анализировать сам объект localStorage. Это очень удобно, потому что вы можете сбросить значение сразу после его установки, чтобы эффективно очистить любые следы. Вот функции для обмена сообщениями:
// use local storage for messaging. Set message in local storage and clear it right away
// This is a safe way how to communicate with other tabs while not leaving any traces
//
function message_broadcast(message)
{
localStorage.setItem('message',JSON.stringify(message));
localStorage.removeItem('message');
}
// receive message
//
function message_receive(ev)
{
if (ev.originalEvent.key!='message') return; // ignore other keys
var message=JSON.parse(ev.originalEvent.newValue);
if (!message) return; // ignore empty msg or msg reset
// here you act on messages.
// you can send objects like { 'command': 'doit', 'data': 'abcd' }
if (message.command == 'doit') alert(message.data);
// etc.
}
Так что теперь, когда ваши вкладки связаны с событием onstorage и реализованы эти две функции, вы можете просто передать сообщение другим вызовам вкладок, например:
message_broadcast({'command':'reset'})
Помните, что отправка одного и того же сообщения дважды будет распространяться только один раз, поэтому, если вам нужно повторить сообщения, добавьте к ним уникальный идентификатор, например
message_broadcast({'command':'reset', 'uid': (new Date).getTime()+Math.random()})
Также помните, что текущая вкладка, которая передает сообщение, фактически не получает его, только другие вкладки или окна в том же домене.
Вы можете спросить, что происходит, если пользователь загружает другую веб-страницу или закрывает свою вкладку сразу после вызова setItem() перед removeItem(). Ну, из моего собственного тестирования браузер приостанавливает разгрузку, пока вся функция message_broadcast()
закончен. Я проверил, чтобы ввести int очень длинный цикл for(), и он все еще ждал завершения цикла перед закрытием. Если пользователь убивает вкладку только между ними, у браузера не будет достаточно времени, чтобы сохранить сообщение на диск, поэтому такой подход кажется мне безопасным способом отправки сообщений без каких-либо следов. Комментарии приветствуются.
Для этого существует современный API - Broadcast Channel
Это так же просто, как:
var bc = new BroadcastChannel('test_channel');
bc.postMessage('This is a test message.'); /* send */
bc.onmessage = function (ev) { console.log(ev); } /* receive */
Нет необходимости, чтобы сообщение было просто DOMString, любой тип объекта может быть отправлен.
Вероятно, кроме чистоты API, главное преимущество этого API - отсутствие строковой классификации объектов.
В настоящее время поддерживается только в Chrome и Firefox, но вы можете найти polyfill, который использует localStorage.
Для тех, кто ищет решение, не основанное на jQuery, это простая версия JavaScript, предложенная Томасом М:
window.addEventListener("storage", message_receive);
function message_broadcast(message) {
localStorage.setItem('message',JSON.stringify(message));
}
function message_receive(ev) {
if (ev.key == 'message') {
var message=JSON.parse(ev.newValue);
}
}
Оформить заказ AcrossTabs - Простое общение между вкладками браузера разных источников. Он использует комбинацию postMessage и sessionStorage API, чтобы сделать общение намного проще и надежнее.
Существуют разные подходы, и у каждого есть свои преимущества и недостатки. Давайте обсудим каждый:
Плюсы:
- Веб-хранилище можно рассматривать упрощенно как улучшение файлов cookie, обеспечивая гораздо большую емкость хранилища. Если вы посмотрите на исходный код Mozilla, то увидите, что 5120 КБ (5 МБ, что соответствует 2,5 миллионам символов в Chrome) - это размер хранилища по умолчанию для всего домена. Это дает вам значительно больше места для работы, чем обычный файл cookie размером 4 КБ.
- Данные не отправляются обратно на сервер для каждого HTTP-запроса (HTML, изображения, JavaScript, CSS и т. Д.), Что уменьшает объем трафика между клиентом и сервером.
- Данные, хранящиеся в localStorage, сохраняются до тех пор, пока не будут явно удалены. Внесенные изменения сохраняются и доступны для всех текущих и будущих посещений сайта.
Минусы:
- Он работает по той же политике происхождения. Таким образом, сохраненные данные будут доступны только для одного источника.
Плюсы:
- По сравнению с другими, нет ничего AFAIK.
Минусы:
- Ограничение в 4 КБ относится ко всему файлу cookie, включая имя, значение, дату истечения срока действия и т. Д. Для поддержки большинства браузеров имя файла должно быть не более 4000 байт, а общий размер файла cookie - менее 4093 байт.
Данные отправляются обратно на сервер для каждого HTTP-запроса (HTML, изображения, JavaScript, CSS и т. Д.), Что увеличивает объем трафика между клиентом и сервером.
Обычно допускается следующее:
- Всего300 печенья
- 4096 байт на файл cookie
- 20 куки на домен
- 81920 байт на домен (учитывая 20 файлов cookie с максимальным размером 4096 = 81920 байт.)
Плюсы:
- Это похоже на
localStorage
, - Изменения доступны только для каждого окна (или вкладки в браузерах, таких как Chrome и Firefox). Внесенные изменения сохраняются и доступны для текущей страницы, а также для будущих посещений сайта в том же окне. После закрытия окна хранилище удаляется.
Минусы:
- Данные доступны только внутри окна / вкладки, в которых они были заданы.
- Данные не являются постоянными, т.е. они будут потеряны после закрытия окна / вкладки.
- подобно
localStorage
, тт работает на политику того же происхождения. Таким образом, сохраненные данные будут доступны только для одного источника.
- Это похоже на
Плюсы:
- Безопасно обеспечивает связь между источниками.
- В качестве точки данных реализация WebKit (используемая Safari и Chrome) в настоящее время не применяет никаких ограничений (кроме тех, которые накладываются из-за нехватки памяти).
Минусы:
- Нужно открыть окно из текущего окна и тогда общаться можно только до тех пор, пока вы держите окна открытыми.
- Проблемы безопасности - Отправка строк через postMessage заключается в том, что вы будете выбирать другие события postMessage, опубликованные другими плагинами JavaScript, поэтому обязательно реализуйте
targetOrigin
и проверка работоспособности данных, передаваемых слушателю сообщений.
Сочетание PostMessage + SessionStorage
Использование postMessage для связи между несколькими вкладками и одновременное использование sessionStorage во всех вновь открытых вкладках / окнах для сохранения передаваемых данных. Данные будут сохраняться до тех пор, пока вкладки / окна остаются открытыми. Таким образом, даже если вкладка / окно открывания закрывается, открытые вкладки / окна будут иметь все данные даже после обновления.
Я написал для этого библиотеку JavaScript с именем AcrossTabs, которая использует API postMessage для связи между вкладками / окнами с разными источниками и сессией сеанса для сохранения идентификатора открытых вкладок / окон в течение всего времени их существования.
Я создал библиотеку sysend.js, она очень маленькая, вы можете проверить ее исходный код. У библиотеки нет внешних зависимостей.
Вы можете использовать его для связи между вкладками / окнами в одном браузере и домене. Библиотека использует BroadcastChannel, если поддерживается, или событие хранения из localStorage.
API очень прост:
sysend.on('foo', function(message) {
console.log(message);
});
sysend.broadcast('foo', {message: 'Hello'});
sysend.broadcast('foo', "hello");
sysend.broadcast('foo'); // empty notification
когда ваш браузер поддерживает BroadcastChannel, он отправляет литеральный объект, а если нет, то сначала сериализуется в JSON, а на другом конце десериализуется.
Последние версии также имеют вспомогательный API для создания прокси для междоменной связи. (требуется один HTML-файл на целевом домене).
Вот демо.
ПРИМЕЧАНИЕ. Если вы реализуете ту же функциональность с помощью localStorage, в IE есть проблема. Событие хранилища отправляется в то же окно, которое вызвало событие, а для других браузеров оно вызывается только для других вкладок / окон.
Другой метод, который люди должны рассмотреть, это использование Shared Workers. Я знаю, что это передовая концепция, но вы можете создать ретранслятор на Shared Worker, который НАМНОГО быстрее, чем localalstorage, и не требует отношения между родительским / дочерним окном, если вы находитесь в одном источнике.
Смотрите мой ответ здесь для некоторого обсуждения, которое я сделал об этом.
Есть небольшой компонент с открытым исходным кодом для синхронизации / связи между вкладками / окнами одного и того же происхождения (отказ от ответственности - я один из авторов!), Основанный на localStorage
,
TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);
TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
DoSomething();
});
TabUtils.CallOnce("lockname", function () {
alert("I run only once across multiple tabs");
});
https://github.com/jitbit/TabUtils
PS Я позволил себе порекомендовать его здесь, так как большинство компонентов "lock/mutex/sync" терпят неудачу при подключениях через веб-сокет, когда события происходят почти одновременно
Я создал модуль, который работает так же, как официальный Broadcastchannel, но имеет запасные варианты, основанные на localalstorage, indexeddb и unix-сокетах. Это гарантирует, что он всегда работает даже с Webworkers или NodeJS. Смотрите https://github.com/pubkey/broadcast-channel
Это развитие storage
часть ответа Томаса М. для Chrome. Мы должны добавить слушателя
window.addEventListener("storage", (e)=> { console.log(e) } );
Загружать / сохранять элемент в хранилище не запускать это событие - мы ДОЛЖНЫ запускать его вручную,
window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME
и теперь все открытые вкладки получат событие
Я написал статью об этом в своем блоге: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a-web-application.
Используя библиотеку, которую я создал storageManager
Вы можете достичь этого следующим образом:
storageManager.savePermanentData('data', 'key'): //saves permanent data
storageManager.saveSyncedSessionData('data', 'key'); //saves session data to all opened tabs
storageManager.saveSessionData('data', 'key'); //saves session data to current tab only
storageManager.getData('key'); //retrieves data
Есть и другие удобные методы для обработки других сценариев.