Skip to content

Commit 4c805fe

Browse files
committed
feat: support ua settings
1 parent 32fcad5 commit 4c805fe

File tree

10 files changed

+244
-24
lines changed

10 files changed

+244
-24
lines changed

bun.lock

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,16 @@
3232
"zod": "^4.1.12",
3333
},
3434
"devDependencies": {
35-
"@tauri-apps/cli": "^2.9.1",
35+
"@tauri-apps/cli": "^2.9.2",
3636
"@types/bytes": "^3.1.5",
37-
"@types/node": "^22.18.12",
37+
"@types/node": "^22.18.13",
3838
"@types/react": "^19.2.2",
3939
"@types/react-dom": "^19.2.2",
4040
"@types/unzipper": "^0.10.11",
4141
"@vitejs/plugin-react": "^4.7.0",
4242
"daisyui": "^5.3.10",
4343
"husky": "^9.1.7",
44-
"tar": "^7.5.1",
44+
"tar": "^7.5.2",
4545
"typescript": "~5.6.3",
4646
"unzipper": "^0.12.3",
4747
"vite": "^6.4.1",
@@ -319,7 +319,7 @@
319319

320320
"csstype": ["[email protected]", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
321321

322-
"daisyui": ["daisyui@5.3.10", "", {}, "sha512-vmjyPmm0hvFhA95KB6uiGmWakziB2pBv6CUcs5Ka/3iMBMn9S+C3SZYx9G9l2JrgTZ1EFn61F/HrPcwaUm2kLQ=="],
322+
"daisyui": ["daisyui@5.4.2", "", {}, "sha512-yLoRFlx5hKvn5ODpT7CVb9oU/fAF2X1BGuLmVZo4LN33r7hcmO8v+gcxB6l33mcMas5jut3lZwHj9erqbMvvEA=="],
323323

324324
"debug": ["[email protected]", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ=="],
325325

@@ -455,7 +455,7 @@
455455

456456
"tapable": ["[email protected]", "", {}, "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg=="],
457457

458-
"tar": ["[email protected].1", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g=="],
458+
"tar": ["[email protected].2", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg=="],
459459

460460
"tinyglobby": ["[email protected]", "", { "dependencies": { "fdir": "^6.4.4", "picomatch": "^4.0.2" } }, "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ=="],
461461

lang/en.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,17 @@
174174
"use_dhcp": "DHCP",
175175
"version": "Version",
176176
"vpn_network": "VPN",
177+
"ua_option_default": "default",
178+
"ua_option_sfi_1_12": "sing-box 1.12 iOS ",
179+
"ua_option_sfm_1_12": "sing-box 1.12 mac",
180+
"ua_option_sfa_1_12": "sing-box 1.12 android",
181+
"ua_option_custom": "custom",
182+
"user_agent_settings": "User Agent Settings",
183+
"open_user_agent": "Open user agent settings",
184+
"custom_ua_placeholder": "Enter custom User Agent",
185+
"ua_hint": "Select or enter a custom User Agent for requests",
186+
"ua_cannot_empty": "User Agent cannot be empty",
187+
"ua_saved": "User Agent settings saved successfully",
188+
"ua_save_failed": "Failed to save User Agent settings",
177189
"zzzzzeof": "Zzzzzeof"
178190
}

lang/zh.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,5 +174,17 @@
174174
"use_dhcp": "DHCP",
175175
"version": "版本",
176176
"vpn_network": "VPN",
177+
"ua_option_default": "默认",
178+
"ua_option_sfi_1_12": "sing-box 1.12 iOS ",
179+
"ua_option_sfm_1_12": "sing-box 1.12 mac",
180+
"ua_option_sfa_1_12": "sing-box 1.12 android",
181+
"ua_option_custom": "自定义",
182+
"user_agent_settings": "User Agent 设置",
183+
"open_user_agent": "打开 UA 设置",
184+
"custom_ua_placeholder": "输入自定义 User Agent",
185+
"ua_hint": "为请求选择或输入自定义 User Agent",
186+
"ua_cannot_empty": "User Agent 不能为空",
187+
"ua_saved": "User Agent 设置保存成功",
188+
"ua_save_failed": "保存 User Agent 设置失败",
177189
"zzzzzeof": "zzzzzeof"
178190
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@
4646
"@types/react": "^19.2.2",
4747
"@types/react-dom": "^19.2.2",
4848
"@vitejs/plugin-react": "^4.7.0",
49-
"daisyui": "^5.3.10",
49+
"daisyui": "^5.4.2",
5050
"husky": "^9.1.7",
51-
"tar": "^7.5.1",
51+
"tar": "^7.5.2",
5252
"typescript": "~5.6.3",
5353
"unzipper": "^0.12.3",
5454
"vite": "^6.4.1"

src-tauri/Cargo.lock

Lines changed: 12 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import { useEffect, useState } from "react";
2+
import { Save, TextIndentLeft, X } from "react-bootstrap-icons";
3+
import { toast } from "sonner";
4+
import { t } from "../../utils/helper";
5+
import { SettingItem } from "../settings/common";
6+
7+
const UA_OPTIONS = [
8+
{
9+
key: "default",
10+
label: t("ua_option_default", "default"),
11+
value: "default"
12+
},
13+
{
14+
key: "sfm_1_12",
15+
label: t("ua_option_sfm_1_12", "sfm 1.12"),
16+
value: "SFM/1.12.9 (Build 1; sing-box 1.12.12; language zh_CN)"
17+
},
18+
{
19+
key: "sfa_1_12",
20+
label: t("ua_option_sfa_1_12", "sfa 1.12"),
21+
value: "SFA/1.12.9 (Build 1; sing-box 1.12.12; language zh_CN)"
22+
},
23+
{
24+
key: "sfi_1_12",
25+
label: t("ua_option_sfi_1_12", "sfi 1.12"),
26+
value: "SFI/1.12.9 (Build 1; sing-box 1.12.12; language zh_CN)"
27+
},
28+
{
29+
key: "custom",
30+
label: t("ua_option_custom", "custom"),
31+
value: ""
32+
}
33+
];
34+
35+
import { getUserAgent, setUserAgent } from "../../single/store";
36+
37+
export default function UASettingsItem() {
38+
const [isOpen, setIsOpen] = useState(false);
39+
const [selectedUA, setSelectedUA] = useState("default");
40+
const [customUA, setCustomUA] = useState("");
41+
const [isLoading, setIsLoading] = useState(false);
42+
43+
useEffect(() => {
44+
if (isOpen) {
45+
loadUA();
46+
}
47+
}, [isOpen]);
48+
49+
const loadUA = async () => {
50+
const ua = await getUserAgent();
51+
const option = UA_OPTIONS.find(opt => opt.value === ua);
52+
if (option) {
53+
setSelectedUA(option.key);
54+
} else {
55+
setSelectedUA("custom");
56+
setCustomUA(ua);
57+
}
58+
};
59+
60+
const handleOpen = () => {
61+
setIsOpen(true);
62+
};
63+
64+
const handleClose = () => {
65+
setIsOpen(false);
66+
};
67+
68+
const handleSave = async () => {
69+
setIsLoading(true);
70+
try {
71+
const uaValue = selectedUA === "custom"
72+
? customUA
73+
: UA_OPTIONS.find(opt => opt.key === selectedUA)?.value || "default";
74+
75+
if (selectedUA === "custom" && !customUA.trim()) {
76+
toast.error(t("ua_cannot_empty", "User Agent cannot be empty"));
77+
return;
78+
}
79+
80+
await setUserAgent(uaValue);
81+
toast.success(t("ua_saved", "User Agent settings saved successfully"));
82+
handleClose();
83+
} catch (error) {
84+
toast.error(t("ua_save_failed", "Failed to save User Agent settings"));
85+
} finally {
86+
setIsLoading(false);
87+
}
88+
};
89+
90+
return (
91+
<>
92+
<SettingItem
93+
icon={<TextIndentLeft className="w-5 h-5 text-gray-500" />}
94+
title={t("user_agent_settings", "User Agent Settings")}
95+
subTitle={t("open_user_agent", "Open user agent settings")}
96+
disabled={false}
97+
onPress={handleOpen}
98+
/>
99+
100+
{isOpen && (
101+
<div className="fixed inset-0 z-50 flex items-center justify-center px-4">
102+
<div
103+
className="absolute inset-0 bg-gray-400/60"
104+
onClick={handleClose}
105+
/>
106+
107+
<div className="relative bg-white rounded-lg p-3 w-80 max-w-full">
108+
<div className="flex items-center justify-between mb-4">
109+
<div className="flex items-center gap-2">
110+
<TextIndentLeft size={14} className="text-gray-500" />
111+
<h3 className="text-xs font-medium text-gray-700">
112+
{t("user_agent_settings", "User Agent Settings")}
113+
</h3>
114+
</div>
115+
<button
116+
onClick={handleClose}
117+
className="hover:bg-gray-100 rounded p-1 transition-colors"
118+
>
119+
<X size={14} className="text-gray-500" />
120+
</button>
121+
</div>
122+
123+
<div className="flex flex-col gap-6">
124+
<div>
125+
<select
126+
className="select select-sm select-ghost border-[0.8px] border-gray-200 "
127+
128+
value={selectedUA}
129+
onChange={(e) => setSelectedUA(e.target.value)}
130+
>
131+
{UA_OPTIONS.map(option => (
132+
<option key={option.key} value={option.key}>
133+
{option.label}
134+
</option>
135+
))}
136+
</select>
137+
138+
{selectedUA === "custom" && (
139+
<input
140+
type="text"
141+
className="w-full px-3 py-2 mt-6 text-xs rounded border border-gray-300 focus:border-gray-400 focus:ring-1 focus:ring-gray-400 outline-none transition-colors "
142+
placeholder={t("custom_ua_placeholder", "Enter custom User Agent")}
143+
value={customUA}
144+
onChange={(e) => setCustomUA(e.target.value)}
145+
/>
146+
)}
147+
148+
<p className="text-xs text-gray-500 mt-2">
149+
{t("ua_hint", "Select or enter a custom User Agent for requests")}
150+
</p>
151+
</div>
152+
</div>
153+
154+
<div className="flex justify-end gap-2 mt-6">
155+
<button
156+
className="px-3 py-1 text-xs rounded bg-transparent hover:bg-gray-100 text-gray-600 transition-colors"
157+
onClick={handleClose}
158+
>
159+
{t("cancel", "Cancel")}
160+
</button>
161+
<button
162+
className="flex items-center gap-1.5 px-3 py-1 text-xs bg-gray-600 text-white hover:bg-gray-700 rounded transition-colors disabled:opacity-50"
163+
onClick={handleSave}
164+
disabled={isLoading}
165+
>
166+
<Save size={14} />
167+
{isLoading ? t("saving", "Saving...") : t("save", "Save")}
168+
</button>
169+
</div>
170+
</div>
171+
</div>
172+
)}
173+
</>
174+
);
175+
}

src/page/developer.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import DNSSettingsItem from "../components/developer/dns-settings";
99
import ToggleLocalConfig from "../components/developer/local-config-toggle";
1010
import StageSetting from "../components/developer/select-stage";
1111
import TunStackSetting from "../components/developer/tun-stack";
12+
import UASettingsItem from "../components/developer/ua-settings";
1213
import { t } from "../utils/helper";
1314

1415
const appWindow = getCurrentWindow();
@@ -33,7 +34,7 @@ export default function Page() {
3334
<TunStackSetting />
3435

3536
<DNSSettingsItem />
36-
37+
<UASettingsItem />
3738
<SettingItem
3839
icon={<JournalText className="w-5 h-5 text-gray-500" />}
3940

src/single/store.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { locale, type } from '@tauri-apps/plugin-os';
22
import { LazyStore } from '@tauri-apps/plugin-store';
33
import { toast } from 'sonner';
4-
import { ALLOWLAN_STORE_KEY, ENABLE_BYPASS_ROUTER_STORE_KEY, ENABLE_TUN_STORE_KEY, USE_DHCP_STORE_KEY } from '../types/definition';
4+
import { ALLOWLAN_STORE_KEY, ENABLE_BYPASS_ROUTER_STORE_KEY, ENABLE_TUN_STORE_KEY, USE_DHCP_STORE_KEY, USER_AGENT_STORE_KEY } from '../types/definition';
55

66
const OsType = type();
77
export const LANGUAGE_STORE_KEY = 'language';
@@ -176,4 +176,19 @@ export async function getDirectDNS(): Promise<string> {
176176
return s;
177177
}
178178
return '223.5.5.5';
179+
}
180+
181+
// 获取用户设置的 User Agent
182+
export async function getUserAgent(): Promise<string> {
183+
const ua = await store.get(USER_AGENT_STORE_KEY) as string | undefined;
184+
if (ua) {
185+
return ua;
186+
}
187+
return 'default';
188+
}
189+
190+
// 设置 User Agent
191+
export async function setUserAgent(ua: string) {
192+
await store.set(USER_AGENT_STORE_KEY, ua);
193+
await store.save();
179194
}

src/types/definition.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ export const TUN_STACK_STORE_KEY = 'tun_stack_key'
1010
export const USE_DHCP_STORE_KEY = 'use_dhcp_key'
1111
export const ENABLE_BYPASS_ROUTER_STORE_KEY = 'enable_bypass_router_key'
1212
export const SUPPORT_LOCAL_FILE_STORE_KEY = 'support_local_file_key'
13-
13+
// User Agent 配置键
14+
export const USER_AGENT_STORE_KEY = 'user_agent_key'
1415

1516
// 允许局域网连接
1617
export const ALLOWLAN_STORE_KEY = 'allow_lan_key'

0 commit comments

Comments
 (0)