Reapply "simplify code. remove methods"
This reverts commit 788333e142.
This commit is contained in:
@@ -25,12 +25,12 @@ class SimplifiedExtension {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
debugLog(`Using relay URL: ${RELAY_URL}`);
|
debugLog(`Using relay URL: ${RELAY_URL}`);
|
||||||
chrome.tabs.onRemoved.addListener(this._onTabRemoved.bind(this));
|
chrome.tabs.onRemoved.addListener(this._onTabRemoved);
|
||||||
chrome.tabs.onActivated.addListener(this._onTabActivated.bind(this));
|
chrome.tabs.onActivated.addListener(this._onTabActivated);
|
||||||
chrome.action.onClicked.addListener(this._onActionClicked.bind(this));
|
chrome.action.onClicked.addListener(this._onActionClicked);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onActionClicked(tab: chrome.tabs.Tab): Promise<void> {
|
private _onActionClicked = async (tab: chrome.tabs.Tab): Promise<void> => {
|
||||||
if (!tab.id) {
|
if (!tab.id) {
|
||||||
debugLog('No tab ID available');
|
debugLog('No tab ID available');
|
||||||
return;
|
return;
|
||||||
@@ -41,23 +41,7 @@ class SimplifiedExtension {
|
|||||||
} else {
|
} else {
|
||||||
await this._connectTab(tab.id);
|
await this._connectTab(tab.id);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
private async _waitForServer(): Promise<void> {
|
|
||||||
const httpUrl = 'http://localhost:9988';
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
try {
|
|
||||||
debugLog('Checking if relay server is available...');
|
|
||||||
await fetch(httpUrl, { method: 'HEAD' });
|
|
||||||
debugLog('Server is available');
|
|
||||||
return;
|
|
||||||
} catch (error: any) {
|
|
||||||
debugLog(`Server not available, retrying in 1 second...`);
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _connectTab(tabId: number): Promise<void> {
|
private async _connectTab(tabId: number): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@@ -67,7 +51,18 @@ class SimplifiedExtension {
|
|||||||
if (!this._connection) {
|
if (!this._connection) {
|
||||||
debugLog('No existing connection, creating new relay connection');
|
debugLog('No existing connection, creating new relay connection');
|
||||||
debugLog('Waiting for server at http://localhost:9988...');
|
debugLog('Waiting for server at http://localhost:9988...');
|
||||||
await this._waitForServer();
|
|
||||||
|
// Wait for server to be available
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
await fetch('http://localhost:9988', { method: 'HEAD' });
|
||||||
|
debugLog('Server is available');
|
||||||
|
break;
|
||||||
|
} catch (error: any) {
|
||||||
|
debugLog('Server not available, retrying in 1 second...');
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
debugLog('Server is ready, creating WebSocket connection to:', RELAY_URL);
|
debugLog('Server is ready, creating WebSocket connection to:', RELAY_URL);
|
||||||
const socket = new WebSocket(RELAY_URL);
|
const socket = new WebSocket(RELAY_URL);
|
||||||
@@ -239,19 +234,19 @@ class SimplifiedExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _onTabRemoved(tabId: number): Promise<void> {
|
private _onTabRemoved = async (tabId: number): Promise<void> => {
|
||||||
debugLog('Tab removed event for tab:', tabId, 'is connected:', this._connectedTabs.has(tabId));
|
debugLog('Tab removed event for tab:', tabId, 'is connected:', this._connectedTabs.has(tabId));
|
||||||
if (!this._connectedTabs.has(tabId)) return;
|
if (!this._connectedTabs.has(tabId)) return;
|
||||||
|
|
||||||
debugLog(`Connected tab ${tabId} was closed, disconnecting`);
|
debugLog(`Connected tab ${tabId} was closed, disconnecting`);
|
||||||
await this._disconnectTab(tabId);
|
await this._disconnectTab(tabId);
|
||||||
}
|
};
|
||||||
|
|
||||||
private async _onTabActivated(activeInfo: chrome.tabs.TabActiveInfo): Promise<void> {
|
private _onTabActivated = async (activeInfo: chrome.tabs.TabActiveInfo): Promise<void> => {
|
||||||
const isConnected = this._connectedTabs.has(activeInfo.tabId);
|
const isConnected = this._connectedTabs.has(activeInfo.tabId);
|
||||||
debugLog('Tab activated:', activeInfo.tabId, 'is connected:', isConnected);
|
debugLog('Tab activated:', activeInfo.tabId, 'is connected:', isConnected);
|
||||||
await this._updateIcon(activeInfo.tabId, isConnected ? 'connected' : 'disconnected');
|
await this._updateIcon(activeInfo.tabId, isConnected ? 'connected' : 'disconnected');
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
new SimplifiedExtension();
|
new SimplifiedExtension();
|
||||||
|
|||||||
@@ -41,16 +41,42 @@ export class RelayConnection {
|
|||||||
private _attachedTabs: Map<number, AttachedTab> = new Map();
|
private _attachedTabs: Map<number, AttachedTab> = new Map();
|
||||||
private _nextSessionId: number = 1;
|
private _nextSessionId: number = 1;
|
||||||
private _ws: WebSocket;
|
private _ws: WebSocket;
|
||||||
private _eventListener: (source: chrome.debugger.DebuggerSession, method: string, params: any) => void;
|
|
||||||
private _detachListener: (source: chrome.debugger.Debuggee, reason: string) => void;
|
|
||||||
private _closed = false;
|
private _closed = false;
|
||||||
|
|
||||||
onclose?: () => void;
|
onclose?: () => void;
|
||||||
|
|
||||||
constructor(ws: WebSocket) {
|
constructor(ws: WebSocket) {
|
||||||
this._ws = ws;
|
this._ws = ws;
|
||||||
this._ws.onmessage = this._onMessage.bind(this);
|
this._ws.onmessage = async (event: MessageEvent) => {
|
||||||
this._ws.onclose = (event) => {
|
let message: ExtensionCommandMessage;
|
||||||
|
try {
|
||||||
|
message = JSON.parse(event.data);
|
||||||
|
} catch (error: any) {
|
||||||
|
debugLog('Error parsing message:', error);
|
||||||
|
this._sendMessage({
|
||||||
|
error: {
|
||||||
|
code: -32700,
|
||||||
|
message: `Error parsing message: ${error.message}`,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLog('Received message:', message);
|
||||||
|
|
||||||
|
const response: ExtensionResponseMessage = {
|
||||||
|
id: message.id,
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
response.result = await this._handleCommand(message);
|
||||||
|
} catch (error: any) {
|
||||||
|
debugLog('Error handling command:', error);
|
||||||
|
response.error = error.message;
|
||||||
|
}
|
||||||
|
debugLog('Sending response:', response);
|
||||||
|
this._sendMessage(response);
|
||||||
|
};
|
||||||
|
this._ws.onclose = (event: CloseEvent) => {
|
||||||
debugLog('WebSocket onclose event:', {
|
debugLog('WebSocket onclose event:', {
|
||||||
code: event.code,
|
code: event.code,
|
||||||
reason: event.reason,
|
reason: event.reason,
|
||||||
@@ -58,13 +84,11 @@ export class RelayConnection {
|
|||||||
});
|
});
|
||||||
this._onClose();
|
this._onClose();
|
||||||
};
|
};
|
||||||
this._ws.onerror = (event) => {
|
this._ws.onerror = (event: Event) => {
|
||||||
debugLog('WebSocket onerror event:', event);
|
debugLog('WebSocket onerror event:', event);
|
||||||
};
|
};
|
||||||
this._eventListener = this._onDebuggerEvent.bind(this);
|
chrome.debugger.onEvent.addListener(this._onDebuggerEvent);
|
||||||
this._detachListener = this._onDebuggerDetach.bind(this);
|
chrome.debugger.onDetach.addListener(this._onDebuggerDetach);
|
||||||
chrome.debugger.onEvent.addListener(this._eventListener);
|
|
||||||
chrome.debugger.onDetach.addListener(this._detachListener);
|
|
||||||
debugLog('RelayConnection created, WebSocket readyState:', this._ws.readyState);
|
debugLog('RelayConnection created, WebSocket readyState:', this._ws.readyState);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -167,8 +191,8 @@ export class RelayConnection {
|
|||||||
debugLog('Connection closing, attached tabs count:', this._attachedTabs.size);
|
debugLog('Connection closing, attached tabs count:', this._attachedTabs.size);
|
||||||
this._closed = true;
|
this._closed = true;
|
||||||
|
|
||||||
chrome.debugger.onEvent.removeListener(this._eventListener);
|
chrome.debugger.onEvent.removeListener(this._onDebuggerEvent);
|
||||||
chrome.debugger.onDetach.removeListener(this._detachListener);
|
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);
|
debugLog('Detaching all tabs:', tabIds);
|
||||||
@@ -191,7 +215,7 @@ export class RelayConnection {
|
|||||||
this.onclose?.();
|
this.onclose?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onDebuggerEvent(source: chrome.debugger.DebuggerSession, method: string, params: any): void {
|
private _onDebuggerEvent = (source: chrome.debugger.DebuggerSession, method: string, params: any): void => {
|
||||||
const tab = this._attachedTabs.get(source.tabId!);
|
const tab = this._attachedTabs.get(source.tabId!);
|
||||||
if (!tab) return;
|
if (!tab) return;
|
||||||
|
|
||||||
@@ -221,9 +245,9 @@ export class RelayConnection {
|
|||||||
params,
|
params,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
private _onDebuggerDetach(source: chrome.debugger.Debuggee, reason: string): void {
|
private _onDebuggerDetach = (source: chrome.debugger.Debuggee, reason: string): void => {
|
||||||
const tabId = source.tabId;
|
const tabId = source.tabId;
|
||||||
debugLog('_onDebuggerDetach called for tab:', tabId, 'reason:', reason, 'isAttached:', tabId ? this._attachedTabs.has(tabId) : false);
|
debugLog('_onDebuggerDetach called for tab:', tabId, 'reason:', reason, 'isAttached:', tabId ? this._attachedTabs.has(tabId) : false);
|
||||||
|
|
||||||
@@ -234,36 +258,7 @@ export class RelayConnection {
|
|||||||
|
|
||||||
debugLog(`Debugger detached from tab ${tabId}: ${reason}`);
|
debugLog(`Debugger detached from tab ${tabId}: ${reason}`);
|
||||||
this.detachTab(tabId);
|
this.detachTab(tabId);
|
||||||
}
|
};
|
||||||
|
|
||||||
private _onMessage(event: MessageEvent): void {
|
|
||||||
this._onMessageAsync(event).catch(e => debugLog('Error handling message:', e));
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _onMessageAsync(event: MessageEvent): Promise<void> {
|
|
||||||
let message: ExtensionCommandMessage;
|
|
||||||
try {
|
|
||||||
message = JSON.parse(event.data);
|
|
||||||
} catch (error: any) {
|
|
||||||
debugLog('Error parsing message:', error);
|
|
||||||
this._sendError(-32700, `Error parsing message: ${error.message}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLog('Received message:', message);
|
|
||||||
|
|
||||||
const response: ExtensionResponseMessage = {
|
|
||||||
id: message.id,
|
|
||||||
};
|
|
||||||
try {
|
|
||||||
response.result = await this._handleCommand(message);
|
|
||||||
} catch (error: any) {
|
|
||||||
debugLog('Error handling command:', error);
|
|
||||||
response.error = error.message;
|
|
||||||
}
|
|
||||||
debugLog('Sending response:', response);
|
|
||||||
this._sendMessage(response);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _handleCommand(message: ExtensionCommandMessage): Promise<any> {
|
private async _handleCommand(message: ExtensionCommandMessage): Promise<any> {
|
||||||
if (message.method === 'attachToTab') {
|
if (message.method === 'attachToTab') {
|
||||||
@@ -339,15 +334,6 @@ export class RelayConnection {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private _sendError(code: number, message: string): void {
|
|
||||||
this._sendMessage({
|
|
||||||
error: {
|
|
||||||
code,
|
|
||||||
message,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _sendMessage(message: any): void {
|
private _sendMessage(message: any): void {
|
||||||
if (this._ws.readyState === WebSocket.OPEN) {
|
if (this._ws.readyState === WebSocket.OPEN) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -66,7 +66,31 @@ export class CDPRelayServer {
|
|||||||
|
|
||||||
this._resetExtensionConnection();
|
this._resetExtensionConnection();
|
||||||
this._wss = new wsServer({ server });
|
this._wss = new wsServer({ server });
|
||||||
this._wss.on('connection', this._onConnection.bind(this));
|
this._wss.on('connection', (ws: WebSocket, request: http.IncomingMessage) => {
|
||||||
|
const url = new URL(`http://localhost${request.url}`);
|
||||||
|
debugLogger(`New connection to ${url.pathname}`);
|
||||||
|
if (url.pathname === this._cdpPath) {
|
||||||
|
this._handlePlaywrightConnection(ws);
|
||||||
|
} else if (url.pathname === this._extensionPath) {
|
||||||
|
if (this._extensionConnection) {
|
||||||
|
ws.close(1000, 'Another extension connection already established');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._extensionConnection = new ExtensionConnection(ws);
|
||||||
|
this._extensionConnection.onclose = (c, reason) => {
|
||||||
|
debugLogger('Extension WebSocket closed:', reason, c === this._extensionConnection);
|
||||||
|
if (this._extensionConnection !== c)
|
||||||
|
return;
|
||||||
|
this._resetExtensionConnection();
|
||||||
|
this._closePlaywrightConnection(`Extension disconnected: ${reason}`);
|
||||||
|
};
|
||||||
|
this._extensionConnection.onmessage = this._handleExtensionMessage.bind(this);
|
||||||
|
this._extensionConnectionPromise.resolve();
|
||||||
|
} else {
|
||||||
|
debugLogger(`Invalid path: ${url.pathname}`);
|
||||||
|
ws.close(4004, 'Invalid path');
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
cdpEndpoint() {
|
cdpEndpoint() {
|
||||||
@@ -88,18 +112,7 @@ export class CDPRelayServer {
|
|||||||
this._closeExtensionConnection(reason);
|
this._closeExtensionConnection(reason);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onConnection(ws: WebSocket, request: http.IncomingMessage): void {
|
|
||||||
const url = new URL(`http://localhost${request.url}`);
|
|
||||||
debugLogger(`New connection to ${url.pathname}`);
|
|
||||||
if (url.pathname === this._cdpPath) {
|
|
||||||
this._handlePlaywrightConnection(ws);
|
|
||||||
} else if (url.pathname === this._extensionPath) {
|
|
||||||
this._handleExtensionConnection(ws);
|
|
||||||
} else {
|
|
||||||
debugLogger(`Invalid path: ${url.pathname}`);
|
|
||||||
ws.close(4004, 'Invalid path');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handlePlaywrightConnection(ws: WebSocket): void {
|
private _handlePlaywrightConnection(ws: WebSocket): void {
|
||||||
if (this._playwrightConnection) {
|
if (this._playwrightConnection) {
|
||||||
@@ -148,22 +161,7 @@ export class CDPRelayServer {
|
|||||||
this._playwrightConnection = null;
|
this._playwrightConnection = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _handleExtensionConnection(ws: WebSocket): void {
|
|
||||||
if (this._extensionConnection) {
|
|
||||||
ws.close(1000, 'Another extension connection already established');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._extensionConnection = new ExtensionConnection(ws);
|
|
||||||
this._extensionConnection.onclose = (c, reason) => {
|
|
||||||
debugLogger('Extension WebSocket closed:', reason, c === this._extensionConnection);
|
|
||||||
if (this._extensionConnection !== c)
|
|
||||||
return;
|
|
||||||
this._resetExtensionConnection();
|
|
||||||
this._closePlaywrightConnection(`Extension disconnected: ${reason}`);
|
|
||||||
};
|
|
||||||
this._extensionConnection.onmessage = this._handleExtensionMessage.bind(this);
|
|
||||||
this._extensionConnectionPromise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleExtensionMessage(message: ExtensionEventMessage) {
|
private _handleExtensionMessage(message: ExtensionEventMessage) {
|
||||||
if (message.method === 'forwardCDPEvent') {
|
if (message.method === 'forwardCDPEvent') {
|
||||||
@@ -208,8 +206,94 @@ export class CDPRelayServer {
|
|||||||
private async _handlePlaywrightMessage(message: CDPCommand): Promise<void> {
|
private async _handlePlaywrightMessage(message: CDPCommand): Promise<void> {
|
||||||
debugLogger('\x1b[36m← Playwright:\x1b[0m', `${message.method} (id=${message.id})`);
|
debugLogger('\x1b[36m← Playwright:\x1b[0m', `${message.method} (id=${message.id})`);
|
||||||
const { id, sessionId, method, params } = message;
|
const { id, sessionId, method, params } = message;
|
||||||
|
|
||||||
|
const forwardToExtension = async (method: string, params: any, sessionId: string | undefined): Promise<any> => {
|
||||||
|
if (!this._extensionConnection)
|
||||||
|
throw new Error('Extension not connected');
|
||||||
|
|
||||||
|
return await this._extensionConnection.send({
|
||||||
|
method: 'forwardCDPCommand',
|
||||||
|
params: { sessionId, method, params }
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCDPCommand = async (method: string, params: any, sessionId: string | undefined): Promise<any> => {
|
||||||
|
switch (method) {
|
||||||
|
case 'Browser.getVersion': {
|
||||||
|
return {
|
||||||
|
protocolVersion: '1.3',
|
||||||
|
product: 'Chrome/Extension-Bridge',
|
||||||
|
revision: '1.0.0',
|
||||||
|
userAgent: 'CDP-Bridge-Server/1.0.0',
|
||||||
|
jsVersion: 'V8',
|
||||||
|
} satisfies Protocol.Browser.GetVersionResponse;
|
||||||
|
}
|
||||||
|
case 'Browser.setDownloadBehavior': {
|
||||||
|
return { };
|
||||||
|
}
|
||||||
|
case 'Target.setAutoAttach': {
|
||||||
|
if (sessionId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
debugLogger('Target.setAutoAttach received (manual attach mode)');
|
||||||
|
debugLogger('Sending Target.attachedToTarget events for existing targets:', this._connectedTargets.size);
|
||||||
|
|
||||||
|
for (const target of this._connectedTargets.values()) {
|
||||||
|
debugLogger('Sending Target.attachedToTarget for sessionId:', target.sessionId, 'targetId:', target.targetId);
|
||||||
|
this._sendToPlaywright({
|
||||||
|
method: 'Target.attachedToTarget',
|
||||||
|
params: {
|
||||||
|
sessionId: target.sessionId,
|
||||||
|
targetInfo: {
|
||||||
|
...target.targetInfo,
|
||||||
|
attached: true
|
||||||
|
},
|
||||||
|
waitingForDebugger: false
|
||||||
|
}
|
||||||
|
} satisfies CDPEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
case 'Target.getTargetInfo': {
|
||||||
|
const targetId = params?.targetId;
|
||||||
|
|
||||||
|
if (targetId) {
|
||||||
|
for (const target of this._connectedTargets.values()) {
|
||||||
|
if (target.targetId === targetId) {
|
||||||
|
return { targetInfo: target.targetInfo };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sessionId) {
|
||||||
|
const target = this._connectedTargets.get(sessionId);
|
||||||
|
if (target) {
|
||||||
|
return { targetInfo: target.targetInfo };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstTarget = this._connectedTargets.values().next().value;
|
||||||
|
return { targetInfo: firstTarget?.targetInfo };
|
||||||
|
}
|
||||||
|
case 'Target.getTargets': {
|
||||||
|
return {
|
||||||
|
targetInfos: Array.from(this._connectedTargets.values()).map(t => ({
|
||||||
|
...t.targetInfo,
|
||||||
|
attached: true,
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'Target.closeTarget': {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return await forwardToExtension(method, params, sessionId);
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this._handleCDPCommand(method, params, sessionId);
|
const result = await handleCDPCommand(method, params, sessionId);
|
||||||
this._sendToPlaywright({ id, sessionId, result });
|
this._sendToPlaywright({ id, sessionId, result });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
debugLogger('\x1b[31mError in the extension:\x1b[0m', e);
|
debugLogger('\x1b[31mError in the extension:\x1b[0m', e);
|
||||||
@@ -221,91 +305,6 @@ export class CDPRelayServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async _handleCDPCommand(method: string, params: any, sessionId: string | undefined): Promise<any> {
|
|
||||||
switch (method) {
|
|
||||||
case 'Browser.getVersion': {
|
|
||||||
return {
|
|
||||||
protocolVersion: '1.3',
|
|
||||||
product: 'Chrome/Extension-Bridge',
|
|
||||||
revision: '1.0.0',
|
|
||||||
userAgent: 'CDP-Bridge-Server/1.0.0',
|
|
||||||
jsVersion: 'V8',
|
|
||||||
} satisfies Protocol.Browser.GetVersionResponse;
|
|
||||||
}
|
|
||||||
case 'Browser.setDownloadBehavior': {
|
|
||||||
return { };
|
|
||||||
}
|
|
||||||
case 'Target.setAutoAttach': {
|
|
||||||
if (sessionId) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
debugLogger('Target.setAutoAttach received (manual attach mode)');
|
|
||||||
debugLogger('Sending Target.attachedToTarget events for existing targets:', this._connectedTargets.size);
|
|
||||||
|
|
||||||
for (const target of this._connectedTargets.values()) {
|
|
||||||
debugLogger('Sending Target.attachedToTarget for sessionId:', target.sessionId, 'targetId:', target.targetId);
|
|
||||||
this._sendToPlaywright({
|
|
||||||
method: 'Target.attachedToTarget',
|
|
||||||
params: {
|
|
||||||
sessionId: target.sessionId,
|
|
||||||
targetInfo: {
|
|
||||||
...target.targetInfo,
|
|
||||||
attached: true
|
|
||||||
},
|
|
||||||
waitingForDebugger: false
|
|
||||||
}
|
|
||||||
} as CDPEvent);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
case 'Target.getTargetInfo': {
|
|
||||||
const targetId = params?.targetId;
|
|
||||||
|
|
||||||
if (targetId) {
|
|
||||||
for (const target of this._connectedTargets.values()) {
|
|
||||||
if (target.targetId === targetId) {
|
|
||||||
return { targetInfo: target.targetInfo };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sessionId) {
|
|
||||||
const target = this._connectedTargets.get(sessionId);
|
|
||||||
if (target) {
|
|
||||||
return { targetInfo: target.targetInfo };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const firstTarget = this._connectedTargets.values().next().value;
|
|
||||||
return { targetInfo: firstTarget?.targetInfo };
|
|
||||||
}
|
|
||||||
case 'Target.getTargets': {
|
|
||||||
return {
|
|
||||||
targetInfos: Array.from(this._connectedTargets.values()).map(t => ({
|
|
||||||
...t.targetInfo,
|
|
||||||
attached: true,
|
|
||||||
}))
|
|
||||||
};
|
|
||||||
}
|
|
||||||
case 'Target.closeTarget': {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return await this._forwardToExtension(method, params, sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
private async _forwardToExtension(method: string, params: any, sessionId: string | undefined): Promise<any> {
|
|
||||||
if (!this._extensionConnection)
|
|
||||||
throw new Error('Extension not connected');
|
|
||||||
|
|
||||||
return await this._extensionConnection.send({
|
|
||||||
method: 'forwardCDPCommand',
|
|
||||||
params: { sessionId, method, params }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private _sendToPlaywright(message: CDPResponse | CDPEvent): void {
|
private _sendToPlaywright(message: CDPResponse | CDPEvent): void {
|
||||||
const logMessage = 'method' in message && message.method
|
const logMessage = 'method' in message && message.method
|
||||||
? message.method
|
? message.method
|
||||||
@@ -325,9 +324,54 @@ class ExtensionConnection {
|
|||||||
|
|
||||||
constructor(ws: WebSocket) {
|
constructor(ws: WebSocket) {
|
||||||
this._ws = ws;
|
this._ws = ws;
|
||||||
this._ws.on('message', this._onMessage.bind(this));
|
|
||||||
this._ws.on('close', this._onClose.bind(this));
|
this._ws.on('message', (event: websocket.RawData) => {
|
||||||
this._ws.on('error', this._onError.bind(this));
|
const eventData = event.toString();
|
||||||
|
let parsedJson;
|
||||||
|
try {
|
||||||
|
parsedJson = JSON.parse(eventData);
|
||||||
|
} catch (e: any) {
|
||||||
|
debugLogger(`<closing ws> Closing websocket due to malformed JSON. eventData=${eventData} e=${e?.message}`);
|
||||||
|
this._ws.close();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleParsedMessage = (object: ExtensionMessage) => {
|
||||||
|
if ('id' in object && this._callbacks.has(object.id)) {
|
||||||
|
const callback = this._callbacks.get(object.id)!;
|
||||||
|
this._callbacks.delete(object.id);
|
||||||
|
if (object.error) {
|
||||||
|
const error = callback.error;
|
||||||
|
error.message = object.error;
|
||||||
|
callback.reject(error);
|
||||||
|
} else {
|
||||||
|
callback.resolve(object.result);
|
||||||
|
}
|
||||||
|
} else if ('id' in object) {
|
||||||
|
debugLogger('← Extension: unexpected response', object);
|
||||||
|
} else {
|
||||||
|
this.onmessage?.(object as ExtensionEventMessage);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
handleParsedMessage(parsedJson);
|
||||||
|
} catch (e: any) {
|
||||||
|
debugLogger(`<closing ws> Closing websocket due to failed onmessage callback. eventData=${eventData} e=${e?.message}`);
|
||||||
|
this._ws.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._ws.on('close', (event: websocket.CloseEvent) => {
|
||||||
|
debugLogger(`<ws closed> code=${event.code} reason=${event.reason}`);
|
||||||
|
this._dispose();
|
||||||
|
this.onclose?.(this, event.reason);
|
||||||
|
});
|
||||||
|
|
||||||
|
this._ws.on('error', (event: websocket.ErrorEvent) => {
|
||||||
|
debugLogger(`<ws error> message=${event.message} type=${event.type} target=${event.target}`);
|
||||||
|
this._dispose();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async send(command: Omit<ExtensionCommandMessage, 'id'>): Promise<any> {
|
async send(command: Omit<ExtensionCommandMessage, 'id'>): Promise<any> {
|
||||||
@@ -347,53 +391,6 @@ class ExtensionConnection {
|
|||||||
this._ws.close(1000, message);
|
this._ws.close(1000, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private _onMessage(event: websocket.RawData) {
|
|
||||||
const eventData = event.toString();
|
|
||||||
let parsedJson;
|
|
||||||
try {
|
|
||||||
parsedJson = JSON.parse(eventData);
|
|
||||||
} catch (e: any) {
|
|
||||||
debugLogger(`<closing ws> Closing websocket due to malformed JSON. eventData=${eventData} e=${e?.message}`);
|
|
||||||
this._ws.close();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
this._handleParsedMessage(parsedJson);
|
|
||||||
} catch (e: any) {
|
|
||||||
debugLogger(`<closing ws> Closing websocket due to failed onmessage callback. eventData=${eventData} e=${e?.message}`);
|
|
||||||
this._ws.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _handleParsedMessage(object: ExtensionMessage) {
|
|
||||||
if ('id' in object && this._callbacks.has(object.id)) {
|
|
||||||
const callback = this._callbacks.get(object.id)!;
|
|
||||||
this._callbacks.delete(object.id);
|
|
||||||
if (object.error) {
|
|
||||||
const error = callback.error;
|
|
||||||
error.message = object.error;
|
|
||||||
callback.reject(error);
|
|
||||||
} else {
|
|
||||||
callback.resolve(object.result);
|
|
||||||
}
|
|
||||||
} else if ('id' in object) {
|
|
||||||
debugLogger('← Extension: unexpected response', object);
|
|
||||||
} else {
|
|
||||||
this.onmessage?.(object as ExtensionEventMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onClose(event: websocket.CloseEvent) {
|
|
||||||
debugLogger(`<ws closed> code=${event.code} reason=${event.reason}`);
|
|
||||||
this._dispose();
|
|
||||||
this.onclose?.(this, event.reason);
|
|
||||||
}
|
|
||||||
|
|
||||||
private _onError(event: websocket.ErrorEvent) {
|
|
||||||
debugLogger(`<ws error> message=${event.message} type=${event.type} target=${event.target}`);
|
|
||||||
this._dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
private _dispose() {
|
private _dispose() {
|
||||||
for (const callback of this._callbacks.values())
|
for (const callback of this._callbacks.values())
|
||||||
callback.reject(new Error('WebSocket closed'));
|
callback.reject(new Error('WebSocket closed'));
|
||||||
|
|||||||
Reference in New Issue
Block a user