Komunikacja między kartami lub oknami

176

Szukałem sposobu, jak komunikować się między wieloma kartami lub oknami w przeglądarce (w tej samej domenie, a nie CORS) bez pozostawiania śladów. Rozwiązań było kilka:

  1. za pomocą obiektu okna
  2. Wyślij wiadomość
  3. ciasteczka
  4. Lokalny magazyn

Pierwsze jest prawdopodobnie najgorszym rozwiązaniem - musisz otworzyć okno z bieżącego okna, a wtedy możesz komunikować się tylko wtedy, gdy masz otwarte okna. Jeśli ponownie załadujesz stronę w którymkolwiek z okien, najprawdopodobniej stracisz komunikację.

Drugie podejście, wykorzystujące postMessage, prawdopodobnie umożliwia komunikację między źródłami, ale występuje ten sam problem, co pierwsze podejście. Musisz zachować obiekt okna.

Trzeci sposób, korzystając z plików cookie, przechowują w przeglądarce pewne dane, które w efekcie mogą wyglądać jak wysyłanie wiadomości do wszystkich okien w tej samej domenie, ale problem polega na tym, że nigdy nie wiadomo, czy wszystkie zakładki przeczytały już „wiadomość”, czy nie wcześniej sprzątanie. Musisz wprowadzić jakiś limit czasu, aby okresowo odczytywać plik cookie. Ponadto jesteś ograniczony maksymalną długością pliku cookie, która wynosi 4KB.

Czwarte rozwiązanie, wykorzystujące localStorage, wydawało się przezwyciężać ograniczenia plików cookie, a nawet można słuchać za pomocą zdarzeń. Sposób korzystania z niego opisano w zaakceptowanej odpowiedzi.

Edytuj 2018: zaakceptowana odpowiedź nadal działa, ale jest nowsze rozwiązanie dla nowoczesnych przeglądarek, aby używać BroadcastChannel. Zobacz drugą odpowiedź, aby zapoznać się z prostym przykładem opisującym, jak łatwo przesyłać wiadomości między kartami przy użyciu BroadcastChannel.

Tomas M.
źródło
4
Dlaczego to pytanie zostało zamknięte jako „zbyt szerokie”, skoro te same pytania były otwarte od lat? Wysyłanie wiadomości do wszystkich otwartych okien / kart za pomocą JavaScript , stackoverflow.com/questions/2236828/… , Jak komunikujesz się między 2 kartami / oknami przeglądarki? i jeszcze kilka.
Dan Dascalescu,
Utworzyłem bibliotekę na localStorage i sessionStorage, aby zarządzać przechowywaniem danych po stronie klienta. Możesz robić takie rzeczy jak storageManager.savePermanentData ('data', 'key'); lub storageManager.saveSyncedSessionData ('data', 'key'); na podstawie tego, jak chcesz, aby Twoje dane się zachowywały. To naprawdę upraszcza proces. Cały
adentum
2
Kilka lat temu stworzyłem bibliotekę sysend.js , w najnowszej wersji korzysta z BroadcastChannel. Możesz przetestować bibliotekę otwierając dwukrotnie tę stronę jcubic.pl/sysend.php , działa ona również z innym pochodzeniem, jeśli podasz proxy iframe.
jcubic
Czy uważam subdomenę za to samo źródło? Mam na myśli, że mam poniżej trzech domen, czy komunikują się one przez interfejs API kanału rozgłoszeniowego? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel

Odpowiedzi:

142

Edycja 2018: W tym celu możesz lepiej użyć BroadcastChannel, zobacz inne odpowiedzi poniżej. Jeśli jednak nadal wolisz używać magazynu lokalnego do komunikacji między kartami, zrób to w ten sposób:

Aby otrzymać powiadomienie, gdy karta wysyła wiadomość do innych zakładek, wystarczy powiązać zdarzenie „przechowywanie”. Na wszystkich kartach zrób to:

$(window).on('storage', message_receive);

Funkcja message_receivebędzie wywoływana za każdym razem, gdy ustawisz jakąkolwiek wartość localStorage na dowolnej innej karcie. Detektor zdarzeń zawiera również dane nowo ustawione na localStorage, więc nie musisz nawet analizować samego obiektu localStorage. Jest to bardzo przydatne, ponieważ można zresetować wartość zaraz po jej ustawieniu, aby skutecznie usunąć wszelkie ślady. Oto funkcje przesyłania wiadomości:

// 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.
}

Więc teraz, gdy twoje karty połączą się ze zdarzeniem onstorage i masz zaimplementowane te dwie funkcje, możesz po prostu rozesłać wiadomość do innych kart, wywołując na przykład:

message_broadcast({'command':'reset'})

Pamiętaj, że dwukrotne wysłanie dokładnie tej samej wiadomości zostanie propagowane tylko raz, więc jeśli chcesz powtarzać wiadomości, dodaj do nich unikalny identyfikator, np.

message_broadcast({'command':'reset', 'uid': (new Date).getTime()+Math.random()})

Pamiętaj też, że bieżąca karta, która emituje wiadomość, w rzeczywistości jej nie odbiera, tylko inne karty lub okna w tej samej domenie.

Możesz zapytać, co się stanie, jeśli użytkownik załaduje inną stronę internetową lub zamknie swoją kartę tuż po wywołaniu setItem () przed removeItem (). Cóż, z moich własnych testów przeglądarka wstrzymuje wyładowywanie do czasu zakończenia całej funkcji message_broadcast(). Testowałem, aby umieścić tam bardzo długi cykl for () i nadal czekałem na zakończenie cyklu przed zamknięciem. Jeśli użytkownik zabije kartę w międzyczasie, przeglądarka nie będzie miała wystarczająco dużo czasu, aby zapisać wiadomość na dysku, dlatego takie podejście wydaje mi się bezpiecznym sposobem wysyłania wiadomości bez żadnych śladów. Komentarze mile widziane.

Tomas M.
źródło
1
czy możesz zignorować zdarzenie remove przed wywołaniem JSON.parse ()?
dandavis
1
pamiętaj o limicie danych dotyczących zdarzeń, w tym wcześniej istniejących danych localStorage. może być lepiej / bezpieczniej używać zdarzeń przechowywania tylko do przesyłania wiadomości zamiast do wysyłania. jak wtedy, gdy dostaniesz pocztówkę z poleceniem odebrania paczki na poczcie ... również, lokalna pamięć trafia na dysk twardy, więc może pozostawić niezamierzone pamięci podręczne i wpłynąć na dzienniki, co jest kolejnym powodem, aby rozważyć inny mechanizm transportu dla aktualna data.
dandavis
1
Zrobiłem coś związanego jakiś czas temu: danml.com/js/localstorageevents.js , że istnieje baza emiterów wydarzeń i „lokalne echo”, dzięki czemu możesz używać EE do wszystkiego, wszędzie.
dandavis
7
Safari nie obsługuje BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth
1
Uwaga
116

Do tego celu służy nowoczesne API - Broadcast Channel

To jest tak proste, jak:

var bc = new BroadcastChannel('test_channel');

bc.postMessage('This is a test message.'); /* send */

bc.onmessage = function (ev) { console.log(ev); } /* receive */

Nie ma potrzeby, aby wiadomość była tylko DOMStringiem, można wysłać dowolny rodzaj obiektu.

Prawdopodobnie poza czystością API jest to główna zaleta tego API - brak ciągnienia obiektów.

Obecnie obsługiwane tylko w Chrome i Firefox, ale możesz znaleźć polyfill, który używa localStorage.

użytkownik
źródło
3
Czekaj, skąd wiesz, skąd pochodzi wiadomość? Czy ignoruje wiadomości pochodzące z tej samej karty?
AturSams
2
@zehelvion: Nadawca tego nie otrzyma, według np. tego fajnego przeglądu . Poza tym możesz umieścić w wiadomości co tylko zechcesz, m.in. w razie potrzeby jakiś identyfikator nadawcy.
Sz.
7
Jest fajny projekt, który podsumowuje tę funkcję w bibliotece między przeglądarkami tutaj: github.com/pubkey/broadcast-channel
james2doyle
Czy były jakieś publiczne sygnały od Safari o tym, czy obsługa tego interfejsu API wyląduje w tej przeglądarce?
Casey,
@AturSams Sprawdzasz, czy MessageEvent.origin, MessageEvent.source lub MessageEvent.ports są tym, czego chcesz. Jak zawsze dokumentacja jest najlepszym miejscem do rozpoczęcia: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Stefan Mihai Stanescu
40

Dla poszukujących rozwiązania nie opartego na jQuery, oto prosta wersja JavaScript rozwiązania dostarczonego przez Thomasa M.

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);
    }
}
Nacho Coloma
źródło
1
Dlaczego pominąłeś wywołanie removeItem?
Tomas M
2
Skupiłem się tylko na różnicach między jQuery i JavaScript.
Nacho Coloma
Zawsze używam biblioteki ze względu na polyfill i możliwość nieobsługiwanej funkcji!
Amin Rahimi
20

Checkout AcrossTabs - Łatwa komunikacja między kartami przeglądarki z różnych źródeł. Używa połączenia API postMessage i sessionStorage, aby komunikacja była znacznie łatwiejsza i niezawodna.


Istnieją różne podejścia, a każde z nich ma swoje zalety i wady. Porozmawiajmy o każdym:

  1. Lokalny magazyn

    Plusy :

    1. Przechowywanie stron internetowych można w uproszczeniu postrzegać jako ulepszenie plików cookie, zapewniające znacznie większą pojemność. Jeśli spojrzysz na kod źródłowy Mozilli, zobaczymy, że 5120 KB ( 5 MB, co równa się 2,5 miliona znaków w Chrome) to domyślny rozmiar pamięci dla całej domeny. Daje to znacznie więcej miejsca do pracy niż typowy plik cookie 4KB.
    2. Dane nie są odsyłane z powrotem do serwera przy każdym żądaniu HTTP (HTML, obrazy, JavaScript, CSS itp.) - zmniejszając ilość ruchu między klientem a serwerem.
    3. Dane przechowywane w localStorage są przechowywane do momentu ich jawnego usunięcia. Wprowadzone zmiany są zapisywane i dostępne dla wszystkich obecnych i przyszłych wizyt w serwisie.

    Wady :

    1. Działa na zasadzie tego samego pochodzenia . Zatem przechowywane dane będą dostępne tylko w tym samym miejscu pochodzenia.
  2. Ciasteczka

    Plusy:

    1. W porównaniu z innymi nie ma nic AFAIK.

    Cons:

    1. Limit 4K dotyczy całego pliku cookie, w tym nazwy, wartości, daty wygaśnięcia itp. Aby obsługiwać większość przeglądarek, należy zachować nazwę poniżej 4000 bajtów, a ogólny rozmiar pliku cookie poniżej 4093 bajtów.
    2. Dane są odsyłane do serwera przy każdym żądaniu HTTP (HTML, obrazy, JavaScript, CSS itp.) - zwiększając natężenie ruchu między klientem a serwerem.

      Zwykle dozwolone są następujące czynności:

      • Łącznie 300 plików cookie
      • 4096 bajtów na plik cookie
      • 20 plików cookie na domenę
      • 81920 bajtów na domenę (biorąc pod uwagę 20 plików cookie o maksymalnym rozmiarze 4096 = 81920 bajtów).
  3. sessionStorage

    Plusy:

    1. Jest podobny do localStorage.
    2. Zmiany są dostępne tylko dla okna (lub karty w przeglądarkach takich jak Chrome i Firefox). Wprowadzone zmiany są zapisywane i dostępne dla bieżącej strony, a także przyszłych wizyt w serwisie w tym samym oknie. Po zamknięciu okna pamięć jest usuwana

    Cons:

    1. Dane są dostępne tylko w oknie / zakładce, w której zostały ustawione.
    2. Dane nie są trwałe, tj. Zostaną utracone po zamknięciu okna / karty.
    3. Na przykład localStoragett działa na zasadzie tego samego pochodzenia . Zatem przechowywane dane będą dostępne tylko w tym samym miejscu pochodzenia.
  4. Wyślij wiadomość

    Plusy:

    1. Bezpiecznie umożliwia cross-origin komunikacji.
    2. Jako punkt danych, implementacja WebKit (używana przez Safari i Chrome) nie wymusza obecnie żadnych ograniczeń (innych niż te narzucone przez brak pamięci).

    Cons:

    1. Musisz otworzyć okno z bieżącego okna, a następnie możesz komunikować się tylko wtedy, gdy masz otwarte okna.
    2. Kwestie bezpieczeństwa - wysyłanie ciągów za pośrednictwem postMessage oznacza, że ​​będziesz odbierać inne zdarzenia postMessage opublikowane przez inne wtyczki JavaScript, więc pamiętaj o zaimplementowaniutargetOrigini sprawdzeniu poprawności danych przekazywanych do nasłuchiwania wiadomości.
  5. Połączenie PostMessage + SessionStorage

    Używanie postMessage do komunikacji między wieloma kartami i jednoczesne używanie sessionStorage we wszystkich nowo otwartych kartach / oknach w celu utrwalenia przekazywanych danych. Dane będą przechowywane tak długo, jak długo karty / okna pozostaną otwarte. Tak więc, nawet jeśli karta / okno otwieracza zostanie zamknięte, otwarte karty / okna będą miały wszystkie dane nawet po odświeżeniu.

Napisałem w tym celu bibliotekę JavaScript, nazwaną AcrossTabs, która używa API postMessage do komunikacji między kartami / oknami pochodzącymi z różnych źródeł i sessionStorage, aby zachować tożsamość otwartych kart / okien tak długo, jak długo żyją.

softvar
źródło
Używając AcrossTabs, czy można otworzyć inną witrynę w innej karcie i przenieść z niej dane do karty nadrzędnej? Będę mieć dane uwierzytelniające dla innej witryny.
Madhur Bhaiya
1
Tak, możesz @MadhurBhaiya
softvar
Największą zaletą pliku cookie jest to, że zezwala na używanie tej samej domeny z różnych źródeł, co jest często przydatne, gdy masz zestaw źródeł, takich jak „a.target.com”, „b.target.com” itp.
StarPinkER
7

Inną metodą, którą ludzie powinni rozważyć, są Shared Workers. Wiem, że jest to nowatorska koncepcja, ale możesz utworzyć przekaźnik na współdzielonym pracowniku, który jest DUŻO szybszy niż lokalny magazyn i nie wymaga relacji między oknem nadrzędnym / podrzędnym, o ile jesteś na tym samym początku.

Zobacz moją odpowiedź tutaj, aby zapoznać się z dyskusją, którą przeprowadziłem na ten temat.

datasedai
źródło
7

Jest mały komponent open source do synchronizacji / komunikacji między kartami / oknami tego samego pochodzenia (zastrzeżenie - jestem jednym z współautorów!) 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 Pozwoliłem sobie polecić to tutaj, ponieważ większość komponentów "blokujących / mutex / synchronizujących" zawodzi na połączeniach websocket, gdy zdarzenia zachodzą prawie jednocześnie

Alex
źródło
6

Stworzyłem bibliotekę sysend.js , jest bardzo mała, możesz sprawdzić jej kod źródłowy. Biblioteka nie ma żadnych zewnętrznych zależności.

Możesz go używać do komunikacji między kartami / oknami w tej samej przeglądarce i domenie. Biblioteka używa BroadcastChannel, jeśli jest obsługiwana, lub zdarzenia magazynu z localStorage.

API jest bardzo proste:

sysend.on('foo', function(message) {
    console.log(message);
});
sysend.broadcast('foo', {message: 'Hello'});
sysend.broadcast('foo', "hello");
sysend.broadcast('foo'); // empty notification

gdy Twoja przeglądarka obsługuje BroadcastChannel, wysłała obiekt literału (ale w rzeczywistości jest on automatycznie serializowany przez przeglądarkę), a jeśli nie, jest najpierw serializowany do JSON i deserializowany na drugim końcu.

Najnowsza wersja ma również pomocniczy interfejs API do tworzenia proxy do komunikacji między domenami. (wymaga pojedynczego pliku html w domenie docelowej).

Oto demo .

EDYCJA :

Nowa wersja wspiera również Cross-Domain komunikację, jeśli zawierają specjalny proxy.htmlplik w domenie docelowej i wywołania proxyfunkcji z domeny źródłowej:

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

(proxy.html to bardzo prosty plik html, który ma tylko jeden tag script z biblioteką).

Jeśli chcesz dwukierunkową komunikację, musisz zrobić to samo w target.comdomenie.

UWAGA : Jeśli zaimplementujesz tę samą funkcjonalność przy użyciu localStorage, w IE występuje problem. Zdarzenie magazynu jest wysyłane do tego samego okna, które wyzwoliło zdarzenie, a dla innych przeglądarek jest wywoływane tylko dla innych kart / okien.

jcubic
źródło
2
Chciałem tylko dać ci za to trochę uznania. Ładny, słodki dodatek, który jest prosty i pozwala mi komunikować się między moimi zakładkami, aby oprogramowanie ostrzegające o wylogowaniu nie wyrzucało ludzi. Dobra robota. Bardzo to polecam, jeśli potrzebujesz prostego w użyciu rozwiązania do przesyłania wiadomości.
BrownPony,
4

Stworzyłem moduł, który działa na równi z oficjalnym Broadcastchannel, ale ma awaryjne rozwiązania oparte na localstorage, indexeddb i unix-sockets. Daje to pewność, że zawsze działa nawet z Webworkers lub NodeJS. Zobacz pubkey: BroadcastChannel

pubkey
źródło
1

Napisałem artykuł na ten temat na swoim blogu: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- Aplikacja internetowa .

Korzystając z utworzonej przeze mnie biblioteki, storageManagermożesz to osiągnąć w następujący sposób:

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

Istnieją również inne wygodne metody obsługi innych scenariuszy

adentum
źródło
0

To jest storageczęść rozwojowa odpowiedzi Tomasa M. na Chrome. Musimy dodać słuchacza

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

Załaduj / zapisz element w magazynie, a nie uruchom tego zdarzenia - MUSIMY wywołać go ręcznie przez

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

a teraz wszystkie otwarte karty otrzymają zdarzenie

Kamil Kiełczewski
źródło