Remove ord directory from tracking

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent 9fbfe309a9
commit 0c197a791d
71 changed files with 0 additions and 39280 deletions
-30
View File
@@ -1,30 +0,0 @@
import React from "react";
// import "./App.css";
import {ConfigProvider} from "antd";
import {Contro} from "@components/Contro";
import {Proxifier} from "@components/Proxifier";
import {EvalInTab} from "@components/EvalInTab";
import {ProxySwitch} from "@components/ProxySwitch";
import './styles/global.css';
function App() {
return (
<ConfigProvider
theme={{
token: {
colorPrimary: "#F28B44",
},
}}
>
<div className="App">
{/*<Contro/>*/}
{/* <Proxifier/> */}
<ProxySwitch/>
{/* <EvalInTab/> */}
</div>
</ConfigProvider>
);
}
export default App;
-168
View File
@@ -1,168 +0,0 @@
import Icon from "@ant-design/icons";
import { CustomIconComponentProps } from "@ant-design/icons/lib/components/Icon";
import React from "react";
interface IconProps extends CustomIconComponentProps {
onClick: (e: React.MouseEvent) => void;
ref?: any;
}
const X = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M4 12L12 4M4 4L12 12"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/x
*/
export const XIcon = (props: Partial<IconProps>) => {
return <Icon component={X} {...props} />;
};
const Check = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M3.33337 8.66669L6.00004 11.3334L12.6667 4.66669"
stroke="#56C991"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/check
*/
export const CheckIcon = (props: Partial<IconProps>) => {
return <Icon component={Check} {...props} />;
};
const PencilAlt = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M7.33329 3.33334H3.99996C3.26358 3.33334 2.66663 3.93029 2.66663 4.66667V12C2.66663 12.7364 3.26358 13.3333 3.99996 13.3333H11.3333C12.0697 13.3333 12.6666 12.7364 12.6666 12V8.66667M11.7238 2.39052C12.2445 1.86983 13.0887 1.86983 13.6094 2.39052C14.1301 2.91122 14.1301 3.75544 13.6094 4.27614L7.88557 10H5.99996L5.99996 8.11438L11.7238 2.39052Z"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/pencil-alt
*/
export const PencilAltIcon = (props: Partial<IconProps>) => {
return <Icon component={PencilAlt} {...props} />;
};
const Refresh = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M2.66663 2.66669V6.00002H3.0543M13.292 7.33335C12.964 4.70248 10.7197 2.66669 7.99996 2.66669C5.76171 2.66669 3.84549 4.04547 3.0543 6.00002M3.0543 6.00002H5.99996M13.3333 13.3334V10H12.9456M12.9456 10C12.1544 11.9546 10.2382 13.3334 7.99996 13.3334C5.28021 13.3334 3.03595 11.2976 2.70789 8.66669M12.9456 10H9.99996"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/refresh
*/
export const RefreshIcon = (props: Partial<IconProps>) => {
return <Icon component={Refresh} {...props} />;
};
const Exit = () => (
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.33333 6.66668C9.10226 6.78107 8.87965 6.90988 8.66667 7.0519C7.05869 8.12418 6 9.95027 6 12.0227C6 15.3239 8.68629 18 12 18C15.3137 18 18 15.3239 18 12.0227C18 9.95027 16.9413 8.12418 15.3333 7.0519C15.1204 6.90988 14.8977 6.78107 14.6667 6.66668M12 5.33334V10.6667"
stroke="#F7544A"
strokeLinecap="round"
/>
</svg>
);
/**
* @description 退出
*/
export const ExitIcon = (props: Partial<IconProps>) => {
return <Icon component={Exit} {...props} />;
};
const PlusSm = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M8 4V8M8 8V12M8 8H12M8 8L4 8"
stroke="currentColor"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/plus-sm
*/
export const PlusSmIcon = (props: Partial<IconProps>) => {
return <Icon component={PlusSm} {...props} />;
};
const Trash = () => (
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
>
<path
d="M12.6666 4.66667L12.0884 12.7617C12.0386 13.4594 11.458 14 10.7585 14H5.24145C4.54193 14 3.96135 13.4594 3.91151 12.7617L3.33329 4.66667M6.66663 7.33333V11.3333M9.33329 7.33333V11.3333M9.99996 4.66667V2.66667C9.99996 2.29848 9.70148 2 9.33329 2H6.66663C6.29844 2 5.99996 2.29848 5.99996 2.66667V4.66667M2.66663 4.66667H13.3333"
stroke="#F7544A"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
);
/**
* @description Icon/Outline/trash
*/
export const TrashIcon = (props: Partial<IconProps>) => {
return <Icon component={Trash} {...props} />;
};
-10
View File
@@ -1,10 +0,0 @@
.add-proxy-form {
padding: 24px;
}
.form-buttons {
display: flex;
justify-content: flex-end;
gap: 8px;
margin-top: 24px;
}
-72
View File
@@ -1,72 +0,0 @@
import React from 'react';
import { Form, Input, Select, InputNumber, Button, message } from 'antd';
import { ProxyActionType } from '@/types/action';
import './index.css';
interface EditFormData {
name: string;
proxyType: string;
scheme?: string;
host?: string;
port?: number;
pacScript?: string;
}
export const AddProxyForm: React.FC = () => {
const [form] = Form.useForm<EditFormData>();
// 初始化表单
React.useEffect(() => {
form.setFieldsValue({
name: '',
proxyType: 'fixed_servers',
scheme: 'http',
host: '127.0.0.1',
port: 8080
});
}, []);
const handleSave = async () => {
try {
const values = await form.validateFields();
const newConfig = {
id: Date.now().toString(),
...values,
enabled: false
};
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.ADD_PROXY_CONFIG,
config: newConfig
});
if (response.success) {
message.success('添加成功');
window.close(); // 关闭窗口
}
} catch (error) {
console.error('Failed to add proxy:', error);
message.error('添加失败');
}
};
return (
<div className="add-proxy-form">
<Form
form={form}
layout="vertical"
onFinish={handleSave}
>
{/* 表单项与之前相同 */}
<Form.Item className="form-buttons">
<Button type="primary" htmlType="submit">
确定
</Button>
<Button onClick={() => window.close()}>
取消
</Button>
</Form.Item>
</Form>
</div>
);
};
-73
View File
@@ -1,73 +0,0 @@
.Contro {
display: flex;
align-items: center;
height: 40px;
padding: 8px 16px;
font-size: 12px;
}
.Contro-error-bg {
background-color: rgba(244, 115, 107, .10);
}
.Contro-success-bg {
background-color: rgba(86, 201, 145, .10);
}
.Contro-cont-input {
height: 24px;
margin-right: 8px;
}
.Contro-lable {
color: #85899E;
}
.Contro-cont {
display: flex;
justify-content: space-between;
align-items: center;
flex: 1;
}
.Contro-cont-text-error {
color: #F6544A;
}
.Contro-cont-text-success {
color: #56C991;
}
.Contro-handle-icon {
display: flex;
align-items: center;
}
.Contro-handle-icon svg {
cursor: pointer;
}
.contro-handle-icon-check {
margin-left: 8px;
}
.icon-p {
display: inline-block;
padding: 4px;
}
.icon-p svg {
color: #85899E;
}
.Contro-cont span.anticon:hover {
background: #F0F1F3;
}
.grey-icon svg {
color: #85899E;
}
.icon-active:active svg {
color: #F28B44;
}
-191
View File
@@ -1,191 +0,0 @@
import React, {useEffect, useMemo, useRef, useState} from "react";
import {Divider, Tooltip, Input} from "antd";
import classNames from "classnames";
import {
CheckIcon,
ExitIcon,
PencilAltIcon,
RefreshIcon,
XIcon,
} from "@assets/icon/icon";
import {wsc} from "@network/chrome";
import "./Contro.css";
interface ControProps {
}
export const Contro: React.FC<ControProps> = () => {
const [isEdit, setIsEdit] = useState<boolean>(false);
const [connected, setConnected] = useState(false);
const [autoFindFailedReason, setAutoFindFailedReason] = useState<string>("");
const [enginePort, setEnginePort] = useState<string>("");
const [enginePortTemp, setEnginePortTemp] = useState<string>(enginePort);
const enginePortRef = useRef<string>(enginePort);
useEffect(() => {
enginePortRef.current = enginePort;
}, [enginePort]);
useEffect(() => {
const yakitConnectInfo = localStorage.getItem("yakit-connect");
if (!yakitConnectInfo) {
findPort(11212, 11222);
} else {
const {port, connected} = JSON.parse(yakitConnectInfo);
setConnected(connected);
setEnginePort(port + "");
setAutoFindFailedReason("");
}
wsc.onWSCMessage((message) => {
if (message.action === wsc.ActionType.STATUS) {
if (message.connected === false) {
handleConnectFail();
} else {
localStorage.setItem(
"yakit-connect",
JSON.stringify({connected: true, port: message.port})
);
setEnginePort(message.port + "");
setConnected(true);
setAutoFindFailedReason("");
}
}
});
}, []);
const findPort = (port: number, max: number) => {
const ws = new WebSocket(`ws://127.0.0.1:${port}`);
ws.onclose = (e: CloseEvent) => {
if (enginePortRef.current) {
handleConnectFail();
return;
}
if (e.reason !== `FoundYakitWebSocketController` && port + 1 <= max) {
setTimeout(() => findPort(port + 1, max), 200);
}
if (port + 1 > max) {
setConnected(false);
setEnginePort("");
setAutoFindFailedReason("Cannot found Yakit");
localStorage.setItem("yakit-connect", "");
}
};
ws.onopen = () => {
setConnected(true);
setEnginePort(port + "");
ws.close(1000, "FoundYakitWebSocketController");
connectPort(port);
};
};
const handleConnectFail = () => {
setConnected(false);
setAutoFindFailedReason("Yakit WebSocket Controller Connect Fail");
localStorage.setItem("yakit-connect", "");
};
const connectPort = (port: number) => {
setAutoFindFailedReason("");
wsc.connect(port);
};
const safeConnected = useMemo(() => {
return connected && enginePort;
}, [connected, enginePort]);
const failConnected = useMemo(() => {
return !connected && autoFindFailedReason;
}, [connected, autoFindFailedReason]);
return (
<>
{safeConnected || failConnected ? (
<div
className={classNames("Contro", {
["Contro-success-bg"]: safeConnected,
["Contro-error-bg"]: failConnected,
})}
>
<div className="Contro-lable">Yakit 引擎连接状态:</div>
<div className="Contro-cont">
{isEdit ? (
<>
<Input
rootClassName="Contro-cont-input"
value={enginePortTemp}
placeholder="输入范围 11212 - 11222"
onChange={(e) => {
const value = e.target.value;
setEnginePortTemp(value);
}}
/>
<div className="Contro-handle-icon">
<XIcon
className="grey-icon icon-p icon-active"
onClick={() => {
setIsEdit(false);
}}
/>
<CheckIcon
className="contro-handle-icon-check icon-p"
onClick={() => {
if (enginePortTemp) {
setIsEdit(false);
setEnginePort(enginePortTemp);
connectPort(Number(enginePortTemp));
}
}}
/>
</div>
</>
) : (
<>
<div
className={classNames("Contro-cont-text", {
["Contro-cont-text-success"]: safeConnected,
["Contro-cont-text-error"]: failConnected,
})}
>
{safeConnected && "已连接(" + enginePort + ")"}
{failConnected &&
(enginePort
? autoFindFailedReason + "(" + enginePort + ")"
: autoFindFailedReason)}
</div>
<div className="Contro-handle-icon">
<Tooltip title="修改监听端口">
<PencilAltIcon
className="grey-icon icon-p icon-active"
onClick={() => {
setIsEdit(true);
setEnginePortTemp(enginePort);
}}
/>
</Tooltip>
<Divider type="vertical" style={{height: 16}}/>
{safeConnected && (
<ExitIcon onClick={() => wsc.disconnect()}/>
)}
{failConnected && (
<RefreshIcon
className="grey-icon icon-active"
onClick={() => {
if (enginePort) {
connectPort(Number(enginePort));
} else {
findPort(11212, 11222);
}
}}
/>
)}
</div>
</>
)}
</div>
</div>
) : null}
</>
);
};
-81
View File
@@ -1,81 +0,0 @@
import React, {useEffect, useState} from "react";
import {Button, Input} from "antd";
import TextArea from "antd/lib/input/TextArea";
import {wsc} from "@network/chrome";
interface EvalInTabProps {
}
export const EvalInTab: React.FC<EvalInTabProps> = () => {
const [funcName, setFuncName] = useState("");
const [inputArgsData, setInputArgsData] = useState("");
const [code, setCode] = useState("");
useEffect(() => {
wsc.onWSCMessage((message) => {
console.log("message from content script:", message)
if (message.action === wsc.ActionType.TO_EXTENSION_PAGE) {
console.log("eval in tab:", message.result);
// alert("from content script: " + JSON.stringify(message.result));
// 发送结果
wsc.sendMessage({"type": "chrome-extension", res: message.result})
}
});
}, []);
const handleClick = async () => {
try {
const [tab] = await wsc.getTab();
await chrome.runtime.sendMessage({
action: wsc.ActionType.INJECT_SCRIPT,
tabId: tab.id,
value: {mode: "CONTENT_CALL_FUNCTION", fn_name: funcName, args: inputArgsData},
});
} catch (error) {
console.log("error:", error)
}
};
const handleInputChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setInputArgsData(event.target.value);
};
const handleEvalCodeClick = async () => {
try {
const [tab] = await wsc.getTab();
await chrome.runtime.sendMessage({
action: wsc.ActionType.INJECT_SCRIPT,
tabId: tab.id,
value: {
mode: "CONTENT_EVAL_CODE",
code: code,
},
});
} catch (error) {
}
}
return (
<div>
<Input
value={funcName}
onChange={(e) => setFuncName(e.target.value)}
placeholder="Enter function name"
></Input>
<TextArea
value={inputArgsData}
onChange={handleInputChange}
placeholder="Enter function args"
/>
<Button onClick={handleClick}>eval func in tab</Button>
<TextArea
value={code}
onChange={(e) => setCode(e.target.value)}
placeholder="Enter your expression (e.g., 22 * 33)"
/>
<Button onClick={handleEvalCodeClick}>eval code in tab</Button>
</div>
);
};
-113
View File
@@ -1,113 +0,0 @@
.Prox-title-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 24px 8px;
}
.Prox-title-wrap-left {
display: flex;
align-items: center;
}
.Prox-title-wrap-left .prox-title {
margin-right: 4px;
color: #31343F;
font-size: 14px;
font-weight: 600;
}
.Prox-title-wrap-left .prox-number {
display: inline-block;
width: 18px;
height: 16px;
text-align: center;
color: #85899E;
border-radius: 8px;
background: #F0F1F3;
font-size: 10px;
}
.Prox-title-wrap-right {
display: flex;
align-items: center;
cursor: pointer;
}
.Prox-title-wrap-right .Prox-add-text {
margin-right: 4px;
color: var(--yakit-primary);
font-size: 12px;
font-weight: 500;
}
.Prox-title-wrap-right .Prox-add-icon svg {
color: var(--yakit-primary);
}
.Prox-list-wrap {
overflow-y: auto;
max-height: 305px;
padding: 16px;
padding-top: 0;
}
.add-list {
display: flex;
justify-content: center;
align-items: center;
height: 28px;
cursor: pointer;
border: 1px solid #EAECF3;
border-radius: 4px;
background-color: #FFFFFF;
}
.add-list:hover {
border: 1px solid var(--yakit-primary);
}
.add-list .add-list-icon svg {
color: #85899E;
}
.Prox-list-wrap .add-list .add-list-text {
margin-left: 4px;
color: #31343F;
font-size: 14px;
font-weight: 600;
}
.Prox-list-wrap .Prox-list-item-wrap {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 8px;
border-radius: 8px;
}
.Prox-list-wrap .Prox-list-item-wrap:hover {
background-color: #F8F8F8;
}
.Prox-list-item-space .ant-space-item .ant-space-compact {
height: 28px;
}
.Prox-list-item-space .ant-space-item .ant-space-compact .ant-select-single {
height: 28px;
}
.Prox-list-wrap .Prox-list-item-wrap:hover .proxy-list-del-icon {
display: block;
}
.proxy-list-del-icon {
display: none;
cursor: pointer;
padding: 4px;
}
.proxy-list-del-icon:hover {
background: #F0F1F3;
}
-311
View File
@@ -1,311 +0,0 @@
import React, {useEffect, useState} from "react";
import {Space, Select, Input, Switch} from "antd";
import {PlusSmIcon, TrashIcon} from "@assets/icon/icon";
import {wsc} from "@network/chrome";
import "./Proxifier.css";
type Scheme = "http" | "socks5";
interface ProxyConfig {
id: string;
scheme: Scheme;
host: string;
port: string;
hostStatus: "error" | "";
portStatus: "error" | "";
open: boolean;
proxy: string;
}
export interface ProxifierProps {}
export const Proxifier: React.FC<ProxifierProps> = () => {
const [proxyList, setProxyList] = useState<ProxyConfig[]>(() => {
const storageProxyList = localStorage.getItem("yakit-proxy-list") || "[]";
return JSON.parse(storageProxyList);
});
useEffect(() => {
localStorage.setItem("yakit-proxy-list", JSON.stringify(proxyList));
}, [proxyList]);
const addNewProxyListItem = (
scheme: Scheme,
host: string,
port: string,
open: boolean,
proxy: string
) => {
const proxyItem: ProxyConfig = {
id: Math.random() + "",
scheme: scheme as Scheme,
host: host,
port: port,
hostStatus: "",
portStatus: "",
open: open,
proxy: proxy,
};
return proxyItem;
};
const parseUrl = (url: string) => {
const regex = /^(.*?):\/\/(.*?):(\d+)/;
const match = url.match(regex);
if (match) {
const scheme = match[1];
const host = match[2];
const port = match[3];
return {
scheme,
host,
port,
};
} else {
return null; // 不匹配格式
}
};
useEffect(() => {
wsc.updateProxyStatus();
wsc.onProxyStatusMessage((msg) => {
console.log("msg", msg)
if (msg.proxy === undefined || msg.enable === undefined) {
return;
}
if (msg.proxy === "" || msg.enable === false) {
if (proxyList.some((i) => i.open)) {
const copyProxyList = [...proxyList];
copyProxyList.forEach((i) => {
i.open = false;
});
setProxyList(copyProxyList);
}
return;
}
if (msg.proxy && msg.enable) {
const copyProxyList = [...proxyList];
let newProxyItem: ProxyConfig = undefined;
if (!copyProxyList.length) {
const urlObj = parseUrl(msg.proxy);
if (urlObj) {
newProxyItem = addNewProxyListItem(
urlObj.scheme as Scheme,
urlObj.host,
urlObj.port,
true,
msg.proxy
);
}
} else {
const proxyExist = copyProxyList.some((i) => i.proxy === msg.proxy);
if (proxyExist) {
const proxyOpen = copyProxyList.some(
(i) => i.open && i.proxy === msg.proxy
);
if (!proxyOpen) {
copyProxyList.forEach((i) => {
i.open = false;
});
for (let i = 0; i < copyProxyList.length; i++) {
if (copyProxyList[i].proxy === msg.proxy) {
copyProxyList[i].open = true;
break;
}
}
}
} else {
copyProxyList.forEach((i) => {
i.open = false;
});
const urlObj = parseUrl(msg.proxy);
if (urlObj) {
newProxyItem = addNewProxyListItem(
urlObj.scheme as Scheme,
urlObj.host,
urlObj.port,
true,
msg.proxy
);
}
}
}
if (newProxyItem) {
copyProxyList.unshift(newProxyItem);
}
setProxyList(copyProxyList);
}
});
}, []);
const hostOnchange = (value: string, id: string) => {
const ipPattern = /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/;
const domainPattern = /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
const copyProxyList = structuredClone(proxyList);
if (value === "" || ipPattern.test(value) || domainPattern.test(value)) {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.hostStatus = "";
i.host = value;
i.proxy = i.scheme + "://" + value + ":" + i.port;
}
});
} else {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.hostStatus = "error";
i.host = value;
i.proxy = i.scheme + "://" + value + ":" + i.port;
}
});
}
setProxyList(copyProxyList);
};
const portOnchange = (value: string, id: string) => {
const portNumber = parseInt(value, 10);
const copyProxyList = structuredClone(proxyList);
if (
value === "" ||
(/^\d+$/.test(value) && portNumber >= 0 && portNumber <= 65535)
) {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.portStatus = "";
const port = value === "" ? "" : portNumber + "";
i.port = port;
i.proxy = i.scheme + "://" + i.host + ":" + port;
}
});
} else {
copyProxyList.forEach((i) => {
if (i.id === id) {
i.portStatus = "error";
i.port = value;
i.proxy = i.scheme + "://" + i.host + ":" + value;
}
});
}
setProxyList(copyProxyList);
};
return (
<div className="Prox">
<div className="Prox-title-wrap">
<div className="Prox-title-wrap-left">
<span className="prox-title">设置代理</span>
<span className="prox-number">{proxyList.length}</span>
</div>
<div
className="Prox-title-wrap-right"
onClick={() => {
setProxyList([
...proxyList,
addNewProxyListItem("http", "", "", false, "http://"),
]);
}}
>
<span className="Prox-add-text">添加</span>
<PlusSmIcon className="Prox-add-icon"/>
</div>
</div>
<div className="Prox-list-wrap">
{proxyList.length ? (
proxyList.map((item) => (
<div className="Prox-list-item-wrap" key={item.id}>
<Space className="Prox-list-item-space">
<Space.Compact>
<Select
value={item.scheme}
style={{width: 88}}
disabled={item.open}
onChange={(value, option) => {
const copyProxyList = structuredClone(proxyList);
copyProxyList.forEach((i) => {
if (i.id === item.id) {
i.scheme = value;
i.proxy = value + "://" + i.host + ":" + i.port;
}
});
setProxyList(copyProxyList);
}}
>
<Select.Option value="http">HTTP</Select.Option>
<Select.Option value="socks5">Socks5</Select.Option>
</Select>
<Input
value={item.host}
style={{width: 136}}
disabled={item.open}
status={item.hostStatus}
onChange={(e) => hostOnchange(e.target.value, item.id)}
/>
<Input
value={item.port}
style={{width: 64}}
disabled={item.open}
status={item.portStatus}
onChange={(e) => portOnchange(e.target.value, item.id)}
/>
</Space.Compact>
</Space>
{!item.open && (
<TrashIcon
className="proxy-list-del-icon"
onClick={() => {
setProxyList(proxyList.filter((i) => i.id !== item.id));
}}
/>
)}
<Switch
checkedChildren="启"
unCheckedChildren="停"
value={item.open}
disabled={
item.hostStatus === "error" ||
item.portStatus === "error" ||
item.host === "" ||
item.port === ""
}
onChange={(checked: boolean) => {
wsc.clearProxy();
const copyProxyList = structuredClone(proxyList);
copyProxyList.forEach((i) => {
if (i.id === item.id) {
i.open = checked;
if (checked) {
wsc.setProxy(item.scheme, item.host, Number(item.port));
}
} else {
i.open = false;
}
});
setProxyList(copyProxyList);
}}
/>
</div>
))
) : (
<div
className="add-list"
onClick={() => {
setProxyList([
addNewProxyListItem(
"http",
"127.0.0.1",
"8083",
false,
"http://127.0.0.1:8083"
),
]);
}}
>
<PlusSmIcon className="add-list-icon"/>
<span className="add-list-text">添加</span>
</div>
)}
</div>
</div>
);
};
-235
View File
@@ -1,235 +0,0 @@
.proxy-container {
min-width: 200px;
background: white;
}
.proxy-menu {
border: none !important;
box-shadow: none !important;
}
.menu-item {
height: 40px !important;
line-height: 40px !important;
margin: 0 !important;
padding: 0 16px !important;
}
.menu-item .anticon {
font-size: 16px;
color: var(--yakit-primary);
margin-right: 8px;
}
.menu-item-label {
font-size: 14px;
color: #333;
}
.menu-item:hover {
background-color: var(--yakit-primary-5) !important;
}
.menu-item:hover .anticon,
.menu-item:hover .menu-item-label {
color: var(--yakit-primary) !important;
}
/* 选中状态 */
.menu-item.ant-menu-item-selected {
background-color: var(--yakit-primary) !important;
}
.menu-item.ant-menu-item-selected .anticon,
.menu-item.ant-menu-item-selected .menu-item-label {
color: white !important;
}
.menu-item.ant-menu-item-selected:hover {
background-color: var(--yakit-primary-hover) !important;
}
/* 分隔线 */
.ant-menu-item-divider {
margin: 4px 0 !important;
border-color: #EAECF3 !important;
}
/* 设置选项 */
.menu-item-setting {
border-top: 1px solid #EAECF3;
margin-top: 4px !important;
}
.menu-item-setting .anticon {
color: #666;
}
.menu-item-setting:hover {
background-color: var(--yakit-primary-5) !important;
}
.menu-item-setting:hover .anticon,
.menu-item-setting:hover .menu-item-label {
color: var(--yakit-primary) !important;
}
/* 调整图标大小和对齐 */
.anticon {
font-size: 16px;
}
/* 添加以下样式来确保下拉菜单显示在正确的位置 */
.ant-dropdown {
position: absolute !important;
top: 100% !important;
left: 0 !important;
width: 100% !important;
min-width: 200px !important;
}
.dropdown-content {
background: white;
border-radius: 4px;
box-shadow: 0 3px 6px -4px rgba(0,0,0,0.12),
0 6px 16px 0 rgba(0,0,0,0.08),
0 9px 28px 8px rgba(0,0,0,0.05);
padding: 4px;
}
/* 确保容器不会限制弹出层 */
.proxy-container {
min-width: 200px;
background: white;
border-radius: 4px;
position: static;
}
/* 添加这个样式来确保下拉菜单显示在正确的位置 */
body {
position: relative;
}
.ant-menu {
border: none !important;
box-shadow: 0 2px 8px rgba(0,0,0,0.15) !important;
padding: 4px 0 !important;
width: 180px !important;
background: white !important;
}
.ant-menu-item {
height: 28px !important;
line-height: 28px !important;
margin: 0 !important;
padding: 0 16px !important;
}
.ant-menu-item:hover {
background-color: #f5f5f5 !important;
}
.menu-icon {
margin-right: 8px;
display: inline-flex;
align-items: center;
}
.menu-item-selected {
background-color: #e6f7ff !important;
}
.ant-menu-item-divider {
margin: 4px 0 !important;
height: 1px !important;
background-color: #f0f0f0 !important;
}
.ant-menu-item:last-child {
margin-top: 4px !important;
border-top: 1px solid #f0f0f0;
}
/* 移除多余的样式 */
.ant-menu-root {
background: transparent !important;
}
/* 调整整体容器大小 */
.ant-menu-root {
width: 180px !important;
min-height: auto !important;
}
/* 添加代理按钮样式 */
.menu-item-add {
color: #666 !important;
}
.menu-item-add:hover {
background-color: #f5f5f5 !important;
color: var(--yakit-primary) !important;
}
.menu-item-add .anticon {
color: #666;
}
.menu-item-add:hover .anticon {
color: var(--yakit-primary);
}
.menu-loading {
pointer-events: none;
opacity: 0.7;
}
.menu-item-loading {
transition: all 0.3s ease;
}
.menu-item-selected {
transition: all 0.3s ease;
background-color: var(--yakit-primary-5) !important;
}
/* 添加过渡效果 */
.ant-menu-item {
transition: all 0.3s ease !important;
}
.ant-menu-item .menu-icon {
transition: color 0.3s ease;
}
.proxy-switch-container {
position: relative;
width: 180px;
overflow: hidden;
}
.panel-watermark {
position: absolute;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
opacity: 0.03;
pointer-events: none;
object-fit: contain;
object-position: right bottom;
z-index: 0;
}
/* 确保菜单项在水印上层 */
.ant-menu-item {
position: relative;
z-index: 1;
background: transparent !important;
}
/* 确保分割线在水印上层 */
.ant-menu-item-divider {
position: relative;
z-index: 1;
}
-311
View File
@@ -1,311 +0,0 @@
import React, {useEffect, useState} from "react";
import {Menu} from "antd";
import {GlobalOutlined, DisconnectOutlined, SettingOutlined, EditOutlined, PlusOutlined} from "@ant-design/icons";
import {ProxyActionType} from '@/types/action';
import "./index.css";
import type { MenuProps } from 'antd';
import type { ProxyConfig } from '@/types/proxy';
// 添加 YAK 图标 URL 常量
const YAK_ICON_URL = chrome.runtime.getURL('/images/yak.svg');
// 固定的代理模式
const FIXED_MODES = [
{
key: 'direct',
name: '[直接连接]',
icon: <DisconnectOutlined />,
color: '#666',
config: {
id: 'direct',
name: '[直接连接]',
proxyType: 'direct',
enabled: false
}
},
{
key: 'system',
name: '[系统代理]',
icon: <SettingOutlined />,
color: '#666',
config: {
id: 'system',
name: '[系统代理]',
proxyType: 'system',
enabled: false
}
}
];
interface CustomProxy {
key: string;
name: string;
color: string;
config: ProxyConfig;
enabled?: boolean;
}
interface ProxySwitchProps {
proxyConfigs: ProxyConfig[];
currentProxy: ProxyConfig | null;
onProxyChange: (config: ProxyConfig) => void;
}
export const ProxySwitch: React.FC<ProxySwitchProps> = () => {
const [initialized, setInitialized] = useState<boolean>(false);
const [currentMode, setCurrentMode] = useState<string>('');
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 loadProxyStatus();
await loadCustomProxies();
setInitialized(true);
};
init();
}, []);
const loadProxyStatus = async () => {
try {
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.GET_PROXY_STATUS
});
if (!response) {
console.log('No response from background script');
return;
}
if (response.success) {
const activeMode = response.data.mode;
if (FIXED_MODES.some(mode => mode.key === activeMode)) {
setCurrentMode(activeMode);
}
}
} catch (error) {
console.error('Error loading proxy status:', error);
setCurrentMode('direct');
}
};
const loadCustomProxies = async () => {
try {
const DB_NAME = 'yaklang_extension';
const STORE_NAME = 'proxy_configs';
// 打开数据库
const db = await new Promise<IDBDatabase>((resolve, reject) => {
const request = indexedDB.open(DB_NAME, 1);
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result);
});
// 从数据库读取代理配置
const configs = await new Promise<ProxyConfig[]>((resolve, reject) => {
try {
const transaction = db.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.getAll();
request.onerror = () => reject(request.error);
request.onsuccess = () => resolve(request.result || []);
} catch (error) {
reject(error);
}
});
// 处理代理配置
const proxies = configs
.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 = configs.find((proxy: ProxyConfig) => proxy.enabled);
if (enabledProxy) {
setCurrentMode(enabledProxy.id);
}
} catch (error) {
console.error('Error loading custom proxies:', error);
setCustomProxies([]);
}
};
const handleModeChange = async (mode: string) => {
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
});
const optionsUrl = chrome.runtime.getURL('/proxy/options.html');
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);
}
}
return;
}
try {
setIsLoading(true);
const fixedMode = FIXED_MODES.find(fixed => fixed.key === mode);
const customProxy = customProxies.find(proxy => proxy.key === mode);
const config = fixedMode?.config || customProxy?.config;
if (!config) {
console.error('No config found for mode:', mode);
return;
}
// 立即更新UI状态
setCurrentMode(mode);
if (customProxy) {
setCustomProxies(prev => prev.map(p => ({
...p,
enabled: p.key === mode
})));
}
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.SET_PROXY_CONFIG,
config,
// 添加一个标志,表示这是从 ProxySwitch 发起的更改
source: 'proxy_switch'
});
if (response?.success === false) {
throw new Error(response.error || '设置代理失败');
}
// 不需要重新加载,因为我们已经更新了本地状态
} catch (error) {
console.error('Error applying proxy config:', error);
// 发生错误时才重新加载以确保状态正确
await loadCustomProxies();
throw error;
} finally {
setIsLoading(false);
}
};
const menuItems: MenuProps['items'] = [
...FIXED_MODES.map(mode => ({
key: mode.key,
icon: <span className="menu-icon" style={{
color: currentMode === mode.key ? 'var(--yakit-primary)' : mode.color
}}>{mode.icon}</span>,
label: `${mode.name}${currentMode === mode.key ? ' ✅' : ''}`,
className: `${currentMode === mode.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
title: mode.name.replace(/[\[\]]/g, '')
})),
{ type: 'divider' },
...customProxies.map(proxy => ({
key: proxy.key,
icon: <span className="menu-icon" style={{
color: currentMode === proxy.key ? 'var(--yakit-primary)' : 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}{currentMode === proxy.key ? ' ✅' : ''}</span>,
className: `${currentMode === proxy.key ? 'menu-item-selected' : ''} ${isLoading ? 'menu-item-loading' : ''}`,
title: proxy.config.scheme
? `${proxy.config.scheme.toUpperCase()} ${proxy.config.host}:${proxy.config.port}`
: `${proxy.config.host}:${proxy.config.port}`
})),
{
key: 'add',
icon: <PlusOutlined />,
label: '添加代理...',
className: 'menu-item-add'
},
{ type: 'divider' },
{
key: 'setting',
icon: <EditOutlined />,
label: '选项'
}
];
return initialized ? (
<div className="proxy-switch-container" style={{ position: 'relative' }}>
<img
src={YAK_ICON_URL}
className="panel-watermark"
alt=""
style={{
position: 'absolute',
right: 0,
bottom: 0,
width: '100%',
height: '100%',
opacity: 0.1,
backgroundColor: '#fff7e6',
pointerEvents: 'none',
objectFit: 'contain',
objectPosition: 'right bottom',
zIndex: 0
}}
/>
<Menu
items={menuItems}
selectedKeys={[currentMode]}
onClick={({ key }) => !isLoading && handleModeChange(key)}
style={{ width: 180, position: 'relative', zIndex: 1, background: 'transparent' }}
className={isLoading ? 'menu-loading' : ''}
/>
</div>
) : (
<div style={{ width: 180, height: 100, display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<span>加载中...</span>
</div>
);
};
-27
View File
@@ -1,27 +0,0 @@
html:root {
--yakit-primary: #F28B44
}
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
overflow: hidden;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
#root {
width: 180px;
height: fit-content;
background: white;
overflow: hidden;
}
-13
View File
@@ -1,13 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
// 获取根元素
const container = document.getElementById('root');
if (!container) throw new Error('Failed to find the root element');
// 创建根
const root = createRoot(container);
// 渲染应用,移除 StrictMode
root.render(<App />);
-69
View File
@@ -1,69 +0,0 @@
export namespace wsc {
export enum ActionType {
CONNECT = 'connect',
SEND_MESSAGE = 'send_message',
DISCONNECT = 'disconnect',
STATUS = 'status',
PROXY_STATUS = 'proxy_status',
SET_PROXY = 'set_proxy',
CLEAR_PROXY = 'clear_proxy',
INJECT_SCRIPT = 'yakit_inject_script',
// 用于接收来自content script的消息
TO_EXTENSION_PAGE = "yakit_to_extension_page",
}
export function connect(port: number, host?: string) {
chrome.runtime.sendMessage({
action: ActionType.CONNECT,
host: host || '127.0.0.1',
port: port,
});
}
export function sendMessage(message: any) {
chrome.runtime.sendMessage({
action: ActionType.SEND_MESSAGE,
message: message,
});
}
export function disconnect() {
chrome.runtime.sendMessage({
action: ActionType.DISCONNECT,
});
}
export function updateWSCStatus() {
chrome.runtime.sendMessage({
action: ActionType.STATUS,
});
}
export function updateProxyStatus() {
chrome.runtime.sendMessage({
action: ActionType.PROXY_STATUS,
});
}
export function onWSCMessage(onMessage: (message: any) => void) {
chrome.runtime.onMessage.addListener(onMessage);
}
export function onProxyStatusMessage(onMessage: (message: { enable: boolean, proxy: string }) => any) {
chrome.runtime.onMessage.addListener(onMessage)
}
export function setProxy(scheme: string, host: string, port: number) {
chrome.runtime.sendMessage({action: ActionType.SET_PROXY, scheme, host, port})
}
export function clearProxy() {
chrome.runtime.sendMessage({action: ActionType.CLEAR_PROXY})
}
// 获取当前tab
export async function getTab() {
return chrome.tabs.query({active: true, currentWindow: true})
}
}
@@ -1,166 +0,0 @@
import React from 'react';
import { Modal, Button, Space, Descriptions, Tabs, Card, Typography, message } from 'antd';
import { ProxyLog } from '@/types/proxy';
const { Text, Paragraph } = Typography;
interface LogDetailProps {
log: ProxyLog | null;
onClose: () => void;
}
export const LogDetail: React.FC<LogDetailProps> = ({ log, onClose }) => {
const renderHttpRequest = (log: ProxyLog) => {
if (!log) return '';
// 构建请求头
const headers = Object.entries(log.requestHeaders || {})
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
// 构建完整的 HTTP 请求
return `${log.method || 'GET'} ${log.url} ${log.protocol || 'HTTP/1.1'}
${headers}
${log.requestBody || ''}`;
};
const renderHttpResponse = (log: ProxyLog) => {
if (!log || !log.responseHeaders) return '';
// 构建响应头
const headers = Object.entries(log.responseHeaders)
.map(([key, value]) => `${key}: ${value}`)
.join('\n');
// 构建完整的 HTTP 响应
return `HTTP/1.1 ${log.status === 'success' ? '200 OK' : '500 Error'}
${headers}
${log.responseBody || ''}`;
};
const handleCopyRaw = () => {
if (!log) return;
navigator.clipboard.writeText(renderHttpRequest(log))
.then(() => message.success('已复制到剪贴板'))
.catch(() => message.error('复制失败'));
};
const formatProxyInfo = (log: ProxyLog) => {
if (!log) return '';
return `${log.proxyName}${log.host ? ` - ${log.host}:${log.port}` : ''}`;
};
return (
<Modal
title="请求详情"
open={!!log}
onCancel={onClose}
width={800}
footer={[
<Button key="copy" onClick={handleCopyRaw}>
复制原始数据
</Button>,
<Button key="close" onClick={onClose}>
关闭
</Button>
]}
>
{log && (
<Space direction="vertical" style={{ width: '100%' }}>
<Descriptions bordered column={2}>
<Descriptions.Item label="请求时间">
{new Date(log.timestamp).toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="代理服务器">
{formatProxyInfo(log)}
</Descriptions.Item>
<Descriptions.Item label="状态">
<Text type={log.status === 'success' ? 'success' : 'danger'}>
{log.status === 'success' ? '成功' : '失败'}
</Text>
</Descriptions.Item>
<Descriptions.Item label="响应时间">
{log.timing?.duration}ms
</Descriptions.Item>
{log.errorMessage && (
<Descriptions.Item label="错误信息" span={2}>
<Text type="danger">{log.errorMessage}</Text>
</Descriptions.Item>
)}
</Descriptions>
<Tabs
items={[
{
key: 'request',
label: '请求数据',
children: (
<Card size="small">
<pre style={{
background: '#f5f5f5',
padding: 16,
borderRadius: 4,
maxHeight: 400,
overflow: 'auto',
margin: 0
}}>
{renderHttpRequest(log)}
</pre>
</Card>
)
},
{
key: 'response',
label: '响应数据',
children: (
<Card size="small">
<pre style={{
background: '#f5f5f5',
padding: 16,
borderRadius: 4,
maxHeight: 400,
overflow: 'auto',
margin: 0
}}>
{renderHttpResponse(log)}
</pre>
</Card>
)
},
{
key: 'timing',
label: '性能数据',
children: (
<Card size="small">
<Descriptions bordered>
<Descriptions.Item label="开始时间">
{new Date(log.timing?.startTime || 0).toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="结束时间">
{new Date(log.timing?.endTime || 0).toLocaleString()}
</Descriptions.Item>
<Descriptions.Item label="总耗时">
{log.timing?.duration}ms
</Descriptions.Item>
<Descriptions.Item label="IP地址">
{log.ip || '-'}
</Descriptions.Item>
<Descriptions.Item label="协议">
{log.protocol || '-'}
</Descriptions.Item>
<Descriptions.Item label="缓存">
{log.fromCache ? '是' : '否'}
</Descriptions.Item>
</Descriptions>
</Card>
)
}
]}
/>
</Space>
)}
</Modal>
);
};
@@ -1,131 +0,0 @@
import React, { useState } from 'react';
import { Table, Button, Space } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { DeleteOutlined, FilterFilled } from '@ant-design/icons';
import { ProxyLog } from '@/types/proxy';
import { LogDetail } from './LogDetail';
interface ProxyLogsProps {
logs: ProxyLog[];
onClearLogs: () => void;
}
export const ProxyLogs: React.FC<ProxyLogsProps> = ({
logs,
onClearLogs
}) => {
const [selectedLog, setSelectedLog] = useState<ProxyLog | null>(null);
const [resourceFilter, setResourceFilter] = useState<string[]>([]);
const columns: ColumnsType<ProxyLog> = [
{
title: '时间',
dataIndex: 'timestamp',
key: 'timestamp',
render: (timestamp: number) => new Date(timestamp).toLocaleString()
},
{
title: 'URL',
dataIndex: 'url',
key: 'url',
ellipsis: true
},
{
title: (
<Space>
类型
{resourceFilter.length > 0 && <FilterFilled style={{ color: '#f50' }} />}
</Space>
),
dataIndex: 'resourceType',
key: 'resourceType',
render: (type: string) => {
const typeMap: Record<string, string> = {
xhr: 'XHR',
fetch: 'Fetch',
script: 'JS',
stylesheet: 'CSS',
image: 'Image',
other: 'Other'
};
return typeMap[type] || 'Other';
},
filters: [
{ text: 'XHR', value: 'xhr' },
{ text: 'Fetch', value: 'fetch' },
{ text: 'JS', value: 'script' },
{ text: 'CSS', value: 'stylesheet' },
{ text: 'Image', value: 'image' },
{ text: 'Other', value: 'other' }
],
filterMode: 'menu' as const,
filtered: resourceFilter.length > 0,
onFilter: (value: string, record: ProxyLog) => record.resourceType === value
},
{
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 filteredLogs = logs.filter(log => {
if (resourceFilter.length === 0) return true;
return resourceFilter.includes(log.resourceType || 'other');
});
return (
<Space direction="vertical" style={{ width: '100%' }}>
<div style={{
marginBottom: 16,
display: 'flex',
justifyContent: 'flex-end'
}}>
<Button
danger
onClick={onClearLogs}
icon={<DeleteOutlined />}
>
清除日志
</Button>
</div>
<Table
dataSource={filteredLogs}
columns={columns}
onRow={(record) => ({
onClick: () => setSelectedLog(record),
style: { cursor: 'pointer' }
})}
pagination={{
pageSize: 10,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total) => `共 ${total} 条`,
pageSizeOptions: ['10', '20', '50', '100']
}}
rowKey="id"
/>
<LogDetail
log={selectedLog}
onClose={() => setSelectedLog(null)}
/>
</Space>
);
};
@@ -1,65 +0,0 @@
/* 表格样式 */
.proxy-table {
background: white;
border-radius: 8px;
}
/* 表头样式 */
.proxy-table .ant-table-thead > tr > th {
background: white !important;
color: #333;
font-weight: 500;
border-bottom: 1px solid var(--border-color);
padding: 12px 16px;
}
/* 斑马纹样式 */
.proxy-table .ant-table-tbody > tr:nth-child(even) {
background-color: #fafafa;
}
/* 单元格样式 */
.proxy-table .ant-table-tbody > tr > td {
border-bottom: 1px solid var(--border-color);
padding: 12px 16px;
}
/* 操作列图标样式 */
.action-icon {
font-size: 16px;
cursor: pointer;
color: #666;
transition: all 0.3s;
}
.action-icon:hover {
color: var(--yakit-primary);
transform: scale(1.1);
}
.action-icon.enabled {
color: var(--yakit-primary);
}
.action-icon.delete:hover {
color: #ff4d4f;
}
/* 间距调整 */
.ant-space-middle {
gap: 16px !important;
}
/* 添加按钮样式 */
.add-proxy-btn {
background-color: var(--yakit-primary);
color: white;
border: none;
border-radius: 4px;
margin-bottom: 16px;
}
.add-proxy-btn:hover {
background-color: var(--yakit-primary-hover) !important;
color: white !important;
}
@@ -1,461 +0,0 @@
import React, { useRef, useState, useEffect, useCallback } from 'react';
import { Card, Input, Space, Button, Select, InputNumber, Form, Table, Tooltip, Popover, Modal, message } from 'antd';
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[];
onAdd: (config: ProxyConfig) => void;
onChange: (configId: string, field: keyof ProxyConfig | 'config', value: any) => void;
onDelete: (configId: string) => void;
onApply: (configId: string) => Promise<void>;
onClear: (configId: string) => Promise<void>;
}
// 修改 EditFormData 接口
interface EditFormData {
name: string;
proxyType: "direct" | "system" | "fixed_servers" | "pac_script" | "auto_detect";
scheme?: "http" | "https" | "socks4" | "socks5";
host?: string;
port?: number;
pacScript?: string;
bypassList?: string;
matchList?: string; // 仅用于 UI 编辑
proxyServer?: string; // 添加 proxyServer 字段,用于 PAC 脚本模式选择代理服务器
}
// 或者更好的方式是创建一个专门的类型
type ProxyConfigField = keyof ProxyConfig | 'config';
export const ProxySettings: React.FC<ProxySettingsProps> = ({
proxyConfigs,
onAdd,
onChange,
onDelete,
onApply,
onClear
}) => {
const [editingConfig, setEditingConfig] = useState<ProxyConfig | null>(null);
const [editModalVisible, setEditModalVisible] = useState(false);
const [form] = Form.useForm<EditFormData>();
// 添加 useEffect 来监听表单值变化
useEffect(() => {
if (editModalVisible && editingConfig) {
form.setFieldsValue({
name: editingConfig.name,
proxyType: editingConfig.proxyType,
scheme: editingConfig.scheme,
host: editingConfig.host,
port: editingConfig.port,
bypassList: editingConfig.bypassList?.join('\n') || '',
matchList: editingConfig.matchList?.join('\n') || '',
proxyServer: editingConfig.host && editingConfig.port
? `${editingConfig.host}:${editingConfig.port}`
: undefined
});
}
}, [editModalVisible, editingConfig, form]);
// 处理添加按钮点击
const handleAdd = useCallback(() => {
setEditingConfig({
id: Date.now().toString(),
name: '',
proxyType: 'fixed_servers',
scheme: 'http' as "http" | "https" | "socks4" | "socks5",
host: '127.0.0.1',
port: 8080,
enabled: false
});
setEditModalVisible(true);
}, []);
// 处理编辑按钮点击
const handleEdit = (record: ProxyConfig) => {
setEditingConfig(record);
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 {
const values = await form.validateFields();
if (editingConfig) {
if (editingConfig.enabled) {
await onClear(editingConfig.id);
}
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)
: [""];
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);
} else {
await onChange(editingConfig.id, 'config', updatedConfig);
}
setEditModalVisible(false);
setEditingConfig(null);
form.resetFields();
}
} catch (error) {
console.error('Validate Failed:', error);
message.error('保存失败,请检查表单');
}
};
// 处理模态框关闭
const handleModalClose = () => {
form.resetFields();
setEditModalVisible(false);
setEditingConfig(null);
};
const columns: ColumnsType<ProxyConfig> = [
{
title: '名称',
dataIndex: 'name',
key: 'name',
render: (text: string) => text
},
{
title: '类型',
dataIndex: 'proxyType',
key: 'proxyType',
render: (text: string) => {
const typeMap = {
direct: '直接连接',
fixed_servers: '代理服务器',
pac_script: 'PAC 脚本'
};
return typeMap[text as keyof typeof typeMap] || text;
}
},
{
title: '协议',
dataIndex: 'scheme',
key: 'scheme'
},
{
title: '主机',
dataIndex: 'host',
key: 'host'
},
{
title: '端口',
dataIndex: 'port',
key: 'port'
},
{
title: '操作',
key: 'action',
width: 120,
render: (_, record: ProxyConfig) => (
<Space size="middle">
<CheckOutlined
className={`action-icon ${record.enabled ? 'enabled' : ''}`}
onClick={async () => {
if (record.enabled) {
await onClear(record.id);
} else {
await onApply(record.id);
}
}}
/>
<EditOutlined
className="action-icon"
onClick={() => handleEdit(record)}
/>
{record.id !== 'direct' && (
<DeleteOutlined
className="action-icon delete"
onClick={() => onDelete(record.id)}
/>
)}
</Space>
)
}
];
const buttonRef = useRef(null);
// 过滤掉固定模式的代理
const filteredProxyConfigs = proxyConfigs.filter(
config => !['direct', 'system'].includes(config.id)
);
return (
<div>
<Space style={{ marginBottom: 16, justifyContent: 'flex-end', width: '100%' }}>
<Button
className="add-proxy-btn"
onClick={handleAdd}
icon={<PlusOutlined />}
>
添加代理
</Button>
</Space>
<Table
className="proxy-table"
dataSource={filteredProxyConfigs}
columns={columns}
rowKey="id"
pagination={false}
bordered={false}
size="middle"
/>
<Modal
title="编辑代理配置"
open={editModalVisible}
onOk={handleEditSave}
onCancel={handleModalClose}
destroyOnClose
>
<Form
form={form}
layout="vertical"
preserve={false}
>
<Form.Item
name="name"
label="名称"
rules={[{ required: true }]}
>
<Input />
</Form.Item>
<Form.Item
name="proxyType"
label="类型"
rules={[{ required: true }]}
>
<Select
options={[
// { label: '直接连接', value: 'direct' },
{ label: '代理服务器', value: 'fixed_servers' },
{ label: 'PAC 脚本', value: 'pac_script' }
]}
onChange={(value) => {
// 当类型改变时,清除相关字段
if (value !== 'fixed_servers') {
form.setFieldsValue({
scheme: undefined,
host: undefined,
port: undefined
});
}
}}
/>
</Form.Item>
<Form.Item
noStyle
shouldUpdate={(prevValues, currentValues) => prevValues.proxyType !== currentValues.proxyType}
>
{({ getFieldValue }) => {
const proxyType = getFieldValue('proxyType');
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>
</Modal>
</div>
);
};
@@ -1,167 +0,0 @@
import { useState, useEffect } from 'react';
import { ProxyConfig } from '@/types/proxy';
import { ProxyActionType } from '@/types/action';
import { message } from 'antd';
export const useProxyConfigs = () => {
const [proxyConfigs, setProxyConfigs] = useState<ProxyConfig[]>([]);
useEffect(() => {
loadConfigs();
const messageListener = (message: any) => {
if (message.action === 'PROXY_CONFIGS_UPDATED') {
loadConfigs();
}
};
chrome.runtime.onMessage.addListener(messageListener);
return () => {
chrome.runtime.onMessage.removeListener(messageListener);
};
}, []);
const loadConfigs = async () => {
try {
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.GET_PROXY_CONFIGS
});
if (response.success) {
setProxyConfigs(response.data || []);
}
} catch (error) {
console.error('Error loading configs:', error);
message.error('加载配置时发生错误');
}
};
const handleAddProxy = async (config: ProxyConfig) => {
try {
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.ADD_PROXY_CONFIG,
config: config
});
if (response.success) {
message.success('添加代理成功');
loadConfigs();
} else {
message.error(response.error || '添加代理失败');
}
} catch (error) {
console.error('Error adding proxy:', error);
message.error('添加代理时发生错误');
}
};
const handleConfigChange = async (configId: string, field: keyof ProxyConfig, value: any) => {
try {
const updatedConfigs = proxyConfigs.map(config =>
config.id === configId ? { ...config, [field]: value } : config
);
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.UPDATE_PROXY_CONFIG,
configs: updatedConfigs
});
if (response?.success) {
setProxyConfigs(response.data || updatedConfigs);
await chrome.runtime.sendMessage({
action: 'PROXY_CONFIGS_UPDATED'
});
message.success('更新配置成功');
} else {
message.error(response?.error || '更新配置失败');
await loadConfigs();
}
} catch (error) {
console.error('Error updating config:', error);
message.error('更新配置时发生错误');
await loadConfigs();
}
};
const handleDeleteProxy = async (configId: string) => {
try {
console.log('Deleting proxy:', configId);
const updatedConfigs = proxyConfigs.filter(config => config.id !== configId);
console.log('Updated configs after delete:', updatedConfigs);
const response = await new Promise<any>((resolve) => {
chrome.runtime.sendMessage({
action: ProxyActionType.UPDATE_PROXY_CONFIG,
configs: updatedConfigs
}, (result) => {
console.log('Delete response received:', result);
resolve(result);
});
});
console.log('Delete response:', response);
if (response?.success) {
setProxyConfigs(response.data || updatedConfigs);
message.success('删除代理成功');
} else {
console.error('Delete failed:', response?.error);
message.error(response?.error || '删除代理失败');
await loadConfigs();
}
} catch (error) {
console.error('Error deleting proxy:', error);
message.error('删除代理时发生错误');
await loadConfigs();
}
};
const handleApplyConfig = async (configId: string) => {
try {
const config = proxyConfigs.find(c => c.id === configId);
if (!config) return;
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 || '应用代理设置失败');
}
} catch (error) {
console.error('Error applying proxy:', error);
message.error('应用代理设置时发生错误');
}
};
const handleClearProxy = async () => {
try {
const response = await chrome.runtime.sendMessage({
action: ProxyActionType.CLEAR_PROXY_CONFIG
});
if (response.success) {
message.success('已切换至直接连接');
} else {
message.error(response.error || '清除代理设置失败');
}
} catch (error) {
console.error('Error clearing proxy:', error);
message.error('清除代理设置时发生错误');
}
};
return {
proxyConfigs,
handleAddProxy,
handleConfigChange,
handleDeleteProxy,
handleApplyConfig,
handleClearProxy
};
};
@@ -1,59 +0,0 @@
import { useState, useEffect } from 'react';
import { ProxyLog } from '@/types/proxy';
import { ProxyActionType } from '@/types/action';
export const useProxyLogs = () => {
const [proxyLogs, setProxyLogs] = useState<ProxyLog[]>([]);
useEffect(() => {
loadLogs();
// 监听日志更新
const handleLogsUpdate = () => {
loadLogs();
};
chrome.runtime.onMessage.addListener((message) => {
if (message.action === 'PROXY_LOGS_UPDATED') {
handleLogsUpdate();
}
});
return () => {
chrome.runtime.onMessage.removeListener(handleLogsUpdate);
};
}, []);
const loadLogs = async () => {
try {
console.log('Fetching proxy logs...');
const response = await chrome.runtime.sendMessage({
action: 'GET_PROXY_LOGS'
});
console.log('Received response:', response);
if (response.success) {
setProxyLogs(response.data || []);
} else {
console.error('Failed to load logs:', response.error);
}
} catch (error) {
console.error('Error loading logs:', error);
}
};
const handleClearLogs = async () => {
try {
await chrome.runtime.sendMessage({
action: ProxyActionType.CLEAR_PROXY_LOGS
});
setProxyLogs([]);
} catch (error) {
console.error('Error clearing logs:', error);
}
};
return {
proxyLogs,
handleClearLogs
};
};
-111
View File
@@ -1,111 +0,0 @@
.options-page {
height: 100vh;
overflow: auto;
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);
}
/* 防止 Modal 出现时页面跳动 */
.ant-modal-wrap {
overflow: hidden;
}
.ant-modal-content {
max-height: 90vh;
overflow: auto;
}
-139
View File
@@ -1,139 +0,0 @@
import React, { useState, useEffect, useRef } from 'react';
import { Layout, Tabs, message } from 'antd';
import { ProxySettings } from './components/ProxySettings';
import { ProxyLogs } from './components/ProxyLogs';
import { useProxyConfigs } from './hooks/useProxyConfigs';
import { useProxyLogs } from './hooks/useProxyLogs';
import { ProxyConfig } from '@/types/proxy';
import { ProxyActionType } from '@/types/action';
const { Content } = Layout;
interface ProxySettingsProps {
proxyConfigs: ProxyConfig[];
onAdd: (config: ProxyConfig) => void;
onChange: (configId: string, field: keyof ProxyConfig | 'config', value: any) => void;
onDelete: (configId: string) => void;
onApply: (configId: string) => Promise<void>;
onClear: (configId: string) => Promise<void>;
}
export const OptionsPage: React.FC = () => {
const {
proxyConfigs,
handleAddProxy,
handleConfigChange: handleConfigChangeHook,
handleDeleteProxy,
handleApplyConfig,
handleClearProxy
} = useProxyConfigs();
const { proxyLogs, handleClearLogs } = useProxyLogs();
const [proxyConfigsState, setProxyConfigs] = useState<ProxyConfig[]>([]);
useEffect(() => {
setProxyConfigs(proxyConfigs);
}, [proxyConfigs]);
// 通知 background 页面已准备就绪
useEffect(() => {
chrome.runtime.sendMessage({ action: 'OPTIONS_PAGE_READY' });
const messageListener = (
message: any,
sender: chrome.runtime.MessageSender,
sendResponse: (response?: any) => void
) => {
if (message.action === 'TRIGGER_ADD_PROXY') {
const proxySettingsElement = document.querySelector('.add-proxy-btn');
if (proxySettingsElement) {
(proxySettingsElement as HTMLElement).click();
}
}
sendResponse();
};
chrome.runtime.onMessage.addListener(messageListener);
return () => {
chrome.runtime.onMessage.removeListener(messageListener);
};
}, []);
const handleAdd = async (config: ProxyConfig) => {
try {
await handleAddProxy(config);
} catch (error) {
console.error('Failed to add proxy:', error);
message.error('添加代理失败');
}
};
const handleConfigChange = async (configId: string, field: keyof ProxyConfig | 'config', value: any) => {
try {
const updatedConfigs = proxyConfigsState.map(config => {
if (config.id === configId) {
if (field === 'config') {
// 如果是整个配置更新
return value;
} else {
// 如果是单个字段更新
return {
...config,
[field]: value
};
}
}
return config;
});
// 更新 IndexedDB
await chrome.runtime.sendMessage({
action: ProxyActionType.UPDATE_PROXY_CONFIG,
configs: updatedConfigs
});
// 更新本地状态
setProxyConfigs(updatedConfigs);
message.success('更新配置成功');
} catch (error) {
console.error('Failed to update config:', error);
message.error('更新配置失败');
}
};
return (
<Layout style={{ height: '100vh' }}>
<Content style={{ padding: '24px' }}>
<Tabs
defaultActiveKey="1"
items={[
{
key: '1',
label: '代理设置',
children: (
<ProxySettings
proxyConfigs={proxyConfigsState}
onAdd={handleAdd}
onChange={handleConfigChange}
onDelete={handleDeleteProxy}
onApply={handleApplyConfig}
onClear={handleClearProxy}
/>
)
},
{
key: '2',
label: '代理日志',
children: (
<ProxyLogs
logs={proxyLogs}
onClearLogs={handleClearLogs}
/>
)
}
]}
/>
</Content>
</Layout>
);
};
-16
View File
@@ -1,16 +0,0 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { App } from 'antd';
import { OptionsPage } from './OptionsPage';
import '@/styles/global.css';
const container = document.getElementById('root');
if (!container) throw new Error('Failed to find the root element');
const root = createRoot(container);
root.render(
<App>
<OptionsPage />
</App>
);
-58
View File
@@ -1,58 +0,0 @@
:root {
--yakit-primary: #F28B44;
--yakit-primary-hover: #f4a061;
--yakit-primary-active: #e87633;
--yakit-primary-5: #fff5eb;
--yakit-primary-10: rgba(242, 139, 68, 0.1);
/* 添加其他全局变量 */
--border-color: #f0f0f0;
--text-color: #333;
--icon-color: #666;
/* 菜单相关变量 */
--menu-item-height: 28px;
--menu-padding: 4px;
--menu-width: 180px;
}
.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;
}
/* 移除所有滚动条 */
::-webkit-scrollbar {
display: none;
}
/* 确保所有内容都在视口内 */
html, body {
overflow: hidden;
height: fit-content;
}
-24
View File
@@ -1,24 +0,0 @@
// export const ActionType = {
// CONNECT: "CONNECT",
// SEND_MESSAGE: "SEND_MESSAGE",
// DISCONNECT: "DISCONNECT",
// SET_PROXY: "SET_PROXY",
// CLEAR_PROXY: "CLEAR_PROXY",
// PROXY_STATUS: "PROXY_STATUS",
// INJECT_SCRIPT: "INJECT_SCRIPT"
// } as const;
// export type ActionType = typeof ActionType[keyof typeof ActionType];
export const ProxyActionType = {
SET_PROXY_CONFIG: "SET_PROXY_CONFIG",
CLEAR_PROXY_CONFIG: "CLEAR_PROXY_CONFIG",
GET_PROXY_STATUS: "GET_PROXY_STATUS",
CLEAR_PROXY_LOGS: "CLEAR_PROXY_LOGS",
GET_PROXY_LOGS: "GET_PROXY_LOGS",
GET_PROXY_CONFIGS: "GET_PROXY_CONFIGS",
ADD_PROXY_CONFIG: "ADD_PROXY_CONFIG",
UPDATE_PROXY_CONFIG: "UPDATE_PROXY_CONFIG"
} as const;
export type ProxyActionType = typeof ProxyActionType[keyof typeof ProxyActionType];
-10
View File
@@ -1,10 +0,0 @@
declare namespace chrome.storage {
interface StorageChange {
oldValue?: any;
newValue?: any;
}
type StorageChanges = {
[key: string]: StorageChange;
};
}
-19
View File
@@ -1,19 +0,0 @@
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_servers';
enabled?: boolean;
id?: string;
name?: string;
timestamp?: number;
}
-45
View File
@@ -1,45 +0,0 @@
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?: PacScript;
bypassList?: string[];
matchList?: string[];
}
export interface ProxyLog {
id: string;
timestamp: number;
url: string;
proxyId: string;
proxyName: string;
status: 'success' | 'error';
errorMessage?: string;
method?: string;
requestHeaders?: Record<string, string>;
requestBody?: string;
responseHeaders?: Record<string, string>;
responseBody?: string;
timing?: {
startTime: number;
endTime: number;
duration: number;
};
protocol?: string;
ip?: string;
fromCache?: boolean;
host?: string;
port?: number;
resourceType?: 'xhr' | 'fetch' | 'script' | 'stylesheet' | 'image' | 'other';
}