作为我网站的OAuth流程的一部分,我正在使用window.open允许用户登录到第三方提供商,并使用postMessage将auth令牌发送回我的网页。基本流程看起来像这样,尽管我删除了与超时,清理,错误处理等有关的额外代码:

const getToken = () => new Promise((resolve, reject) => {
    const childWindow = window.open(buildUrl({
        url: "https://third-party-provider.com/oauth/authorize",
        query: {
            redirect_uri: "my-site.com/oauth-callback"
        }
    })

    window.addEventListener('message', event => {
        if(
            event.origin === window.location.origin &&
            event.source === childWindow
        ) {
            if(event.data.error) reject(event.data.error)
            else resolve(event.data)
        }
    })

    // Plus some extra code to remove the event listener and close
    // the child window.
}


基本思想是,当用户单击授权时,它们将被替换为my-site.com/oauth-callback。该页面完成了服务器端的OAuth流程,然后加载了包含少量javascript的页面,这些javascript只需调用window.opener.postMessage(...)

现在,我的问题的症结在于,至少在理论上,这里确实存在种族条件。问题在于,假设在addEventListener被调用之前childWindow可以调用postMessage。相反,如果我先调用addEventListener,则它可以在设置childWindow之前接收消息,我认为这会引发异常吗?关键问题似乎是window.open不是异步的,因此我无法自动生成窗口并设置消息处理程序。

当前,我的解决方案是首先设置事件处理程序,并假定在执行该函数时消息没有进入。这是一个合理的假设吗?如果不是,是否有更好的方法来确保该流程正确?

最佳答案

这里不应该存在竞争条件,因为window.openwindow.addEventListener一起出现在同一刻度中,并且Javascript既是单线程的又是不可重入的。

如果子窗口确实设法在对window.open的调用完成之前发布消息,则该消息将仅进入当前Javascript上下文的事件队列。您的代码仍在运行,因此addEventListener调用接下来发生。最后,在我们这里看不到的将来,您的代码将控制权返回给浏览器,从而结束了当前的滴答声。

当前滴答结束后,浏览器可以签出事件队列并调度下一部分工作(可能是消息)。您的订户已经就位,所以一切都很好!

07-24 09:44
查看更多