Kommunikation zwischen Registerkarten oder Fenstern

174

Ich suchte nach einer Möglichkeit, zwischen mehreren Registerkarten oder Fenstern in einem Browser (in derselben Domäne, nicht in CORS) zu kommunizieren, ohne Spuren zu hinterlassen. Es gab mehrere Lösungen:

  1. mit Fensterobjekt
  2. POST-Meldung
  3. Kekse
  4. lokaler Speicher

Die erste ist wahrscheinlich die schlechteste Lösung - Sie müssen ein Fenster von Ihrem aktuellen Fenster aus öffnen und können dann nur kommunizieren, solange Sie die Fenster geöffnet halten. Wenn Sie die Seite in einem der Fenster neu laden, haben Sie höchstwahrscheinlich die Kommunikation verloren.

Der zweite Ansatz, der postMessage verwendet, ermöglicht wahrscheinlich die Kommunikation zwischen den Ursprüngen, hat jedoch das gleiche Problem wie der erste Ansatz. Sie müssen ein Fensterobjekt pflegen.

Drittens können Sie mithilfe von Cookies einige Daten im Browser speichern. Dies kann so aussehen, als würden Sie eine Nachricht an alle Fenster in derselben Domain senden. Das Problem ist jedoch, dass Sie nie wissen können, ob alle Registerkarten die "Nachricht" bereits gelesen haben oder nicht Aufräumen. Sie müssen eine Art Zeitüberschreitung implementieren, um das Cookie regelmäßig zu lesen. Darüber hinaus sind Sie durch die maximale Cookie-Länge von 4 KB begrenzt.

Die vierte Lösung, die localStorage verwendet, schien die Einschränkungen von Cookies zu überwinden, und sie kann sogar mithilfe von Ereignissen abgehört werden. Wie man es benutzt, ist in der akzeptierten Antwort beschrieben.

Edit 2018: Die akzeptierte Antwort funktioniert immer noch, aber es gibt eine neuere Lösung für moderne Browser, um BroadcastChannel zu verwenden. In der anderen Antwort finden Sie ein einfaches Beispiel für die einfache Übertragung von Nachrichten zwischen Registerkarten mithilfe von BroadcastChannel.

Tomas M.
quelle
4
Warum wurde diese Frage als "zu weit gefasst" geschlossen, obwohl seit Jahren so ziemlich dieselben Fragen offen sind? Senden einer Nachricht an alle geöffneten Fenster / Registerkarten mit JavaScript , stackoverflow.com/questions/2236828/… . Wie kommunizieren Sie zwischen zwei Browser-Registerkarten / Fenstern? und noch ein paar mehr.
Dan Dascalescu
Ich habe eine Bibliothek über localStorage und sessionStorage erstellt, um die clientseitige Datenspeicherung zu verwalten. Sie können Dinge wie storageManager.savePermanentData ('data', 'key') tun; oder storageManager.saveSyncedSessionData ('data', 'key'); basierend darauf, wie sich Ihre Daten verhalten sollen. Es vereinfacht den Prozess wirklich. Vollständiger Artikel hier: ebenmonney.com/blog/…
adentum
2
Ich habe vor einigen Jahren die Bibliothek sysend.js erstellt. In der neuesten Version wird BroadcastChannel verwendet. Sie können die Bibliothek testen, indem Sie diese Seite zweimal öffnen. Jcubic.pl/sysend.php . Sie funktioniert auch mit einem anderen Ursprung, wenn Sie einen Iframe-Proxy bereitstellen.
Jcubic
Betrachte ich die Subdomain als denselben Ursprung? Ich meine, ich habe weniger als drei Domänen. Kommunizieren sie über die Broadcast-Kanal-API? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel

Antworten:

141

Edit 2018: Sie können BroadcastChannel besser für diesen Zweck verwenden, siehe andere Antworten unten. Wenn Sie dennoch Localstorage für die Kommunikation zwischen Registerkarten verwenden möchten, gehen Sie folgendermaßen vor:

Um benachrichtigt zu werden, wenn ein Tab eine Nachricht an andere Tabs sendet, müssen Sie lediglich das Speicherereignis binden. Gehen Sie auf allen Registerkarten folgendermaßen vor:

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

Die Funktion message_receivewird jedes Mal aufgerufen, wenn Sie einen Wert von localStorage auf einer anderen Registerkarte festlegen. Der Ereignis-Listener enthält auch die Daten, die neu auf localStorage festgelegt wurden, sodass Sie nicht einmal das localStorage-Objekt selbst analysieren müssen. Dies ist sehr praktisch, da Sie den Wert direkt nach dem Festlegen zurücksetzen können, um alle Spuren effektiv zu bereinigen. Hier sind Funktionen für Messaging:

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

Sobald Ihre Registerkarten an das Ereignis onstorage gebunden sind und Sie diese beiden Funktionen implementiert haben, können Sie einfach eine Nachricht an andere Registerkarten senden, die beispielsweise Folgendes aufrufen:

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

Denken Sie daran, dass das zweimalige Senden derselben Nachricht nur einmal weitergegeben wird. Wenn Sie also Nachrichten wiederholen müssen, fügen Sie ihnen eine eindeutige Kennung hinzu, z

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

Denken Sie auch daran, dass die aktuelle Registerkarte, auf der die Nachricht gesendet wird, sie nicht tatsächlich empfängt, sondern nur andere Registerkarten oder Fenster in derselben Domäne.

Sie können fragen, was passiert, wenn der Benutzer eine andere Webseite lädt oder seinen Tab unmittelbar nach dem Aufruf von setItem () vor dem Aufruf von removeItem () schließt. Nach meinen eigenen Tests hält der Browser das Entladen in der Warteschleife, bis die gesamte Funktion abgeschlossen message_broadcast()ist. Ich habe getestet, ob es einen sehr langen for () -Zyklus gibt, und es hat immer noch darauf gewartet, dass der Zyklus beendet ist, bevor es geschlossen wird. Wenn der Benutzer die Registerkarte nur dazwischen beendet, hat der Browser nicht genügend Zeit, um die Nachricht auf der Festplatte zu speichern. Daher scheint mir dieser Ansatz eine sichere Methode zum Senden von Nachrichten ohne Spuren zu sein. Kommentare willkommen.

Tomas M.
quelle
1
Können Sie das Ereignis remove ignorieren, bevor Sie JSON.parse () aufrufen?
Dandavis
1
Beachten Sie das Ereignisdatenlimit, einschließlich bereits vorhandener localStorage-Daten. Es ist möglicherweise besser / sicherer, die Speicherereignisse nur für Nachrichten anstatt für den Versand zu verwenden. Zum Beispiel, wenn Sie eine Postkarte erhalten, die Sie auffordert, ein Paket bei der Post abzuholen. Außerdem wird localstorage auf die Festplatte übertragen, sodass möglicherweise unbeabsichtigte Caches zurückbleiben und sich auf die Protokolle auswirken. Dies ist ein weiterer Grund, einen anderen Transportmechanismus für die in Betracht zu ziehen wirkliche Daten.
Dandavis
1
Ich habe vor einiger Zeit etwas Ähnliches gemacht : danml.com/js/localstorageevents.js , dass man eine Event-Emmiter-Basis und ein "lokales Echo" hat, so dass man den EE für alles überall verwenden kann.
Dandavis
6
Safari unterstützt BroadcastChannel nicht - caniuse.com/#feat=broadcastchannel
Srikanth
1
Nur ein Kopf hoch, Sie können auch gemeinsam genutzte Mitarbeiter verwenden: developer.mozilla.org/en-US/docs/Web/API/SharedWorker (der eine bessere Unterstützung für alle Browser
bietet
115

Zu diesem Zweck gibt es eine moderne API - Broadcast Channel

Es ist so einfach wie:

var bc = new BroadcastChannel('test_channel');

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

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

Es ist nicht erforderlich, dass die Nachricht nur ein DOMString ist. Es kann jede Art von Objekt gesendet werden.

Abgesehen von der API-Sauberkeit ist dies wahrscheinlich der Hauptvorteil dieser API - keine Objekt-Stringifizierung.

Derzeit unterstützt nur in Chrome und Firefox, aber Sie können einen polyfill finden , die localstorage verwendet.

Benutzer
quelle
3
Warten Sie, woher wissen Sie, woher die Nachricht kommt? Ignoriert es Nachrichten, die von derselben Registerkarte stammen?
AturSams
2
@zehelvion: Der Absender wird es nicht erhalten, laut zB dieser schönen Übersicht . Außerdem können Sie in die Nachricht einfügen, was Sie wollen, inkl. ggf. eine ID des Absenders.
Gr.
6
Es gibt ein schönes Projekt, das diese Funktion in einer browserübergreifenden Bibliothek hier zusammenfasst: github.com/pubkey/broadcast-channel
james2doyle
Gab es öffentliche Signale von Safari, ob die Unterstützung für diese API in diesem Browser landen wird oder nicht?
Casey
@AturSams Sie überprüfen, ob MessageEvent.origin, MessageEvent.source oder MessageEvent.ports Ihren Wünschen entsprechen. Wie immer ist die Dokumentation der beste Ausgangspunkt: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Stefan Mihai Stanescu
40

Für diejenigen, die nach einer Lösung suchen, die nicht auf jQuery basiert, ist dies eine einfache JavaScript-Version der von Thomas M bereitgestellten Lösung:

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
quelle
1
Warum haben Sie den Aufruf von removeItem weggelassen?
Tomas M
2
Ich habe mich nur auf die Unterschiede zwischen jQuery und JavaScript konzentriert.
Nacho Coloma
Ich benutze immer eine Bibliothek wegen Polyfill und der Möglichkeit der nicht unterstützten Funktion!
Amin Rahimi
19

Checkout AcrossTabs - Einfache Kommunikation zwischen originübergreifenden Browser-Registerkarten. Es verwendet eine Kombination aus postMessage und sessionStorage API, um die Kommunikation viel einfacher und zuverlässiger zu machen.


Es gibt verschiedene Ansätze und jeder hat seine eigenen Vor- und Nachteile. Lassen Sie uns jeweils diskutieren:

  1. Lokaler Speicher

    Vorteile :

    1. Webspeicher kann vereinfacht als Verbesserung von Cookies angesehen werden und bietet eine viel größere Speicherkapazität. Wenn Sie sich den Mozilla-Quellcode ansehen, sehen Sie, dass 5120 KB ( 5 MB , was 2,5 Millionen Zeichen in Chrome entspricht) die Standardspeichergröße für eine gesamte Domain ist. Dies gibt Ihnen erheblich mehr Platz zum Arbeiten als mit einem typischen 4-KB-Cookie.
    2. Die Daten werden nicht bei jeder HTTP-Anforderung (HTML, Bilder, JavaScript, CSS usw.) an den Server zurückgesendet, wodurch der Datenverkehr zwischen Client und Server verringert wird.
    3. Die in localStorage gespeicherten Daten bleiben bestehen, bis sie explizit gelöscht werden. Die vorgenommenen Änderungen werden gespeichert und stehen für alle aktuellen und zukünftigen Besuche auf der Website zur Verfügung.

    Nachteile :

    1. Es funktioniert mit Richtlinien gleichen Ursprungs . Gespeicherte Daten können also nur am selben Ursprung verfügbar sein.
  2. Kekse

    Vorteile:

    1. Im Vergleich zu anderen gibt es nichts AFAIK.

    Nachteile:

    1. Das 4K-Limit gilt für das gesamte Cookie, einschließlich Name, Wert, Ablaufdatum usw. Um die meisten Browser zu unterstützen, sollten Sie den Namen unter 4000 Byte und die Gesamtgröße des Cookies unter 4093 Byte halten.
    2. Die Daten werden bei jeder HTTP-Anforderung (HTML, Bilder, JavaScript, CSS usw.) an den Server zurückgesendet, wodurch der Datenverkehr zwischen Client und Server erhöht wird.

      In der Regel sind folgende zulässig:

      • Insgesamt 300 Cookies
      • 4096 Bytes pro Cookie
      • 20 Cookies pro Domain
      • 81920 Bytes pro Domain (Bei 20 Cookies mit einer maximalen Größe von 4096 = 81920 Bytes.)
  3. sessionStorage

    Vorteile:

    1. Es ist ähnlich wie localStorage.
    2. Änderungen sind nur pro Fenster (oder Registerkarte in Browsern wie Chrome und Firefox) verfügbar. Die vorgenommenen Änderungen werden gespeichert und sind für die aktuelle Seite sowie für zukünftige Besuche der Site im selben Fenster verfügbar. Sobald das Fenster geschlossen ist, wird der Speicher gelöscht

    Nachteile:

    1. Die Daten sind nur in dem Fenster / der Registerkarte verfügbar, in dem sie festgelegt wurden.
    2. Die Daten sind nicht persistent, dh sie gehen verloren, sobald das Fenster / die Registerkarte geschlossen wird.
    3. Wie localStoragearbeitet tt auf same-origin policy . Gespeicherte Daten können also nur am selben Ursprung verfügbar sein.
  4. POST-Meldung

    Vorteile:

    1. Ermöglicht sicher die Kommunikation zwischen verschiedenen Ursprüngen .
    2. Als Datenpunkt erzwingt die WebKit-Implementierung (von Safari und Chrome verwendet) derzeit keine Einschränkungen (außer denen, die durch Speichermangel entstehen).

    Nachteile:

    1. Sie müssen ein Fenster aus dem aktuellen Fenster öffnen und können dann nur kommunizieren, solange Sie die Fenster geöffnet halten.
    2. Sicherheitsbedenken - Wenn Sie Zeichenfolgen über postMessage senden, werden Sie andere postMessage-Ereignisse abrufen, die von anderen JavaScript-Plugins veröffentlicht wurden. Implementieren Sie daher einetargetOriginund eine Überprüfung der Richtigkeit der Daten, die an den Nachrichtenlistener weitergeleitet werden.
  5. Eine Kombination aus PostMessage + SessionStorage

    Verwenden von postMessage zur Kommunikation zwischen mehreren Registerkarten und gleichzeitiges Verwenden von sessionStorage in allen neu geöffneten Registerkarten / Fenstern, um die Datenübergabe aufrechtzuerhalten. Die Daten bleiben erhalten, solange die Registerkarten / Fenster geöffnet bleiben. Selbst wenn die Registerkarte / das Fenster des Öffners geschlossen wird, enthalten die geöffneten Registerkarten / Fenster die gesamten Daten, auch nachdem sie aktualisiert wurden.

Ich habe hierfür eine JavaScript-Bibliothek mit dem Namen AcrossTabs geschrieben, die die postMessage-API verwendet, um zwischen originalübergreifenden Registerkarten / Fenstern und sessionStorage zu kommunizieren und die geöffnete Tabs / Windows-Identität so lange beizubehalten, wie sie aktiv sind.

softvar
quelle
Ist AcrossTabses mit möglich, eine andere Website in einem anderen Tab zu öffnen und die Daten von diesem auf den übergeordneten Tab zu übertragen? Ich werde Authentifizierungsdetails für die andere Website haben.
Madhur Bhaiya
1
Ja, Sie können @MadhurBhaiya
softvar
Der größte Vorteil von Cookies besteht darin, dass dieselbe Domain zwischen verschiedenen Ursprüngen verwendet werden kann. Dies ist häufig nützlich, wenn Sie eine Reihe von Ursprüngen wie "a.target.com", "b.target.com" usw. haben.
StarPinkER
7

Eine andere Methode, die Menschen in Betracht ziehen sollten, ist Shared Workers. Ich weiß, dass es ein innovatives Konzept ist, aber Sie können ein Relay auf einem Shared Worker erstellen, das VIEL schneller als localstorage ist und keine Beziehung zwischen dem Eltern- / Kind-Fenster erfordert, solange Sie denselben Ursprung haben.

Siehe meine Antwort hier für eine Diskussion, die ich darüber gemacht habe.

datasedai
quelle
7

Es gibt eine winzige Open-Source-Komponente zum Synchronisieren / Kommunizieren zwischen Registerkarten / Fenstern desselben Ursprungs (Haftungsausschluss - ich bin einer der Mitwirkenden!) 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 Ich habe mir erlaubt, es hier zu empfehlen, da die meisten "Lock / Mutex / Sync" -Komponenten bei Websocket-Verbindungen ausfallen, wenn Ereignisse fast gleichzeitig auftreten

Alex
quelle
6

Ich habe eine Bibliothek sysend.js erstellt , sie ist sehr klein, Sie können den Quellcode überprüfen. Die Bibliothek hat keine externen Abhängigkeiten.

Sie können es für die Kommunikation zwischen Registerkarten / Fenstern in demselben Browser und derselben Domäne verwenden. Die Bibliothek verwendet BroadcastChannel (falls unterstützt) oder ein Speicherereignis von localStorage.

API ist sehr einfach:

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

Wenn Ihr Browser BroadcastChannel unterstützt, hat er ein Literalobjekt gesendet (aber es wird tatsächlich vom Browser automatisch serialisiert). Wenn nicht, wird es zuerst in JSON serialisiert und am anderen Ende deserialisiert.

Die aktuelle Version verfügt auch über eine Hilfs-API zum Erstellen eines Proxys für die domänenübergreifende Kommunikation. (Es erfordert eine einzelne HTML-Datei in der Zieldomäne).

Hier ist eine Demo .

EDIT :

Neue Version auch Unterstützung bei domänenübergreifender Kommunikation, wenn Sie spezielle umfassen proxy.htmlDatei auf Zieldomäne und Call - proxyFunktion von Quelldomäne:

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

(proxy.html ist eine sehr einfache HTML-Datei, die nur ein Skript-Tag mit der Bibliothek enthält).

Wenn Sie eine bidirektionale Kommunikation wünschen, müssen Sie dasselbe in der target.comDomäne tun .

HINWEIS : Wenn Sie dieselbe Funktionalität mit localStorage implementieren, liegt ein Problem im IE vor. Das Speicherereignis wird an dasselbe Fenster gesendet, das das Ereignis ausgelöst hat, und für andere Browser wird es nur für andere Registerkarten / Fenster aufgerufen.

jcubic
quelle
2
Ich wollte dir nur ein Lob dafür geben. Netter süßer kleiner Zusatz, der einfach ist und es mir ermöglicht, zwischen meinen Registerkarten zu kommunizieren, um zu verhindern, dass die Abmeldewarnsoftware Leute aus dem Verkehr zieht. Gut gemacht. Ich kann dies nur empfehlen, wenn Sie eine einfach zu verwendende Messaging-Lösung wünschen.
BrownPony
4

Ich habe ein Modul erstellt, das dem offiziellen Broadcast- Kanal entspricht, aber Fallbacks basierend auf localstorage, indexeddb und Unix-Sockets aufweist. Dies stellt sicher, dass es auch mit Webworkern oder NodeJS immer funktioniert. Siehe pubkey: BroadcastChannel

Pubkey
quelle
1

Ich habe einen Artikel dazu in meinem Blog geschrieben: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- Webanwendung .

Mit einer von mir erstellten Bibliothek storageManagerkönnen Sie dies wie folgt erreichen:

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

Es gibt auch andere bequeme Methoden, um auch andere Szenarien zu handhaben

adentum
quelle
0

Dies ist ein Entwicklungsteil storageder Antwort von Tomas M für Chrome. Wir müssen den Hörer hinzufügen

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

Das Laden / Speichern eines Elements im Speicher führt dieses Ereignis nicht aus - wir MÜSSEN es manuell durch auslösen

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

und jetzt erhalten alle geöffneten Registerkarten ein Ereignis

Kamil Kiełczewski
quelle