Some checks failed
build-packages / resolve bundled mosh-client (push) Has been cancelled
build-packages / resolve bundled et-client (push) Has been cancelled
build-packages / build-macos (push) Has been cancelled
build-packages / build-windows (push) Has been cancelled
build-packages / build-linux-x64 (push) Has been cancelled
build-packages / build-linux-arm64 (push) Has been cancelled
build-packages / release (push) Has been cancelled
build-packages / update Nix release metadata (push) Has been cancelled
build-packages / bump homebrew tap (push) Has been cancelled
test / lint-and-test (push) Has been cancelled
AI automation / Route event (push) Has been cancelled
AI automation / Hand reopened issue to maintainers (push) Has been cancelled
AI automation / Clean source issue state (push) Has been cancelled
AI automation / Reconcile handoffs (push) Has been cancelled
AI automation / Classify issue (push) Has been cancelled
AI automation / Claude Code smoke (push) Has been cancelled
AI automation / Review issue follow-up (push) Has been cancelled
AI automation / Publish issue follow-up (push) Has been cancelled
AI automation / Implement with Claude Code (push) Has been cancelled
AI automation / Publish implement PR (push) Has been cancelled
AI automation / Continue queued issue comments (push) Has been cancelled
AI automation / Codex review loop (push) Has been cancelled
AI automation / Publish Codex fix (push) Has been cancelled
AI automation / Clear Codex dispatch marker (push) Has been cancelled
AI automation / Own PR re-request Codex (push) Has been cancelled
AI automation / External PR re-request Codex (push) Has been cancelled
AI automation / Poll Codex reaction / retry (push) Has been cancelled
build-et-binaries / build-linux-x64 (push) Has been cancelled
build-et-binaries / build-linux-arm64 (push) Has been cancelled
build-et-binaries / build-macos-universal (push) Has been cancelled
build-et-binaries / build-windows-x64 (push) Has been cancelled
build-et-binaries / release (push) Has been cancelled
143 lines
4.3 KiB
TypeScript
143 lines
4.3 KiB
TypeScript
/**
|
|
* App-level HTTP(S) network proxy settings.
|
|
*
|
|
* Distinct from SSH ProxyJump / ProxyCommand profiles in the vault.
|
|
* Used by cloud sync (Google Drive / OneDrive / GitHub / WebDAV / S3),
|
|
* AI provider fetches, and other Chromium/Node outbound HTTPS traffic.
|
|
*/
|
|
|
|
export type HttpNetworkProxyMode = 'system' | 'direct' | 'custom';
|
|
|
|
export interface HttpNetworkProxySettings {
|
|
mode: HttpNetworkProxyMode;
|
|
/** Custom proxy URL, e.g. http://127.0.0.1:7890 or socks5://127.0.0.1:1080 */
|
|
url: string;
|
|
/** Comma-separated bypass hosts; Chromium also accepts `<local>`. */
|
|
bypass: string;
|
|
}
|
|
|
|
export const DEFAULT_HTTP_NETWORK_PROXY: HttpNetworkProxySettings = {
|
|
mode: 'system',
|
|
url: '',
|
|
bypass: '<local>',
|
|
};
|
|
|
|
const VALID_MODES = new Set<HttpNetworkProxyMode>(['system', 'direct', 'custom']);
|
|
|
|
function asTrimmedString(value: unknown): string {
|
|
return typeof value === 'string' ? value.trim() : '';
|
|
}
|
|
|
|
/**
|
|
* Strip userinfo from proxy URLs without rewriting the rest of the string.
|
|
* Electron `proxyRules` does not support credentials, and we must not persist
|
|
* proxy passwords in localStorage. Avoid `new URL()` round-trips so incomplete
|
|
* drafts like `http://127.0.0.1:` keep the trailing colon while the user types
|
|
* a port.
|
|
*/
|
|
export function sanitizeProxyUrl(proxyUrl: string): string {
|
|
const trimmed = asTrimmedString(proxyUrl);
|
|
if (!trimmed) return '';
|
|
// Strip userinfo for both scheme URLs (`http://user:pass@host`) and
|
|
// scheme-less drafts (`user:pass@host:8080`). Electron proxyRules does not
|
|
// support credentials and we must not persist proxy passwords.
|
|
return trimmed.replace(/^([a-z][a-z0-9+.-]*:\/\/)?([^/?#]*@)/i, '$1');
|
|
}
|
|
|
|
export function normalizeHttpNetworkProxySettings(
|
|
raw: unknown,
|
|
): HttpNetworkProxySettings {
|
|
if (!raw || typeof raw !== 'object') {
|
|
return { ...DEFAULT_HTTP_NETWORK_PROXY };
|
|
}
|
|
|
|
const record = raw as Record<string, unknown>;
|
|
const modeRaw = asTrimmedString(record.mode);
|
|
const mode: HttpNetworkProxyMode = VALID_MODES.has(modeRaw as HttpNetworkProxyMode)
|
|
? (modeRaw as HttpNetworkProxyMode)
|
|
: 'system';
|
|
const url = sanitizeProxyUrl(asTrimmedString(record.url));
|
|
const bypass = asTrimmedString(record.bypass) || DEFAULT_HTTP_NETWORK_PROXY.bypass;
|
|
|
|
if (mode === 'system') {
|
|
return { mode: 'system', url: '', bypass: DEFAULT_HTTP_NETWORK_PROXY.bypass };
|
|
}
|
|
|
|
if (mode === 'direct') {
|
|
return { mode: 'direct', url: '', bypass: DEFAULT_HTTP_NETWORK_PROXY.bypass };
|
|
}
|
|
|
|
// Allow custom mode with an empty URL so the settings UI can show the
|
|
// URL field before the user has typed anything. Electron apply paths
|
|
// treat empty custom as system until a URL is present.
|
|
return { mode: 'custom', url, bypass };
|
|
}
|
|
|
|
export function areHttpNetworkProxySettingsEqual(
|
|
a: HttpNetworkProxySettings,
|
|
b: HttpNetworkProxySettings,
|
|
): boolean {
|
|
return a.mode === b.mode && a.url === b.url && a.bypass === b.bypass;
|
|
}
|
|
|
|
/** Payload for Electron `session.setProxy`. */
|
|
export function buildElectronProxyConfig(
|
|
settings: HttpNetworkProxySettings,
|
|
): { mode: 'system' } | { mode: 'direct' } | {
|
|
mode: 'fixed_servers';
|
|
proxyRules: string;
|
|
proxyBypassRules: string;
|
|
} {
|
|
if (settings.mode === 'direct') return { mode: 'direct' };
|
|
if (settings.mode === 'custom') {
|
|
return {
|
|
mode: 'fixed_servers',
|
|
proxyRules: settings.url,
|
|
proxyBypassRules: settings.bypass || DEFAULT_HTTP_NETWORK_PROXY.bypass,
|
|
};
|
|
}
|
|
return { mode: 'system' };
|
|
}
|
|
|
|
export type NodeProxyEnv = {
|
|
HTTP_PROXY: string;
|
|
HTTPS_PROXY: string;
|
|
NO_PROXY: string;
|
|
http_proxy: string;
|
|
https_proxy: string;
|
|
no_proxy: string;
|
|
};
|
|
|
|
/**
|
|
* Env vars for Node `http`/`https`/`webdav`/`aws-sdk` stacks that honor
|
|
* HTTP(S)_PROXY. Returns `null` for system mode so callers leave process.env
|
|
* alone (Chromium `net.fetch` still uses OS proxy via session.setProxy).
|
|
*/
|
|
export function buildNodeProxyEnv(
|
|
settings: HttpNetworkProxySettings,
|
|
): NodeProxyEnv | null {
|
|
if (settings.mode === 'system') return null;
|
|
|
|
if (settings.mode === 'direct') {
|
|
return {
|
|
HTTP_PROXY: '',
|
|
HTTPS_PROXY: '',
|
|
NO_PROXY: '',
|
|
http_proxy: '',
|
|
https_proxy: '',
|
|
no_proxy: '',
|
|
};
|
|
}
|
|
|
|
const url = settings.url;
|
|
const bypass = settings.bypass || '';
|
|
return {
|
|
HTTP_PROXY: url,
|
|
HTTPS_PROXY: url,
|
|
NO_PROXY: bypass,
|
|
http_proxy: url,
|
|
https_proxy: url,
|
|
no_proxy: bypass,
|
|
};
|
|
}
|