chrome-cdp-cli 1.5.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/cli/CLIApplication.js +28 -0
- package/dist/cli/CLIInterface.js +65 -1
- package/dist/cli/CommandRouter.js +6 -0
- package/dist/client/ProxyClient.js +278 -0
- package/dist/client/index.js +1 -0
- package/dist/handlers/EvaluateScriptHandler.js +143 -1
- package/dist/handlers/GetConsoleMessageHandler.js +80 -10
- package/dist/handlers/GetNetworkRequestHandler.js +68 -10
- package/dist/handlers/ListConsoleMessagesHandler.js +83 -12
- package/dist/handlers/ListNetworkRequestsHandler.js +67 -11
- package/dist/monitors/ConsoleMonitor.js +47 -0
- package/dist/proxy/ProxyManager.js +267 -0
- package/dist/proxy/index.js +60 -0
- package/dist/proxy/server/CDPEventMonitor.js +263 -0
- package/dist/proxy/server/CDPProxyServer.js +437 -0
- package/dist/proxy/server/CommandExecutionService.js +297 -0
- package/dist/proxy/server/ConnectionPool.js +437 -0
- package/dist/proxy/server/FileSystemSecurity.js +358 -0
- package/dist/proxy/server/HealthMonitor.js +242 -0
- package/dist/proxy/server/MessageStore.js +360 -0
- package/dist/proxy/server/PerformanceMonitor.js +277 -0
- package/dist/proxy/server/ProxyAPIServer.js +1120 -0
- package/dist/proxy/server/SecurityManager.js +337 -0
- package/dist/proxy/server/WSProxy.js +468 -0
- package/dist/proxy/types/ProxyTypes.js +2 -0
- package/dist/utils/logger.js +256 -18
- package/package.json +7 -1
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CommandExecutionService = void 0;
|
|
4
|
+
const logger_1 = require("../../utils/logger");
|
|
5
|
+
class CommandExecutionService {
|
|
6
|
+
constructor(connectionPool) {
|
|
7
|
+
this.pendingCommands = new Map();
|
|
8
|
+
this.commandMetrics = {
|
|
9
|
+
totalCommands: 0,
|
|
10
|
+
successfulCommands: 0,
|
|
11
|
+
failedCommands: 0,
|
|
12
|
+
averageExecutionTime: 0,
|
|
13
|
+
timeoutCount: 0
|
|
14
|
+
};
|
|
15
|
+
this.messageHandlers = new Map();
|
|
16
|
+
this.activeCLIClient = null;
|
|
17
|
+
this.connectionPool = connectionPool;
|
|
18
|
+
this.logger = (0, logger_1.createLogger)({ component: 'CommandExecutionService' });
|
|
19
|
+
}
|
|
20
|
+
async executeCommand(request, clientId) {
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
const commandId = this.generateCommandId();
|
|
23
|
+
const timeout = request.timeout || 30000;
|
|
24
|
+
try {
|
|
25
|
+
if (this.activeCLIClient && clientId && this.activeCLIClient !== clientId) {
|
|
26
|
+
throw new Error('Another CLI client is already connected. Only one CLI client can use the proxy at a time.');
|
|
27
|
+
}
|
|
28
|
+
if (clientId && !this.activeCLIClient) {
|
|
29
|
+
this.activeCLIClient = clientId;
|
|
30
|
+
this.logger.info(`CLI client ${clientId} is now the active client`);
|
|
31
|
+
}
|
|
32
|
+
const connection = this.connectionPool.getConnectionInfo(request.connectionId);
|
|
33
|
+
if (!connection) {
|
|
34
|
+
throw new Error(`Connection ${request.connectionId} not found`);
|
|
35
|
+
}
|
|
36
|
+
if (!connection.isHealthy) {
|
|
37
|
+
throw new Error(`Connection ${request.connectionId} is not healthy`);
|
|
38
|
+
}
|
|
39
|
+
this.logger.debug(`Executing CDP command: ${request.command.method}`, {
|
|
40
|
+
connectionId: request.connectionId,
|
|
41
|
+
commandId,
|
|
42
|
+
method: request.command.method,
|
|
43
|
+
timeout,
|
|
44
|
+
clientId: clientId || 'unknown'
|
|
45
|
+
});
|
|
46
|
+
const cdpMessage = {
|
|
47
|
+
id: typeof request.command.id === 'number' ? request.command.id : this.generateCDPMessageId(),
|
|
48
|
+
method: request.command.method,
|
|
49
|
+
params: request.command.params
|
|
50
|
+
};
|
|
51
|
+
const result = await this.sendCDPCommand(request.connectionId, cdpMessage, timeout);
|
|
52
|
+
const executionTime = Date.now() - startTime;
|
|
53
|
+
this.updateMetrics(true, executionTime);
|
|
54
|
+
this.logger.debug(`CDP command executed successfully: ${request.command.method}`, {
|
|
55
|
+
connectionId: request.connectionId,
|
|
56
|
+
commandId,
|
|
57
|
+
executionTime,
|
|
58
|
+
clientId: clientId || 'unknown'
|
|
59
|
+
});
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
result,
|
|
63
|
+
executionTime
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const executionTime = Date.now() - startTime;
|
|
68
|
+
this.updateMetrics(false, executionTime);
|
|
69
|
+
this.logger.error(`CDP command execution failed: ${request.command.method}`, {
|
|
70
|
+
connectionId: request.connectionId,
|
|
71
|
+
commandId,
|
|
72
|
+
executionTime,
|
|
73
|
+
clientId: clientId || 'unknown',
|
|
74
|
+
error: error instanceof Error ? error.message : String(error)
|
|
75
|
+
});
|
|
76
|
+
let errorCode = 500;
|
|
77
|
+
let errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
78
|
+
if (errorMessage.includes('Another CLI client')) {
|
|
79
|
+
errorCode = 409;
|
|
80
|
+
}
|
|
81
|
+
else if (errorMessage.includes('timeout')) {
|
|
82
|
+
errorCode = 408;
|
|
83
|
+
this.commandMetrics.timeoutCount++;
|
|
84
|
+
}
|
|
85
|
+
else if (errorMessage.includes('not found')) {
|
|
86
|
+
errorCode = 404;
|
|
87
|
+
}
|
|
88
|
+
else if (errorMessage.includes('not healthy')) {
|
|
89
|
+
errorCode = 503;
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: {
|
|
94
|
+
code: errorCode,
|
|
95
|
+
message: errorMessage
|
|
96
|
+
},
|
|
97
|
+
executionTime
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async sendCDPCommand(connectionId, command, timeout) {
|
|
102
|
+
const connection = this.connectionPool.getConnectionInfo(connectionId);
|
|
103
|
+
if (!connection) {
|
|
104
|
+
throw new Error(`Connection ${connectionId} not found`);
|
|
105
|
+
}
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
107
|
+
const commandKey = `${connectionId}:${command.id}`;
|
|
108
|
+
const pendingCommand = {
|
|
109
|
+
id: commandKey,
|
|
110
|
+
connectionId,
|
|
111
|
+
command,
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
timeout,
|
|
114
|
+
resolve,
|
|
115
|
+
reject
|
|
116
|
+
};
|
|
117
|
+
this.pendingCommands.set(commandKey, pendingCommand);
|
|
118
|
+
this.setupMessageHandler(connectionId);
|
|
119
|
+
const timeoutHandle = setTimeout(() => {
|
|
120
|
+
if (this.pendingCommands.has(commandKey)) {
|
|
121
|
+
this.pendingCommands.delete(commandKey);
|
|
122
|
+
reject(new Error(`Command timeout after ${timeout}ms: ${command.method}`));
|
|
123
|
+
}
|
|
124
|
+
}, timeout);
|
|
125
|
+
pendingCommand.resolve = (value) => {
|
|
126
|
+
clearTimeout(timeoutHandle);
|
|
127
|
+
resolve(value);
|
|
128
|
+
};
|
|
129
|
+
pendingCommand.reject = (error) => {
|
|
130
|
+
clearTimeout(timeoutHandle);
|
|
131
|
+
reject(error);
|
|
132
|
+
};
|
|
133
|
+
try {
|
|
134
|
+
const messageStr = JSON.stringify(command);
|
|
135
|
+
connection.connection.send(messageStr);
|
|
136
|
+
this.logger.debug(`Sent CDP command to Chrome: ${command.method}`, {
|
|
137
|
+
connectionId,
|
|
138
|
+
commandId: command.id,
|
|
139
|
+
messageLength: messageStr.length
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
catch (error) {
|
|
143
|
+
clearTimeout(timeoutHandle);
|
|
144
|
+
this.pendingCommands.delete(commandKey);
|
|
145
|
+
reject(new Error(`Failed to send command: ${error instanceof Error ? error.message : String(error)}`));
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
setupMessageHandler(connectionId) {
|
|
150
|
+
if (this.messageHandlers.has(connectionId)) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const connection = this.connectionPool.getConnectionInfo(connectionId);
|
|
154
|
+
if (!connection) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const messageHandler = (data) => {
|
|
158
|
+
try {
|
|
159
|
+
const message = data.toString();
|
|
160
|
+
let cdpResponse;
|
|
161
|
+
try {
|
|
162
|
+
cdpResponse = JSON.parse(message);
|
|
163
|
+
}
|
|
164
|
+
catch (parseError) {
|
|
165
|
+
this.logger.warn(`Invalid JSON from CDP connection ${connectionId}: ${message.substring(0, 100)}`);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (typeof cdpResponse.id === 'undefined') {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const commandKey = `${connectionId}:${cdpResponse.id}`;
|
|
172
|
+
const pendingCommand = this.pendingCommands.get(commandKey);
|
|
173
|
+
if (pendingCommand) {
|
|
174
|
+
this.pendingCommands.delete(commandKey);
|
|
175
|
+
this.logger.debug(`Received CDP response for command: ${pendingCommand.command.method}`, {
|
|
176
|
+
connectionId,
|
|
177
|
+
commandId: cdpResponse.id,
|
|
178
|
+
hasResult: !!cdpResponse.result,
|
|
179
|
+
hasError: !!cdpResponse.error
|
|
180
|
+
});
|
|
181
|
+
if (cdpResponse.error) {
|
|
182
|
+
pendingCommand.reject(new Error(`CDP Error: ${cdpResponse.error.message} (Code: ${cdpResponse.error.code})`));
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
pendingCommand.resolve(cdpResponse.result);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
else {
|
|
189
|
+
this.logger.debug(`Received CDP response for unknown command ID: ${cdpResponse.id}`, {
|
|
190
|
+
connectionId
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
catch (error) {
|
|
195
|
+
this.logger.error(`Error handling CDP message for connection ${connectionId}:`, error);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
connection.connection.on('message', messageHandler);
|
|
199
|
+
this.messageHandlers.set(connectionId, messageHandler);
|
|
200
|
+
this.logger.debug(`Set up message handler for connection: ${connectionId}`);
|
|
201
|
+
connection.connection.on('close', () => {
|
|
202
|
+
this.cleanupMessageHandler(connectionId);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
cleanupMessageHandler(connectionId) {
|
|
206
|
+
const handler = this.messageHandlers.get(connectionId);
|
|
207
|
+
if (handler) {
|
|
208
|
+
const connection = this.connectionPool.getConnectionInfo(connectionId);
|
|
209
|
+
if (connection) {
|
|
210
|
+
connection.connection.off('message', handler);
|
|
211
|
+
}
|
|
212
|
+
this.messageHandlers.delete(connectionId);
|
|
213
|
+
this.logger.debug(`Cleaned up message handler for connection: ${connectionId}`);
|
|
214
|
+
}
|
|
215
|
+
const commandsToCleanup = [];
|
|
216
|
+
for (const [, pendingCommand] of this.pendingCommands.entries()) {
|
|
217
|
+
if (pendingCommand.connectionId === connectionId) {
|
|
218
|
+
commandsToCleanup.push(pendingCommand.id);
|
|
219
|
+
pendingCommand.reject(new Error('Connection closed'));
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
for (const commandKey of commandsToCleanup) {
|
|
223
|
+
this.pendingCommands.delete(commandKey);
|
|
224
|
+
}
|
|
225
|
+
if (commandsToCleanup.length > 0) {
|
|
226
|
+
this.logger.debug(`Cleaned up ${commandsToCleanup.length} pending commands for connection: ${connectionId}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
getMetrics() {
|
|
230
|
+
return { ...this.commandMetrics };
|
|
231
|
+
}
|
|
232
|
+
getPendingCommandsCount() {
|
|
233
|
+
return this.pendingCommands.size;
|
|
234
|
+
}
|
|
235
|
+
getPendingCommandsForConnection(connectionId) {
|
|
236
|
+
let count = 0;
|
|
237
|
+
for (const pendingCommand of this.pendingCommands.values()) {
|
|
238
|
+
if (pendingCommand.connectionId === connectionId) {
|
|
239
|
+
count++;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return count;
|
|
243
|
+
}
|
|
244
|
+
setActiveCLIClient(clientId) {
|
|
245
|
+
if (this.activeCLIClient && this.activeCLIClient !== clientId) {
|
|
246
|
+
this.logger.warn(`Replacing active CLI client ${this.activeCLIClient} with ${clientId}`);
|
|
247
|
+
}
|
|
248
|
+
this.activeCLIClient = clientId;
|
|
249
|
+
this.logger.info(`CLI client ${clientId} is now the active client`);
|
|
250
|
+
}
|
|
251
|
+
releaseActiveCLIClient(clientId) {
|
|
252
|
+
if (clientId && this.activeCLIClient !== clientId) {
|
|
253
|
+
this.logger.warn(`Attempted to release CLI client ${clientId}, but active client is ${this.activeCLIClient}`);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const previousClient = this.activeCLIClient;
|
|
257
|
+
this.activeCLIClient = null;
|
|
258
|
+
if (previousClient) {
|
|
259
|
+
this.logger.info(`Released active CLI client: ${previousClient}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
getActiveCLIClient() {
|
|
263
|
+
return this.activeCLIClient;
|
|
264
|
+
}
|
|
265
|
+
hasActiveCLIClient() {
|
|
266
|
+
return this.activeCLIClient !== null;
|
|
267
|
+
}
|
|
268
|
+
cleanup() {
|
|
269
|
+
for (const [, pendingCommand] of this.pendingCommands.entries()) {
|
|
270
|
+
pendingCommand.reject(new Error('Service shutting down'));
|
|
271
|
+
}
|
|
272
|
+
this.pendingCommands.clear();
|
|
273
|
+
for (const connectionId of this.messageHandlers.keys()) {
|
|
274
|
+
this.cleanupMessageHandler(connectionId);
|
|
275
|
+
}
|
|
276
|
+
this.activeCLIClient = null;
|
|
277
|
+
this.logger.info('CommandExecutionService cleanup completed');
|
|
278
|
+
}
|
|
279
|
+
generateCommandId() {
|
|
280
|
+
return `cmd_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
281
|
+
}
|
|
282
|
+
generateCDPMessageId() {
|
|
283
|
+
return Math.floor(Date.now() % 1000000) + Math.floor(Math.random() * 10000);
|
|
284
|
+
}
|
|
285
|
+
updateMetrics(success, executionTime) {
|
|
286
|
+
this.commandMetrics.totalCommands++;
|
|
287
|
+
if (success) {
|
|
288
|
+
this.commandMetrics.successfulCommands++;
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
this.commandMetrics.failedCommands++;
|
|
292
|
+
}
|
|
293
|
+
const totalTime = this.commandMetrics.averageExecutionTime * (this.commandMetrics.totalCommands - 1) + executionTime;
|
|
294
|
+
this.commandMetrics.averageExecutionTime = totalTime / this.commandMetrics.totalCommands;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
exports.CommandExecutionService = CommandExecutionService;
|