chore(extension): wrap CDP protocol (#604)

This commit is contained in:
Yury Semikhatsky
2025-06-26 16:21:59 -07:00
committed by GitHub
parent ded00dc422
commit 137b74750c
5 changed files with 99 additions and 134 deletions

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
import { Connection, debugLog } from './connection.js';
import { RelayConnection, debugLog } from './relayConnection.js';
/**
* Simple Chrome Extension that pumps CDP messages between chrome.debugger and WebSocket
@@ -29,7 +29,7 @@ type PopupMessage = {
type SendResponse = (response: any) => void;
class TabShareExtension {
private activeConnections: Map<number, Connection>;
private activeConnections: Map<number, RelayConnection>;
constructor() {
this.activeConnections = new Map(); // tabId -> connection
@@ -75,7 +75,7 @@ class TabShareExtension {
let activeTabId: number | null = null;
if (isConnected) {
const [tabId] = this.activeConnections.entries().next().value as [number, Connection];
const [tabId] = this.activeConnections.entries().next().value as [number, RelayConnection];
activeTabId = tabId;
// Get tab info
@@ -123,14 +123,14 @@ class TabShareExtension {
// Store connection
this.activeConnections.set(tabId, info);
void this._updateUI(tabId, { text: '●', color: '#4CAF50', title: 'Disconnect from Playwright MCP' });
await this._updateUI(tabId, { text: '●', color: '#4CAF50', title: 'Disconnect from Playwright MCP' });
debugLog(`Tab ${tabId} connected successfully`);
} catch (error: any) {
debugLog(`Failed to connect tab ${tabId}:`, error.message);
await this._cleanupConnection(tabId);
// Show error to user
void this._updateUI(tabId, { text: '!', color: '#F44336', title: `Connection failed: ${error.message}` });
await this._updateUI(tabId, { text: '!', color: '#F44336', title: `Connection failed: ${error.message}` });
throw error;
}
@@ -143,8 +143,8 @@ class TabShareExtension {
await chrome.action.setTitle({ tabId, title });
}
private _createConnection(tabId: number, socket: WebSocket): Connection {
const connection = new Connection(tabId, socket);
private _createConnection(tabId: number, socket: WebSocket): RelayConnection {
const connection = new RelayConnection(tabId, socket);
socket.onclose = () => {
debugLog(`WebSocket closed for tab ${tabId}`);
void this.disconnectTab(tabId);

View File

@@ -22,14 +22,21 @@ export function debugLog(...args: unknown[]): void {
}
}
export type ProtocolCommand = {
type ProtocolCommand = {
id: number;
sessionId?: string;
method: string;
params?: any;
};
export class Connection {
type ProtocolResponse = {
id?: number;
method?: string;
params?: any;
result?: any;
error?: string;
};
export class RelayConnection {
private _debuggee: chrome.debugger.Debuggee;
private _rootSessionId: string;
private _ws: WebSocket;
@@ -61,21 +68,23 @@ export class Connection {
private _onDebuggerEvent(source: chrome.debugger.DebuggerSession, method: string, params: any): void {
if (source.tabId !== this._debuggee.tabId)
return;
// If the sessionId is not provided, use the root sessionId.
const event = {
sessionId: source.sessionId || this._rootSessionId,
method,
params,
};
debugLog('Forwarding CDP event:', event);
this._ws.send(JSON.stringify(event));
debugLog('Forwarding CDP event:', method, params);
const sessionId = source.sessionId || this._rootSessionId;
this._sendMessage({
method: 'forwardCDPEvent',
params: {
sessionId,
method,
params,
},
});
}
private _onDebuggerDetach(source: chrome.debugger.Debuggee, reason: string): void {
if (source.tabId !== this._debuggee.tabId)
return;
this._sendMessage({
method: 'PWExtension.detachedFromTab',
method: 'detachedFromTab',
params: {
tabId: this._debuggee.tabId,
reason,
@@ -99,29 +108,21 @@ export class Connection {
debugLog('Received message:', message);
const sessionId = message.sessionId;
const response: { id: any; sessionId: any; result?: any; error?: { code: number; message: string } } = {
const response: ProtocolResponse = {
id: message.id,
sessionId,
};
try {
if (message.method.startsWith('PWExtension.'))
response.result = await this._handleExtensionCommand(message);
else
response.result = await this._handleCDPCommand(message);
response.result = await this._handleCommand(message);
} catch (error: any) {
debugLog('Error handling message:', error);
response.error = {
code: -32000,
message: error.message,
};
debugLog('Error handling command:', error);
response.error = error.message;
}
debugLog('Sending response:', response);
this._sendMessage(response);
}
private async _handleExtensionCommand(message: ProtocolCommand): Promise<any> {
if (message.method === 'PWExtension.attachToTab') {
private async _handleCommand(message: ProtocolCommand): Promise<any> {
if (message.method === 'attachToTab') {
debugLog('Attaching debugger to tab:', this._debuggee);
await chrome.debugger.attach(this._debuggee, '1.3');
const result: any = await chrome.debugger.sendCommand(this._debuggee, 'Target.getTargetInfo');
@@ -130,26 +131,24 @@ export class Connection {
targetInfo: result?.targetInfo,
};
}
if (message.method === 'PWExtension.detachFromTab') {
if (message.method === 'detachFromTab') {
debugLog('Detaching debugger from tab:', this._debuggee);
await this.detachDebugger();
return;
return await this.detachDebugger();
}
if (message.method === 'forwardCDPCommand') {
const { sessionId, method, params } = message.params;
debugLog('CDP command:', method, params);
const debuggerSession: chrome.debugger.DebuggerSession = { ...this._debuggee };
// Pass session id, unless it's the root session.
if (sessionId && sessionId !== this._rootSessionId)
debuggerSession.sessionId = sessionId;
// Forward CDP command to chrome.debugger
return await chrome.debugger.sendCommand(
debuggerSession,
method,
params
);
}
}
private async _handleCDPCommand(message: ProtocolCommand): Promise<any> {
const sessionId = message.sessionId;
const debuggerSession: chrome.debugger.DebuggerSession = { ...this._debuggee };
// Pass session id, unless it's the root session.
if (sessionId && sessionId !== this._rootSessionId)
debuggerSession.sessionId = sessionId;
// Forward CDP command to chrome.debugger
const result = await chrome.debugger.sendCommand(
debuggerSession,
message.method,
message.params
);
return result;
}
private _sendError(code: number, message: string): void {
@@ -161,7 +160,7 @@ export class Connection {
});
}
private _sendMessage(message: object): void {
private _sendMessage(message: any): void {
this._ws.send(JSON.stringify(message));
}
}

View File

@@ -7,7 +7,7 @@
"module": "ESNext",
"rootDir": "src",
"outDir": "./lib",
"resolveJsonModule": true
"resolveJsonModule": true,
},
"include": [
"src",