fix: address multi-browser routing issues
- Move recording cleanup before connection delete (was no-op) - Only fallback to default extension when extensionId is null - Add cwd param to /cli/session/new, CLI passes process.cwd() - Require pre-existing sessions for execute/reset endpoints - Remove duplicate ensureRelayServer call in CLI - Increase fetch timeouts from 500ms to 2000ms
This commit is contained in:
+29
-32
@@ -107,9 +107,11 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getExtensionConnection = (extensionId?: string | null): ExtensionConnection | null => {
|
const getExtensionConnection = (extensionId?: string | null): ExtensionConnection | null => {
|
||||||
if (extensionId && extensionConnections.has(extensionId)) {
|
// If specific extensionId requested, only return that one (no fallback)
|
||||||
|
if (extensionId) {
|
||||||
return extensionConnections.get(extensionId) || null
|
return extensionConnections.get(extensionId) || null
|
||||||
}
|
}
|
||||||
|
// Only fallback to default when no extensionId specified
|
||||||
const fallbackId = getDefaultExtensionId()
|
const fallbackId = getDefaultExtensionId()
|
||||||
if (fallbackId) {
|
if (fallbackId) {
|
||||||
return extensionConnections.get(fallbackId) || null
|
return extensionConnections.get(fallbackId) || null
|
||||||
@@ -1224,6 +1226,15 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
logger?.log(`Extension disconnected: code=${event.code} reason=${event.reason || 'none'} (${connectionId})`)
|
logger?.log(`Extension disconnected: code=${event.code} reason=${event.reason || 'none'} (${connectionId})`)
|
||||||
stopExtensionPing(connectionId)
|
stopExtensionPing(connectionId)
|
||||||
|
|
||||||
|
// Cancel any active recordings BEFORE removing connection (cancelRecording checks isExtensionConnected)
|
||||||
|
const recordingRelay = recordingRelays.get(connectionId)
|
||||||
|
if (recordingRelay) {
|
||||||
|
recordingRelay.cancelRecording({}).catch(() => {
|
||||||
|
// Ignore errors during cleanup
|
||||||
|
})
|
||||||
|
}
|
||||||
|
recordingRelays.delete(connectionId)
|
||||||
|
|
||||||
const connection = extensionConnections.get(connectionId)
|
const connection = extensionConnections.get(connectionId)
|
||||||
if (connection) {
|
if (connection) {
|
||||||
for (const pending of connection.pendingRequests.values()) {
|
for (const pending of connection.pendingRequests.values()) {
|
||||||
@@ -1234,7 +1245,6 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
}
|
}
|
||||||
|
|
||||||
extensionConnections.delete(connectionId)
|
extensionConnections.delete(connectionId)
|
||||||
recordingRelays.delete(connectionId)
|
|
||||||
|
|
||||||
for (const [clientId, client] of playwrightClients.entries()) {
|
for (const [clientId, client] of playwrightClients.entries()) {
|
||||||
if (client.extensionId !== connectionId) {
|
if (client.extensionId !== connectionId) {
|
||||||
@@ -1275,28 +1285,19 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
|
|
||||||
app.post('/cli/execute', async (c) => {
|
app.post('/cli/execute', async (c) => {
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json() as { sessionId: string; code: string; timeout?: number; cwd?: string }
|
const body = await c.req.json() as { sessionId: string; code: string; timeout?: number }
|
||||||
const { sessionId, code, timeout = 10000, cwd } = body
|
const { sessionId, code, timeout = 10000 } = body
|
||||||
|
|
||||||
if (!sessionId || !code) {
|
if (!sessionId || !code) {
|
||||||
return c.json({ error: 'sessionId and code are required' }, 400)
|
return c.json({ error: 'sessionId and code are required' }, 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
const manager = await getExecutorManager()
|
const manager = await getExecutorManager()
|
||||||
const defaultExtension = getExtensionConnection(null)
|
const existingExecutor = manager.getSession(sessionId)
|
||||||
const executor = manager.getExecutor({
|
if (!existingExecutor) {
|
||||||
sessionId,
|
return c.json({ text: `Session ${sessionId} not found. Run 'playwriter session new' first.`, images: [], isError: true }, 404)
|
||||||
cwd,
|
}
|
||||||
sessionMetadata: {
|
const result = await existingExecutor.execute(code, timeout)
|
||||||
extensionId: defaultExtension?.id || null,
|
|
||||||
browser: defaultExtension?.info.browser || null,
|
|
||||||
profile: defaultExtension?.info ? { email: defaultExtension.info.email || '', id: defaultExtension.info.id || '' } : null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const result = await executor.execute(code, timeout)
|
|
||||||
|
|
||||||
// Increment session counter after each execution to avoid conflicts
|
|
||||||
nextSessionNumber++
|
|
||||||
|
|
||||||
return c.json(result)
|
return c.json(result)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
@@ -1307,25 +1308,19 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
|
|
||||||
app.post('/cli/reset', async (c) => {
|
app.post('/cli/reset', async (c) => {
|
||||||
try {
|
try {
|
||||||
const body = await c.req.json() as { sessionId: string; cwd?: string }
|
const body = await c.req.json() as { sessionId: string }
|
||||||
const { sessionId, cwd } = body
|
const { sessionId } = body
|
||||||
|
|
||||||
if (!sessionId) {
|
if (!sessionId) {
|
||||||
return c.json({ error: 'sessionId is required' }, 400)
|
return c.json({ error: 'sessionId is required' }, 400)
|
||||||
}
|
}
|
||||||
|
|
||||||
const manager = await getExecutorManager()
|
const manager = await getExecutorManager()
|
||||||
const defaultExtension = getExtensionConnection(null)
|
const existingExecutor = manager.getSession(sessionId)
|
||||||
const executor = manager.getExecutor({
|
if (!existingExecutor) {
|
||||||
sessionId,
|
return c.json({ error: `Session ${sessionId} not found. Run 'playwriter session new' first.` }, 404)
|
||||||
cwd,
|
}
|
||||||
sessionMetadata: {
|
const { page, context } = await existingExecutor.reset()
|
||||||
extensionId: defaultExtension?.id || null,
|
|
||||||
browser: defaultExtension?.info.browser || null,
|
|
||||||
profile: defaultExtension?.info ? { email: defaultExtension.info.email || '', id: defaultExtension.info.id || '' } : null,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
const { page, context } = await executor.reset()
|
|
||||||
|
|
||||||
return c.json({
|
return c.json({
|
||||||
success: true,
|
success: true,
|
||||||
@@ -1348,9 +1343,10 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
})
|
})
|
||||||
|
|
||||||
app.post('/cli/session/new', async (c) => {
|
app.post('/cli/session/new', async (c) => {
|
||||||
const body = await c.req.json().catch(() => ({})) as { extensionId?: string | null }
|
const body = await c.req.json().catch(() => ({})) as { extensionId?: string | null; cwd?: string }
|
||||||
const sessionId = String(nextSessionNumber++)
|
const sessionId = String(nextSessionNumber++)
|
||||||
const extensionId = body.extensionId || null
|
const extensionId = body.extensionId || null
|
||||||
|
const cwd = body.cwd
|
||||||
const extension = getExtensionConnection(extensionId)
|
const extension = getExtensionConnection(extensionId)
|
||||||
if (!extension) {
|
if (!extension) {
|
||||||
return c.json({ error: 'Extension not connected' }, 404)
|
return c.json({ error: 'Extension not connected' }, 404)
|
||||||
@@ -1358,6 +1354,7 @@ export async function startPlayWriterCDPRelayServer({
|
|||||||
const manager = await getExecutorManager()
|
const manager = await getExecutorManager()
|
||||||
const executor = manager.getExecutor({
|
const executor = manager.getExecutor({
|
||||||
sessionId,
|
sessionId,
|
||||||
|
cwd,
|
||||||
sessionMetadata: {
|
sessionMetadata: {
|
||||||
extensionId: extension.id,
|
extensionId: extension.id,
|
||||||
browser: extension.info.browser || null,
|
browser: extension.info.browser || null,
|
||||||
|
|||||||
+5
-12
@@ -55,11 +55,11 @@ async function fetchExtensionsStatus(host?: string): Promise<Array<{
|
|||||||
try {
|
try {
|
||||||
const serverUrl = await getServerUrl(host)
|
const serverUrl = await getServerUrl(host)
|
||||||
const response = await fetch(`${serverUrl}/extensions/status`, {
|
const response = await fetch(`${serverUrl}/extensions/status`, {
|
||||||
signal: AbortSignal.timeout(500),
|
signal: AbortSignal.timeout(2000),
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const fallback = await fetch(`${serverUrl}/extension/status`, {
|
const fallback = await fetch(`${serverUrl}/extension/status`, {
|
||||||
signal: AbortSignal.timeout(500),
|
signal: AbortSignal.timeout(2000),
|
||||||
})
|
})
|
||||||
if (!fallback.ok) {
|
if (!fallback.ok) {
|
||||||
return []
|
return []
|
||||||
@@ -231,21 +231,14 @@ cli
|
|||||||
process.exit(1)
|
process.exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!options.host && !process.env.PLAYWRITER_HOST) {
|
|
||||||
await ensureRelayServer({ logger: console, env: cliRelayEnv })
|
|
||||||
const connected = await waitForExtension({ logger: console, timeoutMs: 10000 })
|
|
||||||
if (!connected) {
|
|
||||||
console.error('Warning: Extension not connected. Commands may fail.')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const serverUrl = await getServerUrl(options.host)
|
const serverUrl = await getServerUrl(options.host)
|
||||||
const extensionId = selectedExtension.extensionId === 'default' ? null : selectedExtension.extensionId
|
const extensionId = selectedExtension.extensionId === 'default' ? null : selectedExtension.extensionId
|
||||||
|
const cwd = process.cwd()
|
||||||
const response = await fetch(`${serverUrl}/cli/session/new`, {
|
const response = await fetch(`${serverUrl}/cli/session/new`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ extensionId }),
|
body: JSON.stringify({ extensionId, cwd }),
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
@@ -279,7 +272,7 @@ cli
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${serverUrl}/cli/sessions`, {
|
const response = await fetch(`${serverUrl}/cli/sessions`, {
|
||||||
signal: AbortSignal.timeout(500),
|
signal: AbortSignal.timeout(2000),
|
||||||
})
|
})
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error(`Error: ${response.status} ${await response.text()}`)
|
console.error(`Error: ${response.status} ${await response.text()}`)
|
||||||
|
|||||||
Reference in New Issue
Block a user