如何实现弹窗打开并等待关闭?Google OAuth2认证场景方案
实现可等待的OAuth2弹窗登录并检测关闭事件
核心实现思路
通过Promise封装弹窗操作,结合轮询检测弹窗的closed状态(跨域场景下最可靠的方式),同时处理弹窗拦截、加载错误等异常情况,最终实现await openPopup(url)的调用形式。
完整代码实现
1. 封装openPopup函数
function openPopup(url) { // 配置弹窗的大小与居中位置,提升用户体验 const width = 600; const height = 600; const left = window.innerWidth / 2 - width / 2; const top = window.innerHeight / 2 - height / 2; // 打开弹窗,指定唯一窗口名称避免重复创建 const popup = window.open( url, "GoogleAuthPopup", `width=${width},height=${height},left=${left},top=${top},popup=true,noopener,noreferrer` ); // 处理弹窗被浏览器拦截的情况 if (!popup) { return Promise.reject(new Error("弹窗被浏览器拦截,请开启弹窗权限后重试")); } return new Promise((resolve, reject) => { // 轮询检测弹窗是否关闭 const checkInterval = setInterval(() => { if (popup.closed) { clearInterval(checkInterval); resolve(); } }, 100); // 监听弹窗加载错误 popup.addEventListener("error", () => { clearInterval(checkInterval); popup.close(); reject(new Error("认证弹窗加载失败")); }); }); }
2. 在登录函数中使用
async logIn() { try { const url = await this.getAuthUrl(); await openPopup(url); // 弹窗关闭后执行后续流程,比如请求后端获取用户信息、更新登录状态 const userData = await this.fetchAuthenticatedUser(); // ... 其他业务逻辑 } catch (err) { console.error("登录出错:", err); // 给用户展示错误提示,比如弹窗拦截、加载失败等 } }
进阶优化:区分「登录完成关闭」与「主动取消关闭」
如果后端处理完认证后,能跳转至一个同域回调页面,可以通过postMessage通知主页面登录状态,实现更精准的流程控制:
回调页面代码(同域)
// 通知主页面登录成功 window.opener.postMessage( { type: "GOOGLE_AUTH_SUCCESS" }, window.opener.origin ); // 关闭弹窗 window.close();
修改后的openPopup函数
function openPopup(url) { // ... 弹窗创建代码与拦截处理同之前 ... return new Promise((resolve, reject) => { let isAuthCompleted = false; const checkInterval = setInterval(() => { if (popup.closed) { clearInterval(checkInterval); window.removeEventListener("message", handleAuthMessage); resolve({ isCompleted: isAuthCompleted }); } }, 100); // 监听来自弹窗的消息 const handleAuthMessage = (event) => { // 验证消息来源,避免跨域恶意消息 if (event.origin !== window.location.origin) return; if (event.data.type === "GOOGLE_AUTH_SUCCESS") { isAuthCompleted = true; } }; window.addEventListener("message", handleAuthMessage); // ... 错误监听代码同之前 ... }); }
登录函数中区分处理
async logIn() { try { const url = await this.getAuthUrl(); const { isCompleted } = await openPopup(url); if (isCompleted) { // 登录成功后的流程 const userData = await this.fetchAuthenticatedUser(); } else { // 用户主动取消登录的提示 console.log("用户取消登录"); } } catch (err) { // 错误处理 } }
关键注意事项
- 跨域限制:主页面无法直接监听跨域弹窗的
close事件,轮询是最兼容的方案。 - 定时器清理:必须在弹窗关闭或出错时清除轮询定时器,避免内存泄漏。
- 弹窗权限:提前提示用户开启弹窗权限,避免因拦截导致流程中断。
内容的提问来源于stack exchange,提问作者Samuele B.
相关产品推荐
相关产品推荐

