fix pac script

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent 9ce77a2c16
commit 2de927a60f
2 changed files with 260 additions and 159 deletions
+27 -15
View File
@@ -214,21 +214,33 @@ export const ProxySwitch: React.FC = () => {
// 添加自定义代理
if (customProxies.length > 0) {
items.push(
...customProxies.map(proxy => ({
key: proxy.key,
label: proxy.name,
icon: <img
src={YAK_ICON_URL}
alt="YAK"
style={{
width: 20,
height: 20,
filter: currentMode === proxy.key ? 'brightness(0) invert(1)' : 'none',
transition: 'filter 0.2s ease-in-out'
}}
/>,
className: `${currentMode === proxy.key ? 'active-item' : ''} menu-id-${proxy.key}`,
}))
...customProxies.map(proxy => {
// 构建提示信息:显示代理协议、主机和端口
const tooltipText = proxy.config.proxyType === 'fixed_servers' && proxy.config.host && proxy.config.port
? `${proxy.config.scheme || 'http'}://${proxy.config.host}:${proxy.config.port}`
: proxy.config.proxyType === 'pac_script'
? 'PAC脚本代理'
: proxy.config.proxyType === 'auto_detect'
? '自动检测代理'
: '';
return {
key: proxy.key,
label: proxy.name,
icon: <img
src={YAK_ICON_URL}
alt="YAK"
style={{
width: 20,
height: 20,
filter: currentMode === proxy.key ? 'brightness(0) invert(1)' : 'none',
transition: 'filter 0.2s ease-in-out'
}}
/>,
className: `${currentMode === proxy.key ? 'active-item' : ''} menu-id-${proxy.key}`,
title: tooltipText, // 添加悬停提示
};
})
);
items.push({type: 'divider'});
}
+233 -144
View File
@@ -1,12 +1,31 @@
import React, { useState, useEffect } from 'react';
import { ConfigProvider, Layout, Typography, List, Button, Form, Input, Select, Space, Card, Divider, Modal } from 'antd';
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons';
import { v4 as uuidv4 } from 'uuid';
import { browser } from 'wxt/browser';
import type { ProxyConfig } from '../../types/proxy';
import { getAllProxyConfigs, saveProxyConfig, deleteProxyConfig, enableProxyConfig, getCurrentProxy } from '../../utils/storage';
import {ContentActionType, ProxyActionType} from '../../types/action';
import './App.css';
import React, { useState, useEffect } from "react";
import {
ConfigProvider,
Layout,
Typography,
List,
Button,
Form,
Input,
Select,
Space,
Card,
Divider,
Modal,
} from "antd";
import { PlusOutlined, DeleteOutlined } from "@ant-design/icons";
import { v4 as uuidv4 } from "uuid";
import { browser } from "wxt/browser";
import type { ProxyConfig } from "../../types/proxy";
import {
getAllProxyConfigs,
saveProxyConfig,
deleteProxyConfig,
enableProxyConfig,
getCurrentProxy,
} from "../../utils/storage";
import { ContentActionType, ProxyActionType } from "../../types/action";
import "./App.css";
const { Header, Content } = Layout;
const { Title } = Typography;
@@ -30,7 +49,7 @@ export default function App() {
loadProxies();
}
};
browser.runtime.onMessage.addListener(handleMessage);
return () => {
browser.runtime.onMessage.removeListener(handleMessage);
@@ -41,116 +60,135 @@ export default function App() {
try {
// 获取所有代理配置
const allConfigs = await getAllProxyConfigs();
// 获取当前启用的代理
const currentProxy = await getCurrentProxy();
const currentProxyId = currentProxy?.id || '';
const currentProxyId = currentProxy?.id || "";
// 检查当前是否为系统代理或直接连接
const isSystemOrDirect = currentProxyId === 'system' || currentProxyId === 'direct';
// 只显示自定义代理服务器配置
const customProxies = allConfigs.filter(config => config.proxyType === "fixed_servers");
const isSystemOrDirect =
currentProxyId === "system" || currentProxyId === "direct";
// 显示自定义代理服务器配置和PAC脚本配置
const customProxies = allConfigs.filter(
(config) =>
config.proxyType === "fixed_servers" ||
config.proxyType === "pac_script"
);
// 如果当前是系统代理或直接连接,则所有自定义代理显示为未启用
if (isSystemOrDirect) {
console.log('系统代理或直接连接已启用,确保自定义代理状态正确');
console.log("系统代理或直接连接已启用,确保自定义代理状态正确");
// 确保UI状态与实际状态一致
setProxies(customProxies.map(proxy => ({
...proxy,
enabled: false
})));
setProxies(
customProxies.map((proxy) => ({
...proxy,
enabled: false,
}))
);
} else {
setProxies(customProxies);
}
} catch (error) {
console.error('Error loading proxies:', error);
console.error("Error loading proxies:", error);
}
};
const handleSave = async (values: any) => {
try {
setLoading(true);
const newProxy: Partial<ProxyConfig> = {
id: uuidv4(),
name: values.name,
enabled: false
enabled: false,
};
if (values.proxyType === 'fixed_servers') {
newProxy.proxyType = 'fixed_servers';
if (values.proxyType === "fixed_servers") {
newProxy.proxyType = "fixed_servers";
newProxy.scheme = values.scheme;
newProxy.host = values.host;
newProxy.port = Number(values.port);
// 处理不经过代理的地址
if (values.bypassList) {
newProxy.bypassList = values.bypassList
.split('\n')
.split("\n")
.map((line: string) => line.trim())
.filter((line: string) => line.length > 0);
} else {
newProxy.bypassList = [];
}
} else if (values.proxyType === 'pac_script') {
newProxy.proxyType = 'pac_script';
newProxy.mode = 'pac_script';
} else if (values.proxyType === "pac_script") {
newProxy.proxyType = "pac_script";
newProxy.mode = "pac_script";
// 处理PAC脚本匹配域名
if (values.matchList) {
newProxy.matchList = values.matchList
.split('\n')
.split("\n")
.map((line: string) => line.trim())
.filter((line: string) => line.length > 0);
}
// 解析选择的代理服务器
const [host, port] = values.proxyServer.split(":");
// 创建PAC脚本
newProxy.pacScript = {
data: `function FindProxyForURL(url, host) {
// 匹配域名列表
const domains = ${JSON.stringify(newProxy.matchList || [])};
// Convert host to lowercase for case-insensitive matching
host = host.toLowerCase();
// 检查是否匹配任何域名
for (let i = 0; i < domains.length; i++) {
const domain = domains[i];
// 支持通配符
if (domain.startsWith('*.') && host.endsWith(domain.substring(1))) {
return 'PROXY ${values.host}:${values.port}';
} else if (host === domain) {
return 'PROXY ${values.host}:${values.port}';
// Define domain patterns
var domains = ${JSON.stringify(newProxy.matchList || [])};
// 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';
}`,
mandatory: true
mandatory: true,
};
// 保存代理服务器信息
newProxy.host = host;
newProxy.port = Number(port);
}
// 添加认证信息
if (values.username) newProxy.username = values.username;
if (values.password) newProxy.password = values.password;
await saveProxyConfig(newProxy as ProxyConfig);
// 重新加载代理列表
await loadProxies();
// 重置表单
form.resetFields();
// 关闭模态框
setIsModalOpen(false);
// 通知后台脚本
browser.runtime.sendMessage({
action: ContentActionType.PROXY_CONFIGS_UPDATED,
source: 'options'
source: "options",
});
} catch (error) {
console.error('Error saving proxy:', error);
console.error("Error saving proxy:", error);
} finally {
setLoading(false);
}
@@ -160,11 +198,11 @@ export default function App() {
try {
await deleteProxyConfig(id);
await loadProxies();
// 通知后台脚本
browser.runtime.sendMessage({
action: ContentActionType.PROXY_CONFIGS_UPDATED,
source: 'options'
source: "options",
});
} catch (error) {
console.error(`Error deleting proxy ${id}:`, error);
@@ -175,20 +213,20 @@ export default function App() {
try {
// 启用代理
await enableProxyConfig(id);
// 发送切换代理请求
await browser.runtime.sendMessage({
action: ProxyActionType.SWITCH_PROXY,
mode: id
mode: id,
});
// 重新加载代理列表
await loadProxies();
// 通知后台脚本
browser.runtime.sendMessage({
action: ContentActionType.PROXY_CONFIGS_UPDATED,
source: 'options'
source: "options",
});
} catch (error) {
console.error(`Error activating proxy ${id}:`, error);
@@ -214,23 +252,25 @@ export default function App() {
>
<Layout className="options-layout">
<Header className="options-header">
<Title level={3} style={{ color: 'white', margin: 0 }}>Yaklang </Title>
<Title level={3} style={{ color: "white", margin: 0 }}>
Yaklang
</Title>
</Header>
<Content className="options-content">
<Card
className="proxy-list-card"
<Card
className="proxy-list-card"
title="代理列表"
extra={
<Button
type="primary"
<Button
type="primary"
icon={<PlusOutlined />}
onClick={showModal}
size="small"
style={{
backgroundColor: "#F28B44",
style={{
backgroundColor: "#F28B44",
borderColor: "#F28B44",
borderRadius: "4px",
fontSize: "13px"
fontSize: "13px",
}}
>
@@ -241,44 +281,52 @@ export default function App() {
dataSource={proxies}
locale={{
emptyText: (
<div style={{ padding: '32px 0', textAlign: 'center' }}>
<div style={{ padding: "32px 0", textAlign: "center" }}>
<div style={{ marginBottom: 16 }}>
<img
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUzLjMzMzMgMzJWNDhDNTMuMzMzMyA0OS40MTc0IDUyLjc3MTQgNTAuNzY1MiA1MS43NzEyIDUxLjc2NTJDNTAuNzcxIDUyLjc2NTIgNDkuNDIzMyA1My4zMzMzIDQ4IDUzLjMzMzNIMTZDMTQuNTc2NyA1My4zMzMzIDEzLjIyODkgNTIuNzcxNCAxMi4yMjg4IDUxLjc3MTJDMTEuMjI4OCA1MC43NzEgMTAuNjY2NyA0OS40MjMzIDEwLjY2NjcgNDhWMTZDMTAuNjY2NyAxNC41NzY3IDExLjIyODggMTMuMjI4OSAxMi4yMjg4IDEyLjIyODhDMTMuMjI4OSAxMS4yMjg4IDE0LjU3NjcgMTAuNjY2NyAxNiAxMC42NjY3SDMyIiBzdHJva2U9IiM1QTVBNUEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMiAzMkg1My4zMzMzVjQyLjY2NjciIHN0cm9rZT0iI0YyOEI0NCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg=="
alt="No data"
<img
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUzLjMzMzMgMzJWNDhDNTMuMzMzMyA0OS40MTc0IDUyLjc3MTQgNTAuNzY1MiA1MS43NzEyIDUxLjc2NTJDNTAuNzcxIDUyLjc2NTIgNDkuNDIzMyA1My4zMzMzIDQ4IDUzLjMzMzNIMTZDMTQuNTc2NyA1My4zMzMzIDEzLjIyODkgNTIuNzcxNCAxMi4yMjg4IDUxLjc3MTJDMTEuMjI4OCA1MC43NzEgMTAuNjY2NyA0OS40MjMzIDEwLjY2NjcgNDhWMTZDMTAuNjY2NyAxNC41NzY3IDExLjIyODggMTMuMjI4OSAxMi4yMjg4IDEyLjIyODhDMTMuMjI4OSAxMS4yMjg4IDE0LjU3NjcgMTAuNjY2NyAxNiAxMC42NjY3SDMyIiBzdHJva2U9IiM1QTVBNUEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMiAzMkg1My4zMzMzVjQyLjY2NjciIHN0cm9rZT0iI0YyOEI0NCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg=="
alt="No data"
style={{ width: 64, height: 64, opacity: 0.5 }}
/>
</div>
<p style={{ color: '#5A5A5A' }}></p>
<p style={{ color: "#5A5A5A" }}></p>
</div>
)
),
}}
renderItem={(proxy) => (
<List.Item
key={proxy.id}
actions={[
<Button
<Button
type={proxy.enabled ? "default" : "primary"}
onClick={() => handleActivate(proxy.id)}
disabled={proxy.enabled}
style={proxy.enabled ? {
backgroundColor: "#F28B44",
color: "white",
borderColor: "#F28B44"
} : undefined}
style={
proxy.enabled
? {
backgroundColor: "#F28B44",
color: "white",
borderColor: "#F28B44",
}
: undefined
}
>
{proxy.enabled ? '已启用' : '启用'}
{proxy.enabled ? "已启用" : "启用"}
</Button>,
<Button
danger
<Button
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(proxy.id)}
/>
/>,
]}
>
<List.Item.Meta
title={proxy.name}
description={`${proxy.scheme}://${proxy.host}:${proxy.port}`}
description={
proxy.proxyType === "pac_script"
? `PAC 脚本 (使用 ${proxy.host}:${proxy.port} 作为代理)`
: `${proxy.scheme}://${proxy.host}:${proxy.port}`
}
/>
</List.Item>
)}
@@ -289,62 +337,62 @@ export default function App() {
<Modal
title="添加新代理"
open={isModalOpen}
className='add-proxy-card'
className="add-proxy-card"
onCancel={handleCancel}
footer={[
<Button key="cancel" onClick={handleCancel}>
Cancel
</Button>,
<Button
key="submit"
type="primary"
<Button
key="submit"
type="primary"
onClick={() => form.submit()}
loading={loading}
>
OK
</Button>
</Button>,
]}
destroyOnClose
>
<Form
form={form}
layout="vertical"
onFinish={handleSave}
>
<Form form={form} layout="vertical" onFinish={handleSave}>
<Form.Item
name="name"
label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入代理名称' }]}
rules={[{ required: true, message: "请输入代理名称" }]}
>
<Input placeholder="为此代理添加一个名称" />
</Form.Item>
<Form.Item
name="proxyType"
label={<span className="required-label"></span>}
initialValue="fixed_servers"
rules={[{ required: true, message: '请选择代理类型' }]}
rules={[{ required: true, message: "请选择代理类型" }]}
>
<Select>
<Option value="fixed_servers"></Option>
<Option value="pac_script">PAC </Option>
</Select>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}
shouldUpdate={(prevValues, currentValues) =>
prevValues.proxyType !== currentValues.proxyType
}
>
{({ getFieldValue }) => {
const proxyType = getFieldValue('proxyType');
if (proxyType === 'fixed_servers') {
const proxyType = getFieldValue("proxyType");
if (proxyType === "fixed_servers") {
return (
<>
<Form.Item
name="scheme"
label={<span className="required-label"></span>}
initialValue="http"
rules={[{ required: true, message: '请选择代理协议' }]}
rules={[
{ required: true, message: "请选择代理协议" },
]}
>
<Select>
<Option value="http">HTTP</Option>
@@ -353,54 +401,113 @@ export default function App() {
<Option value="socks5">SOCKS5</Option>
</Select>
</Form.Item>
<Form.Item
name="host"
label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入主机地址' }]}
rules={[
{ required: true, message: "请输入主机地址" },
]}
>
<Input placeholder="127.0.0.1" />
</Form.Item>
<Form.Item
name="port"
label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入端口' }]}
rules={[{ required: true, message: "请输入端口" }]}
>
<Input placeholder="8080" />
</Form.Item>
<Form.Item
name="bypassList"
label="不经过代理的地址"
>
<Input.TextArea
rows={4}
<Form.Item name="bypassList" label="不经过代理的地址">
<Input.TextArea
rows={4}
placeholder={`例如:
localhost
127.0.0.1
*.example.com`}
/>
<div style={{ marginTop: 8, fontSize: 12, color: '#999' }}> *</div>
<div
style={{
marginTop: 8,
fontSize: 12,
color: "#999",
}}
>
*
</div>
</Form.Item>
<Space style={{ display: "flex" }}>
<Form.Item
name="username"
label="用户名 (可选)"
style={{ flex: 1 }}
>
<Input placeholder="认证用户名" />
</Form.Item>
<Form.Item
name="password"
label="密码 (可选)"
style={{ flex: 1 }}
>
<Input.Password placeholder="认证密码" />
</Form.Item>
</Space>
</>
);
} else if (proxyType === 'pac_script') {
} else if (proxyType === "pac_script") {
return (
<>
<Form.Item
name="matchList"
label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入至少一个匹配域名' }]}
label="匹配域名"
help="每行一个域名,支持通配符 *"
rules={[
{
required: true,
message: "请输入至少一个匹配域名",
},
]}
>
<Input.TextArea
rows={4}
<Input.TextArea
rows={4}
placeholder={`例如:
*.example.com
google.com
github.com`}
/>
<div style={{ marginTop: 8, fontSize: 12, color: '#999' }}> *</div>
</Form.Item>
<Form.Item
name="proxyServer"
label={
<span className="required-label">
</span>
}
rules={[
{ required: true, message: "请选择代理服务器" },
]}
>
<Select placeholder="选择一个代理服务器">
{/* 获取已配置的固定代理服务器列表 */}
{proxies
.filter(
(proxy) => proxy.proxyType === "fixed_servers"
)
.map((proxy) => (
<Option
key={proxy.id}
value={`${proxy.host}:${proxy.port}`}
>
{proxy.name} ({proxy.scheme}://{proxy.host}:
{proxy.port})
</Option>
))}
</Select>
</Form.Item>
</>
);
@@ -408,28 +515,10 @@ github.com`}
return null;
}}
</Form.Item>
<Space style={{ display: 'flex' }}>
<Form.Item
name="username"
label="用户名 (可选)"
style={{ flex: 1 }}
>
<Input placeholder="认证用户名" />
</Form.Item>
<Form.Item
name="password"
label="密码 (可选)"
style={{ flex: 1 }}
>
<Input.Password placeholder="认证密码" />
</Form.Item>
</Space>
</Form>
</Modal>
</Content>
</Layout>
</ConfigProvider>
);
}
}