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.
- package/README.md +12 -0
- package/dist/cli/CLIApplication.js +21 -0
- package/dist/cli/CLIInterface.js +65 -1
- package/dist/cli/CommandRouter.js +1 -0
- package/dist/client/ProxyClient.js +254 -0
- package/dist/client/index.js +1 -0
- package/dist/handlers/EvaluateScriptHandler.js +126 -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 +436 -0
- package/dist/proxy/server/ConnectionPool.js +430 -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 +909 -0
- package/dist/proxy/server/SecurityManager.js +337 -0
- package/dist/proxy/server/WSProxy.js +456 -0
- package/dist/proxy/types/ProxyTypes.js +2 -0
- package/dist/utils/logger.js +256 -18
- package/package.json +7 -1
|
@@ -0,0 +1,267 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.ProxyManager = void 0;
|
|
40
|
+
const ProxyClient_1 = require("../client/ProxyClient");
|
|
41
|
+
const child_process_1 = require("child_process");
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
const fs = __importStar(require("fs"));
|
|
44
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
45
|
+
class ProxyManager {
|
|
46
|
+
constructor(config) {
|
|
47
|
+
this.isStarting = false;
|
|
48
|
+
this.lastHealthCheck = 0;
|
|
49
|
+
this.healthCheckCacheMs = 5000;
|
|
50
|
+
this.config = {
|
|
51
|
+
proxyUrl: 'http://localhost:9223',
|
|
52
|
+
maxRetries: 3,
|
|
53
|
+
retryDelayMs: 1000,
|
|
54
|
+
healthCheckTimeoutMs: 2000,
|
|
55
|
+
startupTimeoutMs: 10000,
|
|
56
|
+
enableLogging: false,
|
|
57
|
+
...config
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
static getInstance(config) {
|
|
61
|
+
if (!ProxyManager.instance) {
|
|
62
|
+
ProxyManager.instance = new ProxyManager(config);
|
|
63
|
+
}
|
|
64
|
+
return ProxyManager.instance;
|
|
65
|
+
}
|
|
66
|
+
async ensureProxyReady() {
|
|
67
|
+
try {
|
|
68
|
+
if (await this.isProxyHealthy()) {
|
|
69
|
+
return true;
|
|
70
|
+
}
|
|
71
|
+
return await this.startOrRestartProxy();
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
this.log(`Failed to ensure proxy ready: ${error instanceof Error ? error.message : error}`);
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
async isProxyHealthy() {
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
if (now - this.lastHealthCheck < this.healthCheckCacheMs) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timeout = setTimeout(() => controller.abort(), this.config.healthCheckTimeoutMs);
|
|
86
|
+
const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/health`, {
|
|
87
|
+
method: 'GET',
|
|
88
|
+
signal: controller.signal
|
|
89
|
+
});
|
|
90
|
+
clearTimeout(timeout);
|
|
91
|
+
if (response.ok) {
|
|
92
|
+
this.lastHealthCheck = now;
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
catch (error) {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
async startOrRestartProxy() {
|
|
102
|
+
if (this.isStarting) {
|
|
103
|
+
return await this.waitForStartup();
|
|
104
|
+
}
|
|
105
|
+
this.isStarting = true;
|
|
106
|
+
try {
|
|
107
|
+
await this.killExistingProxy();
|
|
108
|
+
const success = await this.startProxyProcess();
|
|
109
|
+
if (success) {
|
|
110
|
+
this.log('Proxy server started successfully');
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.log('Failed to start proxy server');
|
|
114
|
+
}
|
|
115
|
+
return success;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
this.isStarting = false;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
async killExistingProxy() {
|
|
122
|
+
if (this.proxyProcess) {
|
|
123
|
+
try {
|
|
124
|
+
this.proxyProcess.kill('SIGTERM');
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
126
|
+
if (!this.proxyProcess.killed) {
|
|
127
|
+
this.proxyProcess.kill('SIGKILL');
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
}
|
|
132
|
+
this.proxyProcess = undefined;
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const { spawn } = require('child_process');
|
|
136
|
+
const port = new URL(this.config.proxyUrl).port || '9223';
|
|
137
|
+
const lsof = spawn('lsof', ['-ti', `:${port}`]);
|
|
138
|
+
let pids = '';
|
|
139
|
+
lsof.stdout.on('data', (data) => {
|
|
140
|
+
pids += data.toString();
|
|
141
|
+
});
|
|
142
|
+
lsof.on('close', (code) => {
|
|
143
|
+
if (code === 0 && pids.trim()) {
|
|
144
|
+
const pidList = pids.trim().split('\n');
|
|
145
|
+
pidList.forEach(pid => {
|
|
146
|
+
try {
|
|
147
|
+
process.kill(parseInt(pid), 'SIGTERM');
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
catch (error) {
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
async startProxyProcess() {
|
|
159
|
+
try {
|
|
160
|
+
const proxyServerPath = this.findProxyServerExecutable();
|
|
161
|
+
if (!proxyServerPath) {
|
|
162
|
+
this.log('Proxy server executable not found');
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
this.log(`Starting proxy server: ${proxyServerPath}`);
|
|
166
|
+
this.proxyProcess = (0, child_process_1.spawn)('node', [proxyServerPath], {
|
|
167
|
+
detached: true,
|
|
168
|
+
stdio: this.config.enableLogging ? 'inherit' : 'ignore'
|
|
169
|
+
});
|
|
170
|
+
this.proxyProcess.unref();
|
|
171
|
+
this.proxyProcess.on('error', (error) => {
|
|
172
|
+
this.log(`Proxy process error: ${error.message}`);
|
|
173
|
+
this.proxyProcess = undefined;
|
|
174
|
+
});
|
|
175
|
+
this.proxyProcess.on('exit', (code, signal) => {
|
|
176
|
+
this.log(`Proxy process exited with code ${code}, signal ${signal}`);
|
|
177
|
+
this.proxyProcess = undefined;
|
|
178
|
+
});
|
|
179
|
+
return await this.waitForProxyStartup();
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
this.log(`Failed to start proxy process: ${error instanceof Error ? error.message : error}`);
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
async waitForProxyStartup() {
|
|
187
|
+
const startTime = Date.now();
|
|
188
|
+
const maxWaitTime = this.config.startupTimeoutMs;
|
|
189
|
+
while (Date.now() - startTime < maxWaitTime) {
|
|
190
|
+
this.lastHealthCheck = 0;
|
|
191
|
+
if (await this.isProxyHealthy()) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
await new Promise(resolve => setTimeout(resolve, 500));
|
|
195
|
+
}
|
|
196
|
+
this.log(`Proxy startup timeout after ${maxWaitTime}ms`);
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
async waitForStartup() {
|
|
200
|
+
const maxWait = this.config.startupTimeoutMs;
|
|
201
|
+
const startTime = Date.now();
|
|
202
|
+
while (this.isStarting && Date.now() - startTime < maxWait) {
|
|
203
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
204
|
+
}
|
|
205
|
+
return !this.isStarting && await this.isProxyHealthy();
|
|
206
|
+
}
|
|
207
|
+
findProxyServerExecutable() {
|
|
208
|
+
const distPath = path.join(__dirname, '../../dist/proxy/index.js');
|
|
209
|
+
if (fs.existsSync(distPath)) {
|
|
210
|
+
return distPath;
|
|
211
|
+
}
|
|
212
|
+
const srcPath = path.join(__dirname, '../proxy/index.js');
|
|
213
|
+
if (fs.existsSync(srcPath)) {
|
|
214
|
+
return srcPath;
|
|
215
|
+
}
|
|
216
|
+
const currentDistPath = path.join(process.cwd(), 'dist/proxy/index.js');
|
|
217
|
+
if (fs.existsSync(currentDistPath)) {
|
|
218
|
+
return currentDistPath;
|
|
219
|
+
}
|
|
220
|
+
const nodeModulesPath = path.join(__dirname, '../../../node_modules/chrome-cdp-cli/dist/proxy/index.js');
|
|
221
|
+
if (fs.existsSync(nodeModulesPath)) {
|
|
222
|
+
return nodeModulesPath;
|
|
223
|
+
}
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
async getProxyClient() {
|
|
227
|
+
if (await this.ensureProxyReady()) {
|
|
228
|
+
return new ProxyClient_1.ProxyClient({
|
|
229
|
+
proxyUrl: this.config.proxyUrl,
|
|
230
|
+
fallbackToDirect: true,
|
|
231
|
+
startProxyIfNeeded: false
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
async shutdown() {
|
|
237
|
+
if (this.proxyProcess) {
|
|
238
|
+
try {
|
|
239
|
+
this.proxyProcess.kill('SIGTERM');
|
|
240
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
241
|
+
if (!this.proxyProcess.killed) {
|
|
242
|
+
this.proxyProcess.kill('SIGKILL');
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
catch (error) {
|
|
246
|
+
}
|
|
247
|
+
this.proxyProcess = undefined;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
setLogging(enabled) {
|
|
251
|
+
this.config.enableLogging = enabled;
|
|
252
|
+
}
|
|
253
|
+
log(message) {
|
|
254
|
+
if (this.config.enableLogging) {
|
|
255
|
+
console.log(`[ProxyManager] ${message}`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
async getStatus() {
|
|
259
|
+
return {
|
|
260
|
+
isHealthy: await this.isProxyHealthy(),
|
|
261
|
+
isStarting: this.isStarting,
|
|
262
|
+
hasProcess: !!this.proxyProcess,
|
|
263
|
+
lastHealthCheck: this.lastHealthCheck
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
exports.ProxyManager = ProxyManager;
|
|
@@ -0,0 +1,60 @@
|
|
|
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 __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.CDPProxyServer = void 0;
|
|
18
|
+
exports.startProxyServer = startProxyServer;
|
|
19
|
+
const CDPProxyServer_1 = require("./server/CDPProxyServer");
|
|
20
|
+
Object.defineProperty(exports, "CDPProxyServer", { enumerable: true, get: function () { return CDPProxyServer_1.CDPProxyServer; } });
|
|
21
|
+
const logger_1 = require("../utils/logger");
|
|
22
|
+
const logger = new logger_1.Logger();
|
|
23
|
+
async function startProxyServer(config) {
|
|
24
|
+
const server = new CDPProxyServer_1.CDPProxyServer();
|
|
25
|
+
try {
|
|
26
|
+
await server.start(config);
|
|
27
|
+
logger.info('Proxy server started successfully');
|
|
28
|
+
return server;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
logger.error('Failed to start proxy server:', error);
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
async function main() {
|
|
36
|
+
if (require.main === module) {
|
|
37
|
+
try {
|
|
38
|
+
const server = await startProxyServer();
|
|
39
|
+
process.on('SIGINT', async () => {
|
|
40
|
+
logger.info('Received SIGINT, shutting down gracefully...');
|
|
41
|
+
await server.stop();
|
|
42
|
+
process.exit(0);
|
|
43
|
+
});
|
|
44
|
+
process.on('SIGTERM', async () => {
|
|
45
|
+
logger.info('Received SIGTERM, shutting down gracefully...');
|
|
46
|
+
await server.stop();
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
logger.error('Proxy server failed to start:', error);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
__exportStar(require("./types/ProxyTypes"), exports);
|
|
57
|
+
main().catch(error => {
|
|
58
|
+
console.error('Unhandled error:', error);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
});
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CDPEventMonitor = void 0;
|
|
4
|
+
const logger_1 = require("../../utils/logger");
|
|
5
|
+
class CDPEventMonitor {
|
|
6
|
+
constructor(messageStore) {
|
|
7
|
+
this.monitoredConnections = new Map();
|
|
8
|
+
this.eventHandlers = new Map();
|
|
9
|
+
this.messageIdCounter = 1;
|
|
10
|
+
this.messageStore = messageStore;
|
|
11
|
+
this.logger = new logger_1.Logger();
|
|
12
|
+
}
|
|
13
|
+
async startMonitoring(connectionInfo) {
|
|
14
|
+
const { id: connectionId, connection } = connectionInfo;
|
|
15
|
+
if (this.monitoredConnections.has(connectionId)) {
|
|
16
|
+
this.logger.debug(`Already monitoring connection ${connectionId}`);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
try {
|
|
20
|
+
this.logger.info(`[DEBUG] Starting CDP event monitoring for connection ${connectionId}`);
|
|
21
|
+
this.logger.info(`[DEBUG] Connection WebSocket state: ${connection.readyState} (OPEN=1)`);
|
|
22
|
+
this.logger.info(`[DEBUG] Setting up event handlers for connection ${connectionId}`);
|
|
23
|
+
this.setupEventHandlers(connectionId, connection);
|
|
24
|
+
this.logger.info(`[DEBUG] Event handlers set up for connection ${connectionId}`);
|
|
25
|
+
this.logger.info(`[DEBUG] Enabling CDP domains for connection ${connectionId}`);
|
|
26
|
+
await this.enableCDPDomains(connection);
|
|
27
|
+
this.logger.info(`[DEBUG] CDP domains enabled for connection ${connectionId}`);
|
|
28
|
+
this.monitoredConnections.set(connectionId, connectionInfo);
|
|
29
|
+
this.logger.info(`[DEBUG] CDP event monitoring started for connection ${connectionId}`);
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
this.logger.error(`[DEBUG] Failed to start monitoring for connection ${connectionId}:`, error);
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async stopMonitoring(connectionId) {
|
|
37
|
+
const connectionInfo = this.monitoredConnections.get(connectionId);
|
|
38
|
+
if (!connectionInfo) {
|
|
39
|
+
this.logger.debug(`Connection ${connectionId} not being monitored`);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
this.logger.info(`Stopping CDP event monitoring for connection ${connectionId}`);
|
|
44
|
+
this.removeEventHandlers(connectionId, connectionInfo.connection);
|
|
45
|
+
this.monitoredConnections.delete(connectionId);
|
|
46
|
+
this.logger.info(`CDP event monitoring stopped for connection ${connectionId}`);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
this.logger.error(`Error stopping monitoring for connection ${connectionId}:`, error);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async stopAllMonitoring() {
|
|
53
|
+
const connectionIds = Array.from(this.monitoredConnections.keys());
|
|
54
|
+
for (const connectionId of connectionIds) {
|
|
55
|
+
await this.stopMonitoring(connectionId);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
getMonitoredConnections() {
|
|
59
|
+
return Array.from(this.monitoredConnections.keys());
|
|
60
|
+
}
|
|
61
|
+
isMonitoring(connectionId) {
|
|
62
|
+
return this.monitoredConnections.has(connectionId);
|
|
63
|
+
}
|
|
64
|
+
async enableCDPDomains(connection) {
|
|
65
|
+
const commands = [
|
|
66
|
+
{ method: 'Runtime.enable', params: {} },
|
|
67
|
+
{ method: 'Log.enable', params: {} },
|
|
68
|
+
{ method: 'Network.enable', params: {} }
|
|
69
|
+
];
|
|
70
|
+
for (const command of commands) {
|
|
71
|
+
try {
|
|
72
|
+
this.logger.info(`[DEBUG] Sending CDP command: ${command.method}`);
|
|
73
|
+
const result = await this.sendCDPCommand(connection, command.method, command.params);
|
|
74
|
+
this.logger.info(`[DEBUG] Successfully enabled CDP domain: ${command.method}, result: ${JSON.stringify(result)}`);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
this.logger.warn(`[DEBUG] Failed to enable ${command.method}:`, error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async sendCDPCommand(connection, method, params = {}) {
|
|
82
|
+
return new Promise((resolve, reject) => {
|
|
83
|
+
const id = this.messageIdCounter++;
|
|
84
|
+
const command = { id, method, params };
|
|
85
|
+
this.logger.info(`[DEBUG] sendCDPCommand: sending command id=${id}, method=${method}`);
|
|
86
|
+
const timeout = setTimeout(() => {
|
|
87
|
+
this.logger.warn(`[DEBUG] sendCDPCommand: timeout for command id=${id}, method=${method}`);
|
|
88
|
+
connection.off('message', messageHandler);
|
|
89
|
+
reject(new Error(`CDP command timeout: ${method}`));
|
|
90
|
+
}, 5000);
|
|
91
|
+
const messageHandler = (data) => {
|
|
92
|
+
try {
|
|
93
|
+
const rawMessage = data.toString();
|
|
94
|
+
const response = JSON.parse(rawMessage);
|
|
95
|
+
this.logger.info(`[DEBUG] sendCDPCommand: received raw message: ${rawMessage.substring(0, 200)}`);
|
|
96
|
+
this.logger.info(`[DEBUG] sendCDPCommand: parsed response, id=${response.id}, method=${response.method}, waiting for id=${id}`);
|
|
97
|
+
if (response.id !== undefined && response.id === id) {
|
|
98
|
+
this.logger.info(`[DEBUG] sendCDPCommand: matched response id=${id} for method=${method}`);
|
|
99
|
+
clearTimeout(timeout);
|
|
100
|
+
connection.off('message', messageHandler);
|
|
101
|
+
if (response.error) {
|
|
102
|
+
this.logger.error(`[DEBUG] sendCDPCommand: command error for method=${method}: ${response.error.message}`);
|
|
103
|
+
reject(new Error(`CDP command error: ${response.error.message}`));
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
this.logger.info(`[DEBUG] sendCDPCommand: command success for method=${method}`);
|
|
107
|
+
resolve(response.result);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
this.logger.debug(`[DEBUG] sendCDPCommand: ignoring message with id=${response.id}, waiting for id=${id}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
this.logger.debug(`[DEBUG] sendCDPCommand: error parsing message:`, error);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
connection.on('message', messageHandler);
|
|
119
|
+
this.logger.info(`[DEBUG] sendCDPCommand: handler registered, sending command`);
|
|
120
|
+
connection.send(JSON.stringify(command));
|
|
121
|
+
this.logger.info(`[DEBUG] sendCDPCommand: command sent`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
setupEventHandlers(connectionId, connection) {
|
|
125
|
+
const handlers = new Map();
|
|
126
|
+
const eventHandler = (data) => {
|
|
127
|
+
try {
|
|
128
|
+
const rawMessage = data.toString();
|
|
129
|
+
const message = JSON.parse(rawMessage);
|
|
130
|
+
this.logger.info(`[DEBUG] setupEventHandlers: received raw message: ${rawMessage.substring(0, 200)}`);
|
|
131
|
+
this.logger.info(`[DEBUG] setupEventHandlers: parsed message, id=${message.id}, method=${message.method || 'none'}, hasParams=${!!message.params}`);
|
|
132
|
+
if (message.method && message.params && message.id === undefined) {
|
|
133
|
+
this.logger.info(`[DEBUG] setupEventHandlers: processing CDP event: ${message.method} for connection ${connectionId}`);
|
|
134
|
+
this.handleCDPEvent(connectionId, message.method, message.params);
|
|
135
|
+
}
|
|
136
|
+
else if (message.id !== undefined) {
|
|
137
|
+
this.logger.debug(`[DEBUG] setupEventHandlers: ignoring command response with id=${message.id}`);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.logger.warn(`[DEBUG] setupEventHandlers: received unexpected message format: ${rawMessage.substring(0, 200)}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
this.logger.debug(`[DEBUG] Error parsing message for connection ${connectionId}:`, error);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
handlers.set('message', eventHandler);
|
|
148
|
+
this.eventHandlers.set(connectionId, handlers);
|
|
149
|
+
connection.on('message', eventHandler);
|
|
150
|
+
this.logger.info(`[DEBUG] Event handler attached to connection ${connectionId}`);
|
|
151
|
+
}
|
|
152
|
+
removeEventHandlers(connectionId, connection) {
|
|
153
|
+
const handlers = this.eventHandlers.get(connectionId);
|
|
154
|
+
if (!handlers) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const [event, handler] of handlers) {
|
|
158
|
+
connection.off(event, handler);
|
|
159
|
+
}
|
|
160
|
+
this.eventHandlers.delete(connectionId);
|
|
161
|
+
}
|
|
162
|
+
handleCDPEvent(connectionId, method, params) {
|
|
163
|
+
try {
|
|
164
|
+
this.logger.info(`[DEBUG] handleCDPEvent called: method=${method}, connectionId=${connectionId}`);
|
|
165
|
+
switch (method) {
|
|
166
|
+
case 'Runtime.consoleAPICalled':
|
|
167
|
+
this.logger.info(`[DEBUG] Processing Runtime.consoleAPICalled event for connection ${connectionId}`);
|
|
168
|
+
this.handleConsoleAPIEvent(connectionId, params);
|
|
169
|
+
break;
|
|
170
|
+
case 'Log.entryAdded':
|
|
171
|
+
this.logger.info(`[DEBUG] Processing Log.entryAdded event for connection ${connectionId}`);
|
|
172
|
+
this.handleLogEntryEvent(connectionId, params);
|
|
173
|
+
break;
|
|
174
|
+
case 'Network.requestWillBeSent':
|
|
175
|
+
this.logger.info(`[DEBUG] Processing Network.requestWillBeSent event for connection ${connectionId}`);
|
|
176
|
+
this.handleNetworkRequestWillBeSent(connectionId, params);
|
|
177
|
+
break;
|
|
178
|
+
case 'Network.responseReceived':
|
|
179
|
+
this.logger.info(`[DEBUG] Processing Network.responseReceived event for connection ${connectionId}`);
|
|
180
|
+
this.handleNetworkResponseReceived(connectionId, params);
|
|
181
|
+
break;
|
|
182
|
+
case 'Network.loadingFinished':
|
|
183
|
+
this.logger.info(`[DEBUG] Processing Network.loadingFinished event for connection ${connectionId}`);
|
|
184
|
+
this.handleNetworkLoadingFinished(connectionId, params);
|
|
185
|
+
break;
|
|
186
|
+
case 'Network.loadingFailed':
|
|
187
|
+
this.logger.info(`[DEBUG] Processing Network.loadingFailed event for connection ${connectionId}`);
|
|
188
|
+
this.handleNetworkLoadingFailed(connectionId, params);
|
|
189
|
+
break;
|
|
190
|
+
default:
|
|
191
|
+
this.logger.debug(`[DEBUG] Ignoring CDP event: ${method}`);
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
catch (error) {
|
|
196
|
+
this.logger.error(`[DEBUG] Error handling CDP event ${method} for connection ${connectionId}:`, error);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
handleConsoleAPIEvent(connectionId, params) {
|
|
200
|
+
this.logger.info(`[DEBUG] handleConsoleAPIEvent called for connection ${connectionId}, params: ${JSON.stringify(params).substring(0, 200)}`);
|
|
201
|
+
this.messageStore.processConsoleAPIEvent(connectionId, params);
|
|
202
|
+
this.logger.info(`[DEBUG] handleConsoleAPIEvent completed for connection ${connectionId}`);
|
|
203
|
+
}
|
|
204
|
+
handleLogEntryEvent(connectionId, params) {
|
|
205
|
+
this.messageStore.processLogEntryEvent(connectionId, params);
|
|
206
|
+
}
|
|
207
|
+
handleNetworkRequestWillBeSent(connectionId, params) {
|
|
208
|
+
try {
|
|
209
|
+
this.logger.info(`[DEBUG] handleNetworkRequestWillBeSent called for connection ${connectionId}, url: ${params.request?.url}, method: ${params.request?.method}`);
|
|
210
|
+
const networkRequest = {
|
|
211
|
+
connectionId,
|
|
212
|
+
requestId: params.requestId,
|
|
213
|
+
url: params.request.url,
|
|
214
|
+
method: params.request.method,
|
|
215
|
+
headers: params.request.headers || {},
|
|
216
|
+
timestamp: params.wallTime ? params.wallTime * 1000 : Date.now(),
|
|
217
|
+
loadingFinished: false
|
|
218
|
+
};
|
|
219
|
+
this.logger.info(`[DEBUG] Created network request: ${JSON.stringify(networkRequest).substring(0, 200)}`);
|
|
220
|
+
this.messageStore.addNetworkRequest(connectionId, networkRequest);
|
|
221
|
+
this.logger.info(`[DEBUG] Network request added to store for connection ${connectionId}`);
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
this.logger.error('[DEBUG] Error processing Network.requestWillBeSent:', error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
handleNetworkResponseReceived(connectionId, params) {
|
|
228
|
+
try {
|
|
229
|
+
const update = {
|
|
230
|
+
status: params.response.status,
|
|
231
|
+
responseHeaders: params.response.headers || {}
|
|
232
|
+
};
|
|
233
|
+
this.messageStore.updateNetworkRequest(connectionId, params.requestId, update);
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
this.logger.error('Error processing Network.responseReceived:', error);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
handleNetworkLoadingFinished(connectionId, params) {
|
|
240
|
+
try {
|
|
241
|
+
const update = {
|
|
242
|
+
loadingFinished: true
|
|
243
|
+
};
|
|
244
|
+
this.messageStore.updateNetworkRequest(connectionId, params.requestId, update);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
this.logger.error('Error processing Network.loadingFinished:', error);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
handleNetworkLoadingFailed(connectionId, params) {
|
|
251
|
+
try {
|
|
252
|
+
const update = {
|
|
253
|
+
status: 0,
|
|
254
|
+
loadingFinished: true
|
|
255
|
+
};
|
|
256
|
+
this.messageStore.updateNetworkRequest(connectionId, params.requestId, update);
|
|
257
|
+
}
|
|
258
|
+
catch (error) {
|
|
259
|
+
this.logger.error('Error processing Network.loadingFailed:', error);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
exports.CDPEventMonitor = CDPEventMonitor;
|