Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 16 additions & 2 deletions packages/browser/bridge-browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,22 @@ function mountBridge(
const configRoute: WebRoute = {
kind: 'exact',
path: BRIDGE_CONFIG_PATH,
handler: (_req, res) => {
res.writeHead(200, { 'content-type': 'application/json' })
handler: (req, res) => {
// Chromium 142+ / Edge 143+ Local Network Access puts the extension's
// fetch to loopback through the CORS checks, so the discovery response
// carries the headers and answers the preflight. The payload holds no
// secret, and authentication still happens on the WebSocket handshake.
const cors = {
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET, OPTIONS',
'access-control-allow-private-network': 'true',
}
if (req.method === 'OPTIONS') {
res.writeHead(204, cors)
res.end()
return
}
res.writeHead(200, { 'content-type': 'application/json', ...cors })
res.end(JSON.stringify({ wsUrl: `ws://127.0.0.1:${ctx.webServer.port}${BRIDGE_PATH}` }))
},
}
Expand Down
12 changes: 11 additions & 1 deletion packages/browser/bridge-browser/tests/composition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,22 @@ describe('real Loader composition', () => {
expect(browserPrompt).toContain('Reuse that injected snapshot')
expect(browserPrompt).not.toMatch(/\p{Script=Han}/u)

// Zero-config discovery endpoint answers with the bridge WebSocket URL.
// Zero-config discovery endpoint answers with the bridge WebSocket URL, and
// carries the CORS headers plus preflight answer that Chromium 142+ / Edge
// 143+ Local Network Access requires of an extension fetching loopback.
const configResponse = await fetch(`http://127.0.0.1:${port}/ext/bridge-config`)
expect(configResponse.status).toBe(200)
expect(configResponse.headers.get('access-control-allow-origin')).toBe('*')
expect(configResponse.headers.get('access-control-allow-methods')).toBe('GET, OPTIONS')
expect(configResponse.headers.get('access-control-allow-private-network')).toBe('true')
const config = await configResponse.json() as { wsUrl?: unknown }
expect(typeof config.wsUrl).toBe('string')
expect(config.wsUrl).toBe(`ws://127.0.0.1:${port}/ext/bridge`)

const preflight = await fetch(`http://127.0.0.1:${port}/ext/bridge-config`, { method: 'OPTIONS' })
expect(preflight.status).toBe(204)
expect(preflight.headers.get('access-control-allow-origin')).toBe('*')
expect(preflight.headers.get('access-control-allow-private-network')).toBe('true')
expect(tools.get('browser_click')).toBeDefined()
expect(tools.get('browser_navigate')).toBeDefined()

Expand Down
Loading