chrome-cdp-cli 1.5.0 → 1.6.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.
@@ -0,0 +1,456 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.WSProxy = void 0;
37
+ const WebSocket = __importStar(require("ws"));
38
+ const logger_1 = require("../../utils/logger");
39
+ class WSProxy {
40
+ constructor(connectionPool) {
41
+ this.connections = new Map();
42
+ this.eventSubscriptions = new Map();
43
+ this.clientEventFilters = new Map();
44
+ this.connectionPool = connectionPool;
45
+ this.logger = (0, logger_1.createLogger)({ component: 'WSProxy' });
46
+ }
47
+ start(wsServer) {
48
+ this.wsServer = wsServer;
49
+ this.wsServer.on('connection', (ws, request) => {
50
+ this.handleConnection(ws, request);
51
+ });
52
+ this.logger.info('WebSocket proxy started');
53
+ }
54
+ stop() {
55
+ if (this.wsServer) {
56
+ for (const [proxyId] of Array.from(this.connections)) {
57
+ this.closeProxyConnection(proxyId);
58
+ }
59
+ this.wsServer.close();
60
+ this.wsServer = undefined;
61
+ this.logger.info('WebSocket proxy stopped');
62
+ }
63
+ }
64
+ handleConnection(ws, request) {
65
+ try {
66
+ const url = new URL(request.url || '', `http://${request.headers.host}`);
67
+ let connectionId = url.searchParams.get('connectionId');
68
+ if (!connectionId) {
69
+ const pathParts = url.pathname.split('/');
70
+ if (pathParts.length >= 3 && pathParts[1] === 'ws') {
71
+ connectionId = pathParts[2];
72
+ }
73
+ }
74
+ if (!connectionId) {
75
+ this.logger.logClientEvent('error', 'unknown', 'WebSocket connection rejected: Connection ID required', {
76
+ clientIP: request.socket.remoteAddress,
77
+ userAgent: request.headers['user-agent']
78
+ });
79
+ ws.close(1008, 'Connection ID required');
80
+ return;
81
+ }
82
+ const cdpConnection = this.connectionPool.getConnectionInfo(connectionId);
83
+ if (!cdpConnection) {
84
+ this.logger.logClientEvent('error', 'unknown', `WebSocket connection rejected: Invalid connection ID ${connectionId}`, {
85
+ connectionId,
86
+ clientIP: request.socket.remoteAddress
87
+ });
88
+ ws.close(1008, 'Invalid connection ID');
89
+ return;
90
+ }
91
+ if (!cdpConnection.isHealthy) {
92
+ this.logger.logClientEvent('error', 'unknown', `WebSocket connection rejected: CDP connection ${connectionId} is not healthy`, {
93
+ connectionId,
94
+ clientIP: request.socket.remoteAddress
95
+ });
96
+ ws.close(1011, 'CDP connection unavailable');
97
+ return;
98
+ }
99
+ const proxyId = this.generateProxyId();
100
+ const proxyConnection = {
101
+ id: proxyId,
102
+ clientWs: ws,
103
+ connectionId,
104
+ createdAt: Date.now(),
105
+ messageCount: 0
106
+ };
107
+ this.connections.set(proxyId, proxyConnection);
108
+ this.incrementClientCount(connectionId);
109
+ if (!this.eventSubscriptions.has(connectionId)) {
110
+ this.eventSubscriptions.set(connectionId, new Set());
111
+ }
112
+ this.eventSubscriptions.get(connectionId).add(proxyId);
113
+ this.clientEventFilters.set(proxyId, new Set());
114
+ this.logger.logClientEvent('connected', proxyId, `WebSocket proxy connection established for CDP connection ${connectionId}`, {
115
+ connectionId,
116
+ clientCount: cdpConnection.clientCount,
117
+ clientIP: request.socket.remoteAddress,
118
+ userAgent: request.headers['user-agent']
119
+ });
120
+ this.setupMessageForwarding(proxyConnection, cdpConnection);
121
+ ws.on('close', (code, reason) => {
122
+ this.logger.logClientEvent('disconnected', proxyId, `WebSocket client disconnected`, {
123
+ connectionId,
124
+ code,
125
+ reason: reason?.toString(),
126
+ messageCount: proxyConnection.messageCount,
127
+ sessionDuration: Date.now() - proxyConnection.createdAt
128
+ });
129
+ this.handleClientDisconnection(proxyId);
130
+ });
131
+ ws.on('error', (error) => {
132
+ this.logger.error(`WebSocket proxy error for ${proxyId}:`, error);
133
+ this.closeProxyConnection(proxyId);
134
+ });
135
+ ws.send(JSON.stringify({
136
+ type: 'proxy-connected',
137
+ proxyId,
138
+ connectionId,
139
+ timestamp: Date.now()
140
+ }));
141
+ }
142
+ catch (error) {
143
+ this.logger.error('Error handling WebSocket connection:', error);
144
+ ws.close(1011, 'Internal server error');
145
+ }
146
+ }
147
+ forwardMessage(proxyId, message) {
148
+ const proxyConnection = this.connections.get(proxyId);
149
+ if (proxyConnection && proxyConnection.clientWs.readyState === WebSocket.OPEN) {
150
+ proxyConnection.clientWs.send(message);
151
+ proxyConnection.messageCount++;
152
+ }
153
+ }
154
+ closeProxyConnection(proxyId) {
155
+ const proxyConnection = this.connections.get(proxyId);
156
+ if (proxyConnection) {
157
+ if (proxyConnection.clientWs.readyState === WebSocket.OPEN) {
158
+ proxyConnection.clientWs.close(1000, 'Proxy connection closed');
159
+ }
160
+ const connectionId = proxyConnection.connectionId;
161
+ const subscriptions = this.eventSubscriptions.get(connectionId);
162
+ if (subscriptions) {
163
+ subscriptions.delete(proxyId);
164
+ if (subscriptions.size === 0) {
165
+ this.eventSubscriptions.delete(connectionId);
166
+ }
167
+ }
168
+ this.clientEventFilters.delete(proxyId);
169
+ this.connectionPool.decrementClientCount(proxyConnection.connectionId);
170
+ this.connections.delete(proxyId);
171
+ this.logger.info(`WebSocket proxy connection ${proxyId} closed`);
172
+ }
173
+ }
174
+ getActiveProxyConnections() {
175
+ return Array.from(this.connections.values());
176
+ }
177
+ getProxyConnectionsForCDP(connectionId) {
178
+ return Array.from(this.connections.values()).filter(proxy => proxy.connectionId === connectionId);
179
+ }
180
+ broadcastToCDPClients(connectionId, message) {
181
+ const proxyConnections = this.getProxyConnectionsForCDP(connectionId);
182
+ let sentCount = 0;
183
+ for (const proxyConnection of proxyConnections) {
184
+ if (proxyConnection.clientWs.readyState === WebSocket.OPEN) {
185
+ try {
186
+ proxyConnection.clientWs.send(message);
187
+ proxyConnection.messageCount++;
188
+ sentCount++;
189
+ }
190
+ catch (error) {
191
+ this.logger.error(`Error broadcasting to proxy ${proxyConnection.id}:`, error);
192
+ this.closeProxyConnection(proxyConnection.id);
193
+ }
194
+ }
195
+ }
196
+ if (sentCount > 0) {
197
+ this.logger.debug(`Broadcasted message to ${sentCount} clients for CDP connection ${connectionId}`);
198
+ }
199
+ }
200
+ incrementClientCount(connectionId) {
201
+ const cdpConnection = this.connectionPool.getConnectionInfo(connectionId);
202
+ if (cdpConnection) {
203
+ cdpConnection.clientCount++;
204
+ cdpConnection.lastUsed = Date.now();
205
+ }
206
+ }
207
+ setupMessageForwarding(proxyConnection, cdpConnection) {
208
+ const { clientWs, connectionId } = proxyConnection;
209
+ clientWs.on('message', (data) => {
210
+ try {
211
+ const message = data.toString();
212
+ let cdpCommand;
213
+ try {
214
+ cdpCommand = JSON.parse(message);
215
+ }
216
+ catch (parseError) {
217
+ this.logger.warn(`Invalid JSON from client ${proxyConnection.id}: ${message.substring(0, 100)}`);
218
+ clientWs.send(JSON.stringify({
219
+ error: { code: -32700, message: 'Parse error' },
220
+ id: null
221
+ }));
222
+ return;
223
+ }
224
+ if (this.isProxyCommand(cdpCommand)) {
225
+ this.handleProxyCommand(proxyConnection, cdpCommand);
226
+ return;
227
+ }
228
+ if (!this.isValidCDPCommand(cdpCommand)) {
229
+ this.logger.warn(`Invalid CDP command from client ${proxyConnection.id}:`, cdpCommand);
230
+ clientWs.send(JSON.stringify({
231
+ error: { code: -32600, message: 'Invalid Request' },
232
+ id: cdpCommand.id || null
233
+ }));
234
+ return;
235
+ }
236
+ if (cdpConnection.connection.readyState === WebSocket.OPEN) {
237
+ cdpConnection.connection.send(message);
238
+ proxyConnection.messageCount++;
239
+ this.logger.debug(`Forwarded CDP command from client ${proxyConnection.id}: ${cdpCommand.method} (id: ${cdpCommand.id})`);
240
+ }
241
+ else {
242
+ this.logger.warn(`CDP connection ${connectionId} is not open, cannot forward command`);
243
+ clientWs.send(JSON.stringify({
244
+ error: { code: -32001, message: 'CDP connection unavailable' },
245
+ id: cdpCommand.id || null
246
+ }));
247
+ }
248
+ }
249
+ catch (error) {
250
+ this.logger.error(`Error forwarding message to CDP from client ${proxyConnection.id}:`, error);
251
+ clientWs.send(JSON.stringify({
252
+ error: { code: -32603, message: 'Internal error' },
253
+ id: null
254
+ }));
255
+ }
256
+ });
257
+ const cdpMessageHandler = (data) => {
258
+ try {
259
+ const message = data.toString();
260
+ let cdpMessage;
261
+ try {
262
+ cdpMessage = JSON.parse(message);
263
+ }
264
+ catch (parseError) {
265
+ this.logger.warn(`Invalid JSON from CDP connection ${connectionId}: ${message.substring(0, 100)}`);
266
+ return;
267
+ }
268
+ if (this.isCDPResponse(cdpMessage)) {
269
+ if (clientWs.readyState === WebSocket.OPEN) {
270
+ clientWs.send(message);
271
+ proxyConnection.messageCount++;
272
+ this.logger.debug(`Forwarded CDP response to client ${proxyConnection.id}: id ${cdpMessage.id}`);
273
+ }
274
+ }
275
+ else if (this.isCDPEvent(cdpMessage)) {
276
+ this.forwardEventToSubscribedClients(connectionId, cdpMessage, message);
277
+ }
278
+ }
279
+ catch (error) {
280
+ this.logger.error(`Error forwarding message to client ${proxyConnection.id}:`, error);
281
+ }
282
+ };
283
+ cdpConnection.connection.on('message', cdpMessageHandler);
284
+ clientWs.on('close', () => {
285
+ cdpConnection.connection.off('message', cdpMessageHandler);
286
+ this.logger.debug(`Cleaned up CDP message handler for proxy ${proxyConnection.id}`);
287
+ });
288
+ }
289
+ forwardEventToSubscribedClients(connectionId, cdpEvent, rawMessage) {
290
+ const subscribedProxies = this.eventSubscriptions.get(connectionId);
291
+ if (!subscribedProxies || subscribedProxies.size === 0) {
292
+ return;
293
+ }
294
+ let forwardedCount = 0;
295
+ for (const proxyId of Array.from(subscribedProxies)) {
296
+ const proxyConnection = this.connections.get(proxyId);
297
+ if (!proxyConnection || proxyConnection.clientWs.readyState !== WebSocket.OPEN) {
298
+ continue;
299
+ }
300
+ const clientFilters = this.clientEventFilters.get(proxyId);
301
+ if (clientFilters && clientFilters.size > 0) {
302
+ if (!clientFilters.has(cdpEvent.method)) {
303
+ continue;
304
+ }
305
+ }
306
+ try {
307
+ proxyConnection.clientWs.send(rawMessage);
308
+ proxyConnection.messageCount++;
309
+ forwardedCount++;
310
+ }
311
+ catch (error) {
312
+ this.logger.error(`Error forwarding event to proxy ${proxyId}:`, error);
313
+ this.closeProxyConnection(proxyId);
314
+ }
315
+ }
316
+ if (forwardedCount > 0) {
317
+ this.logger.debug(`Forwarded CDP event ${cdpEvent.method} to ${forwardedCount} clients for connection ${connectionId}`);
318
+ }
319
+ }
320
+ setClientEventFilters(proxyId, eventMethods) {
321
+ if (!this.connections.has(proxyId)) {
322
+ return false;
323
+ }
324
+ const filters = this.clientEventFilters.get(proxyId);
325
+ if (filters) {
326
+ filters.clear();
327
+ eventMethods.forEach(method => filters.add(method));
328
+ this.logger.debug(`Updated event filters for proxy ${proxyId}: ${eventMethods.join(', ')}`);
329
+ return true;
330
+ }
331
+ return false;
332
+ }
333
+ clearClientEventFilters(proxyId) {
334
+ if (!this.connections.has(proxyId)) {
335
+ return false;
336
+ }
337
+ const filters = this.clientEventFilters.get(proxyId);
338
+ if (filters) {
339
+ filters.clear();
340
+ this.logger.debug(`Cleared event filters for proxy ${proxyId} - will receive all events`);
341
+ return true;
342
+ }
343
+ return false;
344
+ }
345
+ getClientEventFilters(proxyId) {
346
+ const filters = this.clientEventFilters.get(proxyId);
347
+ return filters ? Array.from(filters) : [];
348
+ }
349
+ isValidCDPCommand(message) {
350
+ return (typeof message === 'object' &&
351
+ message !== null &&
352
+ typeof message.method === 'string' &&
353
+ (message.id === undefined || typeof message.id === 'number' || typeof message.id === 'string') &&
354
+ (message.params === undefined || typeof message.params === 'object'));
355
+ }
356
+ isCDPResponse(message) {
357
+ return (typeof message === 'object' &&
358
+ message !== null &&
359
+ (message.id !== undefined) &&
360
+ (message.result !== undefined || message.error !== undefined));
361
+ }
362
+ isCDPEvent(message) {
363
+ return (typeof message === 'object' &&
364
+ message !== null &&
365
+ typeof message.method === 'string' &&
366
+ message.id === undefined);
367
+ }
368
+ isProxyCommand(message) {
369
+ return (typeof message === 'object' &&
370
+ message !== null &&
371
+ typeof message.method === 'string' &&
372
+ message.method.startsWith('Proxy.'));
373
+ }
374
+ handleProxyCommand(proxyConnection, command) {
375
+ const { method, params, id } = command;
376
+ try {
377
+ switch (method) {
378
+ case 'Proxy.setEventFilters':
379
+ {
380
+ const eventMethods = params?.eventMethods || [];
381
+ if (Array.isArray(eventMethods) && eventMethods.every(m => typeof m === 'string')) {
382
+ this.setClientEventFilters(proxyConnection.id, eventMethods);
383
+ proxyConnection.clientWs.send(JSON.stringify({
384
+ id,
385
+ result: { success: true, filters: eventMethods }
386
+ }));
387
+ }
388
+ else {
389
+ proxyConnection.clientWs.send(JSON.stringify({
390
+ id,
391
+ error: { code: -32602, message: 'Invalid eventMethods parameter' }
392
+ }));
393
+ }
394
+ }
395
+ break;
396
+ case 'Proxy.clearEventFilters':
397
+ {
398
+ this.clearClientEventFilters(proxyConnection.id);
399
+ proxyConnection.clientWs.send(JSON.stringify({
400
+ id,
401
+ result: { success: true, filters: [] }
402
+ }));
403
+ }
404
+ break;
405
+ case 'Proxy.getEventFilters':
406
+ {
407
+ const filters = this.getClientEventFilters(proxyConnection.id);
408
+ proxyConnection.clientWs.send(JSON.stringify({
409
+ id,
410
+ result: { filters }
411
+ }));
412
+ }
413
+ break;
414
+ case 'Proxy.getStatus':
415
+ {
416
+ const cdpConnection = this.connectionPool.getConnectionInfo(proxyConnection.connectionId);
417
+ proxyConnection.clientWs.send(JSON.stringify({
418
+ id,
419
+ result: {
420
+ proxyId: proxyConnection.id,
421
+ connectionId: proxyConnection.connectionId,
422
+ messageCount: proxyConnection.messageCount,
423
+ createdAt: proxyConnection.createdAt,
424
+ cdpConnectionHealthy: cdpConnection?.isHealthy || false,
425
+ totalClients: cdpConnection?.clientCount || 0
426
+ }
427
+ }));
428
+ }
429
+ break;
430
+ default:
431
+ proxyConnection.clientWs.send(JSON.stringify({
432
+ id,
433
+ error: { code: -32601, message: `Unknown proxy method: ${method}` }
434
+ }));
435
+ }
436
+ }
437
+ catch (error) {
438
+ this.logger.error(`Error handling proxy command ${method}:`, error);
439
+ proxyConnection.clientWs.send(JSON.stringify({
440
+ id,
441
+ error: { code: -32603, message: 'Internal error' }
442
+ }));
443
+ }
444
+ }
445
+ handleClientDisconnection(proxyId) {
446
+ const proxyConnection = this.connections.get(proxyId);
447
+ if (proxyConnection) {
448
+ this.logger.info(`Client disconnected from proxy ${proxyId}`);
449
+ this.closeProxyConnection(proxyId);
450
+ }
451
+ }
452
+ generateProxyId() {
453
+ return `proxy_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
454
+ }
455
+ }
456
+ exports.WSProxy = WSProxy;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });