Comunicación entre pestañas o ventanas

176

Estaba buscando una manera de comunicarme entre múltiples pestañas o ventanas en un navegador (en el mismo dominio, no CORS) sin dejar rastros. Hubo varias soluciones:

  1. usando un objeto de ventana
  2. postMessage
  3. galletas
  4. almacenamiento local

La primera es probablemente la peor solución: debe abrir una ventana desde su ventana actual y luego puede comunicarse solo mientras mantenga las ventanas abiertas. Si vuelve a cargar la página en cualquiera de las ventanas, lo más probable es que haya perdido la comunicación.

El segundo enfoque, usando postMessage, probablemente habilita la comunicación de origen cruzado, pero sufre el mismo problema que el primer enfoque. Necesita mantener un objeto de ventana.

La tercera forma, usando cookies, almacena algunos datos en el navegador, lo que efectivamente puede parecer enviar un mensaje a todas las ventanas en el mismo dominio, pero el problema es que nunca se puede saber si todas las pestañas leen el "mensaje" ya o no antes limpiar. Debe implementar algún tipo de tiempo de espera para leer la cookie periódicamente. Además, está limitado por la longitud máxima de la cookie, que es 4KB.

La cuarta solución, usando localStorage, parecía superar las limitaciones de las cookies, e incluso se puede escuchar usando eventos. Cómo usarlo se describe en la respuesta aceptada.

Editar 2018: la respuesta aceptada aún funciona, pero hay una solución más nueva para los navegadores modernos, usar BroadcastChannel. Consulte la otra respuesta para ver un ejemplo simple que describe cómo transmitir fácilmente mensajes entre pestañas utilizando BroadcastChannel.

Tomás M
fuente
44
¿Por qué esta pregunta se cerró como "demasiado amplia" cuando casi las mismas preguntas han estado abiertas durante años? Enviando un mensaje a todas las ventanas / pestañas abiertas usando JavaScript , stackoverflow.com/questions/2236828/… , ¿Cómo se comunica entre 2 pestañas / ventanas del navegador? y algunos mas
Dan Dascalescu
Creé una biblioteca sobre localStorage y sessionStorage para administrar el almacenamiento de datos del lado del cliente. Puede hacer cosas como storageManager.savePermanentData ('data', 'key'); o storageManager.saveSyncedSessionData ('datos', 'clave'); según cómo desee que se comporten sus datos. Realmente simplifica el proceso. Artículo completo aquí: ebenmonney.com/blog/…
adentum
2
Creé la biblioteca sysend.js hace unos años, en la última versión usa BroadcastChannel. Puede probar la biblioteca abriendo esta página dos veces jcubic.pl/sysend.php , también funciona con un origen diferente si proporciona un proxy iframe.
jcubic
¿Considero el subdominio como un mismo origen? Quiero decir, tengo menos de tres dominios, ¿se comunican a través de la API de broadcastchannel? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel

Respuestas:

142

Editar 2018: es mejor que use BroadcastChannel para este propósito, vea otras respuestas a continuación. Sin embargo, si aún prefiere utilizar el almacenamiento local para la comunicación entre pestañas, hágalo de esta manera:

Para recibir una notificación cuando una pestaña envía un mensaje a otras pestañas, simplemente necesita vincular el evento 'almacenamiento'. En todas las pestañas, haz esto:

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

Se message_receivellamará a la función cada vez que establezca cualquier valor de localStorage en cualquier otra pestaña. El detector de eventos también contiene los datos recién configurados en localStorage, por lo que ni siquiera necesita analizar el objeto localStorage en sí. Esto es muy útil porque puede restablecer el valor justo después de que se configuró, para limpiar eficazmente cualquier rastro. Aquí hay funciones para la mensajería:

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

Entonces, una vez que sus pestañas se unen al evento de almacenamiento y que tiene implementadas estas dos funciones, simplemente puede transmitir un mensaje a otras pestañas llamando, por ejemplo:

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

Recuerde que enviar exactamente el mismo mensaje dos veces se propagará solo una vez, por lo que si necesita repetir mensajes, agrégueles un identificador único, como

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

Recuerde también que la pestaña actual que transmite el mensaje en realidad no lo recibe, solo otras pestañas o ventanas en el mismo dominio.

Puede preguntar qué sucede si el usuario carga una página web diferente o cierra su pestaña justo después de la llamada setItem () antes de removeItem (). Bueno, según mis propias pruebas, el navegador pone la descarga en espera hasta que la función completa message_broadcast()haya finalizado. Probé para poner dentro un ciclo muy largo para () y todavía esperó a que el ciclo terminara antes de cerrar. Si el usuario mata la pestaña justo en el medio, entonces el navegador no tendrá tiempo suficiente para guardar el mensaje en el disco, por lo que este enfoque me parece una forma segura de cómo enviar mensajes sin ningún rastro. Comentarios bienvenidos

Tomás M
fuente
1
¿Puedes ignorar el evento remove antes de invocar JSON.parse ()?
dandavis
1
tenga en cuenta el límite de datos del evento, incluidos los datos de almacenamiento local preexistentes. podría ser mejor / más seguro usar los eventos de almacenamiento solo para enviar mensajes en lugar de enviarlos. como cuando recibe una tarjeta postal que le dice que recoja un paquete en la oficina de correos ... también, el almacenamiento local va al disco duro, por lo que podría dejar cachés inadvertidos y afectar los registros, que es otra razón para considerar un mecanismo de transporte diferente para el Información actual.
dandavis
1
Hice algo relacionado hace un tiempo: danml.com/js/localstorageevents.js , ese tiene una base de emisor de eventos y "eco local" para que pueda usar el EE para todo en todas partes.
dandavis
77
Safari no es compatible con BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth
1
Solo un aviso, también puede usar trabajadores compartidos: developer.mozilla.org/en-US/docs/Web/API/SharedWorker (que tiene mejor soporte en todos los navegadores)
Seblor
116

Hay una API moderna dedicada para este propósito: Broadcast Channel

Es tan fácil como:

var bc = new BroadcastChannel('test_channel');

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

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

No es necesario que el mensaje sea solo un DOMString, se puede enviar cualquier tipo de objeto.

Probablemente, aparte de la limpieza de la API, es el principal beneficio de esta API: no hay cadena de objetos.

Actualmente solo se admite en Chrome y Firefox, pero puede encontrar un polyfill que use localStorage.

usuario
fuente
3
Espera, ¿cómo sabes de dónde viene el mensaje? ¿Ignora los mensajes que provienen de la misma pestaña?
AturSams
2
@zehelvion: el remitente no lo recibirá, por ejemplo, según esta buena descripción . Además, puedes poner en el mensaje lo que quieras, incl. alguna identificación del remitente, si es necesario.
Sz.
77
Hay un buen proyecto que resume esta característica en una biblioteca de navegador cruzado aquí: github.com/pubkey/broadcast-channel
james2doyle
¿Ha habido alguna señal pública de Safari sobre si el soporte para esta API aterrizará o no en ese navegador?
Casey
@AturSams Usted verifica que MessageEvent.origin, MessageEvent.source o MessageEvent.ports son lo que desea. Como siempre, la documentación es el mejor lugar para comenzar: developer.mozilla.org/en-US/docs/Web/API/MessageEvent
Stefan Mihai Stanescu
40

Para aquellos que buscan una solución no basada en jQuery, esta es una versión simple de JavaScript de la solución provista por 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
fuente
1
¿Por qué omitió la llamada removeItem?
Tomas M
2
Me estaba centrando en las diferencias entre jQuery y JavaScript.
Nacho Coloma
¡siempre uso una lib debido a polyfill y la posibilidad de una función no compatible!
Amin Rahimi
20

Checkout AcrossTabs : comunicación fácil entre pestañas de navegador de origen cruzado. Utiliza una combinación de API postMessage y sessionStorage para hacer que la comunicación sea mucho más fácil y confiable.


Existen diferentes enfoques y cada uno tiene sus propias ventajas y desventajas. Vamos a discutir cada uno:

  1. Almacenamiento local

    Pros :

    1. El almacenamiento web se puede ver de manera simplista como una mejora en las cookies, proporcionando una capacidad de almacenamiento mucho mayor. Si observa el código fuente de Mozilla, podemos ver que 5120 KB ( 5 MB, que equivale a 2.5 millones de caracteres en Chrome) es el tamaño de almacenamiento predeterminado para un dominio completo. Esto le da mucho más espacio para trabajar que una cookie típica de 4KB.
    2. Los datos no se envían de vuelta al servidor para cada solicitud HTTP (HTML, imágenes, JavaScript, CSS, etc.), lo que reduce la cantidad de tráfico entre el cliente y el servidor.
    3. Los datos almacenados en localStorage persisten hasta que se eliminen explícitamente. Los cambios realizados se guardan y están disponibles para todas las visitas actuales y futuras al sitio.

    Contras :

    1. Funciona en la política del mismo origen . Por lo tanto, los datos almacenados solo estarán disponibles en el mismo origen.
  2. Galletas

    Pros:

    1. En comparación con otros, no hay nada AFAIK.

    Contras:

    1. El límite de 4K es para toda la cookie, incluido el nombre, el valor, la fecha de caducidad, etc. Para admitir la mayoría de los navegadores, mantenga el nombre por debajo de 4000 bytes y el tamaño total de la cookie por debajo de 4093 bytes.
    2. Los datos se envían de vuelta al servidor para cada solicitud HTTP (HTML, imágenes, JavaScript, CSS, etc.), lo que aumenta la cantidad de tráfico entre el cliente y el servidor.

      Por lo general, se permiten los siguientes:

      • 300 galletas en total
      • 4096 bytes por cookie
      • 20 cookies por dominio
      • 81920 bytes por dominio (Dadas 20 cookies de tamaño máximo 4096 = 81920 bytes).
  3. sessionStorage

    Pros:

    1. Es similar a localStorage.
    2. Los cambios solo están disponibles por ventana (o pestaña en navegadores como Chrome y Firefox). Los cambios realizados se guardan y están disponibles para la página actual, así como para futuras visitas al sitio en la misma ventana. Una vez que se cierra la ventana, se elimina el almacenamiento

    Contras:

    1. Los datos están disponibles solo dentro de la ventana / pestaña en la que se configuraron.
    2. Los datos no son persistentes, es decir, se perderán una vez que se cierre la ventana / pestaña.
    3. Al igual localStorage, tt funciona en la política del mismo origen . Por lo tanto, los datos almacenados solo estarán disponibles en el mismo origen.
  4. PostMessage

    Pros:

    1. Con seguridad permite la comunicación de origen cruzado .
    2. Como punto de datos, la implementación de WebKit (utilizada por Safari y Chrome) actualmente no impone ningún límite (aparte de los impuestos por quedarse sin memoria).

    Contras:

    1. Necesita abrir una ventana desde la ventana actual y luego puede comunicarse solo mientras mantenga las ventanas abiertas.
    2. Problemas de seguridad : el envío de cadenas a través de postMessage es que recogerá otros eventos postMessage publicados por otros complementos de JavaScript, así que asegúrese de implementar unatargetOrigincomprobación de estado de los datos que se transmiten al escucha de mensajes.
  5. Una combinación de PostMessage + SessionStorage

    Usando postMessage para comunicarse entre múltiples pestañas y al mismo tiempo usando sessionStorage en todas las pestañas / ventanas recién abiertas para persistir la transmisión de datos. Los datos se conservarán mientras las pestañas / ventanas permanezcan abiertas. Por lo tanto, incluso si la pestaña / ventana del abridor se cierra, las pestañas / ventanas abiertas tendrán todos los datos incluso después de actualizarse.

He escrito una biblioteca de JavaScript para esto, llamada AcrossTabs que utiliza la API postMessage para comunicarse entre pestañas / ventanas de origen cruzado y sessionStorage para mantener la identidad de pestañas / ventanas abiertas mientras vivan.

softvar
fuente
Utilizando AcrossTabs, ¿es posible abrir un sitio web diferente en otra pestaña y obtener los datos de él en la pestaña principal? Tendré detalles de autenticación para el otro sitio web.
Madhur Bhaiya
1
Sí, puedes @MadhurBhaiya
softvar
La mayor ventaja de la cookie es que permite el mismo dominio de origen cruzado, lo que suele ser útil cuando tiene un conjunto de orígenes como "a.target.com", "b.target.com", etc.
StarPinkER
7

Otro método que la gente debería considerar usar es Trabajadores Compartidos. Sé que es un concepto de vanguardia, pero puede crear un relé en un Trabajador compartido que sea MUCHO más rápido que el almacenamiento local y que no requiera una relación entre la ventana padre / hijo, siempre y cuando esté en el mismo origen.

Vea mi respuesta aquí para una discusión que hice sobre esto.

datasedai
fuente
7

Hay un pequeño componente de código abierto para sincronizar / comunicarse entre pestañas / ventanas del mismo origen (descargo de responsabilidad: ¡soy uno de los contribuyentes!) Basado en él 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

PD: Me tomé la libertad de recomendarlo aquí, ya que la mayoría de los componentes "lock / mutex / sync" fallan en las conexiones websocket cuando los eventos ocurren casi simultáneamente

Alex
fuente
6

He creado una biblioteca sysend.js , es muy pequeña, puedes consultar su código fuente. La biblioteca no tiene dependencias externas.

Puede usarlo para la comunicación entre pestañas / ventanas en el mismo navegador y dominio. La biblioteca usa BroadcastChannel, si es compatible, o un evento de almacenamiento de localStorage.

API es muy simple:

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

cuando su navegador admite BroadcastChannel, envía un objeto literal (pero de hecho es auto serializado por el navegador) y si no es serializado primero a JSON y deserializado en el otro extremo.

La versión reciente también tiene API auxiliar para crear proxy para la comunicación entre dominios. (requiere un solo archivo html en el dominio de destino).

Aquí está la demostración .

EDITAR :

La nueva versión también admite la comunicación entre dominios , si incluye un proxy.htmlarchivo especial en el dominio de destino y la proxyfunción de llamada desde el dominio de origen:

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

(proxy.html es un archivo html muy simple, que solo tiene una etiqueta de script con la biblioteca).

Si desea una comunicación bidireccional, debe hacer lo mismo en target.com dominio.

NOTA : Si implementará la misma funcionalidad usando localStorage, hay un problema en IE. El evento de almacenamiento se envía a la misma ventana, lo que activó el evento y para otros navegadores solo se invoca para otras pestañas / ventanas.

jcubic
fuente
2
Solo quería darte algunos kudo's para esto. Bonita y dulce adición que es simple y me permite comunicarme entre mis pestañas para evitar que el software de advertencia de cierre de sesión saque a la gente. Buen trabajo. Le recomiendo esto si desea una solución de mensajería fácil de usar.
BrownPony
4

Creé un módulo que funciona igual que el Broadcastchannel oficial pero tiene fallos basados ​​en localstorage, indexeddb y unix-sockets. Esto asegura que siempre funcione incluso con Webworkers o NodeJS. Ver pubkey: BroadcastChannel

pubkey
fuente
1

Escribí un artículo sobre esto en mi blog: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- Aplicación web .

Usando una biblioteca que creé storageManagerpuedes lograr esto de la siguiente manera:

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

También hay otros métodos convenientes para manejar otros escenarios.

adentum
fuente
0

Esta es una storageparte del desarrollo de la respuesta de Tomas M para Chrome. Debemos agregar oyente

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

Cargar / guardar elemento en el almacenamiento no se ejecuta este evento - DEBEMOS activarlo manualmente

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

y ahora, todas las pestañas abiertas recibirán el evento

Kamil Kiełczewski
fuente