分享

浏览器跨标签通讯

 慈溪全媒体 2015-04-17

即将到来的 SharedWorker API 能够在 iframe 甚至浏览器标签或窗口中传输数据。它在几年前就已在 Chrome 中得以实现,不久前也在 Firefox 上实现了,不过它在 IE 和 Safari 中仍然难觅踪影。还好,这个 API 有一种拥有广泛浏览器支持,但鲜为人知的替代方案。是时候探索它了!


现在,我需要对以下应用情景找到一个优雅的解决方案:假设有个人访问了你的网站。他依次登录,打开第二个标签页并在那个标签页里选择了注销。这时,他所打开的第一个标签页看起来仍然保留着「已登录」的状态,但这时他的所有操作要么会重定向到登录页面,要么会直接让他抓狂。更吸引人的解决方式则是判断用户是否已注销,并对页面做相应的改变。譬如可以显示一个对话框来提示用户需要重新验证,或者显示原本的登录视图。


这个功能可以通过 WebSocket API 来实现,不过这就有些小题大做了。毕竟杀鸡焉用牛刀,于是我开始寻找一些其它的跨标签页通信方式。我首先想到的就是使用 cookies 或者 localStorage ,来周期性地通过 setInterval 检查用户是否登录。对这个方案我并不满意,因为这样会把许多 CPU 周期耗费在检查一个可能自始至终都不会满足的条件上。这时候我就觉得还不如就直接用 “comet”(又名轮询)、服务器端事件或者 WebSockets 算了呢。


所以当我发现自己是在骑驴找驴的时候还是很吃惊,因为答案就是一直以来的 localStorage!


你知道 localStorage 会触发一个事件吗?具体地说,不论其中的哪一项在另一个浏览上下文里被添加、修改或删除时,它都会触发一个事件。实际上,这就意味着不论在哪个浏览器的标签页里访问了 localStorage,所有其它的标签页都能通过 window 对象监听到这个事件,就像这样:


window.addEventListener('storage', function (event) {

console.log(event.key, event.newValue);

});


event 对象有几个相应的属性:



不论某个标签页在何时修改了 localStorage,都会对其余的所有标签触发事件。这就意味着我们只要为 localStorage 赋值,就能够跨浏览器标签通信了。请看下面伪代码风格的示例:


var loggedOn;


// TODO: call when logged-in user changes or logs out

logonChanged();


window.addEventListener('storage', updateLogon);

window.addEventListener('focus', checkLogon);


function getUsernameOrNull () {

// TODO: return whether the user is logged on

}


function logonChanged () {

var uname = getUsernameOrNull();

loggedOn = uname;

localStorage.setItem('logged-on', uname);

}


function updateLogon (event) {

if (event.key === 'logged-on') {

loggedOn = event.newValue;

}

}


function checkLogon () {

var uname = getUsernameOrNull();

if (uname !== loggedOn) {

location.reload();

}

}


大意就是当用户打开了两个标签页,在其中一个里执行了注销操作后返回另一个时,页面将重新载入,(如果可以的话)服务器端逻辑将把用户重定向到其它位置。这个检查只在当前标签页获得焦点时执行,这是因为用户可能在注销后立刻重新登录,这种情况下不应将其余标签页的状态全部设为已注销。


这段代码肯定还可以改进,不过它已经很好地满足了需求。更好的实现方式可能会立刻要求用户登录,但要注意它也可能以相反的方式来工作:用户登录后打开另一个已经注销的标签页时,代码会检查并重新载入页面,然后服务器(再说一遍,如果可以的话)就可以把用户重定向到登录页面的不老泉里,期盼着你能有一次打电话给这个网站的经验。


更简单的 API


localStorage API 可以说是 web 浏览器最简单的 API 之一了,并且它还享有相当不错的跨浏览器支持。不过,一些浏览器的仍然存在着 quirks,譬如无痕模式下的 Safari 在设置值时会抛出 QuotaExceededError 的异常,有某些浏览器不支持开箱即用的 JSON,还有一些旧版浏览器会让你感到沮丧。


因此,我整合了一个 local-storage 模块,为 localStorage 提供了简化的 API,从而摆脱这些 quirks,在缺少 localStorage API 时会回退到内存存储,并通过使你为特定键注册或取消注册监听器,使得对 storage 事件的使用更加容易。


截止到写这篇文章时,local-storage@1.3.1 中最新的 API 端点(译者注:2015-01-08) 如下:


  • ls(key, value?) 取得或设置键

  • ls.get(key) 取得键的值

  • ls.set(key, value) 为键指定值

  • ls.remove(key) 移除键

  • ls.on(key, fn(value, old, url)) 监听其它标签页的键值改变并触发 fn

  • ls.off(key, fn) 取消之前使用 ls.on 注册的监听器


同样值得一提的是, local-storage 注册了一个单一的 storage 对象处理器并保持你对每个键的跟踪,而不是注册多个 storage 对象。


我对学习其它跨标签页通信的底层实现方式也很有兴趣!它对离线优先的开发很有帮助,尤其是考虑到当前 SharedWorker 还需要一段时间才能迎来广泛支持,而 WebSocket 在离线使用的情境下也靠不住时,这种通信方式就更有意义了。


    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多