用上自己写的Chrome插件是一种什么感觉,想想都逼格很高,我来教你!
背景
先说这个需求的产生。之前的业务开发中,有一个场景是,因为每个账号的权限和数据不同,需要不停的切换账号去查看不同的数据,但在localhost
环境下登录成功后,却会跳转到qa
环境,需要很笨的打开浏览器控制台去复制qa
环境的cookie
去替换localhost
环境的cookie
。😰
同样的操作做多了之后意识到一定有优化的空间。最开始想到的是在配置文件里将localhost
也同qa
一样做特化,让它做正确的跳转。实践之后,确实解决了登录后错误跳转的问题。但需要反复登录、登出的问题依然在,所以,还得想! 🤔️
最终解决方案是用Chrome插件。
开整
因为功能很简单,所以代码也很简单。Chrome插件开发需要的知识也很简单,前端三件套就行。
准备工作
首先在Chrome浏览器
的扩展程序中加载已解压的扩展程序
1、在浏览器中打开chrome://extensions/ ;
2、点击加载已解压的扩展程序
,选择本地准备好的code目录;
3、加载完成。
开始code
先看一下插件code目录结构,由 前端三件套
+ icon
+ manifest.json
组成。
manifest.json
文件是Chrome插件的配置文件,下面是基本配置属性:
permissions
:插件可以使用的权限,本案例需要cookies
、tabs
。全部权限在这【👉🔗👈】。icons
:各尺寸的icons,icons推荐png,不支持svg。action
:引入html。
代码:
js
{
"manifest_version": 3,
"name": "一键登录",
"description": "自动填充有效token到cookie,仅供内部使用",
"version": "1.0.0",
"permissions": [
"cookies",
"tabs"
],
"icons": {
"16": "images/icon-16.png",
"32": "images/icon-32.png",
"48": "images/icon-48.png",
"128": "images/icon-128.png"
},
"action": {
"default_popup": "popup.html"
}
}
好了,接下来都是操作三件套了(代码比较简单,搞清楚思路以及所需要的功能即可)。
popup.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>一键登录</title>
<link rel="stylesheet" href="popup.css">
</head>
<body>
<div class="login-container">
<h2>一键登录</h2>
<div class="login-item" id="inputLoginItem">
<input class="uid-input" id="uidInput" placeholder="输入uid登录" />
<button class="login-button" id="loginButton">一键登录</button>
</div>
<div class="tips">
当前页面的 host 必须为以下几种才有效:
<div class="tips-iems">
<p> - *.qa.com</p>
<p> - localhost(只能登录qa环境)</p>
</div>
</div>
</div>
</body>
<script src="popup.js"></script>
</html>
popup.js
,考虑的点稍多,一步步来讲
1、配置不同环境的变量(localhost、qa)
js
// 环境Map
const envMap = {
localhost: {
url: 'http://localhost',
domain: 'localhost',
apiPrefix: 'https://api.qa.com',
envName: 'QA',
},
qa: {
url: 'https://www.qa.com',
domain: 'qa.com',
apiPrefix: 'https://api.qa.com',
defaultUserId: 100001,
envName: 'QA',
},
};
2、获取环境变量
js
// 环境名
const env = await getEnv();
// API前缀
const API_PREFIX = envMap[env]?.apiPrefix;
// 当前环境的URL
const BASE_URL = envMap[env]?.url;
// 当前环境的域名
const DOMAIN = envMap[env]?.domain;
// 当前环境默认设置的用户ID
const DEFAULT_USER_ID = envMap[env]?.defaultUserId;
// 当前环境的名字
const envName = envMap[env]?.envName;
// 获取当前环境
async function getEnv() {
let env;
const result = await chrome.tabs.query({
active: true,
currentWindow: true,
});
const url = result[0].url.split('?')[0];
const envKeys = Object.keys(envMap);
envKeys.forEach((item) => {
if (url.includes(envMap[item].domain)) {
env = item;
}
});
return env;
}
3、为dom绑定事件
js
// 登陆按钮
const loginButton = document.getElementById('loginButton');
// uid输入框
const uidInput = document.getElementById('uidInput');
// 点击事件
loginButton.onclick = async function () {
// 检查输入的uid是否合法
if (!uidInput.value || isNaN(Number(uidInput.value))) {
alert('请输入正确的uid');
return null;
}
// 拿到此uid返回的结果
const { token, user } = await fetchToken(uidInput.value, this);
// 如果用户存在,修改浏览器cookie
if (user) {
await setToken(token, user);
}
};
// 添加键盘事件,当按下回车键时,触发登陆按钮的点击事件
uidInput.onkeydown = function (e) {
if (e.code === 'Enter') {
loginButton.click();
}
};
// 获取uid token
async function fetchToken(uid, currentButton = null, isDefault = false) {
if (currentButton) {
currentButton.innerHTML = '登录中...';
currentButton.style.opacity = 0.8;
currentButton.disabled = true;
}
// 返回uid对应的token
const result = await request({
url: `${API_PREFIX}user/getToken?userId=${uid}`,
});
if (currentButton) {
currentButton.innerHTML = '一键登录';
currentButton.removeAttribute('style');
currentButton.disabled = false;
}
if (!result) {
alert('用户不存在');
return {};
}
return { token: result.token, user: uid };
}
// 设置cookie
async function setToken(token, user) {
const env = await getEnv();
if (!env || !user) return;
const { url, domain } = envMap[env];
chrome.cookies.set(
{
url,
name: 'token',
value: token,
domain: domain,
},
function () {
chrome.tabs.query({ active: true, currentWindow: true }, async function (arrayOfTabs) {
const enable = await isEnableHost();
if (enable) {
const redirectUrl = new URL(arrayOfTabs[0].url).searchParams.get('path');
if (redirectUrl) {
chrome.tabs.update(arrayOfTabs[0].id, {
url: redirectUrl,
});
} else {
chrome.tabs.reload(arrayOfTabs[0].id);
}
window.close();
}
});
},
);
}
// 当前host是否启用
async function isEnableHost() {
const env = await getEnv();
if (!env) return false;
const arrayOfTabs = await chrome.tabs.query({
active: true,
currentWindow: true,
});
const currentUrl = arrayOfTabs[0].url.split('?')[0];
return currentUrl.includes(DOMAIN);
}
4、初始化init
js
// 初始化,不是可用环境的域名时,禁用登陆按钮;否则show出当前环境的名字,并显示历史登陆uid记录
async function init() {
if (!env) {
inputLoginButton.disabled = !(await isEnableHost());
uidInput.setAttribute('disabled', 'disabled');
return;
}
envNameElement.innerText = `- 当前为 ${envName} 环境`;
await renderHistory();
}
popup.js
完整代码(一个js文件全部梭哈🤣):
js
(async function () {
// 环境Map
const envMap = {
localhost: {
url: 'http://localhost',
domain: 'localhost',
apiPrefix: 'https://api.qa.com',
envName: 'QA',
},
qa: {
url: 'https://www.qa.com',
domain: 'qa.com',
apiPrefix: 'https://api.qa.com',
defaultUserId: 100001,
envName: 'QA',
},
};
// 环境名
const env = await getEnv();
// API前缀
const API_PREFIX = envMap[env]?.apiPrefix;
// 当前环境的URL
const BASE_URL = envMap[env]?.url;
// 当前环境的域名
const DOMAIN = envMap[env]?.domain;
// 当前环境默认设置的用户ID
const DEFAULT_USER_ID = envMap[env]?.defaultUserId;
// 当前环境的名字
const envName = envMap[env]?.envName;
// 获取当前环境
async function getEnv() {
let env;
const result = await chrome.tabs.query({
active: true,
currentWindow: true,
});
const url = result[0].url.split('?')[0];
const envKeys = Object.keys(envMap);
envKeys.forEach((item) => {
if (url.includes(envMap[item].domain)) {
env = item;
}
});
return env;
}
// 登陆按钮
const loginButton = document.getElementById('loginButton');
// uid输入框
const uidInput = document.getElementById('uidInput');
// 点击事件(一套走完的基本流程)
loginButton.onclick = async function () {
// 1、检查输入的uid是否合法
if (!uidInput.value || isNaN(Number(uidInput.value))) {
alert('请输入正确的uid');
return null;
}
// 2、拿到此uid返回的结果
const { token, user } = await fetchToken(uidInput.value, this);
// 3、如果用户存在,修改浏览器cookie,并刷新页面
if (token && user) {
await setToken(token, user);
}
};
// =================== 基本思路结束 ===================
// 添加键盘事件,当按下回车键时,触发登陆按钮的点击事件
uidInput.onkeydown = function (e) {
if (e.code === 'Enter') {
loginButton.click();
}
};
// 下面是点击流程中需要用到的工具函数
// 获取 uid token
async function fetchToken(uid, currentButton = null, isDefault = false) {
if (currentButton) {
currentButton.innerHTML = '登录中...';
currentButton.style.opacity = 0.8;
currentButton.disabled = true;
}
// 返回uid对应的token
const result = await request({
url: `${API_PREFIX}user/getToken?userId=${uid}`,
});
if (currentButton) {
currentButton.innerHTML = '一键登录';
currentButton.removeAttribute('style');
currentButton.disabled = false;
}
if (!result) {
alert('用户不存在');
return {};
}
return { token: result.token, user: uid };
}
// 设置cookie
async function setToken(token, user) {
const env = await getEnv();
if (!env || !user) return;
const { url, domain } = envMap[env];
chrome.cookies.set(
{
url,
name: 'token',
value: token,
domain: domain,
},
function () {
chrome.tabs.query({ active: true, currentWindow: true }, async function (arrayOfTabs) {
const enable = await isEnableHost();
if (enable) {
const redirectUrl = new URL(arrayOfTabs[0].url).searchParams.get('path');
if (redirectUrl) {
chrome.tabs.update(arrayOfTabs[0].id, {
url: redirectUrl,
});
} else {
chrome.tabs.reload(arrayOfTabs[0].id);
}
window.close();
}
});
},
);
}
// 当前host是否启用
async function isEnableHost() {
const env = await getEnv();
if (!env) return false;
const arrayOfTabs = await chrome.tabs.query({
active: true,
currentWindow: true,
});
const currentUrl = arrayOfTabs[0].url.split('?')[0];
return currentUrl.includes(DOMAIN);
}
// 请求函数
function request({url, headers, method = "get"}) {
return new Promise((resolve, reject) => {
fetch(url, {
method,
headers
})
.then(function (response) {
response
.json()
.then(function (res) {
resolve(res.data);
})
.catch(function (error) {
console.error("Fetch error:" + error);
alert("请求失败");
reject(error);
});
})
.catch(function (error) {
console.error("Fetch error:" + error);
alert("请求失败");
reject(error);
});
})
}
// =========== 工具函数end ===========
// 初始化,不是可用环境的域名时,禁用登陆按钮;否则show出当前环境的名字
async function init() {
if (!env) {
inputLoginButton.disabled = !(await isEnableHost());
uidInput.setAttribute('disabled', 'disabled');
return;
}
envNameElement.innerText = `- 当前为 ${envName} 环境`;
await renderHistory();
}
init();
})();
还可以添加缓存功能(需要给storage
权限),这样就可以记录之前登录过的账号,可以优化的地方还有很多。
总结
看完这个例子后,有没有发现其实就是在操作dom和浏览器常见API,Chrome插件没有想的那么难,快快定制你的专属插件吧 (^-^)
over~🫡