fix setting pac

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent 3ad7379800
commit 9dd9d37460
10 changed files with 1109 additions and 940 deletions
+62 -78
View File
@@ -57,12 +57,25 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
const [customProxies, setCustomProxies] = useState<CustomProxy[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
// 修改存储变化监听
useEffect(() => {
const handleMessage = (message: any) => {
if (message.action === 'PROXY_CONFIGS_UPDATED' && message.source !== 'proxy_switch') {
loadCustomProxies();
}
};
chrome.runtime.onMessage.addListener(handleMessage);
return () => {
chrome.runtime.onMessage.removeListener(handleMessage);
};
}, []);
useEffect(() => {
const init = async () => {
await Promise.all([
loadProxyStatus(),
loadCustomProxies()
]);
// 修改初始化逻辑,避免并行请求
await loadProxyStatus();
await loadCustomProxies();
setInitialized(true);
};
init();
@@ -93,39 +106,6 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
const loadCustomProxies = async () => {
try {
// 首先检查当前是否在 options 页面的上下文中
const currentUrl = window.location.href;
const isInOptionsContext = currentUrl.includes('chrome-extension://') && currentUrl.includes('options.html');
if (isInOptionsContext) {
// 如果在 options 页面上下文中,直接使用消息通信
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.GET_PROXY_CONFIGS
});
if (response?.success && response.data) {
const proxies = response.data
.filter((proxy: ProxyConfig) => !FIXED_MODES.some(mode => mode.key === proxy.id))
.map((proxy: ProxyConfig): CustomProxy => ({
key: proxy.id,
name: proxy.name,
color: '#1890ff',
config: proxy,
enabled: proxy.enabled
}));
setCustomProxies(proxies);
const enabledProxy = response.data.find((proxy: ProxyConfig) => proxy.enabled);
if (enabledProxy) {
setCurrentMode(enabledProxy.id);
} else {
setCurrentMode('direct');
}
return;
}
}
// 如果不在 options 页面上下文中,使用 IndexedDB
const DB_NAME = 'yaklang_extension';
const STORE_NAME = 'proxy_configs';
@@ -165,53 +145,49 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
const enabledProxy = configs.find((proxy: ProxyConfig) => proxy.enabled);
if (enabledProxy) {
setCurrentMode(enabledProxy.id);
} else {
setCurrentMode('direct');
}
} catch (error) {
console.error('Error loading custom proxies:', error);
setCustomProxies([]);
setCurrentMode('direct');
}
};
const handleModeChange = async (mode: string) => {
if (mode === 'setting') {
await chrome.runtime.openOptionsPage?.();
return;
}
if (mode === 'add') {
try {
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
if (activeTab?.url === optionsUrl) {
chrome.tabs.sendMessage(activeTab.id!, {
action: 'TRIGGER_ADD_PROXY'
if (mode === 'setting' || mode === 'add') {
if (mode === 'setting') {
await chrome.runtime.openOptionsPage?.();
}
if (mode === 'add') {
try {
const [activeTab] = await chrome.tabs.query({
active: true,
currentWindow: true
});
} else {
const tab = await chrome.tabs.create({
url: optionsUrl
});
const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
if (tabId === tab.id && changeInfo.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);
chrome.tabs.sendMessage(tab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
}
};
const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
chrome.tabs.onUpdated.addListener(listener);
if (activeTab?.url === optionsUrl) {
chrome.tabs.sendMessage(activeTab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
} else {
const tab = await chrome.tabs.create({
url: optionsUrl
});
const listener = (tabId: number, changeInfo: chrome.tabs.TabChangeInfo) => {
if (tabId === tab.id && changeInfo.status === 'complete') {
chrome.tabs.onUpdated.removeListener(listener);
chrome.tabs.sendMessage(tab.id!, {
action: 'TRIGGER_ADD_PROXY'
});
}
};
chrome.tabs.onUpdated.addListener(listener);
}
} catch (error) {
console.error('Failed to get current tab:', error);
}
} catch (error) {
console.error('Failed to get current tab:', error);
}
return;
}
@@ -227,8 +203,8 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
return;
}
// 立即更新UI状态
setCurrentMode(mode);
if (customProxy) {
setCustomProxies(prev => prev.map(p => ({
...p,
@@ -238,16 +214,20 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.SET_PROXY_CONFIG,
config
config,
// 添加一个标志,表示这是从 ProxySwitch 发起的更改
source: 'proxy_switch'
});
if (response?.success === false) {
throw new Error(response.error || '设置代理失败');
}
await loadCustomProxies();
// 不需要重新加载,因为我们已经更新了本地状态
} catch (error) {
console.error('Error applying proxy config:', error);
// 发生错误时才重新加载以确保状态正确
await loadCustomProxies();
throw error;
} finally {
setIsLoading(false);
@@ -265,13 +245,17 @@ export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
{ type: 'divider' },
...customProxies.map(proxy => ({
key: proxy.key,
icon: <span className="menu-icon" style={{ color: proxy.color }}><GlobalOutlined /></span>,
icon: <span className="menu-icon" style={{ color: proxy.color }}>
{proxy.config.proxyType === 'pac_script' ? '📜' : <GlobalOutlined />}
</span>,
label: <span style={{
color: currentMode === proxy.key ? 'var(--yakit-primary)' : 'inherit',
opacity: isLoading ? 0.7 : 1
}}>{proxy.name}</span>,
className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
title: `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
title: proxy.config.scheme
? `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
: `${proxy.config.host}:${proxy.config.port}`
})),
{
key: 'add',
@@ -4,6 +4,7 @@ import type { ColumnsType } from 'antd/es/table';
import { DeleteOutlined, PlusOutlined, EditOutlined, CheckOutlined } from '@ant-design/icons';
import { ProxyConfig } from '@/types/proxy';
import './index.css';
import punycode from 'punycode';
interface ProxySettingsProps {
proxyConfigs: ProxyConfig[];
@@ -14,7 +15,7 @@ interface ProxySettingsProps {
onClear: (configId: string) => Promise<void>;
}
// 添加编辑表单的接口
// 修改 EditFormData 接口
interface EditFormData {
name: string;
proxyType: "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect";
@@ -22,6 +23,9 @@ interface EditFormData {
host?: string;
port?: number;
pacScript?: string;
bypassList?: string;
matchList?: string; // 仅用于 UI 编辑
proxyServer?: string; // 添加 proxyServer 字段,用于 PAC 脚本模式选择代理服务器
}
// 或者更好的方式是创建一个专门的类型
@@ -48,7 +52,11 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
scheme: editingConfig.scheme,
host: editingConfig.host,
port: editingConfig.port,
pacScript: editingConfig.pacScript
bypassList: editingConfig.bypassList?.join('\n') || '',
matchList: editingConfig.matchList?.join('\n') || '',
proxyServer: editingConfig.host && editingConfig.port
? `${editingConfig.host}:${editingConfig.port}`
: undefined
});
}
}, [editModalVisible, editingConfig, form]);
@@ -73,6 +81,17 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
setEditModalVisible(true);
};
// 添加一个函数来获取可用的代理服务器列表
const getAvailableProxies = (configs: ProxyConfig[]) => {
return configs
.filter(config => config.proxyType === 'fixed_servers')
.map(config => ({
label: `${config.name} (${config.scheme}://${config.host}:${config.port})`,
value: `${config.host}:${config.port}`,
config
}));
};
// 处理编辑保存
const handleEditSave = async () => {
try {
@@ -81,13 +100,108 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
if (editingConfig.enabled) {
await onClear(editingConfig.id);
}
const updatedConfig: ProxyConfig = {
...editingConfig,
...values,
proxyType: values.proxyType as "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect",
scheme: values.scheme as "http" | "https" | "socks4" | "socks5"
};
let updatedConfig: ProxyConfig;
if (values.proxyType === 'fixed_servers') {
// 处理固定代理服务器模式
const bypassList = values.bypassList
? values.bypassList.split('\n').map(line => line.trim()).filter(line => line.length > 0)
: ["localhost", "127.0.0.1"];
updatedConfig = {
id: editingConfig.id,
name: values.name,
enabled: editingConfig.enabled,
proxyType: 'fixed_servers',
scheme: values.scheme,
host: values.host,
port: values.port,
bypassList,
};
} else if (values.proxyType === 'pac_script') {
const domains = values.matchList
? values.matchList.split('\n')
.map(line => line.trim())
.filter(line => line.length > 0)
.map(domain => {
try {
// 如果域名包含非 ASCII 字符,转换为 Punycode
if (/[^\x00-\x7F]/.test(domain)) {
if (domain.startsWith('*.')) {
const suffix = domain.substring(2);
return '*.' + suffix.split('.').map(part => {
return /[^\x00-\x7F]/.test(part) ? 'xn--' + punycode.encode(part) : part;
}).join('.');
} else {
return domain.split('.').map(part => {
return /[^\x00-\x7F]/.test(part) ? 'xn--' + punycode.encode(part) : part;
}).join('.');
}
}
return domain;
} catch (error) {
console.error('Error encoding domain:', domain, error);
return domain;
}
})
: [];
// 从选择的代理服务器中获取配置
const [host, port] = values.proxyServer.split(':');
// 生成 PAC 脚本
const pacScriptContent = `
function FindProxyForURL(url, host) {
// Convert host to lowercase for case-insensitive matching
host = host.toLowerCase();
// Define domain patterns
var domains = ${JSON.stringify(domains)};
// Check each domain pattern
for (var i = 0; i < domains.length; i++) {
var pattern = domains[i].toLowerCase();
if (pattern.startsWith('*.')) {
var suffix = pattern.substring(2);
if (host === suffix || host.endsWith('.' + suffix)) {
return 'PROXY ${host}:${port}';
}
} else if (host === pattern) {
return 'PROXY ${host}:${port}';
}
}
return 'DIRECT';
}`;
updatedConfig = {
id: editingConfig.id,
name: values.name,
enabled: editingConfig.enabled,
proxyType: 'pac_script',
mode: 'pac_script',
// 保存代理服务器信息
host,
port: parseInt(port),
// 保存匹配域名列表
matchList: domains,
pacScript: {
data: pacScriptContent,
mandatory: true
}
};
} else {
// 处理其他模式
updatedConfig = {
id: editingConfig.id,
name: values.name,
enabled: editingConfig.enabled,
proxyType: values.proxyType,
bypassList: [], // 其他模式下设置为空数组
};
}
if (!proxyConfigs.find(config => config.id === editingConfig.id)) {
await onAdd(updatedConfig);
@@ -232,7 +346,7 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
>
<Select
options={[
{ label: '直接连接', value: 'direct' },
// { label: '直接连接', value: 'direct' },
{ label: '代理服务器', value: 'fixed_servers' },
{ label: 'PAC 脚本', value: 'pac_script' }
]}
@@ -254,51 +368,90 @@ export const ProxySettings: React.FC<ProxySettingsProps> = ({
>
{({ getFieldValue }) => {
const proxyType = getFieldValue('proxyType');
return proxyType === 'fixed_servers' ? (
<>
<Form.Item
name="scheme"
label="协议"
rules={[{ required: true, message: '请选择协议' }]}
>
<Select
options={[
{ label: 'HTTP', value: 'http' },
{ label: 'HTTPS', value: 'https' },
{ label: 'SOCKS4', value: 'socks4' },
{ label: 'SOCKS5', value: 'socks5' }
]}
/>
</Form.Item>
<Form.Item
name="host"
label="主机"
rules={[{ required: true, message: '请输入主机地址' }]}
>
<Input placeholder="127.0.0.1" />
</Form.Item>
<Form.Item
name="port"
label="端口"
rules={[{ required: true, message: '请输入端口号' }]}
>
<InputNumber
min={1}
max={65535}
placeholder="8080"
style={{ width: '100%' }}
/>
</Form.Item>
</>
) : proxyType === 'pac_script' ? (
<Form.Item
name="pacScript"
label="PAC 脚本"
rules={[{ required: true, message: '请输入 PAC 脚本' }]}
>
<Input.TextArea rows={4} />
</Form.Item>
) : null;
if (proxyType === 'fixed_servers') {
return (
<>
<Form.Item
name="scheme"
label="协议"
rules={[{ required: true, message: '请选择协议' }]}
>
<Select
options={[
{ label: 'HTTP', value: 'http' },
{ label: 'HTTPS', value: 'https' },
{ label: 'SOCKS4', value: 'socks4' },
{ label: 'SOCKS5', value: 'socks5' }
]}
/>
</Form.Item>
<Form.Item
name="host"
label="主机"
rules={[{ required: true, message: '请输入主机地址' }]}
>
<Input placeholder="127.0.0.1" />
</Form.Item>
<Form.Item
name="port"
label="端口"
rules={[{ required: true, message: '请输入端口号' }]}
>
<InputNumber
min={1}
max={65535}
placeholder="8080"
style={{ width: '100%' }}
/>
</Form.Item>
<Form.Item
name="bypassList"
label="不经过代理的地址"
help="每行一个地址,支持通配符 *"
>
<Input.TextArea
rows={4}
placeholder={`例如:
localhost
127.0.0.1
*.example.com`}
/>
</Form.Item>
</>
);
} else if (proxyType === 'pac_script') {
const availableProxies = getAvailableProxies(proxyConfigs);
return (
<>
<Form.Item
name="matchList"
label="匹配域名"
help="每行一个域名,支持通配符 *"
rules={[{ required: true, message: '请输入至少一个匹配域名' }]}
>
<Input.TextArea
rows={4}
placeholder={`例如:
*.example.com
google.com
github.com`}
/>
</Form.Item>
<Form.Item
name="proxyServer"
label="选择代理服务器"
rules={[{ required: true, message: '请选择代理服务器' }]}
>
<Select
placeholder="选择一个代理服务器"
options={availableProxies}
/>
</Form.Item>
</>
);
}
return null;
}}
</Form.Item>
</Form>
+12 -22
View File
@@ -9,19 +9,16 @@ export const useProxyConfigs = () => {
useEffect(() => {
loadConfigs();
// 监听配置更新
const handleConfigUpdate = () => {
loadConfigs();
const messageListener = (message: any) => {
if (message.action === 'PROXY_CONFIGS_UPDATED') {
loadConfigs();
}
};
chrome.runtime.onMessage.addListener((message) => {
if (message.action === 'PROXY_CONFIGS_UPDATED') {
handleConfigUpdate();
}
});
chrome.runtime.onMessage.addListener(messageListener);
return () => {
chrome.runtime.onMessage.removeListener(handleConfigUpdate);
chrome.runtime.onMessage.removeListener(messageListener);
};
}, []);
@@ -70,6 +67,9 @@ export const useProxyConfigs = () => {
if (response?.success) {
setProxyConfigs(response.data || updatedConfigs);
await chrome.runtime.sendMessage({
action: 'PROXY_CONFIGS_UPDATED'
});
message.success('更新配置成功');
} else {
message.error(response?.error || '更新配置失败');
@@ -88,7 +88,6 @@ export const useProxyConfigs = () => {
const updatedConfigs = proxyConfigs.filter(config => config.id !== configId);
console.log('Updated configs after delete:', updatedConfigs);
// 使用 Promise 包装消息发送
const response = await new Promise<any>((resolve) => {
chrome.runtime.sendMessage({
action: ProxyActionType.UPDATE_PROXY_CONFIG,
@@ -121,24 +120,15 @@ export const useProxyConfigs = () => {
const config = proxyConfigs.find(c => c.id === configId);
if (!config) return;
// 先取消当前启用的代理
const currentEnabled = proxyConfigs.find(c => c.enabled);
if (currentEnabled && currentEnabled.id !== configId) {
// 如果当前启用的不是直接连接,需要先清除代理设置
if (currentEnabled.id !== 'direct') {
await chrome.runtime.sendMessage({
action: ProxyActionType.CLEAR_PROXY_CONFIG
});
}
}
// 应用新的代理设置
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.SET_PROXY_CONFIG,
config: config
});
if (response.success) {
await chrome.runtime.sendMessage({
action: 'PROXY_STATUS_CHANGED'
});
message.success('代理设置已应用');
} else {
message.error(response.error || '应用代理设置失败');
+10 -1
View File
@@ -1,12 +1,21 @@
export interface PacScript {
data?: string;
url?: string;
mandatory?: boolean;
}
export interface ProxyConfig {
id: string;
name: string;
enabled: boolean;
proxyType: "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect";
mode?: string;
host?: string;
port?: number;
scheme?: "http" | "https" | "socks4" | "socks5";
pacScript?: string;
pacScript?: PacScript;
bypassList?: string[];
matchList?: string[];
}
export interface ProxyLog {