前景提要
最近在开发一款电商网站商品素材下载浏览器插件,前几天搞定了图片下载功能,又经过几天焦头烂额的踩坑和尝试,最终把批量下载功能也实现了。昨天想着给插件加上登录功能,方便用户保存相关记录。因为是面向国外用户的插件,想着直接支持google oauth2登录,用户未登录时点击登录按钮,会跳转到插件网站的登录页面,用户选择google登录后会跳转到google登录页面,完成登录后同步登录状态到插件。
也就是要实现登录功能才遇到了这个问题,网上也没搜到太多资料。网上的资料大多是background和content以及popup间的通信方案,这是官方就支持的,因此没有什么好说的,但是我需要的插件和网站直接进行状态/数据同步的方案的资料却不是很多。
好在研究了一天时间(其实大部分时间在解决别的问题),终于还是解决了这个问题。
插件与网站状态同步实现方案
我们的现在的插件有一个background script叫做service_worker.js,以及一个content script叫做content.js。
我们假设插件生效的网站为A网站,域名假设为www.website.com,假设我们的插件网站为B网站,域名假设为www.plugin.com。B网站的登录页面为www.plugin.com/login,B网站记录登录状态采用传统的cookies方式+session方式,而没有采用token方式。
如何让插件知道网站B的登录状态发生了变化?
利用background script 监听网站B的cookies变化
利用content script 监听网站B的登录状态变化
background script 监听网站B的cookies变化
我们可以利用background script的chrome.cookies.onChanged事件来监听网站B的cookies变化。当B网站的cookies发生变化时,会触发该事件,我们可以在事件回调中判断是否是我们插件网站的cookies变化,如果是,就可以根据cookies来判断用户是否登录。
需要修改manifest.json文件,添加cookies权限。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| { "manifest_version": 3, "name": "Plugin", "version": "1.0", "permissions": [ "cookies" ], "background": { "service_worker": "service_worker.js" }, "content_scripts": [ { "matches": [ "https://www.plugin.com/*" ], "js": [ "content.js" ] } ] }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| chrome.cookies.onChanged.addListener(function (changeInfo) { if (changeInfo.cookie.domain === ".plugin.com") { if (changeInfo.cookie.name === "session") { if (changeInfo.cookie.value) { chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "loggedIn" }); } else { chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "notLoggedIn" }); } } } });
|
整个流程是这样的:
- 用户访问登录页面
www.plugin.com/login,进行登录操作,登录成功后,服务器写入session cookies。
service_worker.js文件监听到www.plugin.com/login的cookies变化,进行登录相关业务逻辑处理。然后发送登录状态到content.js文件。
content.js文件监听到登录状态变化,执行相应的逻辑,比如显示/隐藏登录按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ │ │ 用户浏览器 │ │ 插件网站服务器 │ │ │ │ (www.plugin.com) │ └─────────────────┬────────┘ └───────────┬──────────────┘ │ │ ▼ │ ┌──────────────────────────┐ │ │ │ │ │ 登录页面 /login │────────────────▶│ 写入session cookies │ │ │ │ └──────────────────────────┘ │ │ ▼ ┌──────────────────────────┐ │ │ │ 扩展后台服务 │ │ service_worker.js │ │ 监听cookies变化 │ └───────────┬──────────────┘ │ ▼ ┌──────────────────────────┐ │ │ │ 扩展内容脚本 │ │ content.js │ │ │ └───────────┬──────────────┘ │ ▼ ┌──────────────────────────┐ │ │ │ 登录按钮显示/隐藏 │ │ │ └──────────────────────────┘
|
其实可以监听的应该不仅仅是session对应的cookies,其他比如token cookies等也可以监听,只是我们用的登录方案是基于cookies的,因此只需要监听cookies变化即可。
content script 监听网站B的登录状态变化
此种方案需要新增一个content script文件,我们假设叫 login_callback_script.js文件,专门用于监听网站B的登录状态变化,当然了,我们也可以在content.js文件中监听,但是为了避免与插件网站B的其他功能冲突,我们单独新建一个文件。
因为content script运行在网站B的上下文中,因此我们需要对网站B进行改造,新增一个/callback路由,用于提供让callback_script.js运行的环境。因此我们需要在manifest.json新增一个规则,如下:
1 2 3 4 5 6 7 8 9 10
| "content_scripts": [ { "matches": [ "https://plugin.com/callback*" ], "js": ["callback_script.js"], "run_at": "document_start" } ]
|
1 2 3 4 5 6 7 8 9 10 11
|
if (document.cookie.includes("session")) { chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "loggedIn" }); } else { chrome.runtime.sendMessage({ url: "www.plugin.com/login", status: "notLoggedIn" }); }
|
1 2 3 4 5 6 7 8 9 10 11
| chrome.runtime.onMessage.addListener(function (request, sender, sendResponse) { if (request.url === "www.plugin.com/login") { if (request.status === "loggedIn") { } else if (request.status === "notLoggedIn") { } } });
|
整个流程是这样的:
- 用户访问登录页面
www.plugin.com/login,进行登录操作,登录成功后,页面跳转至www.plugin.com/callback。
login_callback_script.js文件运行,判断用户是否登录,并发送登录状态到service_worker.js文件。
service_worker.js文件会根据登录状态变化,执行相应的逻辑,比如通知content.js文件登录状态发生了变化。
content.js文件会根据登录状态变化,执行相应的逻辑,比如显示/隐藏登录按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ ┌─────────────────────────────────┐ │ 用户浏览器 │ │ 登录回调脚本 │ │ 扩展后台服务 │ │ (访问 www.plugin.com/login) │ │ (login_callback_script.js) │ │ (service_worker.js) │ └─────────────────┬───────────────┘ └─────────────────┬───────────────┘ └─────────────────┬───────────────┘ │ │ │ │ 1. 用户登录成功 │ │ │ 页面跳转至 callback │ │ ├─────────────────────────────────────────>│ │ │ │ │ │ │ 2. 判断登录状态并发送消息 │ │ ├─────────────────────────────────────────>│ │ │ │ │ │ │ │ │ │ 3. 处理登录状态变化 │ │ │ 通知内容脚本 │ │ ├─────────────────┐ │ │ │ │ │ │ │ │ ┌─────────────────┴───────────────┐ ┌─────────────────┴───────────────┐ └─────────────────┴───────────────┘ │ 扩展内容脚本 │ │ │ │ (content.js) │ │ │ └─────────────────┬───────────────┘ └─────────────────────────────────┘ │ │ 4. 接收登录状态通知 │ 执行相应逻辑 │ │ 5. 显示/隐藏登录按钮 │ └─────────────────────────────────────────────────────────┐ │ ▼ ┌─────────────────────────┐ │ 用户界面更新 │ └─────────────────────────┘
|
看清了有些麻烦是吧,所以我们可以把login_callback_script.js文件中的逻辑放到content.js文件中,但仍然需要新增一个页面路径/callback,让content.js文件可以运行。
修改后的流程是这样的:
- 用户访问登录页面
www.plugin.com/login,进行登录操作,登录成功后,页面跳转至www.plugin.com/callback。
- B网站
content.js文件开始运行,判断用户是否登录,并发送登录状态到service_worker.js文件。
service_worker.js文件会根据登录状态变化,执行相应的逻辑,比如通知content.js文件登录状态发生了变化。
- A网站
content.js文件会根据登录状态变化,执行相应的逻辑,比如显示/隐藏登录按钮。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ │ │ 用户浏览器 │ │ B网站 (www.plugin.com) │ │ │ │ │ └─────────────────┬────────┘ └───────────┬──────────────┘ │ │ ▼ ▼ ┌──────────────────────────┐ ┌──────────────────────────┐ │ │ │ │ │ 登录页面 /login │────▶│ 回调页面 /callback │ │ │ │ │ └──────────────────────────┘ └───────────┬──────────────┘ │ ▼ ┌──────────────────────────┐ │ │ │ B网站 content.js │ │ │ └───────────┬──────────────┘ │ ▼ ┌──────────────────────────┐ │ │ │ 扩展后台服务 │ │ service_worker.js │ │ │ └───────────┬──────────────┘ │ ▼ ┌──────────────────────────┐ │ │ │ A网站 content.js │ │ │ └───────────┬──────────────┘ │ ▼ ┌──────────────────────────┐ │ │ │ 登录按钮显示/隐藏 │ │ │ └──────────────────────────┘
|
浪费我半天时间timeout 的问题
本来我是想着给插件添加oAuth2第三方登录,我想起我之前有一个demo项目实现了oAuth2,因此想着拿来直接改改就行,哪知道之前的demo很久没用了,google的oAuth2实例超过6个月不用就被删除了,因此我就新建了一个新的实例,结果遇到了一个登录超时的问题。用户在授权之后,服务器需要请求/token接口来获取access token,但是我之前的demo中,/token接口的一直是正常运行的,这次一直是请求超时,我想过是GFW的问题,所以我在运行的时候,一直是给系统设置了代理,但始终没有解决这个问题。
最后我不得不给node-fetch配置代理,不知道为什么,之前的demo中没有配置代理就正常工作,现在就不行了,好在配置了代理之后,就正常运行了。
这里再次诅咒GFW,它的存在导致了中国的程序员要解决很多原本不应该存在的问题,才能和世界上其他的程序员一样。