diff --git a/extension/manifest.json b/extension/manifest.json index 415b636..b1f2f37 100644 --- a/extension/manifest.json +++ b/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Playwriter MCP", - "version": "0.0.54", + "version": "0.0.55", "description": "Automate your Browser using Cursor, Claude, VS Code. More capable and context efficient than Playwright MCP.", "permissions": ["debugger", "tabGroups"], "host_permissions": [""], diff --git a/extension/package.json b/extension/package.json index eca2ead..9716f25 100644 --- a/extension/package.json +++ b/extension/package.json @@ -1,6 +1,6 @@ { "name": "mcp-extension", - "version": "0.0.54", + "version": "0.0.55", "description": "Playwright MCP Browser Extension", "private": true, "repository": { diff --git a/extension/src/background.ts b/extension/src/background.ts index 84a3d54..add4f4e 100644 --- a/extension/src/background.ts +++ b/extension/src/background.ts @@ -14,6 +14,7 @@ let childSessions: Map = new Map() let nextSessionId = 1 let playwriterGroupId: number | null = null let syncTabGroupQueue: Promise = Promise.resolve() +let replacedRetryInterval: ReturnType | null = null const store = createStore(() => ({ tabs: new Map(), @@ -450,6 +451,31 @@ function closeConnection(reason: string): void { } } +function startReplacedRetryLoop(): void { + if (replacedRetryInterval) return + logger.debug('Starting replaced retry loop (every 3 seconds)') + replacedRetryInterval = setInterval(async () => { + if (ws?.readyState === WebSocket.OPEN || store.getState().connectionState !== 'error') { + clearInterval(replacedRetryInterval!) + replacedRetryInterval = null + logger.debug('Stopped replaced retry loop') + return + } + try { + const response = await fetch('http://localhost:19988/extension/status') + const data = await response.json() + if (!data.connected) { + store.setState({ connectionState: 'disconnected', errorText: undefined }) + logger.debug('Extension slot is free, cleared error state') + } else { + logger.debug('Extension slot still taken, will retry...') + } + } catch { + logger.debug('Server not available, will retry...') + } + }, 3000) +} + function handleConnectionClose(reason: string, code: number): void { logger.debug('Connection closed:', { reason, code }) @@ -474,6 +500,7 @@ function handleConnectionClose(reason: string, code: number): void { connectionState: 'error', errorText: 'Disconnected: Replaced by another extension', }) + startReplacedRetryLoop() return } diff --git a/playwriter/CHANGELOG.md b/playwriter/CHANGELOG.md index 991e8c1..816af31 100644 --- a/playwriter/CHANGELOG.md +++ b/playwriter/CHANGELOG.md @@ -1,5 +1,12 @@ # Changelog +## 0.0.27 + +### Patch Changes + +- **Fixed gray icon on about:blank pages**: `about:blank` pages now show the black (clickable) icon instead of gray (restricted). Chrome returns `undefined` for `tab.url` on blank pages, which was incorrectly treated as restricted. +- **Auto-recovery after extension replacement**: When another extension instance takes over the connection, the replaced extension now polls `/extension/status` every 3 seconds. When the slot becomes free, it clears the error state so the user can click to reconnect. + ## 0.0.26 ### Patch Changes diff --git a/playwriter/package.json b/playwriter/package.json index 2fa6aaf..75710e3 100644 --- a/playwriter/package.json +++ b/playwriter/package.json @@ -1,7 +1,7 @@ { "name": "playwriter", "description": "", - "version": "0.0.26", + "version": "0.0.27", "type": "module", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/playwriter/src/extension/cdp-relay.ts b/playwriter/src/extension/cdp-relay.ts index 1fe2da3..1e0e6fb 100644 --- a/playwriter/src/extension/cdp-relay.ts +++ b/playwriter/src/extension/cdp-relay.ts @@ -281,6 +281,10 @@ export async function startPlayWriterCDPRelayServer({ port = 19988, host = '127. return c.json({ version: VERSION }) }) + app.get('/extension/status', (c) => { + return c.json({ connected: extensionWs !== null }) + }) + app.post('/mcp-log', async (c) => { try { const { level, args } = await c.req.json()