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) { if (customProxies.length > 0) {
items.push( items.push(
...customProxies.map(proxy => ({ ...customProxies.map(proxy => {
key: proxy.key, // 构建提示信息:显示代理协议、主机和端口
label: proxy.name, const tooltipText = proxy.config.proxyType === 'fixed_servers' && proxy.config.host && proxy.config.port
icon: <img ? `${proxy.config.scheme || 'http'}://${proxy.config.host}:${proxy.config.port}`
src={YAK_ICON_URL} : proxy.config.proxyType === 'pac_script'
alt="YAK" ? 'PAC脚本代理'
style={{ : proxy.config.proxyType === 'auto_detect'
width: 20, ? '自动检测代理'
height: 20, : '';
filter: currentMode === proxy.key ? 'brightness(0) invert(1)' : 'none',
transition: 'filter 0.2s ease-in-out' return {
}} key: proxy.key,
/>, label: proxy.name,
className: `${currentMode === proxy.key ? 'active-item' : ''} menu-id-${proxy.key}`, 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'}); items.push({type: 'divider'});
} }
+188 -99
View File
@@ -1,12 +1,31 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from "react";
import { ConfigProvider, Layout, Typography, List, Button, Form, Input, Select, Space, Card, Divider, Modal } from 'antd'; import {
import { PlusOutlined, DeleteOutlined } from '@ant-design/icons'; ConfigProvider,
import { v4 as uuidv4 } from 'uuid'; Layout,
import { browser } from 'wxt/browser'; Typography,
import type { ProxyConfig } from '../../types/proxy'; List,
import { getAllProxyConfigs, saveProxyConfig, deleteProxyConfig, enableProxyConfig, getCurrentProxy } from '../../utils/storage'; Button,
import {ContentActionType, ProxyActionType} from '../../types/action'; Form,
import './App.css'; 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 { Header, Content } = Layout;
const { Title } = Typography; const { Title } = Typography;
@@ -44,27 +63,34 @@ export default function App() {
// 获取当前启用的代理 // 获取当前启用的代理
const currentProxy = await getCurrentProxy(); const currentProxy = await getCurrentProxy();
const currentProxyId = currentProxy?.id || ''; const currentProxyId = currentProxy?.id || "";
// 检查当前是否为系统代理或直接连接 // 检查当前是否为系统代理或直接连接
const isSystemOrDirect = currentProxyId === 'system' || currentProxyId === 'direct'; const isSystemOrDirect =
currentProxyId === "system" || currentProxyId === "direct";
// 显示自定义代理服务器配置 // 显示自定义代理服务器配置和PAC脚本配置
const customProxies = allConfigs.filter(config => config.proxyType === "fixed_servers"); const customProxies = allConfigs.filter(
(config) =>
config.proxyType === "fixed_servers" ||
config.proxyType === "pac_script"
);
// 如果当前是系统代理或直接连接,则所有自定义代理显示为未启用 // 如果当前是系统代理或直接连接,则所有自定义代理显示为未启用
if (isSystemOrDirect) { if (isSystemOrDirect) {
console.log('系统代理或直接连接已启用,确保自定义代理状态正确'); console.log("系统代理或直接连接已启用,确保自定义代理状态正确");
// 确保UI状态与实际状态一致 // 确保UI状态与实际状态一致
setProxies(customProxies.map(proxy => ({ setProxies(
...proxy, customProxies.map((proxy) => ({
enabled: false ...proxy,
}))); enabled: false,
}))
);
} else { } else {
setProxies(customProxies); setProxies(customProxies);
} }
} catch (error) { } catch (error) {
console.error('Error loading proxies:', error); console.error("Error loading proxies:", error);
} }
}; };
@@ -75,11 +101,11 @@ export default function App() {
const newProxy: Partial<ProxyConfig> = { const newProxy: Partial<ProxyConfig> = {
id: uuidv4(), id: uuidv4(),
name: values.name, name: values.name,
enabled: false enabled: false,
}; };
if (values.proxyType === 'fixed_servers') { if (values.proxyType === "fixed_servers") {
newProxy.proxyType = 'fixed_servers'; newProxy.proxyType = "fixed_servers";
newProxy.scheme = values.scheme; newProxy.scheme = values.scheme;
newProxy.host = values.host; newProxy.host = values.host;
newProxy.port = Number(values.port); newProxy.port = Number(values.port);
@@ -87,46 +113,58 @@ export default function App() {
// 处理不经过代理的地址 // 处理不经过代理的地址
if (values.bypassList) { if (values.bypassList) {
newProxy.bypassList = values.bypassList newProxy.bypassList = values.bypassList
.split('\n') .split("\n")
.map((line: string) => line.trim()) .map((line: string) => line.trim())
.filter((line: string) => line.length > 0); .filter((line: string) => line.length > 0);
} else { } else {
newProxy.bypassList = []; newProxy.bypassList = [];
} }
} else if (values.proxyType === 'pac_script') { } else if (values.proxyType === "pac_script") {
newProxy.proxyType = 'pac_script'; newProxy.proxyType = "pac_script";
newProxy.mode = 'pac_script'; newProxy.mode = "pac_script";
// 处理PAC脚本匹配域名 // 处理PAC脚本匹配域名
if (values.matchList) { if (values.matchList) {
newProxy.matchList = values.matchList newProxy.matchList = values.matchList
.split('\n') .split("\n")
.map((line: string) => line.trim()) .map((line: string) => line.trim())
.filter((line: string) => line.length > 0); .filter((line: string) => line.length > 0);
} }
// 解析选择的代理服务器
const [host, port] = values.proxyServer.split(":");
// 创建PAC脚本 // 创建PAC脚本
newProxy.pacScript = { newProxy.pacScript = {
data: `function FindProxyForURL(url, host) { data: `function FindProxyForURL(url, host) {
// 匹配域名列表 // Convert host to lowercase for case-insensitive matching
const domains = ${JSON.stringify(newProxy.matchList || [])}; host = host.toLowerCase();
// 检查是否匹配任何域名 // Define domain patterns
for (let i = 0; i < domains.length; i++) { var domains = ${JSON.stringify(newProxy.matchList || [])};
const domain = domains[i];
// 支持通配符 // Check each domain pattern
if (domain.startsWith('*.') && host.endsWith(domain.substring(1))) { for (var i = 0; i < domains.length; i++) {
return 'PROXY ${values.host}:${values.port}'; var pattern = domains[i].toLowerCase();
} else if (host === domain) {
return 'PROXY ${values.host}:${values.port}'; 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'; return 'DIRECT';
}`, }`,
mandatory: true mandatory: true,
}; };
// 保存代理服务器信息
newProxy.host = host;
newProxy.port = Number(port);
} }
// 添加认证信息 // 添加认证信息
@@ -147,10 +185,10 @@ export default function App() {
// 通知后台脚本 // 通知后台脚本
browser.runtime.sendMessage({ browser.runtime.sendMessage({
action: ContentActionType.PROXY_CONFIGS_UPDATED, action: ContentActionType.PROXY_CONFIGS_UPDATED,
source: 'options' source: "options",
}); });
} catch (error) { } catch (error) {
console.error('Error saving proxy:', error); console.error("Error saving proxy:", error);
} finally { } finally {
setLoading(false); setLoading(false);
} }
@@ -164,7 +202,7 @@ export default function App() {
// 通知后台脚本 // 通知后台脚本
browser.runtime.sendMessage({ browser.runtime.sendMessage({
action: ContentActionType.PROXY_CONFIGS_UPDATED, action: ContentActionType.PROXY_CONFIGS_UPDATED,
source: 'options' source: "options",
}); });
} catch (error) { } catch (error) {
console.error(`Error deleting proxy ${id}:`, error); console.error(`Error deleting proxy ${id}:`, error);
@@ -179,7 +217,7 @@ export default function App() {
// 发送切换代理请求 // 发送切换代理请求
await browser.runtime.sendMessage({ await browser.runtime.sendMessage({
action: ProxyActionType.SWITCH_PROXY, action: ProxyActionType.SWITCH_PROXY,
mode: id mode: id,
}); });
// 重新加载代理列表 // 重新加载代理列表
@@ -188,7 +226,7 @@ export default function App() {
// 通知后台脚本 // 通知后台脚本
browser.runtime.sendMessage({ browser.runtime.sendMessage({
action: ContentActionType.PROXY_CONFIGS_UPDATED, action: ContentActionType.PROXY_CONFIGS_UPDATED,
source: 'options' source: "options",
}); });
} catch (error) { } catch (error) {
console.error(`Error activating proxy ${id}:`, error); console.error(`Error activating proxy ${id}:`, error);
@@ -214,7 +252,9 @@ export default function App() {
> >
<Layout className="options-layout"> <Layout className="options-layout">
<Header className="options-header"> <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> </Header>
<Content className="options-content"> <Content className="options-content">
<Card <Card
@@ -230,7 +270,7 @@ export default function App() {
backgroundColor: "#F28B44", backgroundColor: "#F28B44",
borderColor: "#F28B44", borderColor: "#F28B44",
borderRadius: "4px", borderRadius: "4px",
fontSize: "13px" fontSize: "13px",
}} }}
> >
@@ -241,7 +281,7 @@ export default function App() {
dataSource={proxies} dataSource={proxies}
locale={{ locale={{
emptyText: ( emptyText: (
<div style={{ padding: '32px 0', textAlign: 'center' }}> <div style={{ padding: "32px 0", textAlign: "center" }}>
<div style={{ marginBottom: 16 }}> <div style={{ marginBottom: 16 }}>
<img <img
src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUzLjMzMzMgMzJWNDhDNTMuMzMzMyA0OS40MTc0IDUyLjc3MTQgNTAuNzY1MiA1MS43NzEyIDUxLjc2NTJDNTAuNzcxIDUyLjc2NTIgNDkuNDIzMyA1My4zMzMzIDQ4IDUzLjMzMzNIMTZDMTQuNTc2NyA1My4zMzMzIDEzLjIyODkgNTIuNzcxNCAxMi4yMjg4IDUxLjc3MTJDMTEuMjI4OCA1MC43NzEgMTAuNjY2NyA0OS40MjMzIDEwLjY2NjcgNDhWMTZDMTAuNjY2NyAxNC41NzY3IDExLjIyODggMTMuMjI4OSAxMi4yMjg4IDEyLjIyODhDMTMuMjI4OSAxMS4yMjg4IDE0LjU3NjcgMTAuNjY2NyAxNiAxMC42NjY3SDMyIiBzdHJva2U9IiM1QTVBNUEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMiAzMkg1My4zMzMzVjQyLjY2NjciIHN0cm9rZT0iI0YyOEI0NCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg==" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNjQiIGhlaWdodD0iNjQiIHZpZXdCb3g9IjAgMCA2NCA2NCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTUzLjMzMzMgMzJWNDhDNTMuMzMzMyA0OS40MTc0IDUyLjc3MTQgNTAuNzY1MiA1MS43NzEyIDUxLjc2NTJDNTAuNzcxIDUyLjc2NTIgNDkuNDIzMyA1My4zMzMzIDQ4IDUzLjMzMzNIMTZDMTQuNTc2NyA1My4zMzMzIDEzLjIyODkgNTIuNzcxNCAxMi4yMjg4IDUxLjc3MTJDMTEuMjI4OCA1MC43NzEgMTAuNjY2NyA0OS40MjMzIDEwLjY2NjcgNDhWMTZDMTAuNjY2NyAxNC41NzY3IDExLjIyODggMTMuMjI4OSAxMi4yMjg4IDEyLjIyODhDMTMuMjI4OSAxMS4yMjg4IDE0LjU3NjcgMTAuNjY2NyAxNiAxMC42NjY3SDMyIiBzdHJva2U9IiM1QTVBNUEiIHN0cm9rZS13aWR0aD0iNCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+CjxwYXRoIGQ9Ik0zMiAzMkg1My4zMzMzVjQyLjY2NjciIHN0cm9rZT0iI0YyOEI0NCIgc3Ryb2tlLXdpZHRoPSI0IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz4KPC9zdmc+Cg=="
@@ -249,9 +289,9 @@ export default function App() {
style={{ width: 64, height: 64, opacity: 0.5 }} style={{ width: 64, height: 64, opacity: 0.5 }}
/> />
</div> </div>
<p style={{ color: '#5A5A5A' }}></p> <p style={{ color: "#5A5A5A" }}></p>
</div> </div>
) ),
}} }}
renderItem={(proxy) => ( renderItem={(proxy) => (
<List.Item <List.Item
@@ -261,24 +301,32 @@ export default function App() {
type={proxy.enabled ? "default" : "primary"} type={proxy.enabled ? "default" : "primary"}
onClick={() => handleActivate(proxy.id)} onClick={() => handleActivate(proxy.id)}
disabled={proxy.enabled} disabled={proxy.enabled}
style={proxy.enabled ? { style={
backgroundColor: "#F28B44", proxy.enabled
color: "white", ? {
borderColor: "#F28B44" backgroundColor: "#F28B44",
} : undefined} color: "white",
borderColor: "#F28B44",
}
: undefined
}
> >
{proxy.enabled ? '已启用' : '启用'} {proxy.enabled ? "已启用" : "启用"}
</Button>, </Button>,
<Button <Button
danger danger
icon={<DeleteOutlined />} icon={<DeleteOutlined />}
onClick={() => handleDelete(proxy.id)} onClick={() => handleDelete(proxy.id)}
/> />,
]} ]}
> >
<List.Item.Meta <List.Item.Meta
title={proxy.name} 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> </List.Item>
)} )}
@@ -289,7 +337,7 @@ export default function App() {
<Modal <Modal
title="添加新代理" title="添加新代理"
open={isModalOpen} open={isModalOpen}
className='add-proxy-card' className="add-proxy-card"
onCancel={handleCancel} onCancel={handleCancel}
footer={[ footer={[
<Button key="cancel" onClick={handleCancel}> <Button key="cancel" onClick={handleCancel}>
@@ -302,19 +350,15 @@ export default function App() {
loading={loading} loading={loading}
> >
OK OK
</Button> </Button>,
]} ]}
destroyOnClose destroyOnClose
> >
<Form <Form form={form} layout="vertical" onFinish={handleSave}>
form={form}
layout="vertical"
onFinish={handleSave}
>
<Form.Item <Form.Item
name="name" name="name"
label={<span className="required-label"></span>} label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入代理名称' }]} rules={[{ required: true, message: "请输入代理名称" }]}
> >
<Input placeholder="为此代理添加一个名称" /> <Input placeholder="为此代理添加一个名称" />
</Form.Item> </Form.Item>
@@ -323,7 +367,7 @@ export default function App() {
name="proxyType" name="proxyType"
label={<span className="required-label"></span>} label={<span className="required-label"></span>}
initialValue="fixed_servers" initialValue="fixed_servers"
rules={[{ required: true, message: '请选择代理类型' }]} rules={[{ required: true, message: "请选择代理类型" }]}
> >
<Select> <Select>
<Option value="fixed_servers"></Option> <Option value="fixed_servers"></Option>
@@ -333,18 +377,22 @@ export default function App() {
<Form.Item <Form.Item
noStyle noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType} shouldUpdate={(prevValues, currentValues) =>
prevValues.proxyType !== currentValues.proxyType
}
> >
{({ getFieldValue }) => { {({ getFieldValue }) => {
const proxyType = getFieldValue('proxyType'); const proxyType = getFieldValue("proxyType");
if (proxyType === 'fixed_servers') { if (proxyType === "fixed_servers") {
return ( return (
<> <>
<Form.Item <Form.Item
name="scheme" name="scheme"
label={<span className="required-label"></span>} label={<span className="required-label"></span>}
initialValue="http" initialValue="http"
rules={[{ required: true, message: '请选择代理协议' }]} rules={[
{ required: true, message: "请选择代理协议" },
]}
> >
<Select> <Select>
<Option value="http">HTTP</Option> <Option value="http">HTTP</Option>
@@ -357,7 +405,9 @@ export default function App() {
<Form.Item <Form.Item
name="host" name="host"
label={<span className="required-label"></span>} label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入主机地址' }]} rules={[
{ required: true, message: "请输入主机地址" },
]}
> >
<Input placeholder="127.0.0.1" /> <Input placeholder="127.0.0.1" />
</Form.Item> </Form.Item>
@@ -365,15 +415,12 @@ export default function App() {
<Form.Item <Form.Item
name="port" name="port"
label={<span className="required-label"></span>} label={<span className="required-label"></span>}
rules={[{ required: true, message: '请输入端口' }]} rules={[{ required: true, message: "请输入端口" }]}
> >
<Input placeholder="8080" /> <Input placeholder="8080" />
</Form.Item> </Form.Item>
<Form.Item <Form.Item name="bypassList" label="不经过代理的地址">
name="bypassList"
label="不经过代理的地址"
>
<Input.TextArea <Input.TextArea
rows={4} rows={4}
placeholder={`例如: placeholder={`例如:
@@ -381,17 +428,49 @@ localhost
127.0.0.1 127.0.0.1
*.example.com`} *.example.com`}
/> />
<div style={{ marginTop: 8, fontSize: 12, color: '#999' }}> *</div> <div
style={{
marginTop: 8,
fontSize: 12,
color: "#999",
}}
>
*
</div>
</Form.Item> </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 ( return (
<> <>
<Form.Item <Form.Item
name="matchList" name="matchList"
label={<span className="required-label"></span>} label="匹配域名"
rules={[{ required: true, message: '请输入至少一个匹配域名' }]} help="每行一个域名,支持通配符 *"
rules={[
{
required: true,
message: "请输入至少一个匹配域名",
},
]}
> >
<Input.TextArea <Input.TextArea
rows={4} rows={4}
@@ -400,7 +479,35 @@ localhost
google.com google.com
github.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> </Form.Item>
</> </>
); );
@@ -408,24 +515,6 @@ github.com`}
return null; return null;
}} }}
</Form.Item> </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> </Form>
</Modal> </Modal>
</Content> </Content>