Skip to content

Commit ca6e56e

Browse files
CrystoraWendong-Fanclaude
authored
feat: add proxy configuration (#1096)
Co-authored-by: Wendong-Fan <w3ndong.fan@gmail.com> Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6dc6db7 commit ca6e56e

17 files changed

Lines changed: 336 additions & 16 deletions

File tree

electron/main/index.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import { registerUpdateIpcHandlers, update } from './update';
5151
import {
5252
getEmailFolderPath,
5353
getEnvPath,
54+
maskProxyUrl,
55+
readGlobalEnvKey,
5456
removeEnvKey,
5557
updateEnvBlock,
5658
} from './utils/envUtil';
@@ -77,6 +79,7 @@ let fileReader: FileReader | null = null;
7779
let python_process: ChildProcessWithoutNullStreams | null = null;
7880
let backendPort: number = 5001;
7981
let browser_port = 9222;
82+
let proxyUrl: string | null = null;
8083

8184
// Protocol URL queue for handling URLs before window is ready
8285
let protocolUrlQueue: string[] = [];
@@ -130,6 +133,16 @@ app.commandLine.appendSwitch('max_old_space_size', '4096');
130133
app.commandLine.appendSwitch('enable-features', 'MemoryPressureReduction');
131134
app.commandLine.appendSwitch('renderer-process-limit', '8');
132135

136+
// ==================== Proxy configuration ====================
137+
// Read proxy from global .env file on startup
138+
proxyUrl = readGlobalEnvKey('HTTP_PROXY');
139+
if (proxyUrl) {
140+
log.info(`[PROXY] Applying proxy configuration: ${maskProxyUrl(proxyUrl)}`);
141+
app.commandLine.appendSwitch('proxy-server', proxyUrl);
142+
} else {
143+
log.info('[PROXY] No proxy configured');
144+
}
145+
133146
// ==================== Anti-fingerprint settings ====================
134147
// Disable automation controlled indicator to avoid detection
135148
app.commandLine.appendSwitch('disable-blink-features', 'AutomationControlled');
@@ -992,6 +1005,16 @@ function registerIpcHandlers() {
9921005
return { success: true };
9931006
});
9941007

1008+
// ==================== read global env handler ====================
1009+
const ALLOWED_GLOBAL_ENV_KEYS = new Set(['HTTP_PROXY', 'HTTPS_PROXY']);
1010+
ipcMain.handle('read-global-env', async (_event, key: string) => {
1011+
if (!ALLOWED_GLOBAL_ENV_KEYS.has(key)) {
1012+
log.warn(`[ENV] Blocked read of disallowed global env key: ${key}`);
1013+
return { value: null };
1014+
}
1015+
return { value: readGlobalEnvKey(key) };
1016+
});
1017+
9951018
// ==================== new window handler ====================
9961019
ipcMain.handle('open-win', (_, arg) => {
9971020
const childWindow = new BrowserWindow({
@@ -1952,6 +1975,17 @@ app.whenReady().then(async () => {
19521975
session.fromPartition('persist:main_window').setUserAgent(normalUserAgent);
19531976
log.info('[ANTI-FINGERPRINT] User Agent set for all sessions');
19541977

1978+
// ==================== Apply proxy to Electron sessions ====================
1979+
if (proxyUrl) {
1980+
const proxyConfig = { proxyRules: proxyUrl };
1981+
await session.defaultSession.setProxy(proxyConfig);
1982+
await session.fromPartition('persist:user_login').setProxy(proxyConfig);
1983+
await session.fromPartition('persist:main_window').setProxy(proxyConfig);
1984+
log.info(
1985+
`[PROXY] Applied proxy to all sessions: ${maskProxyUrl(proxyUrl)}`
1986+
);
1987+
}
1988+
19551989
// ==================== download handle ====================
19561990
session.defaultSession.on('will-download', (event, item, _webContents) => {
19571991
item.once('done', (_event, _state) => {

electron/main/init.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import os from 'os';
2222
import path from 'path';
2323
import { promisify } from 'util';
2424
import { PromiseReturnType } from './install-deps';
25+
import { maskProxyUrl, readGlobalEnvKey } from './utils/envUtil';
2526
import {
2627
getBackendPath,
2728
getBinaryPath,
@@ -232,6 +233,25 @@ export async function startBackend(
232233
const uvEnv = getUvEnv(currentVersion);
233234
const globalEnvPath = path.join(os.homedir(), '.eigent', '.env');
234235

236+
// Load proxy configuration from global .env file
237+
const proxyUrl = readGlobalEnvKey('HTTP_PROXY');
238+
239+
// Build proxy env vars if configured
240+
const proxyEnv = proxyUrl
241+
? {
242+
HTTP_PROXY: proxyUrl,
243+
HTTPS_PROXY: proxyUrl,
244+
http_proxy: proxyUrl,
245+
https_proxy: proxyUrl,
246+
}
247+
: {};
248+
249+
if (proxyUrl) {
250+
log.info(
251+
`[BACKEND] Proxy configured for backend: ${maskProxyUrl(proxyUrl)}`
252+
);
253+
}
254+
235255
const envProxyEnabled = process.env.VITE_USE_LOCAL_PROXY === 'true';
236256
const envProxyUrl = process.env.VITE_PROXY_URL;
237257
let resolvedServerUrl: string | undefined;
@@ -242,21 +262,26 @@ export async function startBackend(
242262
if (resolvedServerUrl) {
243263
resolvedSource = 'process.env VITE_*';
244264
} else {
245-
log.warn('VITE_USE_LOCAL_PROXY is true but VITE_PROXY_URL is empty or invalid, ignoring');
265+
log.warn(
266+
'VITE_USE_LOCAL_PROXY is true but VITE_PROXY_URL is empty or invalid, ignoring'
267+
);
246268
}
247269
}
248270

249271
const devServerUrl = process.env.VITE_DEV_SERVER_URL;
250272
if (!resolvedServerUrl && devServerUrl) {
251273
const devEnvPath = path.join(app.getAppPath(), '.env.development');
252-
const devProxyEnabled = readEnvValue(devEnvPath, 'VITE_USE_LOCAL_PROXY') === 'true';
274+
const devProxyEnabled =
275+
readEnvValue(devEnvPath, 'VITE_USE_LOCAL_PROXY') === 'true';
253276
const devProxyUrl = readEnvValue(devEnvPath, 'VITE_PROXY_URL');
254277
if (devProxyEnabled) {
255278
resolvedServerUrl = buildLocalServerUrl(devProxyUrl);
256279
if (resolvedServerUrl) {
257280
resolvedSource = `dev env file (${devEnvPath})`;
258281
} else {
259-
log.warn(`VITE_USE_LOCAL_PROXY is true in ${devEnvPath} but VITE_PROXY_URL is empty or invalid, ignoring`);
282+
log.warn(
283+
`VITE_USE_LOCAL_PROXY is true in ${devEnvPath} but VITE_PROXY_URL is empty or invalid, ignoring`
284+
);
260285
}
261286
}
262287
}
@@ -275,11 +300,14 @@ export async function startBackend(
275300
}
276301

277302
const serverUrl = resolvedServerUrl || DEFAULT_SERVER_URL;
278-
log.info(`Backend SERVER_URL resolved to: ${serverUrl} (source: ${resolvedSource})`);
303+
log.info(
304+
`Backend SERVER_URL resolved to: ${serverUrl} (source: ${resolvedSource})`
305+
);
279306

280307
const env = {
281308
...process.env,
282309
...uvEnv,
310+
...proxyEnv,
283311
SERVER_URL: serverUrl,
284312
PYTHONIOENCODING: 'utf-8',
285313
PYTHONUNBUFFERED: '1',

electron/main/utils/envUtil.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,52 @@ export function removeEnvKey(lines: string[], key: string) {
8888
return [...lines.slice(0, start + 1), ...newBlock, ...lines.slice(end)];
8989
}
9090

91+
/**
92+
* Read the value of a key from the global ~/.eigent/.env file.
93+
*/
94+
export function readGlobalEnvKey(key: string): string | null {
95+
try {
96+
const globalEnvPath = path.join(os.homedir(), '.eigent', '.env');
97+
if (!fs.existsSync(globalEnvPath)) return null;
98+
const content = fs.readFileSync(globalEnvPath, 'utf-8');
99+
const prefix = key + '=';
100+
for (const line of content.split(/\r?\n/)) {
101+
if (line.startsWith(prefix)) {
102+
let value = line.slice(prefix.length).trim();
103+
// Strip surrounding quotes (single or double)
104+
if (
105+
(value.startsWith('"') && value.endsWith('"')) ||
106+
(value.startsWith("'") && value.endsWith("'"))
107+
) {
108+
value = value.slice(1, -1);
109+
}
110+
return value;
111+
}
112+
}
113+
} catch {
114+
// ignore read errors
115+
}
116+
return null;
117+
}
118+
119+
/**
120+
* Mask credentials in a proxy URL for safe logging.
121+
* e.g. "http://user:pass@host:port" → "http://***:***@host:port"
122+
*/
123+
export function maskProxyUrl(url: string): string {
124+
try {
125+
const parsed = new URL(url);
126+
if (parsed.username || parsed.password) {
127+
parsed.username = '***';
128+
parsed.password = '***';
129+
return parsed.toString();
130+
}
131+
} catch {
132+
// not a valid URL, return as-is
133+
}
134+
return url;
135+
}
136+
91137
export function getEmailFolderPath(email: string) {
92138
const tempEmail = email
93139
.split('@')[0]

electron/preload/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
150150
getEmailFolderPath: (email: string) =>
151151
ipcRenderer.invoke('get-email-folder-path', email),
152152
restartApp: () => ipcRenderer.invoke('restart-app'),
153+
readGlobalEnv: (key: string) => ipcRenderer.invoke('read-global-env', key),
153154
});
154155

155156
// --------- Preload scripts loading ---------

src/i18n/locales/ar/setting.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,13 @@
159159
"recommended": "موصى به",
160160
"reset": "إعادة تعيين",
161161
"reset-success": "تمت إعادة التعيين بنجاح!",
162-
"reset-failed": "فشل إعادة التعيين!"
162+
"reset-failed": "فشل إعادة التعيين!",
163+
164+
"network-proxy": "وكيل الشبكة",
165+
"network-proxy-description": "قم بتكوين خادم وكيل لطلبات الشبكة. هذا مفيد إذا كنت بحاجة إلى الوصول إلى واجهات برمجة التطبيقات الخارجية عبر وكيل.",
166+
"proxy-placeholder": "http://127.0.0.1:7890",
167+
"proxy-saved-restart-required": "تم حفظ تكوين الوكيل. أعد تشغيل التطبيق لتطبيق التغييرات.",
168+
"proxy-save-failed": "فشل حفظ تكوين الوكيل.",
169+
"proxy-invalid-url": "عنوان URL للوكيل غير صالح. يجب أن يبدأ بـ http:// أو https:// أو socks4:// أو socks5://.",
170+
"proxy-restart-hint": "يجب إعادة التشغيل لتطبيق تغييرات الوكيل."
163171
}

src/i18n/locales/de/setting.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,13 @@
159159
"recommended": "Empfohlen",
160160
"reset": "Zurücksetzen",
161161
"reset-success": "Zurücksetzen erfolgreich!",
162-
"reset-failed": "Zurücksetzen fehlgeschlagen!"
162+
"reset-failed": "Zurücksetzen fehlgeschlagen!",
163+
164+
"network-proxy": "Netzwerk-Proxy",
165+
"network-proxy-description": "Konfigurieren Sie einen Proxy-Server für Netzwerkanfragen. Dies ist nützlich, wenn Sie über einen Proxy auf externe APIs zugreifen müssen.",
166+
"proxy-placeholder": "http://127.0.0.1:7890",
167+
"proxy-saved-restart-required": "Proxy-Konfiguration gespeichert. Starten Sie die App neu, um die Änderungen anzuwenden.",
168+
"proxy-save-failed": "Proxy-Konfiguration konnte nicht gespeichert werden.",
169+
"proxy-invalid-url": "Ungültige Proxy-URL. Muss mit http://, https://, socks4:// oder socks5:// beginnen.",
170+
"proxy-restart-hint": "Neustart erforderlich, um Proxy-Änderungen anzuwenden."
163171
}

src/i18n/locales/en-us/setting.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,5 +186,13 @@
186186
"failed-to-delete-cookies": "Failed to delete cookies. Please try again.",
187187
"confirm-delete-all-cookies": "Are you sure you want to delete all cookies? This action cannot be undone.",
188188
"all-cookies-deleted": "All cookies have been deleted successfully.",
189-
"cookie-delete-warning": "Note: Deleting cookies will log you out of the associated websites. You may need to restart the browser to see changes take effect."
189+
"cookie-delete-warning": "Note: Deleting cookies will log you out of the associated websites. You may need to restart the browser to see changes take effect.",
190+
191+
"network-proxy": "Network Proxy",
192+
"network-proxy-description": "Configure a proxy server for network requests. This is useful if you need to access external APIs through a proxy.",
193+
"proxy-placeholder": "http://127.0.0.1:7890",
194+
"proxy-saved-restart-required": "Proxy configuration saved. Restart the app to apply changes.",
195+
"proxy-save-failed": "Failed to save proxy configuration.",
196+
"proxy-invalid-url": "Invalid proxy URL. Must start with http://, https://, socks4://, or socks5://.",
197+
"proxy-restart-hint": "Restart required to apply proxy changes."
190198
}

src/i18n/locales/es/setting.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,13 @@
159159
"recommended": "Recomendado",
160160
"reset": "Restablecer",
161161
"reset-success": "¡Restablecido correctamente!",
162-
"reset-failed": "¡Error al restablecer!"
162+
"reset-failed": "¡Error al restablecer!",
163+
164+
"network-proxy": "Proxy de red",
165+
"network-proxy-description": "Configure un servidor proxy para las solicitudes de red. Esto es útil si necesita acceder a APIs externas a través de un proxy.",
166+
"proxy-placeholder": "http://127.0.0.1:7890",
167+
"proxy-saved-restart-required": "Configuración de proxy guardada. Reinicie la aplicación para aplicar los cambios.",
168+
"proxy-save-failed": "Error al guardar la configuración del proxy.",
169+
"proxy-invalid-url": "URL de proxy no válida. Debe comenzar con http://, https://, socks4:// o socks5://.",
170+
"proxy-restart-hint": "Es necesario reiniciar para aplicar los cambios del proxy."
163171
}

src/i18n/locales/fr/setting.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,5 +135,13 @@
135135
"recommended": "Recommandé",
136136
"reset": "Réinitialiser",
137137
"reset-success": "Réinitialisation réussie !",
138-
"reset-failed": "Échec de la réinitialisation !"
138+
"reset-failed": "Échec de la réinitialisation !",
139+
140+
"network-proxy": "Proxy réseau",
141+
"network-proxy-description": "Configurez un serveur proxy pour les requêtes réseau. Utile si vous devez accéder à des API externes via un proxy.",
142+
"proxy-placeholder": "http://127.0.0.1:7890",
143+
"proxy-saved-restart-required": "Configuration du proxy enregistrée. Redémarrez l'application pour appliquer les modifications.",
144+
"proxy-save-failed": "Échec de l'enregistrement de la configuration du proxy.",
145+
"proxy-invalid-url": "URL de proxy invalide. Doit commencer par http://, https://, socks4:// ou socks5://.",
146+
"proxy-restart-hint": "Redémarrage nécessaire pour appliquer les modifications du proxy."
139147
}

src/i18n/locales/it/setting.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,5 +159,13 @@
159159
"recommended": "Consigliato",
160160
"reset": "Reimposta",
161161
"reset-success": "Reimpostazione riuscita!",
162-
"reset-failed": "Reimpostazione non riuscita!"
162+
"reset-failed": "Reimpostazione non riuscita!",
163+
164+
"network-proxy": "Proxy di rete",
165+
"network-proxy-description": "Configura un server proxy per le richieste di rete. Utile se devi accedere ad API esterne tramite un proxy.",
166+
"proxy-placeholder": "http://127.0.0.1:7890",
167+
"proxy-saved-restart-required": "Configurazione proxy salvata. Riavvia l'app per applicare le modifiche.",
168+
"proxy-save-failed": "Impossibile salvare la configurazione del proxy.",
169+
"proxy-invalid-url": "URL proxy non valido. Deve iniziare con http://, https://, socks4:// o socks5://.",
170+
"proxy-restart-hint": "Riavvio necessario per applicare le modifiche del proxy."
163171
}

0 commit comments

Comments
 (0)