@dynamicu/chromedebug-mcp 2.2.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.
Files changed (95) hide show
  1. package/CLAUDE.md +344 -0
  2. package/LICENSE +21 -0
  3. package/README.md +250 -0
  4. package/chrome-extension/README.md +41 -0
  5. package/chrome-extension/background.js +3917 -0
  6. package/chrome-extension/chrome-session-manager.js +706 -0
  7. package/chrome-extension/content.css +181 -0
  8. package/chrome-extension/content.js +3022 -0
  9. package/chrome-extension/data-buffer.js +435 -0
  10. package/chrome-extension/dom-tracker.js +411 -0
  11. package/chrome-extension/extension-config.js +78 -0
  12. package/chrome-extension/firebase-client.js +278 -0
  13. package/chrome-extension/firebase-config.js +32 -0
  14. package/chrome-extension/firebase-config.module.js +22 -0
  15. package/chrome-extension/firebase-config.module.template.js +27 -0
  16. package/chrome-extension/firebase-config.template.js +36 -0
  17. package/chrome-extension/frame-capture.js +407 -0
  18. package/chrome-extension/icon128.png +1 -0
  19. package/chrome-extension/icon16.png +1 -0
  20. package/chrome-extension/icon48.png +1 -0
  21. package/chrome-extension/license-helper.js +181 -0
  22. package/chrome-extension/logger.js +23 -0
  23. package/chrome-extension/manifest.json +73 -0
  24. package/chrome-extension/network-tracker.js +510 -0
  25. package/chrome-extension/offscreen.html +10 -0
  26. package/chrome-extension/options.html +203 -0
  27. package/chrome-extension/options.js +282 -0
  28. package/chrome-extension/pako.min.js +2 -0
  29. package/chrome-extension/performance-monitor.js +533 -0
  30. package/chrome-extension/pii-redactor.js +405 -0
  31. package/chrome-extension/popup.html +532 -0
  32. package/chrome-extension/popup.js +2446 -0
  33. package/chrome-extension/upload-manager.js +323 -0
  34. package/chrome-extension/web-vitals.iife.js +1 -0
  35. package/config/api-keys.json +11 -0
  36. package/config/chrome-pilot-config.json +45 -0
  37. package/package.json +126 -0
  38. package/scripts/cleanup-processes.js +109 -0
  39. package/scripts/config-manager.js +280 -0
  40. package/scripts/generate-extension-config.js +53 -0
  41. package/scripts/setup-security.js +64 -0
  42. package/src/capture/architecture.js +426 -0
  43. package/src/capture/error-handling-tests.md +38 -0
  44. package/src/capture/error-handling-types.ts +360 -0
  45. package/src/capture/index.js +508 -0
  46. package/src/capture/interfaces.js +625 -0
  47. package/src/capture/memory-manager.js +713 -0
  48. package/src/capture/types.js +342 -0
  49. package/src/chrome-controller.js +2658 -0
  50. package/src/cli.js +19 -0
  51. package/src/config-loader.js +303 -0
  52. package/src/database.js +2178 -0
  53. package/src/firebase-license-manager.js +462 -0
  54. package/src/firebase-privacy-guard.js +397 -0
  55. package/src/http-server.js +1516 -0
  56. package/src/index-direct.js +157 -0
  57. package/src/index-modular.js +219 -0
  58. package/src/index-monolithic-backup.js +2230 -0
  59. package/src/index.js +305 -0
  60. package/src/legacy/chrome-controller-old.js +1406 -0
  61. package/src/legacy/index-express.js +625 -0
  62. package/src/legacy/index-old.js +977 -0
  63. package/src/legacy/routes.js +260 -0
  64. package/src/legacy/shared-storage.js +101 -0
  65. package/src/logger.js +10 -0
  66. package/src/mcp/handlers/chrome-tool-handler.js +306 -0
  67. package/src/mcp/handlers/element-tool-handler.js +51 -0
  68. package/src/mcp/handlers/frame-tool-handler.js +957 -0
  69. package/src/mcp/handlers/request-handler.js +104 -0
  70. package/src/mcp/handlers/workflow-tool-handler.js +636 -0
  71. package/src/mcp/server.js +68 -0
  72. package/src/mcp/tools/index.js +701 -0
  73. package/src/middleware/auth.js +371 -0
  74. package/src/middleware/security.js +267 -0
  75. package/src/port-discovery.js +258 -0
  76. package/src/routes/admin.js +182 -0
  77. package/src/services/browser-daemon.js +494 -0
  78. package/src/services/chrome-service.js +375 -0
  79. package/src/services/failover-manager.js +412 -0
  80. package/src/services/git-safety-service.js +675 -0
  81. package/src/services/heartbeat-manager.js +200 -0
  82. package/src/services/http-client.js +195 -0
  83. package/src/services/process-manager.js +318 -0
  84. package/src/services/process-tracker.js +574 -0
  85. package/src/services/profile-manager.js +449 -0
  86. package/src/services/project-manager.js +415 -0
  87. package/src/services/session-manager.js +497 -0
  88. package/src/services/session-registry.js +491 -0
  89. package/src/services/unified-session-manager.js +678 -0
  90. package/src/shared-storage-old.js +267 -0
  91. package/src/standalone-server.js +53 -0
  92. package/src/utils/extension-path.js +145 -0
  93. package/src/utils.js +187 -0
  94. package/src/validation/log-transformer.js +125 -0
  95. package/src/validation/schemas.js +391 -0
@@ -0,0 +1,258 @@
1
+ // Port discovery utilities for Chrome Debug
2
+ // This helps Chrome extension and MCP clients find the running HTTP server
3
+ //
4
+ // Note: Claude Code (MCP client) may create .chromedebug-port-[UUID] files
5
+ // to track multiple sessions. Our code only uses .chromedebug-port (no UUID).
6
+ // We don't clean these files as we didn't create them.
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+ import crypto from 'crypto';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ // Session management for port discovery
16
+ let currentSessionId = null;
17
+
18
+ /**
19
+ * Get or generate a session ID for port discovery
20
+ */
21
+ function getSessionId() {
22
+ if (!currentSessionId) {
23
+ currentSessionId = crypto.randomBytes(8).toString('hex');
24
+ }
25
+ return currentSessionId;
26
+ }
27
+
28
+ /**
29
+ * Set a custom session ID for port discovery
30
+ */
31
+ export function setPortDiscoverySessionId(sessionId) {
32
+ if (typeof sessionId === 'string' && sessionId.match(/^[a-zA-Z0-9_-]+$/)) {
33
+ currentSessionId = sessionId;
34
+ console.log(`Port discovery session ID set to: ${sessionId}`);
35
+ } else {
36
+ throw new Error('Session ID must be alphanumeric with dashes/underscores only');
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Get the session-specific port file path
42
+ */
43
+ function getPortFilePath() {
44
+ const sessionId = getSessionId();
45
+ return path.join(__dirname, `../.chromedebug-port-${sessionId}`);
46
+ }
47
+
48
+ /**
49
+ * Get the legacy port file path (for backward compatibility)
50
+ */
51
+ function getLegacyPortFilePath() {
52
+ return path.join(__dirname, '../.chromedebug-port');
53
+ }
54
+
55
+ /**
56
+ * Write the current port to a session-specific file for discovery
57
+ */
58
+ export function writePortFile(port, options = {}) {
59
+ const { sessionId, writeLegacy = true } = options;
60
+
61
+ // Set session ID if provided
62
+ if (sessionId) {
63
+ setPortDiscoverySessionId(sessionId);
64
+ }
65
+
66
+ const portFilePath = getPortFilePath();
67
+ const legacyPortFilePath = getLegacyPortFilePath();
68
+
69
+ try {
70
+ // Write to session-specific file
71
+ fs.writeFileSync(portFilePath, port.toString());
72
+ console.log(`Port ${port} written to ${portFilePath} [Session: ${getSessionId()}]`);
73
+
74
+ // Optionally write to legacy file for backward compatibility
75
+ if (writeLegacy) {
76
+ try {
77
+ fs.writeFileSync(legacyPortFilePath, port.toString());
78
+ } catch (legacyError) {
79
+ console.warn(`Could not write legacy port file: ${legacyError.message}`);
80
+ }
81
+ }
82
+ } catch (error) {
83
+ console.error('Failed to write port file:', error);
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Read the port from session-specific or legacy file
89
+ */
90
+ export function readPortFile(sessionId = null) {
91
+ // If session ID provided, use that specific session
92
+ if (sessionId) {
93
+ try {
94
+ const sessionPortFile = path.join(__dirname, `../.chromedebug-port-${sessionId}`);
95
+ if (fs.existsSync(sessionPortFile)) {
96
+ const port = parseInt(fs.readFileSync(sessionPortFile, 'utf8').trim());
97
+ if (!isNaN(port)) {
98
+ return port;
99
+ }
100
+ }
101
+ } catch (error) {
102
+ console.error(`Failed to read session port file for ${sessionId}:`, error);
103
+ }
104
+ }
105
+
106
+ // Try current session file
107
+ const portFilePath = getPortFilePath();
108
+ try {
109
+ if (fs.existsSync(portFilePath)) {
110
+ const port = parseInt(fs.readFileSync(portFilePath, 'utf8').trim());
111
+ if (!isNaN(port)) {
112
+ return port;
113
+ }
114
+ }
115
+ } catch (error) {
116
+ console.error('Failed to read session port file:', error);
117
+ }
118
+
119
+ // Fall back to legacy file
120
+ const legacyPortFilePath = getLegacyPortFilePath();
121
+ try {
122
+ if (fs.existsSync(legacyPortFilePath)) {
123
+ const port = parseInt(fs.readFileSync(legacyPortFilePath, 'utf8').trim());
124
+ if (!isNaN(port)) {
125
+ return port;
126
+ }
127
+ }
128
+ } catch (error) {
129
+ console.error('Failed to read legacy port file:', error);
130
+ }
131
+
132
+ return null;
133
+ }
134
+
135
+ /**
136
+ * Remove the port files on shutdown
137
+ */
138
+ export function removePortFile() {
139
+ const portFilePath = getPortFilePath();
140
+ const legacyPortFilePath = getLegacyPortFilePath();
141
+
142
+ try {
143
+ if (fs.existsSync(portFilePath)) {
144
+ fs.unlinkSync(portFilePath);
145
+ console.log(`Removed session port file: ${portFilePath}`);
146
+ }
147
+ } catch (error) {
148
+ console.error('Failed to remove session port file:', error);
149
+ }
150
+
151
+ // Only remove legacy file if no other sessions are using it
152
+ try {
153
+ if (fs.existsSync(legacyPortFilePath)) {
154
+ // Check if there are other session port files
155
+ const portFiles = fs.readdirSync(__dirname + '/..')
156
+ .filter(f => f.startsWith('.chromedebug-port-') && f !== `.chromedebug-port-${getSessionId()}`);
157
+
158
+ if (portFiles.length === 0) {
159
+ fs.unlinkSync(legacyPortFilePath);
160
+ console.log('Removed legacy port file (no other sessions)');
161
+ }
162
+ }
163
+ } catch (error) {
164
+ console.error('Failed to remove legacy port file:', error);
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Try to discover the Chrome Debug server by checking multiple ports
170
+ */
171
+ export async function discoverServer(preferredPorts = [3000, 3001, 3002, 3028], options = {}) {
172
+ const { sessionId, extendedRange = true } = options;
173
+
174
+ // Extend port range to reduce conflicts in multi-session environments
175
+ if (extendedRange) {
176
+ preferredPorts = [
177
+ ...preferredPorts,
178
+ // Original extended range (maintain backward compatibility)
179
+ 3029, 3030, 3031, 3032, 3033, 3034, 3035, 3036,
180
+ // Safer fallback ranges (avoid common service conflicts)
181
+ 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8087, 8088, 8089, 8090,
182
+ 9000, 9001, 9002, 9003, 9004, 9005, 9006, 9007, 9008, 9009, 9010
183
+ ];
184
+ }
185
+
186
+ // First check session-specific port file
187
+ const filePort = readPortFile(sessionId);
188
+ if (filePort) {
189
+ preferredPorts.unshift(filePort);
190
+ }
191
+
192
+ // Try each port with exponential backoff
193
+ for (let i = 0; i < preferredPorts.length; i++) {
194
+ const port = preferredPorts[i];
195
+ try {
196
+ const response = await fetch(`http://localhost:${port}/chromedebug/status`, {
197
+ method: 'GET',
198
+ signal: AbortSignal.timeout(1000) // 1 second timeout
199
+ });
200
+
201
+ if (response.ok) {
202
+ const data = await response.json();
203
+ if (data.status === 'online') {
204
+ console.log(`Discovered ChromeDebug server on port ${port}`);
205
+ return port;
206
+ }
207
+ }
208
+ } catch (error) {
209
+ // Port not available or wrong server, continue
210
+ // Add small delay for later ports to avoid overwhelming the system
211
+ if (i > 3) {
212
+ await new Promise(resolve => setTimeout(resolve, 100));
213
+ }
214
+ }
215
+ }
216
+
217
+ return null;
218
+ }
219
+
220
+ /**
221
+ * Get session information for port discovery
222
+ */
223
+ export function getPortDiscoveryInfo() {
224
+ return {
225
+ sessionId: getSessionId(),
226
+ portFile: getPortFilePath(),
227
+ legacyPortFile: getLegacyPortFilePath(),
228
+ currentPort: readPortFile()
229
+ };
230
+ }
231
+
232
+ /**
233
+ * Find all active ChromeDebug sessions by scanning port files
234
+ */
235
+ export function findActiveSessions() {
236
+ const sessions = [];
237
+
238
+ try {
239
+ const files = fs.readdirSync(__dirname + '/..');
240
+ const portFiles = files.filter(f => f.startsWith('.chromedebug-port-'));
241
+
242
+ for (const file of portFiles) {
243
+ const sessionId = file.replace('.chromedebug-port-', '');
244
+ try {
245
+ const port = parseInt(fs.readFileSync(path.join(__dirname, '..', file), 'utf8').trim());
246
+ if (!isNaN(port)) {
247
+ sessions.push({ sessionId, port, portFile: file });
248
+ }
249
+ } catch (error) {
250
+ console.warn(`Could not read port file ${file}: ${error.message}`);
251
+ }
252
+ }
253
+ } catch (error) {
254
+ console.error('Failed to scan for active sessions:', error);
255
+ }
256
+
257
+ return sessions;
258
+ }
@@ -0,0 +1,182 @@
1
+ import express from 'express';
2
+ import {
3
+ generateApiKey,
4
+ listApiKeys,
5
+ revokeApiKey,
6
+ activateApiKey,
7
+ deleteApiKey,
8
+ requireAdmin,
9
+ ROLES
10
+ } from '../middleware/auth.js';
11
+ import { createValidator } from '../validation/schemas.js';
12
+ import { createApiKeySchema, updateApiKeySchema } from '../validation/schemas.js';
13
+ import { rateLimits } from '../middleware/security.js';
14
+
15
+ const router = express.Router();
16
+
17
+ // Apply admin authentication and rate limiting to all admin routes
18
+ router.use(requireAdmin);
19
+ router.use(rateLimits.auth);
20
+
21
+ // Generate new API key
22
+ router.post('/api-keys', createValidator(createApiKeySchema), (req, res) => {
23
+ try {
24
+ const { name, role } = req.body;
25
+
26
+ // Validate role
27
+ if (!Object.values(ROLES).includes(role)) {
28
+ return res.status(400).json({
29
+ error: 'Invalid role',
30
+ validRoles: Object.values(ROLES)
31
+ });
32
+ }
33
+
34
+ const result = generateApiKey(name, role);
35
+
36
+ res.status(201).json({
37
+ success: true,
38
+ message: 'API key created successfully',
39
+ apiKey: result.apiKey,
40
+ keyInfo: result.keyData
41
+ });
42
+ } catch (error) {
43
+ console.error('Error creating API key:', error);
44
+ res.status(500).json({
45
+ error: 'Failed to create API key',
46
+ details: error.message
47
+ });
48
+ }
49
+ });
50
+
51
+ // List all API keys
52
+ router.get('/api-keys', (req, res) => {
53
+ try {
54
+ const keys = listApiKeys();
55
+ res.json({
56
+ success: true,
57
+ keys,
58
+ total: keys.length
59
+ });
60
+ } catch (error) {
61
+ console.error('Error listing API keys:', error);
62
+ res.status(500).json({
63
+ error: 'Failed to list API keys',
64
+ details: error.message
65
+ });
66
+ }
67
+ });
68
+
69
+ // Update API key (revoke/activate)
70
+ router.patch('/api-keys/:keyId', createValidator(updateApiKeySchema), (req, res) => {
71
+ try {
72
+ const { keyId } = req.params;
73
+ const { active } = req.body;
74
+
75
+ if (typeof active === 'boolean') {
76
+ const success = active ?
77
+ activateApiKey(keyId) :
78
+ revokeApiKey(keyId);
79
+
80
+ if (success) {
81
+ res.json({
82
+ success: true,
83
+ message: active ? 'API key activated' : 'API key revoked'
84
+ });
85
+ } else {
86
+ res.status(404).json({
87
+ error: 'API key not found'
88
+ });
89
+ }
90
+ } else {
91
+ res.status(400).json({
92
+ error: 'Invalid update data'
93
+ });
94
+ }
95
+ } catch (error) {
96
+ console.error('Error updating API key:', error);
97
+ res.status(500).json({
98
+ error: 'Failed to update API key',
99
+ details: error.message
100
+ });
101
+ }
102
+ });
103
+
104
+ // Delete API key permanently
105
+ router.delete('/api-keys/:keyId', (req, res) => {
106
+ try {
107
+ const { keyId } = req.params;
108
+ const success = deleteApiKey(keyId);
109
+
110
+ if (success) {
111
+ res.json({
112
+ success: true,
113
+ message: 'API key deleted successfully'
114
+ });
115
+ } else {
116
+ res.status(404).json({
117
+ error: 'API key not found'
118
+ });
119
+ }
120
+ } catch (error) {
121
+ console.error('Error deleting API key:', error);
122
+ res.status(500).json({
123
+ error: 'Failed to delete API key',
124
+ details: error.message
125
+ });
126
+ }
127
+ });
128
+
129
+ // System status and security info
130
+ router.get('/status', (req, res) => {
131
+ try {
132
+ const keys = listApiKeys();
133
+ const activeKeys = keys.filter(k => k.active);
134
+
135
+ res.json({
136
+ success: true,
137
+ system: {
138
+ totalApiKeys: keys.length,
139
+ activeApiKeys: activeKeys.length,
140
+ roles: Object.values(ROLES),
141
+ securityFeatures: [
142
+ 'API Key Authentication',
143
+ 'JWT Bearer Tokens',
144
+ 'Role-based Access Control',
145
+ 'Rate Limiting',
146
+ 'CORS Protection',
147
+ 'Security Headers',
148
+ 'Request Validation',
149
+ 'Audit Logging'
150
+ ]
151
+ },
152
+ timestamp: new Date().toISOString()
153
+ });
154
+ } catch (error) {
155
+ console.error('Error getting admin status:', error);
156
+ res.status(500).json({
157
+ error: 'Failed to get status',
158
+ details: error.message
159
+ });
160
+ }
161
+ });
162
+
163
+ // Security audit log
164
+ router.get('/audit-log', (req, res) => {
165
+ try {
166
+ // This would typically come from a proper audit log database
167
+ // For now, return a placeholder response
168
+ res.json({
169
+ success: true,
170
+ message: 'Audit logging not yet implemented',
171
+ note: 'Security events are currently logged to console'
172
+ });
173
+ } catch (error) {
174
+ console.error('Error getting audit log:', error);
175
+ res.status(500).json({
176
+ error: 'Failed to get audit log',
177
+ details: error.message
178
+ });
179
+ }
180
+ });
181
+
182
+ export default router;