背景介绍
真实的项目通常都是需要携带动态的 cookie 的,复制粘贴到本地可不行,我这边提供两个解决方法。
-
第一个就是通过不安全浏览器打开 localhost 项目中直接调用线上地址,并且通过插件或者浏览器自己设置,使得强制携带跨站 cookie。浏览器看的话是 www.juejin.com/api/test 这个前面域名替换为真实要访问的域名,具体详细配置可以看我往篇跨域处理
-
代理模式,也就是通过 webpack 代理或者 vite 代理访问真实的地址,浏览器看的话接口为 localhost:3000/api/test,当启动了代理服务器后,我们需要将真实的 cookie 设置到 localhost下。
先获取对应 domain 下的 cookie。然后就设置到 localhost 就行,这里的话 chrome 119 以上的版本会获取不到带有独立分区(partitionKey)的 cookie,需要手动设置顶级域名 。也就是大象放冰箱需要几步
const cookies = await chrome.cookies.getAll({
url: 'https://www.juejin.com',
partitionKey: {
topLevelSite: 'https://shopping.com',
hasCrossSiteAncestor: false, // 表示此 cookie 不是通过跨站请求设置的
}
});
// 留个作业,自己写循环 cookies 手动设置
chrome.cookies.set({
url: 'http://localhost', domain: localhost, name, value, path: '/'
})
初始项目,磨刀不误砍柴工
先创建一个 chrome 插件项目
npm create vite@latest
可以参考我的目录
这里会存在一些热更新问题,因为 public 目录不会触发,但是为了方便,不想处理路径问题,可以先这样。所以需要自己手动 build 或者更改一下 src 目录的代码
我的做法是后者,然后搭配监听自动更新
继续来实现我们的项目,核心有两个,一个业务逻辑(也就是 curd cookie),一个是如何使用 chrome api
思路展示,谁都是产品经理
我的实现是获取当前用户输入的网址,然后循环获取这个网址的 list,然后扁平化数组,循环手动设置到 localhost (setCookies)
然后使用 storage 存储用户输入的网址,避免下次还需要输入
然后提供更新和删除逻辑,我实现都是全量删除,因为我想大部分人都是配置一次就不管了,用不到的功能咱们就不做,更新也就是重新调用 setCookies 函数
删除的话就是遍历 localhost 下面的 cookie 删掉,然后把存储的网站 list 更新
除此之外,我懒得用第三方 message,自己实现了下提示
还有注意下小细节,domain 是没有协议的,也就是 https/http,所以是 domain: localhost。url 是有协议,设置的时候我们要用 url: http://localhost 的。所以我默认就在输入框帮用户输入了 https:// ,除此之外输入框回车就可以更新
核心代码,我知道你很急,但是你先别急
// import './App.css';
import { useState, useEffect } from "react";
// 用线上就行
const urls = [];
function App() {
const [baseUrl, setBaseUrl] = useState(urls);
const [inputVal, setInputVal] = useState("https://");
const [message, setMessage] = useState("");
useEffect(() => {
const fetchBaseUrl = async () => {
const res = await chrome.storage.local.get("baseUrl");
// 检查返回的结果
if (res.baseUrl) {
setBaseUrl(res.baseUrl);
} else {
setBaseUrl(urls); // 使用默认值
}
};
fetchBaseUrl();
}, []); // 依赖数组为空,意味着只在组件挂载时运行一次
useEffect(() => {
return () => {
// 组件卸载时清除定时器
clearTimeout();
};
});
const showMessage = (msg) => {
setMessage(msg);
let timer = null;
if (!timer) {
timer = setTimeout(() => {
setMessage("");
}, 2000);
}
return () => clearTimeout(timer);
}
const deleteCookies = async () => {
// 重置
chrome.storage.local.set({ baseUrl: urls }, () => {
setBaseUrl(urls);
showMessage("重置成功");
});
chrome.cookies.getAll({ url: "http://localhost" }, (cookies) => {
cookies.forEach((cookie) => {
chrome.cookies.remove(
{ url: "http://localhost", name: cookie.name },
(details) => {
if (chrome.runtime.lastError) {
console.error(
`Error deleting cookie: ${cookie.name}`,
chrome.runtime.lastError
);
} else {
console.log(`Deleted cookie: ${cookie.name}`);
// alert('删除成功', cookie.name);
}
}
);
});
});
};
const setCookies = (cookies) => {
for (const { name, value } of cookies) {
chrome.cookies.set(
{
url: "http://localhost",
domain: "localhost",
name,
value,
path: "/",
// path: '/'
// 可以根据需要设置其它可选的字段
},
function (setCookie) {
if (chrome.runtime.lastError) {
console.error(
`Error setting cookie for ${name}:`,
chrome.runtime.lastError
);
} else {
console.log("Cookie set:", setCookie, name, value);
}
}
);
}
}
const setAllCookies = async () => {
const cookiePromises = urls.map((url) => chrome.cookies.getAll({ url }));
const cookieArray = await Promise.all(cookiePromises);
const cookies = cookieArray.flat();
if (Array.isArray(cookies) && cookieArray.length > 0) {
showMessage("获取 cookies 成功");
setCookies(cookies)
}
};
const onAddUrl = (e) => {
if (e.key === "Enter") {
// 阻止换行
e.preventDefault();
// 获取当前输入的 URL
const inputUrl = e.target.value;
// 获取所有 cookies
chrome.cookies.getAll({ url: inputUrl }, (cookies) => {
if (Array.isArray(cookies) && cookies.length > 0) {
setCookies(cookies);
showMessage(`获取 cookies 成功`);
// 更新新的 URL 列表
setBaseUrl((prevUrls) => {
const uniqueUrls = [...new Set([...prevUrls, inputUrl])];
// 存储新的 URL 列表
chrome.storage.local.set({ baseUrl: uniqueUrls });
return uniqueUrls; // 返回新的 URL 列表
});
} else {
showMessage(`获取 cookies 失败,请登录或者检查域名完整`);
}
});
// 重置输入值
setInputVal("https://");
}
};
return (
<div
style={{
width: 260,
// maxHeight: 400,
overflow: "auto",
padding: 10,
position: "relative",
}}
>
<div>目前获取的网站 cookie</div>
{baseUrl?.map((v) => (
<p key={v}>{v}</p>
))}
<p>添加新的网站</p>
<textarea
value={inputVal}
onKeyDown={onAddUrl}
onChange={(e) => setInputVal(e.target.value)}
style={{ width: "100%", height: 100, boxSizing: "border-box" }}
></textarea>
<div>
<button onClick={setAllCookies} style={{ marginRight: 10 }}>
更新 cookies
</button>
<button
onClick={() => {
deleteCookies();
}}
>
删除 cookies
</button>
</div>
{message && (
<div
style={{
color: "green",
border: "1px solid green",
padding: '2px 4px',
zIndex: 100,
top: 10,
position: "absolute",
left: "50%",
transform: "translateX(-50%)",
background: 'white',
display: 'inline-block',
}}
>
{message}
</div>
)}
</div>
);
}
export default App;
看不懂或者懒得看的同学可以复制过去,跑起来回去看思路就知道了
我这里没考虑到的情况是需要监听 cookie 变化,现在的插件当 cookie 更新后可能需要用户手动更新一下
如果想要实现监听变化,可以在 backgroud.js 文件实现
autoSyncCookiesEvent();
function autoSyncCookiesEvent() {
console.log('start autoSyncCookiesEvent');
chrome.cookies.onChanged.addListener(async (params = {}) => {
const { cookie: { domain, name, value } = {} } = params;
const localStorage = await chrome.storage.local.get();
const { domainList = [] } = localStorage || {};
domainList.forEach(async (item = {}) => {
const { isAuto = false, source, target, cookieNameList = [] } = item;
if (!isAuto) return;
// 1. 过滤域名
if (removeProtocol(source) === domain) {
// 2. 过滤name
const flag = cookieNameList.findIndex((cookieName) => cookieName === name) !== -1;
if (!flag) return;
// 3. 更新目标地址的cookie
await chrome.cookies.set({
url: addProtocol(target),
domain: removeProtocol(target),
name,
path: '/',
value
});
}
});
});
}
const addProtocol = (uri) => {
return uri.startsWith('http') ? uri : `http://${uri}`;
};
// 移除协议头
const removeProtocol = (uri) => {
return uri.startsWith('http') ? uri.replace('http://', '').replace('https://', '') : uri;
};