feat: improve macos client proxy setup
This commit is contained in:
@@ -6,6 +6,24 @@ const DEFAULT_CLIENT_SETTINGS = {
|
||||
homeBypassEnabled: false,
|
||||
};
|
||||
|
||||
function normalizeProxyPort(value, fallback = settings.proxyPort) {
|
||||
const parsed = Number.parseInt(value, 10);
|
||||
const min = Number.isInteger(settings.clientProxyPortStart)
|
||||
? settings.clientProxyPortStart
|
||||
: settings.proxyPort;
|
||||
const max = Number.isInteger(settings.clientProxyPortEnd)
|
||||
? settings.clientProxyPortEnd
|
||||
: min;
|
||||
const fallbackPort =
|
||||
Number.isInteger(fallback) && fallback >= min && fallback <= max
|
||||
? fallback
|
||||
: min;
|
||||
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
||||
return fallbackPort;
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
function readJson(filePath, fallback) {
|
||||
try {
|
||||
if (!fs.existsSync(filePath)) return fallback;
|
||||
@@ -23,12 +41,14 @@ function writeJson(filePath, value) {
|
||||
export function normalizeClientSettings(input = {}) {
|
||||
return {
|
||||
homeBypassEnabled: Boolean(input.homeBypassEnabled),
|
||||
proxyPort: normalizeProxyPort(input.proxyPort),
|
||||
};
|
||||
}
|
||||
|
||||
export function readClientSettings() {
|
||||
return normalizeClientSettings({
|
||||
...DEFAULT_CLIENT_SETTINGS,
|
||||
proxyPort: settings.proxyPort,
|
||||
...readJson(settings.clientSettingsPath, {}),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ export const settings = {
|
||||
appMode: process.env.APP_MODE === "client" ? "client" : "gateway",
|
||||
port: Number(process.env.PORT || 3456),
|
||||
proxyPort: Number(process.env.PROXY_PORT || 8080),
|
||||
clientProxyPortStart: Number(process.env.CLIENT_PROXY_PORT_START || 8080),
|
||||
clientProxyPortEnd: Number(process.env.CLIENT_PROXY_PORT_END || 8090),
|
||||
tproxyPort: Number(process.env.TPROXY_PORT || 7895),
|
||||
bindIp: process.env.PROXY_BIND_IP || "0.0.0.0",
|
||||
dataDir,
|
||||
|
||||
@@ -598,15 +598,21 @@ function publicState() {
|
||||
const state = readJson(settings.statePath, {});
|
||||
const customRules = readJson(settings.customRulesPath, []);
|
||||
const deviceProfiles = readDeviceProfiles();
|
||||
const clientSettings = readClientSettings();
|
||||
const { subscriptionUrl, ...rest } = state;
|
||||
return {
|
||||
mode: settings.appMode,
|
||||
port: settings.port,
|
||||
proxyPort: settings.proxyPort,
|
||||
proxyPort:
|
||||
settings.appMode === "client" ? clientSettings.proxyPort : settings.proxyPort,
|
||||
clientProxyPortRange: {
|
||||
start: settings.clientProxyPortStart,
|
||||
end: settings.clientProxyPortEnd,
|
||||
},
|
||||
proxyBindIp: settings.bindIp,
|
||||
tproxyPort: settings.appMode === "gateway" ? settings.tproxyPort : null,
|
||||
routingRuDirect: settings.routingRuDirect,
|
||||
clientSettings: readClientSettings(),
|
||||
clientSettings,
|
||||
configExists: fs.existsSync(settings.configPath),
|
||||
singboxRunning: Boolean(singboxProcess),
|
||||
singboxStartedAt,
|
||||
|
||||
@@ -264,6 +264,7 @@ export function buildGatewayConfig(
|
||||
const clientOutbound = clientSettings?.homeBypassEnabled
|
||||
? "direct"
|
||||
: vpnOutbound.tag;
|
||||
const mixedProxyPort = clientSettings?.proxyPort || settings.proxyPort;
|
||||
const proxyOnlyRules = [{ inbound: [MIXED_INBOUND], outbound: clientOutbound }];
|
||||
const inbounds = [
|
||||
...(clientMode
|
||||
@@ -282,7 +283,7 @@ export function buildGatewayConfig(
|
||||
type: "mixed",
|
||||
tag: "mixed-in",
|
||||
listen: settings.bindIp,
|
||||
listen_port: settings.proxyPort,
|
||||
listen_port: mixedProxyPort,
|
||||
sniff: true,
|
||||
set_system_proxy: false,
|
||||
},
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { flagFor } from '../utils/country.js';
|
||||
import { formatBytes, formatRelative } from '../utils/format.js';
|
||||
|
||||
@@ -166,8 +166,21 @@ function ClientSetup({
|
||||
);
|
||||
}
|
||||
|
||||
function ProxyCard({ state }) {
|
||||
const port = state?.proxyPort || 8080;
|
||||
function ProxyCard({ state, settings, busy, onSave }) {
|
||||
const range = state?.clientProxyPortRange || { start: 8080, end: 8090 };
|
||||
const port = settings?.proxyPort || state?.proxyPort || 8080;
|
||||
const [draftPort, setDraftPort] = useState(String(port));
|
||||
|
||||
useEffect(() => {
|
||||
setDraftPort(String(port));
|
||||
}, [port]);
|
||||
|
||||
const parsedDraftPort = Number.parseInt(draftPort, 10);
|
||||
const portInvalid =
|
||||
!Number.isInteger(parsedDraftPort) ||
|
||||
parsedDraftPort < range.start ||
|
||||
parsedDraftPort > range.end;
|
||||
const portDirty = !portInvalid && parsedDraftPort !== port;
|
||||
const urls = useMemo(() => ({
|
||||
http: `http://127.0.0.1:${port}`,
|
||||
socks: `socks5://127.0.0.1:${port}`,
|
||||
@@ -179,6 +192,29 @@ function ProxyCard({ state }) {
|
||||
<h2>Локальный proxy</h2>
|
||||
<span className="badge info">127.0.0.1:{port}</span>
|
||||
</div>
|
||||
<div className="field" style={{ marginBottom: 12 }}>
|
||||
<label className="field-label">Порт proxy</label>
|
||||
<div className="subscription-input">
|
||||
<input
|
||||
className="input"
|
||||
type="number"
|
||||
min={range.start}
|
||||
max={range.end}
|
||||
value={draftPort}
|
||||
onChange={(e) => setDraftPort(e.target.value)}
|
||||
/>
|
||||
<button
|
||||
className="btn btn-secondary"
|
||||
disabled={busy || !portDirty}
|
||||
onClick={() => onSave({ ...settings, proxyPort: parsedDraftPort })}
|
||||
>
|
||||
Сохранить
|
||||
</button>
|
||||
</div>
|
||||
<small className={portInvalid ? 'field-error' : 'field-hint'}>
|
||||
Доступный диапазон: {range.start}–{range.end}
|
||||
</small>
|
||||
</div>
|
||||
<div className="copy-stack">
|
||||
<CopyField label="HTTP / HTTPS" value={urls.http} />
|
||||
<CopyField label="SOCKS5" value={urls.socks} />
|
||||
@@ -187,8 +223,9 @@ function ProxyCard({ state }) {
|
||||
);
|
||||
}
|
||||
|
||||
function HomeBypassCard({ settings, busy, onSave }) {
|
||||
function HomeBypassCard({ state, settings, busy, onSave }) {
|
||||
const enabled = Boolean(settings?.homeBypassEnabled);
|
||||
const port = settings?.proxyPort || state?.proxyPort || 8080;
|
||||
|
||||
return (
|
||||
<div className="card">
|
||||
@@ -199,7 +236,7 @@ function HomeBypassCard({ settings, busy, onSave }) {
|
||||
</span>
|
||||
</div>
|
||||
<p className="muted">
|
||||
Включайте дома: приложения продолжают использовать <code>127.0.0.1:8080</code>, но VPN не используется.
|
||||
Включайте дома: приложения продолжают использовать <code>127.0.0.1:{port}</code>, но VPN не используется.
|
||||
</p>
|
||||
<label className="switch-row">
|
||||
<span>
|
||||
@@ -288,8 +325,13 @@ export function ClientOverviewPage({
|
||||
onApply={onApply}
|
||||
/>
|
||||
<div className="grid-2">
|
||||
<ProxyCard state={state} />
|
||||
<HomeBypassCard settings={clientSettings} busy={busy} onSave={onSaveClientSettings} />
|
||||
<ProxyCard
|
||||
state={state}
|
||||
settings={clientSettings}
|
||||
busy={busy}
|
||||
onSave={onSaveClientSettings}
|
||||
/>
|
||||
<HomeBypassCard state={state} settings={clientSettings} busy={busy} onSave={onSaveClientSettings} />
|
||||
</div>
|
||||
<div className="grid-2">
|
||||
<ClientActions
|
||||
|
||||
Reference in New Issue
Block a user