browser-debugger-cli
Version:
DevTools telemetry in your terminal. For humans and agents. Direct WebSocket to Chrome's debugging port.
217 lines • 8.69 kB
JavaScript
/**
* Response Handler
*
* Handles responses from worker process and forwards them to CLI clients.
* Manages worker exit scenarios and transforms worker responses to client format.
*/
import { getCommandName, isCommandResponse, } from '../../ipc/index.js';
/**
* Handles worker responses and exit events.
*/
export class ResponseHandler {
pendingRequests;
sessionService;
sendResponse;
constructor(pendingRequests, sessionService, sendResponse) {
this.pendingRequests = pendingRequests;
this.sessionService = sessionService;
this.sendResponse = sendResponse;
}
/**
* Handle response from worker (lifecycle signals or command responses).
*/
handleWorkerResponse(message) {
console.error(`[daemon] Received worker response: ${message.type} (requestId: ${message.requestId})`);
if (message.type === 'worker_ready') {
console.error('[daemon] Worker ready signal (already processed during launch)');
return;
}
if (isCommandResponse(message.type)) {
const pending = this.pendingRequests.get(message.requestId);
if (!pending) {
console.error(`[daemon] No pending request found for requestId: ${message.requestId}`);
return;
}
this.pendingRequests.remove(message.requestId);
this.forwardCommandResponse(pending.socket, pending.sessionId, message, pending);
}
}
/**
* Handle worker exit event.
*/
handleWorkerExit(code, signal) {
console.error(`[daemon] Worker exit detected (code: ${code ?? 'null'}, signal: ${signal ?? 'null'})`);
if (this.pendingRequests.size === 0) {
return;
}
const errorMessage = 'Worker process exited before responding';
for (const [requestId, pending] of this.pendingRequests.getAll()) {
this.pendingRequests.remove(requestId);
if (pending.commandName === 'worker_status') {
const statusResponse = {
type: 'status_response',
sessionId: pending.sessionId,
status: 'error',
...(pending.statusData && { data: pending.statusData }),
error: errorMessage,
};
this.sendResponse(pending.socket, statusResponse);
continue;
}
if (pending.commandName === 'worker_peek') {
const peekResponse = {
type: 'peek_response',
sessionId: pending.sessionId,
status: 'error',
error: errorMessage,
};
this.sendResponse(pending.socket, peekResponse);
continue;
}
if (pending.commandName === 'worker_har_data') {
const harDataResponse = {
type: 'har_data_response',
sessionId: pending.sessionId,
status: 'error',
error: errorMessage,
};
this.sendResponse(pending.socket, harDataResponse);
continue;
}
if (pending.commandName) {
const response = {
type: `${pending.commandName}_response`,
sessionId: pending.sessionId,
status: 'error',
error: errorMessage,
};
this.sendResponse(pending.socket, response);
continue;
}
const fallback = {
type: 'status_response',
sessionId: pending.sessionId,
status: 'error',
error: errorMessage,
};
this.sendResponse(pending.socket, fallback);
}
}
/**
* Generic forwarder for all command responses.
*/
forwardCommandResponse(socket, sessionId, workerResponse, pendingRequest) {
const commandName = getCommandName(workerResponse.type);
if (!commandName) {
console.error(`[daemon] Invalid worker response type: ${workerResponse.type}`);
return;
}
if (commandName === 'worker_status') {
this.forwardWorkerStatusResponse(socket, sessionId, workerResponse, pendingRequest);
return;
}
if (commandName === 'worker_peek') {
const { requestId: _requestId, success, data, error, } = workerResponse;
const peekResponse = {
type: 'peek_response',
sessionId,
status: success ? 'ok' : 'error',
...(success &&
data && {
data: {
sessionPid: this.sessionService.readPid() ?? 0,
preview: {
version: data.version,
success: true,
timestamp: new Date(data.startTime).toISOString(),
duration: data.duration,
target: data.target,
data: {
network: data.network,
console: data.console,
},
partial: true,
},
},
}),
...(error && { error }),
};
this.sendResponse(socket, peekResponse);
console.error('[daemon] Forwarded worker_peek_response to client (transformed to PeekResponse)');
return;
}
if (commandName === 'worker_har_data') {
const { requestId: _requestId, success, data, error, } = workerResponse;
const harDataResponse = {
type: 'har_data_response',
sessionId,
status: success ? 'ok' : 'error',
...(success &&
data && {
data: {
sessionPid: this.sessionService.readPid() ?? 0,
requests: data.requests,
},
}),
...(error && { error }),
};
this.sendResponse(socket, harDataResponse);
console.error('[daemon] Forwarded worker_har_data_response to client (transformed to HARDataResponse)');
return;
}
const { requestId: _requestId, success, ...rest } = workerResponse;
const response = {
...rest,
type: `${commandName}_response`,
sessionId,
status: success ? 'ok' : 'error',
};
this.sendResponse(socket, response);
console.error(`[daemon] Forwarded ${commandName}_response to client`);
}
/**
* Forward worker status response with enriched activity data.
*/
forwardWorkerStatusResponse(socket, sessionId, workerResponse, pendingRequest) {
const { success, data, error } = workerResponse;
const baseStatusData = pendingRequest?.statusData;
if (success && data && baseStatusData) {
const enrichedData = {
...baseStatusData,
activity: data.activity,
pageState: data.target,
navigationId: data.navigationId,
};
const statusResponse = {
type: 'status_response',
sessionId,
status: 'ok',
data: enrichedData,
};
this.sendResponse(socket, statusResponse);
console.error('[daemon] Forwarded worker_status_response to client (enriched with activity data)');
return;
}
if (baseStatusData) {
const statusResponse = {
type: 'status_response',
sessionId,
status: error ? 'error' : 'ok',
data: baseStatusData,
...(error && { error }),
};
this.sendResponse(socket, statusResponse);
console.error('[daemon] Forwarded status_response to client (worker query failed, using base data only)');
return;
}
const fallback = {
type: 'status_response',
sessionId,
status: 'error',
error: error ?? 'Failed to retrieve status data',
};
this.sendResponse(socket, fallback);
console.error('[daemon] Forwarded status_response error (no base data available)');
}
}
//# sourceMappingURL=responseHandler.js.map