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,436 @@
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.CDPProxyServer = void 0;
40
+ const express_1 = __importDefault(require("express"));
41
+ const http_1 = __importDefault(require("http"));
42
+ const ws_1 = require("ws");
43
+ const ConnectionPool_1 = require("./ConnectionPool");
44
+ const MessageStore_1 = require("./MessageStore");
45
+ const HealthMonitor_1 = require("./HealthMonitor");
46
+ const ProxyAPIServer_1 = require("./ProxyAPIServer");
47
+ const WSProxy_1 = require("./WSProxy");
48
+ const CDPEventMonitor_1 = require("./CDPEventMonitor");
49
+ const PerformanceMonitor_1 = require("./PerformanceMonitor");
50
+ const SecurityManager_1 = require("./SecurityManager");
51
+ const FileSystemSecurity_1 = require("./FileSystemSecurity");
52
+ const logger_1 = require("../../utils/logger");
53
+ const os = __importStar(require("os"));
54
+ const path = __importStar(require("path"));
55
+ class CDPProxyServer {
56
+ constructor() {
57
+ this.isRunning = false;
58
+ this.startTime = 0;
59
+ this.fileSystemSecurity = new FileSystemSecurity_1.FileSystemSecurity();
60
+ const logDir = path.join(os.homedir(), '.chrome-cdp-cli', 'logs');
61
+ const logFile = path.join(logDir, 'proxy-server.log');
62
+ this.logger = (0, logger_1.createLogger)({
63
+ component: 'ProxyServer',
64
+ file: logFile,
65
+ enableStructured: true,
66
+ maxFileSize: 10 * 1024 * 1024,
67
+ maxFiles: 5
68
+ });
69
+ this.checkLogFileSecurity(logFile);
70
+ this.app = (0, express_1.default)();
71
+ this.httpServer = http_1.default.createServer(this.app);
72
+ this.config = this.getDefaultConfig();
73
+ this.messageStore = new MessageStore_1.MessageStore(this.config.maxConsoleMessages, this.config.maxNetworkRequests);
74
+ this.eventMonitor = new CDPEventMonitor_1.CDPEventMonitor(this.messageStore);
75
+ this.connectionPool = new ConnectionPool_1.ConnectionPool(this.eventMonitor, this.messageStore);
76
+ this.healthMonitor = new HealthMonitor_1.HealthMonitor(this.connectionPool);
77
+ this.performanceMonitor = new PerformanceMonitor_1.PerformanceMonitor(this.connectionPool, this.messageStore);
78
+ this.securityManager = new SecurityManager_1.SecurityManager();
79
+ this.apiServer = new ProxyAPIServer_1.ProxyAPIServer(this.connectionPool, this.messageStore, this.healthMonitor, this.performanceMonitor);
80
+ this.wsProxy = new WSProxy_1.WSProxy(this.connectionPool);
81
+ }
82
+ async start(configOverride) {
83
+ if (this.isRunning) {
84
+ this.logger.warn('Proxy server is already running');
85
+ return;
86
+ }
87
+ try {
88
+ this.startTime = Date.now();
89
+ const fileConfig = await this.loadConfigurationSecurely();
90
+ this.logger.logServerEvent('startup', 'Starting proxy server', {
91
+ config: this.config,
92
+ fileConfig,
93
+ configOverride,
94
+ nodeVersion: process.version,
95
+ platform: process.platform,
96
+ arch: process.arch,
97
+ pid: process.pid
98
+ });
99
+ this.config = { ...this.config, ...fileConfig, ...configOverride };
100
+ this.messageStore = new MessageStore_1.MessageStore(this.config.maxConsoleMessages, this.config.maxNetworkRequests);
101
+ this.eventMonitor = new CDPEventMonitor_1.CDPEventMonitor(this.messageStore);
102
+ this.connectionPool = new ConnectionPool_1.ConnectionPool(this.eventMonitor, this.messageStore, this.config);
103
+ this.healthMonitor = new HealthMonitor_1.HealthMonitor(this.connectionPool);
104
+ this.performanceMonitor = new PerformanceMonitor_1.PerformanceMonitor(this.connectionPool, this.messageStore);
105
+ this.securityManager = new SecurityManager_1.SecurityManager({
106
+ allowedHosts: ['localhost', '127.0.0.1', '192.168.*', '10.*', '172.*']
107
+ });
108
+ this.apiServer = new ProxyAPIServer_1.ProxyAPIServer(this.connectionPool, this.messageStore, this.healthMonitor, this.performanceMonitor, this.securityManager);
109
+ this.setupMiddleware();
110
+ this.apiServer.setupRoutes(this.app);
111
+ this.wsServer = new ws_1.WebSocketServer({ server: this.httpServer });
112
+ this.wsProxy.start(this.wsServer);
113
+ this.healthMonitor.start(this.config.healthCheckInterval);
114
+ this.performanceMonitor.start(60000);
115
+ this.startMemoryCleanup();
116
+ await this.startHttpServer();
117
+ this.resetAutoShutdownTimer();
118
+ this.isRunning = true;
119
+ const startupTime = Date.now() - this.startTime;
120
+ this.logger.logServerEvent('startup', `Proxy server started successfully on ${this.config.host}:${this.config.port}`, {
121
+ startupTimeMs: startupTime,
122
+ bindAddress: `${this.config.host}:${this.config.port}`,
123
+ autoShutdownTimeout: this.config.autoShutdownTimeout,
124
+ maxConnections: this.config.maxConsoleMessages,
125
+ securityEnabled: true,
126
+ fileSystemSecurityEnabled: true
127
+ });
128
+ }
129
+ catch (error) {
130
+ this.logger.logServerEvent('error', 'Failed to start proxy server', {
131
+ startupTimeMs: Date.now() - this.startTime,
132
+ configAttempted: this.config
133
+ }, error);
134
+ await this.cleanup();
135
+ throw error;
136
+ }
137
+ }
138
+ async stop() {
139
+ if (!this.isRunning) {
140
+ return;
141
+ }
142
+ const shutdownStartTime = Date.now();
143
+ this.logger.logServerEvent('shutdown', 'Stopping proxy server', {
144
+ uptime: Date.now() - this.startTime,
145
+ activeConnections: this.connectionPool.getAllConnections().length
146
+ });
147
+ try {
148
+ if (this.autoShutdownTimer) {
149
+ clearTimeout(this.autoShutdownTimer);
150
+ this.autoShutdownTimer = undefined;
151
+ }
152
+ if (this.memoryCleanupTimer) {
153
+ clearInterval(this.memoryCleanupTimer);
154
+ this.memoryCleanupTimer = undefined;
155
+ }
156
+ this.healthMonitor.stop();
157
+ this.performanceMonitor.stop();
158
+ await this.eventMonitor.stopAllMonitoring();
159
+ this.wsProxy.stop();
160
+ if (this.wsServer) {
161
+ this.wsServer.close();
162
+ }
163
+ await this.stopHttpServer();
164
+ await this.cleanup();
165
+ this.isRunning = false;
166
+ const shutdownTime = Date.now() - shutdownStartTime;
167
+ this.logger.logServerEvent('shutdown', 'Proxy server stopped successfully', {
168
+ shutdownTimeMs: shutdownTime,
169
+ totalUptime: Date.now() - this.startTime
170
+ });
171
+ this.logger.close();
172
+ }
173
+ catch (error) {
174
+ this.logger.logServerEvent('error', 'Error during proxy server shutdown', {
175
+ shutdownTimeMs: Date.now() - shutdownStartTime
176
+ }, error);
177
+ throw error;
178
+ }
179
+ }
180
+ isServerRunning() {
181
+ return this.isRunning;
182
+ }
183
+ getConfig() {
184
+ return { ...this.config };
185
+ }
186
+ getStatus() {
187
+ const memoryStats = this.messageStore.getStorageStats();
188
+ return {
189
+ isRunning: this.isRunning,
190
+ config: this.config,
191
+ connections: this.connectionPool.getAllConnections().length,
192
+ uptime: this.isRunning ? Date.now() - (this.connectionPool.getAllConnections()[0]?.createdAt || Date.now()) : 0,
193
+ memory: {
194
+ totalConsoleMessages: memoryStats.totalConsoleMessages,
195
+ totalNetworkRequests: memoryStats.totalNetworkRequests,
196
+ connectionsWithData: memoryStats.connections,
197
+ maxConsoleMessages: this.config.maxConsoleMessages,
198
+ maxNetworkRequests: this.config.maxNetworkRequests
199
+ },
200
+ security: {
201
+ securityManagerEnabled: true,
202
+ fileSystemSecurityEnabled: true,
203
+ allowedDirectories: this.fileSystemSecurity.getConfig().allowedDirectories.length
204
+ }
205
+ };
206
+ }
207
+ getFileSystemSecurity() {
208
+ return this.fileSystemSecurity;
209
+ }
210
+ getSecurityManager() {
211
+ return this.securityManager;
212
+ }
213
+ resetAutoShutdownTimer() {
214
+ if (this.autoShutdownTimer) {
215
+ clearTimeout(this.autoShutdownTimer);
216
+ }
217
+ if (this.config.autoShutdownTimeout > 0) {
218
+ this.autoShutdownTimer = setTimeout(async () => {
219
+ this.logger.logServerEvent('shutdown', 'Auto-shutdown timeout reached, stopping proxy server', {
220
+ timeoutMs: this.config.autoShutdownTimeout,
221
+ uptime: Date.now() - this.startTime,
222
+ activeConnections: this.connectionPool.getAllConnections().length
223
+ });
224
+ await this.stop();
225
+ }, this.config.autoShutdownTimeout);
226
+ }
227
+ }
228
+ startMemoryCleanup() {
229
+ this.memoryCleanupTimer = setInterval(() => {
230
+ try {
231
+ const beforeStats = this.messageStore.getStorageStats();
232
+ const beforeMemory = process.memoryUsage();
233
+ this.messageStore.enforceMemoryLimits();
234
+ this.connectionPool.cleanupUnusedConnections(this.config.autoShutdownTimeout);
235
+ const afterStats = this.messageStore.getStorageStats();
236
+ const afterMemory = process.memoryUsage();
237
+ this.logger.logMemoryEvent('cleanup', 'Periodic memory cleanup completed', {
238
+ memoryUsage: afterMemory,
239
+ connectionCount: this.connectionPool.getAllConnections().length,
240
+ messageCount: afterStats.totalConsoleMessages,
241
+ requestCount: afterStats.totalNetworkRequests
242
+ });
243
+ const messagesCleanedUp = beforeStats.totalConsoleMessages - afterStats.totalConsoleMessages;
244
+ const requestsCleanedUp = beforeStats.totalNetworkRequests - afterStats.totalNetworkRequests;
245
+ if (messagesCleanedUp > 0 || requestsCleanedUp > 0) {
246
+ this.logger.logMemoryEvent('cleanup', 'Memory cleanup removed old data', {
247
+ messagesRemoved: messagesCleanedUp,
248
+ requestsRemoved: requestsCleanedUp,
249
+ memoryFreed: beforeMemory.heapUsed - afterMemory.heapUsed
250
+ });
251
+ }
252
+ }
253
+ catch (error) {
254
+ this.logger.logServerEvent('error', 'Error during periodic memory cleanup', {
255
+ cleanupInterval: 60000
256
+ }, error);
257
+ }
258
+ }, 60000);
259
+ this.logger.info('Started periodic memory cleanup', { intervalMs: 60000 });
260
+ }
261
+ setupMiddleware() {
262
+ this.app.use(this.securityManager.getSecurityHeadersMiddleware());
263
+ this.app.use(this.securityManager.getSecurityLoggingMiddleware());
264
+ this.app.use(express_1.default.json({
265
+ limit: this.securityManager.getConfig().maxRequestBodySize,
266
+ strict: true,
267
+ type: 'application/json'
268
+ }));
269
+ this.app.use(this.securityManager.getRequestValidationMiddleware());
270
+ this.app.use(this.securityManager.getInputSanitizationMiddleware());
271
+ this.app.use(this.securityManager.getHostValidationMiddleware());
272
+ this.app.use((req, res, next) => {
273
+ const origin = req.get('origin');
274
+ if (!origin || origin.includes('localhost') || origin.includes('127.0.0.1')) {
275
+ res.header('Access-Control-Allow-Origin', origin || 'http://localhost:*');
276
+ }
277
+ res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
278
+ res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
279
+ res.header('Access-Control-Max-Age', '86400');
280
+ if (req.method === 'OPTIONS') {
281
+ res.sendStatus(200);
282
+ }
283
+ else {
284
+ next();
285
+ }
286
+ });
287
+ this.app.use((req, res, next) => {
288
+ const startTime = Date.now();
289
+ this.logger.debug(`API Request: ${req.method} ${req.path}`, {
290
+ method: req.method,
291
+ path: req.path,
292
+ clientIP: req.ip,
293
+ userAgent: req.get('user-agent')
294
+ });
295
+ res.on('finish', () => {
296
+ const duration = Date.now() - startTime;
297
+ this.logger.logAPIEvent(req.method, req.path, res.statusCode, duration, req.ip);
298
+ });
299
+ this.resetAutoShutdownTimer();
300
+ next();
301
+ });
302
+ this.app.use((error, req, res, _next) => {
303
+ this.logger.logAPIEvent(req.method, req.path, 500, 0, req.ip, error);
304
+ this.logger.logSecurityEvent('api_error', 'API error occurred', {
305
+ method: req.method,
306
+ path: req.path,
307
+ ip: req.ip,
308
+ userAgent: req.get('user-agent'),
309
+ error: error.message
310
+ });
311
+ res.status(500).json({
312
+ success: false,
313
+ error: 'Internal server error',
314
+ timestamp: Date.now()
315
+ });
316
+ });
317
+ }
318
+ async startHttpServer() {
319
+ return new Promise((resolve, reject) => {
320
+ this.httpServer.listen(this.config.port, this.config.host, () => {
321
+ resolve();
322
+ });
323
+ this.httpServer.on('error', (error) => {
324
+ if (error.code === 'EADDRINUSE') {
325
+ reject(new Error(`Port ${this.config.port} is already in use`));
326
+ }
327
+ else {
328
+ reject(error);
329
+ }
330
+ });
331
+ });
332
+ }
333
+ async stopHttpServer() {
334
+ return new Promise((resolve) => {
335
+ if (this.httpServer) {
336
+ this.httpServer.close(() => {
337
+ resolve();
338
+ });
339
+ }
340
+ else {
341
+ resolve();
342
+ }
343
+ });
344
+ }
345
+ async cleanup() {
346
+ try {
347
+ const connections = this.connectionPool.getAllConnections();
348
+ for (const connection of connections) {
349
+ await this.connectionPool.closeConnection(connection.id);
350
+ }
351
+ this.connectionPool.cleanup();
352
+ this.messageStore.clearAll();
353
+ }
354
+ catch (error) {
355
+ this.logger.error('Error during cleanup:', error);
356
+ }
357
+ }
358
+ getDefaultConfig() {
359
+ return {
360
+ port: 9223,
361
+ host: 'localhost',
362
+ maxConsoleMessages: 1000,
363
+ maxNetworkRequests: 500,
364
+ autoShutdownTimeout: 300000,
365
+ reconnectMaxAttempts: 5,
366
+ reconnectBackoffMs: 1000,
367
+ healthCheckInterval: 30000
368
+ };
369
+ }
370
+ async checkLogFileSecurity(logFile) {
371
+ try {
372
+ const securityCheck = await this.fileSystemSecurity.checkConfigurationSecurity(logFile);
373
+ if (!securityCheck.isSecure) {
374
+ this.logger.warn('Log file security issues detected', {
375
+ issues: securityCheck.issues,
376
+ recommendations: securityCheck.recommendations
377
+ });
378
+ try {
379
+ await this.fileSystemSecurity.fixConfigurationPermissions(logFile);
380
+ this.logger.info('Log file permissions fixed');
381
+ }
382
+ catch (error) {
383
+ this.logger.warn('Failed to fix log file permissions', { error });
384
+ }
385
+ }
386
+ }
387
+ catch (error) {
388
+ this.logger.warn('Log file security check failed', { error });
389
+ }
390
+ }
391
+ async loadConfigurationSecurely(configPath) {
392
+ if (!configPath) {
393
+ const defaultConfigPath = path.join(os.homedir(), '.chrome-cdp-cli', 'proxy.json');
394
+ configPath = defaultConfigPath;
395
+ }
396
+ try {
397
+ const securityCheck = await this.fileSystemSecurity.checkConfigurationSecurity(configPath);
398
+ if (!securityCheck.isSecure) {
399
+ this.logger.warn('Configuration file security issues detected', {
400
+ configPath: configPath.replace(os.homedir(), '~'),
401
+ issues: securityCheck.issues,
402
+ recommendations: securityCheck.recommendations
403
+ });
404
+ try {
405
+ await this.fileSystemSecurity.fixConfigurationPermissions(configPath);
406
+ }
407
+ catch (error) {
408
+ this.logger.warn('Failed to fix configuration file permissions', { error });
409
+ }
410
+ }
411
+ const configContent = await this.fileSystemSecurity.readFileSecurely(configPath);
412
+ const config = JSON.parse(configContent);
413
+ this.logger.info('Configuration loaded securely', {
414
+ configPath: configPath.replace(os.homedir(), '~'),
415
+ hasCustomConfig: true
416
+ });
417
+ return config;
418
+ }
419
+ catch (error) {
420
+ if (error.code === 'ENOENT') {
421
+ this.logger.info('No configuration file found, using defaults', {
422
+ configPath: configPath.replace(os.homedir(), '~')
423
+ });
424
+ return {};
425
+ }
426
+ else {
427
+ this.logger.warn('Failed to load configuration file', {
428
+ configPath: configPath.replace(os.homedir(), '~'),
429
+ error: error.message
430
+ });
431
+ return {};
432
+ }
433
+ }
434
+ }
435
+ }
436
+ exports.CDPProxyServer = CDPProxyServer;