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.
This commit is contained in:
Neil
2026-10-19 23:13:50 -07:00
committed by GitHub
parent f884b9ff05
commit db6b3fd04f
2 changed files with 25 additions and 4 deletions
@@ -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<string> | 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
}
@@ -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()
}
})