diff --git a/public/background.js b/public/background.js
index c8e427a..a9a25cb 100644
--- a/public/background.js
+++ b/public/background.js
@@ -25,7 +25,6 @@ chrome.sidePanel.setOptions({
});
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
- console.log("msg", msg)
switch (msg.action) {
case ActionType.CONNECT:
console.info("Start to connect websocket")
diff --git a/public/manifest.json b/public/manifest.json
index f7a74bd..b70a88b 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -24,7 +24,7 @@
},
"content_scripts": [
{
- "matches": ["https://github.com/*"],
+ "matches": ["http://mitm/"],
"run_at": "document_start",
"js": ["proxy/content.js"]
}
diff --git a/public/proxy.js b/public/proxy.js
index 5ca555a..4a53572 100644
--- a/public/proxy.js
+++ b/public/proxy.js
@@ -13,26 +13,47 @@ function getProxySettings() {
async function handleSetProxyConfig(config, sendResponse) {
try {
- // 处理直接连接的情况
- if (config.proxyType === 'direct') {
+ // 处理代理服务器的情况
+ if (config.proxyType === 'fixed_servers') {
+ // 固定代理服务器模式需要验证 host 和 port
+ if (!config || !config.host || !config.port) {
+ sendResponse({
+ success: false,
+ error: '无效的代理配置:缺少主机或端口'
+ });
+ return;
+ }
+
+ const proxyConfig = {
+ mode: "fixed_servers",
+ rules: {
+ singleProxy: {
+ scheme: config.scheme || 'http',
+ host: config.host,
+ port: parseInt(config.port)
+ },
+ bypassList: config.bypassList || ["localhost", "127.0.0.1"]
+ }
+ };
+
await new Promise((resolve) => {
chrome.proxy.settings.set({
- value: {mode: "direct"},
+ value: proxyConfig,
scope: 'regular'
}, resolve);
});
const settings = await getProxySettings();
- const isSuccess = settings.value.mode === "direct";
+ 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 proxyStore.setCurrentProxy({
...config,
timestamp: Date.now()
});
- // 更新代理列表状态
const configs = await proxyStore.getProxyConfigs();
const updatedConfigs = configs.map(c => ({
...c,
@@ -40,136 +61,123 @@ async function handleSetProxyConfig(config, sendResponse) {
}));
await proxyStore.saveProxyConfigs(updatedConfigs);
- console.log('Direct connection set successfully');
+ console.log('Proxy successfully set:', settings.value);
sendResponse({success: true});
-
+
// 通知所有 content scripts 更新
await notifyProxyStatusChanged();
} else {
- console.error('Failed to set direct connection');
+ console.error('Proxy settings verification failed');
sendResponse({
success: false,
- error: '无法设置直接连接'
+ error: '代理设置验证失败'
});
}
return;
- }
-
- // 添加系统代理的处理
- if (config.proxyType === 'system') {
- try {
- // 先清除当前的代理设置
- await new Promise((resolve) => {
- chrome.proxy.settings.clear({
- scope: 'regular'
- }, resolve);
- });
-
- // 然后设置为系统代理
- await new Promise((resolve) => {
- chrome.proxy.settings.set({
- value: {mode: "system"},
- scope: 'regular'
- }, resolve);
- });
-
- const settings = await getProxySettings();
- const isSuccess = settings.value.mode === "system";
-
- if (isSuccess) {
- // 更新存储
- await proxyStore.setCurrentProxy({
- ...config,
- timestamp: Date.now()
- });
-
- // 更新代理列表状态
- const configs = await proxyStore.getProxyConfigs();
- const updatedConfigs = configs.map(c => ({
- ...c,
- enabled: c.id === config.id
- }));
- await proxyStore.saveProxyConfigs(updatedConfigs);
-
- console.log('System proxy set successfully');
- sendResponse({success: true});
-
- // 通知所有 content scripts 更新
- await notifyProxyStatusChanged();
- } else {
- console.error('Failed to set system proxy');
- sendResponse({
- success: false,
- error: '无法设置系统代理'
- });
- }
- } catch (error) {
- console.error('Error setting system proxy:', error);
+ } else if (config.proxyType === 'pac_script') {
+ // PAC 脚本模式需要验证 pacScript
+ if (!config || !config.pacScript || !config.pacScript.data) {
sendResponse({
success: false,
- error: error.message || '设置系统代理时发生错误'
+ error: '无效的 PAC 脚本配置'
+ });
+ return;
+ }
+
+ const proxyConfig = {
+ mode: "pac_script",
+ pacScript: config.pacScript
+ };
+
+ await new Promise((resolve) => {
+ chrome.proxy.settings.set({
+ value: proxyConfig,
+ scope: 'regular'
+ }, resolve);
+ });
+
+ const settings = await getProxySettings();
+ const isSuccess = settings.value.mode === "pac_script" &&
+ settings.value.pacScript &&
+ settings.value.pacScript.data;
+
+ if (isSuccess) {
+ await proxyStore.setCurrentProxy({
+ ...config,
+ timestamp: Date.now()
+ });
+
+ const configs = await proxyStore.getProxyConfigs();
+ const updatedConfigs = configs.map(c => ({
+ ...c,
+ enabled: c.id === config.id
+ }));
+ await proxyStore.saveProxyConfigs(updatedConfigs);
+
+ console.log('PAC script proxy successfully set:', settings.value);
+ sendResponse({success: true});
+
+ // 通知所有 content scripts 更新
+ await notifyProxyStatusChanged();
+ } else {
+ console.error('PAC script settings verification failed', {
+ expected: config,
+ actual: settings.value
+ });
+ sendResponse({
+ success: false,
+ error: '代理设置验证失败'
});
}
return;
- }
+ } else if (config.proxyType === 'direct' || config.proxyType === 'system') {
+ // 直接连接或系统代理模式
+ const proxyConfig = {
+ mode: config.proxyType
+ };
- // 处理代理服务器的情况
- if (!config || !config.host || !config.port) {
- sendResponse({
- success: false,
- error: '无效的代理配置'
+ await new Promise((resolve) => {
+ chrome.proxy.settings.set({
+ value: proxyConfig,
+ scope: 'regular'
+ }, resolve);
});
- return;
- }
- const proxyConfig = {
- mode: "fixed_servers",
- rules: {
- singleProxy: {
- scheme: config.scheme || 'http',
- host: config.host,
- port: parseInt(config.port)
- },
- bypassList: ["localhost", "127.0.0.1"]
+ const settings = await getProxySettings();
+ const isSuccess = settings.value.mode === config.proxyType;
+
+ if (isSuccess) {
+ await proxyStore.setCurrentProxy({
+ ...config,
+ timestamp: Date.now()
+ });
+
+ 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});
+
+ // 通知所有 content scripts 更新
+ await notifyProxyStatusChanged();
+ } else {
+ console.error('Proxy settings verification failed');
+ sendResponse({
+ success: false,
+ error: '代理设置验证失败'
+ });
}
- };
-
- await new Promise((resolve) => {
- chrome.proxy.settings.set({
- value: proxyConfig,
- scope: 'regular'
- }, resolve);
- });
-
- 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 proxyStore.setCurrentProxy({
- ...config,
- timestamp: Date.now()
- });
-
- 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});
-
- // 通知所有 content scripts 更新
- await notifyProxyStatusChanged();
+ return;
} else {
- console.error('Proxy settings verification failed');
sendResponse({
success: false,
- error: '代理设置验证失败'
+ error: '不支持的代理类型'
});
+ return;
}
} catch (error) {
console.error('Error setting proxy:', error);
@@ -192,9 +200,9 @@ async function handleClearProxyConfig(sendResponse) {
enabled: false
}));
await proxyStore.saveProxyConfigs(updatedConfigs);
-
+
sendResponse({ success: true });
-
+
// 通知所有 content scripts 更新
await notifyProxyStatusChanged();
} catch (error) {
@@ -276,51 +284,73 @@ async function queueProxyLog(details, error = null) {
}
}
-// 添加 URL 拦截处理
-function setupUrlInterceptor() {
- chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
- try {
- const url = new URL(details.url);
-
- if (url.hostname === 'mitm') {
- // 解析参数
- const host = url.searchParams.get('host');
- const port = parseInt(url.searchParams.get('port') || '0');
- const scheme = url.searchParams.get('scheme');
+// 添加检查和设置初始代理的函数
+async function checkAndSetInitialProxy() {
+ try {
+ // 确保默认配置存在
+ await ProxySettings.setDefaultConfigs();
- if (host && port && scheme) {
- // 创建新的代理配置
- const newConfig = {
- id: Date.now().toString(),
- name: "Yakit MITM",
- proxyType: 'fixed_servers',
- scheme: scheme,
- host: host,
- port: port,
- enabled: true
- };
+ // 获取当前的代理设置
+ const settings = await getProxySettings();
+ console.log('Current proxy settings:', settings);
- // 直接使用 proxyStore 实例的方法
- const result = await proxyStore.addAndEnableProxy(newConfig);
-
- if (result.success) {
- // 重定向回 mitm 页面
- chrome.tabs.update(details.tabId, {
- url: "http://mitm/"
- });
- } else {
- console.error('Failed to add and enable proxy:', result.error);
- }
- }
+ // 检查是否存在固定代理服务器设置
+ if (settings.value.mode === "fixed_servers" &&
+ settings.value.rules &&
+ settings.value.rules.singleProxy) {
+
+ const proxy = settings.value.rules.singleProxy;
+
+ // 获取现有配置
+ const existingConfigs = await proxyStore.getProxyConfigs();
+
+ // 检查是否已存在相同的 MITM 配置
+ const existingMitm = existingConfigs.find(config =>
+ config.host === proxy.host &&
+ config.port === proxy.port &&
+ config.scheme === proxy.scheme
+ );
+
+ if (!existingMitm) {
+ // 创建新的 MITM 配置
+ const newConfig = {
+ id: Date.now().toString(),
+ name: "Yakit MITM",
+ proxyType: 'fixed_servers',
+ scheme: proxy.scheme || 'http',
+ host: proxy.host,
+ port: proxy.port,
+ enabled: true,
+ bypassList: ["localhost", "127.0.0.1"],
+ matchList: []
+ };
+
+ // 添加到现有配置中
+ const updatedConfigs = [...existingConfigs, newConfig];
+ await proxyStore.saveProxyConfigs(updatedConfigs);
+
+ // 启用新配置
+ await handleSetProxyConfig(newConfig, () => {});
+
+ console.log('Added and enabled Yakit MITM config from existing proxy settings');
+ return;
}
- } catch (error) {
- console.error('Error handling proxy URL:', error);
}
- }, {
- url: [{
- hostEquals: 'mitm'
- }]
- });
+
+ // 如果没有检测到代理或已存在配置,则设置为系统代理
+ await handleSetProxyConfig({
+ id: 'system',
+ name: '[系统代理]',
+ proxyType: 'system',
+ enabled: true
+ }, () => {});
+
+ // 设置认证监听
+ await ProxyAuth.setupAuthListener();
+
+ } catch (error) {
+ console.error('Error during initialization:', error);
+ }
}
// 修改 setupProxyHandlers 函数
@@ -331,9 +361,6 @@ export function setupProxyHandlers() {
// 设置代理请求监听器
setupProxyRequestListener();
-
- // 设置 URL 拦截器
- setupUrlInterceptor();
// 消息监听器
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
@@ -458,44 +485,17 @@ export function setupProxyHandlers() {
});
sendResponse({ success: true });
return true;
- return handleClearProxyConfig(sendResponse);
}
});
// 在扩展启动时初始化
chrome.runtime.onInstalled.addListener(async () => {
- try {
- // 确保默认配置存在
- await ProxySettings.setDefaultConfigs();
-
- // 设置为系统代理
- await handleSetProxyConfig({
- id: 'system',
- name: '[系统代理]',
- proxyType: 'system',
- enabled: true
- }, () => {});
-
- // 设置认证监听
- await ProxyAuth.setupAuthListener();
- } catch (error) {
- console.error('Error during installation:', error);
- }
+ await checkAndSetInitialProxy();
});
- // 添加启动时的初始化
+ // 浏览器启动时初始化
chrome.runtime.onStartup.addListener(async () => {
- try {
- // 设置为系统代理
- await handleSetProxyConfig({
- id: 'system',
- name: '[系统代理]',
- proxyType: 'system',
- enabled: true
- }, () => {});
- } catch (error) {
- console.error('Error during startup:', error);
- }
+ await checkAndSetInitialProxy();
});
}
@@ -509,4 +509,43 @@ async function notifyProxyStatusChanged() {
// 忽略不支持的标签页
}
}
+}
+
+async function setProxyConfig(config) {
+ try {
+ let chromeProxyConfig;
+
+ if (config.proxyType === 'pac_script') {
+ chromeProxyConfig = {
+ mode: "pac_script",
+ pacScript: config.pacScript
+ };
+ } else if (config.proxyType === 'fixed_servers') {
+ chromeProxyConfig = {
+ mode: "fixed_servers",
+ rules: {
+ singleProxy: {
+ scheme: config.scheme,
+ host: config.host,
+ port: config.port
+ },
+ bypassList: config.bypassList || []
+ }
+ };
+ } else {
+ chromeProxyConfig = {
+ mode: config.proxyType // direct, system, auto_detect
+ };
+ }
+
+ await chrome.proxy.settings.set({
+ value: chromeProxyConfig,
+ scope: 'regular'
+ });
+
+ return { success: true };
+ } catch (error) {
+ console.error('Failed to set proxy config:', error);
+ return { success: false, error: error.message };
+ }
}
\ No newline at end of file
diff --git a/public/proxy/content.js b/public/proxy/content.js
index 9917a30..48aaf07 100644
--- a/public/proxy/content.js
+++ b/public/proxy/content.js
@@ -16,8 +16,7 @@ const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg');
async function sendMessageWithRetry(message, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
- const response = await chrome.runtime.sendMessage(message);
- return response;
+ return await chrome.runtime.sendMessage(message);
} catch (error) {
console.warn(`Attempt ${i + 1} failed:`, error);
if (i === maxRetries - 1) {
@@ -36,40 +35,23 @@ async function getCurrentProxy() {
action: ProxyActionType.GET_PROXY_STATUS
});
- if (!response) {
- console.log('No response from background script');
- return { enable: false, proxy: '' };
- }
-
- // 检查响应格式
- if (!response.success) {
- console.error('Error in proxy status response:', response.error);
- return { enable: false, proxy: '' };
+ if (!response || !response.success) {
+ console.log('No valid response from background script');
+ return { enable: false, proxy: '', currentMode: 'direct' };
}
const status = response.data;
console.log('Proxy status from background:', status);
- // 根据状态返回正确的格式
- if (status.mode === 'fixed_servers' && status.enabled && status.config) {
- const config = status.config;
- // 只在确实有配置时才返回代理信息
- if (config.scheme && config.host && config.port) {
- return {
- enable: true,
- proxy: `${config.scheme}://${config.host}:${config.port}`
- };
- }
- }
-
- // 对于直连或系统代理,返回相应状态
+ // 返回当前模式
return {
- enable: false,
- proxy: status.mode === 'system' ? 'system' : ''
+ enable: status.enabled,
+ proxy: status.mode === 'system' ? 'system' : '',
+ currentMode: status.mode || 'direct' // 使用 mode 来判断当前激活的代理
};
} catch (error) {
console.error('Error getting proxy status:', error);
- return { enable: false, proxy: '' };
+ return { enable: false, proxy: '', currentMode: 'direct' };
}
}
@@ -99,12 +81,36 @@ async function getProxyConfigs() {
async function switchProxy(config) {
try {
console.log('Switching proxy:', config);
+
+ // 根据配置类型构建正确的配置对象
+ let proxyConfig;
+ if (config.proxyType === 'system') {
+ proxyConfig = {
+ id: 'system',
+ name: '[系统代理]',
+ proxyType: 'system',
+ enabled: true
+ };
+ } else if (config.proxyType === 'direct') {
+ proxyConfig = {
+ id: 'direct',
+ name: '[直接连接]',
+ proxyType: 'direct',
+ enabled: false
+ };
+ } else {
+ proxyConfig = {
+ ...config,
+ enabled: true
+ };
+ }
+
await sendMessageWithRetry({
action: ProxyActionType.SET_PROXY_CONFIG,
- config: config
+ config: proxyConfig
});
- await updatePanel();
+ await PanelManager.updatePanel();
} catch (error) {
console.error('Error switching proxy:', error);
}
@@ -124,410 +130,55 @@ async function clearProxy() {
});
console.log('Clear proxy response:', response);
- await updatePanel();
+ // 使用 PanelManager 的 updatePanel 方法
+ await PanelManager.updatePanel();
} catch (error) {
console.error('Error clearing proxy:', error);
}
}
-// 更新面板显示
-async function updatePanel() {
- const panel = document.getElementById('yakit-proxy-panel')?.shadowRoot?.querySelector('.panel-content');
- if (!panel) return;
-
- const currentProxy = await getCurrentProxy();
- const configs = await getProxyConfigs();
+// 修改 PanelManager
+const PanelManager = {
+ panel: null,
+ messageListener: null,
+ _updating: false,
+ _updateQueue: Promise.resolve(),
+ _currentState: null, // 用于跟踪当前状态
+ _lastUpdate: null, // 添加最后更新时间戳
+ _lastState: null,
+ _pollingInterval: null,
- console.log('Current proxy status:', currentProxy);
-
- // 修改这里的判断逻辑
- let html = `
-
-
🟢
-
[直接连接]
-

-
-
-
⚙️
-
[系统代理]
-

-
-
- `;
-
- // 修改自定义代理配置的判断逻辑
- configs.forEach(config => {
- if (config.id !== 'direct' && config.id !== 'system') {
- const proxyUrl = `${config.scheme}://${config.host}:${config.port}`;
- const isActive = currentProxy.enable && currentProxy.proxy === proxyUrl && config.enabled;
-
- // 添加 title 属性显示详细信息
- const tooltipText = `${config.scheme.toUpperCase()} ${config.host}:${config.port}`;
-
- html += `
-
-
🌐
-
${config.name}
-

-
- `;
- }
- });
-
- // 修改添加操作按钮部分
- html += `
-
- ➕
- 添加代理...
-
-
- 👨💻
- 选项
-
- `;
-
- panel.innerHTML = html;
-
- // 添加事件监听
- panel.querySelectorAll('.proxy-item').forEach(item => {
- item.addEventListener('click', async () => {
- const id = item.dataset.id;
- if (id === 'direct') {
- await clearProxy();
- } else if (id === 'system') {
- await switchProxy({
- id: 'system',
- name: '[系统代理]',
- proxyType: 'system'
- });
- } else {
- const config = configs.find(c => c.id === id);
- if (config) {
- await switchProxy(config);
- }
- }
- });
- });
-
- // 修改添加代理和选项按钮的事件处理
- panel.querySelector('.add-proxy')?.addEventListener('click', async () => {
- try {
- // 通过发送消息给 background script 来处理添加代理
- await sendMessageWithRetry({
- action: 'OPEN_OPTIONS_PAGE',
- triggerAdd: true // 标记需要触发添加代理
- });
- } catch (error) {
- console.error('Error handling add proxy:', error);
- }
- });
-
- panel.querySelector('.settings')?.addEventListener('click', async () => {
- try {
- // 通过发送消息给 background script 来打开选项页
- await sendMessageWithRetry({
- action: 'OPEN_OPTIONS_PAGE'
- });
- } catch (error) {
- console.error('Error opening options page:', error);
- }
- });
-}
-
-// 创建并注入悬浮框
-function createFloatingPanel() {
- console.log("Creating floating panel...");
-
- // 检查是否已存在面板
- if (document.getElementById('yakit-proxy-panel')) {
- return;
- }
-
- // 创建容器
- const container = document.createElement('div');
- container.id = 'yakit-proxy-panel';
-
- // 创建 shadow DOM
- const shadow = container.attachShadow({ mode: 'open' });
-
- // 添加样式
- const style = document.createElement('style');
- style.textContent = `
- .floating-panel {
- position: fixed;
- top: 20px;
- right: 0;
- width: 180px;
- background: white;
- border-radius: 8px 0 0 8px;
- box-shadow: 0 2px 10px rgba(0,0,0,0.1);
- z-index: 2147483647;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
- transition: transform 0.3s ease;
+ init() {
+ // 确保 document.body 存在
+ if (!document.body) {
+ console.log('Body not ready, waiting...');
+ this.waitForBody();
+ return;
}
- .floating-panel.collapsed {
- transform: translateX(100%);
+ if (this.panel) {
+ console.log('Panel already exists, updating...');
+ this.updatePanel();
+ return;
+ }
+
+ console.log('Creating new panel...');
+ this.createPanel();
+ },
+
+ // 添加等待 body 的方法
+ waitForBody() {
+ if (document.body) {
+ this.init();
+ return;
}
- .panel-header {
- padding: 4px;
- border-bottom: 1px solid #eee;
- border-radius: 8px 0 0 0;
- background: #f8f9fa;
- display: flex;
- justify-content: space-between;
- align-items: center;
- }
-
- .collapse-trigger {
- position: absolute;
- left: -20px;
- top: 0;
- width: 20px;
- height: 100%;
- background: #f8f9fa;
- border-radius: 8px 0 0 8px;
- cursor: pointer;
- display: flex;
- align-items: center;
- justify-content: center;
- box-shadow: -2px 0 5px rgba(0,0,0,0.1);
- }
-
- .collapse-trigger:hover {
- background: #e9ecef;
- }
-
- .collapse-trigger::after {
- content: '◀';
- transition: transform 0.3s ease;
- }
-
- .floating-panel.collapsed .collapse-trigger::after {
- transform: rotate(180deg);
- }
-
- .panel-content {
- padding: 4px;
- }
-
- .proxy-item {
- position: relative;
- display: flex;
- align-items: center;
- padding: 4px 10px;
- cursor: pointer;
- transition: all 0.2s;
- color: #666;
- border-left: 3px solid transparent;
- overflow: hidden;
- }
-
- .proxy-item > span {
- position: relative;
- z-index: 1;
- }
-
- .watermark-icon {
- position: absolute;
- right: 0;
- width: 100%;
- height: 100%;
- opacity: 0.3;
- pointer-events: none;
- display: none;
- object-fit: contain;
- object-position: right center;
- padding: 4px;
- }
-
- .proxy-item.active .watermark-icon {
- display: block;
- }
-
- .proxy-item.active {
- color: #ff6b00 !important;
- background: #fff7e6;
- border-left: 3px solid #ff6b00;
- }
-
- .proxy-item.active span:first-child {
- color: #ff6b00 !important;
- }
-
- .proxy-item span:first-child {
- margin-right: 8px;
- font-size: 16px;
- color: #666;
- transition: color 0.2s;
- }
-
- .proxy-item:hover {
- background: #f5f5f5;
- }
-
- .proxy-item:hover span:first-child {
- color: #ff6b00;
- }
-
- .add-proxy {
- display: flex;
- align-items: center;
- padding: 4px 10px;
- color: #1890ff;
- cursor: pointer;
- border-top: 1px solid #eee;
- transition: all 0.2s;
- }
-
- .add-proxy:hover {
- background: #f5f5f5;
- }
-
- .settings {
- padding: 4px 10px;
- color: #666;
- cursor: pointer;
- border-top: 1px solid #eee;
- transition: all 0.2s;
- }
-
- .settings:hover {
- background: #f5f5f5;
- }
-
- .panel-header .header-content {
- display: flex;
- align-items: center;
- gap: 8px;
- padding: 0 4px;
- }
-
- .yak-icon {
- width: 24px;
- height: 24px;
- object-fit: contain;
- }
-
- .divider {
- height: 1px;
- background-color: #eee;
- margin: 4px 0;
- }
- `;
-
- // 创建面板内容
- const panel = document.createElement('div');
- panel.className = 'floating-panel';
- panel.innerHTML = `
-
-
-
-
- 🟢
- [直接连接]
-
-
- ⚙️
- [系统代理]
-
-
-
- ➕
- [添加代理...]
-
-
- 👨💻
- 选项
-
-
- `;
-
- // 将样式和面板添加到 shadow DOM
- shadow.appendChild(style);
- shadow.appendChild(panel);
-
- // 将容器添加到页面
- document.body.appendChild(container);
-
- // 添加事件监听器
- const addEventListeners = () => {
- // 收起/展开功能
- panel.querySelector('.collapse-trigger')?.addEventListener('click', (e) => {
- e.stopPropagation();
- panel.classList.toggle('collapsed');
- });
-
- // 添加代理按钮
- panel.querySelector('.add-proxy')?.addEventListener('click', () => {
- chrome.runtime.sendMessage({ action: 'OPEN_OPTIONS_PAGE' });
- });
-
- // 设置按钮
- panel.querySelector('.settings')?.addEventListener('click', () => {
- chrome.runtime.sendMessage({ action: 'OPEN_OPTIONS_PAGE' });
- });
- };
-
- // 初始化事件监听器
- addEventListeners();
-
- // 初始更新面板
- updatePanel();
-
- // 添加代理变化监听
- let messageListener = (message) => {
- if (message.action === 'PROXY_STATUS_CHANGED') {
- updatePanel();
- }
- };
-
- // 确保只添加一次监听器
- chrome.runtime.onMessage.removeListener(messageListener);
- chrome.runtime.onMessage.addListener(messageListener);
-
- // 验证面板是否成功创建
- console.log("Panel created:", {
- containerExists: !!document.getElementById('yakit-proxy-panel'),
- containerVisible: window.getComputedStyle(container).display !== 'none',
- shadowRoot: !!container.shadowRoot,
- panelElement: !!container.shadowRoot?.querySelector('.floating-panel')
- });
-}
-
-// 使用 MutationObserver 确保在 DOM 准备好时创建面板
-function initPanel() {
- if (document.body) {
- console.log("Body found, creating panel");
- // 确保background script已经准备好
- sendMessageWithRetry({ action: 'PING' })
- .then(() => {
- createFloatingPanel();
- })
- .catch(error => {
- console.error('Failed to initialize panel:', error);
- // 可以在这里添加重试逻辑
- setTimeout(initPanel, 1000);
- });
- } else {
- console.log("Body not found, waiting...");
+ console.log('Setting up MutationObserver for body');
const observer = new MutationObserver((mutations, obs) => {
if (document.body) {
- console.log("Body found via observer");
+ console.log('Body found via observer');
obs.disconnect();
- initPanel();
+ this.init();
}
});
@@ -535,18 +186,528 @@ function initPanel() {
childList: true,
subtree: true
});
- }
-}
+ },
+
+ createPanel() {
+ if (!document.body) {
+ console.log('Body not available during panel creation');
+ return;
+ }
-// 尝试多种方式来确保面板被创建
+ // 创建容器
+ const container = document.createElement('div');
+ container.id = 'yakit-proxy-panel';
+
+ // 创建 shadow DOM
+ const shadow = container.attachShadow({ mode: 'open' });
+
+ // 添加样式
+ const style = document.createElement('style');
+ style.textContent = `
+ .floating-panel {
+ position: fixed;
+ top: 20px;
+ right: 0;
+ width: 180px;
+ background: white;
+ border-radius: 8px 0 0 8px;
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
+ z-index: 2147483647;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+ transition: transform 0.3s ease;
+ }
+
+ .floating-panel.collapsed {
+ transform: translateX(100%);
+ }
+
+ .panel-header {
+ padding: 4px;
+ border-bottom: 1px solid #eee;
+ border-radius: 8px 0 0 0;
+ background: #f8f9fa;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ .collapse-trigger {
+ position: absolute;
+ left: -20px;
+ top: 0;
+ width: 20px;
+ height: 100%;
+ background: #f8f9fa;
+ border-radius: 8px 0 0 8px;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: -2px 0 5px rgba(0,0,0,0.1);
+ }
+
+ .collapse-trigger:hover {
+ background: #e9ecef;
+ }
+
+ .collapse-trigger::after {
+ content: '◀';
+ transition: transform 0.3s ease;
+ }
+
+ .floating-panel.collapsed .collapse-trigger::after {
+ transform: rotate(180deg);
+ }
+
+ .panel-content {
+ padding: 4px;
+ }
+
+ .proxy-item {
+ position: relative;
+ display: flex;
+ align-items: center;
+ padding: 4px 10px;
+ cursor: pointer;
+ transition: all 0.2s;
+ color: #666;
+ border-left: 3px solid transparent;
+ overflow: hidden;
+ }
+
+ .proxy-item > span {
+ position: relative;
+ z-index: 1;
+ }
+
+ .watermark-icon {
+ position: absolute;
+ right: 0;
+ width: 100%;
+ height: 100%;
+ opacity: 0.3;
+ pointer-events: none;
+ display: none;
+ object-fit: contain;
+ object-position: right center;
+ padding: 4px;
+ }
+
+ .proxy-item.active .watermark-icon {
+ display: block;
+ }
+
+ .proxy-item.active {
+ color: #ff6b00 !important;
+ background: #fff7e6;
+ border-left: 3px solid #ff6b00;
+ }
+
+ .proxy-item.active span:first-child {
+ color: #ff6b00 !important;
+ }
+
+ .proxy-item span:first-child {
+ margin-right: 8px;
+ font-size: 16px;
+ color: #666;
+ transition: color 0.2s;
+ }
+
+ .proxy-item:hover {
+ background: #f5f5f5;
+ }
+
+ .proxy-item:hover span:first-child {
+ color: #ff6b00;
+ }
+
+ .add-proxy {
+ display: flex;
+ align-items: center;
+ padding: 4px 10px;
+ color: #1890ff;
+ cursor: pointer;
+ border-top: 1px solid #eee;
+ transition: all 0.2s;
+ }
+
+ .add-proxy:hover {
+ background: #f5f5f5;
+ }
+
+ .settings {
+ padding: 4px 10px;
+ color: #666;
+ cursor: pointer;
+ border-top: 1px solid #eee;
+ transition: all 0.2s;
+ }
+
+ .settings:hover {
+ background: #f5f5f5;
+ }
+
+ .panel-header .header-content {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 0 4px;
+ }
+
+ .yak-icon {
+ width: 24px;
+ height: 24px;
+ object-fit: contain;
+ }
+
+ .divider {
+ height: 1px;
+ background-color: #eee;
+ margin: 4px 0;
+ }
+ `;
+
+ // 创建面板内容
+ const panel = document.createElement('div');
+ panel.className = 'floating-panel';
+ panel.innerHTML = `
+
+
+
+
+ 🟢
+ [直接连接]
+
+
+ ⚙️
+ [系统代理]
+
+
+
+ ➕
+ [添加代理...]
+
+
+ 👨💻
+ 选项
+
+
+ `;
+
+ // 将样式和面板添加到 shadow DOM
+ shadow.appendChild(style);
+ shadow.appendChild(panel);
+
+ // 确保安全地添加到 body
+ try {
+ document.body.appendChild(container);
+ this.panel = container;
+
+ // 添加折叠触发器的点击事件
+ const floatingPanel = shadow.querySelector('.floating-panel');
+ const collapseTrigger = shadow.querySelector('.collapse-trigger');
+
+ collapseTrigger.addEventListener('click', () => {
+ floatingPanel.classList.toggle('collapsed');
+ });
+
+ // 设置消息监听和开始轮询
+ this.setupMessageListener();
+
+ // 初始更新面板
+ this.updatePanel();
+
+ // 添加页面卸载时的清理
+ window.addEventListener('unload', () => {
+ if (this._pollingInterval) {
+ clearInterval(this._pollingInterval);
+ }
+ if (this.messageListener) {
+ chrome.runtime.onMessage.removeListener(this.messageListener);
+ }
+ });
+ } catch (error) {
+ console.error('Error creating panel:', error);
+ }
+ },
+
+ setupMessageListener() {
+ if (this.messageListener) {
+ chrome.runtime.onMessage.removeListener(this.messageListener);
+ }
+
+ this.messageListener = async (message, sender) => {
+ // 只检查消息是否来自同一个扩展
+ if (sender.id !== chrome.runtime.id) {
+ return;
+ }
+
+ // 处理状态更新消息
+ if (message.action === 'PROXY_STATUS_CHANGED' ||
+ message.action === 'PROXY_CONFIGS_UPDATED') {
+ console.log('Received update message:', message, 'from:', sender);
+ await this.updatePanel();
+ }
+ };
+
+ chrome.runtime.onMessage.addListener(this.messageListener);
+ },
+
+ async updatePanel() {
+ if (!this.panel || !document.body.contains(this.panel)) {
+ console.log('Panel not in document, recreating...');
+ this.createPanel();
+ return;
+ }
+
+ const panel = this.panel.shadowRoot?.querySelector('.panel-content');
+ if (!panel) return;
+
+ // 使用更新队列确保更新按顺序执行
+ this._updateQueue = this._updateQueue.then(async () => {
+ if (this._updating) {
+ console.log('Update already in progress, skipping...');
+ return;
+ }
+
+ try {
+ this._updating = true;
+
+ // 获取最新状态
+ const [currentProxy, configs] = await Promise.all([
+ getCurrentProxy(),
+ getProxyConfigs()
+ ]);
+
+ // 状态没有变化时不更新
+ const newState = JSON.stringify({ currentProxy, configs });
+ if (this._currentState === newState) {
+ console.log('State unchanged, skipping update');
+ return;
+ }
+ this._currentState = newState;
+
+ // 再次检查面板状态
+ if (!this.panel || !document.body.contains(this.panel)) {
+ console.log('Panel was removed during data fetch');
+ return;
+ }
+
+ console.log('Updating panel with:', { currentProxy, configs });
+
+ if (!Array.isArray(configs)) {
+ console.error('Invalid configs:', configs);
+ return;
+ }
+
+ // 保存当前激活的项
+ const currentActiveId = panel.querySelector('.proxy-item.active')?.dataset.id;
+
+ // 构建新的 HTML
+ const newHtml = this._buildPanelHtml(currentProxy, configs);
+
+ // 创建一个临时容器来比较内容
+ const temp = document.createElement('div');
+ temp.innerHTML = newHtml;
+
+ // 只在内容真正改变时更新
+ if (panel.innerHTML !== temp.innerHTML) {
+ requestAnimationFrame(() => {
+ panel.innerHTML = newHtml;
+ this._bindEventListeners(panel, configs, currentProxy);
+
+ // 验证更新后的状态
+ const newActiveId = panel.querySelector('.proxy-item.active')?.dataset.id;
+ if (currentActiveId !== newActiveId) {
+ console.log('Active state changed:', {
+ from: currentActiveId,
+ to: newActiveId
+ });
+ }
+ });
+ }
+ } catch (error) {
+ console.error('Error updating panel:', error);
+ } finally {
+ this._updating = false;
+ }
+ }).catch(error => {
+ console.error('Error in update queue:', error);
+ this._updating = false;
+ });
+
+ return this._updateQueue;
+ },
+
+ // 将 HTML 构建逻辑抽离成单独的方法
+ _buildPanelHtml(currentProxy, configs) {
+ let html = `
+
+
🟢
+
[直接连接]
+

+
+
+
⚙️
+
[系统代理]
+

+
+
+ `;
+
+ // 添加自定义代理配置
+ configs.forEach(config => {
+ if (config.proxyType !== 'direct' && config.proxyType !== 'system') {
+ // 判断是否激活:当前模式为 fixed_servers 且配置已启用
+ const isActive = currentProxy.currentMode === 'fixed_servers' &&
+ config.enabled;
+
+ const tooltipText = config.proxyType === 'pac_script'
+ ? 'PAC Script'
+ : `${(config.proxyType || 'HTTP').toUpperCase()} ${config.host || ''}:${config.port || ''}`;
+
+ const proxyIcon = config.proxyType === 'pac_script' ? '📜' : '🌐';
+
+ html += `
+
+
${proxyIcon}
+
${config.name || '未命名代理'}
+

+
+ `;
+ }
+ });
+
+ // 添加操作按钮
+ html += `
+
+ ➕
+ 添加代理...
+
+
+ 👨💻
+ 选项
+
+ `;
+
+ return html;
+ },
+
+ _bindEventListeners(panel, configs, currentProxy) {
+ // 代理项点击事件
+ panel.querySelectorAll('.proxy-item').forEach(item => {
+ const id = item.dataset.id;
+
+ // 使用事件委托来提高性能
+ const clickHandler = async (e) => {
+ e.stopPropagation();
+
+ if (this._updating) {
+ console.log('Panel is updating, ignoring click');
+ return;
+ }
+
+ // 添加点击反馈
+ const originalOpacity = item.style.opacity;
+ item.style.opacity = '0.7';
+
+ try {
+ // 立即更新 UI 状态,不等待响应
+ panel.querySelectorAll('.proxy-item').forEach(i => {
+ i.classList.remove('active');
+ i.querySelector('span').style.color = '#666';
+ });
+ item.classList.add('active');
+ item.querySelector('span').style.color = '#ff6b00';
+
+ if (id === 'direct') {
+ await clearProxy();
+ } else if (id === 'system') {
+ await switchProxy({
+ id: 'system',
+ name: '[系统代理]',
+ proxyType: 'system'
+ });
+ } else {
+ const config = configs.find(c => c.id === id);
+ if (config) {
+ await switchProxy(config);
+ }
+ }
+ } catch (error) {
+ console.error('Error handling proxy item click:', error);
+ // 发生错误时恢复原状
+ await this.updatePanel();
+ } finally {
+ item.style.opacity = originalOpacity;
+ }
+ };
+
+ // 使用 { once: true } 确保事件监听器不会重复
+ item.addEventListener('click', clickHandler, { once: true });
+ });
+
+ // 添加代理按钮
+ panel.querySelector('.add-proxy')?.addEventListener('click', async () => {
+ try {
+ await sendMessageWithRetry({
+ action: 'OPEN_OPTIONS_PAGE',
+ triggerAdd: true
+ });
+ } catch (error) {
+ console.error('Error handling add proxy:', error);
+ }
+ });
+
+ // 设置按钮
+ panel.querySelector('.settings')?.addEventListener('click', async () => {
+ try {
+ await sendMessageWithRetry({
+ action: 'OPEN_OPTIONS_PAGE'
+ });
+ } catch (error) {
+ console.error('Error opening options page:', error);
+ }
+ });
+ }
+};
+
+// 修改初始化调用
console.log("Setting up initialization...");
+// 根据文档状态决定初始化方式
+if (document.readyState === 'loading') {
+ console.log('Document still loading, waiting for DOMContentLoaded');
+ document.addEventListener('DOMContentLoaded', () => {
+ console.log('DOMContentLoaded fired');
+ PanelManager.init();
+ });
+} else {
+ console.log('Document already loaded, initializing immediately');
+ PanelManager.init();
+}
+
+// 保留 load 事件作为备份
window.addEventListener('load', () => {
console.log("Window load triggered");
- initPanel();
+ if (!PanelManager.panel) {
+ PanelManager.init();
+ }
});
-// 在关键位置添加更多日志
+// 添加更详细的日志
console.log("Document readyState:", document.readyState);
console.log("Document body exists:", !!document.body);
console.log("Document documentElement exists:", !!document.documentElement);
diff --git a/public/proxy/options.js b/public/proxy/options.js
deleted file mode 100644
index 8d83f40..0000000
--- a/public/proxy/options.js
+++ /dev/null
@@ -1,167 +0,0 @@
-console.log('Options page script loaded');
-
-import { ProxySettings } from './proxy-settings.js';
-
-let currentConfigs = [];
-
-document.addEventListener('DOMContentLoaded', async () => {
- try {
- await initOptionsPage();
- setupEventListeners();
- } catch (error) {
- console.error('Failed to initialize options page:', error);
- showError(error.message);
- }
-});
-
-async function initOptionsPage() {
- const result = await ProxySettings.exportSettings();
- currentConfigs = result.settings || [];
- renderProxyConfigs();
-}
-
-function renderProxyConfigs() {
- const proxyList = document.getElementById('proxyList');
- const template = document.getElementById('proxyItemTemplate');
-
- // 清除除了直接连接以外的所有配置
- const items = proxyList.querySelectorAll('.proxy-item:not([data-id="direct"])');
- items.forEach(item => item.remove());
-
- // 渲染其他代理配置
- currentConfigs.forEach(config => {
- if (config.id === 'direct') return; // 跳过直接连接
-
- const clone = template.content.cloneNode(true);
- const proxyItem = clone.querySelector('.proxy-item');
-
- proxyItem.dataset.id = config.id;
- proxyItem.querySelector('.proxy-name').value = config.name;
- proxyItem.querySelector('.proxy-type-select').value = config.proxyType;
-
- updateProxyTypeSettings(proxyItem, config);
- proxyList.appendChild(proxyItem);
- });
-}
-
-function updateProxyTypeSettings(proxyItem, config) {
- const serverSettings = proxyItem.querySelector('.proxy-server-settings');
- const pacSettings = proxyItem.querySelector('.pac-script-settings');
-
- if (config.proxyType === 'fixed_servers') {
- serverSettings.style.display = 'block';
- pacSettings.style.display = 'none';
-
- proxyItem.querySelector('.proxy-scheme').value = config.scheme || 'http';
- proxyItem.querySelector('.proxy-host').value = config.host || '';
- proxyItem.querySelector('.proxy-port').value = config.port || '';
- }
- else if (config.proxyType === 'pac_script') {
- serverSettings.style.display = 'none';
- pacSettings.style.display = 'block';
-
- proxyItem.querySelector('.pac-script').value = config.pacScript || '';
- }
- else {
- serverSettings.style.display = 'none';
- pacSettings.style.display = 'none';
- }
-}
-
-function setupEventListeners() {
- // 添加代理按钮
- document.getElementById('addProxy').addEventListener('click', addNewProxy);
-
- // 代理列表变更事件
- document.getElementById('proxyList').addEventListener('change', handleProxyChange);
-
- // 删除代理按钮
- document.getElementById('proxyList').addEventListener('click', handleProxyDelete);
-}
-
-async function addNewProxy() {
- const newConfig = {
- id: Date.now().toString(),
- name: '新建代理',
- proxyType: 'fixed_servers',
- scheme: 'http',
- host: '127.0.0.1',
- port: 8080
- };
-
- currentConfigs = [...currentConfigs, newConfig];
- await ProxySettings.importSettings(currentConfigs);
- renderProxyConfigs();
-}
-
-async function handleProxyChange(e) {
- const proxyItem = e.target.closest('.proxy-item');
- if (!proxyItem) return;
-
- const configId = proxyItem.dataset.id;
- const configIndex = currentConfigs.findIndex(c => c.id === configId);
- if (configIndex === -1) return;
-
- const updatedConfig = { ...currentConfigs[configIndex] };
-
- if (e.target.classList.contains('proxy-name')) {
- updatedConfig.name = e.target.value;
- } else if (e.target.classList.contains('proxy-type-select')) {
- updatedConfig.proxyType = e.target.value;
- // 如果切换到固定服务器模式,设置默认值
- if (updatedConfig.proxyType === 'fixed_servers' && !updatedConfig.scheme) {
- updatedConfig.scheme = 'http';
- updatedConfig.host = '127.0.0.1';
- updatedConfig.port = 8080;
- }
- } else if (e.target.classList.contains('proxy-scheme')) {
- updatedConfig.scheme = e.target.value;
- } else if (e.target.classList.contains('proxy-host')) {
- updatedConfig.host = e.target.value;
- } else if (e.target.classList.contains('proxy-port')) {
- updatedConfig.port = parseInt(e.target.value) || '';
- } else if (e.target.classList.contains('pac-script')) {
- updatedConfig.pacScript = e.target.value;
- }
-
- currentConfigs[configIndex] = updatedConfig;
- await ProxySettings.importSettings(currentConfigs);
-
- // 如果代理类型改变,需要更新UI
- if (e.target.classList.contains('proxy-type-select')) {
- updateProxyTypeSettings(proxyItem, updatedConfig);
- }
-}
-
-async function handleProxyDelete(e) {
- const deleteBtn = e.target.closest('.delete-btn');
- if (!deleteBtn) return;
-
- const proxyItem = deleteBtn.closest('.proxy-item');
- const configId = proxyItem.dataset.id;
-
- currentConfigs = currentConfigs.filter(c => c.id !== configId);
- await ProxySettings.importSettings(currentConfigs);
- renderProxyConfigs();
-}
-
-function showError(message) {
- const container = document.querySelector('.container');
- const errorDiv = document.createElement('div');
- errorDiv.className = 'error-message';
- errorDiv.style.cssText = `
- color: #ff4d4f;
- padding: 8px 16px;
- margin-bottom: 16px;
- background-color: #fff2f0;
- border: 1px solid #ffccc7;
- border-radius: 4px;
- `;
- errorDiv.textContent = message;
- container.insertBefore(errorDiv, container.firstChild);
-
- // 5秒后自动移除错误信息
- setTimeout(() => {
- errorDiv.remove();
- }, 3000);
-}
\ No newline at end of file
diff --git a/public/types/action.js b/public/types/action.js
index 0fb8da0..cbca12a 100644
--- a/public/types/action.js
+++ b/public/types/action.js
@@ -9,4 +9,5 @@ export const ProxyActionType = {
GET_PROXY_CONFIGS: "GET_PROXY_CONFIGS",
ADD_PROXY_CONFIG: "ADD_PROXY_CONFIG",
UPDATE_PROXY_CONFIG: "UPDATE_PROXY_CONFIG",
+ OPEN_OPTIONS_PAGE: "OPEN_OPTIONS_PAGE",
};
\ No newline at end of file
diff --git a/src/components/ProxySwitch/index.tsx b/src/components/ProxySwitch/index.tsx
index de927bb..c216ad2 100644
--- a/src/components/ProxySwitch/index.tsx
+++ b/src/components/ProxySwitch/index.tsx
@@ -57,12 +57,25 @@ export const ProxySwitch: React.FC = () => {
const [customProxies, setCustomProxies] = useState([]);
const [isLoading, setIsLoading] = useState(false);
+ // 修改存储变化监听
+ useEffect(() => {
+ const handleMessage = (message: any) => {
+ if (message.action === 'PROXY_CONFIGS_UPDATED' && message.source !== 'proxy_switch') {
+ loadCustomProxies();
+ }
+ };
+
+ chrome.runtime.onMessage.addListener(handleMessage);
+ return () => {
+ chrome.runtime.onMessage.removeListener(handleMessage);
+ };
+ }, []);
+
useEffect(() => {
const init = async () => {
- await Promise.all([
- loadProxyStatus(),
- loadCustomProxies()
- ]);
+ // 修改初始化逻辑,避免并行请求
+ await loadProxyStatus();
+ await loadCustomProxies();
setInitialized(true);
};
init();
@@ -93,39 +106,6 @@ export const ProxySwitch: React.FC = () => {
const loadCustomProxies = async () => {
try {
- // 首先检查当前是否在 options 页面的上下文中
- const currentUrl = window.location.href;
- const isInOptionsContext = currentUrl.includes('chrome-extension://') && currentUrl.includes('options.html');
-
- if (isInOptionsContext) {
- // 如果在 options 页面上下文中,直接使用消息通信
- const response = await chrome.runtime.sendMessage({
- action: ProxyActionType.GET_PROXY_CONFIGS
- });
-
- if (response?.success && response.data) {
- const proxies = response.data
- .filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id))
- .map((proxy: ProxyConfig): CustomProxy => ({
- key: proxy.id,
- name: proxy.name,
- color: '#1890ff',
- config: proxy,
- enabled: proxy.enabled
- }));
- setCustomProxies(proxies);
-
- const enabledProxy = response.data.find((proxy: ProxyConfig) => proxy.enabled);
- if (enabledProxy) {
- setCurrentMode(enabledProxy.id);
- } else {
- setCurrentMode('direct');
- }
- return;
- }
- }
-
- // 如果不在 options 页面上下文中,使用 IndexedDB
const DB_NAME = 'yaklang_extension';
const STORE_NAME = 'proxy_configs';
@@ -165,53 +145,49 @@ export const ProxySwitch: React.FC = () => {
const enabledProxy = configs.find((proxy: ProxyConfig) => proxy.enabled);
if (enabledProxy) {
setCurrentMode(enabledProxy.id);
- } else {
- setCurrentMode('direct');
}
-
} catch (error) {
console.error('Error loading custom proxies:', error);
setCustomProxies([]);
- setCurrentMode('direct');
}
};
const handleModeChange = async (mode: string) => {
- if (mode === 'setting') {
- await chrome.runtime.openOptionsPage?.();
- return;
- }
-
- if (mode === 'add') {
- try {
- const [activeTab] = await chrome.tabs.query({
- active: true,
- currentWindow: true
- });
- const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
-
- if (activeTab?.url === optionsUrl) {
- chrome.tabs.sendMessage(activeTab.id!, {
- action: 'TRIGGER_ADD_PROXY'
+ if (mode === 'setting' || mode === 'add') {
+ if (mode === 'setting') {
+ await chrome.runtime.openOptionsPage?.();
+ }
+ if (mode === 'add') {
+ try {
+ const [activeTab] = await chrome.tabs.query({
+ active: true,
+ currentWindow: true
});
- } else {
- const tab = await chrome.tabs.create({
- url: optionsUrl
- });
-
- const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
- if (tabId === tab.id && changeInfo.status === 'complete') {
- chrome.tabs.onUpdated.removeListener(listener);
- chrome.tabs.sendMessage(tab.id!, {
- action: 'TRIGGER_ADD_PROXY'
- });
- }
- };
+ const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
- chrome.tabs.onUpdated.addListener(listener);
+ if (activeTab?.url === optionsUrl) {
+ chrome.tabs.sendMessage(activeTab.id!, {
+ action: 'TRIGGER_ADD_PROXY'
+ });
+ } else {
+ const tab = await chrome.tabs.create({
+ url: optionsUrl
+ });
+
+ const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
+ if (tabId === tab.id && changeInfo.status === 'complete') {
+ chrome.tabs.onUpdated.removeListener(listener);
+ chrome.tabs.sendMessage(tab.id!, {
+ action: 'TRIGGER_ADD_PROXY'
+ });
+ }
+ };
+
+ chrome.tabs.onUpdated.addListener(listener);
+ }
+ } catch (error) {
+ console.error('Failed to get current tab:', error);
}
- } catch (error) {
- console.error('Failed to get current tab:', error);
}
return;
}
@@ -227,8 +203,8 @@ export const ProxySwitch: React.FC = () => {
return;
}
+ // 立即更新UI状态
setCurrentMode(mode);
-
if (customProxy) {
setCustomProxies(prev => prev.map(p => ({
...p,
@@ -238,16 +214,20 @@ export const ProxySwitch: React.FC = () => {
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.SET_PROXY_CONFIG,
- config
+ config,
+ // 添加一个标志,表示这是从 ProxySwitch 发起的更改
+ source: 'proxy_switch'
});
if (response?.success === false) {
throw new Error(response.error || '设置代理失败');
}
- await loadCustomProxies();
+ // 不需要重新加载,因为我们已经更新了本地状态
} catch (error) {
console.error('Error applying proxy config:', error);
+ // 发生错误时才重新加载以确保状态正确
+ await loadCustomProxies();
throw error;
} finally {
setIsLoading(false);
@@ -265,13 +245,17 @@ export const ProxySwitch: React.FC = () => {
{ type: 'divider' },
...customProxies.map(proxy => ({
key: proxy.key,
- icon: ,
+ icon:
+ {proxy.config.proxyType === 'pac_script' ? '📜' : }
+ ,
label: {proxy.name},
className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
- title: `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
+ title: proxy.config.scheme
+ ? `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
+ : `${proxy.config.host}:${proxy.config.port}`
})),
{
key: 'add',
diff --git a/src/pages/OptionsPage/components/ProxySettings/index.tsx b/src/pages/OptionsPage/components/ProxySettings/index.tsx
index 5c9f75c..f1aa480 100644
--- a/src/pages/OptionsPage/components/ProxySettings/index.tsx
+++ b/src/pages/OptionsPage/components/ProxySettings/index.tsx
@@ -4,6 +4,7 @@ import type { ColumnsType } from 'antd/es/table';
import { DeleteOutlined, PlusOutlined, EditOutlined, CheckOutlined } from '@ant-design/icons';
import { ProxyConfig } from '@/types/proxy';
import './index.css';
+import punycode from 'punycode';
interface ProxySettingsProps {
proxyConfigs: ProxyConfig[];
@@ -14,7 +15,7 @@ interface ProxySettingsProps {
onClear: (configId: string) => Promise;
}
-// 添加编辑表单的接口
+// 修改 EditFormData 接口
interface EditFormData {
name: string;
proxyType: "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect";
@@ -22,6 +23,9 @@ interface EditFormData {
host?: string;
port?: number;
pacScript?: string;
+ bypassList?: string;
+ matchList?: string; // 仅用于 UI 编辑
+ proxyServer?: string; // 添加 proxyServer 字段,用于 PAC 脚本模式选择代理服务器
}
// 或者更好的方式是创建一个专门的类型
@@ -48,7 +52,11 @@ export const ProxySettings: React.FC = ({
scheme: editingConfig.scheme,
host: editingConfig.host,
port: editingConfig.port,
- pacScript: editingConfig.pacScript
+ bypassList: editingConfig.bypassList?.join('\n') || '',
+ matchList: editingConfig.matchList?.join('\n') || '',
+ proxyServer: editingConfig.host && editingConfig.port
+ ? `${editingConfig.host}:${editingConfig.port}`
+ : undefined
});
}
}, [editModalVisible, editingConfig, form]);
@@ -73,6 +81,17 @@ export const ProxySettings: React.FC = ({
setEditModalVisible(true);
};
+ // 添加一个函数来获取可用的代理服务器列表
+ const getAvailableProxies = (configs: ProxyConfig[]) => {
+ return configs
+ .filter(config => config.proxyType === 'fixed_servers')
+ .map(config => ({
+ label: `${config.name} (${config.scheme}://${config.host}:${config.port})`,
+ value: `${config.host}:${config.port}`,
+ config
+ }));
+ };
+
// 处理编辑保存
const handleEditSave = async () => {
try {
@@ -81,13 +100,108 @@ export const ProxySettings: React.FC = ({
if (editingConfig.enabled) {
await onClear(editingConfig.id);
}
-
- const updatedConfig: ProxyConfig = {
- ...editingConfig,
- ...values,
- proxyType: values.proxyType as "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect",
- scheme: values.scheme as "http" | "https" | "socks4" | "socks5"
- };
+
+ let updatedConfig: ProxyConfig;
+
+ if (values.proxyType === 'fixed_servers') {
+ // 处理固定代理服务器模式
+ const bypassList = values.bypassList
+ ? values.bypassList.split('\n').map(line => line.trim()).filter(line => line.length > 0)
+ : ["localhost", "127.0.0.1"];
+
+ updatedConfig = {
+ id: editingConfig.id,
+ name: values.name,
+ enabled: editingConfig.enabled,
+ proxyType: 'fixed_servers',
+ scheme: values.scheme,
+ host: values.host,
+ port: values.port,
+ bypassList,
+ };
+ } else if (values.proxyType === 'pac_script') {
+ const domains = values.matchList
+ ? values.matchList.split('\n')
+ .map(line => line.trim())
+ .filter(line => line.length > 0)
+ .map(domain => {
+ try {
+ // 如果域名包含非 ASCII 字符,转换为 Punycode
+ if (/[^\x00-\x7F]/.test(domain)) {
+ if (domain.startsWith('*.')) {
+ const suffix = domain.substring(2);
+ return '*.' + suffix.split('.').map(part => {
+ return /[^\x00-\x7F]/.test(part) ? 'xn--' + punycode.encode(part) : part;
+ }).join('.');
+ } else {
+ return domain.split('.').map(part => {
+ return /[^\x00-\x7F]/.test(part) ? 'xn--' + punycode.encode(part) : part;
+ }).join('.');
+ }
+ }
+ return domain;
+ } catch (error) {
+ console.error('Error encoding domain:', domain, error);
+ return domain;
+ }
+ })
+ : [];
+
+ // 从选择的代理服务器中获取配置
+ const [host, port] = values.proxyServer.split(':');
+
+ // 生成 PAC 脚本
+ const pacScriptContent = `
+function FindProxyForURL(url, host) {
+ // Convert host to lowercase for case-insensitive matching
+ host = host.toLowerCase();
+
+ // Define domain patterns
+ var domains = ${JSON.stringify(domains)};
+
+ // Check each domain pattern
+ for (var i = 0; i < domains.length; i++) {
+ var pattern = domains[i].toLowerCase();
+
+ if (pattern.startsWith('*.')) {
+ var suffix = pattern.substring(2);
+ if (host === suffix || host.endsWith('.' + suffix)) {
+ return 'PROXY ${host}:${port}';
+ }
+ } else if (host === pattern) {
+ return 'PROXY ${host}:${port}';
+ }
+ }
+
+ return 'DIRECT';
+}`;
+
+ updatedConfig = {
+ id: editingConfig.id,
+ name: values.name,
+ enabled: editingConfig.enabled,
+ proxyType: 'pac_script',
+ mode: 'pac_script',
+ // 保存代理服务器信息
+ host,
+ port: parseInt(port),
+ // 保存匹配域名列表
+ matchList: domains,
+ pacScript: {
+ data: pacScriptContent,
+ mandatory: true
+ }
+ };
+ } else {
+ // 处理其他模式
+ updatedConfig = {
+ id: editingConfig.id,
+ name: values.name,
+ enabled: editingConfig.enabled,
+ proxyType: values.proxyType,
+ bypassList: [], // 其他模式下设置为空数组
+ };
+ }
if (!proxyConfigs.find(config => config.id === editingConfig.id)) {
await onAdd(updatedConfig);
@@ -232,7 +346,7 @@ export const ProxySettings: React.FC = ({
>