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
package/README.md
CHANGED
|
@@ -390,6 +390,18 @@ chrome-cdp-cli list_console_messages
|
|
|
390
390
|
chrome-cdp-cli list_console_messages --filter '{"types":["error","warn"]}'
|
|
391
391
|
```
|
|
392
392
|
|
|
393
|
+
**Note**: Console monitoring only captures messages generated *after* monitoring starts. For historical messages or immediate console operations, use the eval-first approach:
|
|
394
|
+
|
|
395
|
+
```bash
|
|
396
|
+
# Generate and capture console messages in one command
|
|
397
|
+
chrome-cdp-cli eval "console.log('Test message'); console.warn('Warning'); 'Messages logged'"
|
|
398
|
+
|
|
399
|
+
# Check for existing console history (if page maintains it)
|
|
400
|
+
chrome-cdp-cli eval "window.consoleHistory || window._console_logs || 'No custom console history'"
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
See [Console Monitoring Documentation](docs/CONSOLE_MONITORING.md) for detailed solutions and workarounds.
|
|
404
|
+
|
|
393
405
|
#### Network Monitoring
|
|
394
406
|
```bash
|
|
395
407
|
# Get latest network request
|
|
@@ -6,11 +6,13 @@ const ConnectionManager_1 = require("../connection/ConnectionManager");
|
|
|
6
6
|
const handlers_1 = require("../handlers");
|
|
7
7
|
const logger_1 = require("../utils/logger");
|
|
8
8
|
const CommandRouter_1 = require("./CommandRouter");
|
|
9
|
+
const ProxyManager_1 = require("../proxy/ProxyManager");
|
|
9
10
|
class CLIApplication {
|
|
10
11
|
constructor() {
|
|
11
12
|
this.cli = new CLIInterface_1.CLIInterface();
|
|
12
13
|
this.connectionManager = new ConnectionManager_1.ConnectionManager();
|
|
13
14
|
this.logger = new logger_1.Logger();
|
|
15
|
+
this.proxyManager = ProxyManager_1.ProxyManager.getInstance();
|
|
14
16
|
this.setupHandlers();
|
|
15
17
|
}
|
|
16
18
|
setupHandlers() {
|
|
@@ -35,20 +37,40 @@ class CLIApplication {
|
|
|
35
37
|
}
|
|
36
38
|
async run(argv) {
|
|
37
39
|
try {
|
|
40
|
+
console.log('[DEBUG] CLIApplication.run called with argv:', argv);
|
|
38
41
|
const command = this.cli.parseArgs(argv);
|
|
42
|
+
console.log('[DEBUG] Parsed command:', command);
|
|
43
|
+
if (command.config.verbose) {
|
|
44
|
+
this.proxyManager.setLogging(true);
|
|
45
|
+
}
|
|
46
|
+
console.log('[DEBUG] Ensuring proxy is ready...');
|
|
47
|
+
await this.ensureProxyReady();
|
|
39
48
|
if (this.needsConnection(command.name)) {
|
|
49
|
+
console.log('[DEBUG] Command needs connection, ensuring connection...');
|
|
40
50
|
await this.ensureConnection(command);
|
|
41
51
|
}
|
|
52
|
+
console.log('[DEBUG] Executing command via CLI interface...');
|
|
42
53
|
const result = await this.cli.execute(command);
|
|
54
|
+
console.log('[DEBUG] Command execution result:', result);
|
|
43
55
|
this.outputResult(result, command);
|
|
44
56
|
return result.exitCode || (result.success ? CommandRouter_1.ExitCode.SUCCESS : CommandRouter_1.ExitCode.GENERAL_ERROR);
|
|
45
57
|
}
|
|
46
58
|
catch (error) {
|
|
59
|
+
console.log('[DEBUG] Error in CLIApplication.run:', error);
|
|
47
60
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
48
61
|
console.error(`Error: ${errorMessage}`);
|
|
49
62
|
return CommandRouter_1.ExitCode.GENERAL_ERROR;
|
|
50
63
|
}
|
|
51
64
|
}
|
|
65
|
+
async ensureProxyReady() {
|
|
66
|
+
try {
|
|
67
|
+
const isReady = await this.proxyManager.ensureProxyReady();
|
|
68
|
+
if (!isReady) {
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
52
74
|
needsConnection(commandName) {
|
|
53
75
|
const noConnectionCommands = [
|
|
54
76
|
'help',
|
|
@@ -104,6 +126,12 @@ class CLIApplication {
|
|
|
104
126
|
this.logger.error('Error during shutdown:', error);
|
|
105
127
|
}
|
|
106
128
|
}
|
|
129
|
+
try {
|
|
130
|
+
await this.proxyManager.shutdown();
|
|
131
|
+
}
|
|
132
|
+
catch (error) {
|
|
133
|
+
this.logger.error('Error shutting down proxy manager:', error);
|
|
134
|
+
}
|
|
107
135
|
}
|
|
108
136
|
getCLI() {
|
|
109
137
|
return this.cli;
|
package/dist/cli/CLIInterface.js
CHANGED
|
@@ -39,6 +39,7 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
const CLIInterface_1 = require("../interfaces/CLIInterface");
|
|
40
40
|
const CommandRegistry_1 = require("./CommandRegistry");
|
|
41
41
|
const CommandRouter_1 = require("./CommandRouter");
|
|
42
|
+
const packageJson = require('../../package.json');
|
|
42
43
|
class CLIInterface {
|
|
43
44
|
constructor() {
|
|
44
45
|
this.program = new commander_1.Command();
|
|
@@ -50,7 +51,7 @@ class CLIInterface {
|
|
|
50
51
|
this.program
|
|
51
52
|
.name('chrome-cdp-cli')
|
|
52
53
|
.description('Command-line tool for controlling Chrome browser via DevTools Protocol')
|
|
53
|
-
.version(
|
|
54
|
+
.version(packageJson.version)
|
|
54
55
|
.allowUnknownOption(true)
|
|
55
56
|
.allowExcessArguments(true);
|
|
56
57
|
this.program
|
|
@@ -65,6 +66,10 @@ class CLIInterface {
|
|
|
65
66
|
parseArgs(argv) {
|
|
66
67
|
try {
|
|
67
68
|
const args = argv.slice(2);
|
|
69
|
+
if (args.includes('--version') || args.includes('-V')) {
|
|
70
|
+
console.log(packageJson.version);
|
|
71
|
+
process.exit(0);
|
|
72
|
+
}
|
|
68
73
|
const options = {};
|
|
69
74
|
const commandArgs = [];
|
|
70
75
|
let i = 0;
|
|
@@ -341,6 +346,65 @@ class CLIInterface {
|
|
|
341
346
|
if (result.data === undefined || result.data === null) {
|
|
342
347
|
return 'Success';
|
|
343
348
|
}
|
|
349
|
+
let output = '';
|
|
350
|
+
let dataSourceInfo = '';
|
|
351
|
+
if (result.dataSource === 'proxy' && result.hasHistoricalData) {
|
|
352
|
+
dataSourceInfo = '📊 Data from proxy server (includes historical data)\n';
|
|
353
|
+
}
|
|
354
|
+
else if (result.dataSource === 'direct' && result.hasHistoricalData === false) {
|
|
355
|
+
dataSourceInfo = '⚠️ Data from direct connection (new messages only, no historical data)\n';
|
|
356
|
+
}
|
|
357
|
+
if (result.data && typeof result.data === 'object') {
|
|
358
|
+
const data = result.data;
|
|
359
|
+
if (data.messages && Array.isArray(data.messages)) {
|
|
360
|
+
output += dataSourceInfo;
|
|
361
|
+
if (data.messages.length === 0) {
|
|
362
|
+
output += 'No console messages found.';
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
output += `Found ${data.messages.length} console message(s):\n\n`;
|
|
366
|
+
data.messages.forEach((msg, index) => {
|
|
367
|
+
const timestamp = new Date(msg.timestamp).toISOString();
|
|
368
|
+
output += `[${index + 1}] ${timestamp} [${msg.type.toUpperCase()}] ${msg.text}\n`;
|
|
369
|
+
if (msg.args && msg.args.length > 0) {
|
|
370
|
+
output += ` Args: ${JSON.stringify(msg.args)}\n`;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
}
|
|
374
|
+
return output.trim();
|
|
375
|
+
}
|
|
376
|
+
if (data.requests && Array.isArray(data.requests)) {
|
|
377
|
+
output += dataSourceInfo;
|
|
378
|
+
if (data.requests.length === 0) {
|
|
379
|
+
output += 'No network requests found.';
|
|
380
|
+
}
|
|
381
|
+
else {
|
|
382
|
+
output += `Found ${data.requests.length} network request(s):\n\n`;
|
|
383
|
+
data.requests.forEach((req, index) => {
|
|
384
|
+
const timestamp = new Date(req.timestamp).toISOString();
|
|
385
|
+
const status = req.status ? ` [${req.status}]` : ' [pending]';
|
|
386
|
+
output += `[${index + 1}] ${timestamp} ${req.method} ${req.url}${status}\n`;
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return output.trim();
|
|
390
|
+
}
|
|
391
|
+
if (data.type && data.text !== undefined && data.timestamp) {
|
|
392
|
+
output += dataSourceInfo;
|
|
393
|
+
const timestamp = new Date(data.timestamp).toISOString();
|
|
394
|
+
output += `${timestamp} [${data.type.toUpperCase()}] ${data.text}`;
|
|
395
|
+
if (data.args && data.args.length > 0) {
|
|
396
|
+
output += `\nArgs: ${JSON.stringify(data.args)}`;
|
|
397
|
+
}
|
|
398
|
+
return output;
|
|
399
|
+
}
|
|
400
|
+
if (data.requestId && data.url && data.method) {
|
|
401
|
+
output += dataSourceInfo;
|
|
402
|
+
const timestamp = new Date(data.timestamp).toISOString();
|
|
403
|
+
const status = data.status ? ` [${data.status}]` : ' [pending]';
|
|
404
|
+
output += `${timestamp} ${data.method} ${data.url}${status}`;
|
|
405
|
+
return output;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
344
408
|
if (typeof result.data === 'string') {
|
|
345
409
|
return result.data;
|
|
346
410
|
}
|
|
@@ -169,14 +169,18 @@ class CommandRouter {
|
|
|
169
169
|
}
|
|
170
170
|
async executeWithTimeout(handler, command) {
|
|
171
171
|
const timeout = command.config.timeout;
|
|
172
|
+
console.log(`[DEBUG] CommandRouter.executeWithTimeout called for command: ${command.name}, timeout: ${timeout}ms`);
|
|
172
173
|
const timeoutPromise = new Promise((_, reject) => {
|
|
173
174
|
setTimeout(() => {
|
|
175
|
+
console.log(`[DEBUG] Command timeout reached for: ${command.name} after ${timeout}ms`);
|
|
174
176
|
reject(new Error(`Command timeout after ${timeout}ms`));
|
|
175
177
|
}, timeout);
|
|
176
178
|
});
|
|
179
|
+
console.log(`[DEBUG] Starting handler execution for: ${command.name}`);
|
|
177
180
|
const executionPromise = handler.execute(this.client, command.args);
|
|
178
181
|
try {
|
|
179
182
|
const result = await Promise.race([executionPromise, timeoutPromise]);
|
|
183
|
+
console.log(`[DEBUG] Command completed successfully for: ${command.name}`);
|
|
180
184
|
if (!result || typeof result !== 'object') {
|
|
181
185
|
return {
|
|
182
186
|
success: false,
|
|
@@ -190,6 +194,7 @@ class CommandRouter {
|
|
|
190
194
|
return result;
|
|
191
195
|
}
|
|
192
196
|
catch (error) {
|
|
197
|
+
console.log(`[DEBUG] Command execution error for: ${command.name}:`, error);
|
|
193
198
|
if (error instanceof Error && error.message.includes('timeout')) {
|
|
194
199
|
return {
|
|
195
200
|
success: false,
|
|
@@ -214,6 +219,7 @@ Global Options:
|
|
|
214
219
|
-q, --quiet Enable quiet mode
|
|
215
220
|
-t, --timeout <ms> Command timeout in milliseconds (default: 30000)
|
|
216
221
|
-c, --config <path> Configuration file path
|
|
222
|
+
-V, --version Show version number
|
|
217
223
|
|
|
218
224
|
Available Commands:
|
|
219
225
|
${commands.map(cmd => ` ${cmd.padEnd(20)} - ${this.getCommandDescription(cmd)}`).join('\n')}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.ProxyClient = void 0;
|
|
7
|
+
const ws_1 = require("ws");
|
|
8
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
9
|
+
const ProxyManager_1 = require("../proxy/ProxyManager");
|
|
10
|
+
class ProxyClient {
|
|
11
|
+
constructor(config) {
|
|
12
|
+
this.config = {
|
|
13
|
+
proxyUrl: 'http://localhost:9223',
|
|
14
|
+
fallbackToDirect: true,
|
|
15
|
+
startProxyIfNeeded: true,
|
|
16
|
+
...config
|
|
17
|
+
};
|
|
18
|
+
this.proxyManager = ProxyManager_1.ProxyManager.getInstance();
|
|
19
|
+
}
|
|
20
|
+
async ensureProxyRunning() {
|
|
21
|
+
if (this.config.startProxyIfNeeded) {
|
|
22
|
+
return await this.proxyManager.ensureProxyReady();
|
|
23
|
+
}
|
|
24
|
+
return await this.isProxyRunning();
|
|
25
|
+
}
|
|
26
|
+
async isProxyAvailable() {
|
|
27
|
+
return await this.isProxyRunning();
|
|
28
|
+
}
|
|
29
|
+
async isProxyRunning() {
|
|
30
|
+
try {
|
|
31
|
+
const controller = new AbortController();
|
|
32
|
+
const timeout = setTimeout(() => controller.abort(), 2000);
|
|
33
|
+
const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/health`, {
|
|
34
|
+
method: 'GET',
|
|
35
|
+
signal: controller.signal
|
|
36
|
+
});
|
|
37
|
+
clearTimeout(timeout);
|
|
38
|
+
return response.ok;
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async connect(host, port, targetId) {
|
|
45
|
+
const request = {
|
|
46
|
+
host,
|
|
47
|
+
port,
|
|
48
|
+
targetId
|
|
49
|
+
};
|
|
50
|
+
try {
|
|
51
|
+
const controller = new AbortController();
|
|
52
|
+
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
53
|
+
const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/connect`, {
|
|
54
|
+
method: 'POST',
|
|
55
|
+
headers: {
|
|
56
|
+
'Content-Type': 'application/json'
|
|
57
|
+
},
|
|
58
|
+
body: JSON.stringify(request),
|
|
59
|
+
signal: controller.signal
|
|
60
|
+
});
|
|
61
|
+
clearTimeout(timeout);
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Proxy connect failed: ${response.status} ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const result = await response.json();
|
|
66
|
+
if (!result.success) {
|
|
67
|
+
throw new Error(`Proxy connect failed: ${result.error}`);
|
|
68
|
+
}
|
|
69
|
+
this.connectionId = result.data.connectionId;
|
|
70
|
+
return this.connectionId;
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
throw new Error(`Failed to connect through proxy: ${error instanceof Error ? error.message : error}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
async getConsoleMessages(filter) {
|
|
77
|
+
if (!this.connectionId) {
|
|
78
|
+
throw new Error('No active connection. Call connect() first.');
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
const url = new URL(`${this.config.proxyUrl}/api/console/${this.connectionId}`);
|
|
82
|
+
if (filter) {
|
|
83
|
+
if (filter.types) {
|
|
84
|
+
url.searchParams.set('types', filter.types.join(','));
|
|
85
|
+
}
|
|
86
|
+
if (filter.textPattern) {
|
|
87
|
+
url.searchParams.set('textPattern', filter.textPattern);
|
|
88
|
+
}
|
|
89
|
+
if (filter.maxMessages) {
|
|
90
|
+
url.searchParams.set('maxMessages', filter.maxMessages.toString());
|
|
91
|
+
}
|
|
92
|
+
if (filter.startTime) {
|
|
93
|
+
url.searchParams.set('startTime', filter.startTime.toString());
|
|
94
|
+
}
|
|
95
|
+
if (filter.endTime) {
|
|
96
|
+
url.searchParams.set('endTime', filter.endTime.toString());
|
|
97
|
+
}
|
|
98
|
+
if (filter.source) {
|
|
99
|
+
url.searchParams.set('source', filter.source);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const controller = new AbortController();
|
|
103
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
104
|
+
const response = await (0, node_fetch_1.default)(url.toString(), {
|
|
105
|
+
method: 'GET',
|
|
106
|
+
signal: controller.signal
|
|
107
|
+
});
|
|
108
|
+
clearTimeout(timeout);
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new Error(`Failed to get console messages: ${response.status} ${response.statusText}`);
|
|
111
|
+
}
|
|
112
|
+
const result = await response.json();
|
|
113
|
+
if (!result.success) {
|
|
114
|
+
throw new Error(`Failed to get console messages: ${result.error}`);
|
|
115
|
+
}
|
|
116
|
+
return result.data?.messages || [];
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
throw new Error(`Failed to get console messages: ${error instanceof Error ? error.message : error}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async getNetworkRequests(filter) {
|
|
123
|
+
if (!this.connectionId) {
|
|
124
|
+
throw new Error('No active connection. Call connect() first.');
|
|
125
|
+
}
|
|
126
|
+
try {
|
|
127
|
+
const url = new URL(`${this.config.proxyUrl}/api/network/${this.connectionId}`);
|
|
128
|
+
if (filter) {
|
|
129
|
+
if (filter.methods) {
|
|
130
|
+
url.searchParams.set('methods', filter.methods.join(','));
|
|
131
|
+
}
|
|
132
|
+
if (filter.statusCodes) {
|
|
133
|
+
url.searchParams.set('statusCodes', filter.statusCodes.join(','));
|
|
134
|
+
}
|
|
135
|
+
if (filter.urlPattern) {
|
|
136
|
+
url.searchParams.set('urlPattern', filter.urlPattern);
|
|
137
|
+
}
|
|
138
|
+
if (filter.maxRequests) {
|
|
139
|
+
url.searchParams.set('maxRequests', filter.maxRequests.toString());
|
|
140
|
+
}
|
|
141
|
+
if (filter.startTime) {
|
|
142
|
+
url.searchParams.set('startTime', filter.startTime.toString());
|
|
143
|
+
}
|
|
144
|
+
if (filter.endTime) {
|
|
145
|
+
url.searchParams.set('endTime', filter.endTime.toString());
|
|
146
|
+
}
|
|
147
|
+
if (filter.includeResponseBody !== undefined) {
|
|
148
|
+
url.searchParams.set('includeResponseBody', filter.includeResponseBody.toString());
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const controller = new AbortController();
|
|
152
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
153
|
+
const response = await (0, node_fetch_1.default)(url.toString(), {
|
|
154
|
+
method: 'GET',
|
|
155
|
+
signal: controller.signal
|
|
156
|
+
});
|
|
157
|
+
clearTimeout(timeout);
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
throw new Error(`Failed to get network requests: ${response.status} ${response.statusText}`);
|
|
160
|
+
}
|
|
161
|
+
const result = await response.json();
|
|
162
|
+
if (!result.success) {
|
|
163
|
+
throw new Error(`Failed to get network requests: ${result.error}`);
|
|
164
|
+
}
|
|
165
|
+
return result.data?.requests || [];
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
throw new Error(`Failed to get network requests: ${error instanceof Error ? error.message : error}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async createWebSocketProxy() {
|
|
172
|
+
if (!this.connectionId) {
|
|
173
|
+
throw new Error('No active connection. Call connect() first.');
|
|
174
|
+
}
|
|
175
|
+
console.log(`[DEBUG] Creating WebSocket proxy for connection: ${this.connectionId}`);
|
|
176
|
+
try {
|
|
177
|
+
const wsUrl = this.config.proxyUrl.replace('http://', 'ws://').replace('https://', 'wss://');
|
|
178
|
+
const fullWsUrl = `${wsUrl}/ws/${this.connectionId}`;
|
|
179
|
+
console.log(`[DEBUG] WebSocket URL: ${fullWsUrl}`);
|
|
180
|
+
const ws = new ws_1.WebSocket(fullWsUrl);
|
|
181
|
+
return new Promise((resolve, reject) => {
|
|
182
|
+
const timeout = setTimeout(() => {
|
|
183
|
+
console.log(`[DEBUG] WebSocket connection timeout for ${this.connectionId}`);
|
|
184
|
+
reject(new Error('WebSocket connection timeout'));
|
|
185
|
+
}, 10000);
|
|
186
|
+
ws.on('open', () => {
|
|
187
|
+
console.log(`[DEBUG] WebSocket connection opened for ${this.connectionId}`);
|
|
188
|
+
clearTimeout(timeout);
|
|
189
|
+
this.wsConnection = ws;
|
|
190
|
+
resolve(ws);
|
|
191
|
+
});
|
|
192
|
+
ws.on('error', (error) => {
|
|
193
|
+
console.log(`[DEBUG] WebSocket connection error for ${this.connectionId}:`, error);
|
|
194
|
+
clearTimeout(timeout);
|
|
195
|
+
reject(error);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
console.log(`[DEBUG] Failed to create WebSocket proxy for ${this.connectionId}:`, error);
|
|
201
|
+
throw new Error(`Failed to create WebSocket proxy: ${error instanceof Error ? error.message : error}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
async healthCheck() {
|
|
205
|
+
if (!this.connectionId) {
|
|
206
|
+
return null;
|
|
207
|
+
}
|
|
208
|
+
try {
|
|
209
|
+
const controller = new AbortController();
|
|
210
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
211
|
+
const response = await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/health/${this.connectionId}`, {
|
|
212
|
+
method: 'GET',
|
|
213
|
+
signal: controller.signal
|
|
214
|
+
});
|
|
215
|
+
clearTimeout(timeout);
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
throw new Error(`Health check failed: ${response.status} ${response.statusText}`);
|
|
218
|
+
}
|
|
219
|
+
const result = await response.json();
|
|
220
|
+
if (!result.success) {
|
|
221
|
+
throw new Error(`Health check failed: ${result.error}`);
|
|
222
|
+
}
|
|
223
|
+
return result.data || null;
|
|
224
|
+
}
|
|
225
|
+
catch (error) {
|
|
226
|
+
console.warn('Health check failed:', error instanceof Error ? error.message : error);
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
async disconnect() {
|
|
231
|
+
try {
|
|
232
|
+
if (this.connectionId) {
|
|
233
|
+
try {
|
|
234
|
+
const controller = new AbortController();
|
|
235
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
236
|
+
await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/client/release`, {
|
|
237
|
+
method: 'POST',
|
|
238
|
+
headers: {
|
|
239
|
+
'Content-Type': 'application/json',
|
|
240
|
+
'x-client-id': `proxy_client_${Date.now()}`
|
|
241
|
+
},
|
|
242
|
+
signal: controller.signal
|
|
243
|
+
});
|
|
244
|
+
clearTimeout(timeout);
|
|
245
|
+
}
|
|
246
|
+
catch (error) {
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (this.wsConnection) {
|
|
250
|
+
this.wsConnection.close();
|
|
251
|
+
this.wsConnection = undefined;
|
|
252
|
+
}
|
|
253
|
+
if (this.connectionId) {
|
|
254
|
+
try {
|
|
255
|
+
const controller = new AbortController();
|
|
256
|
+
const timeout = setTimeout(() => controller.abort(), 5000);
|
|
257
|
+
await (0, node_fetch_1.default)(`${this.config.proxyUrl}/api/connection/${this.connectionId}`, {
|
|
258
|
+
method: 'DELETE',
|
|
259
|
+
signal: controller.signal
|
|
260
|
+
});
|
|
261
|
+
clearTimeout(timeout);
|
|
262
|
+
}
|
|
263
|
+
catch (error) {
|
|
264
|
+
}
|
|
265
|
+
this.connectionId = undefined;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
getConnectionId() {
|
|
272
|
+
return this.connectionId;
|
|
273
|
+
}
|
|
274
|
+
getConfig() {
|
|
275
|
+
return { ...this.config };
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
exports.ProxyClient = ProxyClient;
|
package/dist/client/index.js
CHANGED
|
@@ -1,12 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
2
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
6
|
exports.EvaluateScriptHandler = void 0;
|
|
7
|
+
const ProxyClient_1 = require("../client/ProxyClient");
|
|
4
8
|
const fs_1 = require("fs");
|
|
9
|
+
const node_fetch_1 = __importDefault(require("node-fetch"));
|
|
5
10
|
class EvaluateScriptHandler {
|
|
6
|
-
constructor() {
|
|
11
|
+
constructor(useProxy = false) {
|
|
7
12
|
this.name = 'eval';
|
|
13
|
+
this.proxyClient = new ProxyClient_1.ProxyClient();
|
|
14
|
+
this.useProxy = useProxy;
|
|
8
15
|
}
|
|
9
16
|
async execute(client, args) {
|
|
17
|
+
console.log('[DEBUG] EvaluateScriptHandler.execute called with args:', args);
|
|
10
18
|
const scriptArgs = args;
|
|
11
19
|
if (!scriptArgs.expression && !scriptArgs.file) {
|
|
12
20
|
return {
|
|
@@ -20,6 +28,140 @@ class EvaluateScriptHandler {
|
|
|
20
28
|
error: 'Cannot specify both "expression" and "file" arguments'
|
|
21
29
|
};
|
|
22
30
|
}
|
|
31
|
+
console.log('[DEBUG] Arguments validated, useProxy:', this.useProxy);
|
|
32
|
+
try {
|
|
33
|
+
if (this.useProxy) {
|
|
34
|
+
console.log('[DEBUG] Checking proxy availability...');
|
|
35
|
+
const proxyAvailable = await this.proxyClient.isProxyAvailable();
|
|
36
|
+
console.log('[DEBUG] Proxy available:', proxyAvailable);
|
|
37
|
+
if (proxyAvailable) {
|
|
38
|
+
console.log('[INFO] Using proxy connection for script evaluation');
|
|
39
|
+
console.log('[DEBUG] About to call executeWithProxy...');
|
|
40
|
+
const result = await this.executeWithProxy(scriptArgs);
|
|
41
|
+
console.log('[DEBUG] executeWithProxy returned:', result);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
console.warn('[WARN] Proxy not available, falling back to direct CDP connection');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (error) {
|
|
50
|
+
console.warn('[WARN] Proxy execution failed, falling back to direct CDP:', error instanceof Error ? error.message : error);
|
|
51
|
+
}
|
|
52
|
+
console.log('[DEBUG] Falling back to direct CDP');
|
|
53
|
+
return await this.executeWithDirectCDP(client, scriptArgs);
|
|
54
|
+
}
|
|
55
|
+
async executeWithProxy(scriptArgs) {
|
|
56
|
+
try {
|
|
57
|
+
console.log('[DEBUG] Starting executeWithProxy');
|
|
58
|
+
let expression;
|
|
59
|
+
if (scriptArgs.file) {
|
|
60
|
+
expression = await this.readScriptFile(scriptArgs.file);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
expression = scriptArgs.expression;
|
|
64
|
+
}
|
|
65
|
+
console.log('[DEBUG] Expression to execute:', expression.substring(0, 100));
|
|
66
|
+
console.log('[DEBUG] Creating new proxy connection...');
|
|
67
|
+
const connectionId = await this.proxyClient.connect('localhost', 9222);
|
|
68
|
+
console.log(`[DEBUG] Created new proxy connection: ${connectionId}`);
|
|
69
|
+
try {
|
|
70
|
+
const result = await this.executeScriptThroughHTTP(connectionId, expression, scriptArgs);
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
data: result
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
await this.proxyClient.disconnect();
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
console.log('[DEBUG] Error in executeWithProxy:', error);
|
|
82
|
+
return {
|
|
83
|
+
success: false,
|
|
84
|
+
error: error instanceof Error ? error.message : String(error)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async executeScriptThroughHTTP(connectionId, expression, args) {
|
|
89
|
+
const timeout = args.timeout || 30000;
|
|
90
|
+
const awaitPromise = args.awaitPromise ?? true;
|
|
91
|
+
const returnByValue = args.returnByValue ?? true;
|
|
92
|
+
console.log(`[DEBUG] Starting HTTP script execution, timeout: ${timeout}ms`);
|
|
93
|
+
console.log(`[DEBUG] Expression: ${expression.substring(0, 100)}${expression.length > 100 ? '...' : ''}`);
|
|
94
|
+
try {
|
|
95
|
+
const proxyUrl = this.proxyClient.getConfig().proxyUrl;
|
|
96
|
+
const commandId = Date.now() + Math.floor(Math.random() * 10000);
|
|
97
|
+
const command = {
|
|
98
|
+
id: commandId,
|
|
99
|
+
method: 'Runtime.evaluate',
|
|
100
|
+
params: {
|
|
101
|
+
expression: expression,
|
|
102
|
+
awaitPromise: awaitPromise,
|
|
103
|
+
returnByValue: returnByValue,
|
|
104
|
+
userGesture: true,
|
|
105
|
+
generatePreview: false
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
console.log(`[DEBUG] Sending HTTP command to ${proxyUrl}/api/execute/${connectionId}`);
|
|
109
|
+
const controller = new AbortController();
|
|
110
|
+
const timeoutHandle = setTimeout(() => controller.abort(), timeout);
|
|
111
|
+
try {
|
|
112
|
+
const response = await (0, node_fetch_1.default)(`${proxyUrl}/api/execute/${connectionId}`, {
|
|
113
|
+
method: 'POST',
|
|
114
|
+
headers: {
|
|
115
|
+
'Content-Type': 'application/json',
|
|
116
|
+
'x-client-id': `eval_handler_${Date.now()}`
|
|
117
|
+
},
|
|
118
|
+
body: JSON.stringify({
|
|
119
|
+
command,
|
|
120
|
+
timeout
|
|
121
|
+
}),
|
|
122
|
+
signal: controller.signal
|
|
123
|
+
});
|
|
124
|
+
clearTimeout(timeoutHandle);
|
|
125
|
+
if (!response.ok) {
|
|
126
|
+
const errorData = await response.json().catch(() => ({}));
|
|
127
|
+
throw new Error(`HTTP ${response.status}: ${errorData.error || response.statusText}`);
|
|
128
|
+
}
|
|
129
|
+
const result = await response.json();
|
|
130
|
+
console.log(`[DEBUG] HTTP command response:`, result);
|
|
131
|
+
if (!result.success) {
|
|
132
|
+
throw new Error(`Command execution failed: ${result.error || 'Unknown error'}`);
|
|
133
|
+
}
|
|
134
|
+
const commandResult = result.data.result;
|
|
135
|
+
if (result.data.error) {
|
|
136
|
+
throw new Error(`CDP Error: ${result.data.error.message}`);
|
|
137
|
+
}
|
|
138
|
+
if (commandResult.exceptionDetails) {
|
|
139
|
+
console.log(`[DEBUG] Exception details:`, commandResult.exceptionDetails);
|
|
140
|
+
const error = new Error(commandResult.result?.description || 'Script execution failed');
|
|
141
|
+
error.exceptionDetails = commandResult.exceptionDetails;
|
|
142
|
+
throw error;
|
|
143
|
+
}
|
|
144
|
+
let value = commandResult.result?.value;
|
|
145
|
+
if (commandResult.result?.type === 'undefined') {
|
|
146
|
+
value = undefined;
|
|
147
|
+
}
|
|
148
|
+
else if (commandResult.result?.unserializableValue) {
|
|
149
|
+
value = commandResult.result.unserializableValue;
|
|
150
|
+
}
|
|
151
|
+
console.log(`[DEBUG] Successful HTTP result:`, value);
|
|
152
|
+
return value;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
clearTimeout(timeoutHandle);
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
console.log(`[DEBUG] Error in HTTP script execution:`, error);
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
async executeWithDirectCDP(client, scriptArgs) {
|
|
23
165
|
try {
|
|
24
166
|
let expression;
|
|
25
167
|
if (scriptArgs.file) {
|