【问题标题】:Detecting Unique Browser Tabs检测唯一的浏览器标签
【发布时间】:2014-02-27 08:05:13
【问题描述】:

在我网站的每个页面上,我都使用 AJAX 来轮询服务器并检索消息列表。服务器维护一个消息列表和消息所针对的 SessionId(我在 ASP.NET 环境中,但我觉得这个问题适用于任何服务器端技术)。如果找到特定 SessionId 的消息,则将其返回到客户端脚本。我使用 JavaScript 库来创建通知(使用 noty,一个 Jquery 通知插件)。一旦返回特定消息,服务器就会丢弃该消息。

如果用户只为特定网站打开了一个选项卡/窗口,这很有效。但是,假设他们有两个打开并且他们做了一些导致生成警告消息的事情。我无法控制通知转到哪个选项卡,因此用户可能最终看不到警告消息。

是否有唯一标识浏览器选项卡的方法?然后我可以将它作为我的 AJAX 调用中的参数之一传递。

【问题讨论】:

  • window.MYID=Math.random(); ... doServerStuff(MYID, ...); ... if(response.id==MYID) ... 您可以使用 window.name 在刷新期间保留 ID。
  • @dandavis 这会持续多个请求吗?假设我在 Default.aspx 上设置了 window.MYID,然后用户导航到 MyAccount.aspx。 window.MYID 是否仍会被初始化为相同的值?
  • window.MYID 不会,但 window.name 会。如果可用,您也可以使用 sessionStorage。
  • 您的意思是用户在其中一个浏览器选项卡中执行了不允许的操作,而您想在另一个浏览器选项卡中显示消息?
  • 没有。我想要一个基于 AJAX 的消息系统,将消息发送回正确的选项卡。例如,用户在选项卡 X 上执行了一些不允许的操作,我想在选项卡 X 上显示一条消息。但我希望同一个系统也允许将用户重定向到另一个页面并仍然在选项卡 X 上显示消息,而不是选项卡是的。

标签: javascript asp.net browser


【解决方案1】:

首先,轮询似乎不是一个好的机制。当您拥有大量活跃用户时,它可能会导致您的服务器崩溃。理想情况下,您应该在对无效操作导致的请求的响应中返回一条消息。

以下解决方案可能对您有用。灵感来自@SergioGarcia 的回复。

在表单标签结束之前保留一个隐藏的输入,该标签存储一个唯一 ID,用于唯一标识选项卡。您将根据唯一的 tabID 将消息存储在服务器会话中,

<input type="hidden" id="hiddenInputTabId" value="<%=getValue()%>" />

然后定义getValue。

function string getValue() {
    var v = getValueFormBodyOrAccessValueDirectlyByMakingInput_a_ServerSideControl();
    if (string.IsNullOrWhiteSpace(v)) {
        return Guid.NewId();
    } else {
        return v;
    }
}

因为它是一个隐藏的输入,你应该在 POSTed 表单正文中获取它的值,对于低于 sn-p 的 ajax 请求,应该注意在 you can access on server side 的标头中发送该值。

$.ajaxSetup({
    beforeSend: function(xhr) {
        xhr.setRequestHeader("tabId", $('#hiddenInputTabId').val());
    },
});

在返回对轮询请求的响应时,可以检查相同的标头,并且只应响应提供的 tabId 可用的响应消息。

【讨论】:

  • 我同意,轮询不是一个好主意。我正在考虑使用 SignalR 作为替代方案。
【解决方案2】:

您可以添加一个名为 tabId 的查询字符串参数,并使用 javascript 控制它与选项卡的绑定。

下面有一个功能原型:

$(function () {

  // from: https://developer.mozilla.org/en-US/docs/Web/API/Window.location
  function getQueryStringParameter (sVar) {
    return decodeURI(window.location.search.replace(new RegExp("^(?:.*[&\\?]" + encodeURI(sVar).replace(/[\.\+\*]/g, "\\$&") + "(?:\\=([^&]*))?)?.*$", "i"), "$1"));
  }

  // from: http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript
  function newGuid() {
    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
      var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
      return v.toString(16);
    });
  }

  window.tabId = getQueryStringParameter('tabId');

  // tabId not defined, reload the page setting it
  if (!window.tabId) {
    window.tabId = newGuid();
  }

  // on click set the tabId of each link to current page
  $(document).on('click', 'a', function (e) {
    var $this = $(this);
    var newLocation = $(this).attr("href");

    // In page links
    if (newLocation.match(/^#.+$/)) {
      return;
    }

    // Outbound links
    if (newLocation.match(new RegExp("^https?")) && !newLocation.match(new RegExp("^https?://" + window.location.host))) {
      return;
    }

    // Insert tab id
    if (newLocation.match(/(\?|&)tabId=[0-9a-f-]+/)) {
      newLocation.replace(/(\?|&)tabId=[0-9a-f-]+/, (newLocation.indexOf('?') == -1 ? "?" : "&") + "tabId=" + window.tabId);
    } else {
      newLocation += (newLocation.indexOf('?') == -1 ? "?" : "&") + "tabId=" + window.tabId;
    }

    window.location.href = newLocation;
    e.preventDefault();
  });
});

如果您在应用程序中输入页面时未对查询字符串设置tabId 参数,则会将其设置为新的 UUID (Guid)。

当页面在查询字符串上有tabId 参数时,它会在页面内定义window.tabId 变量,您可以在应用程序中使用它。

当用户点击您页面中的任何链接时,将触发一个 javascript 事件,并且链接 url 将被重定向以匹配当前的tabId。右键单击以在新选项卡中打开或中间单击不会触发该事件,并且用户将被发送到没有查询字符串参数的新选项卡,因此新页面将在该页面中创建一个新的tabId。

你可以在这里看到它的工作原理:http://codepen.io/anon/pen/sCcvK

【讨论】:

  • 我喜欢你用这个去哪里。我不喜欢使用查询字符串来传递它,但我想这是一个很好的与服务器端无关的解决方案。
  • 根据服务器端使用的技术,它可以是路由的一部分,例如在 ASP .net MVC 中可以是/{tabId}/{controller}/{action}/{id}。
  • 嗯,这只是在避免问题。它仍然在 URL 中。理想情况下,这不会出现在用户可见的任何内容上,除了 HTML 源代码。
  • Url 和 Javascript 上下文是选项卡之间唯一的区别。您必须使用其中之一,当您重新加载页面时,JS 上下文将被清除。
【解决方案3】:

您可以通过加载您的客户端使用 javascript 生成一个唯一的选项卡 ID。

我强烈建议您使用一些东西进行标签间通信,例如intercom.js,它可以从单个标签广播消息,并通过单一连接到所有其他标签。 Intertab 与socket.io 一起使用,它具有长轮询回退,因此它在您当前的系统中也可能很好。我同意轮询是一个糟糕的选择,您应该改用 websockets。

如果你在服务器上使用ZMQ,那么在浏览器中你也可以使用NullMQ(对于websockets ofc)。我认为它没有 intertab 支持,因此您应该制作自己的 intertab 解决方案以使其工作。编写这样一个系统并不难,你只需要一个公共存储,例如 localStorage,但它甚至可以是 cookie……如果你没有存储事件,你必须 ping 该存储以进行更改设置间隔。您必须将消息存储在那里,以及哪个选项卡广播它们(可能在信号量中)以及它最后一次 ping 存储的时间。之后,您可以使每个选项卡与其他选项卡保持同步,或者通过使用唯一的选项卡 ID,您可以将自定义消息发送到任何选项卡。如果广播选项卡有存储超时(它很长时间没有ping存储),那么它可能已关闭,因此您应该将广播服务分配给另一个选项卡。

【讨论】:

    【解决方案4】:

    所以我最终做的是改变通知框架的运作方式,以防止需要识别独特的标签。在无状态网络上分层信息太难了。

    我没有使用 Ajax 将消息立即发送到客户端,而是在每个页面上将它们构建到 List&lt;Message&gt; 属性中。在PreRender 上,我使用ClientScript.RegisterStartupScript() 将它们呈现给客户端。但是如果我需要将用户发送到另一个页面,我开始使用Server.Transfer() 而不是Response.Redirect(),以便它保留消息队列。新页面检查旧页面以查看它是否存在以及是否是正确的类型。如果它是正确的类型,我将它转换并从旧页面检索消息队列并将它们添加到新页面的队列中。而且由于Server.Transfer()不会更新客户端的URL,所以我还添加了一个JavaScript函数来手动将状态推送到支持的浏览器中的URL。

    我知道我在这个问题上采取的方向与我在这个问题上的做法有点不同,但我认为我一开始就走错了。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-12
      • 1970-01-01
      相关资源
      最近更新 更多