需求背景:
在软件里加入第三方登录小窗进行软件的登录。
方案
方案一
第一个方案决定使用 iframe 加载第三方 web 登录网址。但是发现当页面点击登录重定向到二级过度登录页面时,会报跨域的错误。
方案二
于是决定尝试使用 webview 来内嵌这个第三方登录页面。
踩坑过程
electron : 10.0.1
vue : 2.5.16
1、在主进程里需要允许主窗口使用 webviewTag:
const win = new Browser({
...,
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
webviewTag: true, // 启用 <webview> tag 标签
},
});
2、然后在登录界面上增加一个登陆按钮,并且点击后弹出第三方登录窗口:
<button @click="login">第三方登录</button>
...
<div class="login-wrap" ref="loginWrap" v-show="showLogin">
<!-- 这里不进行静态加载 webview 是因为在 electron 中每次点击登录按钮就变更 webview 的 src 会报错 -->
<!-- <webview ref="qcloudLogin" nodeintegration></webview> -->
<button class="login__close" type="icon" title="关闭" @click="onCloseLoginDialog">
<i class="close"></i>
<button>
</div>
3、点击登录按钮后,动态向 $(".login-wrap") 插入 webview 并加载第三方 web 登录链接。
export default {
mounted(){},
method:{
async login(){
this.loginLoading = true;
const url = await dao.getLoginUrl(); // 获取第三方登录链接
this.loginLoading = false;
if (!url) return;
// 动态加入 webview 标签
this.webview = document.createElement('webview');
this.webview.src = url;
this.webview.style.height = '100%';
$(this.webview).attr('nodeintegration', ''); // 内嵌页面需要调用 node 则开启该开关
this.$refs.loginWrap.appendChild(this.webview);
this.webview.addEventListener('ipc-message', this.handleLogin);
this.showLogin = true;
},
// ipc-message:可与内嵌的页面进行通信,处理第三方登录成功与失败
handleLogin(event){},
// 关闭登录时记得移除事件与 webview 标签
onCloseLoginDialog() {
this.showQcloudLogin = false;
this.webview.removeEventListener('ipc-message', this.handleQcloudLogin);
this.$refs.loginWrap.removeChild(this.webview);
this.webview = {};
},
},
}
这里明显埋了一个坑,第三方做的 web 登录页面中肯定除了登录,它还要为自己的网站引流,比如说会有注册账号,忘记密码等功能。在我们的软件里也不想预加载一个脚本去把这些跳转按钮给隐藏了(要为新注册用户导量呀),所以要将这些非登录的页面跳转到浏览器去让用户做接下来的操作。因此,这里需要去处理。
4、对于第三方页面可能有的新开页跳转,我们在渲染进程中可以直接监听 webview 的新窗口打开事件来处理:
注意,在创建 webview 标签时千万不要给其加 allowpopups 属性,因为在 ‘new-window’ 事件中调用 event.preventDefault() 阻止 electron 自己弹窗是不生效的。
this.webview.addEventListener('new-window', (e) => {
const { protocol } = require('url').parse(e.url)
if (protocol === 'http:' || protocol === 'https:') {
await require('electron').shell.openExternal(e.url);
}
});
5、如果第三方页面是重定向已有页面,不进行新开页面跳转,在渲染进程监听 webview 标签的 “will-navigate” (导航即将跳转)事件并调用 event.preventDefault() 是阻止不了 webview 的窗口跳转的。官方文档 webview 事件里也有进行说明。
在网上找了很久,看别人是怎么解决的,终于让我找到了这篇博客:截獲 <webview>
will-navigate 事件, 為內容來源實作白名單
其实是借助主进程来帮忙。在主进程创建的窗口上的一个 webContents 有一个事件:‘did-attach-webview’,当<webview>
被挂载到页面内容中时,该事件会被触发。这使得当<webview>
挂载到登陆页面时,可以通过 ‘did-attach-webview’ 事件取到这个 webview 标签的 webContent。
然后在这个 webContent 的 will-navigate 事件里就可以截获页面跳转,阻止 webview 跳转到我们不想要的页面并可以拉取浏览器进行跳转。
// 注意这份代码应该写在主进程里,而不是渲染进程哦
win.webContents.on('did-attach-webview', (e, webContent)=> {
webContent.on('will-navigate', (e, url) => {
if (util.isUrlAllowed(url)) return;
// 不允许的网址则阻止页面跳转并拉取浏览器展示页面
e.preventDefault();
require('electron').shell.openExternal(url);
});
});