我正在寻找一种方法,如何在浏览器中的多个选项卡或窗口(在同一域上,而不是CORS)之间进行通信而不留下痕迹。有几种解决方案:
使用window对象
postMessage
饼干
localStorage
第一种可能是最糟糕的解决方案——您需要从当前窗口打开一个窗口,然后您只能在保持窗口打开的情况下进行通信。如果在任何窗口中重新加载页面,很可能会失去通信。
第二种方法(使用postMessage)可能支持跨源通信,但它遇到了与第一种方法相同的问题。您需要维护一个窗口对象。
第三种方法是使用cookie,在浏览器中存储一些数据,这实际上看起来像是向同一域上的所有窗口发送消息,但问题是,在清理之前,您永远无法知道所有选项卡是否已经阅读了“消息”。您必须实现某种类型的超时来定期读取cookie。此外,您还受到最大cookie长度(4 KB)的限制。
第四种解决方案,使用localStorage,似乎克服了cookie的限制,甚至可以使用事件监听。如何使用它在公认的答案中有描述。
有一个基于localStorage的小开源组件,可以在相同来源的选项卡/窗口之间同步和通信(免责声明-我是贡献者之一!)
TabUtils.BroadcastMessageToAllTabs("eventName", eventDataString);
TabUtils.OnBroadcastMessage("eventName", function (eventDataString) {
DoSomething();
});
TabUtils.CallOnce("lockname", function () {
alert("I run only once across multiple tabs");
});
注:我冒昧地在这里推荐它,因为当事件几乎同时发生时,大多数“锁/互斥/同步”组件在websocket连接上失败。
我创建了一个syend .js库,用于在浏览器选项卡和窗口之间发送消息。该库没有任何外部依赖项。
您可以使用它在同一浏览器和域中的选项卡/窗口之间进行通信。该库使用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文件)。
这里是一个演示。
新版本也支持跨域通信,如果你在目标域中包含一个特殊的proxy.html文件,并从源域中调用代理函数:
sysend.proxy('https://target.com');
(proxy.html是一个非常简单的HTML文件,它只有一个带有库的脚本标记)。
如果你想要双向通信,你需要在其他域上做同样的事情。
注意:如果您将使用localStorage实现相同的功能,则Internet Explorer中存在一个问题。存储事件被发送到相同的窗口,这触发了事件,对于其他浏览器,它只被其他选项卡/窗口调用。
为此,您最好使用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()})
还要记住,广播消息的当前选项卡实际上并没有接收到消息,只接收到同一域中的其他选项卡或窗口。
You may ask what happens if the user loads a different webpage or closes his tab just after the setItem() call before the removeItem(). Well, from my own testing the browser puts unloading on hold until the entire function message_broadcast() is finished. I tested to put some very long for() cycle in there and it still waited for the cycle to finish before closing. If the user kills the tab just in-between, then the browser won't have enough time to save the message to disk, thus this approach seems to me like safe way how to send messages without any traces.