diff --git a/public/manifest.json b/public/manifest.json index 918990e..0bb866c 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -25,10 +25,11 @@ "permissions": [ "proxy", "storage", - "sidePanel" + "sidePanel", + "webRequest" ], "host_permissions": [ - "*://*/*" + "" ], "web_accessible_resources": [ diff --git a/public/proxy.js b/public/proxy.js index e82e069..7bf7699 100644 --- a/public/proxy.js +++ b/public/proxy.js @@ -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: [""] } + ); + + // 监听请求错误 + chrome.webRequest.onErrorOccurred.addListener( + (details) => { + // 使用非阻塞方式处理错误 + queueProxyLog(details, new Error(details.error)).catch(error => { + console.error('Error in proxy error listener:', error); + }); + }, + { 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; } }); diff --git a/public/proxy/proxy-logs.js b/public/proxy/proxy-logs.js new file mode 100644 index 0000000..5bda8b6 --- /dev/null +++ b/public/proxy/proxy-logs.js @@ -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); + }); + } +} \ No newline at end of file diff --git a/public/types/action.js b/public/types/action.js index 59f9dbf..b8b4850 100644 --- a/public/types/action.js +++ b/public/types/action.js @@ -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" }; \ No newline at end of file diff --git a/src/pages/OptionsPage/index.tsx b/src/pages/OptionsPage/index.tsx index 8418e1a..4e92b80 100644 --- a/src/pages/OptionsPage/index.tsx +++ b/src/pages/OptionsPage/index.tsx @@ -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 ( @@ -370,11 +390,33 @@ export const OptionsPage: React.FC = () => { key: 'logs', label: '代理日志', children: ( - + <> +
+ +
+
`共 ${total} 条`, + pageSizeOptions: ['10', '20', '50', '100'] + }} + rowKey="id" + /> + ) } ]} diff --git a/src/types/action.ts b/src/types/action.ts index 634ac88..f77b087 100644 --- a/src/types/action.ts +++ b/src/types/action.ts @@ -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]; \ No newline at end of file +export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType]; \ No newline at end of file