标签或窗口之间的通信

时间:2015-01-30 07:04:50

标签: javascript html5 browser ipc broadcast-channel

我正在寻找一种方法,如何在浏览器中的多个选项卡或窗口之间进行通信(在同一个域上,而不是CORS上),而不会留下痕迹。有几种解决方案:

  1. using window object
  2. postMessage
  3. cookies
  4. localStorage
  5. 第一种可能是最糟糕的解决方案 - 您需要从当前窗口打开一个窗口,然后只要您打开窗户就可以进行通信。如果您在任何窗口中重新加载页面,则很可能会丢失通信。

    使用postMessage的第二种方法可能启用跨源通信,但遇到与第一种方法相同的问题。你需要维护一个窗口对象。

    第三种方式,使用cookie,将一些数据存储在浏览器中,这可以有效地看起来像向同一域中的所有窗口发送消息,但问题是你永远无法知道所有标签是否都读取了"消息"在清理之前是否已经。您必须实现某种超时以定期读取cookie。此外,您受限于最大cookie长度,即4KB。

    第四种解决方案,使用localStorage,似乎克服了cookie的限制,甚至可以使用事件来收听。如何使用它在接受的答案中描述。

    编辑2018:接受的答案仍然有效,但现代浏览器有一个更新的解决方案,使用BroadcastChannel。有关如何使用BroadcastChannel在标签之间轻松传输消息的简单示例,请参阅另一个答案。

10 个答案:

答案 0 :(得分:119)

编辑2018:您最好将BroadcastChannel用于此目的,请参阅下面的其他答案。但是,如果您仍然希望使用localstorage在标签之间进行通信,请按以下方式进行:

为了在标签向其他标签发送消息时收到通知,您只需要绑定'存储'事件。在所有标签中,执行以下操作:

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

每次在任何其他选项卡中设置localStorage的任何值时,都会调用函数message_receive。事件监听器还包含新设置到localStorage的数据,因此您甚至不需要解析localStorage对象本身。这非常方便,因为您可以在设置后立即重置值,以有效清理任何痕迹。以下是消息传递的功能:

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

现在,一旦你的标签绑定在onstorage事件上,并且你实现了这两个功能,你就可以简单地向其他标签调用消息,例如:

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

请记住,两次发送完全相同的消息只会传播一次,因此如果您需要重复消息,请为它们添加一些唯一标识符,例如

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

另请注意,广播邮件的当前标签实际上并未收到,只有同一域中的其他标签或窗口。

如果用户在removeItem()之前的setItem()调用之后加载不同的网页或关闭其标签,您可能会问到会发生什么。好吧,根据我自己的测试,浏览器暂停卸载,直到整个函数message_broadcast()完成。我测试了将一些非常长的for()循环放入其中并且它仍然等待循环结束然后关闭。如果用户只是在两者之间杀死了标签,那么浏览器就没有足够的时间将消息保存到磁盘,因此这种方法对我而言似乎是一种安全的方式来发送没有任何痕迹的消息。欢迎评论。

答案 1 :(得分:78)

有一个专门用于此目的的现代API - Broadcast Channel

这很简单:

{{1}}
  

消息不需要只是一个DOMString,可以发送任何类型的对象。

除了API清洁之外,它可能是此API的主要优点 - 无对象字符串化。

目前supported仅适用于Chrome和Firefox,但您可以找到使用localStorage的polyfill。

答案 2 :(得分:31)

对于那些寻找不基于jQuery的解决方案的人来说,这是Thomas M提供的解决方案的简单JavaScript版本:

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);
    }
}

答案 3 :(得分:15)

结帐AcrossTabs - 跨源浏览器标签之间的轻松沟通。它结合使用postMessagesessionStorage API,使通信更加轻松可靠

有不同的方法,每种方法都有自己的优点和缺点。让我们讨论每个:

  1. <强> LocalStorage

    优点

    1. 可以简单地将Web存储视为对cookie的改进,提供更大的存储容量。如果您查看Mozilla源代码,我们可以看到 5120KB 5MB ,等于Chrome上的 250万个字符)是默认的存储空间大小整个域名。与典型的4KB cookie相比,这为您提供了更多的工作空间。
    2. 每次HTTP请求(HTML,图像,JavaScript,CSS等)都不会将数据发送回服务器 - 减少了客户端和服务器之间的流量。
    3. 存储在localStorage中的数据会一直存在,直到被明确删除。所做的更改已保存,可用于当前和将来对网站的所有访问。
    4. <强>缺点

      1. 适用于same-origin policy。因此,存储的数据只能在同一个源上使用。
    5. <强> Cookies

      <强>优点:

      1. 与其他人相比,AFAIK没什么。
      2. <强>缺点:

        1. 4K限制适用于整个cookie,包括名称,值,到期日期等。要支持大多数浏览器,请将名称保留在4000字节以下,并将整体cookie大小保持在4093字节以下。
        2. 数据会针对每个HTTP请求(HTML,图像,JavaScript,CSS等)发送回服务器 - 增加客户端和服务器之间的流量。

          通常,允许以下内容:

            总共
          • 300 个Cookie
          • 每个
          • 4096字节
          • 每个域
          • 20个Cookie
          • 每个域
          • 81920字节(给定20个最大大小为4096的Cookie = 81920字节。)
      3. <强> sessionStorage

        <强>优点:

        1. 类似于localStorage
        2. 更改仅适用于每个窗口(或Chrome和Firefox等浏览器中的标签)。所做的更改将保存并可用于当前页面,以及将来在同一窗口中访问该站点。窗口关闭后,存储将被删除
        3. <强>缺点:

          1. 数据仅在设置它的窗口/标签内可用。
          2. 数据不是永久性的,即一旦窗口/标签关闭,它就会丢失。
          3. localStorage一样,tt适用于same-origin policy。因此,存储的数据只能在同一个源上使用。
        4. <强> PostMessage

          <强>优点:

          1. 安全地启用cross-origin通信。
          2. 作为一个数据点,WebKit实现(由Safari和Chrome使用)目前不会强制执行任何限制(除了因内存不足而产生的限制)。
          3. <强>缺点:

            1. 需要从当前窗口打开一个窗口,然后只要你打开窗户就可以进行通信。
            2. 安全问题 - 通过postMessage发送字符串是您将获取其他JavaScript插件发布的其他postMessage事件,因此请务必实施targetOrigin以及对传递给消息监听器的数据进行健全性检查。
          4. PostMessage + SessionStorage

            的组合

            使用postMessage在多个选项卡之间进行通信,同时在所有新打开的选项卡/窗口中使用sessionStorage来保留传递的数据。只要选项卡/窗口保持打开状态,数据就会保留。因此,即使开启者选项卡/窗口关闭,打开的选项卡/窗口也会在刷新后获得整个数据。

          5. 我为此编写了一个JavaScript库,命名为AcrossTabs,它使用postMessage API在跨源选项卡/窗口和sessionStorage之间进行通信,以便只要它们存在就保持打开的选项卡/窗口标识。

答案 4 :(得分:7)

人们应该考虑使用的另一种方法是共享工作者。我知道这是一个前沿的概念,但你可以在共​​享工作者上创建一个比本地存储快很多的中继,并且不需要父/子窗口之间的关系,只要你&#39 ;来自同一个起源。

有关此问题的一些讨论,请参阅我的回答here

答案 5 :(得分:6)

基于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。我冒昧地推荐它,因为当事件几乎同时发生时,大多数“lock / mutex / sync”组件都在websocket连接上失败

答案 6 :(得分:3)

我创建了一个模块,该模块的工作原理与官方Broadcastchannel相同,但具有基于localstorage,indexeddb和unix-sockets的后备功能。这样可以确保即使在Webworkers或NodeJS上也可以正常使用。参见pubkey:BroadcastChannel

答案 7 :(得分:2)

这是Tomas M answer的Chrome开发storage的一部分。我们必须添加监听器

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

在存储中加载/保存项目不会引发此事件-我们必须通过以下方式手动触发

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

现在,所有打开的选项卡都将接收事件

答案 8 :(得分:0)

我在博客上写了一篇文章:http://www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a-web-application

使用我创建的{test: /\.scss$/, loaders: [ 'style', 'css', 'sass' ]} 库,您可以按如下方式实现:

storageManager

还有其他方便的方法来处理其他场景

答案 9 :(得分:0)

我已经创建了一个库sysend.js,它很小,您可以检查其源代码。该库没有任何外部依赖项。

您可以将其用于同一浏览器和域中的选项卡/窗口之间的通信。该库使用BroadcastChannel(如果支持)或localStorage中的存储事件。

API非常简单:

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

当您的支持者支持BroadcastChannel时,它将发送文字对象,如果没有,则首先将其序列化为JSON,然后在另一端反序列化。

最新版本还具有帮助程序API,用于创建跨域通信的代理。 (它需要目标域上的单个html文件)。

这里是demo

注意:如果您将使用localStorage实现相同的功能,则IE中存在问题。存储事件被发送到同一窗口,该窗口触发了该事件,并且对于其他浏览器,仅对其他选项卡/窗口调用。

相关问题