diff --git a/public/background.js b/public/background.js index 7297ea0..c8e427a 100644 --- a/public/background.js +++ b/public/background.js @@ -1,6 +1,5 @@ import {ActionType, injectScriptAndSendMessage, WebSocketManager} from './socket.js'; import { setupProxyHandlers } from './proxy.js'; -import { ProxyActionType } from './types/action.js'; console.info("Chrome Extension Background is loaded"); @@ -25,7 +24,7 @@ chrome.sidePanel.setOptions({ console.error('Error setting side panel options:', error); }); -chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) { +chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => { console.log("msg", msg) switch (msg.action) { case ActionType.CONNECT: diff --git a/public/manifest.json b/public/manifest.json index c4f7b03..6dc35b9 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -22,6 +22,13 @@ "service_worker": "background.js", "type": "module" }, + "content_scripts": [ + { + "matches": ["https://github.com/*"], + "run_at": "document_start", + "js": ["proxy/content.js"] + } + ], "permissions": [ "proxy", "storage", @@ -42,7 +49,8 @@ "types/*.js", "proxy/*.js", "socket.js", - "proxy.js" + "proxy.js", + "proxy/content.js" ], "matches": [""] } diff --git a/public/proxy.js b/public/proxy.js index 0107223..f202272 100644 --- a/public/proxy.js +++ b/public/proxy.js @@ -42,6 +42,9 @@ async function handleSetProxyConfig(config, sendResponse) { console.log('Direct connection set successfully'); sendResponse({success: true}); + + // 通知所有 content scripts 更新 + await notifyProxyStatusChanged(); } else { console.error('Failed to set direct connection'); sendResponse({ @@ -90,6 +93,9 @@ async function handleSetProxyConfig(config, sendResponse) { console.log('System proxy set successfully'); sendResponse({success: true}); + + // 通知所有 content scripts 更新 + await notifyProxyStatusChanged(); } else { console.error('Failed to set system proxy'); sendResponse({ @@ -155,6 +161,9 @@ async function handleSetProxyConfig(config, sendResponse) { console.log('Proxy successfully set:', settings.value); sendResponse({success: true}); + + // 通知所有 content scripts 更新 + await notifyProxyStatusChanged(); } else { console.error('Proxy settings verification failed'); sendResponse({ @@ -173,49 +182,24 @@ async function handleSetProxyConfig(config, sendResponse) { async function handleClearProxyConfig(sendResponse) { try { - await new Promise((resolve) => { - chrome.proxy.settings.clear({ - scope: 'regular' - }, resolve); + await chrome.proxy.settings.clear({ + scope: 'regular' }); - - await new Promise((resolve) => { - chrome.proxy.settings.set({ - value: {mode: "system"}, - scope: 'regular' - }, resolve); - }); - - // 只移除当前代理配置,保留代理列表 - await proxyStore.clearCurrentProxy(); - - // 更新所有代理的启用状态 + // 获取所有配置并禁用 const configs = await proxyStore.getProxyConfigs(); const updatedConfigs = configs.map(config => ({ ...config, enabled: false })); await proxyStore.saveProxyConfigs(updatedConfigs); - - const settings = await getProxySettings(); - const isSuccess = settings.value.mode === "system"; - - if (isSuccess) { - console.log('Proxy successfully cleared'); - sendResponse({success: true}); - } else { - console.error('Failed to clear proxy settings'); - sendResponse({ - success: false, - error: '无法清除代理设置' - }); - } + + sendResponse({ success: true }); + + // 通知所有 content scripts 更新 + await notifyProxyStatusChanged(); } catch (error) { - console.error('Error clearing proxy:', error); - sendResponse({ - success: false, - error: error.message || '清除代理时发生错误' - }); + console.error('Error clearing proxy config:', error); + sendResponse({ success: false, error: error.message }); } } @@ -361,7 +345,9 @@ export function setupProxyHandlers() { return true; case ProxyActionType.CLEAR_PROXY_CONFIG: - handleClearProxyConfig(sendResponse); + (async () => { + await handleClearProxyConfig(sendResponse); + })(); return true; case ProxyActionType.GET_PROXY_STATUS: @@ -394,17 +380,15 @@ export function setupProxyHandlers() { return true; case ProxyActionType.GET_PROXY_CONFIGS: - proxyStore.getProxyConfigs().then(configs => { - sendResponse({ - success: true, - data: configs - }); - }).catch(error => { - sendResponse({ - success: false, - error: error.message - }); - }); + (async () => { + try { + const configs = await proxyStore.getProxyConfigs(); + sendResponse({ success: true, data: configs }); + } catch (error) { + console.error('Error getting proxy configs:', error); + sendResponse({ success: false, error: error.message }); + } + })(); return true; case ProxyActionType.ADD_PROXY_CONFIG: @@ -422,7 +406,7 @@ export function setupProxyHandlers() { return true; case ProxyActionType.UPDATE_PROXY_CONFIG: - (async () => { // 使用立即执行的异步函数 + (async () => { try { if (!msg.configs || !Array.isArray(msg.configs)) { throw new Error('无效的配置数据'); @@ -440,6 +424,9 @@ export function setupProxyHandlers() { success: true, data: updatedConfigs }); + + // 通知所有 content scripts 更新 + await notifyProxyStatusChanged(); } catch (error) { console.error('Error updating proxy configs:', error); sendResponse({ @@ -448,7 +435,50 @@ export function setupProxyHandlers() { }); } })(); - return true; // 保持消息端口打开 + return true; + + case 'OPEN_OPTIONS_PAGE': + // 打开选项页 + chrome.tabs.create({ + url: chrome.runtime.getURL('/proxy/options.html') + }).then(tab => { + if (msg.triggerAdd) { + // 如果需要触发添加代理,等待页面加载完成 + const listener = (tabId, changeInfo) => { + 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); + } + }); + sendResponse({ success: true }); + return true; + + case 'PROXY_GET_STATUS': + return handleGetProxyStatus(sendResponse); + + case 'PROXY_GET_CONFIGS': + (async () => { + try { + const configs = await proxyStore.getProxyConfigs(); + sendResponse({ success: true, data: configs }); + } catch (error) { + console.error('Error getting proxy configs:', error); + sendResponse({ success: false, error: error.message }); + } + })(); + return true; + + case 'PROXY_SET_CONFIG': + return handleSetProxyConfig(msg.config, sendResponse); + + case 'PROXY_CLEAR_CONFIG': + return handleClearProxyConfig(sendResponse); } }); @@ -458,9 +488,13 @@ export function setupProxyHandlers() { // 确保默认配置存在 await ProxySettings.setDefaultConfigs(); - // 清除之前的代理设置 - await handleClearProxyConfig(() => { - }); + // 设置为系统代理 + await handleSetProxyConfig({ + id: 'system', + name: '[系统代理]', + proxyType: 'system', + enabled: true + }, () => {}); // 设置认证监听 await ProxyAuth.setupAuthListener(); @@ -468,4 +502,31 @@ export function setupProxyHandlers() { console.error('Error during installation:', error); } }); + + // 添加启动时的初始化 + chrome.runtime.onStartup.addListener(async () => { + try { + // 设置为系统代理 + await handleSetProxyConfig({ + id: 'system', + name: '[系统代理]', + proxyType: 'system', + enabled: true + }, () => {}); + } catch (error) { + console.error('Error during startup:', error); + } + }); +} + +// 当代理状态改变时通知所有内容脚本 +async function notifyProxyStatusChanged() { + const tabs = await chrome.tabs.query({}); + for (const tab of tabs) { + try { + chrome.tabs.sendMessage(tab.id, { action: 'PROXY_STATUS_CHANGED' }); + } catch (error) { + // 忽略不支持的标签页 + } + } } \ No newline at end of file diff --git a/public/proxy/content.js b/public/proxy/content.js new file mode 100644 index 0000000..b77396f --- /dev/null +++ b/public/proxy/content.js @@ -0,0 +1,489 @@ +console.log("Content script starting..."); + +// 代理操作类型常量 +const ProxyActionType = { + SET_PROXY_CONFIG: 'SET_PROXY_CONFIG', + CLEAR_PROXY_CONFIG: 'CLEAR_PROXY_CONFIG', + GET_PROXY_STATUS: 'GET_PROXY_STATUS', + GET_PROXY_CONFIGS: 'GET_PROXY_CONFIGS', + UPDATE_PROXY_CONFIG: 'UPDATE_PROXY_CONFIG' +}; + +// 添加一个通用的消息发送函数 +async function sendMessageWithRetry(message, maxRetries = 3) { + for (let i = 0; i < maxRetries; i++) { + try { + const response = await chrome.runtime.sendMessage(message); + return response; + } catch (error) { + console.warn(`Attempt ${i + 1} failed:`, error); + if (i === maxRetries - 1) { + throw error; + } + // 等待一小段时间后重试 + await new Promise(resolve => setTimeout(resolve, 500)); + } + } +} + +// 获取当前代理状态 +async function getCurrentProxy() { + try { + const response = await sendMessageWithRetry({ + 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: '' }; + } + + 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' : '' + }; + } catch (error) { + console.error('Error getting proxy status:', error); + return { enable: false, proxy: '' }; + } +} + +// 获取所有代理配置 +async function getProxyConfigs() { + try { + const response = await sendMessageWithRetry({ + action: ProxyActionType.GET_PROXY_CONFIGS + }); + + if (!response) { + console.log('No response from background script'); + return []; + } + if (!response.success) { + console.error('Error in response:', response.error); + return []; + } + return response.data || []; + } catch (error) { + console.error('Error getting proxy configs:', error); + return []; + } +} + +// 切换代理 +async function switchProxy(config) { + try { + console.log('Switching proxy:', config); + await sendMessageWithRetry({ + action: ProxyActionType.SET_PROXY_CONFIG, + config: config + }); + + await updatePanel(); + } catch (error) { + console.error('Error switching proxy:', error); + } +} + +// 清除代理 +async function clearProxy() { + try { + const response = await sendMessageWithRetry({ + action: ProxyActionType.SET_PROXY_CONFIG, + config: { + id: 'direct', + name: '[直接连接]', + proxyType: 'direct', + enabled: false + } + }); + + console.log('Clear proxy response:', response); + await 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(); + + console.log('Current proxy status:', currentProxy); + + // 修改这里的判断逻辑 + let html = ` +
+ 🔴 + [直接连接] +
+
+ ⚙️ + [系统代理] +
+ `; + + // 添加自定义代理配置 + configs.forEach(config => { + if (config.id !== 'direct' && config.id !== 'system') { + // 检查当前代理是否与配置匹配 + const isActive = currentProxy.enable && + currentProxy.proxy === `${config.scheme}://${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: 200px; + 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: 12px; + } + + .proxy-item { + display: flex; + align-items: center; + padding: 8px 12px; + cursor: pointer; + transition: all 0.2s; + color: #666; + border-left: 3px solid transparent; + } + + .proxy-item:hover { + background: #f5f5f5; + } + + .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 span:first-child { + color: #ff6b00; + } + + .add-proxy { + display: flex; + align-items: center; + padding: 8px 12px; + color: #1890ff; + cursor: pointer; + border-top: 1px solid #eee; + transition: all 0.2s; + } + + .add-proxy:hover { + background: #f5f5f5; + } + + .settings { + padding: 8px 12px; + color: #666; + cursor: pointer; + border-top: 1px solid #eee; + transition: all 0.2s; + } + + .settings:hover { + background: #f5f5f5; + } + `; + + // 创建面板内容 + 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..."); + const observer = new MutationObserver((mutations, obs) => { + if (document.body) { + console.log("Body found via observer"); + obs.disconnect(); + initPanel(); + } + }); + + observer.observe(document.documentElement, { + childList: true, + subtree: true + }); + } +} + +// 尝试多种方式来确保面板被创建 +console.log("Setting up initialization..."); + +window.addEventListener('load', () => { + console.log("Window load triggered"); + initPanel(); +}); + +// 在关键位置添加更多日志 +console.log("Document readyState:", document.readyState); +console.log("Document body exists:", !!document.body); +console.log("Document documentElement exists:", !!document.documentElement); diff --git a/src/components/ProxySwitch/index.css b/src/components/ProxySwitch/index.css index 9c92859..f004061 100644 --- a/src/components/ProxySwitch/index.css +++ b/src/components/ProxySwitch/index.css @@ -177,4 +177,27 @@ body { .menu-item-add:hover .anticon { color: var(--yakit-primary); -} \ No newline at end of file +} + +.menu-loading { + pointer-events: none; + opacity: 0.7; +} + +.menu-item-loading { + transition: all 0.3s ease; +} + +.menu-item-selected { + transition: all 0.3s ease; + background-color: var(--yakit-primary-5) !important; +} + +/* 添加过渡效果 */ +.ant-menu-item { + transition: all 0.3s ease !important; +} + +.ant-menu-item .menu-icon { + transition: color 0.3s ease; +} \ No newline at end of file diff --git a/src/components/ProxySwitch/index.tsx b/src/components/ProxySwitch/index.tsx index e0bf4fd..b8b0969 100644 --- a/src/components/ProxySwitch/index.tsx +++ b/src/components/ProxySwitch/index.tsx @@ -55,6 +55,7 @@ export const ProxySwitch: React.FC = ({ }) => { const [currentMode, setCurrentMode] = useState('direct'); const [customProxies, setCustomProxies] = useState([]); + const [isLoading, setIsLoading] = useState(false); useEffect(() => { loadProxyStatus(); @@ -110,6 +111,7 @@ export const ProxySwitch: React.FC = ({ const handleApplyConfig = async (mode: string) => { try { + setIsLoading(true); const fixedMode = FIXED_MODES.find(fixed => fixed.key === mode); const customProxy = customProxies.find(proxy => proxy.key === mode); @@ -120,17 +122,30 @@ export const ProxySwitch: React.FC = ({ return; } + setCurrentMode(mode); + + if (customProxy) { + setCustomProxies(prev => prev.map(p => ({ + ...p, + enabled: p.key === mode + }))); + } + const response = await chrome.runtime.sendMessage({ action: ProxyActionType.SET_PROXY_CONFIG, config }); - if (response.success) { - setCurrentMode(mode); - await loadCustomProxies(); + if (response?.success === false) { + throw new Error(response.error || '设置代理失败'); } + + await loadCustomProxies(); } catch (error) { - console.error('Failed to apply proxy config:', error); + console.error('Error applying proxy config:', error); + throw error; + } finally { + setIsLoading(false); } }; @@ -164,12 +179,9 @@ export const ProxySwitch: React.FC = ({ const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => { if (tabId === tab.id && changeInfo.status === 'complete') { chrome.tabs.onUpdated.removeListener(listener); - // 给页面一点时间完全初始化 - // setTimeout(() => { chrome.tabs.sendMessage(tab.id!, { action: 'TRIGGER_ADD_PROXY' }); - // }, 500); // 减少延迟时间 } }; @@ -193,14 +205,17 @@ export const ProxySwitch: React.FC = ({ key: mode.key, icon: {mode.icon}, label: mode.name, - className: currentMode === mode.key ? 'menu-item-selected' : '' + className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}` })), { type: 'divider' }, ...customProxies.map(proxy => ({ key: proxy.key, icon: , - label: {proxy.name}, - className: proxy.enabled ? 'menu-item-selected' : '' + label: {proxy.name}, + className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}` })), { key: 'add', @@ -220,8 +235,9 @@ export const ProxySwitch: React.FC = ({ handleModeChange(key)} + onClick={({ key }) => !isLoading && handleModeChange(key)} style={{ width: 180 }} + className={isLoading ? 'menu-loading' : ''} /> ); }; \ No newline at end of file