CookieFlow
🚀 一个简洁现代的Cookie复制和管理工具
📖 开发背景
单点登录导致不同环境需要使用不同个cookie,手动去控制台粘贴过于繁琐麻烦,于是开发这款插件用于提高调试效率。
🛠️ 使用技术栈
- React18 : 18.react.dev/ - 现代前端框架
- Tailwindcss : tailwindcss.com/ - 原子化CSS框架
- Antd : ant.design/ - 企业级UI设计语言
- Vite : vite.dev/ - 下一代前端构建工具
✨ 插件功能
- ✅ 复制源地址
cookies到目标地址 - ✅ 清空目标地址的
cookies - ✅ 历史记录支持且可删除
- ✅
URL合法性校验 - ✅ 消息提示弹窗
- ✅ 自动读取当前页面
URL
🏗️ 项目架构
参考百度TDA平台中AI助手组件的架构设计:UI层与业务逻辑抽离,状态管理与工具函数抽离,采用高内聚低耦合、单一职责原则的分层架构。
scss
🎨 UI层 (Popup.jsx) ← 纯视图渲染
↓
⚛️ Hook层 (useCookieFlow.js) ← 状态管理 + 副作用处理
↓
🔧 Utils层 (cookie.js, storage.js, urlValidation.js) ← 纯函数工具集
📁 代码结构
csharp
CookieFlow/
├── 📁 assets/
│ ├── 📄 icon.svg # 插件图标
│ └── 📄 icon*.png # 不同尺寸图标
├── 📁 src/
│ ├── 📁 components/
│ │ └── 📄 UrlInput.jsx # URL输入框组件
│ ├── 📁 hooks/
│ │ └── 📄 useCookieFlow.js # 自定义业务Hook
│ ├── 📁 pages/
│ │ └── 📁 popup/
│ │ ├── 📄 index.html # 弹窗页面模板
│ │ ├── 📄 index.jsx # React应用入口
│ │ └── 📄 Popup.jsx # 主弹窗组件
│ ├── 📁 styles/
│ │ └── 📄 index.css # 全局样式
│ └── 📁 utils/ # 工具函数层
│ ├── 📄 cookie.js # Cookie操作核心逻辑
│ ├── 📄 storage.js # Chrome存储API封装
│ ├── 📄 history.js # 历史记录处理逻辑
│ ├── 📄 message.jsx # 消息提示封装
│ ├── 📄 urlValidation.js # URL校验工具
│ └── 📄 init.js # 初始化逻辑
├── 📄 manifest.json # Chrome扩展配置
├── 📄 package.json # 项目依赖配置
└── 📄 vite.config.js # Vite构建配置
🚀 快速开始
开发命令
bash
# 安装依赖
npm install
# 开发模式
npm run dev
# 构建生产版本
npm run build
安装使用
- 克隆项目代码
- 运行
npm install安装依赖 - 运行
npm run build构建项目 - 在Chrome中打开扩展程序页面 (
chrome://extensions/) - 启用"开发者模式"
- 点击"加载已解压的扩展程序",选择项目的
dist目录
🔌 Chrome API 使用
本项目主要使用以下Chrome扩展API:
-
chrome.cookies - 管理浏览器Cookie数据
getAll()- 获取指定URL的Cookieset()- 设置Cookieremove()- 删除Cookie
-
chrome.tabs - 获取浏览器标签页信息
query()- 查询当前活跃标签页
-
chrome.storage.local - 本地数据存储
get()- 读取本地存储set()- 保存本地存储
🔗 开源地址
GitHub : Mebius1916/CookieFlow
本项目采用开源协议,欢迎提交 PR 参与开源贡献!🎉
📚 源码讲解
1️⃣ Cookie复制功能
功能流程: 输入验证 → 获取源Cookie → 设置目标Cookie → 保存历史记录 → 显示结果
1.1 顶层处理函数
位于 useCookieFlow.js 中的主处理函数,负责状态管理和流程控制:
javascript
const handleCopyCookies = async () => {
setCopyLoading(true); // 设置加载状态
try {
const result = await copyCookies(sourceUrl, targetUrl);
if (result) {
// 更新历史记录UI
await updateHistory(setSourceHistory, setTargetHistory);
}
} finally {
setCopyLoading(false); // 恢复按钮状态
}
};
1.2 核心业务逻辑
位于 cookie.js 中的 copyCookies 函数,实现完整的复制流程:
javascript
export const copyCookies = async (sourceUrl, targetUrl) => {
// 1. URL预处理:提取origin部分(协议+域名+端口)
const processedSourceUrl = extractOrigin(sourceUrl);
const processedTargetUrl = extractOrigin(targetUrl);
// 2. URL合法性验证
const { validateFormUrls } = await import('./urlValidation');
const validation = validateFormUrls(processedSourceUrl, processedTargetUrl);
if (!validation.isValid) {
const { message } = await import('antd');
message.error(validation.errors[0]);
return false;
}
try {
// 3. 获取源地址所有Cookie
const cookies = await getCookies(processedSourceUrl);
// 4. 检查Cookie数量
if (cookies.length === 0) {
const { showInfo } = await import('./message');
showInfo('源地址没有可复制的Cookie');
return true;
}
// 5. 批量设置目标地址Cookie
const result = await setCookies(cookies, processedTargetUrl);
// 6. 保存操作记录到历史
await saveCookieOperation(processedSourceUrl, processedTargetUrl, result.successCount);
// 7. 显示操作结果
const { showOperationResult } = await import('./message');
showOperationResult('复制', result);
return true;
} catch (error) {
console.error('复制Cookie失败:', error);
const { showError } = await import('./message');
showError('复制Cookie');
return false;
}
};
1.3 Cookie获取与设置
获取Cookie:使用Chrome API获取指定域名下的所有Cookie
javascript
export const getCookies = async (url) => {
if (!isValidUrl(url)) {
console.error('无效的URL格式:', url);
return [];
}
try {
return await chrome.cookies.getAll({ url });
} catch (error) {
console.error('获取Cookie失败:', error);
return [];
}
};
设置Cookie:批量设置Cookie到目标URL,保持原有属性
javascript
export const setCookies = async (cookies, targetUrl) => {
if (!isValidUrl(targetUrl)) {
return { successCount: 0 };
}
let successCount = 0;
for (const cookie of cookies) {
try {
await chrome.cookies.set({
url: targetUrl,
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path,
secure: cookie.secure,
httpOnly: cookie.httpOnly,
sameSite: cookie.sameSite,
expirationDate: cookie.expirationDate,
});
successCount++;
} catch (error) {
console.error(`设置Cookie失败: ${cookie.name}`, error);
}
}
return { successCount };
};
2️⃣ Cookie清空功能
功能流程: URL验证 → 获取所有Cookie → 逐个删除 → 显示结果
2.1 清空处理函数
javascript
const handleClearCookies = async () => {
setClearLoading(true);
try {
await clearCookies(targetUrl);
} finally {
setClearLoading(false);
}
};
2.2 清空实现逻辑
javascript
export const clearCookies = async (url) => {
if (!url) {
const { message } = await import('antd');
message.error('目标地址不能为空');
return { successCount: 0 };
}
// 提取URL的origin部分
const processedUrl = extractOrigin(url);
if (!isValidUrl(processedUrl)) {
const { message } = await import('antd');
message.error('无效的URL格式');
return { successCount: 0 };
}
try {
// 获取所有Cookie
const cookies = await chrome.cookies.getAll({ url: processedUrl });
let successCount = 0;
// 逐个删除Cookie
for (const cookie of cookies) {
try {
await chrome.cookies.remove({
url: processedUrl,
name: cookie.name
});
successCount++;
} catch (error) {
console.error(`删除Cookie失败: ${cookie.name}`, error);
}
}
// 显示操作结果
const { showOperationResult } = await import('./message');
showOperationResult('清除', { successCount });
return { successCount };
} catch (error) {
console.error('清除Cookie失败:', error);
const { showError } = await import('./message');
showError('清除Cookie');
return { successCount: 0 };
}
};
3️⃣ 历史记录管理
3.1 数据结构设计
历史记录采用数组结构,每条记录包含:
javascript
const record = {
source: 'https://example.com', // 源地址
target: 'http://localhost:8080', // 目标地址
timestamp: Date.now(), // 操作时间戳
count: 5 // 成功复制的Cookie数量
};
3.2 存储机制
保存操作记录:每次复制操作后自动保存
javascript
export const saveCookieOperation = async (source, target, successCount) => {
try {
const record = {
source,
target,
timestamp: Date.now(),
count: successCount
};
// 获取现有历史记录
const history = await getCookieHistory();
// 新记录插入首位,限制最多10条
const newHistory = [record, ...(history || [])].slice(0, 10);
// 保存到Chrome本地存储
await saveCookieHistory(newHistory);
return true;
} catch (error) {
console.error('保存Cookie操作记录失败:', error);
return false;
}
};
存储API封装 (位于 storage.js):
javascript
// 读取历史记录
export const getCookieHistory = async () => {
try {
const result = await chrome.storage.local.get('cookieHistory');
return result.cookieHistory || [];
} catch (error) {
console.error('获取Cookie历史记录失败:', error);
return [];
}
};
// 保存历史记录
export const saveCookieHistory = async (history) => {
try {
await chrome.storage.local.set({ cookieHistory: history });
} catch (error) {
console.error('保存Cookie历史记录失败:', error);
throw error;
}
};
3.3 历史记录提取
从完整历史记录中提取URL列表,用于下拉框显示:
javascript
// 通用历史记录获取函数
const getUrlHistory = async (field) => {
try {
const history = await getCookieHistory();
if (!history || history.length === 0) return [];
// 使用Set去重,slice限制最多10条
return [...new Set(history.map(item => item[field]))].slice(0, 10);
} catch (error) {
console.error(`获取${field}历史记录失败:`, error);
return [];
}
};
// 导出特定类型的历史记录获取函数
export const getSourceUrlHistory = () => getUrlHistory('source');
export const getTargetUrlHistory = () => getUrlHistory('target');
3.4 并发更新优化
使用 Promise.all 并发请求,提升性能:
javascript
export const updateHistory = async (setSourceHistory, setTargetHistory) => {
try {
// 并发获取源地址和目标地址历史记录
const [sourceHistory, targetHistory] = await Promise.all([
getSourceUrlHistory(),
getTargetUrlHistory()
]);
// 更新UI状态
setSourceHistory(sourceHistory || []);
setTargetHistory(targetHistory || []);
} catch (error) {
console.error('更新历史记录失败:', error);
}
};
4️⃣ 历史记录删除
4.1 UI交互设计
在 UrlInput.jsx 组件中,鼠标悬停时显示删除按钮:
javascript
<div className="flex items-center justify-between max-w-64 cursor-pointer py-1 px-2 hover:bg-gray-100 group">
<span className="truncate flex-1 mr-2">{url}</span>
<DeleteOutlined
className="text-gray-400 hover:text-red-500 opacity-0 group-hover:opacity-100 transition-opacity"
onClick={(e) => handleDeleteHistory(index, e)}
/>
</div>
4.2 删除事件处理
javascript
const handleDeleteHistory = (index, e) => {
e.stopPropagation(); // 阻止事件冒泡,避免触发选择操作
if (onDeleteHistory) {
onDeleteHistory(index);
}
};
4.3 删除业务逻辑
Hook层处理:
javascript
const handleDeleteSourceHistory = async (index) => {
try {
await deleteSourceUrlHistory(index);
// 删除后重新获取最新历史记录
const newSourceHistory = await getSourceUrlHistory();
setSourceHistory(newSourceHistory || []);
} catch (error) {
console.error('删除源地址历史记录失败:', error);
showError('删除历史记录');
}
};
Utils层实现:
javascript
// 通用删除函数
const deleteUrlHistory = async (index, field) => {
try {
const history = await getCookieHistory();
if (!history || history.length === 0) return;
// 获取去重后的URL列表
const urls = [...new Set(history.map(item => item[field]))].slice(0, 10);
const urlToDelete = urls[index];
if (urlToDelete) {
// 过滤掉包含该URL的所有记录
const newHistory = history.filter(item => item[field] !== urlToDelete);
await saveCookieHistory(newHistory);
}
} catch (error) {
console.error(`删除${field}历史记录失败:`, error);
throw error;
}
};
// 导出特定类型的删除函数
export const deleteSourceUrlHistory = (index) =>
deleteUrlHistory(index, 'source');
export const deleteTargetUrlHistory = (index) =>
deleteUrlHistory(index, 'target');
5️⃣ URL校验机制
5.1 基础校验函数
javascript
export const isValidUrl = (url) => {
try {
new URL(url); // 使用浏览器原生URL构造函数校验
return true;
} catch (error) {
return false;
}
};
5.2 表单验证
提供详细的错误信息用于用户提示:
javascript
export const validateFormUrls = (sourceUrl, targetUrl) => {
const errors = [];
if (!sourceUrl || !sourceUrl.trim()) {
errors.push('源地址不能为空');
}
if (!targetUrl || !targetUrl.trim()) {
errors.push('目标地址不能为空');
}
if (sourceUrl && sourceUrl.trim() && !isValidUrl(sourceUrl.trim())) {
errors.push('源地址格式不正确');
}
if (targetUrl && targetUrl.trim() && !isValidUrl(targetUrl.trim())) {
errors.push('目标地址格式不正确');
}
return {
isValid: errors.length === 0,
errors
};
};
5.3 URL处理
提取URL的origin部分,确保Cookie操作的准确性:
javascript
export const extractOrigin = (url) => {
try {
const urlObj = new URL(url);
return urlObj.origin; // 返回 "协议://域名:端口"
} catch (error) {
console.error('URL格式无效:', url);
return url;
}
};
6️⃣ 消息提示系统
位于 message.jsx 中,封装Antd的message组件:
javascript
// 操作结果提示
export const showOperationResult = (operation, result) => {
if (!result) {
message.error(`${operation}Cookie失败`);
return;
}
if (result.successCount > 0) {
message.success(`成功${operation} ${result.successCount} 个Cookie`);
} else {
message.info(`没有Cookie被${operation}`);
}
};
// 错误提示
export const showError = (action) => {
message.error(`${action}失败`);
};
// 信息提示
export const showInfo = (content) => {
message.info(content);
};