Files
orca/src/shared/network-proxy.ts
T
OrcaWinandOrcaWin 924d757ad6 fix(i18n): code shared validation errors; translate speech model, setup source and project group copy (#35737)
* fix(i18n): code shared validation errors and translate speech model, setup source and project group copy

* fix(i18n): keep detected label for unknown setup import providers

---------

Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
2026-10-28 23:49:27 -07:00

139 lines
3.9 KiB
TypeScript

export type NetworkProxySettings = {
httpProxyUrl?: string | null
httpProxyBypassRules?: string | null
}
// One English table: `message` stays English; the settings UI translates by code.
export const PROXY_URL_FAILURE_COPY = {
tooLong: 'Proxy URL is too long.',
invalid: 'Enter a valid proxy URL.',
unsupportedProtocol: 'Use an http, https, socks, socks4, or socks5 proxy URL.',
missingHost: 'Proxy URL must include a host.'
} as const
export type ProxyUrlFailureCode = keyof typeof PROXY_URL_FAILURE_COPY
export type ProxyUrlValidationResult =
| { ok: true; value: string; message?: undefined }
| { ok: false; value: ''; message: string; code: ProxyUrlFailureCode }
function proxyUrlFailure(code: ProxyUrlFailureCode): ProxyUrlValidationResult {
return { ok: false, value: '', message: PROXY_URL_FAILURE_COPY[code], code }
}
const PROXY_URL_MAX_LENGTH = 2048
const PROXY_BYPASS_RULES_MAX_LENGTH = 4096
const PROXY_PROTOCOLS = new Set(['http:', 'https:', 'socks:', 'socks4:', 'socks5:'])
const PROXY_ENV_KEYS = [
'HTTPS_PROXY',
'https_proxy',
'ALL_PROXY',
'all_proxy',
'HTTP_PROXY',
'http_proxy'
] as const
const NO_PROXY_ENV_KEYS = ['NO_PROXY', 'no_proxy'] as const
function formatProxyUrl(url: URL): string {
const auth =
url.username || url.password ? `${url.username}${url.password ? `:${url.password}` : ''}@` : ''
return `${url.protocol}//${auth}${url.host}`
}
export function normalizeProxyUrl(value: unknown): ProxyUrlValidationResult {
if (typeof value !== 'string') {
return { ok: true, value: '' }
}
const trimmed = value.trim()
if (!trimmed) {
return { ok: true, value: '' }
}
if (trimmed.length > PROXY_URL_MAX_LENGTH) {
return proxyUrlFailure('tooLong')
}
let parsed: URL
try {
parsed = new URL(trimmed)
} catch {
return proxyUrlFailure('invalid')
}
if (!PROXY_PROTOCOLS.has(parsed.protocol)) {
return proxyUrlFailure('unsupportedProtocol')
}
if (!parsed.hostname) {
return proxyUrlFailure('missingHost')
}
return { ok: true, value: formatProxyUrl(parsed) }
}
export function normalizeProxyBypassRules(value: unknown): string {
if (typeof value !== 'string') {
return ''
}
return value
.slice(0, PROXY_BYPASS_RULES_MAX_LENGTH)
.split(/[;,\n]/)
.map((rule) => rule.trim())
.filter(Boolean)
.join(';')
}
export function getProxyUrlFromEnvironment(
env: Record<string, string | undefined>
): ProxyUrlValidationResult {
for (const key of PROXY_ENV_KEYS) {
if (env[key]) {
return normalizeProxyUrl(env[key])
}
}
return { ok: true, value: '' }
}
export function getProxyBypassRulesFromEnvironment(
env: Record<string, string | undefined>
): string {
for (const key of NO_PROXY_ENV_KEYS) {
if (env[key]) {
return normalizeProxyBypassRules(env[key])
}
}
return ''
}
export function buildConfiguredProxyEnv(
settings: NetworkProxySettings | null | undefined
): Record<string, string> {
const proxy = normalizeProxyUrl(settings?.httpProxyUrl)
if (!proxy.ok || !proxy.value) {
return {}
}
const env: Record<string, string> = {
HTTP_PROXY: proxy.value,
HTTPS_PROXY: proxy.value,
ALL_PROXY: proxy.value,
http_proxy: proxy.value,
https_proxy: proxy.value,
all_proxy: proxy.value
}
const bypassRules = normalizeProxyBypassRules(settings?.httpProxyBypassRules)
// Explicit proxy settings must not inherit shell NO_PROXY; only the configured bypass field controls child-process exclusions.
const noProxy = bypassRules ? bypassRules.replaceAll(';', ',') : ''
env.NO_PROXY = noProxy
env.no_proxy = noProxy
return env
}
export function redactProxyUrl(value: string): string {
const parsed = normalizeProxyUrl(value)
if (!parsed.ok || !parsed.value) {
return parsed.value
}
const url = new URL(parsed.value)
if (url.username || url.password) {
url.username = '***'
url.password = url.password ? '***' : ''
}
return formatProxyUrl(url)
}