add support for logging from extension
This commit is contained in:
@@ -51,3 +51,5 @@ each test() block should reset the extension connection to make sure tests are i
|
|||||||
NEVER call browser.close() in the tests
|
NEVER call browser.close() in the tests
|
||||||
|
|
||||||
remember that every time the extension is activated in a tab that tab gets added to the available pages. so if you toggle the extension and then do .newPage() there will be 2 pages, not 1.
|
remember that every time the extension is activated in a tab that tab gets added to the available pages. so if you toggle the extension and then do .newPage() there will be 2 pages, not 1.
|
||||||
|
|
||||||
|
to debug server or extension issues you can also inspect the file playwriter/relay-server.log to see both extension and server logs. with all cdp events sent. to see if there are events missing or something broken. this file is recreated every time the server is started and appended in real time. use rg to only read relevant lines and parts because it can get quite long
|
||||||
|
|||||||
+66
-66
@@ -1,4 +1,4 @@
|
|||||||
import { RelayConnection, debugLog } from './relayConnection'
|
import { RelayConnection, logger } from './relayConnection'
|
||||||
import { createStore } from 'zustand/vanilla'
|
import { createStore } from 'zustand/vanilla'
|
||||||
|
|
||||||
// Relay URL - fixed port for MCP bridge
|
// Relay URL - fixed port for MCP bridge
|
||||||
@@ -80,8 +80,8 @@ declare global {
|
|||||||
async function resetDebugger() {
|
async function resetDebugger() {
|
||||||
let targets = await chrome.debugger.getTargets()
|
let targets = await chrome.debugger.getTargets()
|
||||||
targets = targets.filter((x) => x.tabId && x.attached)
|
targets = targets.filter((x) => x.tabId && x.attached)
|
||||||
console.log(`found ${targets.length} existing debugger targets. detaching them before background script starts`)
|
logger.log(`found ${targets.length} existing debugger targets. detaching them before background script starts`)
|
||||||
console.log(targets)
|
logger.log(targets)
|
||||||
for (const target of targets) {
|
for (const target of targets) {
|
||||||
await chrome.debugger.detach({ tabId: target.tabId })
|
await chrome.debugger.detach({ tabId: target.tabId })
|
||||||
}
|
}
|
||||||
@@ -145,7 +145,7 @@ const icons = {
|
|||||||
} as const
|
} as const
|
||||||
|
|
||||||
useExtensionStore.subscribe(async (state, prevState) => {
|
useExtensionStore.subscribe(async (state, prevState) => {
|
||||||
console.log(state)
|
logger.log(state)
|
||||||
const { connectionState, connectedTabs, errorText } = state
|
const { connectionState, connectedTabs, errorText } = state
|
||||||
|
|
||||||
const tabs = await chrome.tabs.query({})
|
const tabs = await chrome.tabs.query({})
|
||||||
@@ -193,54 +193,54 @@ useExtensionStore.subscribe(async (state, prevState) => {
|
|||||||
async function ensureConnection(): Promise<void> {
|
async function ensureConnection(): Promise<void> {
|
||||||
const { connection } = useExtensionStore.getState()
|
const { connection } = useExtensionStore.getState()
|
||||||
if (connection) {
|
if (connection) {
|
||||||
debugLog('Connection already exists, reusing')
|
logger.debug('Connection already exists, reusing')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('No existing connection, creating new relay connection')
|
logger.debug('No existing connection, creating new relay connection')
|
||||||
debugLog('Waiting for server at http://localhost:19988...')
|
logger.debug('Waiting for server at http://localhost:19988...')
|
||||||
|
|
||||||
useExtensionStore.setState({ connectionState: 'reconnecting' })
|
useExtensionStore.setState({ connectionState: 'reconnecting' })
|
||||||
while (true) {
|
while (true) {
|
||||||
try {
|
try {
|
||||||
await fetch('http://localhost:19988', { method: 'HEAD' })
|
await fetch('http://localhost:19988', { method: 'HEAD' })
|
||||||
debugLog('Server is available')
|
logger.debug('Server is available')
|
||||||
break
|
break
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('Server not available, retrying in 1 second...')
|
logger.debug('Server not available, retrying in 1 second...')
|
||||||
await new Promise((resolve) => setTimeout(resolve, 1000))
|
await new Promise((resolve) => setTimeout(resolve, 1000))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Server is ready, creating WebSocket connection to:', RELAY_URL)
|
logger.debug('Server is ready, creating WebSocket connection to:', RELAY_URL)
|
||||||
const socket = new WebSocket(RELAY_URL)
|
const socket = new WebSocket(RELAY_URL)
|
||||||
debugLog('WebSocket created, initial readyState:', socket.readyState, '(0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)')
|
logger.debug('WebSocket created, initial readyState:', socket.readyState, '(0=CONNECTING, 1=OPEN, 2=CLOSING, 3=CLOSED)')
|
||||||
|
|
||||||
await new Promise<void>((resolve, reject) => {
|
await new Promise<void>((resolve, reject) => {
|
||||||
let timeoutFired = false
|
let timeoutFired = false
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
timeoutFired = true
|
timeoutFired = true
|
||||||
debugLog('=== WebSocket connection TIMEOUT after 5 seconds ===')
|
logger.debug('=== WebSocket connection TIMEOUT after 5 seconds ===')
|
||||||
debugLog('Final WebSocket readyState:', socket.readyState)
|
logger.debug('Final WebSocket readyState:', socket.readyState)
|
||||||
debugLog('WebSocket URL:', socket.url)
|
logger.debug('WebSocket URL:', socket.url)
|
||||||
debugLog('Socket protocol:', socket.protocol)
|
logger.debug('Socket protocol:', socket.protocol)
|
||||||
reject(new Error('Connection timeout'))
|
reject(new Error('Connection timeout'))
|
||||||
}, 5000)
|
}, 5000)
|
||||||
|
|
||||||
socket.onopen = () => {
|
socket.onopen = () => {
|
||||||
if (timeoutFired) {
|
if (timeoutFired) {
|
||||||
debugLog('WebSocket opened but timeout already fired!')
|
logger.debug('WebSocket opened but timeout already fired!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
debugLog('WebSocket onopen fired! readyState:', socket.readyState)
|
logger.debug('WebSocket onopen fired! readyState:', socket.readyState)
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
resolve()
|
resolve()
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.onerror = (error) => {
|
socket.onerror = (error) => {
|
||||||
debugLog('WebSocket onerror during connection:', error)
|
logger.debug('WebSocket onerror during connection:', error)
|
||||||
debugLog('Error type:', error.type)
|
logger.debug('Error type:', error.type)
|
||||||
debugLog('Current readyState:', socket.readyState)
|
logger.debug('Current readyState:', socket.readyState)
|
||||||
if (!timeoutFired) {
|
if (!timeoutFired) {
|
||||||
clearTimeout(timeout)
|
clearTimeout(timeout)
|
||||||
reject(new Error('WebSocket connection failed'))
|
reject(new Error('WebSocket connection failed'))
|
||||||
@@ -248,7 +248,7 @@ async function ensureConnection(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
socket.onclose = (event) => {
|
socket.onclose = (event) => {
|
||||||
debugLog('WebSocket onclose during connection setup:', {
|
logger.debug('WebSocket onclose during connection setup:', {
|
||||||
code: event.code,
|
code: event.code,
|
||||||
reason: event.reason,
|
reason: event.reason,
|
||||||
wasClean: event.wasClean,
|
wasClean: event.wasClean,
|
||||||
@@ -260,19 +260,19 @@ async function ensureConnection(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Event handlers set, waiting for connection...')
|
logger.debug('Event handlers set, waiting for connection...')
|
||||||
})
|
})
|
||||||
|
|
||||||
debugLog('WebSocket connected successfully, creating RelayConnection instance')
|
logger.debug('WebSocket connected successfully, creating RelayConnection instance')
|
||||||
const newConnection = new RelayConnection({
|
const newConnection = new RelayConnection({
|
||||||
ws: socket,
|
ws: socket,
|
||||||
onClose: (reason, code) => {
|
onClose: (reason, code) => {
|
||||||
debugLog('=== Relay connection onClose callback triggered ===', { reason, code })
|
logger.debug('=== Relay connection onClose callback triggered ===', { reason, code })
|
||||||
const { connectedTabs } = useExtensionStore.getState()
|
const { connectedTabs } = useExtensionStore.getState()
|
||||||
debugLog('Connected tabs before potential reconnection:', Array.from(connectedTabs.keys()))
|
logger.debug('Connected tabs before potential reconnection:', Array.from(connectedTabs.keys()))
|
||||||
|
|
||||||
if (reason === 'Extension Replaced' || code === 4001) {
|
if (reason === 'Extension Replaced' || code === 4001) {
|
||||||
debugLog('Connection replaced by another extension instance. Not reconnecting.')
|
logger.debug('Connection replaced by another extension instance. Not reconnecting.')
|
||||||
useExtensionStore.setState({
|
useExtensionStore.setState({
|
||||||
connection: undefined,
|
connection: undefined,
|
||||||
connectionState: 'error',
|
connectionState: 'error',
|
||||||
@@ -284,15 +284,15 @@ async function ensureConnection(): Promise<void> {
|
|||||||
useExtensionStore.setState({ connection: undefined, connectionState: 'disconnected' })
|
useExtensionStore.setState({ connection: undefined, connectionState: 'disconnected' })
|
||||||
|
|
||||||
if (connectedTabs.size > 0) {
|
if (connectedTabs.size > 0) {
|
||||||
debugLog('Tabs still connected, triggering reconnection')
|
logger.debug('Tabs still connected, triggering reconnection')
|
||||||
void reconnect()
|
void reconnect()
|
||||||
} else {
|
} else {
|
||||||
debugLog('No tabs to reconnect')
|
logger.debug('No tabs to reconnect')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onTabDetached: (tabId, reason) => {
|
onTabDetached: (tabId, reason) => {
|
||||||
debugLog('=== Manual tab detachment detected for tab:', tabId, '===')
|
logger.debug('=== Manual tab detachment detected for tab:', tabId, '===')
|
||||||
debugLog('User closed debugger via Chrome automation bar')
|
logger.debug('User closed debugger via Chrome automation bar')
|
||||||
|
|
||||||
useExtensionStore.setState((state) => {
|
useExtensionStore.setState((state) => {
|
||||||
const newTabs = new Map(state.connectedTabs)
|
const newTabs = new Map(state.connectedTabs)
|
||||||
@@ -303,17 +303,17 @@ async function ensureConnection(): Promise<void> {
|
|||||||
// if user cancels debugger. disconnect everything
|
// if user cancels debugger. disconnect everything
|
||||||
useExtensionStore.setState({ connectionState: 'disconnected' })
|
useExtensionStore.setState({ connectionState: 'disconnected' })
|
||||||
}
|
}
|
||||||
debugLog('Removed tab from _connectedTabs map')
|
logger.debug('Removed tab from _connectedTabs map')
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
useExtensionStore.setState({ connection: newConnection })
|
useExtensionStore.setState({ connection: newConnection })
|
||||||
debugLog('Connection established, WebSocket open (caller should set connectionState)')
|
logger.debug('Connection established, WebSocket open (caller should set connectionState)')
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectTab(tabId: number): Promise<void> {
|
async function connectTab(tabId: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
debugLog(`=== Starting connection to tab ${tabId} ===`)
|
logger.debug(`=== Starting connection to tab ${tabId} ===`)
|
||||||
|
|
||||||
useExtensionStore.setState((state) => {
|
useExtensionStore.setState((state) => {
|
||||||
const newTabs = new Map(state.connectedTabs)
|
const newTabs = new Map(state.connectedTabs)
|
||||||
@@ -326,11 +326,11 @@ async function connectTab(tabId: number): Promise<void> {
|
|||||||
|
|
||||||
await ensureConnection()
|
await ensureConnection()
|
||||||
|
|
||||||
debugLog('Calling attachTab for tab:', tabId)
|
logger.debug('Calling attachTab for tab:', tabId)
|
||||||
const { connection } = useExtensionStore.getState()
|
const { connection } = useExtensionStore.getState()
|
||||||
if (!connection) return
|
if (!connection) return
|
||||||
const targetInfo = await connection.attachTab(tabId)
|
const targetInfo = await connection.attachTab(tabId)
|
||||||
debugLog('attachTab completed, updating targetId in connectedTabs map')
|
logger.debug('attachTab completed, updating targetId in connectedTabs map')
|
||||||
useExtensionStore.setState((state) => {
|
useExtensionStore.setState((state) => {
|
||||||
const newTabs = new Map(state.connectedTabs)
|
const newTabs = new Map(state.connectedTabs)
|
||||||
newTabs.set(tabId, {
|
newTabs.set(tabId, {
|
||||||
@@ -340,11 +340,11 @@ async function connectTab(tabId: number): Promise<void> {
|
|||||||
return { connectedTabs: newTabs, connectionState: 'connected' }
|
return { connectedTabs: newTabs, connectionState: 'connected' }
|
||||||
})
|
})
|
||||||
|
|
||||||
debugLog(`=== Successfully connected to tab ${tabId} ===`)
|
logger.debug(`=== Successfully connected to tab ${tabId} ===`)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog(`=== Failed to connect to tab ${tabId} ===`)
|
logger.debug(`=== Failed to connect to tab ${tabId} ===`)
|
||||||
debugLog('Error details:', error)
|
logger.debug('Error details:', error)
|
||||||
debugLog('Error stack:', error.stack)
|
logger.debug('Error stack:', error.stack)
|
||||||
|
|
||||||
useExtensionStore.setState((state) => {
|
useExtensionStore.setState((state) => {
|
||||||
const newTabs = new Map(state.connectedTabs)
|
const newTabs = new Map(state.connectedTabs)
|
||||||
@@ -367,55 +367,55 @@ async function connectTab(tabId: number): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function disconnectTab(tabId: number): Promise<void> {
|
async function disconnectTab(tabId: number): Promise<void> {
|
||||||
debugLog(`=== Disconnecting tab ${tabId} ===`)
|
logger.debug(`=== Disconnecting tab ${tabId} ===`)
|
||||||
|
|
||||||
const { connectedTabs, connection } = useExtensionStore.getState()
|
const { connectedTabs, connection } = useExtensionStore.getState()
|
||||||
if (!connectedTabs.has(tabId)) {
|
if (!connectedTabs.has(tabId)) {
|
||||||
debugLog('Tab not in connectedTabs map, ignoring disconnect')
|
logger.debug('Tab not in connectedTabs map, ignoring disconnect')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Calling detachTab on connection')
|
logger.debug('Calling detachTab on connection')
|
||||||
connection?.detachTab(tabId)
|
connection?.detachTab(tabId)
|
||||||
useExtensionStore.setState((state) => {
|
useExtensionStore.setState((state) => {
|
||||||
const newTabs = new Map(state.connectedTabs)
|
const newTabs = new Map(state.connectedTabs)
|
||||||
newTabs.delete(tabId)
|
newTabs.delete(tabId)
|
||||||
return { connectedTabs: newTabs }
|
return { connectedTabs: newTabs }
|
||||||
})
|
})
|
||||||
debugLog('Tab removed from connectedTabs map')
|
logger.debug('Tab removed from connectedTabs map')
|
||||||
|
|
||||||
const { connectedTabs: updatedTabs, connection: updatedConnection } = useExtensionStore.getState()
|
const { connectedTabs: updatedTabs, connection: updatedConnection } = useExtensionStore.getState()
|
||||||
debugLog('Connected tabs remaining:', updatedTabs.size)
|
logger.debug('Connected tabs remaining:', updatedTabs.size)
|
||||||
if (updatedTabs.size === 0 && updatedConnection) {
|
if (updatedTabs.size === 0 && updatedConnection) {
|
||||||
debugLog('No tabs remaining, closing relay connection')
|
logger.debug('No tabs remaining, closing relay connection')
|
||||||
updatedConnection.close('All tabs disconnected')
|
updatedConnection.close('All tabs disconnected')
|
||||||
useExtensionStore.setState({ connection: undefined, connectionState: 'disconnected' })
|
useExtensionStore.setState({ connection: undefined, connectionState: 'disconnected' })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function reconnect(): Promise<void> {
|
async function reconnect(): Promise<void> {
|
||||||
debugLog('=== Starting reconnection ===')
|
logger.debug('=== Starting reconnection ===')
|
||||||
const { connectedTabs } = useExtensionStore.getState()
|
const { connectedTabs } = useExtensionStore.getState()
|
||||||
debugLog('Tabs to reconnect:', Array.from(connectedTabs.keys()))
|
logger.debug('Tabs to reconnect:', Array.from(connectedTabs.keys()))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await ensureConnection()
|
await ensureConnection()
|
||||||
|
|
||||||
const tabsToReconnect = Array.from(connectedTabs.keys())
|
const tabsToReconnect = Array.from(connectedTabs.keys())
|
||||||
debugLog('Re-attaching', tabsToReconnect.length, 'tabs')
|
logger.debug('Re-attaching', tabsToReconnect.length, 'tabs')
|
||||||
|
|
||||||
for (const tabId of tabsToReconnect) {
|
for (const tabId of tabsToReconnect) {
|
||||||
const { connectedTabs: currentTabs } = useExtensionStore.getState()
|
const { connectedTabs: currentTabs } = useExtensionStore.getState()
|
||||||
if (!currentTabs.has(tabId)) {
|
if (!currentTabs.has(tabId)) {
|
||||||
debugLog('Tab', tabId, 'was manually disconnected during reconnection, skipping')
|
logger.debug('Tab', tabId, 'was manually disconnected during reconnection, skipping')
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
debugLog('Checking if tab', tabId, 'still exists')
|
logger.debug('Checking if tab', tabId, 'still exists')
|
||||||
await chrome.tabs.get(tabId)
|
await chrome.tabs.get(tabId)
|
||||||
|
|
||||||
debugLog('Re-attaching tab:', tabId)
|
logger.debug('Re-attaching tab:', tabId)
|
||||||
const { connection } = useExtensionStore.getState()
|
const { connection } = useExtensionStore.getState()
|
||||||
if (!connection) return
|
if (!connection) return
|
||||||
const targetInfo = await connection.attachTab(tabId)
|
const targetInfo = await connection.attachTab(tabId)
|
||||||
@@ -427,9 +427,9 @@ async function reconnect(): Promise<void> {
|
|||||||
})
|
})
|
||||||
return { connectedTabs: newTabs }
|
return { connectedTabs: newTabs }
|
||||||
})
|
})
|
||||||
debugLog('Successfully re-attached tab:', tabId)
|
logger.debug('Successfully re-attached tab:', tabId)
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('Failed to re-attach tab:', tabId, error.message)
|
logger.debug('Failed to re-attach tab:', tabId, error.message)
|
||||||
useExtensionStore.setState((state) => {
|
useExtensionStore.setState((state) => {
|
||||||
const newTabs = new Map(state.connectedTabs)
|
const newTabs = new Map(state.connectedTabs)
|
||||||
newTabs.delete(tabId)
|
newTabs.delete(tabId)
|
||||||
@@ -439,18 +439,18 @@ async function reconnect(): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { connectedTabs: finalTabs } = useExtensionStore.getState()
|
const { connectedTabs: finalTabs } = useExtensionStore.getState()
|
||||||
debugLog('=== Reconnection complete ===')
|
logger.debug('=== Reconnection complete ===')
|
||||||
debugLog('Successfully reconnected tabs:', finalTabs.size)
|
logger.debug('Successfully reconnected tabs:', finalTabs.size)
|
||||||
|
|
||||||
if (finalTabs.size > 0) {
|
if (finalTabs.size > 0) {
|
||||||
useExtensionStore.setState({ connectionState: 'connected' })
|
useExtensionStore.setState({ connectionState: 'connected' })
|
||||||
debugLog('Set connectionState to connected')
|
logger.debug('Set connectionState to connected')
|
||||||
} else {
|
} else {
|
||||||
debugLog('No tabs successfully reconnected, staying in reconnecting state')
|
logger.debug('No tabs successfully reconnected, staying in reconnecting state')
|
||||||
useExtensionStore.setState({ connectionState: 'disconnected' })
|
useExtensionStore.setState({ connectionState: 'disconnected' })
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('=== Reconnection failed ===', error)
|
logger.debug('=== Reconnection failed ===', error)
|
||||||
|
|
||||||
useExtensionStore.setState({
|
useExtensionStore.setState({
|
||||||
connectedTabs: new Map(),
|
connectedTabs: new Map(),
|
||||||
@@ -462,21 +462,21 @@ async function reconnect(): Promise<void> {
|
|||||||
|
|
||||||
async function onTabRemoved(tabId: number): Promise<void> {
|
async function onTabRemoved(tabId: number): Promise<void> {
|
||||||
const { connectedTabs } = useExtensionStore.getState()
|
const { connectedTabs } = useExtensionStore.getState()
|
||||||
debugLog('Tab removed event for tab:', tabId, 'is connected:', connectedTabs.has(tabId))
|
logger.debug('Tab removed event for tab:', tabId, 'is connected:', connectedTabs.has(tabId))
|
||||||
if (!connectedTabs.has(tabId)) return
|
if (!connectedTabs.has(tabId)) return
|
||||||
|
|
||||||
debugLog(`Connected tab ${tabId} was closed, disconnecting`)
|
logger.debug(`Connected tab ${tabId} was closed, disconnecting`)
|
||||||
await disconnectTab(tabId)
|
await disconnectTab(tabId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onTabActivated(activeInfo: chrome.tabs.TabActiveInfo): Promise<void> {
|
async function onTabActivated(activeInfo: chrome.tabs.TabActiveInfo): Promise<void> {
|
||||||
debugLog('Tab activated:', activeInfo.tabId)
|
logger.debug('Tab activated:', activeInfo.tabId)
|
||||||
useExtensionStore.setState({ currentTabId: activeInfo.tabId })
|
useExtensionStore.setState({ currentTabId: activeInfo.tabId })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function onActionClicked(tab: chrome.tabs.Tab): Promise<void> {
|
async function onActionClicked(tab: chrome.tabs.Tab): Promise<void> {
|
||||||
if (!tab.id) {
|
if (!tab.id) {
|
||||||
debugLog('No tab ID available')
|
logger.debug('No tab ID available')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,13 +484,13 @@ async function onActionClicked(tab: chrome.tabs.Tab): Promise<void> {
|
|||||||
const tabInfo = connectedTabs.get(tab.id)
|
const tabInfo = connectedTabs.get(tab.id)
|
||||||
|
|
||||||
if (connectionState === 'error') {
|
if (connectionState === 'error') {
|
||||||
debugLog('Global error state - retrying reconnection')
|
logger.debug('Global error state - retrying reconnection')
|
||||||
await reconnect()
|
await reconnect()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connectionState === 'reconnecting') {
|
if (connectionState === 'reconnecting') {
|
||||||
debugLog('User clicked during reconnection, canceling and disconnecting all tabs')
|
logger.debug('User clicked during reconnection, canceling and disconnecting all tabs')
|
||||||
|
|
||||||
const tabsToDisconnect = Array.from(connectedTabs.keys())
|
const tabsToDisconnect = Array.from(connectedTabs.keys())
|
||||||
|
|
||||||
@@ -508,7 +508,7 @@ async function onActionClicked(tab: chrome.tabs.Tab): Promise<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (tabInfo?.state === 'error') {
|
if (tabInfo?.state === 'error') {
|
||||||
debugLog('Tab has error - disconnecting to clear state')
|
logger.debug('Tab has error - disconnecting to clear state')
|
||||||
await disconnectTab(tab.id)
|
await disconnectTab(tab.id)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -520,7 +520,7 @@ async function onActionClicked(tab: chrome.tabs.Tab): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog(`Using relay URL: ${RELAY_URL}`)
|
logger.debug(`Using relay URL: ${RELAY_URL}`)
|
||||||
chrome.tabs.onRemoved.addListener(onTabRemoved)
|
chrome.tabs.onRemoved.addListener(onTabRemoved)
|
||||||
chrome.tabs.onActivated.addListener(onTabActivated)
|
chrome.tabs.onActivated.addListener(onTabActivated)
|
||||||
chrome.action.onClicked.addListener(onActionClicked)
|
chrome.action.onClicked.addListener(onActionClicked)
|
||||||
|
|||||||
@@ -17,14 +17,56 @@
|
|||||||
import type { CDPEvent, Protocol } from 'playwriter/src/cdp-types';
|
import type { CDPEvent, Protocol } from 'playwriter/src/cdp-types';
|
||||||
import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwriter/src/extension/protocol';
|
import type { ExtensionCommandMessage, ExtensionResponseMessage } from 'playwriter/src/extension/protocol';
|
||||||
|
|
||||||
export function debugLog(...args: unknown[]): void {
|
let activeConnection: RelayConnection | undefined;
|
||||||
const enabled = true;
|
|
||||||
if (enabled) {
|
export const logger = {
|
||||||
// eslint-disable-next-line no-console
|
log: (...args: any[]) => logToRemote('log', args),
|
||||||
console.log('[Extension]', ...args);
|
debug: (...args: any[]) => logToRemote('debug', args),
|
||||||
|
info: (...args: any[]) => logToRemote('info', args),
|
||||||
|
warn: (...args: any[]) => logToRemote('warn', args),
|
||||||
|
error: (...args: any[]) => logToRemote('error', args),
|
||||||
|
};
|
||||||
|
|
||||||
|
function logToRemote(level: 'log' | 'debug' | 'info' | 'warn' | 'error', args: any[]) {
|
||||||
|
// Always log to local console
|
||||||
|
console[level](...args);
|
||||||
|
|
||||||
|
if (activeConnection) {
|
||||||
|
activeConnection.sendLog(level, args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function safeSerialize(arg: any): string {
|
||||||
|
if (arg === undefined) return 'undefined';
|
||||||
|
if (arg === null) return 'null';
|
||||||
|
if (typeof arg === 'function') return `[Function: ${arg.name || 'anonymous'}]`;
|
||||||
|
if (typeof arg === 'symbol') return String(arg);
|
||||||
|
|
||||||
|
if (arg instanceof Error) {
|
||||||
|
return arg.stack || arg.message || String(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof arg === 'object') {
|
||||||
|
try {
|
||||||
|
const seen = new WeakSet();
|
||||||
|
return JSON.stringify(arg, (key, value) => {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (seen.has(value)) return '[Circular]';
|
||||||
|
seen.add(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
return String(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
interface AttachedTab {
|
interface AttachedTab {
|
||||||
debuggee: chrome.debugger.Debuggee;
|
debuggee: chrome.debugger.Debuggee;
|
||||||
targetId: string;
|
targetId: string;
|
||||||
@@ -58,7 +100,7 @@ export class RelayConnection {
|
|||||||
try {
|
try {
|
||||||
message = JSON.parse(event.data);
|
message = JSON.parse(event.data);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('Error parsing message:', error);
|
logger.debug('Error parsing message:', error);
|
||||||
this._sendMessage({
|
this._sendMessage({
|
||||||
error: {
|
error: {
|
||||||
code: -32700,
|
code: -32700,
|
||||||
@@ -68,7 +110,7 @@ export class RelayConnection {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Received message:', message);
|
logger.debug('Received message:', message);
|
||||||
|
|
||||||
const response: ExtensionResponseMessage = {
|
const response: ExtensionResponseMessage = {
|
||||||
id: message.id,
|
id: message.id,
|
||||||
@@ -76,14 +118,14 @@ export class RelayConnection {
|
|||||||
try {
|
try {
|
||||||
response.result = await this._handleCommand(message);
|
response.result = await this._handleCommand(message);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('Error handling command:', error);
|
logger.debug('Error handling command:', error);
|
||||||
response.error = error.message;
|
response.error = error.message;
|
||||||
}
|
}
|
||||||
debugLog('Sending response:', response);
|
logger.debug('Sending response:', response);
|
||||||
this._sendMessage(response);
|
this._sendMessage(response);
|
||||||
};
|
};
|
||||||
this._ws.onclose = (event: CloseEvent) => {
|
this._ws.onclose = (event: CloseEvent) => {
|
||||||
debugLog('WebSocket onclose event:', {
|
logger.debug('WebSocket onclose event:', {
|
||||||
code: event.code,
|
code: event.code,
|
||||||
reason: event.reason,
|
reason: event.reason,
|
||||||
wasClean: event.wasClean
|
wasClean: event.wasClean
|
||||||
@@ -91,33 +133,44 @@ export class RelayConnection {
|
|||||||
this._onClose(event.reason, event.code);
|
this._onClose(event.reason, event.code);
|
||||||
};
|
};
|
||||||
this._ws.onerror = (event: Event) => {
|
this._ws.onerror = (event: Event) => {
|
||||||
debugLog('WebSocket onerror event:', event);
|
logger.debug('WebSocket onerror event:', event);
|
||||||
};
|
};
|
||||||
chrome.debugger.onEvent.addListener(this._onDebuggerEvent);
|
chrome.debugger.onEvent.addListener(this._onDebuggerEvent);
|
||||||
chrome.debugger.onDetach.addListener(this._onDebuggerDetach);
|
chrome.debugger.onDetach.addListener(this._onDebuggerDetach);
|
||||||
debugLog('RelayConnection created, WebSocket readyState:', this._ws.readyState);
|
logger.debug('RelayConnection created, WebSocket readyState:', this._ws.readyState);
|
||||||
|
activeConnection = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
sendLog(level: string, args: any[]) {
|
||||||
|
this._sendMessage({
|
||||||
|
method: 'log',
|
||||||
|
params: {
|
||||||
|
level,
|
||||||
|
args: args.map(arg => safeSerialize(arg))
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async attachTab(tabId: number): Promise<Protocol.Target.TargetInfo> {
|
async attachTab(tabId: number): Promise<Protocol.Target.TargetInfo> {
|
||||||
const debuggee = { tabId };
|
const debuggee = { tabId };
|
||||||
|
|
||||||
debugLog('Attaching debugger to tab:', tabId, 'WebSocket state:', this._ws.readyState);
|
logger.debug('Attaching debugger to tab:', tabId, 'WebSocket state:', this._ws.readyState);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await chrome.debugger.attach(debuggee, '1.3');
|
await chrome.debugger.attach(debuggee, '1.3');
|
||||||
debugLog('Debugger attached successfully to tab:', tabId);
|
logger.debug('Debugger attached successfully to tab:', tabId);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('ERROR attaching debugger to tab:', tabId, error);
|
logger.debug('ERROR attaching debugger to tab:', tabId, error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Sending Target.getTargetInfo command for tab:', tabId);
|
logger.debug('Sending Target.getTargetInfo command for tab:', tabId);
|
||||||
const result = await chrome.debugger.sendCommand(
|
const result = await chrome.debugger.sendCommand(
|
||||||
debuggee,
|
debuggee,
|
||||||
'Target.getTargetInfo'
|
'Target.getTargetInfo'
|
||||||
) as Protocol.Target.GetTargetInfoResponse;
|
) as Protocol.Target.GetTargetInfoResponse;
|
||||||
|
|
||||||
debugLog('Received targetInfo for tab:', tabId, result.targetInfo);
|
logger.debug('Received targetInfo for tab:', tabId, result.targetInfo);
|
||||||
|
|
||||||
const targetInfo = result.targetInfo;
|
const targetInfo = result.targetInfo;
|
||||||
const sessionId = `pw-tab-${this._nextSessionId++}`;
|
const sessionId = `pw-tab-${this._nextSessionId++}`;
|
||||||
@@ -130,7 +183,7 @@ export class RelayConnection {
|
|||||||
executionContexts: new Map()
|
executionContexts: new Map()
|
||||||
});
|
});
|
||||||
|
|
||||||
debugLog('Sending Target.attachedToTarget event, WebSocket state:', this._ws.readyState);
|
logger.debug('Sending Target.attachedToTarget event, WebSocket state:', this._ws.readyState);
|
||||||
this._sendMessage({
|
this._sendMessage({
|
||||||
method: 'forwardCDPEvent',
|
method: 'forwardCDPEvent',
|
||||||
params: {
|
params: {
|
||||||
@@ -146,7 +199,7 @@ export class RelayConnection {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
debugLog('Tab attached successfully:', tabId, 'sessionId:', sessionId, 'targetId:', targetInfo.targetId);
|
logger.debug('Tab attached successfully:', tabId, 'sessionId:', sessionId, 'targetId:', targetInfo.targetId);
|
||||||
return targetInfo;
|
return targetInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,11 +210,11 @@ export class RelayConnection {
|
|||||||
private _cleanupTab(tabId: number, shouldDetachDebugger: boolean): void {
|
private _cleanupTab(tabId: number, shouldDetachDebugger: boolean): void {
|
||||||
const tab = this._attachedTabs.get(tabId);
|
const tab = this._attachedTabs.get(tabId);
|
||||||
if (!tab) {
|
if (!tab) {
|
||||||
debugLog('cleanupTab: tab not found in map:', tabId);
|
logger.debug('cleanupTab: tab not found in map:', tabId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Cleaning up tab:', tabId, 'sessionId:', tab.sessionId, 'shouldDetach:', shouldDetachDebugger);
|
logger.debug('Cleaning up tab:', tabId, 'sessionId:', tab.sessionId, 'shouldDetach:', shouldDetachDebugger);
|
||||||
|
|
||||||
this._sendMessage({
|
this._sendMessage({
|
||||||
method: 'forwardCDPEvent',
|
method: 'forwardCDPEvent',
|
||||||
@@ -175,32 +228,32 @@ export class RelayConnection {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this._attachedTabs.delete(tabId);
|
this._attachedTabs.delete(tabId);
|
||||||
debugLog('Removed tab from _attachedTabs map. Remaining tabs:', this._attachedTabs.size);
|
logger.debug('Removed tab from _attachedTabs map. Remaining tabs:', this._attachedTabs.size);
|
||||||
|
|
||||||
if (shouldDetachDebugger) {
|
if (shouldDetachDebugger) {
|
||||||
chrome.debugger.detach(tab.debuggee)
|
chrome.debugger.detach(tab.debuggee)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
debugLog('Successfully detached debugger from tab:', tabId);
|
logger.debug('Successfully detached debugger from tab:', tabId);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
debugLog('Error detaching debugger from tab:', tabId, err.message);
|
logger.debug('Error detaching debugger from tab:', tabId, err.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(message: string): void {
|
close(message: string): void {
|
||||||
debugLog('Closing RelayConnection, reason:', message, 'current state:', this._ws.readyState);
|
logger.debug('Closing RelayConnection, reason:', message, 'current state:', this._ws.readyState);
|
||||||
this._ws.close(1000, message);
|
this._ws.close(1000, message);
|
||||||
this._onClose(message, 1000);
|
this._onClose(message, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onClose(reason: string = 'Unknown', code: number = 1000) {
|
private _onClose(reason: string = 'Unknown', code: number = 1000) {
|
||||||
if (this._closed) {
|
if (this._closed) {
|
||||||
debugLog('_onClose called but already closed');
|
logger.debug('_onClose called but already closed');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Connection closing, attached tabs count:', this._attachedTabs.size);
|
logger.debug('Connection closing, attached tabs count:', this._attachedTabs.size);
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
|
|
||||||
|
|
||||||
@@ -208,23 +261,26 @@ export class RelayConnection {
|
|||||||
chrome.debugger.onDetach.removeListener(this._onDebuggerDetach);
|
chrome.debugger.onDetach.removeListener(this._onDebuggerDetach);
|
||||||
|
|
||||||
const tabIds = Array.from(this._attachedTabs.keys());
|
const tabIds = Array.from(this._attachedTabs.keys());
|
||||||
debugLog('Detaching all tabs:', tabIds);
|
logger.debug('Detaching all tabs:', tabIds);
|
||||||
|
|
||||||
for (const [tabId, tab] of this._attachedTabs) {
|
for (const [tabId, tab] of this._attachedTabs) {
|
||||||
debugLog('Detaching debugger from tab:', tabId);
|
logger.debug('Detaching debugger from tab:', tabId);
|
||||||
chrome.debugger.detach(tab.debuggee)
|
chrome.debugger.detach(tab.debuggee)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
debugLog('Successfully detached from tab:', tabId);
|
logger.debug('Successfully detached from tab:', tabId);
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
debugLog('Error detaching from tab:', tabId, err.message);
|
logger.debug('Error detaching from tab:', tabId, err.message);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this._attachedTabs.clear();
|
this._attachedTabs.clear();
|
||||||
debugLog('All tabs cleared from map. Chrome automation bar should disappear in a few seconds.');
|
logger.debug('All tabs cleared from map. Chrome automation bar should disappear in a few seconds.');
|
||||||
|
|
||||||
debugLog('Connection closed, calling onClose callback');
|
logger.debug('Connection closed, calling onClose callback');
|
||||||
|
if (activeConnection === this) {
|
||||||
|
activeConnection = undefined;
|
||||||
|
}
|
||||||
this._onCloseCallback?.(reason, code);
|
this._onCloseCallback?.(reason, code);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,17 +294,17 @@ export class RelayConnection {
|
|||||||
if (method === 'Runtime.executionContextCreated') {
|
if (method === 'Runtime.executionContextCreated') {
|
||||||
const contextEvent = params as Protocol.Runtime.ExecutionContextCreatedEvent;
|
const contextEvent = params as Protocol.Runtime.ExecutionContextCreatedEvent;
|
||||||
tab.executionContexts.set(contextEvent.context.id, contextEvent);
|
tab.executionContexts.set(contextEvent.context.id, contextEvent);
|
||||||
debugLog('Cached execution context:', contextEvent.context.id, 'for tab:', source.tabId, 'total contexts:', tab.executionContexts.size);
|
logger.debug('Cached execution context:', contextEvent.context.id, 'for tab:', source.tabId, 'total contexts:', tab.executionContexts.size);
|
||||||
} else if (method === 'Runtime.executionContextDestroyed') {
|
} else if (method === 'Runtime.executionContextDestroyed') {
|
||||||
const destroyedEvent = params as Protocol.Runtime.ExecutionContextDestroyedEvent;
|
const destroyedEvent = params as Protocol.Runtime.ExecutionContextDestroyedEvent;
|
||||||
tab.executionContexts.delete(destroyedEvent.executionContextId);
|
tab.executionContexts.delete(destroyedEvent.executionContextId);
|
||||||
debugLog('Removed execution context:', destroyedEvent.executionContextId, 'from tab:', source.tabId, 'remaining:', tab.executionContexts.size);
|
logger.debug('Removed execution context:', destroyedEvent.executionContextId, 'from tab:', source.tabId, 'remaining:', tab.executionContexts.size);
|
||||||
} else if (method === 'Runtime.executionContextsCleared') {
|
} else if (method === 'Runtime.executionContextsCleared') {
|
||||||
tab.executionContexts.clear();
|
tab.executionContexts.clear();
|
||||||
debugLog('Cleared all execution contexts for tab:', source.tabId);
|
logger.debug('Cleared all execution contexts for tab:', source.tabId);
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Forwarding CDP event:', method, 'from tab:', source.tabId);
|
logger.debug('Forwarding CDP event:', method, 'from tab:', source.tabId);
|
||||||
|
|
||||||
this._sendMessage({
|
this._sendMessage({
|
||||||
method: 'forwardCDPEvent',
|
method: 'forwardCDPEvent',
|
||||||
@@ -262,15 +318,15 @@ export class RelayConnection {
|
|||||||
|
|
||||||
private _onDebuggerDetach = (source: chrome.debugger.Debuggee, reason: `${chrome.debugger.DetachReason}`): void => {
|
private _onDebuggerDetach = (source: chrome.debugger.Debuggee, reason: `${chrome.debugger.DetachReason}`): void => {
|
||||||
const tabId = source.tabId;
|
const tabId = source.tabId;
|
||||||
debugLog('_onDebuggerDetach called for tab:', tabId, 'reason:', reason, 'isAttached:', tabId ? this._attachedTabs.has(tabId) : false);
|
logger.debug('_onDebuggerDetach called for tab:', tabId, 'reason:', reason, 'isAttached:', tabId ? this._attachedTabs.has(tabId) : false);
|
||||||
|
|
||||||
if (!tabId || !this._attachedTabs.has(tabId)) {
|
if (!tabId || !this._attachedTabs.has(tabId)) {
|
||||||
debugLog('Ignoring debugger detach event for untracked tab:', tabId);
|
logger.debug('Ignoring debugger detach event for untracked tab:', tabId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog(`Manual debugger detachment detected for tab ${tabId}: ${reason}`);
|
logger.debug(`Manual debugger detachment detected for tab ${tabId}: ${reason}`);
|
||||||
debugLog('User closed debugger via Chrome automation bar, calling onTabDetached callback');
|
logger.debug('User closed debugger via Chrome automation bar, calling onTabDetached callback');
|
||||||
this._onTabDetachedCallback?.(tabId, reason);
|
this._onTabDetachedCallback?.(tabId, reason);
|
||||||
|
|
||||||
this._cleanupTab(tabId, false);
|
this._cleanupTab(tabId, false);
|
||||||
@@ -286,14 +342,14 @@ export class RelayConnection {
|
|||||||
|
|
||||||
if (msg.params.method === 'Target.createTarget') {
|
if (msg.params.method === 'Target.createTarget') {
|
||||||
const url = msg.params.params?.url || 'about:blank';
|
const url = msg.params.params?.url || 'about:blank';
|
||||||
debugLog('Creating new tab with URL:', url);
|
logger.debug('Creating new tab with URL:', url);
|
||||||
|
|
||||||
const tab = await chrome.tabs.create({ url, active: false });
|
const tab = await chrome.tabs.create({ url, active: false });
|
||||||
if (!tab.id) {
|
if (!tab.id) {
|
||||||
throw new Error('Failed to create tab');
|
throw new Error('Failed to create tab');
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Created tab:', tab.id, 'waiting for it to load...');
|
logger.debug('Created tab:', tab.id, 'waiting for it to load...');
|
||||||
|
|
||||||
// Wait a bit for tab to initialize
|
// Wait a bit for tab to initialize
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
@@ -305,17 +361,17 @@ export class RelayConnection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (msg.params.method === 'Target.closeTarget' && msg.params.params?.targetId) {
|
if (msg.params.method === 'Target.closeTarget' && msg.params.params?.targetId) {
|
||||||
debugLog('Closing target:', msg.params.params.targetId);
|
logger.debug('Closing target:', msg.params.params.targetId);
|
||||||
|
|
||||||
for (const [tabId, tab] of this._attachedTabs) {
|
for (const [tabId, tab] of this._attachedTabs) {
|
||||||
if (tab.targetId === msg.params.params.targetId) {
|
if (tab.targetId === msg.params.params.targetId) {
|
||||||
debugLog('Found tab to close:', tabId);
|
logger.debug('Found tab to close:', tabId);
|
||||||
await chrome.tabs.remove(tabId);
|
await chrome.tabs.remove(tabId);
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('Target not found:', msg.params.params.targetId);
|
logger.debug('Target not found:', msg.params.params.targetId);
|
||||||
throw new Error(`Target not found: ${msg.params.params.targetId}`);
|
throw new Error(`Target not found: ${msg.params.params.targetId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -338,7 +394,7 @@ export class RelayConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
debugLog('CDP command:', msg.params.method, 'for tab:', targetTab.debuggee.tabId);
|
logger.debug('CDP command:', msg.params.method, 'for tab:', targetTab.debuggee.tabId);
|
||||||
|
|
||||||
const debuggerSession: chrome.debugger.DebuggerSession = {
|
const debuggerSession: chrome.debugger.DebuggerSession = {
|
||||||
...targetTab.debuggee,
|
...targetTab.debuggee,
|
||||||
@@ -356,10 +412,10 @@ export class RelayConnection {
|
|||||||
// This causes page.evaluate() to hang because Playwright has no execution context IDs.
|
// This causes page.evaluate() to hang because Playwright has no execution context IDs.
|
||||||
// Solution: manually replay all cached contexts so Playwright gets fresh, valid IDs.
|
// Solution: manually replay all cached contexts so Playwright gets fresh, valid IDs.
|
||||||
if (msg.params.method === 'Runtime.enable' && targetTab.executionContexts.size > 0) {
|
if (msg.params.method === 'Runtime.enable' && targetTab.executionContexts.size > 0) {
|
||||||
debugLog('Runtime.enable called, replaying', targetTab.executionContexts.size, 'cached execution contexts for tab:', targetTab.debuggee.tabId);
|
logger.debug('Runtime.enable called, replaying', targetTab.executionContexts.size, 'cached execution contexts for tab:', targetTab.debuggee.tabId);
|
||||||
|
|
||||||
for (const contextEvent of targetTab.executionContexts.values()) {
|
for (const contextEvent of targetTab.executionContexts.values()) {
|
||||||
debugLog('Replaying execution context:', contextEvent.context.id);
|
logger.debug('Replaying execution context:', contextEvent.context.id);
|
||||||
this._sendMessage({
|
this._sendMessage({
|
||||||
method: 'forwardCDPEvent',
|
method: 'forwardCDPEvent',
|
||||||
params: {
|
params: {
|
||||||
@@ -379,12 +435,12 @@ export class RelayConnection {
|
|||||||
if (this._ws.readyState === WebSocket.OPEN) {
|
if (this._ws.readyState === WebSocket.OPEN) {
|
||||||
try {
|
try {
|
||||||
this._ws.send(JSON.stringify(message));
|
this._ws.send(JSON.stringify(message));
|
||||||
debugLog('Message sent successfully, type:', message.method || 'response');
|
// logger.debug('Message sent successfully, type:', message.method || 'response');
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
debugLog('ERROR sending message:', error, 'message type:', message.method || 'response');
|
logger.debug('ERROR sending message:', error, 'message type:', message.method || 'response');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
debugLog('Cannot send message, WebSocket not open. State:', this._ws.readyState, 'message type:', message.method || 'response');
|
logger.debug('Cannot send message, WebSocket not open. State:', this._ws.readyState, 'message type:', message.method || 'response');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -379,6 +379,11 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127.
|
|||||||
} else {
|
} else {
|
||||||
pending.resolve(message.result)
|
pending.resolve(message.result)
|
||||||
}
|
}
|
||||||
|
} else if (message.method === 'log') {
|
||||||
|
const { level, args } = message.params
|
||||||
|
const logFn = (logger as any)[level] || logger.log
|
||||||
|
const prefix = chalk.yellow(`[Extension] [${level.toUpperCase()}]`)
|
||||||
|
logFn(prefix, ...args)
|
||||||
} else {
|
} else {
|
||||||
const extensionEvent = message as ExtensionEventMessage
|
const extensionEvent = message as ExtensionEventMessage
|
||||||
|
|
||||||
|
|||||||
@@ -44,4 +44,12 @@ export type ExtensionEventMessage =
|
|||||||
}
|
}
|
||||||
}[keyof ProtocolMapping.Events]
|
}[keyof ProtocolMapping.Events]
|
||||||
|
|
||||||
export type ExtensionMessage = ExtensionResponseMessage | ExtensionEventMessage
|
export type ExtensionLogMessage = {
|
||||||
|
method: 'log'
|
||||||
|
params: {
|
||||||
|
level: 'log' | 'debug' | 'info' | 'warn' | 'error'
|
||||||
|
args: string[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ExtensionMessage = ExtensionResponseMessage | ExtensionEventMessage | ExtensionLogMessage
|
||||||
|
|||||||
Reference in New Issue
Block a user