mirror of
https://github.com/yaklang/yaklang-chrome-extension.git
synced 2026-09-22 03:10:43 +08:00
add proxy switch demo
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@ build.pem
|
|||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
/2.5.21_0
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env.local
|
.env.local
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import {ActionType, injectScriptAndSendMessage, WebSocketManager} from './socket.js';
|
import {ActionType, injectScriptAndSendMessage, WebSocketManager} from './socket.js';
|
||||||
|
import { setupProxyHandlers } from './proxy.js';
|
||||||
|
import { ProxyActionType } from './types/action.js';
|
||||||
|
|
||||||
console.info("Chrome Extenstion Background is loaded")
|
console.info("Chrome Extenstion Background is loaded")
|
||||||
|
|
||||||
const websocketManager = new WebSocketManager();
|
const websocketManager = new WebSocketManager();
|
||||||
|
|
||||||
|
// 设置代理处理器
|
||||||
|
setupProxyHandlers();
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
|
chrome.runtime.onMessage.addListener(function (msg, sender, sendResponse) {
|
||||||
console.log("msg", msg)
|
console.log("msg", msg)
|
||||||
|
|||||||
+7
-3
@@ -3,6 +3,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.contentScriptInjected = true;
|
window.contentScriptInjected = true;
|
||||||
|
window.badgeCount = 0;
|
||||||
// 检查并插入 CSS 样式
|
// 检查并插入 CSS 样式
|
||||||
const styleId = 'injected-css-style';
|
const styleId = 'injected-css-style';
|
||||||
if (!document.getElementById(styleId)) {
|
if (!document.getElementById(styleId)) {
|
||||||
@@ -38,18 +39,21 @@
|
|||||||
script.remove();
|
script.remove();
|
||||||
};
|
};
|
||||||
(document.head || document.documentElement).appendChild(script);
|
(document.head || document.documentElement).appendChild(script);
|
||||||
window.addEventListener('message', function onMessage(event) {
|
window.addEventListener('message', async function onMessage(event) {
|
||||||
if (event.source !== window || event.data.type !== 'FROM_INJECT_JS') {
|
if (event.source !== window || event.data.type !== 'FROM_INJECT_JS') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
window.removeEventListener('message', onMessage);
|
window.removeEventListener('message', onMessage);
|
||||||
// Send the result to the background script
|
window.badgeCount += 1;
|
||||||
// chrome.runtime.sendMessage({ action: 'yakit_to_extension_page', result: event.data.result });
|
|
||||||
// 直接向向发送端返回结果
|
// 直接向向发送端返回结果
|
||||||
sendResponse({action: 'yakit_to_extension_page', result: event.data.result});
|
sendResponse({action: 'yakit_to_extension_page', result: event.data.result});
|
||||||
|
// Send updated badge count to background script
|
||||||
|
await chrome.runtime.sendMessage({action: 'yakit_badge', data: window.badgeCount.toString()});
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
})()
|
})()
|
||||||
|
|
||||||
|
|||||||
+17
-10
@@ -3,36 +3,43 @@
|
|||||||
"name": "Yakit Chrome Endpoint",
|
"name": "Yakit Chrome Endpoint",
|
||||||
"version": "1.0",
|
"version": "1.0",
|
||||||
"description": "A Endpoint for Yakit MITM or more",
|
"description": "A Endpoint for Yakit MITM or more",
|
||||||
|
"options_ui": {
|
||||||
|
"page": "proxy/options.html",
|
||||||
|
"open_in_tab": true
|
||||||
|
},
|
||||||
"action": {
|
"action": {
|
||||||
"default_popup": "index.html",
|
"default_title": "Click to open panel",
|
||||||
"default_icon": {
|
"default_icon": {
|
||||||
"16": "/images/icon16.png",
|
"16": "/images/icon16.png",
|
||||||
"48": "/images/icon48.png",
|
"48": "/images/icon48.png",
|
||||||
"128": "/images/icon128.png"
|
"128": "/images/icon128.png"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"side_panel": {
|
||||||
|
"default_path" : "index.html"
|
||||||
|
},
|
||||||
"background": {
|
"background": {
|
||||||
"service_worker": "background.js",
|
"service_worker": "background.js",
|
||||||
"type": "module"
|
"type": "module"
|
||||||
},
|
},
|
||||||
"permissions": [
|
"permissions": [
|
||||||
"webNavigation",
|
|
||||||
"activeTab",
|
|
||||||
"scripting",
|
|
||||||
"tabs",
|
|
||||||
"proxy",
|
"proxy",
|
||||||
"storage",
|
"storage",
|
||||||
"webRequest"
|
"sidePanel"
|
||||||
],
|
],
|
||||||
"host_permissions": [
|
"host_permissions": [
|
||||||
"<all_urls>"
|
"*://*/*"
|
||||||
],
|
],
|
||||||
|
|
||||||
"web_accessible_resources": [
|
"web_accessible_resources": [
|
||||||
{
|
{
|
||||||
"resources": ["inject.js"],
|
"resources": [
|
||||||
"matches": ["<all_urls>"],
|
"types/*.js",
|
||||||
"use_dynamic_url": true
|
"proxy/*.js",
|
||||||
|
"socket.js",
|
||||||
|
"proxy.js"
|
||||||
|
],
|
||||||
|
"matches": ["<all_urls>"]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"icons": {
|
"icons": {
|
||||||
|
|||||||
+214
@@ -0,0 +1,214 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
// 记录代理日志
|
||||||
|
async function logProxyRequest(details, proxyConfig, error = null) {
|
||||||
|
const log = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
timestamp: Date.now(),
|
||||||
|
url: details.url,
|
||||||
|
proxyId: proxyConfig.id,
|
||||||
|
proxyName: proxyConfig.name,
|
||||||
|
status: error ? 'error' : 'success',
|
||||||
|
errorMessage: error?.message
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = await chrome.storage.local.get('proxyLogs');
|
||||||
|
const logs = result.proxyLogs || [];
|
||||||
|
const updatedLogs = [log, ...logs].slice(0, 1000);
|
||||||
|
await chrome.storage.local.set({ proxyLogs: updatedLogs });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSetProxyConfig(config, sendResponse) {
|
||||||
|
try {
|
||||||
|
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: ["localhost", "127.0.0.1"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await chrome.proxy.settings.set({
|
||||||
|
value: proxyConfig,
|
||||||
|
scope: 'regular'
|
||||||
|
});
|
||||||
|
|
||||||
|
const settings = await chrome.proxy.settings.get({});
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await chrome.storage.local.get('proxyConfigs');
|
||||||
|
if (result.proxyConfigs) {
|
||||||
|
const updatedConfigs = result.proxyConfigs.map(c => ({
|
||||||
|
...c,
|
||||||
|
enabled: c.id === config.id
|
||||||
|
}));
|
||||||
|
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Proxy successfully set:', settings.value);
|
||||||
|
sendResponse({ success: true });
|
||||||
|
} else {
|
||||||
|
console.error('Proxy settings verification failed');
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
error: '代理设置验证失败'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting proxy:', error);
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
error: error.message || '设置代理时发生错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleClearProxyConfig(sendResponse) {
|
||||||
|
try {
|
||||||
|
await chrome.proxy.settings.clear({
|
||||||
|
scope: 'regular'
|
||||||
|
});
|
||||||
|
|
||||||
|
await chrome.proxy.settings.set({
|
||||||
|
value: { mode: "system" },
|
||||||
|
scope: 'regular'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 只移除当前代理配置,保留代理列表
|
||||||
|
await chrome.storage.local.remove([
|
||||||
|
'currentProxy',
|
||||||
|
'proxyAuthHandlers'
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 更新所有代理的启用状态
|
||||||
|
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 settings = await chrome.proxy.settings.get({});
|
||||||
|
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: '无法清除代理设置'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error clearing proxy:', error);
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
error: error.message || '清除代理时发生错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleGetProxyStatus(sendResponse) {
|
||||||
|
try {
|
||||||
|
const settings = await chrome.proxy.settings.get({});
|
||||||
|
const currentProxy = await chrome.storage.local.get('currentProxy');
|
||||||
|
|
||||||
|
const status = {
|
||||||
|
enabled: settings.value.mode === "fixed_servers",
|
||||||
|
config: currentProxy.currentProxy || null,
|
||||||
|
mode: settings.value.mode
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Current proxy status:', status);
|
||||||
|
sendResponse({
|
||||||
|
success: true,
|
||||||
|
data: status
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting proxy status:', error);
|
||||||
|
sendResponse({
|
||||||
|
success: false,
|
||||||
|
error: error.message || '获取代理状态时发生错误'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出代理处理器设置函数
|
||||||
|
export function setupProxyHandlers() {
|
||||||
|
// 设置代理错误处理和认证
|
||||||
|
ProxyAuth.setupErrorHandler();
|
||||||
|
ProxyAuth.setupAuthListener();
|
||||||
|
|
||||||
|
// 消息监听器
|
||||||
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
|
console.log("Proxy message:", msg);
|
||||||
|
|
||||||
|
switch (msg.action) {
|
||||||
|
case ProxyActionType.SET_PROXY_CONFIG:
|
||||||
|
handleSetProxyConfig(msg.config, sendResponse);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ProxyActionType.CLEAR_PROXY_CONFIG:
|
||||||
|
handleClearProxyConfig(sendResponse);
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case ProxyActionType.GET_PROXY_STATUS:
|
||||||
|
handleGetProxyStatus(sendResponse);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 在扩展启动时初始化
|
||||||
|
chrome.runtime.onInstalled.addListener(async () => {
|
||||||
|
try {
|
||||||
|
// 确保默认配置存在
|
||||||
|
await ProxySettings.setDefaultConfigs();
|
||||||
|
|
||||||
|
// 清除之前的代理设置
|
||||||
|
await handleClearProxyConfig(() => {});
|
||||||
|
|
||||||
|
// 设置认证监听
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
body {
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||||
|
margin: 0;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #f5f5f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: white;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header h1 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-item {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #e8e8e8;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 16px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-item:hover {
|
||||||
|
box-shadow: 0 2px 8px rgba(0,0,0,0.09);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-name {
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-name:focus {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-type-select,
|
||||||
|
.proxy-scheme,
|
||||||
|
.proxy-host,
|
||||||
|
.proxy-port {
|
||||||
|
width: 100%;
|
||||||
|
height: 32px;
|
||||||
|
padding: 4px 11px;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
border-radius: 4px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-type-select:focus,
|
||||||
|
.proxy-scheme:focus,
|
||||||
|
.proxy-host:focus,
|
||||||
|
.proxy-port:focus {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24,144,255,0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
background: #1890ff;
|
||||||
|
border: none;
|
||||||
|
color: white;
|
||||||
|
padding: 4px 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary:hover {
|
||||||
|
background: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary {
|
||||||
|
background: white;
|
||||||
|
border: 1px solid #d9d9d9;
|
||||||
|
color: rgba(0,0,0,0.85);
|
||||||
|
padding: 4px 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:hover {
|
||||||
|
border-color: #40a9ff;
|
||||||
|
color: #40a9ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
padding: 4px 8px;
|
||||||
|
background: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.delete-btn:hover {
|
||||||
|
background: #ff7875;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pac-script {
|
||||||
|
font-family: monospace;
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<title>代理设置</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script src="../options.bundle.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,170 @@
|
|||||||
|
console.log('Options page script loaded');
|
||||||
|
|
||||||
|
import { ProxySettings } from './proxy-settings.js';
|
||||||
|
import { ProxyManager } from './proxy-manager.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_server') {
|
||||||
|
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_server',
|
||||||
|
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_server' && !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();
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ... 其他代码保持不变
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
// 代理认证管理
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取认证信息
|
||||||
|
const getAuth = async () => {
|
||||||
|
const result = await chrome.storage.local.get('proxyAuth');
|
||||||
|
return result.proxyAuth;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 清除认证信息
|
||||||
|
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 setupErrorHandler() {
|
||||||
|
// 在 Manifest V3 中,我们不能使用 chrome.proxy.onProxyError
|
||||||
|
// 所以我们只记录错误到 storage
|
||||||
|
try {
|
||||||
|
const logError = async (error) => {
|
||||||
|
const errors = await chrome.storage.local.get('proxyErrors') || [];
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置代理认证信息
|
||||||
|
static async setProxyAuth(username, password) {
|
||||||
|
try {
|
||||||
|
await chrome.storage.local.set({
|
||||||
|
proxyAuth: {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
timestamp: Date.now()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error setting proxy auth:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取代理认证信息
|
||||||
|
static async getProxyAuth() {
|
||||||
|
try {
|
||||||
|
const result = await chrome.storage.local.get('proxyAuth');
|
||||||
|
return result.proxyAuth || null;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error getting proxy auth:', error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清除代理认证信息
|
||||||
|
static async clearProxyAuth() {
|
||||||
|
try {
|
||||||
|
await chrome.storage.local.remove('proxyAuth');
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error clearing proxy auth:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
// 代理配置管理
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
// 代理配置存储和管理
|
||||||
|
export const ProxySettings = {
|
||||||
|
async importSettings(settings) {
|
||||||
|
try {
|
||||||
|
if (Array.isArray(settings) && settings.every(s => s.proxyType)) {
|
||||||
|
await chrome.storage.local.set({proxyConfigs: settings});
|
||||||
|
return {success: true};
|
||||||
|
}
|
||||||
|
return {success: false, error: "Invalid settings format"};
|
||||||
|
} catch (error) {
|
||||||
|
return {success: false, error: error.message};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async exportSettings() {
|
||||||
|
try {
|
||||||
|
const {proxyConfigs} = await chrome.storage.local.get('proxyConfigs');
|
||||||
|
return {success: true, settings: proxyConfigs || []};
|
||||||
|
} catch (error) {
|
||||||
|
return {success: false, error: error.message};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async setDefaultConfigs() {
|
||||||
|
const result = await chrome.storage.local.get('proxyConfigs');
|
||||||
|
if (!result.proxyConfigs) {
|
||||||
|
const defaultConfigs = [{
|
||||||
|
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: [] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>My Sidepanel</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>All sites sidepanel extension</h1>
|
||||||
|
<p>This side panel is enabled on all sites</p>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
+3
-2
@@ -8,6 +8,7 @@ export const ActionType = {
|
|||||||
CLEAR_PROXY: 'clear_proxy',
|
CLEAR_PROXY: 'clear_proxy',
|
||||||
INJECT_SCRIPT: 'yakit_inject_script',
|
INJECT_SCRIPT: 'yakit_inject_script',
|
||||||
TO_EXTENSION_PAGE: "yakit_to_extension_page",
|
TO_EXTENSION_PAGE: "yakit_to_extension_page",
|
||||||
|
BADGE_COUNT: "yakit_badge",
|
||||||
}
|
}
|
||||||
|
|
||||||
export class WebSocketManager {
|
export class WebSocketManager {
|
||||||
@@ -22,7 +23,7 @@ export class WebSocketManager {
|
|||||||
|
|
||||||
this.socket.onopen = () => {
|
this.socket.onopen = () => {
|
||||||
chrome.runtime.sendMessage({action: ActionType.STATUS, connected: true, port: port});
|
chrome.runtime.sendMessage({action: ActionType.STATUS, connected: true, port: port});
|
||||||
// this.startHeartbeat();
|
this.startHeartbeat();
|
||||||
};
|
};
|
||||||
|
|
||||||
this.socket.onmessage = (event) => {
|
this.socket.onmessage = (event) => {
|
||||||
@@ -66,7 +67,7 @@ export class WebSocketManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
startHeartbeat() {
|
startHeartbeat() {
|
||||||
this.intervalId = setInterval(() => this.heartbeat(), 3000);
|
this.intervalId = setInterval(() => this.heartbeat(), 25000);
|
||||||
}
|
}
|
||||||
|
|
||||||
stopHeartbeat() {
|
stopHeartbeat() {
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export const ProxyActionType = {
|
||||||
|
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
|
||||||
|
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
|
||||||
|
GET_PROXY_STATUS: "GET_PROXY_STATUS"
|
||||||
|
};
|
||||||
+5
-3
@@ -4,6 +4,7 @@ import {ConfigProvider} from "antd";
|
|||||||
import {Contro} from "@components/Contro";
|
import {Contro} from "@components/Contro";
|
||||||
import {Proxifier} from "@components/Proxifier";
|
import {Proxifier} from "@components/Proxifier";
|
||||||
import {EvalInTab} from "@components/EvalInTab";
|
import {EvalInTab} from "@components/EvalInTab";
|
||||||
|
import {ProxySwitch} from "@components/ProxySwitch";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
@@ -15,10 +16,11 @@ function App() {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="App">
|
<div className="App">
|
||||||
<Contro/>
|
{/*<Contro/>*/}
|
||||||
{/*<Proxifier/>*/}
|
{/* <Proxifier/> */}
|
||||||
|
<ProxySwitch/>
|
||||||
|
|
||||||
<EvalInTab/>
|
{/* <EvalInTab/> */}
|
||||||
</div>
|
</div>
|
||||||
</ConfigProvider>
|
</ConfigProvider>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ export const EvalInTab: React.FC<EvalInTabProps> = () => {
|
|||||||
value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData},
|
value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData},
|
||||||
});
|
});
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log("error:", error)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
.proxy-switch {
|
||||||
|
padding: 16px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch .ant-select {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch .ant-space {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-title-text {
|
||||||
|
color: #31343F;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-content {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #F8F8F8;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-status {
|
||||||
|
color: #85899E;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-proxy {
|
||||||
|
color: #31343F;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-status-active {
|
||||||
|
color: var(--yakit-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-switch-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,288 @@
|
|||||||
|
import React, {useEffect, useState} from "react";
|
||||||
|
import {Select, Button, Tooltip, Modal, Form, Input, Radio, Space, message} from "antd";
|
||||||
|
import {PlusOutlined, SettingOutlined} from "@ant-design/icons";
|
||||||
|
import {ProxyConfig} from "@/types/proxy";
|
||||||
|
import {StorageChanges} from "@/types/chrome";
|
||||||
|
import "./index.css";
|
||||||
|
|
||||||
|
export const ProxySwitch: React.FC = () => {
|
||||||
|
const [currentMode, setCurrentMode] = useState<string>("direct");
|
||||||
|
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
|
||||||
|
const [isModalVisible, setIsModalVisible] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [proxyHost, setProxyHost] = useState('');
|
||||||
|
const [proxyPort, setProxyPort] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadConfigs().catch(error => {
|
||||||
|
console.error("Failed to load configs:", error);
|
||||||
|
// 可以在这里添加错误提示
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (proxyConfigs.length > 0) {
|
||||||
|
applyProxySettings(currentMode).catch(error => {
|
||||||
|
console.error("Failed to apply proxy settings:", error);
|
||||||
|
// 可以在这里添加错误提示
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [proxyConfigs, currentMode]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleStorageChange = (changes: StorageChanges) => {
|
||||||
|
if (changes.proxyConfigs) {
|
||||||
|
const newConfigs = changes.proxyConfigs.newValue;
|
||||||
|
setProxyConfigs(newConfigs);
|
||||||
|
const currentConfig = newConfigs.find((c: ProxyConfig) => c.id === currentMode);
|
||||||
|
if (!currentConfig || !currentConfig.enabled) {
|
||||||
|
setCurrentMode("direct");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||||
|
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||||
|
}, [currentMode]);
|
||||||
|
|
||||||
|
const loadConfigs = async () => {
|
||||||
|
try {
|
||||||
|
const result = await chrome.storage.local.get('proxyConfigs');
|
||||||
|
if (!result.proxyConfigs) {
|
||||||
|
const defaultConfigs: ProxyConfig[] = [
|
||||||
|
{
|
||||||
|
id: "direct",
|
||||||
|
name: "直接连接",
|
||||||
|
proxyType: "direct" as const,
|
||||||
|
enabled: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "default",
|
||||||
|
name: "默认代理",
|
||||||
|
proxyType: "fixed_server" as const,
|
||||||
|
host: "127.0.0.1",
|
||||||
|
port: 8080,
|
||||||
|
scheme: "http",
|
||||||
|
enabled: false
|
||||||
|
}
|
||||||
|
];
|
||||||
|
await chrome.storage.local.set({proxyConfigs: defaultConfigs});
|
||||||
|
setProxyConfigs(defaultConfigs);
|
||||||
|
setCurrentMode("direct");
|
||||||
|
} else {
|
||||||
|
setProxyConfigs(result.proxyConfigs);
|
||||||
|
const currentConfig = result.proxyConfigs.find((c: ProxyConfig) => c.id === currentMode);
|
||||||
|
if (!currentConfig || !currentConfig.enabled) {
|
||||||
|
setCurrentMode("direct");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error in loadConfigs:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyProxySettings = async (modeId: string) => {
|
||||||
|
try {
|
||||||
|
const config = proxyConfigs.find((c: ProxyConfig) => c.id === modeId);
|
||||||
|
if (!config || !config.enabled) return false;
|
||||||
|
|
||||||
|
if (config.proxyType === "direct") {
|
||||||
|
chrome.proxy.settings.clear({scope: 'regular'});
|
||||||
|
return true;
|
||||||
|
} else if (config.proxyType === "fixed_server") {
|
||||||
|
chrome.proxy.settings.set({
|
||||||
|
value: {
|
||||||
|
mode: "fixed_servers",
|
||||||
|
rules: {
|
||||||
|
singleProxy: {
|
||||||
|
scheme: config.scheme,
|
||||||
|
host: config.host,
|
||||||
|
port: Number(config.port)
|
||||||
|
},
|
||||||
|
bypassList: ["<-loopback>"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope: "regular"
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
} else if (config.proxyType === "pac_script") {
|
||||||
|
chrome.proxy.settings.set({
|
||||||
|
value: {
|
||||||
|
mode: "pac_script",
|
||||||
|
pacScript: {
|
||||||
|
data: config.pacScript
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scope: "regular"
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to apply proxy settings:", error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddProxy = () => {
|
||||||
|
form.resetFields();
|
||||||
|
setIsModalVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleModalOk = () => {
|
||||||
|
form.validateFields().then(async values => {
|
||||||
|
try {
|
||||||
|
const newConfig: ProxyConfig = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
name: values.name,
|
||||||
|
proxyType: values.proxyType as "direct" | "fixed_server" | "pac_script" | "auto_detect",
|
||||||
|
host: values.host,
|
||||||
|
port: values.port ? parseInt(values.port) : undefined,
|
||||||
|
scheme: values.scheme,
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
const updatedConfigs = [...proxyConfigs, newConfig];
|
||||||
|
await chrome.storage.local.set({proxyConfigs: updatedConfigs});
|
||||||
|
setProxyConfigs(updatedConfigs);
|
||||||
|
setIsModalVisible(false);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to save new proxy config:", error);
|
||||||
|
// 可以在这里添加错误提示
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSettingClick = () => {
|
||||||
|
if (chrome.runtime.openOptionsPage) {
|
||||||
|
chrome.runtime.openOptionsPage();
|
||||||
|
} else {
|
||||||
|
chrome.tabs.create({
|
||||||
|
url: chrome.runtime.getURL('proxy/options.html')
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleApplyProxy = async () => {
|
||||||
|
try {
|
||||||
|
const response = await chrome.runtime.sendMessage({
|
||||||
|
action: 'SET_PROXY_CONFIG',
|
||||||
|
config: {
|
||||||
|
host: proxyHost,
|
||||||
|
port: parseInt(proxyPort),
|
||||||
|
scheme: 'http',
|
||||||
|
proxyType: 'fixed_server',
|
||||||
|
id: 'default',
|
||||||
|
name: '默认代理'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
message.success('代理设置已应用');
|
||||||
|
} else {
|
||||||
|
message.error(response.error || '代理设置失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error applying proxy:', error);
|
||||||
|
message.error('应用代理设置时发生错误');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="proxy-switch">
|
||||||
|
<div className="proxy-switch-header">
|
||||||
|
<Select
|
||||||
|
value={currentMode}
|
||||||
|
onChange={setCurrentMode}
|
||||||
|
style={{width: 200}}
|
||||||
|
options={proxyConfigs.filter((config: ProxyConfig) => config.enabled).map((config: ProxyConfig) => ({
|
||||||
|
label: config.name,
|
||||||
|
value: config.id
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
<Space>
|
||||||
|
<Tooltip title="添加代理">
|
||||||
|
<Button
|
||||||
|
icon={<PlusOutlined/>}
|
||||||
|
onClick={handleAddProxy}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
<Tooltip title="代理设置">
|
||||||
|
<Button
|
||||||
|
icon={<SettingOutlined/>}
|
||||||
|
onClick={handleSettingClick}
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
title="添加代理配置"
|
||||||
|
open={isModalVisible}
|
||||||
|
onOk={handleModalOk}
|
||||||
|
onCancel={() => setIsModalVisible(false)}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<Form.Item
|
||||||
|
name="name"
|
||||||
|
label="配置名称"
|
||||||
|
rules={[{required: true}]}
|
||||||
|
>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="proxyType"
|
||||||
|
label="代理类型"
|
||||||
|
rules={[{required: true}]}
|
||||||
|
>
|
||||||
|
<Radio.Group>
|
||||||
|
<Radio value="direct">直接连接</Radio>
|
||||||
|
<Radio value="fixed_server">代理服务器</Radio>
|
||||||
|
<Radio value="pac_script">PAC 脚本</Radio>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
noStyle
|
||||||
|
shouldUpdate={(prev, curr) => prev.proxyType !== curr.proxyType}
|
||||||
|
>
|
||||||
|
{({getFieldValue}) =>
|
||||||
|
getFieldValue('proxyType') === 'fixed_server' && (
|
||||||
|
<>
|
||||||
|
<Form.Item
|
||||||
|
name="scheme"
|
||||||
|
label="代理协议"
|
||||||
|
rules={[{required: true}]}
|
||||||
|
>
|
||||||
|
<Select>
|
||||||
|
<Select.Option value="http">HTTP</Select.Option>
|
||||||
|
<Select.Option value="https">HTTPS</Select.Option>
|
||||||
|
<Select.Option value="socks4">SOCKS4</Select.Option>
|
||||||
|
<Select.Option value="socks5">SOCKS5</Select.Option>
|
||||||
|
</Select>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="host"
|
||||||
|
label="代理服务器"
|
||||||
|
rules={[{required: true}]}
|
||||||
|
>
|
||||||
|
<Input/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="port"
|
||||||
|
label="端口"
|
||||||
|
rules={[{required: true}]}
|
||||||
|
>
|
||||||
|
<Input type="number"/>
|
||||||
|
</Form.Item>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
.options-page {
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f0f2f5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-layout-content {
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-tabs {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-tabs-nav::before {
|
||||||
|
border-bottom-color: #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn {
|
||||||
|
color: var(--yakit-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-tabs-ink-bar {
|
||||||
|
background: var(--yakit-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-card {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-card-head {
|
||||||
|
border-bottom: none;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-card-body {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-btn-primary {
|
||||||
|
background: var(--yakit-primary);
|
||||||
|
border-color: var(--yakit-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-btn-primary:hover {
|
||||||
|
background: var(--yakit-primary-hover);
|
||||||
|
border-color: var(--yakit-primary-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-action-btn.ant-btn-default {
|
||||||
|
color: rgba(0, 0, 0, 0.65);
|
||||||
|
border-color: #d9d9d9;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-action-btn.ant-btn-default:hover {
|
||||||
|
color: var(--yakit-primary);
|
||||||
|
border-color: var(--yakit-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-action-btn.ant-btn-primary {
|
||||||
|
color: #fff;
|
||||||
|
background: var(--yakit-primary);
|
||||||
|
border-color: var(--yakit-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-action-btn.ant-btn-primary.ant-btn-dangerous {
|
||||||
|
background: #ff4d4f;
|
||||||
|
border-color: #ff4d4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.proxy-action-btn.ant-btn-primary.ant-btn-dangerous:hover {
|
||||||
|
background: #ff7875;
|
||||||
|
border-color: #ff7875;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-table-wrapper {
|
||||||
|
background: #fff;
|
||||||
|
padding: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-input:focus,
|
||||||
|
.options-page .ant-input-focused {
|
||||||
|
border-color: var(--yakit-primary);
|
||||||
|
box-shadow: 0 0 0 2px var(--yakit-primary-5);
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-select-focused .ant-select-selector {
|
||||||
|
border-color: var(--yakit-primary) !important;
|
||||||
|
box-shadow: 0 0 0 2px var(--yakit-primary-5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.options-page .ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||||
|
background-color: var(--yakit-primary-5);
|
||||||
|
}
|
||||||
@@ -0,0 +1,385 @@
|
|||||||
|
import React, { useEffect, useState } from "react";
|
||||||
|
import { Layout, Button, Card, Input, Select, InputNumber, Space, Typography, Modal, Switch, Tabs, Table, Form, App } from "antd";
|
||||||
|
import { PlusOutlined, DeleteOutlined, ImportOutlined, ExportOutlined } from "@ant-design/icons";
|
||||||
|
import { ProxyConfig } from "@/types/proxy";
|
||||||
|
import { StorageChanges } from "@/types/chrome";
|
||||||
|
import './index.css';
|
||||||
|
import { ProxyActionType } from '@/types/action';
|
||||||
|
|
||||||
|
const { Header, Content } = Layout;
|
||||||
|
const { Title } = Typography;
|
||||||
|
const { TextArea } = Input;
|
||||||
|
|
||||||
|
const headerStyle = {
|
||||||
|
background: '#fff',
|
||||||
|
padding: '0 24px',
|
||||||
|
borderBottom: '1px solid #f0f0f0'
|
||||||
|
};
|
||||||
|
|
||||||
|
const contentStyle = {
|
||||||
|
padding: '24px',
|
||||||
|
background: '#f0f2f5',
|
||||||
|
minHeight: '100vh'
|
||||||
|
};
|
||||||
|
|
||||||
|
const titleStyle = {
|
||||||
|
margin: '16px 0',
|
||||||
|
color: '#31343F'
|
||||||
|
};
|
||||||
|
|
||||||
|
interface ProxyLog {
|
||||||
|
id: string;
|
||||||
|
timestamp: number;
|
||||||
|
url: string;
|
||||||
|
proxyId: string;
|
||||||
|
proxyName: string;
|
||||||
|
status: 'success' | 'error';
|
||||||
|
errorMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const OptionsPage: React.FC = () => {
|
||||||
|
const { message } = App.useApp();
|
||||||
|
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
|
||||||
|
const [proxyLogs, setProxyLogs] = useState<ProxyLog[]>([]);
|
||||||
|
const [activeTab, setActiveTab] = useState('settings');
|
||||||
|
const [currentConfigId, setCurrentConfigId] = useState<string>('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadConfigs();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const loadConfigs = async () => {
|
||||||
|
const result = await chrome.storage.local.get('proxyConfigs');
|
||||||
|
setProxyConfigs(result.proxyConfigs || []);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAddProxy = () => {
|
||||||
|
const newConfig: ProxyConfig = {
|
||||||
|
id: Date.now().toString(),
|
||||||
|
name: '新建代理',
|
||||||
|
proxyType: 'fixed_server',
|
||||||
|
scheme: 'http',
|
||||||
|
host: '127.0.0.1',
|
||||||
|
port: 8080,
|
||||||
|
enabled: false
|
||||||
|
};
|
||||||
|
const updatedConfigs = [...proxyConfigs, newConfig];
|
||||||
|
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||||
|
setProxyConfigs(updatedConfigs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleConfigChange = (configId: string, field: keyof ProxyConfig, value: any) => {
|
||||||
|
const updatedConfigs = proxyConfigs.map(config => {
|
||||||
|
if (config.id === configId) {
|
||||||
|
return { ...config, [field]: value };
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||||
|
setProxyConfigs(updatedConfigs);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeleteProxy = (configId: string) => {
|
||||||
|
Modal.confirm({
|
||||||
|
title: '确认删除',
|
||||||
|
content: '确定要删除这个代理配置吗?',
|
||||||
|
onOk: () => {
|
||||||
|
const updatedConfigs = proxyConfigs.filter(config => config.id !== configId);
|
||||||
|
chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||||
|
setProxyConfigs(updatedConfigs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleStorageChange = (changes: StorageChanges) => {
|
||||||
|
if (changes.proxyConfigs) {
|
||||||
|
setProxyConfigs(changes.proxyConfigs.newValue || []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||||
|
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const loadLogs = async () => {
|
||||||
|
const result = await chrome.storage.local.get('proxyLogs');
|
||||||
|
setProxyLogs(result.proxyLogs || []);
|
||||||
|
};
|
||||||
|
loadLogs();
|
||||||
|
|
||||||
|
const handleStorageChange = (changes: StorageChanges) => {
|
||||||
|
if (changes.proxyLogs) {
|
||||||
|
setProxyLogs(changes.proxyLogs.newValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
chrome.storage.onChanged.addListener(handleStorageChange);
|
||||||
|
return () => chrome.storage.onChanged.removeListener(handleStorageChange);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
title: '时间',
|
||||||
|
dataIndex: 'timestamp',
|
||||||
|
key: 'timestamp',
|
||||||
|
render: (timestamp: number) => new Date(timestamp).toLocaleString()
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'URL',
|
||||||
|
dataIndex: 'url',
|
||||||
|
key: 'url',
|
||||||
|
ellipsis: true,
|
||||||
|
render: (url: string) => (
|
||||||
|
<a
|
||||||
|
href={url}
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
style={{
|
||||||
|
color: '#1890ff',
|
||||||
|
textDecoration: 'none',
|
||||||
|
maxWidth: '400px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap',
|
||||||
|
display: 'block'
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
chrome.tabs.create({ url });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{url}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '使用代理',
|
||||||
|
dataIndex: 'proxyName',
|
||||||
|
key: 'proxyName',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'status',
|
||||||
|
key: 'status',
|
||||||
|
render: (status: string) => (
|
||||||
|
<span style={{ color: status === 'success' ? '#52c41a' : '#ff4d4f' }}>
|
||||||
|
{status === 'success' ? '成功' : '失败'}
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '错误信息',
|
||||||
|
dataIndex: 'errorMessage',
|
||||||
|
key: 'errorMessage',
|
||||||
|
ellipsis: true,
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleApplyConfig = async (configId: string) => {
|
||||||
|
const config = proxyConfigs.find(c => c.id === configId);
|
||||||
|
if (config) {
|
||||||
|
try {
|
||||||
|
const response = await new Promise<any>((resolve) => {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
action: ProxyActionType.SET_PROXY_CONFIG,
|
||||||
|
config: {
|
||||||
|
...config,
|
||||||
|
scheme: config.scheme || 'http',
|
||||||
|
host: config.host || '127.0.0.1',
|
||||||
|
port: Number(config.port) || 8080,
|
||||||
|
}
|
||||||
|
}, resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response && response.success) {
|
||||||
|
const updatedConfigs = proxyConfigs.map(c => ({
|
||||||
|
...c,
|
||||||
|
enabled: c.id === configId
|
||||||
|
}));
|
||||||
|
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||||
|
setProxyConfigs(updatedConfigs);
|
||||||
|
message.success('代理设置已应用');
|
||||||
|
} else {
|
||||||
|
message.error((response && response.error) || '代理设置失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to apply proxy config:', error);
|
||||||
|
message.error('操作失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClearProxy = async (configId: string) => {
|
||||||
|
try {
|
||||||
|
const response = await new Promise<any>((resolve) => {
|
||||||
|
chrome.runtime.sendMessage({
|
||||||
|
action: ProxyActionType.CLEAR_PROXY_CONFIG
|
||||||
|
}, resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response && response.success) {
|
||||||
|
const updatedConfigs = proxyConfigs.map(c => ({
|
||||||
|
...c,
|
||||||
|
enabled: false
|
||||||
|
}));
|
||||||
|
await chrome.storage.local.set({ proxyConfigs: updatedConfigs });
|
||||||
|
setProxyConfigs(updatedConfigs);
|
||||||
|
message.success('代理已取消');
|
||||||
|
} else {
|
||||||
|
message.error((response && response.error) || '取消代理失败');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error clearing proxy:', error);
|
||||||
|
message.error('操作失败');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Layout className="options-page">
|
||||||
|
<Content style={contentStyle}>
|
||||||
|
<Tabs
|
||||||
|
activeKey={activeTab}
|
||||||
|
onChange={setActiveTab}
|
||||||
|
className="proxy-tabs"
|
||||||
|
tabBarExtraContent={{
|
||||||
|
right: (
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
icon={<PlusOutlined />}
|
||||||
|
onClick={handleAddProxy}
|
||||||
|
>
|
||||||
|
添加代理
|
||||||
|
</Button>
|
||||||
|
<Button icon={<ImportOutlined />}>导入</Button>
|
||||||
|
<Button icon={<ExportOutlined />}>导出</Button>
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
items={[
|
||||||
|
{
|
||||||
|
key: 'settings',
|
||||||
|
label: '代理设置',
|
||||||
|
children: (
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
{proxyConfigs.map(config => (
|
||||||
|
<Card
|
||||||
|
key={config.id}
|
||||||
|
size="small"
|
||||||
|
title={
|
||||||
|
<Input
|
||||||
|
placeholder="代理名称"
|
||||||
|
value={config.name}
|
||||||
|
onChange={e => handleConfigChange(config.id, 'name', e.target.value)}
|
||||||
|
disabled={config.id === 'direct'}
|
||||||
|
variant="borderless"
|
||||||
|
style={{ fontSize: '16px', padding: 0 }}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
extra={
|
||||||
|
<Space>
|
||||||
|
<Button
|
||||||
|
className="proxy-action-btn"
|
||||||
|
type={config.enabled ? "primary" : "default"}
|
||||||
|
danger={config.enabled}
|
||||||
|
onClick={() => config.enabled ?
|
||||||
|
handleClearProxy(config.id) :
|
||||||
|
handleApplyConfig(config.id)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{config.enabled ? '取消应用' : '应用选项'}
|
||||||
|
</Button>
|
||||||
|
{config.id !== 'direct' && (
|
||||||
|
<Button
|
||||||
|
danger
|
||||||
|
icon={<DeleteOutlined />}
|
||||||
|
onClick={() => handleDeleteProxy(config.id)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
}
|
||||||
|
style={{ borderRadius: '4px' }}
|
||||||
|
>
|
||||||
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
|
<Select
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
value={config.proxyType}
|
||||||
|
onChange={value => handleConfigChange(config.id, 'proxyType', value)}
|
||||||
|
disabled={config.id === 'direct'}
|
||||||
|
>
|
||||||
|
<Select.Option value="direct">直接连接</Select.Option>
|
||||||
|
<Select.Option value="fixed_server">代理服务器</Select.Option>
|
||||||
|
<Select.Option value="pac_script">PAC 脚本</Select.Option>
|
||||||
|
<Select.Option value="bypass_list">代理规则列表</Select.Option>
|
||||||
|
</Select>
|
||||||
|
|
||||||
|
{config.proxyType === 'bypass_list' && (
|
||||||
|
<TextArea
|
||||||
|
rows={4}
|
||||||
|
value={config.bypassList?.join('\n')}
|
||||||
|
onChange={e => handleConfigChange(config.id, 'bypassList', e.target.value.split('\n'))}
|
||||||
|
placeholder="每行一个规则,例如:
|
||||||
|
*.example.com
|
||||||
|
[::1]
|
||||||
|
127.0.0.1"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{config.proxyType === 'fixed_server' && (
|
||||||
|
<Space style={{ width: '100%' }}>
|
||||||
|
<Select
|
||||||
|
style={{ width: 120 }}
|
||||||
|
value={config.scheme}
|
||||||
|
onChange={value => handleConfigChange(config.id, 'scheme', value)}
|
||||||
|
>
|
||||||
|
<Select.Option value="http">HTTP</Select.Option>
|
||||||
|
<Select.Option value="https">HTTPS</Select.Option>
|
||||||
|
<Select.Option value="socks4">SOCKS4</Select.Option>
|
||||||
|
<Select.Option value="socks5">SOCKS5</Select.Option>
|
||||||
|
</Select>
|
||||||
|
<Input
|
||||||
|
placeholder="代理服务器"
|
||||||
|
value={config.host}
|
||||||
|
onChange={e => handleConfigChange(config.id, 'host', e.target.value)}
|
||||||
|
/>
|
||||||
|
<InputNumber
|
||||||
|
placeholder="端口"
|
||||||
|
value={config.port}
|
||||||
|
onChange={value => handleConfigChange(config.id, 'port', value)}
|
||||||
|
style={{ width: 100 }}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{config.proxyType === 'pac_script' && (
|
||||||
|
<TextArea
|
||||||
|
rows={4}
|
||||||
|
value={config.pacScript}
|
||||||
|
onChange={e => handleConfigChange(config.id, 'pacScript', e.target.value)}
|
||||||
|
placeholder="输入 PAC 脚本"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</Space>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'logs',
|
||||||
|
label: '代理日志',
|
||||||
|
children: (
|
||||||
|
<Table
|
||||||
|
dataSource={proxyLogs}
|
||||||
|
columns={columns}
|
||||||
|
pagination={{ pageSize: 50 }}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Content>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { createRoot } from 'react-dom/client';
|
||||||
|
import { App } from 'antd';
|
||||||
|
import { OptionsPage } from './OptionsPage';
|
||||||
|
import zhCN from 'antd/locale/zh_CN';
|
||||||
|
import '@/styles/global.css';
|
||||||
|
|
||||||
|
const container = document.getElementById('root');
|
||||||
|
const root = createRoot(container!);
|
||||||
|
|
||||||
|
root.render(
|
||||||
|
<App>
|
||||||
|
<OptionsPage />
|
||||||
|
</App>
|
||||||
|
);
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
:root {
|
||||||
|
--yakit-primary: #F28B44;
|
||||||
|
--yakit-primary-hover: #f4a061;
|
||||||
|
--yakit-primary-active: #e87633;
|
||||||
|
--yakit-primary-5: rgba(242, 139, 68, 0.05);
|
||||||
|
--yakit-primary-10: rgba(242, 139, 68, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-primary {
|
||||||
|
background-color: var(--yakit-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-primary:hover {
|
||||||
|
background-color: var(--yakit-primary-hover) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn-primary:active {
|
||||||
|
background-color: var(--yakit-primary-active) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-item-option-selected:not(.ant-select-item-option-disabled) {
|
||||||
|
background-color: var(--yakit-primary-5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-focused .ant-select-selector,
|
||||||
|
.ant-input-focused,
|
||||||
|
.ant-input:focus,
|
||||||
|
.ant-input-number-focused,
|
||||||
|
.ant-input-number:focus {
|
||||||
|
border-color: var(--yakit-primary) !important;
|
||||||
|
box-shadow: 0 0 0 2px var(--yakit-primary-10) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-btn:not(.ant-btn-primary):hover {
|
||||||
|
color: var(--yakit-primary) !important;
|
||||||
|
border-color: var(--yakit-primary) !important;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
export const ProxyActionType = {
|
||||||
|
CONNECT: "CONNECT",
|
||||||
|
SEND_MESSAGE: "SEND_MESSAGE",
|
||||||
|
DISCONNECT: "DISCONNECT",
|
||||||
|
SET_PROXY: "SET_PROXY",
|
||||||
|
CLEAR_PROXY: "CLEAR_PROXY",
|
||||||
|
PROXY_STATUS: "PROXY_STATUS",
|
||||||
|
INJECT_SCRIPT: "INJECT_SCRIPT",
|
||||||
|
BADGE_COUNT: "BADGE_COUNT",
|
||||||
|
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
|
||||||
|
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
|
||||||
|
GET_PROXY_STATUS: "GET_PROXY_STATUS"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType];
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
export interface StorageChange<T = any> {
|
||||||
|
oldValue?: T;
|
||||||
|
newValue?: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StorageChanges {
|
||||||
|
[key: string]: StorageChange;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProxyConfig {
|
||||||
|
host: string;
|
||||||
|
port: number;
|
||||||
|
scheme: 'http' | 'https' | 'socks5';
|
||||||
|
proxyType: 'fixed_server';
|
||||||
|
enabled?: boolean;
|
||||||
|
id?: string;
|
||||||
|
name?: string;
|
||||||
|
timestamp?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
export interface ProxyConfig {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
enabled: boolean;
|
||||||
|
proxyType: "direct" | "fixed_server" | "pac_script" | "auto_detect" | "bypass_list";
|
||||||
|
host?: string;
|
||||||
|
port?: number;
|
||||||
|
scheme?: "http" | "https" | "socks4" | "socks5";
|
||||||
|
pacScript?: string;
|
||||||
|
bypassList?: string[];
|
||||||
|
}
|
||||||
+5
-1
@@ -10,7 +10,11 @@
|
|||||||
"moduleResolution": "node",
|
"moduleResolution": "node",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@*": ["src/*"]
|
"@/*": ["./src/*"],
|
||||||
|
"@components/*": ["./src/components/*"],
|
||||||
|
"@assets/*": ["./src/assets/*"],
|
||||||
|
"@network/*": ["./src/network/*"],
|
||||||
|
"@types/*": ["./src/types/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
|
|||||||
+20
-7
@@ -6,10 +6,15 @@ const webpack = require('webpack');
|
|||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
mode: 'development', // 设置模式为开发模式
|
mode: 'development', // 设置模式为开发模式
|
||||||
entry: './src/index.jsx', // 指定入口文件
|
entry: {
|
||||||
|
main: './src/index.jsx',
|
||||||
|
options: './src/pages/options.tsx'
|
||||||
|
},
|
||||||
output: {
|
output: {
|
||||||
path: path.resolve(__dirname, 'build'), // 输出目录
|
path: path.resolve(__dirname, 'build'), // 输出目录
|
||||||
filename: 'bundle.js', // 输出文件名
|
filename: '[name].bundle.js', // 输出文件名
|
||||||
|
publicPath: '/',
|
||||||
|
clean: true
|
||||||
},
|
},
|
||||||
plugins: [
|
plugins: [
|
||||||
new webpack.DefinePlugin({
|
new webpack.DefinePlugin({
|
||||||
@@ -18,16 +23,22 @@ module.exports = {
|
|||||||
}),
|
}),
|
||||||
new HtmlWebpackPlugin({
|
new HtmlWebpackPlugin({
|
||||||
template: './public/index.html',
|
template: './public/index.html',
|
||||||
filename: 'index.html'
|
filename: 'index.html',
|
||||||
|
chunks: ['main']
|
||||||
|
}),
|
||||||
|
new HtmlWebpackPlugin({
|
||||||
|
template: './public/proxy/options.html',
|
||||||
|
filename: 'proxy/options.html',
|
||||||
|
chunks: ['options'],
|
||||||
|
publicPath: '../'
|
||||||
}),
|
}),
|
||||||
new CopyWebpackPlugin({
|
new CopyWebpackPlugin({
|
||||||
patterns: [
|
patterns: [
|
||||||
// copy public assets exclude index.html
|
|
||||||
{
|
{
|
||||||
from: path.resolve(__dirname, 'public'),
|
from: path.resolve(__dirname, 'public'),
|
||||||
to: path.resolve(__dirname, 'build'),
|
to: path.resolve(__dirname, 'build'),
|
||||||
globOptions: {
|
globOptions: {
|
||||||
ignore: ['**/index.html']
|
ignore: ['**/index.html', '**/proxy/options.html']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -37,8 +48,8 @@ module.exports = {
|
|||||||
module: {
|
module: {
|
||||||
rules: [
|
rules: [
|
||||||
{
|
{
|
||||||
test: /\.css$/, // 匹配所有的 css 文件
|
test: /\.css$/,
|
||||||
use: ['style-loader', 'css-loader'] // 对匹配到的文件使用这两个 loader
|
use: ['style-loader', 'css-loader']
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
test: /\.tsx?$/, // 匹配TS和TSX文件
|
test: /\.tsx?$/, // 匹配TS和TSX文件
|
||||||
@@ -60,9 +71,11 @@ module.exports = {
|
|||||||
resolve: {
|
resolve: {
|
||||||
extensions: ['.tsx', '.ts', '.js', '.jsx'], // 解析扩展(确保能够解析JS和JSX文件)
|
extensions: ['.tsx', '.ts', '.js', '.jsx'], // 解析扩展(确保能够解析JS和JSX文件)
|
||||||
alias: {
|
alias: {
|
||||||
|
'@': path.resolve(__dirname, './src'),
|
||||||
'@assets': path.resolve(__dirname, './src/assets'),
|
'@assets': path.resolve(__dirname, './src/assets'),
|
||||||
'@components': path.resolve(__dirname, './src/components'),
|
'@components': path.resolve(__dirname, './src/components'),
|
||||||
'@network': path.resolve(__dirname, './src/network'),
|
'@network': path.resolve(__dirname, './src/network'),
|
||||||
|
'@types': path.resolve(__dirname, './src/types'),
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
devtool: 'inline-source-map', // 生成内联源映射,便于调试
|
devtool: 'inline-source-map', // 生成内联源映射,便于调试
|
||||||
|
|||||||
Reference in New Issue
Block a user