如何防止Meteor.userId()和依赖userId的订阅更新之间的客户端竞争条件?

时间:2014-06-27 06:35:04

标签: meteor

我看到一个可重复的问题,即用户使用Meteor服务器进行身份验证(“登录”),然后更新依赖于userId的客户端订阅(以及相关的UI模板被动更新) 之前 Meteor.userId()注册成功登录。

例如,在此代码段中,断言将throw

var coll = new Meteor.Collection("test");

if (Meteor.isServer) {
    Meteor.publish('mineOrPublic', function () {
        // Publish public records and those owned by subscribing user
        return coll.find({owner: { $in: [ this.userId, null ]}});
    });
}

if (Meteor.isClient) {
    var sub = Meteor.subscribe('mineOrPublic');
    var cursor = coll.find({});
    cursor.observe({
        added: function (doc) {
            if (doc.owner) {
                // This should always be true?!
                assert(doc.owner === Meteor.userId());
            }
        }
    });
}

类似于上面的added函数,如果我编写一个检查Meteor.userId()的模板助手,它将看到null的值,即使用数据上下文调用它也是如此带有所有者的文件。

Meteor collection Pub / Sub和帐户userId更新机制之间显然存在竞争条件。在我看来Meteor.userId()应该始终在任何订阅更新之前根据服务器发布函数中this.userId的更改进行更新,但由于某种原因,相反通常似乎是真的(也就是说,上面代码中的assert通常会抛出)。

我关心的原因是因为我的软件包依赖于在客户端获取有效的Meteor身份验证令牌(使用Accounts._storedLoginToken())来保护对Meteor服务器上存储的文件的HTTP请求。在Meteor.userId()之前,身份验证令牌不正确。所以事件的流程通常是这样的:

  • 用户登录
  • 根据this.userId
  • 中的更改,在服务器重新运行时发布功能
  • 客户开始接收与userId更改相对应的新文档。
  • UI模板被动更新以添加由新文档驱动的DOM元素
  • 某些DOM元素是<img>个标记,其src=值取决于数据上下文。
  • HTTP请求被触发并最终因403(禁止)错误而失败,因为尚未设置所需的身份验证cookie。
  • Meteor.userId()最终在客户端上更新,并且代码被动地运行以设置身份验证cookie
  • 重新运行依赖于Cookie更新代码中设置的会话变量的模板中的帮助程序,但DOM不会更改,因为<img>标记中的网址不会更改。
  • 由于DOM没有更改,因此标记不会重试失败的加载图像的尝试。
  • 一切都安定下来,用户必须手动重新加载页面才能显示图像。

我想出了两种可能解决此问题的方法:

  1. 在生成<img>标记的URL的模板助手中,始终附加一个虚拟查询字符串,例如:"?time=" + new Date().getTime()。这会导致DOM在每次调用帮助程序时都会更改并修复问题,但它会搞砸浏览器缓存,如果不协调会导致某些资产不必要地多次加载,等等。

  2. 在每个触及文档数据的模板助手中添加以下测试:

    if (this.owner && this.owner !== Meteor.userId()) { 
       // Perhaps Meteor.loggingIn() could be used above?
       // Invalid state, output placeholder
    } else {
       // Valid state, output proper value for template
    }
    
  3. 我真的希望有人知道一种解决这个问题的方法。或者,如果达成共识,这是一个错误,Meteor的行为在这方面是不正确的。我很乐意在Github上提出一个问题。我非常喜欢和Meteor一起工作,但这是一种在“它只是有效”的齿轮中磨砺的那种烦躁的烦恼。

    感谢所有见解。

2 个答案:

答案 0 :(得分:3)

在尝试了很多事情之后,OP中示例代码的这种变化似乎始终如一地解决了竞争条件,我发现这是一个可接受的解决方案,与我最初尝试的解决方法不同。

我仍然觉得这种逻辑应该是不必要的,并且欢迎关于Meteor在OP示例代码中的行为是正确还是错误的其他方法或意见。如果Meteor的行为错误的评论中出现了共识,我将在Github上为此创建一个问题。

感谢您提供任何其他反馈或替代解决方案。

var coll = new Meteor.Collection("test");

if (Meteor.isServer) {

    Meteor.publish('mineOrPublic', function (clientUserId) {
        if (this.userId === clientUserId) {
            // Publish public records and those owned by subscribing user
            return coll.find({owner: { $in: [ this.userId, null ]}});
        } else {
            // Don't return user owned docs unless client sub matches
            return coll.find({owner: null});
        }
    });
}

if (Meteor.isClient) {

    Deps.autorun(function () {
        // Resubscribe anytime userId changes
        var sub = Meteor.subscribe('mineOrPublic', Meteor.userId());
    });

    var cursor = coll.find({});

    cursor.observe({
        added: function (doc) {
            if (doc.owner) {
                // This should always be true?!
                assert(doc.owner === Meteor.userId());
            }
        }
    });
}

此代码的工作原理是为服务器发布功能提供在客户端自己的登录状态之前运行时需要识别的信息,从而打破竞争条件。

我认为这是Meteor应该自动执行的操作:在客户端this.userId更新之前,客户端不应该根据发布函数中Meteor.userId()的更改来查看文档。

其他人是否同意?

答案 1 :(得分:-1)

我尝试使用在服务器上运行的代码。与FileCollection包相关联。

if (Meteor.isServer) {
  CurrentUserId = null;
  Meteor.publish(null, function() {
    CurrentUserId = this.userId;
  });
}

....
OrgFiles.allow({
  read: function (userId, file) {
     if (CurrentUserId !== file.metadata.owner) {
       return false;
     } else {
       return true;
     }
  }
 ...