From 8b4697d6053d59d918327e4ef550d194b60a5516 Mon Sep 17 00:00:00 2001 From: liangxu Date: Fri, 1 May 2026 17:20:00 +0800 Subject: [PATCH] chore(desktop-client): gate dev-only debug paths in packaged builds Wrap the network observer, renderer-devtools proxy (both the main-side ipc handler registration and the preload-side response listener), and the auto-open-devtools triggers behind !app.isPackaged so packaged builds don't ship debugging side channels. Add a tiny console-guard imported first in renderer/main.ts that no-ops console.* in import.meta.env.PROD so a packaged build stays quiet. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../src/main/execution-devtools.ts | 9 ++ .../src/main/network-observer.ts | 26 ++++ .../src/main/renderer-devtools-proxy.ts | 18 ++- apps/desktop-client/src/preload/bridge.ts | 112 +++++++++--------- .../src/renderer/console-guard.ts | 8 ++ apps/desktop-client/src/renderer/main.ts | 2 + 6 files changed, 117 insertions(+), 58 deletions(-) create mode 100644 apps/desktop-client/src/renderer/console-guard.ts diff --git a/apps/desktop-client/src/main/execution-devtools.ts b/apps/desktop-client/src/main/execution-devtools.ts index fa99ae6..9d44c43 100644 --- a/apps/desktop-client/src/main/execution-devtools.ts +++ b/apps/desktop-client/src/main/execution-devtools.ts @@ -1,3 +1,4 @@ +import { app } from "electron/main"; import type { WebContents } from "electron/main"; function normalizeBooleanEnv(value: string | undefined): boolean | null { @@ -16,6 +17,10 @@ function normalizeBooleanEnv(value: string | undefined): boolean | null { } export function shouldAutoOpenExecutionDevtools(): boolean { + if (app.isPackaged) { + return false; + } + const explicit = normalizeBooleanEnv(process.env.GEO_DESKTOP_EXECUTION_DEVTOOLS); if (explicit !== null) { return explicit; @@ -25,6 +30,10 @@ export function shouldAutoOpenExecutionDevtools(): boolean { } export function shouldAutoOpenBackgroundExecutionDevtools(): boolean { + if (app.isPackaged) { + return false; + } + const explicit = normalizeBooleanEnv(process.env.GEO_DESKTOP_EXECUTION_DEVTOOLS); if (explicit !== null) { return explicit; diff --git a/apps/desktop-client/src/main/network-observer.ts b/apps/desktop-client/src/main/network-observer.ts index f93a3f5..a9fad48 100644 --- a/apps/desktop-client/src/main/network-observer.ts +++ b/apps/desktop-client/src/main/network-observer.ts @@ -1,5 +1,6 @@ import { randomUUID } from "node:crypto"; +import { app } from "electron/main"; import type { Session, WebContents } from "electron/main"; import type { @@ -39,11 +40,16 @@ const requestFilter = { urls: ["http://*/*", "https://*/*", "ws://*/*", "wss://*/*"], }; const redactedQueryKeys = /token|signature|sig|auth|key|password|session/i; +const networkObserverEnabled = !app.isPackaged; let rendererTarget: WebContents | null = null; let originalFetch: typeof globalThis.fetch | null = null; export function installObservedGlobalFetch(): void { + if (!networkObserverEnabled) { + return; + } + if (originalFetch || typeof globalThis.fetch !== "function") { return; } @@ -79,6 +85,10 @@ export function installObservedGlobalFetch(): void { } export function registerObservedRequestRendererTarget(webContents: WebContents): void { + if (!networkObserverEnabled) { + return; + } + rendererTarget = webContents; webContents.once("destroyed", () => { if (rendererTarget === webContents) { @@ -91,6 +101,10 @@ export function observeSessionRequests( target: Session, options: { label: string; partition?: string | null }, ): void { + if (!networkObserverEnabled) { + return; + } + if (observedSessions.has(target)) { return; } @@ -175,6 +189,10 @@ export function observeSessionRequests( } export function startObservedRequest(options: StartObservedRequestOptions): string { + if (!networkObserverEnabled) { + return ""; + } + const id = `main:${randomUUID()}`; const active: ActiveObservedRequest = { id, @@ -239,6 +257,14 @@ export function failObservedRequest(id: string, error: unknown): void { } export function getObservedRequestSnapshot(): DesktopObservedRequestSnapshot { + if (!networkObserverEnabled) { + return { + rendererAttached: false, + activeCount: 0, + recent: [], + }; + } + return { rendererAttached: Boolean(rendererTarget && !rendererTarget.isDestroyed()), activeCount: activeRequests.size, diff --git a/apps/desktop-client/src/main/renderer-devtools-proxy.ts b/apps/desktop-client/src/main/renderer-devtools-proxy.ts index 762b1bf..65479ba 100644 --- a/apps/desktop-client/src/main/renderer-devtools-proxy.ts +++ b/apps/desktop-client/src/main/renderer-devtools-proxy.ts @@ -1,4 +1,4 @@ -import { ipcMain } from "electron/main"; +import { app, ipcMain } from "electron/main"; import type { IpcMainEvent, WebContents } from "electron/main"; interface RendererProxyRequest { @@ -26,10 +26,17 @@ const pendingRequests = new Map(); const rendererProxyRequestChannel = "desktop:renderer-devtools-proxy:request"; const rendererProxyResponseChannel = "desktop:renderer-devtools-proxy:response"; +const rendererDevtoolsProxyEnabled = !app.isPackaged; -ipcMain.on(rendererProxyResponseChannel, handleRendererProxyResponse); +if (rendererDevtoolsProxyEnabled) { + ipcMain.on(rendererProxyResponseChannel, handleRendererProxyResponse); +} export function registerRendererDevtoolsProxyTarget(webContents: WebContents): void { + if (!rendererDevtoolsProxyEnabled) { + return; + } + rendererWebContents = webContents; webContents.once("destroyed", () => { if (rendererWebContents === webContents) { @@ -39,12 +46,17 @@ export function registerRendererDevtoolsProxyTarget(webContents: WebContents): v } export function canUseRendererDevtoolsProxy(): boolean { - return Boolean(rendererWebContents && !rendererWebContents.isDestroyed()); + return rendererDevtoolsProxyEnabled + && Boolean(rendererWebContents && !rendererWebContents.isDestroyed()); } export async function rendererDevtoolsFetch( request: RendererProxyRequest, ): Promise { + if (!rendererDevtoolsProxyEnabled) { + throw new Error("renderer_devtools_proxy_unavailable"); + } + if (!rendererWebContents || rendererWebContents.isDestroyed()) { throw new Error("renderer_devtools_proxy_unavailable"); } diff --git a/apps/desktop-client/src/preload/bridge.ts b/apps/desktop-client/src/preload/bridge.ts index 21b0dcc..3fcbc84 100644 --- a/apps/desktop-client/src/preload/bridge.ts +++ b/apps/desktop-client/src/preload/bridge.ts @@ -35,64 +35,66 @@ interface DesktopAppSettings { const rendererProxyRequestChannel = "desktop:renderer-devtools-proxy:request"; const rendererProxyResponseChannel = "desktop:renderer-devtools-proxy:response"; -ipcRenderer.on( - rendererProxyRequestChannel, - async ( - _event, - payload: { - requestId?: unknown; - request?: { - url?: unknown; - method?: unknown; - headers?: unknown; - body?: unknown; - }; - }, - ) => { - const requestId = typeof payload?.requestId === "string" ? payload.requestId : ""; - const request = payload?.request; - if (!requestId || !request || typeof request !== "object") { - return; - } - - const url = typeof request.url === "string" ? request.url : ""; - const method = typeof request.method === "string" ? request.method : "GET"; - const headers = isStringRecord(request.headers) ? request.headers : {}; - const body = typeof request.body === "string" ? request.body : undefined; - - const response = await (async () => { - try { - const fetchResponse = await fetch(url, { - method, - headers, - body, - }); - const bodyText = await fetchResponse.text(); - return { - ok: fetchResponse.ok, - status: fetchResponse.status, - statusText: fetchResponse.statusText, - headers: Object.fromEntries(fetchResponse.headers.entries()), - bodyText, - }; - } catch (error) { - return { - ok: false, - status: 0, - statusText: "", - headers: {}, - bodyText: "", - error: String(error && ((error as Error).message || error)), +if (import.meta.env.DEV) { + ipcRenderer.on( + rendererProxyRequestChannel, + async ( + _event, + payload: { + requestId?: unknown; + request?: { + url?: unknown; + method?: unknown; + headers?: unknown; + body?: unknown; }; + }, + ) => { + const requestId = typeof payload?.requestId === "string" ? payload.requestId : ""; + const request = payload?.request; + if (!requestId || !request || typeof request !== "object") { + return; } - })(); - ipcRenderer.send(rendererProxyResponseChannel, { - requestId, - response, - }); - }, -); + const url = typeof request.url === "string" ? request.url : ""; + const method = typeof request.method === "string" ? request.method : "GET"; + const headers = isStringRecord(request.headers) ? request.headers : {}; + const body = typeof request.body === "string" ? request.body : undefined; + + const response = await (async () => { + try { + const fetchResponse = await fetch(url, { + method, + headers, + body, + }); + const bodyText = await fetchResponse.text(); + return { + ok: fetchResponse.ok, + status: fetchResponse.status, + statusText: fetchResponse.statusText, + headers: Object.fromEntries(fetchResponse.headers.entries()), + bodyText, + }; + } catch (error) { + return { + ok: false, + status: 0, + statusText: "", + headers: {}, + bodyText: "", + error: String(error && ((error as Error).message || error)), + }; + } + })(); + + ipcRenderer.send(rendererProxyResponseChannel, { + requestId, + response, + }); + }, + ); +} const desktopBridge = { app: { diff --git a/apps/desktop-client/src/renderer/console-guard.ts b/apps/desktop-client/src/renderer/console-guard.ts new file mode 100644 index 0000000..6232f8c --- /dev/null +++ b/apps/desktop-client/src/renderer/console-guard.ts @@ -0,0 +1,8 @@ +if (import.meta.env.PROD) { + const noop = () => undefined; + console.log = noop; + console.info = noop; + console.warn = noop; + console.error = noop; + console.debug = noop; +} diff --git a/apps/desktop-client/src/renderer/main.ts b/apps/desktop-client/src/renderer/main.ts index 263a0c0..a3d017b 100644 --- a/apps/desktop-client/src/renderer/main.ts +++ b/apps/desktop-client/src/renderer/main.ts @@ -1,3 +1,5 @@ +import "./console-guard"; + import { createApp } from "vue"; import App from "./App.vue";