标签或窗口之间的通讯


176

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

  1. 使用窗口对象
  2. postMessage
  3. 饼干
  4. 本地存储

第一个可能是最糟糕的解决方案-您需要从当前窗口中打开一个窗口,然后您只有在保持打开状态的情况下才能进行通信。如果您在任何窗口中重新加载页面,则很可能失去了通信。

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

第三种方法,使用cookie,将一些数据存储在浏览器中,这实际上看起来像是向同一域中的所有窗口发送消息,但是问题是您永远无法知道所有选项卡之前是否都已读取“消息”打扫干净。您必须实现某种超时才能定期读取Cookie。此外,您受到最大Cookie长度(4KB)的限制。

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

编辑2018:可接受的答案仍然有效,但是对于现代浏览器来说,有一个更新的解决方案可以使用BroadcastChannel。请参阅其他答案,以获取一个简单的示例,该示例描述了如何使用BroadcastChannel在选项卡之间轻松地传输消息。


4
当几乎相同的问题已经问了多年时,为什么这个问题被封闭为“过于广泛”?使用JavaScript向所有打开的窗口/选项卡发送消息stackoverflow.com /questions/2236828/…如何在2个浏览器选项卡/窗口之间通信?还有一些。
Dan Dascalescu 2015年

我在localStorage和sessionStorage上创建了一个库来管理客户端数据存储。您可以执行诸如storageManager.savePermanentData('data','key');之类的操作。或storageManager.saveSyncedSessionData('data','key'); 根据您希望数据的行为。它确实简化了过程。完整的文章在这里:ebenmonney.com/blog/...
adentum


2
我几年前创建了库sysend.js,在最新版本中它使用BroadcastChannel。您可以通过两次打开此页面jcubic.pl/sysend.php来测试该库,如果您提供iframe代理,它也可以用于不同的源。
jcubic

我是否将子域视为同一来源?我的意思是,我确实有以下三个域,它们是否通过广播频道api进行通信?alpha.firstdomain.com,beta.firstdomain.com,gama.firstdomain.com
Tejas Patel

Answers:


142

编辑2018:您可以更好地为此目的使用BroadcastChannel,请参阅下面的其他答案。但是,如果您仍然希望使用本地存储在选项卡之间进行通信,请按照以下方式进行操作:

为了在某​​个选项卡向其他选项卡发送消息时获得通知,您只需要在“存储”事件上进行绑定。在所有标签中,执行以下操作:

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

message_receive每次在任何其他选项卡中设置localStorage的任何值时,都会调用该函数。事件侦听器还包含新设置为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()})

还要记住,广播消息的当前选项卡实际上并没有收到,只有同一域中的其他选项卡或窗口才收到。

您可能会问,如果用户在setItem()调用之后,在removeItem()之前加载了另一个网页或关闭了他的选项卡,将会发生什么情况。好吧,根据我自己的测试,浏览器将保持卸载状态,直到整个功能message_broadcast()完成。我测试过要放置很长的for()周期,但在关闭之前它仍然等待周期结束。如果用户只是杀死它们之间的选项卡,那么浏览器将没有足够的时间将消息保存到磁盘,因此在我看来,这种方法是一种安全的方法,它可以毫无痕迹地发送消息。欢迎发表评论。


1
您可以在调用JSON.parse()之前忽略remove事件吗?
dandavis

1
请记住事件数据限制,包括预先存在的localStorage数据。将存储事件仅用于消息传递而不是发送可能会更好/更安全。例如,当您收到明信片告诉您在邮局领取包裹时...同样,本地存储会进入硬盘驱动器,因此它可能会留下无意中的缓存并影响日志,这是考虑为邮件使用不同传输机制的另一个原因实际数据。
dandavis

1
我做过一些相关的事情:danml.com/js/localstorageevents.js,该事件具有事件发射器库和“本地回显”,因此您可以在任何地方使用EE。
dandavis

7
Safari浏览器不支持广播信- caniuse.com/#feat=broadcastchannel
SRIKANTH

1
请注意,您也可以使用共享工作程序:developer.mozilla.org/en-US/docs/Web/API/SharedWorker(在所有浏览器中都有更好的支持)
Seblor,

116

有专门用于此目的的现代API- 广播频道

就像这样简单:

var bc = new BroadcastChannel('test_channel');

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

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

消息不必只是DOMString,可以发送任何类型的对象。

除了API的简洁性外,这大概是该API的主要优点-无需对象字符串化。

当前仅在Chrome和Firefox中受支持,但是您可以找到使用localStorage的polyfill。


3
等一下,您怎么知道消息的来源?它会忽略来自同一选项卡的消息吗?
AturSams

2
@zehelvion:根据这个很好的概述,发件人不会收到它。此外,您可以随心所欲地添加消息,包括。发件人的一些ID(如果需要)。
Sz。

7
有一个很好的项目在这里的跨浏览器库中包装了此功能:github.com/pubkey/broadcast-channel
james2doyle

Safari是否有任何公开信号表明对该浏览器是否支持此API?
Casey

@AturSams您检查MessageEvent.origin,MessageEvent.source或MessageEvent.ports是否是您想要的。与往常一样,文档是开始的最佳位置:developer.mozilla.org/zh-CN/docs/Web/API/MessageEvent
Stefan Mihai Stanescu

40

对于那些寻找不基于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);
    }
}

1
为什么省略了removeItem调用?
Tomas M

2
我只是关注jQuery和JavaScript之间的差异。
纳乔·科洛玛

由于polyfill和不受支持的功能,我总是使用lib!
阿敏·拉希米

20

结帐AcrossTabs - 跨域浏览器标签页之间容易沟通。它结合使用了postMessagesessionStorage API,使通信更加轻松和可靠。


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

  1. 本地存储

    优点

    1. Web存储可以简单地视为对Cookie的一种改进,它提供了更大的存储容量。如果您查看Mozilla源代码,我们可以看到5120KB5MB,在Chrome上等于250万个字符)是整个域的默认存储大小。与典型的4KB Cookie相比,这为您提供了更多的处理空间。
    2. 不会针对每个HTTP请求(HTML,图像,JavaScript,CSS等)将数据发送回服务器,从而减少了客户端与服务器之间的通信量。
    3. 存储在localStorage中的数据将一直保留到明确删除为止。所做的更改将被保存,并且可用于当前和将来对该站点的所有访问。

    缺点

    1. 它适用于同源策略。因此,存储的数据只能在相同的来源上使用。
  2. 饼干

    优点:

    1. 与其他人相比,没有任何AFAIK。

    缺点:

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

      通常,允许以下操作:

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

    优点:

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

    缺点:

    1. 数据仅在设置它的窗口/选项卡内可用。
    2. 数据不是持久性的,即一旦关闭窗口/选项卡,它将丢失。
    3. localStoragett 一样,它遵循同源策略。因此,存储的数据只能在相同的来源上使用。
  4. 留言

    优点:

    1. 安全地启用跨域通信。
    2. 作为数据点,WebKit实现(由Safari和Chrome使用)目前不强制执行任何限制(内存不足导致的限制除外)。

    缺点:

    1. 需要从当前窗口打开一个窗口,然后仅在保持窗口打开的情况下才能进行通信。
    2. 安全问题 -通过postMessage发送字符串是您将拾取其他JavaScript插件发布的其他postMessage事件,因此请确保对targetOrigin传递给消息侦听器的数据实施和完整性检查。
  5. PostMessage + SessionStorage的组合

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

我为此编写了一个名为AcrossTabs的JavaScript库,该库使用postMessage API在跨域选项卡/窗口和sessionStorage之间进行通信,以在打开的选项卡/窗口身份存在时将它们持久化。


使用AcrossTabs,是否可以在另一个标签中打开其他网站,并将数据从其获取到父标签?我将提供另一个网站的身份验证详细信息。
Madhur Bhaiya

1
是的,您可以@MadhurBhaiya
softvar

Cookie的最大优点是它允许跨起源同一个域,当你有一组起源是常用的,如“a.target.com”,“b.target.com”等
StarPinkER

7

人们应该考虑使用的另一种方法是共享工作者。我知道这是一个最先进的概念,但是您可以在Shared Worker上创建一个中继,该中继的速度比localstorage快得多,并且只要您位于同一来源,就不需要父/子窗口之间的关系。

有关我对此所做的一些讨论,请参见此处的答案。


7

有一个很小的开源组件可以在相同来源的标签/窗口之间进行同步/通信(免责声明-我是其中的一员!)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

附言:在事件几乎同时发生的情况下,大多数“锁定/互斥/同步”组件在websocket连接上失败,因此我在这里推荐它


6

我创建了一个库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文件)。

这是演示

编辑

如果您在目标域上包括特殊文件并从源域调用函数,则新版本还支持跨域通信:proxy.htmlproxy

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

(proxy.html这是一个非常简单的html文件,该库中只有一个script标签)。

如果要双向通信,则需要在target.com域上进行相同的通信。

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


2
只是想为此给您一些荣誉。不错的添加功能,简单易用,使我可以在各个选项卡之间进行交流,以防止注销警告软件引起人们的注意。不错的工作。如果您想要一个简单易用的消息传递解决方案,我强烈建议您这样做。
BrownPony

4

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


1

我在我的博客上写了一篇关于此的文章:http : //www.ebenmonney.com/blog/how-to-implement-remember-me-functionality-using-token-based-authentication-and-localstorage-in-a-网络应用程序

使用我创建的库storageManager,可以实现以下目的:

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

还有其他方便的方法也可以处理其他情况


0

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

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

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

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

现在,所有打开的标签页都会收到事件

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.