feat: add CDP discovery endpoints (/json/version, /json/list)

Standard Chrome DevTools Protocol HTTP discovery endpoints that allow
tools like Playwright to connect using just the HTTP URL.

- /json/version returns browser info and webSocketDebuggerUrl
- /json/list returns array of connected targets
- Supports GET/PUT methods and trailing slashes for compatibility
This commit is contained in:
Tommy D. Rossi
2026-01-12 18:31:33 +01:00
parent f1b14f5ae1
commit 3ac560792a
+10 -12
View File
@@ -363,11 +363,9 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
const app = new Hono() const app = new Hono()
const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app }) const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app })
// Helper to build WebSocket URL from request host const getCdpWsUrl = (c: { req: { header: (name: string) => string | undefined } }) => {
const getCdpWsUrl = (reqHost: string | undefined) => { const hostHeader = c.req.header('host') || `${host}:${port}`
const hostHeader = reqHost || `${host}:${port}` return `ws://${hostHeader}/cdp`
const wsUrl = `ws://${hostHeader}/cdp`
return token ? `${wsUrl}?token=${token}` : wsUrl
} }
app.get('/', (c) => { app.get('/', (c) => {
@@ -383,7 +381,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
}) })
// CDP Discovery Endpoints - Standard Chrome DevTools Protocol HTTP API // CDP Discovery Endpoints - Standard Chrome DevTools Protocol HTTP API
// These allow tools like Playwright to discover the WebSocket URL automatically // Allows tools like Playwright to discover the WebSocket URL via http://host:port
// Spec: https://chromium.googlesource.com/chromium/src/+/main/content/browser/devtools/devtools_http_handler.cc // Spec: https://chromium.googlesource.com/chromium/src/+/main/content/browser/devtools/devtools_http_handler.cc
app app
@@ -391,18 +389,18 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
return c.json({ return c.json({
'Browser': `Playwriter/${VERSION}`, 'Browser': `Playwriter/${VERSION}`,
'Protocol-Version': '1.3', 'Protocol-Version': '1.3',
'webSocketDebuggerUrl': getCdpWsUrl(c.req.header('host')) 'webSocketDebuggerUrl': getCdpWsUrl(c)
}) })
}) })
.on(['GET', 'PUT'], '/json/version/', (c) => { .on(['GET', 'PUT'], '/json/version/', (c) => {
return c.json({ return c.json({
'Browser': `Playwriter/${VERSION}`, 'Browser': `Playwriter/${VERSION}`,
'Protocol-Version': '1.3', 'Protocol-Version': '1.3',
'webSocketDebuggerUrl': getCdpWsUrl(c.req.header('host')) 'webSocketDebuggerUrl': getCdpWsUrl(c)
}) })
}) })
.on(['GET', 'PUT'], '/json/list', (c) => { .on(['GET', 'PUT'], '/json/list', (c) => {
const wsUrl = getCdpWsUrl(c.req.header('host')) const wsUrl = getCdpWsUrl(c)
return c.json( return c.json(
Array.from(connectedTargets.values()).map(t => ({ Array.from(connectedTargets.values()).map(t => ({
id: t.targetId, id: t.targetId,
@@ -416,7 +414,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
) )
}) })
.on(['GET', 'PUT'], '/json/list/', (c) => { .on(['GET', 'PUT'], '/json/list/', (c) => {
const wsUrl = getCdpWsUrl(c.req.header('host')) const wsUrl = getCdpWsUrl(c)
return c.json( return c.json(
Array.from(connectedTargets.values()).map(t => ({ Array.from(connectedTargets.values()).map(t => ({
id: t.targetId, id: t.targetId,
@@ -430,7 +428,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
) )
}) })
.on(['GET', 'PUT'], '/json', (c) => { .on(['GET', 'PUT'], '/json', (c) => {
const wsUrl = getCdpWsUrl(c.req.header('host')) const wsUrl = getCdpWsUrl(c)
return c.json( return c.json(
Array.from(connectedTargets.values()).map(t => ({ Array.from(connectedTargets.values()).map(t => ({
id: t.targetId, id: t.targetId,
@@ -444,7 +442,7 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
) )
}) })
.on(['GET', 'PUT'], '/json/', (c) => { .on(['GET', 'PUT'], '/json/', (c) => {
const wsUrl = getCdpWsUrl(c.req.header('host')) const wsUrl = getCdpWsUrl(c)
return c.json( return c.json(
Array.from(connectedTargets.values()).map(t => ({ Array.from(connectedTargets.values()).map(t => ({
id: t.targetId, id: t.targetId,