From db6b3fd04f1059defae0af2d13b1ab26c00d3442 Mon Sep 17 00:00:00 2001 From: Neil <4138956+nwparker@users.noreply.github.com> Date: Mon, 19 Oct 2026 23:13:50 -0700 Subject: [PATCH] fix(web-client): a damaged browser file list disables only the browser client (#31025) A corrupt or truncated .orca-web-client-files.json threw while the remote server built its HTTP handler, which could stop the server from starting. Catch it, warn, and answer browser requests with 404 instead. --- .../runtime/rpc/static-web-client-handler.ts | 9 ++++++++- .../static-web-client-shared-assets.test.ts | 20 ++++++++++++++++--- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/main/runtime/rpc/static-web-client-handler.ts b/src/main/runtime/rpc/static-web-client-handler.ts index cd0b700f880..8560d5b33fe 100644 --- a/src/main/runtime/rpc/static-web-client-handler.ts +++ b/src/main/runtime/rpc/static-web-client-handler.ts @@ -25,7 +25,14 @@ export const answerWithoutWebClient: RequestListener = (_request, response) => { export function createStaticWebClientHandler(staticRoot: string): RequestListener { const resolvedRoot = resolve(staticRoot) - const files = readBundledWebClientFiles(resolvedRoot) + let files: ReadonlySet | undefined + try { + files = readBundledWebClientFiles(resolvedRoot) + } catch (error) { + // Why: a damaged install must disable only the browser client, not the runtime server. + console.warn(`[ws-transport] browser client disabled: ${String(error)}`) + return answerWithoutWebClient + } if (basename(resolvedRoot) === 'renderer' && !files) { return answerWithoutWebClient } diff --git a/src/main/runtime/rpc/static-web-client-shared-assets.test.ts b/src/main/runtime/rpc/static-web-client-shared-assets.test.ts index 710e895c6da..ccaca342313 100644 --- a/src/main/runtime/rpc/static-web-client-shared-assets.test.ts +++ b/src/main/runtime/rpc/static-web-client-shared-assets.test.ts @@ -3,7 +3,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs' import { createServer, type Server } from 'node:http' import { tmpdir } from 'node:os' import { dirname, join } from 'node:path' -import { afterEach, expect, it } from 'vitest' +import { afterEach, expect, it, vi } from 'vitest' import { createStaticWebClientHandler } from './static-web-client-handler' const roots: string[] = [] @@ -83,6 +83,20 @@ it('fails closed when a renderer has no manifest or a corrupt manifest', async ( rmSync(join(renderer, '.orca-web-client-files.json')) const endpoint = await serve(renderer) expect((await fetch(`${endpoint}/assets/desktop.js`)).status).toBe(404) - writeFileSync(join(renderer, '.orca-web-client-files.json'), '{') - expect(() => createStaticWebClientHandler(renderer)).toThrow() +}) +it('disables only the browser client when the manifest is truncated or invalid', async () => { + const warn = vi.spyOn(console, 'warn').mockImplementation(() => undefined) + try { + for (const manifest of ['{', JSON.stringify(['../index.html'])]) { + const { renderer } = fixture() + writeFileSync(join(renderer, '.orca-web-client-files.json'), manifest) + const endpoint = await serve(renderer) + for (const file of ['', 'web-index.html', 'assets/shared.js', 'assets/desktop.js']) { + expect((await fetch(`${endpoint}/${file}`)).status, file).toBe(404) + } + } + expect(warn).toHaveBeenCalledTimes(2) + } finally { + warn.mockRestore() + } })