作为我网站的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.open
和window.addEventListener
一起出现在同一刻度中,并且Javascript既是单线程的又是不可重入的。
如果子窗口确实设法在对window.open
的调用完成之前发布消息,则该消息将仅进入当前Javascript上下文的事件队列。您的代码仍在运行,因此addEventListener
调用接下来发生。最后,在我们这里看不到的将来,您的代码将控制权返回给浏览器,从而结束了当前的滴答声。
当前滴答结束后,浏览器可以签出事件队列并调度下一部分工作(可能是消息)。您的订户已经就位,所以一切都很好!