Comunicação entre guias ou janelas

176

Eu estava procurando uma maneira de se comunicar entre várias guias ou janelas em um navegador (no mesmo domínio, não no CORS) sem deixar vestígios. Havia várias soluções:

  1. usando objeto de janela
  2. postar mensagem
  3. biscoitos
  4. localStorage

A primeira é provavelmente a pior solução - você precisa abrir uma janela a partir da janela atual e só pode se comunicar desde que mantenha as janelas abertas. Se você recarregar a página em qualquer uma das janelas, provavelmente perderá a comunicação.

A segunda abordagem, usando o postMessage, provavelmente habilita a comunicação entre origens, mas sofre o mesmo problema da primeira abordagem. Você precisa manter um objeto de janela.

Terceira maneira, usando cookies, armazene alguns dados no navegador, que podem parecer efetivamente enviar uma mensagem para todas as janelas no mesmo domínio, mas o problema é que você nunca pode saber se todas as guias já leem ou não a "mensagem" antes limpando. Você precisa implementar algum tipo de tempo limite para ler o cookie periodicamente. Além disso, você está limitado pelo tamanho máximo do cookie, que é de 4KB.

A quarta solução, usando o localStorage, parecia superar as limitações dos cookies e pode até ser ouvida usando eventos. Como usá-lo é descrito na resposta aceita.

Editar 2018: a resposta aceita ainda funciona, mas há uma solução mais recente para navegadores modernos, para usar o BroadcastChannel. Veja a outra resposta para um exemplo simples que descreve como transmitir facilmente mensagens entre guias usando BroadcastChannel.

Tomas M
fonte
4
Por que essa pergunta foi encerrada como "muito ampla" quando praticamente as mesmas perguntas estão abertas há anos? Enviando uma mensagem para todas as janelas / guias abertas usando JavaScript , stackoverflow.com/questions/2236828/… , como você se comunica entre duas guias / janelas do navegador? e mais alguns.
Dan Dascalescu
Criei uma biblioteca sobre localStorage e sessionStorage para gerenciar o armazenamento de dados do lado do cliente. Você pode fazer coisas como storageManager.savePermanentData ('data', 'key'); ou storageManager.saveSyncedSessionData ('data', 'key'); com base em como você deseja que seus dados se comportem. Simplifica realmente o processo. Artigo completo aqui: ebenmonney.com/blog/…
adentum
2
Eu criei a biblioteca sysend.js alguns anos atrás, na versão mais recente, ela usa BroadcastChannel. Você pode testar a biblioteca abrindo esta página duas vezes jcubic.pl/sysend.php ; ela também funcionará com origem diferente se você fornecer o proxy iframe.
Jcubic #
Considero o subdomínio como uma mesma origem? Quero dizer, eu tenho abaixo de três domínios, eles se comunicam através da API broadcastchannel? alpha.firstdomain.com, beta.firstdomain.com, gama.firstdomain.com
Tejas Patel:

Respostas:

142

Editar 2018: é melhor usar o BroadcastChannel para esse fim, veja outras respostas abaixo. No entanto, se você ainda preferir usar o armazenamento local para comunicação entre guias, faça o seguinte:

Para ser notificado quando uma guia envia uma mensagem para outras guias, basta vincular o evento 'storage'. Em todas as guias, faça o seguinte:

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

A função message_receiveserá chamada toda vez que você definir qualquer valor de localStorage em qualquer outra guia. O ouvinte de eventos também contém os dados recentemente configurados para localStorage, portanto, você nem precisa analisar o próprio objeto localStorage. Isso é muito útil, pois você pode redefinir o valor logo após a configuração, para efetivamente limpar qualquer rastreio. Aqui estão as funções para mensagens:

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

Portanto, agora que suas guias se ligam ao evento onstorage e você tem essas duas funções implementadas, você pode simplesmente transmitir uma mensagem para outras guias chamando, por exemplo:

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

Lembre-se de que o envio exato da mesma mensagem duas vezes será propagado apenas uma vez; portanto, se você precisar repetir mensagens, adicione um identificador exclusivo a elas, como

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

Lembre-se também de que a guia atual que transmite a mensagem não a recebe de fato, apenas outras guias ou janelas no mesmo domínio.

Você pode perguntar o que acontece se o usuário carregar uma página da Web diferente ou fechar sua guia logo após a chamada setItem () antes do removeItem (). Bem, do meu próprio teste, o navegador coloca a descarga em espera até que toda a função message_broadcast()esteja concluída. Eu testei para colocar algum tempo muito longo no ciclo () e ele ainda esperou o ciclo terminar antes de fechar. Se o usuário matar a guia apenas no meio, o navegador não terá tempo suficiente para salvar a mensagem em disco, portanto, essa abordagem me parece uma maneira segura de enviar mensagens sem nenhum vestígio. Comentários bem-vindos.

Tomas M
fonte
1
você pode ignorar o evento remove antes de chamar JSON.parse ()?
precisa saber é
1
lembre-se do limite de dados do evento, incluindo dados locaisStorage preexistentes. pode ser melhor / mais seguro usar os eventos de armazenamento apenas para mensagens em vez de remessa. como quando você recebe um cartão postal pedindo para você pegar um pacote na agência postal ... também, o armazenamento local vai para o disco rígido, para que possa deixar caches inadvertidos e afetar os logs, que é outro motivo para considerar um mecanismo de transporte diferente para o dados reais.
precisa saber é
1
Eu fiz algo relacionado há algum tempo: danml.com/js/localstorageevents.js , que possui uma base de emmiter de eventos e "eco local" para que você possa usar o EE para tudo em qualquer lugar.
dandavis
7
Safari não suportam BroadcastChannel - caniuse.com/#feat=broadcastchannel
Srikanth
1
Apenas um heads-up, você também pode usar trabalhadores compartilhados: developer.mozilla.org/en-US/docs/Web/API/SharedWorker (que tem melhor suporte em todos os navegadores)
Seblor
116

Existe uma API moderna dedicada para esse fim - Broadcast Channel

É tão fácil quanto:

var bc = new BroadcastChannel('test_channel');

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

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

Não há necessidade de a mensagem ser apenas uma DOMString, qualquer tipo de objeto pode ser enviado.

Provavelmente, além da limpeza da API, é o principal benefício dessa API - nenhuma string de objeto.

Atualmente suportado apenas no Chrome e Firefox, mas você pode encontrar um polyfill que usa o localStorage.

do utilizador
fonte
3
Espere, como você sabe de onde vem a mensagem? Ignora as mensagens que vêm da mesma guia?
precisa saber é o seguinte
2
@zehelvion: O remetente não o receberá, de acordo com, por exemplo, esta bela visão geral . Além disso, você pode colocar na mensagem o que quiser, incl. algum ID do remetente, se necessário.
Sz.
7
Há um bom projeto que termina este recurso em uma biblioteca de cross-browser aqui: github.com/pubkey/broadcast-channel
james2doyle
Houve algum sinal público do Safari sobre se o suporte a essa API chegará ou não nesse navegador?
Casey
@AturSams Verifique se MessageEvent.origin, MessageEvent.source ou MessageEvent.ports são o que você deseja. Como sempre, a documentação é o melhor lugar para começar: developer.mozilla.org/pt-BR/docs/Web/API/MessageEvent
Stefan Mihai Stanescu
40

Para quem procura uma solução não baseada em jQuery, esta é uma versão JavaScript simples da solução fornecida 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
fonte
1
Por que você omitiu a chamada removeItem?
Tomas M
2
Eu estava apenas focando nas diferenças entre jQuery e JavaScript.
Nacho Coloma
Eu sempre uso uma lib por causa do polyfill e possibilidade de recurso não suportado!
Amin Rahimi
20

Checkout AcrossTabs - Comunicação fácil entre as guias do navegador de várias origens. Ele usa uma combinação da API postMessage e sessionStorage para tornar a comunicação muito mais fácil e confiável.


Existem abordagens diferentes e cada uma tem suas próprias vantagens e desvantagens. Vamos discutir cada um:

  1. LocalStorage

    Prós :

    1. O armazenamento na Web pode ser visto de maneira simplista como uma melhoria nos cookies, fornecendo uma capacidade de armazenamento muito maior. Se você olhar para o código-fonte da Mozilla, podemos ver que 5120 KB ( 5 MB é igual a 2,5 milhões de caracteres no Chrome) é o tamanho de armazenamento padrão para um domínio inteiro. Isso oferece muito mais espaço para trabalhar do que um cookie típico de 4KB.
    2. Os dados não são enviados de volta ao servidor para todas as solicitações HTTP (HTML, imagens, JavaScript, CSS etc.) - reduzindo a quantidade de tráfego entre cliente e servidor.
    3. Os dados armazenados no localStorage persistem até serem excluídos explicitamente. As alterações feitas são salvas e disponíveis para todas as visitas atuais e futuras ao site.

    Contras :

    1. Funciona na política de mesma origem . Portanto, os dados armazenados somente estarão disponíveis na mesma origem.
  2. Biscoitos

    Prós:

    1. Comparado a outros, não há nada AFAIK.

    Contras:

    1. O limite de 4K é para todo o cookie, incluindo nome, valor, data de validade etc. Para dar suporte à maioria dos navegadores, mantenha o nome abaixo de 4000 bytes e o tamanho geral do cookie abaixo de 4093 bytes.
    2. Os dados são enviados de volta ao servidor para cada solicitação HTTP (HTML, imagens, JavaScript, CSS, etc.) - aumentando a quantidade de tráfego entre cliente e servidor.

      Normalmente, o seguinte é permitido:

      • 300 cookies no total
      • 4096 bytes por cookie
      • 20 cookies por domínio
      • 81920 bytes por domínio (dados 20 cookies de tamanho máximo 4096 = 81920 bytes).
  3. sessionStorage

    Prós:

    1. É semelhante a localStorage.
    2. As alterações estão disponíveis apenas por janela (ou guia em navegadores como Chrome e Firefox). As alterações feitas são salvas e disponíveis para a página atual, bem como futuras visitas ao site na mesma janela. Depois que a janela é fechada, o armazenamento é excluído

    Contras:

    1. Os dados estão disponíveis apenas dentro da janela / guia em que foram configurados.
    2. Os dados não são persistentes, ou seja, serão perdidos quando a janela / guia for fechada.
    3. Como localStorage, tt funciona na política de mesma origem . Portanto, os dados armazenados somente estarão disponíveis na mesma origem.
  4. Postar mensagem

    Prós:

    1. Permite com segurança a comunicação entre origens .
    2. Como ponto de dados, a implementação do WebKit (usada pelo Safari e Chrome) atualmente não impõe nenhum limite (além daqueles impostos pela falta de memória).

    Contras:

    1. É necessário abrir uma janela a partir da janela atual e, em seguida, pode se comunicar apenas enquanto você mantiver as janelas abertas.
    2. Preocupações com segurança - O envio de strings via postMessage é que você selecionará outros eventos postMessage publicados por outros plugins JavaScript, portanto, implemente umatargetOriginverificação de integridade dos dados que estão sendo transmitidos ao ouvinte de mensagens.
  5. Uma combinação de PostMessage + SessionStorage

    Usando postMessage para se comunicar entre várias guias e ao mesmo tempo usando sessionStorage em todas as guias / janelas recém-abertas para manter os dados sendo transmitidos. Os dados serão mantidos enquanto as guias / janelas permanecerem abertas. Portanto, mesmo que a aba / janela do abridor seja fechada, as abas / janelas abertas terão todos os dados mesmo após a atualização.

Eu escrevi uma biblioteca JavaScript para isso, chamada AcrossTabs, que usa a API postMessage para se comunicar entre guias / janelas de origem cruzada e sessionStorage para manter a identidade das guias / janelas abertas enquanto elas durarem.

softvar
fonte
Usando AcrossTabs, é possível abrir um site diferente em outra guia e obter os dados dele na guia pai? Terei detalhes de autenticação para outro site.
Madhur Bhaiya
1
Sim, você pode @MadhurBhaiya
softvar
A maior vantagem do cookie é que ele permite o mesmo domínio de origem cruzada, o que geralmente é útil quando você tem um conjunto de origens como "a.target.com", "b.target.com" etc.
StarPinkER em
7

Outro método que as pessoas devem considerar usar é Trabalhadores Compartilhados. Eu sei que é um conceito de vanguarda, mas você pode criar uma retransmissão em um trabalhador compartilhado MUITO mais rápido que o armazenamento local e não exige um relacionamento entre a janela pai / filho, desde que você tenha a mesma origem.

Veja minha resposta aqui para alguma discussão que fiz sobre isso.

datasedai
fonte
7

Há um pequeno componente de código-fonte aberto para sincronizar / comunicar-se entre guias / janelas da mesma origem (isenção de responsabilidade - eu sou um dos colaboradores!) Com base 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 Tomei a liberdade de recomendá-lo aqui, pois a maioria dos componentes "lock / mutex / sync" falha nas conexões do soquete da web quando os eventos acontecem quase simultaneamente

Alex
fonte
6

Eu criei uma biblioteca sysend.js , é muito pequena, você pode verificar o código fonte. A biblioteca não possui dependências externas.

Você pode usá-lo para comunicação entre guias / janelas no mesmo navegador e domínio. A biblioteca usa BroadcastChannel, se suportado, ou evento de armazenamento de localStorage.

API é muito simples:

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

quando seu navegador suporta BroadcastChannel, ele enviou um objeto literal (mas na verdade é serializado automaticamente pelo navegador) e, se não, é serializado para JSON primeiro e desserializado por outro lado.

A versão recente também possui a API auxiliar para criar proxy para a comunicação entre domínios. (requer um arquivo html único no domínio de destino).

Aqui está uma demonstração .

EDIT :

A nova versão também oferece suporte à comunicação entre domínios , se você incluir um proxy.htmlarquivo especial no domínio de destino e chamar a proxyfunção do domínio de origem:

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

(proxy.html é um arquivo html muito simples, que possui apenas uma tag de script na biblioteca).

Se você quiser uma comunicação bidirecional, precisará fazer o mesmo no target.comdomínio.

NOTA : Se você implementar a mesma funcionalidade usando o localStorage, haverá um problema no IE. O evento de armazenamento é enviado para a mesma janela, que acionou o evento e, para outros navegadores, é invocado apenas para outras guias / janelas.

jcubic
fonte
2
Só queria te dar um pouco de kudo por isso. Uma pequena adição agradável e simples que é simples e permite que eu me comunique entre minhas guias para impedir que o software de aviso de logout inicie as pessoas. Bom trabalho. Eu recomendo isso se você quiser uma solução de mensagens simples de usar.
BrownPony
4

Criei um módulo que funciona igual ao Broadcastchannel oficial, mas possui fallbacks baseados em armazenamento local, indexeddb e unix-sockets. Isso garante que sempre funcione mesmo com Webworkers ou NodeJS. Veja pubkey: BroadcastChannel

pubkey
fonte
1

Eu escrevi um artigo sobre isso no meu blog: http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a- aplicação web .

Usando uma biblioteca que eu criei, storageManagervocê pode fazer isso da seguinte maneira:

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

Existem outros métodos convenientes para lidar com outros cenários também

adentum
fonte
0

Esta é uma storageparte do desenvolvimento da resposta do Tomas M para o Chrome. Devemos adicionar ouvinte

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

Carregar / salvar item no armazenamento para não executar este evento - DEVEMOS acioná-lo manualmente

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

e agora, todas as guias abertas receberão evento

Kamil Kiełczewski
fonte