Связь между вкладками или окнами

176

Я искал способ связи между несколькими вкладками или окнами в браузере (в одном домене, а не в CORS), не оставляя следов. Было несколько решений:

  1. используя объект окна
  2. PostMessage
  3. печенье
  4. LocalStorage

Первое, вероятно, наихудшее решение - вам нужно открыть окно из текущего окна, и тогда вы сможете общаться только до тех пор, пока вы держите окна открытыми. Если вы перезагрузите страницу в любом из окон, вы, скорее всего, потеряли связь.

Второй подход, использующий postMessage, возможно, обеспечивает связь между источниками, но сталкивается с той же проблемой, что и первый подход. Вам нужно поддерживать объект окна.

Третий способ, используя куки, хранить некоторые данные в браузере, что может выглядеть как отправка сообщения всем окнам в одном домене, но проблема в том, что вы никогда не узнаете, читали ли все вкладки «сообщение» или нет ранее. убираться. Вы должны установить какое-то время ожидания для периодического чтения куки. Кроме того, вы ограничены максимальной длиной куки, которая составляет 4 КБ.

Четвертое решение, использующее localStorage, казалось, преодолевает ограничения файлов cookie, и его можно даже прослушивать, используя события. Как использовать это описано в принятом ответе.

Edit 2018: принятый ответ все еще работает, но для современных браузеров существует более новое решение - использовать BroadcastChannel. См. Другой ответ для простого примера, описывающего, как легко передавать сообщение между вкладками с помощью BroadcastChannel.

Томас М
источник
4
Почему этот вопрос был закрыт как «слишком широкий», когда почти одни и те же вопросы были открыты годами? Отправка сообщения на все открытые окна / вкладки с помощью JavaScript , stackoverflow.com/questions/2236828/… , Как вы общаетесь между 2 вкладками / окнами браузера? и еще несколько.
Дан Даскалеску
Я создал библиотеку поверх localStorage и sessionStorage для управления хранением данных на стороне клиента. Вы можете делать такие вещи, как storageManager.savePermanentData ('data', 'key'); или storageManager.saveSyncedSessionData («данные», «ключ»); в зависимости от того, как вы хотите, чтобы ваши данные вели себя. Это действительно упрощает процесс. Полный текст статьи здесь: ebenmonney.com/blog/...
adentum
2
Я создал библиотеку sysend.js несколько лет назад, в последней версии она использует BroadcastChannel. Вы можете протестировать библиотеку, дважды открыв эту страницу jcubic.pl/sysend.php , она также будет работать с другим источником, если вы предоставите прокси iframe.
jcubic
Считать ли поддомен одним и тем же источником? Я имею в виду, у меня есть ниже трех доменов, они общаются через широковещательный канал API? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Техас Патель

Ответы:

142

Редактировать 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 (), и он все еще ждал завершения цикла перед закрытием. Если пользователь убивает вкладку только между ними, у браузера не будет достаточно времени, чтобы сохранить сообщение на диск, поэтому такой подход кажется мне безопасным способом отправки сообщений без каких-либо следов. Комментарии приветствуются.

Томас М
источник
1
Вы можете игнорировать событие удаления перед вызовом JSON.parse ()?
Дандавис
1
имейте в виду ограничение данных события, включая уже существующие данные localStorage. было бы лучше / безопаснее использовать события хранения только для обмена сообщениями вместо доставки. например, когда вы получаете открытку с указанием забрать посылку в почтовом отделении ... кроме того, локальное хранилище отправляется на жесткий диск, поэтому оно может оставлять непреднамеренные кэши и влиять на журналы, что является еще одной причиной для рассмотрения другого транспортного механизма для фактические данные.
Дандавис
1
Некоторое время назад я сделал что-то связанное: danml.com/js/localstorageevents.js , у которого есть база источника событий и «локальное эхо», так что вы можете использовать EE для всего и везде.
Дандавис
7
Safari не поддерживает BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth
1
Просто
наперед
116

Для этого существует современный 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.

пользователь
источник
3
Подождите, как вы узнаете, откуда пришло сообщение? Это игнорирует сообщения, которые приходят из той же вкладки?
AturSams
2
@zehelvion: отправитель не получит его, например, согласно этому хорошему обзору . Кроме того, вы можете поместить в сообщение все, что захотите, в т.ч. некоторый идентификатор отправителя, если это необходимо.
СЗ
7
Есть хороший проект, который оборачивает эту функцию в кросс-браузерную библиотеку здесь: github.com/pubkey/broadcast-channel
james2doyle
Были ли какие-либо публичные сигналы от Safari о том, появится ли поддержка этого API в этом браузере?
Кейси
@AturSams Вы проверяете, что вы хотите MessageEvent.origin, MessageEvent.source или MessageEvent.ports. Как всегда, лучше всего начать с документации: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Стефан Михай Станеску
40

Для тех, кто ищет решение, не основанное на 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);
    }
}
Начо Колома
источник
1
Почему вы пропустили вызов removeItem?
Томас М
2
Я просто сосредоточился на различиях между jQuery и JavaScript.
Начо Колома
я всегда использую lib из-за полифилла и возможности не поддерживаемой функции!
Амин Рахими
20

Оформить заказ AcrossTabs - Простое общение между вкладками браузера разных источников. Он использует комбинацию postMessage и sessionStorage API, чтобы сделать общение намного проще и надежнее.


Существуют разные подходы, и у каждого есть свои преимущества и недостатки. Давайте обсудим каждый:

  1. LocalStorage

    Плюсы :

    1. Веб-хранилище можно рассматривать упрощенно как улучшение файлов cookie, что обеспечивает гораздо большую емкость хранилища. Если вы посмотрите на исходный код Mozilla, то увидите, что 5120 КБ ( 5 МБ, что соответствует 2,5 млн. Символов в Chrome) является размером хранилища по умолчанию для всего домена. Это дает вам значительно больше места для работы, чем обычный файл cookie размером 4 КБ.
    2. Данные не отправляются обратно на сервер для каждого HTTP-запроса (HTML, изображения, JavaScript, CSS и т. Д.), Что уменьшает объем трафика между клиентом и сервером.
    3. Данные, хранящиеся в localStorage, сохраняются до тех пор, пока не будут явно удалены. Внесенные изменения сохраняются и доступны для всех текущих и будущих посещений сайта.

    Минусы :

    1. Он работает по политике того же происхождения . Таким образом, сохраненные данные будут доступны только для одного источника.
  2. Печенье

    Плюсы:

    1. По сравнению с другими, нет ничего AFAIK.

    Минусы:

    1. Ограничение 4 КБ относится ко всему файлу cookie, включая имя, значение, дату истечения срока действия и т. Д. Для поддержки большинства браузеров сохраняйте имя менее 4000 байт, а общий размер куки - менее 4093 байт.
    2. Данные отправляются обратно на сервер для каждого HTTP-запроса (HTML, изображения, JavaScript, CSS и т. Д.), Что увеличивает объем трафика между клиентом и сервером.

      Обычно допускается следующее:

      • Всего 300 печенья
      • 4096 байт на файл cookie
      • 20 куки на домен
      • 81920 байт на домен (учитывая 20 файлов cookie с максимальным размером 4096 = 81920 байт.)
  3. sessionStorage

    Плюсы:

    1. Это похоже на localStorage.
    2. Изменения доступны только для каждого окна (или вкладки в браузерах, таких как Chrome и Firefox). Внесенные изменения сохраняются и доступны для текущей страницы, а также для будущих посещений сайта в том же окне. После закрытия окна хранилище удаляется.

    Минусы:

    1. Данные доступны только внутри окна / вкладки, в которой они были установлены.
    2. Данные не являются постоянными, т.е. они будут потеряны после закрытия окна / вкладки.
    3. Мол localStorage, тт работает на политику того же происхождения . Таким образом, сохраненные данные будут доступны только для одного источника.
  4. PostMessage

    Плюсы:

    1. Безопасно обеспечивает связь между источниками .
    2. В качестве точки данных реализация WebKit (используемая Safari и Chrome) в настоящее время не применяет никаких ограничений (кроме тех, которые накладываются из-за нехватки памяти).

    Минусы:

    1. Нужно открыть окно из текущего окна и тогда общаться можно только до тех пор, пока вы держите окна открытыми.
    2. Проблемы безопасности. Отправка строк через postMessage заключается в том, что вы будете выбирать другие события postMessage, опубликованные другими подключаемыми модулями JavaScript, поэтому обязательно реализуйтеtargetOriginпроверку исправности и данных для данных, передаваемых слушателю сообщений.
  5. Сочетание PostMessage + SessionStorage

    Использование postMessage для связи между несколькими вкладками и одновременное использование sessionStorage во всех вновь открытых вкладках / окнах для сохранения передаваемых данных. Данные будут сохраняться до тех пор, пока вкладки / окна остаются открытыми. Таким образом, даже если вкладка / окно открывания закрывается, открытые вкладки / окна будут иметь все данные даже после обновления.

Для этого я написал библиотеку JavaScript с именем AcrossTabs, которая использует API-интерфейс postMessage для обмена данными между вкладками / окнами разных источников и sessionStorage, чтобы сохранять открытость идентификаторов открытых вкладок / окон в течение всего времени их существования.

softvar
источник
Используя AcrossTabs, возможно ли открыть другой веб-сайт на другой вкладке и передать данные с него на родительскую вкладку? У меня будут данные аутентификации для другого сайта.
Мадхур Бхайя
1
Да, вы можете @MadhurBhaiya
softvar
Самым большим преимуществом cookie является то, что он позволяет использовать один и тот же домен для перекрестного происхождения, что обычно полезно, когда у вас есть набор источников, таких как «a.target.com», «b.target.com» и т. Д.
StarPinkER
7

Другой метод, который люди должны рассмотреть, это использование Shared Workers. Я знаю, что это передовая концепция, но вы можете создать ретранслятор на Shared Worker, который НАМНОГО быстрее, чем localalstorage, и не требует отношения между родительским / дочерним окном, если вы находитесь в одном источнике.

Смотрите мой ответ здесь для некоторого обсуждения, которое я сделал об этом.

datasedai
источник
7

Существует крошечный компонент с открытым исходным кодом для синхронизации / связи между вкладками / окнами одного и того же происхождения (отказ от ответственности - я один из авторов!), Основанный вокруг 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» терпят неудачу при подключениях через веб-сокет, когда события происходят почти одновременно

Alex
источник
6

Я создал библиотеку 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-файл на целевом домене).

Вот демо .

РЕДАКТИРОВАТЬ :

Новая версия также поддерживает междоменную связь, если вы включаете специальный proxy.htmlфайл в целевой домен и вызываете proxyфункцию из исходного домена:

sysend.proxy('https://target.com');

(proxy.html это очень простой HTML-файл, в котором есть только один скрипт-тег с библиотекой).

Если вы хотите двустороннюю связь, вы должны сделать то же самое для target.comдомена.

ПРИМЕЧАНИЕ . Если вы реализуете те же функции с помощью localStorage, в IE есть проблема. Событие хранилища отправляется в то же окно, которое вызвало событие, а для других браузеров оно вызывается только для других вкладок / окон.

jcubic
источник
2
Просто хотел дать тебе немного кудо за это. Приятное маленькое дополнение, которое простое и позволяет мне общаться между моими вкладками, чтобы программа предупреждения о выходе из системы не пинала людей. Хорошая работа. Я настоятельно рекомендую это, если вы хотите простое в использовании решение для обмена сообщениями.
BrownPony
4

Я создал модуль, который работает так же, как официальный Broadcastchannel, но имеет запасные варианты, основанные на localalstorage, indexeddb и unix-сокетах. Это гарантирует, что он всегда работает даже с Webworkers или NodeJS. Смотрите pubkey: BroadcastChannel

Публичных
источник
1

Я написал статью об этом в своем блоге: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- веб-приложение .

Используя созданную мной библиотеку, 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

Есть и другие удобные методы для обработки других сценариев, а также

adentum
источник
0

Это storageчасть разработки ответа Tomas M для Chrome. Мы должны добавить слушателя

window.addEventListener("storage", (e)=> { console.log(e) } );

Загрузка / сохранение элемента в хранилище не запускает это событие - мы ДОЛЖНЫ вызвать его вручную

window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME

и теперь все открытые вкладки получат событие

Камил Келчевски
источник