use indexedDB manager proxySwitch

This commit is contained in:
go0p
2025-03-02 15:31:20 +08:00
parent 47ccb37a26
commit 8e2af7a869
23 changed files with 1542 additions and 753 deletions
+12
View File
@@ -9,6 +9,18 @@ const websocketManager = new WebSocketManager();
// 设置代理处理器
setupProxyHandlers();
// 添加点击事件处理
chrome.action.onClicked.addListener((tab) => {
// 打开侧边栏
chrome.sidePanel.open({ windowId: tab.windowId });
});
// 可选:设置默认打开状态
chrome.sidePanel.setOptions({
enabled: true,
path: 'index.html'
});
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
console.log("msg", msg)
switch (msg.action) {
+116
View File
@@ -0,0 +1,116 @@
class Database {
constructor() {
this.DB_NAME = 'yaklang_extension';
this.DB_VERSION = 1;
this.stores = {
PROXY_LOGS: 'proxy_logs',
PROXY_CONFIGS: 'proxy_configs',
CURRENT_PROXY: 'current_proxy',
PROXY_AUTH: 'proxy_auth'
};
}
async initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
request.onupgradeneeded = (event) => {
const db = event.target.result;
// 代理日志存储
if (!db.objectStoreNames.contains(this.stores.PROXY_LOGS)) {
const logsStore = db.createObjectStore(this.stores.PROXY_LOGS, { keyPath: 'id' });
logsStore.createIndex('timestamp', 'timestamp');
logsStore.createIndex('resourceType', 'resourceType');
logsStore.createIndex('status', 'status');
}
// 代理配置列表存储
if (!db.objectStoreNames.contains(this.stores.PROXY_CONFIGS)) {
const configsStore = db.createObjectStore(this.stores.PROXY_CONFIGS, { keyPath: 'id' });
configsStore.createIndex('name', 'name');
configsStore.createIndex('enabled', 'enabled');
}
// 当前代理配置存储
if (!db.objectStoreNames.contains(this.stores.CURRENT_PROXY)) {
db.createObjectStore(this.stores.CURRENT_PROXY);
}
// 代理认证信息存储
if (!db.objectStoreNames.contains(this.stores.PROXY_AUTH)) {
const authStore = db.createObjectStore(this.stores.PROXY_AUTH, { keyPath: 'id' });
authStore.createIndex('host', 'host');
}
};
});
}
async getStore(storeName, mode = 'readonly') {
const db = await this.initDB();
const tx = db.transaction(storeName, mode);
return tx.objectStore(storeName);
}
// 通用的 CRUD 操作
async get(storeName, key) {
const store = await this.getStore(storeName);
return new Promise((resolve, reject) => {
const request = store.get(key);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async getAll(storeName) {
const store = await this.getStore(storeName);
return new Promise((resolve, reject) => {
const request = store.getAll();
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async put(storeName, value, key = undefined) {
try {
const store = await this.getStore(storeName, 'readwrite');
return new Promise((resolve, reject) => {
const request = key ? store.put(value, key) : store.put(value);
request.onsuccess = () => {
console.log(`Successfully put data in ${storeName}:`, value);
resolve(request.result);
};
request.onerror = () => {
console.error(`Error putting data in ${storeName}:`, request.error);
reject(request.error);
};
});
} catch (error) {
console.error(`Error in put operation for ${storeName}:`, error);
throw error;
}
}
async delete(storeName, key) {
const store = await this.getStore(storeName, 'readwrite');
return new Promise((resolve, reject) => {
const request = store.delete(key);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async clear(storeName) {
const store = await this.getStore(storeName, 'readwrite');
return new Promise((resolve, reject) => {
const request = store.clear();
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
export const db = new Database();
+137
View File
@@ -0,0 +1,137 @@
import { db } from './db.js';
class ProxyStore {
constructor() {
this.MAX_LOGS = 1000;
}
// 代理配置相关操作
async getProxyConfigs() {
return await db.getAll(db.stores.PROXY_CONFIGS);
}
async saveProxyConfigs(configs) {
try {
console.log('Saving proxy configs:', configs);
// 确保配置数组有效
if (!Array.isArray(configs)) {
throw new Error('配置必须是数组');
}
// 开始事务
const store = await db.getStore(db.stores.PROXY_CONFIGS, 'readwrite');
// 清除现有配置
await store.clear();
// 保存新配置
for (const config of configs) {
await store.put(config);
}
console.log('Proxy configs saved successfully');
this.notifyConfigUpdate();
return true;
} catch (error) {
console.error('Error saving proxy configs:', error);
throw error;
}
}
async getCurrentProxy() {
return await db.get(db.stores.CURRENT_PROXY, 'current');
}
async setCurrentProxy(proxy) {
await db.put(db.stores.CURRENT_PROXY, proxy, 'current');
}
async clearCurrentProxy() {
await db.delete(db.stores.CURRENT_PROXY, 'current');
}
// 代理日志相关操作
async getLogs() {
const logs = await db.getAll(db.stores.PROXY_LOGS);
return logs.sort((a, b) => b.timestamp - a.timestamp);
}
async addLog(log) {
await db.put(db.stores.PROXY_LOGS, log);
await this.cleanOldLogs();
}
async clearLogs() {
await db.clear(db.stores.PROXY_LOGS);
}
async cleanOldLogs() {
const store = await db.getStore(db.stores.PROXY_LOGS, 'readwrite');
const countRequest = store.count();
countRequest.onsuccess = () => {
if (countRequest.result > this.MAX_LOGS) {
const excess = countRequest.result - this.MAX_LOGS;
const cursorRequest = store.index('timestamp').openCursor();
let deleted = 0;
cursorRequest.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && deleted < excess) {
cursor.delete();
deleted++;
cursor.continue();
}
};
}
};
}
// 代理认证相关操作
async getAuthHandlers() {
return await db.getAll(db.stores.PROXY_AUTH);
}
async saveAuthHandler(handler) {
await db.put(db.stores.PROXY_AUTH, handler);
}
async deleteAuthHandler(id) {
await db.delete(db.stores.PROXY_AUTH, id);
}
async clearAuthHandlers() {
await db.clear(db.stores.PROXY_AUTH);
}
async getErrors() {
return await db.get(db.stores.PROXY_AUTH, 'errors') || [];
}
async saveErrors(errors) {
await db.put(db.stores.PROXY_AUTH, errors, 'errors');
}
async getAuth() {
return await db.get(db.stores.PROXY_AUTH, 'auth');
}
async saveAuth(auth) {
await db.put(db.stores.PROXY_AUTH, auth, 'auth');
}
async clearAuth() {
await db.delete(db.stores.PROXY_AUTH, 'auth');
}
notifyConfigUpdate() {
chrome.runtime.sendMessage({
action: 'PROXY_CONFIGS_UPDATED'
}).catch(() => {
// 忽略接收者不存在的错误
});
}
}
export const proxyStore = new ProxyStore();
+3 -2
View File
@@ -16,7 +16,7 @@
}
},
"side_panel": {
"default_path" : "index.html"
"default_path": "index.html"
},
"background": {
"service_worker": "background.js",
@@ -26,7 +26,8 @@
"proxy",
"storage",
"sidePanel",
"webRequest"
"webRequest",
"declarativeNetRequest"
],
"host_permissions": [
"<all_urls>"
+150 -120
View File
@@ -1,69 +1,44 @@
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';
import { proxyLogs } from './proxy/proxy-logs.js';
import { proxyStore } from './db/proxy-store.js';
// 记录代理日志
async function logProxyRequest(details, proxyConfig, error = null) {
try {
// 只记录主文档和 XHR 请求
if (!['main_frame', 'xmlhttprequest'].includes(details.type)) {
return;
}
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);
}
// 修改代理状态获取函数为 Promise 形式
function getProxySettings() {
return new Promise((resolve) => {
chrome.proxy.settings.get({}, resolve);
});
}
async function handleSetProxyConfig(config, sendResponse) {
try {
// 处理直接连接的情况
if (config.proxyType === 'direct') {
await chrome.proxy.settings.set({
value: { mode: "direct" },
scope: 'regular'
await new Promise((resolve) => {
chrome.proxy.settings.set({
value: { mode: "direct" },
scope: 'regular'
}, resolve);
});
const settings = await chrome.proxy.settings.get({});
const settings = await getProxySettings();
const isSuccess = settings.value.mode === "direct";
if (isSuccess) {
// 更新存储
await chrome.storage.local.set({
currentProxy: {
...config,
timestamp: Date.now()
}
await proxyStore.setCurrentProxy({
...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 });
}
const configs = await proxyStore.getProxyConfigs();
const updatedConfigs = configs.map(c => ({
...c,
enabled: c.id === config.id
}));
await proxyStore.saveProxyConfigs(updatedConfigs);
console.log('Direct connection set successfully');
sendResponse({ success: true });
@@ -98,32 +73,30 @@ async function handleSetProxyConfig(config, sendResponse) {
}
};
await chrome.proxy.settings.set({
value: proxyConfig,
scope: 'regular'
await new Promise((resolve) => {
chrome.proxy.settings.set({
value: proxyConfig,
scope: 'regular'
}, resolve);
});
const settings = await chrome.proxy.settings.get({});
const settings = await getProxySettings();
const isSuccess = settings.value.mode === "fixed_servers" &&
settings.value.rules.singleProxy.host === config.host &&
settings.value.rules.singleProxy.port === parseInt(config.port);
if (isSuccess) {
await chrome.storage.local.set({
currentProxy: {
...config,
timestamp: Date.now()
}
await proxyStore.setCurrentProxy({
...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 });
}
const configs = await proxyStore.getProxyConfigs();
const updatedConfigs = configs.map(c => ({
...c,
enabled: c.id === config.id
}));
await proxyStore.saveProxyConfigs(updatedConfigs);
console.log('Proxy successfully set:', settings.value);
sendResponse({ success: true });
@@ -145,32 +118,31 @@ async function handleSetProxyConfig(config, sendResponse) {
async function handleClearProxyConfig(sendResponse) {
try {
await chrome.proxy.settings.clear({
scope: 'regular'
await new Promise((resolve) => {
chrome.proxy.settings.clear({
scope: 'regular'
}, resolve);
});
await chrome.proxy.settings.set({
value: { mode: "system" },
scope: 'regular'
await new Promise((resolve) => {
chrome.proxy.settings.set({
value: { mode: "system" },
scope: 'regular'
}, resolve);
});
// 只移除当前代理配置,保留代理列表
await chrome.storage.local.remove([
'currentProxy',
'proxyAuthHandlers'
]);
await proxyStore.clearCurrentProxy();
// 更新所有代理的启用状态
const result = await chrome.storage.local.get('proxyConfigs');
if (result.proxyConfigs) {
const updatedConfigs = result.proxyConfigs.map(config => ({
...config,
enabled: false
}));
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
}
const configs = await proxyStore.getProxyConfigs();
const updatedConfigs = configs.map(config => ({
...config,
enabled: false
}));
await proxyStore.saveProxyConfigs(updatedConfigs);
const settings = await chrome.proxy.settings.get({});
const settings = await getProxySettings();
const isSuccess = settings.value.mode === "system";
if (isSuccess) {
@@ -194,12 +166,12 @@ async function handleClearProxyConfig(sendResponse) {
async function handleGetProxyStatus(sendResponse) {
try {
const settings = await chrome.proxy.settings.get({});
const currentProxy = await chrome.storage.local.get('currentProxy');
const settings = await getProxySettings();
const currentProxy = await proxyStore.getCurrentProxy();
const status = {
enabled: settings.value.mode === "fixed_servers",
config: currentProxy.currentProxy || null,
config: currentProxy || null,
mode: settings.value.mode
};
@@ -245,25 +217,24 @@ function setupProxyRequestListener() {
// 使用队列处理日志
async function queueProxyLog(details, error = null) {
// 检查是否是扩展自身的请求
if (details.url.startsWith('chrome-extension://')) {
return;
}
try {
// 检查代理状态
const settings = await getProxySettings();
if (settings.value.mode !== "fixed_servers") {
return;
}
// 检查代理状态
const settings = await chrome.proxy.settings.get({});
if (settings.value.mode !== "fixed_servers") {
return;
}
// 获取当前代理配置
const currentProxy = await proxyStore.getCurrentProxy();
if (!currentProxy) {
return;
}
// 获取当前代理配置
const { currentProxy } = await chrome.storage.local.get('currentProxy');
if (!currentProxy) {
return;
// 记录日志
await proxyLogs.logRequest(details, currentProxy, error);
} catch (error) {
console.error('Error in queueProxyLog:', error);
}
// 记录日志
await logProxyRequest(details, currentProxy, error);
}
// 导出代理处理器设置函数
@@ -292,18 +263,86 @@ export function setupProxyHandlers() {
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 });
}
case ProxyActionType.GET_PROXY_LOGS:
proxyLogs.getLogs().then(logs => {
sendResponse({
success: true,
data: logs
});
}).catch(error => {
sendResponse({
success: false,
error: error.message
});
});
return true;
case ProxyActionType.CLEAR_PROXY_LOGS:
proxyLogs.clearLogs().then(() => {
sendResponse({ success: true });
}).catch(error => {
sendResponse({
success: false,
error: error.message
});
});
return true;
case ProxyActionType.GET_PROXY_CONFIGS:
proxyStore.getProxyConfigs().then(configs => {
sendResponse({
success: true,
data: configs
});
}).catch(error => {
sendResponse({
success: false,
error: error.message
});
});
return true;
case ProxyActionType.ADD_PROXY_CONFIG:
proxyStore.getProxyConfigs().then(async configs => {
const newConfigs = [...configs, msg.config];
await proxyStore.saveProxyConfigs(newConfigs);
sendResponse({ success: true });
}).catch(error => {
sendResponse({
success: false,
error: error.message
});
});
return true;
case ProxyActionType.UPDATE_PROXY_CONFIG:
(async () => { // 使用立即执行的异步函数
try {
if (!msg.configs || !Array.isArray(msg.configs)) {
throw new Error('无效的配置数据');
}
console.log('Updating proxy configs:', msg.configs);
await proxyStore.saveProxyConfigs(msg.configs);
// 获取最新的配置
const updatedConfigs = await proxyStore.getProxyConfigs();
console.log('Configs updated successfully:', updatedConfigs);
// 发送响应
sendResponse({
success: true,
data: updatedConfigs
});
} catch (error) {
console.error('Error updating proxy configs:', error);
sendResponse({
success: false,
error: error.message || '更新代理配置失败'
});
}
})();
return true; // 保持消息端口打开
}
});
@@ -318,15 +357,6 @@ export function setupProxyHandlers() {
// 设置认证监听
await ProxyAuth.setupAuthListener();
// 初始化存储
const storage = await chrome.storage.local.get(['proxyConfigs', 'proxyLogs']);
if (!storage.proxyConfigs) {
await chrome.storage.local.set({ proxyConfigs: [] });
}
if (!storage.proxyLogs) {
await chrome.storage.local.set({ proxyLogs: [] });
}
} catch (error) {
console.error('Error during installation:', error);
}
-1
View File
@@ -1,7 +1,6 @@
console.log('Options page script loaded');
import { ProxySettings } from './proxy-settings.js';
import { ProxyManager } from './proxy-manager.js';
let currentConfigs = [];
+71 -53
View File
@@ -1,72 +1,91 @@
// 代理认证管理
import { proxyStore } from '../db/proxy-store.js';
export class ProxyAuth {
static async setupAuthListener() {
try {
// 保存认证信息到 storage
const saveAuth = async (config) => {
await chrome.storage.local.set({
proxyAuth: {
username: config.username,
password: config.password,
timestamp: Date.now()
// 使用 chrome.webRequest.onAuthRequired 的非阻塞版本
chrome.webRequest.onAuthRequired.addListener(
async (details) => {
try {
// 获取认证处理器
const handlers = await proxyStore.getAuthHandlers();
const handler = handlers.find(h =>
details.challenger?.host === h.host
);
if (handler) {
// 使用 declarativeNetRequest 规则来处理认证
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [handler.id],
addRules: [{
id: parseInt(handler.id),
priority: 1,
action: {
type: 'modifyHeaders',
requestHeaders: [
{
header: 'Proxy-Authorization',
operation: 'set',
value: 'Basic ' + btoa(`${handler.username}:${handler.password}`)
}
]
},
condition: {
domains: [handler.host],
resourceTypes: ['main_frame', 'sub_frame', 'stylesheet', 'script', 'image', 'font', 'object', 'xmlhttprequest', 'ping', 'csp_report', 'media', 'websocket', 'other']
}
}]
});
}
});
};
} catch (error) {
console.error('Auth error:', error);
}
},
{ urls: ["<all_urls>"] }
);
}
// 获取认证信息
const getAuth = async () => {
const result = await chrome.storage.local.get('proxyAuth');
return result.proxyAuth;
};
static async saveAuthHandler(host, username, password) {
const handler = {
id: Date.now().toString(),
host,
username,
password
};
await proxyStore.saveAuthHandler(handler);
await this.setupAuthListener(); // 重新设置认证规则
}
// 清除认证信息
const clearAuth = async () => {
await chrome.storage.local.remove('proxyAuth');
};
return {
saveAuth,
getAuth,
clearAuth
};
} catch (error) {
console.error('Error setting up auth listener:', error);
return null;
static async removeAuthHandler(host) {
const handlers = await proxyStore.getAuthHandlers();
const handler = handlers.find(h => h.host === host);
if (handler) {
await proxyStore.deleteAuthHandler(handler.id);
// 移除对应的认证规则
await chrome.declarativeNetRequest.updateDynamicRules({
removeRuleIds: [parseInt(handler.id)]
});
}
}
static setupErrorHandler() {
// 在 Manifest V3 中,我们不能使用 chrome.proxy.onProxyError
// 所以我们只记录错误到 storage
try {
const logError = async (error) => {
const errors = await chrome.storage.local.get('proxyErrors') || [];
// 使用 storage 记录错误
return {
logError: async (error) => {
const errors = await proxyStore.getErrors() || [];
errors.push({
timestamp: Date.now(),
error: error.message || error
});
await chrome.storage.local.set({
proxyErrors: errors.slice(-100) // 只保留最近100条错误记录
});
};
return { logError };
} catch (error) {
console.error('Error setting up error handler:', error);
return null;
}
await proxyStore.saveErrors(errors.slice(-100)); // 只保留最近100条错误记录
}
};
}
// 设置代理认证信息
static async setProxyAuth(username, password) {
try {
await chrome.storage.local.set({
proxyAuth: {
username,
password,
timestamp: Date.now()
}
});
await proxyStore.saveAuth({ username, password, timestamp: Date.now() });
return true;
} catch (error) {
console.error('Error setting proxy auth:', error);
@@ -77,8 +96,7 @@ export class ProxyAuth {
// 获取代理认证信息
static async getProxyAuth() {
try {
const result = await chrome.storage.local.get('proxyAuth');
return result.proxyAuth || null;
return await proxyStore.getAuth();
} catch (error) {
console.error('Error getting proxy auth:', error);
return null;
@@ -88,7 +106,7 @@ export class ProxyAuth {
// 清除代理认证信息
static async clearProxyAuth() {
try {
await chrome.storage.local.remove('proxyAuth');
await proxyStore.clearAuth();
return true;
} catch (error) {
console.error('Error clearing proxy auth:', error);
+155 -71
View File
@@ -1,12 +1,17 @@
// 日志数据库管理
export class ProxyLogs {
static DB_NAME = 'yakit_proxy_logs';
static STORE_NAME = 'logs';
static VERSION = 1;
import { proxyStore } from '../db/proxy-store.js';
static async openDB() {
// 日志数据库管理
class ProxyLogs {
constructor() {
this.DB_NAME = 'proxy_extension';
this.STORE_NAME = 'proxy_logs';
this.DB_VERSION = 1;
this.MAX_LOGS = 1000; // 最多保存1000条日志
}
async initDB() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.DB_NAME, this.VERSION);
const request = indexedDB.open(this.DB_NAME, this.DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
@@ -17,82 +22,161 @@ export class ProxyLogs {
const store = db.createObjectStore(this.STORE_NAME, { keyPath: 'id' });
// 创建索引
store.createIndex('timestamp', 'timestamp');
store.createIndex('url', 'url');
store.createIndex('proxyId', 'proxyId');
store.createIndex('resourceType', 'resourceType');
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();
}
};
async getResourceType(details) {
try {
// 首先检查请求类型
if (details.type) {
// 直接使用 Chrome 提供的类型
switch (details.type) {
case 'main_frame': return 'page';
case 'xmlhttprequest': {
// 检查请求头来区分 XHR 和 Fetch
const isFetch = details.requestHeaders?.some(
header => header.name.toLowerCase() === 'sec-fetch-mode' &&
header.value === 'cors'
);
return isFetch ? 'fetch' : 'xhr';
}
};
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);
case 'script': return 'script';
case 'stylesheet': return 'stylesheet';
case 'image': return 'image';
case 'media': return 'media';
case 'font': return 'font';
case 'websocket': return 'websocket';
}
}
// 根据文件扩展名和内容类型判断
const contentType = details.requestHeaders?.find(
header => header.name.toLowerCase() === 'content-type'
)?.value || '';
const url = new URL(details.url);
const pathname = url.pathname.toLowerCase();
// 检查文件扩展名
if (pathname.endsWith('.js')) return 'script';
if (pathname.endsWith('.css')) return 'stylesheet';
if (/\.(png|jpg|jpeg|gif|webp|svg|ico)$/.test(pathname)) return 'image';
if (/\.(mp3|mp4|wav|ogg|webm)$/.test(pathname)) return 'media';
if (/\.(woff|woff2|ttf|eot|otf)$/.test(pathname)) return 'font';
// 根据内容类型判断
if (contentType) {
if (contentType.includes('javascript')) return 'script';
if (contentType.includes('css')) return 'stylesheet';
if (contentType.includes('image/')) return 'image';
if (contentType.includes('audio/') || contentType.includes('video/')) return 'media';
if (contentType.includes('font/') || contentType.includes('application/font')) return 'font';
if (contentType.includes('application/json')) return 'xhr';
if (contentType.includes('application/x-www-form-urlencoded')) return 'xhr';
}
// 检查 Accept 头
const acceptHeader = details.requestHeaders?.find(
header => header.name.toLowerCase() === 'accept'
)?.value || '';
if (acceptHeader) {
if (acceptHeader.includes('application/json')) return 'xhr';
if (acceptHeader.includes('text/javascript')) return 'script';
if (acceptHeader.includes('text/css')) return 'stylesheet';
if (acceptHeader.includes('image/')) return 'image';
}
console.log('Resource type detection:', {
url: details.url,
type: details.type,
contentType,
acceptHeader,
headers: details.requestHeaders
});
return 'other';
} catch (error) {
console.error('Error determining resource type:', error);
return 'other';
}
}
async logRequest(details, proxyConfig, error = null) {
try {
// 检查是否是扩展自身的请求
if (details.url.startsWith('chrome-extension://')) {
return;
}
// 获取资源类型
const resourceType = await this.getResourceType(details);
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,
method: details.method,
requestHeaders: details.requestHeaders?.reduce((acc, header) => {
acc[header.name] = header.value;
return acc;
}, {}),
requestBody: details.requestBody?.raw?.[0]?.bytes
? decodeURIComponent(String.fromCharCode.apply(null, new Uint8Array(details.requestBody.raw[0].bytes)))
: null,
responseHeaders: details.responseHeaders?.reduce((acc, header) => {
acc[header.name] = header.value;
return acc;
}, {}),
timing: {
startTime: details.timeStamp,
endTime: Date.now(),
duration: Date.now() - details.timeStamp
},
protocol: details.protocol || details.type,
ip: details.ip,
fromCache: details.fromCache,
resourceType
};
request.onerror = () => reject(request.error);
});
// 使用 proxyStore 存储日志
await proxyStore.addLog(log);
this.notifyLogUpdate();
} catch (error) {
console.error('Error logging proxy 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();
async getLogs() {
return await proxyStore.getLogs();
}
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
async clearLogs() {
await proxyStore.clearLogs();
this.notifyLogUpdate();
}
notifyLogUpdate() {
// 通知前端日志已更新
chrome.runtime.sendMessage({
action: 'PROXY_LOGS_UPDATED'
}).catch(() => {
// 忽略接收者不存在的错误
});
}
}
}
// 导出实例而不是直接使用顶层 await
export const proxyLogs = new ProxyLogs();
// 移除这些顶层 await 语句
// await proxyStore.addLog({...});
// const logs = await proxyStore.getLogs();
-87
View File
@@ -1,87 +0,0 @@
// 代理配置管理
export class ProxyManager {
static async setProxy(config) {
try {
const proxyConfig = {
mode: "fixed_servers",
rules: {
singleProxy: {
scheme: config.scheme,
host: config.host,
port: config.port
},
bypassList: ["localhost", "127.0.0.1"]
}
};
await chrome.proxy.settings.set({
value: proxyConfig,
scope: 'regular'
});
// 保存当前配置
await chrome.storage.local.set({
currentProxy: {
...config,
timestamp: Date.now()
}
});
return true;
} catch (error) {
console.error('Error setting proxy:', error);
return false;
}
}
static async clearProxy() {
try {
await chrome.proxy.settings.clear({scope: 'regular'});
await chrome.storage.local.remove('currentProxy');
return true;
} catch (error) {
console.error('Error clearing proxy:', error);
return false;
}
}
static async getProxyStatus() {
try {
const settings = await chrome.proxy.settings.get({});
const currentProxy = await chrome.storage.local.get('currentProxy');
return {
enabled: settings.value.mode === "fixed_servers",
config: currentProxy.currentProxy || null
};
} catch (error) {
console.error('Error getting proxy status:', error);
return {
enabled: false,
config: null
};
}
}
static async deleteProxy(proxyId) {
try {
// 获取当前代理配置
const result = await chrome.storage.local.get(['proxyConfigs', 'currentProxy']);
const configs = result.proxyConfigs || [];
const currentProxy = result.currentProxy;
// 如果要删除的代理正在使用中,先清除代理设置
if (currentProxy && currentProxy.id === proxyId) {
await clearProxyConfig();
}
// 从配置列表中删除代理
const updatedConfigs = configs.filter(config => config.id !== proxyId);
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
return true;
} catch (error) {
console.error('Error deleting proxy:', error);
return false;
}
}
}
+21 -19
View File
@@ -1,41 +1,43 @@
import { proxyStore } from '../db/proxy-store.js';
// 代理配置存储和管理
export const ProxySettings = {
async importSettings(settings) {
export class ProxySettings {
static async importSettings(settings) {
try {
if (Array.isArray(settings) && settings.every(s => s.proxyType)) {
await chrome.storage.local.set({proxyConfigs: settings});
await proxyStore.saveProxyConfigs(settings);
return {success: true};
}
return {success: false, error: "Invalid settings format"};
} catch (error) {
return {success: false, error: error.message};
}
},
}
async exportSettings() {
static async exportSettings() {
try {
const {proxyConfigs} = await chrome.storage.local.get('proxyConfigs');
return {success: true, settings: proxyConfigs || []};
const configs = await proxyStore.getProxyConfigs();
return {success: true, settings: configs || []};
} catch (error) {
return {success: false, error: error.message};
}
},
}
async setDefaultConfigs() {
const result = await chrome.storage.local.get('proxyConfigs');
if (!result.proxyConfigs) {
const defaultConfigs = [{
static async setDefaultConfigs() {
const configs = await proxyStore.getProxyConfigs();
if (!configs || configs.length === 0) {
// 设置默认的直接连接配置
await proxyStore.saveProxyConfigs([{
id: 'direct',
name: '直接连接',
proxyType: 'direct',
enabled: false
}];
await chrome.storage.local.set({ proxyConfigs: defaultConfigs });
}]);
}
// 确保 proxyLogs 存在
const logsResult = await chrome.storage.local.get('proxyLogs');
if (!logsResult.proxyLogs) {
await chrome.storage.local.set({ proxyLogs: [] });
// 确保日志存储已初始化
const logs = await proxyStore.getLogs(); // 使用 getLogs 而不是 getProxyLogs
if (!logs || logs.length === 0) {
await proxyStore.clearLogs(); // 初始化日志存储
}
}
};
}
+7 -1
View File
@@ -1,6 +1,12 @@
// 这个是插件 background 中使用的 action 类型
// 和前端的 action 要保持一致
export const ProxyActionType = {
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
GET_PROXY_STATUS: "GET_PROXY_STATUS",
CLEAR_PROXY_LOGS: "CLEAR_PROXY_LOGS"
GET_PROXY_LOGS: "GET_PROXY_LOGS",
CLEAR_PROXY_LOGS: "CLEAR_PROXY_LOGS",
GET_PROXY_CONFIGS: "GET_PROXY_CONFIGS",
ADD_PROXY_CONFIG: "ADD_PROXY_CONFIG",
UPDATE_PROXY_CONFIG: "UPDATE_PROXY_CONFIG"
};