Comunicazione tra schede o finestre


176

Stavo cercando un modo per comunicare tra più schede o finestre in un browser (sullo stesso dominio, non su CORS) senza lasciare tracce. C'erano diverse soluzioni:

  1. usando l'oggetto finestra
  2. postMessage
  3. biscotti
  4. memoria locale

La prima è probabilmente la soluzione peggiore: è necessario aprire una finestra dalla finestra corrente e quindi è possibile comunicare solo finché si mantengono aperte le finestre. Se ricarichi la pagina in una qualsiasi delle finestre, molto probabilmente hai perso la comunicazione.

Il secondo approccio, utilizzando postMessage, probabilmente abilita la comunicazione tra origini, ma presenta lo stesso problema del primo approccio. È necessario mantenere un oggetto finestra.

Terzo modo, utilizzando i cookie, memorizza alcuni dati nel browser, che può effettivamente sembrare come inviare un messaggio a tutte le finestre sullo stesso dominio, ma il problema è che non puoi mai sapere se tutte le schede leggono il "messaggio" già o no prima pulire. Devi implementare una sorta di timeout per leggere periodicamente il cookie. Inoltre sei limitato dalla lunghezza massima dei cookie, che è 4KB.

La quarta soluzione, utilizzando localStorage, sembrava superare i limiti dei cookie e può persino essere ascoltata usando gli eventi. Come usarlo è descritto nella risposta accettata.

Modifica 2018: la risposta accettata funziona ancora, ma esiste una soluzione più recente per i browser moderni, per utilizzare BroadcastChannel. Vedi l'altra risposta per un semplice esempio che descrive come trasmettere facilmente messaggi tra le schede usando BroadcastChannel.


4
Perché questa domanda è stata chiusa come "troppo ampia" quando praticamente le stesse domande sono state aperte per anni? Invio di un messaggio a tutte le finestre / schede aperte tramite JavaScript , stackoverflow.com/questions/2236828/… , Come si comunica tra 2 schede / finestre del browser? e pochi altri.
Dan Dascalescu,

Ho creato una libreria su localStorage e sessionStorage per gestire l'archiviazione dei dati sul lato client. Puoi fare cose come storageManager.savePermanentData ('data', 'chiave'); oppure storageManager.saveSyncedSessionData ('data', 'chiave'); in base a come desideri che i tuoi dati si comportino. Semplifica davvero il processo. Articolo completo qui: ebenmonney.com/blog/…
adentum,


2
Ho creato la libreria sysend.js alcuni anni fa, nell'ultima versione utilizza BroadcastChannel. Puoi testare la libreria aprendo questa pagina due volte jcubic.pl/sysend.php , funziona anche con origini diverse se fornisci il proxy iframe.
jcubic,

Considero il sottodominio come la stessa origine? Voglio dire, ho meno di tre domini, comunicano tramite API di canale di trasmissione? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel,

Risposte:


142

Modifica 2018: è possibile utilizzare BroadcastChannel per questo scopo, vedere le altre risposte di seguito. Tuttavia, se preferisci comunque utilizzare localstorage per la comunicazione tra le schede, fallo in questo modo:

Per ricevere una notifica quando una scheda invia un messaggio ad altre schede, è sufficiente associare un evento di "archiviazione". In tutte le schede, procedere come segue:

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

La funzione message_receiveverrà chiamata ogni volta che si imposta un valore di localStorage in qualsiasi altra scheda. Il listener di eventi contiene anche i dati appena impostati su localStorage, quindi non è nemmeno necessario analizzare l'oggetto localStorage stesso. Questo è molto utile perché è possibile ripristinare il valore subito dopo averlo impostato, per ripulire efficacemente eventuali tracce. Ecco le funzioni per la messaggistica:

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

Quindi ora una volta che le tue schede si legano all'evento onstorage e hai implementato queste due funzioni, puoi semplicemente trasmettere un messaggio ad altre schede chiamando, ad esempio:

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

Ricorda che l'invio dello stesso messaggio due volte verrà propagato una sola volta, quindi se devi ripetere i messaggi, aggiungi un identificatore univoco a loro, come

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

Ricorda inoltre che la scheda corrente che trasmette il messaggio in realtà non lo riceve, ma solo altre schede o finestre sullo stesso dominio.

Puoi chiedere cosa succede se l'utente carica una pagina web diversa o chiude la sua scheda subito dopo la chiamata setItem () prima di removeItem (). Bene, dai miei test il browser mette in attesa lo scarico fino al completamento dell'intera funzione message_broadcast(). Ho provato a inserire un intervallo molto lungo per () il ciclo e ho ancora aspettato che il ciclo finisse prima di chiudere. Se l'utente uccide la scheda tra di loro, il browser non avrà abbastanza tempo per salvare il messaggio sul disco, quindi questo approccio mi sembra un modo sicuro per inviare messaggi senza alcuna traccia. Commenti benvenuti.


1
puoi ignorare l'evento remove prima di invocare JSON.parse ()?
Dandavis,

1
tenere presente il limite dei dati dell'evento, inclusi i dati di archivio locale preesistenti. potrebbe essere meglio / più sicuro utilizzare gli eventi di archiviazione solo per la messaggistica anziché per la spedizione. come quando ricevi una cartolina che ti dice di ritirare un pacco presso l'ufficio postale ... inoltre, localstorage va sul disco rigido, quindi potrebbe lasciare cache involontarie e influire sui registri, un altro motivo per considerare un diverso meccanismo di trasporto per il dati reali.
Dandavis,

1
ho fatto qualcosa di simile un po 'di tempo fa: danml.com/js/localstorageevents.js , che uno ha una base di emittenti di eventi ed "eco locale" in modo da poter usare l'EE per tutto ovunque.
Dandavis,

7
Safari non supporta BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth

1
Solo un avvertimento, puoi anche utilizzare i lavoratori condivisi: developer.mozilla.org/en-US/docs/Web/API/SharedWorker (che ha un supporto migliore tra i browser)
Seblor

116

Esiste un'API moderna dedicata a questo scopo: Broadcast Channel

È facile come:

var bc = new BroadcastChannel('test_channel');

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

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

Non è necessario che il messaggio sia solo un DOMString, è possibile inviare qualsiasi tipo di oggetto.

Probabilmente, a parte la pulizia dell'API, è il principale vantaggio di questa API: nessuna stringa di oggetti.

Attualmente supportato solo in Chrome e Firefox, ma è possibile trovare un polyfill che utilizza localStorage.


3
Aspetta, come fai a sapere da dove proviene il messaggio? Ignora i messaggi che provengono dalla stessa scheda?
AturSams,

2
@zehelvion: il mittente non lo riceverà, ad esempio questa bella panoramica . Inoltre, puoi inserire nel messaggio quello che vuoi, incl. qualche ID del mittente, se necessario.
Sz.

7
C'è un bel progetto che racchiude questa funzione in una libreria cross-browser qui: github.com/pubkey/broadcast-channel
james2doyle,

Ci sono stati segnali pubblici da Safari sull'opportunità o meno di supportare questa API in quel browser?
Casey,

@AturSams Verifica che MessageEvent.origin, MessageEvent.source o MessageEvent.ports siano ciò che desideri. Come sempre la documentazione è il punto di partenza migliore: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Stefan Mihai Stanescu,

40

Per coloro che cercano una soluzione non basata su jQuery, questa è una semplice versione JavaScript della soluzione fornita da Thomas 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);
    }
}

1
Perché hai omesso la chiamata removeItem?
Tomas M,

2
Mi stavo solo concentrando sulle differenze tra jQuery e JavaScript.
Nacho Coloma,

uso sempre una lib a causa di polyfill e possibilità di funzionalità non supportata!
Amin Rahimi,

20

Acquista su AcrossTabs - Comunicazione semplice tra le schede del browser di origine incrociata. Utilizza una combinazione di API postMessage e sessionStorage per rendere la comunicazione molto più semplice e affidabile.


Esistono approcci diversi e ognuno ha i suoi vantaggi e svantaggi. Discutiamo ciascuno:

  1. Memoria locale

    Pro :

    1. L'archiviazione Web può essere vista in modo semplicistico come un miglioramento dei cookie, fornendo una capacità di archiviazione molto maggiore. Se osservi il codice sorgente di Mozilla, possiamo vedere che 5120 KB ( 5 MB che equivalgono a 2,5 milioni di caratteri su Chrome) è la dimensione di archiviazione predefinita per un intero dominio. Questo ti dà molto più spazio su cui lavorare rispetto a un tipico cookie 4KB.
    2. I dati non vengono rinviati al server per ogni richiesta HTTP (HTML, immagini, JavaScript, CSS, ecc.), Riducendo la quantità di traffico tra client e server.
    3. I dati memorizzati in localStorage persistono fino a quando non vengono esplicitamente eliminati. Le modifiche apportate vengono salvate e disponibili per tutte le visite attuali e future al sito.

    Contro :

    1. Funziona sulla stessa politica di origine . Pertanto, i dati archiviati saranno disponibili solo sulla stessa origine.
  2. Biscotti

    Professionisti:

    1. Rispetto ad altri, non c'è nulla di AFAIK.

    Contro:

    1. Il limite 4K è per l'intero cookie, inclusi nome, valore, data di scadenza, ecc. Per supportare la maggior parte dei browser, mantenere il nome sotto i 4000 byte e la dimensione complessiva del cookie sotto i 4093 byte.
    2. I dati vengono rinviati al server per ogni richiesta HTTP (HTML, immagini, JavaScript, CSS, ecc.), Aumentando la quantità di traffico tra client e server.

      In genere, sono consentiti:

      • 300 biscotti in totale
      • 4096 byte per cookie
      • 20 cookie per dominio
      • 81920 byte per dominio (dati 20 cookie di dimensione massima 4096 = 81920 byte.)
  3. sessionStorage

    Professionisti:

    1. È simile a localStorage.
    2. Le modifiche sono disponibili solo per finestra (o scheda nei browser come Chrome e Firefox). Le modifiche apportate vengono salvate e disponibili per la pagina corrente, nonché le visite future al sito nella stessa finestra. Una volta chiusa la finestra, la memoria viene eliminata

    Contro:

    1. I dati sono disponibili solo all'interno della finestra / scheda in cui sono stati impostati.
    2. I dati non sono persistenti, cioè andranno persi una volta chiusa la finestra / scheda.
    3. Come localStorage, tt funziona sulla politica della stessa origine . Pertanto, i dati archiviati saranno disponibili solo sulla stessa origine.
  4. PostMessage

    Professionisti:

    1. Abilita in modo sicuro la comunicazione tra origini .
    2. Come punto dati, l'implementazione di WebKit (utilizzata da Safari e Chrome) non impone attualmente limiti (diversi da quelli imposti dalla memoria insufficiente).

    Contro:

    1. È necessario aprire una finestra dalla finestra corrente e quindi può comunicare solo finché si mantengono aperte le finestre.
    2. Problemi di sicurezza - L'invio di stringhe tramite postMessage prevede la raccolta di altri eventi postMessage pubblicati da altri plug-in JavaScript, pertanto assicurarsi di implementare untargetOrigincontrollo diintegritàe un controllo di integrità per i dati trasmessi al listener di messaggi.
  5. Una combinazione di PostMessage + SessionStorage

    Utilizzo di postMessage per comunicare tra più schede e allo stesso tempo utilizzo di sessionStorage in tutte le schede / finestre appena aperte per rendere persistenti i dati trasmessi. I dati rimarranno persistenti fintanto che le schede / finestre rimarranno aperte. Quindi, anche se la scheda / finestra di apertura viene chiusa, le schede / finestre aperte avranno tutti i dati anche dopo essere stati aggiornati.

Per questo ho scritto una libreria JavaScript, denominata AcrossTabs che utilizza l'API postMessage per comunicare tra le schede / finestre e origine sessione di origine incrociata per mantenere le schede / identità di Windows aperte finché vivono.


Utilizzando AcrossTabs, è possibile aprire un sito Web diverso in un'altra scheda e ottenere i dati da esso alla scheda padre? Avrò i dettagli di autenticazione per un altro sito Web.
Madhur Bhaiya,

1
Sì, puoi @MadhurBhaiya
softvar

Il più grande vantaggio dei cookie è che consente lo stesso dominio di origine incrociata, che è comunemente utile quando si ha un insieme di origini come "a.target.com", "b.target.com" ecc.
StarPinkER

7

Un altro metodo che le persone dovrebbero considerare di utilizzare è Shared Workers. So che è un concetto all'avanguardia, ma è possibile creare un relè su un lavoratore condiviso che è MOLTO più veloce del deposito locale e non richiede una relazione tra la finestra padre / figlio, purché si trovi sulla stessa origine.

Vedi la mia risposta qui per alcune discussioni che ho fatto al riguardo.


7

C'è un piccolo componente open source per sincronizzare / comunicare tra schede / finestre della stessa origine (disclaimer - sono uno dei collaboratori!) Basato su 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 Mi sono preso la libertà di consigliarlo qui poiché la maggior parte dei componenti "lock / mutex / sync" non riescono sulle connessioni del websocket quando gli eventi accadono quasi contemporaneamente


6

Ho creato una libreria sysend.js , è molto piccola, puoi verificarne il codice sorgente. La libreria non ha dipendenze esterne.

Puoi usarlo per la comunicazione tra schede / finestre nello stesso browser e dominio. La libreria utilizza BroadcastChannel, se supportato, o un evento di archiviazione da localStorage.

L'API è molto semplice:

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

quando il tuo browser supporta BroadcastChannel ha inviato un oggetto letterale (ma in realtà è serializzato automaticamente dal browser) e in caso contrario è serializzato su JSON prima e deserializzato dall'altra parte.

La versione recente ha anche l'API di supporto per creare proxy per la comunicazione tra domini. (richiede un singolo file html sul dominio di destinazione).

Ecco la demo .

MODIFICA :

La nuova versione supporta anche la comunicazione tra domini , se si include un proxy.htmlfile speciale nel dominio di destinazione e si chiama la proxyfunzione dal dominio di origine:

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

(proxy.html è un file html molto semplice, che ha un solo tag script con la libreria).

Se desideri una comunicazione bidirezionale, devi fare lo stesso sul target.comdominio.

NOTA : se implementerai la stessa funzionalità utilizzando localStorage, c'è un problema in IE. L'evento di archiviazione viene inviato alla stessa finestra, che ha attivato l'evento e per altri browser è invocato solo per altre schede / finestre.


2
Volevo solo darti dei kudo per questo. Bella piccola aggiunta dolce che è semplice e mi permette di comunicare tra le mie schede per impedire al software di avviso di disconnessione di dare il calcio d'inizio alle persone. Bel lavoro. Lo consiglio vivamente se si desidera una soluzione di messaggistica semplice da usare.
BrownPony,


1

Ho scritto un articolo su questo sul mio blog: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- applicazione web .

Utilizzando una libreria che ho creato storageManagerè possibile ottenere ciò come segue:

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

Esistono anche altri metodi utili per gestire anche altri scenari


0

Questa è una storageparte di sviluppo della risposta di Tomas M per Chrome. Dobbiamo aggiungere ascoltatore

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

Caricare / salvare l'elemento in memoria non eseguire questo evento - DOVREBBE attivarlo manualmente

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

e ora, tutte le schede aperte riceveranno l'evento

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.