Communication entre onglets ou fenêtres

176

Je cherchais un moyen de communiquer entre plusieurs onglets ou fenêtres dans un navigateur (sur le même domaine, pas CORS) sans laisser de traces. Il y avait plusieurs solutions:

  1. utilisation d'un objet window
  2. postMessage
  3. biscuits
  4. stockage local

La première est probablement la pire solution - vous devez ouvrir une fenêtre à partir de votre fenêtre actuelle et vous ne pouvez communiquer que tant que vous gardez les fenêtres ouvertes. Si vous rechargez la page dans l'une des fenêtres, vous avez probablement perdu la communication.

La deuxième approche, utilisant postMessage, permet probablement une communication inter-origine, mais souffre du même problème que la première approche. Vous devez gérer un objet fenêtre.

Troisièmement, à l'aide de cookies, stockez certaines données dans le navigateur, ce qui peut effectivement ressembler à l'envoi d'un message à toutes les fenêtres du même domaine, mais le problème est que vous ne pouvez jamais savoir si tous les onglets lisent déjà ou non le "message". nettoyer. Vous devez mettre en œuvre une sorte de délai d'expiration pour lire le cookie périodiquement. De plus, vous êtes limité par la longueur maximale des cookies, qui est de 4 Ko.

La quatrième solution, utilisant localStorage, semble surmonter les limitations des cookies, et peut même être écoutée en utilisant des événements. Comment l'utiliser est décrit dans la réponse acceptée.

Edit 2018: la réponse acceptée fonctionne toujours, mais il existe une solution plus récente pour les navigateurs modernes, pour utiliser BroadcastChannel. Voir l'autre réponse pour un exemple simple décrivant comment transmettre facilement un message entre les onglets à l'aide de BroadcastChannel.

Tomas M
la source
4
Pourquoi cette question a-t-elle été classée comme "trop ​​large" alors qu'à peu près les mêmes questions sont ouvertes depuis des années? Envoi d'un message à toutes les fenêtres / onglets ouverts en utilisant JavaScript , stackoverflow.com/questions/2236828/… , Comment communiquez-vous entre 2 onglets / fenêtres du navigateur? et quelques autres.
Dan Dascalescu du
J'ai créé une bibliothèque sur localStorage et sessionStorage pour gérer le stockage de données côté client. Vous pouvez faire des choses comme storageManager.savePermanentData ('data', 'key'); ou storageManager.saveSyncedSessionData ('data', 'key'); en fonction de la manière dont vous souhaitez que vos données se comportent. Cela simplifie vraiment le processus. Article complet ici: ebenmonney.com/blog/…
adentum
2
J'ai créé la bibliothèque sysend.js il y a quelques années, dans la dernière version, elle utilise BroadcastChannel. Vous pouvez tester la bibliothèque en ouvrant cette page deux fois jcubic.pl/sysend.php , cela fonctionne également avec une origine différente si vous fournissez un proxy iframe.
jcubic
Est-ce que je considère le sous-domaine comme une même origine? Je veux dire, j'ai moins de trois domaines, communiquent-ils via l'API de broadcastchannel? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel

Réponses:

142

Edit 2018: Vous pouvez mieux utiliser BroadcastChannel à cette fin, voir les autres réponses ci-dessous. Pourtant, si vous préférez toujours utiliser localstorage pour la communication entre les onglets, procédez comme suit:

Pour être averti lorsqu'un onglet envoie un message à d'autres onglets, il vous suffit de lier l'événement «stockage». Dans tous les onglets, procédez comme suit:

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

La fonction message_receivesera appelée chaque fois que vous définissez une valeur de localStorage dans un autre onglet. L'écouteur d'événements contient également les données nouvellement définies sur localStorage, vous n'avez donc même pas besoin d'analyser l'objet localStorage lui-même. C'est très pratique car vous pouvez réinitialiser la valeur juste après sa définition, pour nettoyer efficacement les traces. Voici les fonctions de messagerie:

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

Alors maintenant, une fois que vos onglets se lient à l'événement onstorage, et que ces deux fonctions sont implémentées, vous pouvez simplement diffuser un message vers d'autres onglets appelant, par exemple:

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

N'oubliez pas que l'envoi du même message deux fois ne sera propagé qu'une seule fois, donc si vous devez répéter des messages, ajoutez-leur un identifiant unique, comme

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

Rappelez-vous également que l'onglet actuel qui diffuse le message ne le reçoit pas réellement, uniquement d'autres onglets ou fenêtres du même domaine.

Vous pouvez demander ce qui se passe si l'utilisateur charge une page Web différente ou ferme son onglet juste après l'appel setItem () avant le removeItem (). Eh bien, d'après mes propres tests, le navigateur met le déchargement en attente jusqu'à ce que toute la fonction message_broadcast()soit terminée. J'ai testé pour y mettre un cycle très long pour () et il a encore attendu que le cycle se termine avant de fermer. Si l'utilisateur tue l'onglet juste entre les deux, alors le navigateur n'aura pas assez de temps pour enregistrer le message sur le disque, donc cette approche me semble être un moyen sûr d'envoyer des messages sans aucune trace. Commentaires bienvenus.

Tomas M
la source
1
pouvez-vous ignorer l'événement remove avant d'appeler JSON.parse ()?
dandavis
1
gardez à l'esprit la limite de données d'événement, y compris les données de stockage local préexistantes. il peut être préférable / plus sûr d'utiliser les événements de stockage uniquement pour la messagerie plutôt que pour l'expédition. comme lorsque vous recevez une carte postale vous demandant de récupérer un colis au bureau de poste ... aussi, le stockage local va sur le disque dur, il peut donc laisser des caches par inadvertance et affecter les journaux, ce qui est une autre raison d'envisager un mécanisme de transport différent pour le les données réelles.
dandavis
1
J'ai fait quelque chose lié il y a quelque temps: danml.com/js/localstorageevents.js , que l'on a une base d'émetteur d'événements et un "écho local" afin que vous puissiez utiliser l'EE pour tout partout.
dandavis
7
Safari ne prend pas en charge BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth
1
Attention
116

Il existe une API moderne dédiée à cet effet - Broadcast Channel

C'est aussi simple que:

var bc = new BroadcastChannel('test_channel');

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

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

Il n'est pas nécessaire que le message soit juste une DOMString, tout type d'objet peut être envoyé.

Probablement, mis à part la propreté de l'API, c'est le principal avantage de cette API - aucune stringification d'objet.

Actuellement pris en charge uniquement dans Chrome et Firefox, mais vous pouvez trouver un polyfill qui utilise localStorage.

utilisateur
la source
3
Attendez, comment savez-vous d'où vient le message? Ignore-t-il les messages provenant du même onglet?
AturSams le
2
@zehelvion: L'expéditeur ne le recevra pas, d'après par exemple ce bel aperçu . En outre, vous pouvez mettre dans le message ce que vous voulez, y compris. un identifiant de l'expéditeur, si nécessaire.
Sz.
7
Il y a un beau projet qui termine cette fonctionnalité dans une bibliothèque multi-navigateurs ici: github.com/pubkey/broadcast-channel
james2doyle
Y a-t-il eu des signaux publics de Safari indiquant si le support de cette API atterrira ou non dans ce navigateur?
Casey
@AturSams Vous vérifiez que MessageEvent.origin, MessageEvent.source ou MessageEvent.ports sont ce que vous voulez. Comme toujours, la documentation est le meilleur endroit pour commencer: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Stefan Mihai Stanescu
40

Pour ceux qui recherchent une solution non basée sur jQuery, il s'agit d'une version JavaScript simple de la solution fournie par 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);
    }
}
Nacho Coloma
la source
1
Pourquoi avez-vous omis l'appel removeItem?
Tomas M du
2
Je me concentrais simplement sur les différences entre jQuery et JavaScript.
Nacho Coloma
J'utilise toujours une lib à cause du polyfill et de la possibilité d'une fonctionnalité non prise en charge!
Amin Rahimi
20

Checkout AcrossTabs - Communication facile entre les onglets du navigateur cross-origin. Il utilise une combinaison d' API postMessage et sessionStorage pour rendre la communication beaucoup plus facile et fiable.


Il existe différentes approches et chacune a ses propres avantages et inconvénients. Permet de discuter de chacun:

  1. Stockage local

    Avantages :

    1. Le stockage Web peut être considéré de manière simpliste comme une amélioration des cookies, offrant une capacité de stockage beaucoup plus grande. Si vous regardez le code source de Mozilla, nous pouvons voir que 5120 Ko ( 5 Mo qui équivaut à 2,5 millions de caractères sur Chrome) est la taille de stockage par défaut pour un domaine entier. Cela vous donne beaucoup plus d'espace pour travailler qu'un cookie typique de 4 Ko.
    2. Les données ne sont pas renvoyées au serveur pour chaque requête HTTP (HTML, images, JavaScript, CSS, etc.) - ce qui réduit le trafic entre le client et le serveur.
    3. Les données stockées dans localStorage persistent jusqu'à leur suppression explicite. Les modifications apportées sont enregistrées et disponibles pour toutes les visites actuelles et futures du site.

    Inconvénients :

    1. Cela fonctionne sur une politique de même origine . Ainsi, les données stockées ne pourront être disponibles que sur la même origine.
  2. Biscuits

    Avantages:

    1. Comparé aux autres, il n'y a rien d'AFAIK.

    Les inconvénients:

    1. La limite de 4K est pour l'ensemble du cookie, y compris le nom, la valeur, la date d'expiration, etc. Pour prendre en charge la plupart des navigateurs, gardez le nom sous 4 000 octets et la taille globale du cookie sous 4 093 octets.
    2. Les données sont renvoyées au serveur pour chaque requête HTTP (HTML, images, JavaScript, CSS, etc.) - augmentant la quantité de trafic entre le client et le serveur.

      En règle générale, les éléments suivants sont autorisés:

      • 300 cookies au total
      • 4096 octets par cookie
      • 20 cookies par domaine
      • 81920 octets par domaine (avec 20 cookies de taille maximale 4096 = 81920 octets.)
  3. sessionStorage

    Avantages:

    1. C'est similaire à localStorage.
    2. Les modifications ne sont disponibles que par fenêtre (ou onglet dans les navigateurs comme Chrome et Firefox). Les modifications apportées sont enregistrées et disponibles pour la page en cours, ainsi que les futures visites du site dans la même fenêtre. Une fois la fenêtre fermée, le stockage est supprimé

    Les inconvénients:

    1. Les données ne sont disponibles que dans la fenêtre / l'onglet dans lequel elles ont été définies.
    2. Les données ne sont pas persistantes, c'est-à-dire qu'elles seront perdues une fois la fenêtre / l'onglet fermé.
    3. Comme localStorage, tt fonctionne sur la politique de même origine . Ainsi, les données stockées ne pourront être disponibles que sur la même origine.
  4. PostMessage

    Avantages:

    1. Permet en toute sécurité la communication inter-origine .
    2. En tant que point de données, l'implémentation WebKit (utilisée par Safari et Chrome) n'applique actuellement aucune limite (autre que celles imposées par manque de mémoire).

    Les inconvénients:

    1. Besoin d'ouvrir une fenêtre à partir de la fenêtre actuelle et de ne pouvoir communiquer que tant que vous gardez les fenêtres ouvertes.
    2. Problèmes de sécurité - L'envoi de chaînes via postMessage signifie que vous récupérerez d'autres événements postMessage publiés par d'autres plug-ins JavaScript, assurez-vous donc de mettre en œuvre unetargetOriginvérification et une vérification de l'intégritédes données transmises à l'écouteur de messages.
  5. Une combinaison de PostMessage + SessionStorage

    Utilisation de postMessage pour communiquer entre plusieurs onglets et en même temps utilisation de sessionStorage dans tous les onglets / fenêtres nouvellement ouverts pour conserver les données transmises. Les données seront conservées tant que les onglets / fenêtres resteront ouverts. Ainsi, même si l'onglet / la fenêtre d'ouverture se ferme, les onglets / fenêtres ouverts auront toutes les données même après avoir été actualisés.

J'ai écrit une bibliothèque JavaScript pour cela, nommée AcrossTabs qui utilise l'API postMessage pour communiquer entre les onglets / fenêtres d'origine croisée et sessionStorage pour conserver l'identité des onglets / fenêtres ouverts aussi longtemps qu'ils vivent.

softvar
la source
En utilisant AcrossTabs, est-il possible d'ouvrir un site Web différent dans un autre onglet et d'en récupérer les données dans l'onglet parent? J'aurai les détails d'authentification pour un autre site Web.
Madhur Bhaiya
1
Oui, vous pouvez @MadhurBhaiya
softvar
Le plus grand avantage du cookie est qu'il permet une origine croisée du même domaine, ce qui est généralement utile lorsque vous avez un ensemble d'origines telles que "a.target.com", "b.target.com", etc.
StarPinkER
7

Les travailleurs partagés sont une autre méthode que les gens devraient envisager d'utiliser. Je sais que c'est un concept de pointe, mais vous pouvez créer un relais sur un Shared Worker qui est BEAUCOUP plus rapide que le stockage local et qui ne nécessite pas de relation entre la fenêtre parent / enfant, tant que vous êtes sur la même origine.

Voir ma réponse ici pour une discussion que j'ai faite à ce sujet.

datasedai
la source
7

Il existe un petit composant open-source pour synchroniser / communiquer entre les onglets / fenêtres de la même origine (avertissement - je suis l'un des contributeurs!) Basé sur 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 J'ai pris la liberté de le recommander ici car la plupart des composants "lock / mutex / sync" échouent sur les connexions websocket lorsque les événements se produisent presque simultanément

Alex
la source
6

J'ai créé une bibliothèque sysend.js , elle est très petite, vous pouvez vérifier son code source. La bibliothèque n'a pas de dépendances externes.

Vous pouvez l'utiliser pour la communication entre les onglets / fenêtres dans le même navigateur et domaine. La bibliothèque utilise BroadcastChannel, s'il est pris en charge, ou un événement de stockage de localStorage.

L'API est très simple:

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

lorsque votre navigateur prend en charge BroadcastChannel, il a envoyé un objet littéral (mais il est en fait auto-sérialisé par le navigateur) et sinon il est d'abord sérialisé en JSON et désérialisé à l'autre extrémité.

La version récente a également une API d'assistance pour créer un proxy pour la communication entre domaines. (il nécessite un seul fichier html sur le domaine cible).

Voici la démo .

MODIFIER :

La nouvelle version prend également en charge la communication entre domaines , si vous incluez un proxy.htmlfichier spécial sur le domaine cible et appelez la proxyfonction à partir du domaine source:

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

(proxy.html c'est un fichier html très simple, qui n'a qu'une seule balise de script avec la bibliothèque).

Si vous souhaitez une communication bidirectionnelle, vous devez faire de même sur le target.comdomaine.

REMARQUE : si vous implémentez la même fonctionnalité à l'aide de localStorage, il y a un problème dans IE. L'événement de stockage est envoyé à la même fenêtre, qui a déclenché l'événement et pour les autres navigateurs, il n'est appelé que pour les autres onglets / fenêtres.

jcubic
la source
2
Je voulais juste te féliciter pour ça. Joli petit ajout qui est simple et me permet de communiquer entre mes onglets pour empêcher le logiciel d'avertissement de déconnexion de renvoyer les gens. Bon travail. Je le recommande vivement si vous souhaitez une solution de messagerie simple à utiliser.
BrownPony
4

J'ai créé un module qui fonctionne de la même manière que le canal de diffusion officiel mais qui a des solutions de secours basées sur localstorage, indexeddb et unix-sockets. Cela garantit qu'il fonctionne toujours même avec les Webworkers ou NodeJS. Voir pubkey: BroadcastChannel

pubkey
la source
1

J'ai écrit un article à ce sujet sur mon blog: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- application Web .

En utilisant une bibliothèque que j'ai créée, storageManagervous pouvez y parvenir comme suit:

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

Il existe également d'autres méthodes pratiques pour gérer d'autres scénarios

adentum
la source
0

Ceci est une storagepartie du développement de Tomas M answer for Chrome. Il faut ajouter un auditeur

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

Charger / enregistrer l'élément dans le stockage ne lance pas cet événement - nous DEVONS le déclencher manuellement en

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

et maintenant, tous les onglets ouverts recevront l'événement

Kamil Kiełczewski
la source