diff --git a/src/common/types/settings.ts b/src/common/types/settings.ts index 6dcfb7ef..68870e1b 100644 --- a/src/common/types/settings.ts +++ b/src/common/types/settings.ts @@ -84,6 +84,7 @@ export interface UserSettings { installId?: string; // Unique install identifier for analytics machineId?: string; // @deprecated - legacy machine identifier retained for migrations pendingWhatsNewVersion?: string | null; // Version whose What's New should be shown after restart (auto-update) + httpProxy?: string; // HTTP proxy URL (e.g. http://user:pass@host:port), empty string means no proxy // Sort preferences installedToolsSort?: InstalledToolsSortOption; connectionsSort?: ConnectionsSortOption; diff --git a/src/main/index.ts b/src/main/index.ts index c12a683b..7b15754c 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,4 +1,4 @@ -import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, MenuItemConstructorOptions, nativeTheme, shell } from "electron"; +import { app, BrowserWindow, clipboard, dialog, ipcMain, Menu, MenuItemConstructorOptions, nativeTheme, session, shell } from "electron"; import * as fs from "fs"; import { createWriteStream } from "fs"; import * as http from "http"; @@ -396,6 +396,10 @@ class ToolBoxApp { ipcMain.handle(SETTINGS_CHANNELS.UPDATE_USER_SETTINGS, (_, settings) => { this.settingsManager.updateUserSettings(settings); this.api.emitEvent(ToolBoxEvent.SETTINGS_UPDATED, settings); + // Apply proxy immediately if the httpProxy setting changed + if ("httpProxy" in settings) { + this.applyProxySettings(settings.httpProxy ?? ""); + } }); ipcMain.handle(SETTINGS_CHANNELS.GET_SETTING, (_, key) => { @@ -2420,6 +2424,34 @@ class ToolBoxApp { this.sendWhatsNewRequest("auto-update", currentVersion); } + /** + * Apply HTTP proxy settings to the Electron session and Node.js environment variables. + * @param proxyUrl - Proxy URL (e.g. "http://user:pass@host:port") or empty string to clear. + */ + private applyProxySettings(proxyUrl: string): void { + const trimmed = proxyUrl.trim(); + if (trimmed) { + // Redact credentials from URL before logging + const redacted = trimmed.replace(/:\/\/([^:@]+:[^@]+@)/, "://@"); + // Configure Chromium-based network requests (renderer, BrowserViews) + session.defaultSession.setProxy({ proxyRules: trimmed }).catch((err) => { + logError(err instanceof Error ? err : new Error(String(err))); + }); + // Configure Node.js native network requests (main process fetch, http, https) + process.env.HTTP_PROXY = trimmed; + process.env.HTTPS_PROXY = trimmed; + logInfo(`[Proxy] HTTP proxy configured: ${redacted}`); + } else { + // Clear proxy — use direct connection + session.defaultSession.setProxy({ proxyRules: "" }).catch((err) => { + logError(err instanceof Error ? err : new Error(String(err))); + }); + delete process.env.HTTP_PROXY; + delete process.env.HTTPS_PROXY; + logInfo("[Proxy] HTTP proxy cleared — using direct connection"); + } + } + /** * Check Supabase connectivity * Tests if the Supabase API is accessible @@ -2776,6 +2808,12 @@ class ToolBoxApp { // Register protocol handler after app is ready this.browserviewProtocolManager.registerHandler(); + // Apply saved proxy settings before any network requests (apply even if empty to clear any env vars) + const savedProxy = this.settingsManager.getSetting("httpProxy"); + if (savedProxy !== undefined) { + this.applyProxySettings(savedProxy); + } + this.createWindow(); logCheckpoint("Main window created"); diff --git a/src/main/managers/settingsManager.ts b/src/main/managers/settingsManager.ts index 844c2e08..a68dec4b 100644 --- a/src/main/managers/settingsManager.ts +++ b/src/main/managers/settingsManager.ts @@ -28,6 +28,7 @@ export class SettingsManager { toolConnections: {}, // Map of toolId to connectionId toolSecondaryConnections: {}, // Map of toolId to secondary connectionId connectionsSort: "last-used", + httpProxy: "", // HTTP proxy URL, empty means no proxy }, }); diff --git a/src/renderer/index.html b/src/renderer/index.html index 5358154b..94b25256 100644 --- a/src/renderer/index.html +++ b/src/renderer/index.html @@ -378,6 +378,20 @@ +
+
+

Network

+

Configure a proxy server for all internet connections made by ToolBox.

+
+
+
+ + + Leave empty for a direct connection. Format: http://user:password@host:port. Credentials are stored in plain text in local settings. Applies to all ToolBox network requests on save. +
+
+
+

Updates

diff --git a/src/renderer/modules/globalSearchManagement.ts b/src/renderer/modules/globalSearchManagement.ts index 7569b924..25eae5bf 100644 --- a/src/renderer/modules/globalSearchManagement.ts +++ b/src/renderer/modules/globalSearchManagement.ts @@ -40,6 +40,7 @@ const SETTINGS_ENTRIES: Array<{ name: string; description: string; focusId?: str { name: "Terminal Font", description: "Customize the integrated terminal font", focusId: "sidebar-terminal-font-select" }, { name: "Deprecated Tools", description: "Control visibility of deprecated tools", focusId: "sidebar-deprecated-tools-select" }, { name: "Tool Display Mode", description: "Choose standard or compact tool display", focusId: "sidebar-tool-display-mode-select" }, + { name: "HTTP Proxy", description: "Configure a proxy server for internet connections", focusId: "sidebar-http-proxy-input" }, { name: "Connections", description: "Manage Dataverse connections" }, { name: "Installed Tools", description: "Browse installed tools" }, { name: "Marketplace", description: "Browse and install tools from the marketplace" }, diff --git a/src/renderer/modules/settingsManagement.ts b/src/renderer/modules/settingsManagement.ts index 4c036e9b..7c172786 100644 --- a/src/renderer/modules/settingsManagement.ts +++ b/src/renderer/modules/settingsManagement.ts @@ -26,6 +26,7 @@ export async function loadSidebarSettings(): Promise { const customFontInput = document.getElementById("sidebar-terminal-font-custom") as HTMLInputElement; const customFontContainer = document.getElementById("custom-font-input-container"); const notificationDurationSelect = document.getElementById("sidebar-notification-duration-select") as HTMLSelectElement | null; + const httpProxyInput = document.getElementById("sidebar-http-proxy-input") as HTMLInputElement | null; if (themeSelect && autoUpdateCheck && showDebugMenuCheck && deprecatedToolsSelect && toolDisplayModeSelect && terminalFontSelect) { const settings = await window.toolboxAPI.getUserSettings(); @@ -39,6 +40,7 @@ export async function loadSidebarSettings(): Promise { toolDisplayMode: settings.toolDisplayMode ?? "standard", terminalFont: settings.terminalFont || DEFAULT_TERMINAL_FONT, notificationDuration: settings.notificationDuration ?? DEFAULT_NOTIFICATION_DURATION, + httpProxy: settings.httpProxy ?? "", }; themeSelect.value = settings.theme; @@ -51,6 +53,10 @@ export async function loadSidebarSettings(): Promise { notificationDurationSelect.value = String(settings.notificationDuration ?? DEFAULT_NOTIFICATION_DURATION); } + if (httpProxyInput) { + httpProxyInput.value = settings.httpProxy ?? ""; + } + const terminalFont = settings.terminalFont || DEFAULT_TERMINAL_FONT; // Check if the font is a predefined option @@ -90,6 +96,7 @@ export async function saveSidebarSettings(): Promise { const terminalFontSelect = document.getElementById("sidebar-terminal-font-select") as any; // Fluent UI select element const customFontInput = document.getElementById("sidebar-terminal-font-custom") as HTMLInputElement; const notificationDurationSelect = document.getElementById("sidebar-notification-duration-select") as HTMLSelectElement | null; + const httpProxyInput = document.getElementById("sidebar-http-proxy-input") as HTMLInputElement | null; if (!themeSelect || !autoUpdateCheck || !showDebugMenuCheck || !deprecatedToolsSelect || !toolDisplayModeSelect || !terminalFontSelect) return; @@ -101,6 +108,7 @@ export async function saveSidebarSettings(): Promise { } const notificationDuration = notificationDurationSelect ? Number(notificationDurationSelect.value) : 5000; + const httpProxy = httpProxyInput ? httpProxyInput.value.trim() : ""; const currentSettings = { theme: themeSelect.value, @@ -110,6 +118,7 @@ export async function saveSidebarSettings(): Promise { toolDisplayMode: toolDisplayModeSelect.value, terminalFont: terminalFont, notificationDuration, + httpProxy, }; // Only include changed settings in the update @@ -136,6 +145,9 @@ export async function saveSidebarSettings(): Promise { if (currentSettings.notificationDuration !== originalSettings.notificationDuration) { changedSettings.notificationDuration = currentSettings.notificationDuration; } + if (currentSettings.httpProxy !== originalSettings.httpProxy) { + changedSettings.httpProxy = currentSettings.httpProxy; + } // Only save and emit event if something changed if (Object.keys(changedSettings).length > 0) { diff --git a/src/renderer/types/index.ts b/src/renderer/types/index.ts index 0db92ec4..3e156985 100644 --- a/src/renderer/types/index.ts +++ b/src/renderer/types/index.ts @@ -57,6 +57,7 @@ export interface SettingsState { toolDisplayMode?: string; terminalFont?: string; notificationDuration?: number; + httpProxy?: string; } /**