选项卡之间的通信或 windows

Communication between tabs or windows

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

  1. using the window object
  2. postMessage
  3. cookies
  4. localStorage

第一个可能是最糟糕的解决方案 - 您需要从当前的 window 打开一个 window 然后只要保持 windows 打开就可以通信。如果您在任何 windows 中重新加载页面,您很可能会失去通信。

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

第三种方式,使用 cookie,在浏览器中存储一些数据,这看起来像是向同一域中的所有 windows 发送消息,但问题是您永远无法知道是否所有选项卡在清理之前是否已经阅读了“消息”。您必须实施某种超时才能定期读取 cookie。此外,您受到最大 cookie 长度的限制,即 4 KB。

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

在 2018 年,接受的答案仍然有效,但现代浏览器有一个更新的解决方案,即使用 BroadcastChannel。请参阅另一个答案以获取描述如何使用 BroadcastChannel 在选项卡之间轻松传输消息的简单示例。

您最好为此使用 BroadcastChannel。请参阅下面的其他答案。然而,如果您仍然更喜欢使用 localstorage 进行选项卡之间的通信,请这样做:

为了在一个选项卡向其他选项卡发送消息时得到通知,您只需绑定 'storage' 事件。在所有选项卡中,执行此操作:

$(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()})

还要记住,当前广播消息的标签实际上并没有收到消息,只有其他标签或同一域中的 windows。

您可能会问,如果用户在 removeItem() 之前的 setItem() 调用之后加载不同的网页或关闭他的选项卡,会发生什么情况。好吧,根据我自己的测试,浏览器会暂停卸载,直到整个功能 message_broadcast() 完成。我测试了在那里放置一些很长的 for() 循环,它仍然在等待循环完成后再关闭。如果用户在中间关闭选项卡,那么浏览器将没有足够的时间将消息保存到磁盘,因此在我看来,这种方法是一种安全的发送消息的方式,没有任何痕迹。

对于那些搜索不基于 jQuery 的解决方案的人来说,这是解决方案 :

的普通 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);
    }
}

人们应该考虑使用的另一种方法是共享工作者。我知道这是一个尖端概念,但您可以在共享工作器上创建一个中继,它比本地存储快 ,并且不需要 parent/child 之间的关系window,只要同源即可。

查看我的回答here,了解我对此所做的一些讨论。

我在我的博客上写了一篇关于这个的文章:Sharing sessionStorage data across browser tabs

使用库,我创建了 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

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

结帐 AcrossTabs - Easy communication between cross-origin browser tabs. It uses a combination of the postMessage and sessionStorage API 以使交流更加轻松可靠。


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

  1. LocalStorage

    优点:

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

    缺点

    1. 适用于 same-origin policy。因此,存储的数据将只能在同一来源上使用。
  2. Cookies

    优点:

    1. 与其他人相比,据我所知没有什么。

    缺点:

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

    通常允许以下内容:

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

    优点:

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

    缺点:

    1. 数据仅在设置数据的 window/tab 内可用。
    2. 数据不是永久性的,即一旦 window/tab 关闭,数据就会丢失。
    3. 喜欢 localStorage,tt 在 same-origin policy 上工作。因此,存储的数据将只能在同一来源上使用。
  4. PostMessage

    优点:

    1. 安全地启用 cross-origin 通信。
    2. 作为一个数据点,WebKit 实现(由 Safari 和 Chrome 使用)目前不强制执行任何限制(除了 运行 内存不足所施加的限制) .

    缺点:

    1. 需要从当前window打开一个window,然后只要保持windows打开就可以通信。
    2. 安全问题 - 通过postMessage发送字符串是你会获取其他JavaScript插件发布的其他postMessage事件,因此请务必对传递给消息侦听器的数据实施 targetOrigin 和健全性检查。
  5. PostMessage+SessionStorage

    的组合

    使用 postMessage 在多个选项卡之间进行通信,同时在所有新打开的 tabs/windows 中使用 sessionStorage 来持久化传递的数据。只要 tabs/windows 保持打开状态,数据就会保留。因此,即使打开器 tab/window 被关闭,打开的 tabs/windows 即使在刷新后也会拥有完整的数据。

我为此编写了一个 JavaScript 库,名为 AcrossTabs,它使用 postMessage API 在跨源 tabs/windows 和 sessionStorage 之间进行通信,以持久化打开的 tabs/windows只要活着就是身份

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

就这么简单:

var bc = new BroadcastChannel('test_channel');

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

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

There is no need for the message to be just a DOMString. Any kind of object can be sent.

可能,除了 API 干净之外,它的主要好处是 API - 没有对象字符串化。

目前supported only in Chrome and Firefox, but you can find a polyfill使用localStorage。

a tiny open-source component 可以在同源 tabs/windows 之间进行同步和通信(免责声明 - 我是贡献者之一!)基于 localStorage

TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);

TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
    DoSomething();
});

TabUtils.CallOnce("lockname", function () {
    alert("I run only once across multiple tabs");
});

P.S.: 我冒昧地在这里推荐它,因为当事件几乎同时发生时,大多数“lock/mutex/sync”组件在 websocket 连接上失败。

我创建了一个模块,与官方 Broadcastchannel, but it has fallbacks based on localstorage, indexeddb and unix-sockets. This makes sure it always works even with Webworkers or Node.js. See pubkey:BroadcastChannel 一样工作。

我创建了一个库 sysend.js for sending messages between browser tabs and windows。该库没有任何外部依赖项。

您可以使用它在同一浏览器和域中 tabs/windows 之间进行通信。该库使用 BroadcastChannel(如果支持)或来自 localStorage 的存储事件。

API很简单:

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

当您的浏览器支持 BroadcastChannel 时,它会发送一个文字对象(但实际上它是由浏览器自动序列化的),如果不支持,它会先序列化为 JSON,然后在另一端反序列化。

最新版本还有一个助手 API 来创建跨域通信的代理(它需要目标域上的单个 HTML 文件)。

这里是a demo.

新版本还支持跨域通信,如果你在目标域中包含一个特殊的proxy.html文件并从源域:

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

(proxy.html 是一个非常简单的 HTML 文件,只有一个脚本标签与库有关。

如果你想要双向通信,你需要在其他域上做同样的事情。

注意:如果您将使用 localStorage 实现相同的功能,则 Internet Explorer 中存在问题。存储事件被发送到触发事件的同一个 window,对于其他浏览器,它只被其他 tabs/windows.

调用

这是 为 Chrome 开发的 storage 部分。我们必须添加一个监听器:

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

Load/save 存储中的项目不会触发此事件 - 我们必须通过

手动触发它
window.dispatchEvent( new Event('storage') ); // THIS IS IMPORTANT ON CHROME

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