@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.
- package/CLAUDE.md +344 -0
- package/LICENSE +21 -0
- package/README.md +250 -0
- package/chrome-extension/README.md +41 -0
- package/chrome-extension/background.js +3917 -0
- package/chrome-extension/chrome-session-manager.js +706 -0
- package/chrome-extension/content.css +181 -0
- package/chrome-extension/content.js +3022 -0
- package/chrome-extension/data-buffer.js +435 -0
- package/chrome-extension/dom-tracker.js +411 -0
- package/chrome-extension/extension-config.js +78 -0
- package/chrome-extension/firebase-client.js +278 -0
- package/chrome-extension/firebase-config.js +32 -0
- package/chrome-extension/firebase-config.module.js +22 -0
- package/chrome-extension/firebase-config.module.template.js +27 -0
- package/chrome-extension/firebase-config.template.js +36 -0
- package/chrome-extension/frame-capture.js +407 -0
- package/chrome-extension/icon128.png +1 -0
- package/chrome-extension/icon16.png +1 -0
- package/chrome-extension/icon48.png +1 -0
- package/chrome-extension/license-helper.js +181 -0
- package/chrome-extension/logger.js +23 -0
- package/chrome-extension/manifest.json +73 -0
- package/chrome-extension/network-tracker.js +510 -0
- package/chrome-extension/offscreen.html +10 -0
- package/chrome-extension/options.html +203 -0
- package/chrome-extension/options.js +282 -0
- package/chrome-extension/pako.min.js +2 -0
- package/chrome-extension/performance-monitor.js +533 -0
- package/chrome-extension/pii-redactor.js +405 -0
- package/chrome-extension/popup.html +532 -0
- package/chrome-extension/popup.js +2446 -0
- package/chrome-extension/upload-manager.js +323 -0
- package/chrome-extension/web-vitals.iife.js +1 -0
- package/config/api-keys.json +11 -0
- package/config/chrome-pilot-config.json +45 -0
- package/package.json +126 -0
- package/scripts/cleanup-processes.js +109 -0
- package/scripts/config-manager.js +280 -0
- package/scripts/generate-extension-config.js +53 -0
- package/scripts/setup-security.js +64 -0
- package/src/capture/architecture.js +426 -0
- package/src/capture/error-handling-tests.md +38 -0
- package/src/capture/error-handling-types.ts +360 -0
- package/src/capture/index.js +508 -0
- package/src/capture/interfaces.js +625 -0
- package/src/capture/memory-manager.js +713 -0
- package/src/capture/types.js +342 -0
- package/src/chrome-controller.js +2658 -0
- package/src/cli.js +19 -0
- package/src/config-loader.js +303 -0
- package/src/database.js +2178 -0
- package/src/firebase-license-manager.js +462 -0
- package/src/firebase-privacy-guard.js +397 -0
- package/src/http-server.js +1516 -0
- package/src/index-direct.js +157 -0
- package/src/index-modular.js +219 -0
- package/src/index-monolithic-backup.js +2230 -0
- package/src/index.js +305 -0
- package/src/legacy/chrome-controller-old.js +1406 -0
- package/src/legacy/index-express.js +625 -0
- package/src/legacy/index-old.js +977 -0
- package/src/legacy/routes.js +260 -0
- package/src/legacy/shared-storage.js +101 -0
- package/src/logger.js +10 -0
- package/src/mcp/handlers/chrome-tool-handler.js +306 -0
- package/src/mcp/handlers/element-tool-handler.js +51 -0
- package/src/mcp/handlers/frame-tool-handler.js +957 -0
- package/src/mcp/handlers/request-handler.js +104 -0
- package/src/mcp/handlers/workflow-tool-handler.js +636 -0
- package/src/mcp/server.js +68 -0
- package/src/mcp/tools/index.js +701 -0
- package/src/middleware/auth.js +371 -0
- package/src/middleware/security.js +267 -0
- package/src/port-discovery.js +258 -0
- package/src/routes/admin.js +182 -0
- package/src/services/browser-daemon.js +494 -0
- package/src/services/chrome-service.js +375 -0
- package/src/services/failover-manager.js +412 -0
- package/src/services/git-safety-service.js +675 -0
- package/src/services/heartbeat-manager.js +200 -0
- package/src/services/http-client.js +195 -0
- package/src/services/process-manager.js +318 -0
- package/src/services/process-tracker.js +574 -0
- package/src/services/profile-manager.js +449 -0
- package/src/services/project-manager.js +415 -0
- package/src/services/session-manager.js +497 -0
- package/src/services/session-registry.js +491 -0
- package/src/services/unified-session-manager.js +678 -0
- package/src/shared-storage-old.js +267 -0
- package/src/standalone-server.js +53 -0
- package/src/utils/extension-path.js +145 -0
- package/src/utils.js +187 -0
- package/src/validation/log-transformer.js +125 -0
- package/src/validation/schemas.js +391 -0
|
@@ -0,0 +1,449 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProfileManager - Chrome Profile Isolation Manager
|
|
3
|
+
*
|
|
4
|
+
* Manages isolated Chrome profiles for true session separation.
|
|
5
|
+
* Each session gets its own completely isolated profile directory
|
|
6
|
+
* with separate storage, cookies, cache, and settings.
|
|
7
|
+
*
|
|
8
|
+
* Key Features:
|
|
9
|
+
* - UUID-based profile isolation (no data leakage between sessions)
|
|
10
|
+
* - Secure profile directory validation and cleanup
|
|
11
|
+
* - Storage quotas and limits to prevent DoS
|
|
12
|
+
* - Automatic cleanup of orphaned profiles
|
|
13
|
+
* - Profile lifecycle management with proper resource tracking
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { promises as fs } from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import { randomUUID } from 'crypto';
|
|
20
|
+
|
|
21
|
+
export class ProfileManager {
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.profilesDir = options.profilesDir || path.join(os.tmpdir(), 'chrome-pilot-profiles');
|
|
24
|
+
this.activeProfiles = new Map(); // sessionId -> { profilePath, claudeInstanceId, createdAt, size }
|
|
25
|
+
this.initialized = false;
|
|
26
|
+
|
|
27
|
+
// Security and resource limits
|
|
28
|
+
this.config = {
|
|
29
|
+
maxProfileSizeMB: options.maxProfileSizeMB || 500, // 500MB per profile
|
|
30
|
+
maxTotalProfilesMB: options.maxTotalProfilesMB || 5000, // 5GB total
|
|
31
|
+
profileCleanupAgeMs: options.profileCleanupAgeMs || 24 * 60 * 60 * 1000, // 24 hours
|
|
32
|
+
...options
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
console.log(`[ProfileManager] Initialized with profiles directory: ${this.profilesDir}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initializes the profile manager
|
|
40
|
+
*/
|
|
41
|
+
async initialize() {
|
|
42
|
+
if (this.initialized) return;
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
// Create profiles directory if it doesn't exist
|
|
46
|
+
await fs.mkdir(this.profilesDir, { recursive: true });
|
|
47
|
+
|
|
48
|
+
// Clean up any orphaned profiles from previous runs
|
|
49
|
+
await this.cleanupOrphanedProfiles();
|
|
50
|
+
|
|
51
|
+
this.initialized = true;
|
|
52
|
+
console.log('[ProfileManager] Initialized successfully');
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('[ProfileManager] Initialization failed:', error);
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Creates an isolated Chrome profile for a session
|
|
61
|
+
* @param {string} sessionId - Unique session identifier
|
|
62
|
+
* @param {string} claudeInstanceId - Claude instance identifier
|
|
63
|
+
* @returns {Promise<string>} Path to the created profile directory
|
|
64
|
+
*/
|
|
65
|
+
async createSessionProfile(sessionId, claudeInstanceId) {
|
|
66
|
+
if (!this.initialized) {
|
|
67
|
+
await this.initialize();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Validate inputs
|
|
71
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
72
|
+
throw new Error('Valid sessionId is required');
|
|
73
|
+
}
|
|
74
|
+
if (!claudeInstanceId || typeof claudeInstanceId !== 'string') {
|
|
75
|
+
throw new Error('Valid claudeInstanceId is required');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Security: Validate session ID doesn't contain path traversal attempts
|
|
79
|
+
// Decode URL-encoded attempts first
|
|
80
|
+
let decodedSessionId = sessionId;
|
|
81
|
+
try {
|
|
82
|
+
decodedSessionId = decodeURIComponent(sessionId);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
// If decoding fails, use original
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (sessionId.includes('..') || sessionId.includes('/') || sessionId.includes('\\') ||
|
|
88
|
+
decodedSessionId.includes('..') || decodedSessionId.includes('/') || decodedSessionId.includes('\\') ||
|
|
89
|
+
sessionId.includes('%2e') || sessionId.includes('%2f') || sessionId.includes('%5c')) {
|
|
90
|
+
throw new Error('Security: Session ID contains invalid characters');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Check if profile already exists
|
|
94
|
+
if (this.activeProfiles.has(sessionId)) {
|
|
95
|
+
throw new Error(`Profile already exists for session: ${sessionId}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
// Create unique profile directory
|
|
100
|
+
const profileName = `session-${sessionId}`;
|
|
101
|
+
const profilePath = path.join(this.profilesDir, profileName);
|
|
102
|
+
|
|
103
|
+
// Security: Ensure the profile path is within our profiles directory
|
|
104
|
+
const resolvedProfilePath = path.resolve(profilePath);
|
|
105
|
+
const resolvedProfilesDir = path.resolve(this.profilesDir);
|
|
106
|
+
|
|
107
|
+
if (!resolvedProfilePath.startsWith(resolvedProfilesDir)) {
|
|
108
|
+
throw new Error('Security: Profile path outside allowed directory');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// Check total space usage before creating new profile
|
|
112
|
+
await this.checkSpaceQuota();
|
|
113
|
+
|
|
114
|
+
// Create profile directory structure
|
|
115
|
+
await fs.mkdir(profilePath, { recursive: true });
|
|
116
|
+
|
|
117
|
+
// Create Chrome profile subdirectories
|
|
118
|
+
const chromeProfileDirs = [
|
|
119
|
+
'Default',
|
|
120
|
+
'Default/Cache',
|
|
121
|
+
'Default/Code Cache',
|
|
122
|
+
'Default/GPUCache',
|
|
123
|
+
'Default/Storage',
|
|
124
|
+
'Default/databases',
|
|
125
|
+
'Default/Local Storage',
|
|
126
|
+
'Default/Session Storage'
|
|
127
|
+
];
|
|
128
|
+
|
|
129
|
+
for (const dir of chromeProfileDirs) {
|
|
130
|
+
await fs.mkdir(path.join(profilePath, dir), { recursive: true });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Create profile configuration
|
|
134
|
+
await this.createProfileConfig(profilePath, sessionId, claudeInstanceId);
|
|
135
|
+
|
|
136
|
+
// Track the profile
|
|
137
|
+
const profileInfo = {
|
|
138
|
+
profilePath: resolvedProfilePath,
|
|
139
|
+
claudeInstanceId,
|
|
140
|
+
createdAt: new Date(),
|
|
141
|
+
size: 0 // Will be updated during cleanup
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
this.activeProfiles.set(sessionId, profileInfo);
|
|
145
|
+
|
|
146
|
+
console.log(`[ProfileManager] Created profile for session ${sessionId}: ${resolvedProfilePath}`);
|
|
147
|
+
return resolvedProfilePath;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
console.error(`[ProfileManager] Failed to create profile for session ${sessionId}:`, error);
|
|
150
|
+
// Cleanup partial profile creation
|
|
151
|
+
try {
|
|
152
|
+
const profilePath = path.join(this.profilesDir, `session-${sessionId}`);
|
|
153
|
+
await fs.rm(profilePath, { recursive: true, force: true });
|
|
154
|
+
} catch (cleanupError) {
|
|
155
|
+
console.warn('[ProfileManager] Failed to cleanup partial profile:', cleanupError);
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Creates a basic Chrome profile configuration
|
|
163
|
+
* @param {string} profilePath - Path to the profile directory
|
|
164
|
+
* @param {string} sessionId - Session ID
|
|
165
|
+
* @param {string} claudeInstanceId - Claude instance ID
|
|
166
|
+
*/
|
|
167
|
+
async createProfileConfig(profilePath, sessionId, claudeInstanceId) {
|
|
168
|
+
const defaultDir = path.join(profilePath, 'Default');
|
|
169
|
+
|
|
170
|
+
// Create Preferences file with security settings
|
|
171
|
+
const preferences = {
|
|
172
|
+
profile: {
|
|
173
|
+
name: `Chrome Pilot Session ${sessionId.substring(0, 8)}`,
|
|
174
|
+
managed_user_id: sessionId,
|
|
175
|
+
is_supervised: false
|
|
176
|
+
},
|
|
177
|
+
browser: {
|
|
178
|
+
check_default_browser: false,
|
|
179
|
+
show_home_button: false
|
|
180
|
+
},
|
|
181
|
+
security: {
|
|
182
|
+
// Enhanced security for isolated sessions
|
|
183
|
+
allow_cross_origin_auth_prompts: false,
|
|
184
|
+
mixed_content: {
|
|
185
|
+
auto_upgrade_insecure_requests: true
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
privacy: {
|
|
189
|
+
// Privacy settings for session isolation
|
|
190
|
+
dns_prefetching_enabled: false,
|
|
191
|
+
network_prediction_enabled: false,
|
|
192
|
+
background_mode_enabled: false
|
|
193
|
+
},
|
|
194
|
+
session: {
|
|
195
|
+
restore_on_startup: 1, // New Tab Page
|
|
196
|
+
startup_urls: []
|
|
197
|
+
},
|
|
198
|
+
// Session metadata for debugging
|
|
199
|
+
chrome_pilot: {
|
|
200
|
+
session_id: sessionId,
|
|
201
|
+
claude_instance_id: claudeInstanceId,
|
|
202
|
+
created_at: new Date().toISOString(),
|
|
203
|
+
profile_version: '1.0'
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const preferencesPath = path.join(defaultDir, 'Preferences');
|
|
208
|
+
await fs.writeFile(preferencesPath, JSON.stringify(preferences, null, 2));
|
|
209
|
+
|
|
210
|
+
// Create Local State file
|
|
211
|
+
const localState = {
|
|
212
|
+
profile: {
|
|
213
|
+
info_cache: {
|
|
214
|
+
Default: {
|
|
215
|
+
name: `Chrome Pilot Session ${sessionId.substring(0, 8)}`,
|
|
216
|
+
user_name: `session-${sessionId}`,
|
|
217
|
+
is_supervised: false,
|
|
218
|
+
is_child: false,
|
|
219
|
+
is_force_signin_enabled: false
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
last_used: 'Default',
|
|
223
|
+
last_active_profiles: ['Default']
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const localStatePath = path.join(profilePath, 'Local State');
|
|
228
|
+
await fs.writeFile(localStatePath, JSON.stringify(localState, null, 2));
|
|
229
|
+
|
|
230
|
+
console.log(`[ProfileManager] Created profile configuration for session ${sessionId}`);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Cleans up a session profile
|
|
235
|
+
* @param {string} sessionId - Session ID to cleanup
|
|
236
|
+
* @returns {Promise<boolean>} True if cleanup was successful
|
|
237
|
+
*/
|
|
238
|
+
async cleanupProfile(sessionId) {
|
|
239
|
+
if (!sessionId || !this.activeProfiles.has(sessionId)) {
|
|
240
|
+
console.warn(`[ProfileManager] Attempted to cleanup non-existent profile: ${sessionId}`);
|
|
241
|
+
return false;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
try {
|
|
245
|
+
const profileInfo = this.activeProfiles.get(sessionId);
|
|
246
|
+
const profilePath = profileInfo.profilePath;
|
|
247
|
+
|
|
248
|
+
// Security: Validate profile path before deletion
|
|
249
|
+
const resolvedProfilePath = path.resolve(profilePath);
|
|
250
|
+
const resolvedProfilesDir = path.resolve(this.profilesDir);
|
|
251
|
+
|
|
252
|
+
if (!resolvedProfilePath.startsWith(resolvedProfilesDir)) {
|
|
253
|
+
throw new Error('Security: Refusing to delete path outside profiles directory');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Calculate profile size for logging
|
|
257
|
+
try {
|
|
258
|
+
const stats = await this.calculateDirectorySize(profilePath);
|
|
259
|
+
console.log(`[ProfileManager] Profile ${sessionId} size: ${(stats.size / 1024 / 1024).toFixed(2)}MB (${stats.files} files)`);
|
|
260
|
+
} catch (sizeError) {
|
|
261
|
+
console.warn('[ProfileManager] Could not calculate profile size:', sizeError);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Remove profile directory
|
|
265
|
+
await fs.rm(profilePath, { recursive: true, force: true });
|
|
266
|
+
|
|
267
|
+
// Remove from active profiles
|
|
268
|
+
this.activeProfiles.delete(sessionId);
|
|
269
|
+
|
|
270
|
+
console.log(`[ProfileManager] Cleaned up profile for session ${sessionId}`);
|
|
271
|
+
return true;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error(`[ProfileManager] Error cleaning up profile for session ${sessionId}:`, error);
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Checks space quota before creating new profiles
|
|
280
|
+
*/
|
|
281
|
+
async checkSpaceQuota() {
|
|
282
|
+
try {
|
|
283
|
+
const totalSize = await this.calculateTotalProfilesSize();
|
|
284
|
+
const totalSizeMB = totalSize / 1024 / 1024;
|
|
285
|
+
|
|
286
|
+
if (totalSizeMB > this.config.maxTotalProfilesMB) {
|
|
287
|
+
throw new Error(`Total profiles size (${totalSizeMB.toFixed(2)}MB) exceeds limit (${this.config.maxTotalProfilesMB}MB)`);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
console.log(`[ProfileManager] Current profiles size: ${totalSizeMB.toFixed(2)}MB / ${this.config.maxTotalProfilesMB}MB`);
|
|
291
|
+
} catch (error) {
|
|
292
|
+
if (error.message.includes('exceeds limit')) {
|
|
293
|
+
throw error;
|
|
294
|
+
}
|
|
295
|
+
console.warn('[ProfileManager] Could not check space quota:', error);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Calculates total size of all profiles
|
|
301
|
+
* @returns {Promise<number>} Total size in bytes
|
|
302
|
+
*/
|
|
303
|
+
async calculateTotalProfilesSize() {
|
|
304
|
+
try {
|
|
305
|
+
const entries = await fs.readdir(this.profilesDir);
|
|
306
|
+
let totalSize = 0;
|
|
307
|
+
|
|
308
|
+
for (const entry of entries) {
|
|
309
|
+
const entryPath = path.join(this.profilesDir, entry);
|
|
310
|
+
const stats = await fs.stat(entryPath);
|
|
311
|
+
|
|
312
|
+
if (stats.isDirectory()) {
|
|
313
|
+
const dirStats = await this.calculateDirectorySize(entryPath);
|
|
314
|
+
totalSize += dirStats.size;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return totalSize;
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.warn('[ProfileManager] Error calculating total profiles size:', error);
|
|
321
|
+
return 0;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Calculates size of a directory recursively
|
|
327
|
+
* @param {string} dirPath - Directory path
|
|
328
|
+
* @returns {Promise<{size: number, files: number}>} Size in bytes and file count
|
|
329
|
+
*/
|
|
330
|
+
async calculateDirectorySize(dirPath) {
|
|
331
|
+
let totalSize = 0;
|
|
332
|
+
let fileCount = 0;
|
|
333
|
+
|
|
334
|
+
async function traverse(currentPath) {
|
|
335
|
+
try {
|
|
336
|
+
const entries = await fs.readdir(currentPath);
|
|
337
|
+
|
|
338
|
+
for (const entry of entries) {
|
|
339
|
+
const entryPath = path.join(currentPath, entry);
|
|
340
|
+
const stats = await fs.stat(entryPath);
|
|
341
|
+
|
|
342
|
+
if (stats.isDirectory()) {
|
|
343
|
+
await traverse(entryPath);
|
|
344
|
+
} else {
|
|
345
|
+
totalSize += stats.size;
|
|
346
|
+
fileCount++;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
// Skip directories/files we can't access
|
|
351
|
+
console.debug(`[ProfileManager] Skipping inaccessible path: ${currentPath}`);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
await traverse(dirPath);
|
|
356
|
+
return { size: totalSize, files: fileCount };
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
/**
|
|
360
|
+
* Cleans up orphaned profiles from previous runs
|
|
361
|
+
*/
|
|
362
|
+
async cleanupOrphanedProfiles() {
|
|
363
|
+
try {
|
|
364
|
+
const entries = await fs.readdir(this.profilesDir);
|
|
365
|
+
const sessionDirs = entries.filter(entry => entry.startsWith('session-'));
|
|
366
|
+
|
|
367
|
+
let cleanedCount = 0;
|
|
368
|
+
|
|
369
|
+
for (const sessionDir of sessionDirs) {
|
|
370
|
+
const sessionDirPath = path.join(this.profilesDir, sessionDir);
|
|
371
|
+
|
|
372
|
+
try {
|
|
373
|
+
const stats = await fs.stat(sessionDirPath);
|
|
374
|
+
const age = Date.now() - stats.mtime.getTime();
|
|
375
|
+
|
|
376
|
+
// Clean up profiles older than configured age
|
|
377
|
+
if (age > this.config.profileCleanupAgeMs) {
|
|
378
|
+
await fs.rm(sessionDirPath, { recursive: true, force: true });
|
|
379
|
+
cleanedCount++;
|
|
380
|
+
console.log(`[ProfileManager] Cleaned up orphaned profile: ${sessionDir}`);
|
|
381
|
+
}
|
|
382
|
+
} catch (error) {
|
|
383
|
+
console.warn(`[ProfileManager] Error processing orphaned profile ${sessionDir}:`, error);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (cleanedCount > 0) {
|
|
388
|
+
console.log(`[ProfileManager] Cleaned up ${cleanedCount} orphaned profiles`);
|
|
389
|
+
}
|
|
390
|
+
} catch (error) {
|
|
391
|
+
console.warn('[ProfileManager] Error during orphaned profile cleanup:', error);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Gets information about a specific profile
|
|
397
|
+
* @param {string} sessionId - Session ID
|
|
398
|
+
* @returns {Object|null} Profile information or null if not found
|
|
399
|
+
*/
|
|
400
|
+
getProfileInfo(sessionId) {
|
|
401
|
+
if (this.activeProfiles.has(sessionId)) {
|
|
402
|
+
return { ...this.activeProfiles.get(sessionId) };
|
|
403
|
+
}
|
|
404
|
+
return null;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Lists all active profiles
|
|
409
|
+
* @returns {Array} Array of profile information objects
|
|
410
|
+
*/
|
|
411
|
+
listActiveProfiles() {
|
|
412
|
+
const profiles = [];
|
|
413
|
+
for (const [sessionId, profileInfo] of this.activeProfiles) {
|
|
414
|
+
profiles.push({
|
|
415
|
+
sessionId,
|
|
416
|
+
...profileInfo
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
return profiles;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* Gets manager status and statistics
|
|
424
|
+
* @returns {Object} Status information
|
|
425
|
+
*/
|
|
426
|
+
getStatus() {
|
|
427
|
+
return {
|
|
428
|
+
initialized: this.initialized,
|
|
429
|
+
profilesDir: this.profilesDir,
|
|
430
|
+
activeProfiles: this.activeProfiles.size,
|
|
431
|
+
config: this.config
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Cleanup method for graceful shutdown
|
|
437
|
+
*/
|
|
438
|
+
async cleanup() {
|
|
439
|
+
console.log('[ProfileManager] Cleaning up active profiles...');
|
|
440
|
+
|
|
441
|
+
// Clean up all active profiles
|
|
442
|
+
const sessionIds = Array.from(this.activeProfiles.keys());
|
|
443
|
+
for (const sessionId of sessionIds) {
|
|
444
|
+
await this.cleanupProfile(sessionId);
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
console.log('[ProfileManager] Cleanup completed');
|
|
448
|
+
}
|
|
449
|
+
}
|