add proxy switch demo

This commit is contained in:
go0p
2025-03-02 15:31:20 +08:00
parent 5cb1d1fa22
commit 47ccb37a26
6 changed files with 299 additions and 32 deletions
+3 -2
View File
@@ -25,10 +25,11 @@
"permissions": [
"proxy",
"storage",
"sidePanel"
"sidePanel",
"webRequest"
],
"host_permissions": [
"*://*/*"
"<all_urls>"
],
"web_accessible_resources": [
+133 -13
View File
@@ -2,27 +2,82 @@ import { ProxyManager } from './proxy/proxy-manager.js';
import { ProxySettings } from './proxy/proxy-settings.js';
import { ProxyAuth } from './proxy/proxy-auth.js';
import { ProxyActionType } from './types/action.js';
import { ProxyLogs } from './proxy/proxy-logs.js';
// 记录代理日志
async function logProxyRequest(details, proxyConfig, error = null) {
const log = {
id: Date.now().toString(),
timestamp: Date.now(),
url: details.url,
proxyId: proxyConfig.id,
proxyName: proxyConfig.name,
status: error ? 'error' : 'success',
errorMessage: error?.message
};
try {
// 只记录主文档和 XHR 请求
if (!['main_frame', 'xmlhttprequest'].includes(details.type)) {
return;
}
const result = await chrome.storage.local.get('proxyLogs');
const logs = result.proxyLogs || [];
const updatedLogs = [log, ...logs].slice(0, 1000);
await chrome.storage.local.set({ proxyLogs: updatedLogs });
const log = {
id: Date.now().toString(),
timestamp: Date.now(),
url: details.url,
proxyId: proxyConfig.id,
proxyName: proxyConfig.name,
type: details.type,
status: error ? 'error' : 'success',
errorMessage: error?.message
};
// 使用 storage 存储日志
const result = await chrome.storage.local.get('proxyLogs');
const logs = result.proxyLogs || [];
const updatedLogs = [log, ...logs].slice(0, 100); // 只保留最新的 100 条
await chrome.storage.local.set({ proxyLogs: updatedLogs });
} catch (error) {
console.error('Error logging proxy request:', error);
}
}
async function handleSetProxyConfig(config, sendResponse) {
try {
// 处理直接连接的情况
if (config.proxyType === 'direct') {
await chrome.proxy.settings.set({
value: { mode: "direct" },
scope: 'regular'
});
const settings = await chrome.proxy.settings.get({});
const isSuccess = settings.value.mode === "direct";
if (isSuccess) {
// 更新存储
await chrome.storage.local.set({
currentProxy: {
...config,
timestamp: Date.now()
}
});
// 更新代理列表状态
const result = await chrome.storage.local.get('proxyConfigs');
if (result.proxyConfigs) {
const updatedConfigs = result.proxyConfigs.map(c => ({
...c,
enabled: c.id === config.id
}));
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
}
console.log('Direct connection set successfully');
sendResponse({ success: true });
} else {
console.error('Failed to set direct connection');
sendResponse({
success: false,
error: '无法设置直接连接'
});
}
return;
}
// 处理代理服务器的情况
if (!config || !config.host || !config.port) {
sendResponse({
success: false,
@@ -162,12 +217,64 @@ async function handleGetProxyStatus(sendResponse) {
}
}
// 添加代理请求监听器
function setupProxyRequestListener() {
// 监听请求发送
chrome.webRequest.onBeforeRequest.addListener(
(details) => {
// 使用非阻塞方式处理请求
queueProxyLog(details).catch(error => {
console.error('Error in proxy request listener:', error);
});
// 不需要返回值
},
{ urls: ["<all_urls>"] }
);
// 监听请求错误
chrome.webRequest.onErrorOccurred.addListener(
(details) => {
// 使用非阻塞方式处理错误
queueProxyLog(details, new Error(details.error)).catch(error => {
console.error('Error in proxy error listener:', error);
});
},
{ urls: ["<all_urls>"] }
);
}
// 使用队列处理日志
async function queueProxyLog(details, error = null) {
// 检查是否是扩展自身的请求
if (details.url.startsWith('chrome-extension://')) {
return;
}
// 检查代理状态
const settings = await chrome.proxy.settings.get({});
if (settings.value.mode !== "fixed_servers") {
return;
}
// 获取当前代理配置
const { currentProxy } = await chrome.storage.local.get('currentProxy');
if (!currentProxy) {
return;
}
// 记录日志
await logProxyRequest(details, currentProxy, error);
}
// 导出代理处理器设置函数
export function setupProxyHandlers() {
// 设置代理错误处理和认证
ProxyAuth.setupErrorHandler();
ProxyAuth.setupAuthListener();
// 设置代理请求监听器
setupProxyRequestListener();
// 消息监听器
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
console.log("Proxy message:", msg);
@@ -184,6 +291,19 @@ export function setupProxyHandlers() {
case ProxyActionType.GET_PROXY_STATUS:
handleGetProxyStatus(sendResponse);
return true;
case ProxyActionType.CLEAR_PROXY_LOGS:
chrome.storage.local.set({ proxyLogs: [] }, () => {
if (chrome.runtime.lastError) {
sendResponse({
success: false,
error: chrome.runtime.lastError.message
});
} else {
sendResponse({ success: true });
}
});
return true;
}
});
+98
View File
@@ -0,0 +1,98 @@
// 日志数据库管理
export class ProxyLogs {
static DB_NAME = 'yakit_proxy_logs';
static STORE_NAME = 'logs';
static VERSION = 1;
static async openDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
if (!db.objectStoreNames.contains(this.STORE_NAME)) {
const store = db.createObjectStore(this.STORE_NAME, { keyPath: 'id' });
// 创建索引
store.createIndex('timestamp', 'timestamp');
store.createIndex('url', 'url');
store.createIndex('proxyId', 'proxyId');
store.createIndex('status', 'status');
}
};
});
}
static async addLog(log) {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.STORE_NAME], 'readwrite');
const store = transaction.objectStore(this.STORE_NAME);
// 添加新日志
const request = store.add(log);
request.onsuccess = () => {
// 删除旧日志,只保留最新的 100 条
const countRequest = store.count();
countRequest.onsuccess = () => {
if (countRequest.result > 100) {
const index = store.index('timestamp');
const cursorRequest = index.openCursor();
let deleteCount = countRequest.result - 100;
cursorRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && deleteCount > 0) {
store.delete(cursor.primaryKey);
deleteCount--;
cursor.continue();
}
};
}
};
resolve();
};
request.onerror = () => reject(request.error);
});
}
static async getLogs(limit = 100) {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.STORE_NAME], 'readonly');
const store = transaction.objectStore(this.STORE_NAME);
const index = store.index('timestamp');
const request = index.openCursor(null, 'prev');
const logs = [];
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && logs.length < limit) {
logs.push(cursor.value);
cursor.continue();
} else {
resolve(logs);
}
};
request.onerror = () => reject(request.error);
});
}
static async clearLogs() {
const db = await this.openDB();
return new Promise((resolve, reject) => {
const transaction = db.transaction([this.STORE_NAME], 'readwrite');
const store = transaction.objectStore(this.STORE_NAME);
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
+2 -1
View File
@@ -1,5 +1,6 @@
export const ProxyActionType = {
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
GET_PROXY_STATUS: "GET_PROXY_STATUS"
GET_PROXY_STATUS: "GET_PROXY_STATUS",
CLEAR_PROXY_LOGS: "CLEAR_PROXY_LOGS"
};
+48 -6
View File
@@ -103,17 +103,21 @@ export const OptionsPage: React.FC = () => {
}, []);
useEffect(() => {
// 加载日志
const loadLogs = async () => {
const result = await chrome.storage.local.get('proxyLogs');
setProxyLogs(result.proxyLogs || []);
};
loadLogs();
// 监听存储变化
const handleStorageChange = (changes: StorageChanges) => {
if (changes.proxyLogs) {
setProxyLogs(changes.proxyLogs.newValue);
setProxyLogs(changes.proxyLogs.newValue || []);
}
};
chrome.storage.onChanged.addListener(handleStorageChange);
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
}, []);
@@ -235,6 +239,22 @@ export const OptionsPage: React.FC = () => {
}
};
const handleClearLogs = () => {
chrome.runtime.sendMessage({
action: ProxyActionType.CLEAR_PROXY_LOGS
}, (response) => {
if (chrome.runtime.lastError) {
message.error(chrome.runtime.lastError.message || '清除日志失败');
return;
}
if (response?.success) {
message.success('日志已清除');
} else {
message.error(response?.error || '清除日志失败');
}
});
};
return (
<Layout className="options-page">
<Content style={contentStyle}>
@@ -370,11 +390,33 @@ export const OptionsPage: React.FC = () => {
key: 'logs',
label: '代理日志',
children: (
<Table
dataSource={proxyLogs}
columns={columns}
pagination={{ pageSize: 50 }}
/>
<>
<div style={{
marginBottom: 16,
display: 'flex',
justifyContent: 'flex-end'
}}>
<Button
danger
onClick={handleClearLogs}
icon={<DeleteOutlined />}
>
</Button>
</div>
<Table
dataSource={proxyLogs}
columns={columns}
pagination={{
pageSize: 10,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `${total}`,
pageSizeOptions: ['10', '20', '50', '100']
}}
rowKey="id"
/>
</>
)
}
]}
+15 -10
View File
@@ -1,15 +1,20 @@
// export const ActionType = {
// CONNECT: "CONNECT",
// SEND_MESSAGE: "SEND_MESSAGE",
// DISCONNECT: "DISCONNECT",
// SET_PROXY: "SET_PROXY",
// CLEAR_PROXY: "CLEAR_PROXY",
// PROXY_STATUS: "PROXY_STATUS",
// INJECT_SCRIPT: "INJECT_SCRIPT"
// } as const;
// export type ActionType = typeof ActionType[keyof typeof ActionType];
export const ProxyActionType = {
CONNECT: "CONNECT",
SEND_MESSAGE: "SEND_MESSAGE",
DISCONNECT: "DISCONNECT",
SET_PROXY: "SET_PROXY",
CLEAR_PROXY: "CLEAR_PROXY",
PROXY_STATUS: "PROXY_STATUS",
INJECT_SCRIPT: "INJECT_SCRIPT",
BADGE_COUNT: "BADGE_COUNT",
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
GET_PROXY_STATUS: "GET_PROXY_STATUS"
GET_PROXY_STATUS: "GET_PROXY_STATUS",
CLEAR_PROXY_LOGS: "CLEAR_PROXY_LOGS"
} as const;
export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType];
export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType];