add proxy switch demo

This commit is contained in:
go0p
2025-03-04 16:07:14 +08:00
parent 692ab7760e
commit 8c30607514
28 changed files with 12458 additions and 28 deletions
+160
View File
@@ -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;
}
+11
View File
@@ -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>
+170
View File
@@ -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);
}
// ... 其他代码保持不变
+98
View File
@@ -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;
}
}
}
+87
View File
@@ -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;
}
}
}
+41
View File
@@ -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: [] });
}
}
};