web-dev-qa-db-fra.com

Communication entre onglets ou fenêtres

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

  1. en utilisant l'objet window
  2. postMessage
  3. biscuits
  4. stockage local

La première solution est probablement la pire des solutions - vous devez ouvrir une fenêtre à partir de votre fenêtre actuelle et vous ne pouvez alors communiquer que si vous maintenez 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, qui utilise postMessage, permet probablement la communication entre plusieurs origines, mais présente le même problème que la première. Vous devez gérer un objet window.

Troisièmement, en utilisant des cookies, stockez des données dans le navigateur, ce qui peut ressembler à envoyer 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 le "message" déjà ou pas avant. nettoyer. Vous devez mettre en place une sorte de délai d'attente pour lire le cookie périodiquement. De plus, vous êtes limité par la longueur maximale du cookie, qui est de 4 Ko.

La quatrième solution, utilisant localStorage, semblait dépasser les limites des cookies et pouvait même être écoutée en utilisant des événements. Son utilisation est décrite 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, consistant à utiliser BroadcastChannel. Voir l’autre réponse pour un exemple simple décrivant comment transmettre facilement un message entre des onglets à l’aide de BroadcastChannel.

113
Tomas M

Edit 2018: Vous pouvez mieux utiliser BroadcastChannel à cette fin, voir les réponses ci-dessous. Cependant, si vous préférez toujours utiliser le stockage local 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_receive sera 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. Ceci est très pratique car vous pouvez réinitialiser la valeur juste après son paramétrage pour nettoyer efficacement toutes les traces. Voici les fonctions pour la 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 sont liés à l'événement onstorage et que ces deux fonctions sont implémentées, vous pouvez simplement diffuser un message à d'autres onglets appelant, par exemple:

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

N'oubliez pas que l'envoi du même message exactement deux fois ne sera propagé qu'une seule fois. Si vous devez répéter les messages, ajoutez-leur un identificateur 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, mais uniquement les 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 de setItem () avant le removeItem (). De mon côté, le navigateur met le déchargement en attente jusqu'à la fin de la fonction message_broadcast(). J'ai essayé de mettre là un très long cycle pour () et il attendait toujours que le cycle se termine avant de se fermer. Si l'utilisateur supprime l'onglet juste entre les deux, le navigateur n'aura pas assez de temps pour enregistrer le message sur le disque. Cette approche me semble donc être un moyen sûr d'envoyer des messages sans aucune trace. Commentaires bienvenus.

110
Tomas M

Une API moderne est 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 simplement une chaîne DOMString, tout type d'objet peut être envoyé.

Mis à part la propreté de l'API, c'est probablement le principal avantage de cette API: pas de stringification d'objet.

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

71
user

Pour ceux qui recherchent une solution non basée sur jQuery, voici 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);
    }
}
29
Nacho Coloma

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


Il existe différentes approches et chacune a ses avantages et ses inconvénients. Permet de discuter 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, vous pouvez voir que 5120KB (5 Mo, ce qui correspond à 2,5 millions de caractères sur Chrome) est la taille de stockage par défaut d'un domaine entier. Cela vous donne considérablement plus d'espace pour travailler avec un cookie typique de 4Ko.
    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 volume de trafic entre le client et le serveur.
    3. Les données stockées dans localStorage persistent jusqu'à ce qu'elles soient explicitement supprimées. Les modifications apportées sont enregistrées et disponibles pour toutes les visites actuelles et futures du site.

    Les inconvénients:

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

    Avantages:

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

    Les inconvénients:

    1. La limite de 4K s'applique à l'ensemble du cookie, y compris son nom, sa valeur, sa date d'expiration, etc. Pour prendre en charge la plupart des navigateurs, conservez le nom sous 4000 octets et la taille totale du cookie sous 4093 octets.
    2. Les données sont renvoyées au serveur pour chaque requête HTTP (HTML, images, JavaScript, CSS, etc.), ce qui augmente 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 tels que Chrome et Firefox). Les modifications apportées sont enregistrées et disponibles pour la page en cours, ainsi que les visites futures sur le 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 sont disponibles uniquement 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 politique de même origine . Ainsi, les données stockées ne seront disponibles que sur la même origine.
  4. PostMessage

    Avantages:

    1. Active en toute sécurité origine croisée communication.
    2. En tant que point de données, l’implémentation WebKit (utilisée par Safari et Chrome) n’impose actuellement aucune limite (autre que celles imposées par un manque de mémoire).

    Les inconvénients:

    1. Vous devez ouvrir une fenêtre à partir de la fenêtre actuelle, puis ne pouvez 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 plugins JavaScript. Veillez donc à implémenter une targetOrigin et un contrôle d'intégrité des données transmises. l'écouteur de messages.
  5. Une combinaison de PostMessage + SessionStorage

    En utilisant postMessage pour communiquer entre plusieurs onglets et en même temps, en utilisant sessionStorage dans tous les onglets/fenêtres récemment 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 est fermé, les données entières des onglets/fenêtres ouverts seront conservées même après actualisation.

J'ai écrit une bibliothèque JavaScript pour cela, nommée AcrossTabs , qui utilise l'API postMessage pour communiquer entre les onglets/fenêtres cross-Origin et sessionStorage afin de conserver l'identité des onglets/fenêtres ouverts tant qu'ils sont conservés.

14
softvar

Les travailleurs partagés sont une autre méthode que les utilisateurs devraient envisager d'utiliser. Je sais que le concept est à la pointe de la technologie, mais vous pouvez créer sur un travailleur partagé un relais beaucoup plus rapide que le stockage local et ne nécessitant 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.

7
datasedai

Il existe un minuscule composant open-source pour synchroniser/communiquer entre des onglets/fenêtres de la même origine (disclaimer - 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

P.S. J'ai pris la liberté de le recommander ici car la plupart des composants "lock/mutex/sync" échouent sur les connexions websocket lorsque des événements se produisent presque simultanément.

6
Alex

J'ai créé un module qui fonctionne de la même manière que le Broadcastchannel officiel mais comporte des replis basés sur localstorage, indexeddb et unix-sockets. Cela garantit que cela fonctionne toujours, même avec Webworkers ou NodeJS. Voir pubkey: BroadcastChannel

2
pubkey

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 du même navigateur et du même domaine. La bibliothèque utilise BroadcastChannel, si 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 envoie un objet littéral et sinon, il est d'abord sérialisé en JSON et désérialisé de l'autre côté.

Les versions récentes ont également une API auxiliaire pour créer un proxy pour la communication entre domaines. (Cela nécessite un fichier HTML unique sur le domaine cible).

Voici demo .

NOTE: Si vous voulez implémenter la même fonctionnalité en utilisant localStorage, il y a un problème dans IE. L'événement de stockage est envoyé à la même fenêtre, ce qui a déclenché l'événement et n'est appelé pour d'autres navigateurs que pour d'autres onglets/fenêtres.

0
jcubic

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- une application web .

En utilisant une bibliothèque que j'ai créée storageManager, vous 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.

0
adentum