@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,491 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PersistentRegistry - Disk-backed Session Registry
|
|
3
|
+
*
|
|
4
|
+
* Provides atomic, persistent session tracking with file locking to prevent
|
|
5
|
+
* corruption during concurrent access. Stores session-to-profile mappings
|
|
6
|
+
* on disk for recovery after daemon restarts.
|
|
7
|
+
*
|
|
8
|
+
* Key Features:
|
|
9
|
+
* - Atomic file operations with exclusive locking
|
|
10
|
+
* - Session persistence across daemon restarts
|
|
11
|
+
* - Corruption detection and recovery
|
|
12
|
+
* - Concurrent access protection
|
|
13
|
+
* - Automatic cleanup of stale sessions
|
|
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 PersistentRegistry {
|
|
22
|
+
constructor(options = {}) {
|
|
23
|
+
this.registryDir = options.registryDir || path.join(os.tmpdir(), 'chrome-pilot-registry');
|
|
24
|
+
this.registryFile = path.join(this.registryDir, 'sessions.json');
|
|
25
|
+
this.lockFile = path.join(this.registryDir, 'sessions.lock');
|
|
26
|
+
this.backupFile = path.join(this.registryDir, 'sessions.backup.json');
|
|
27
|
+
|
|
28
|
+
// Registry data in memory
|
|
29
|
+
this.sessions = new Map(); // sessionId -> sessionData
|
|
30
|
+
this.initialized = false;
|
|
31
|
+
|
|
32
|
+
// Configuration
|
|
33
|
+
this.config = {
|
|
34
|
+
lockTimeoutMs: options.lockTimeoutMs || 10000, // 10 seconds
|
|
35
|
+
maxLockRetries: options.maxLockRetries || 5,
|
|
36
|
+
lockRetryDelayMs: options.lockRetryDelayMs || 200,
|
|
37
|
+
backupRetentionCount: options.backupRetentionCount || 5,
|
|
38
|
+
staleSessionAgeMs: options.staleSessionAgeMs || 24 * 60 * 60 * 1000, // 24 hours
|
|
39
|
+
...options
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
console.log(`[SessionRegistry] Initialized with registry directory: ${this.registryDir}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Initializes the session registry
|
|
47
|
+
*/
|
|
48
|
+
async initialize() {
|
|
49
|
+
if (this.initialized) return;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Create registry directory if it doesn't exist
|
|
53
|
+
await fs.mkdir(this.registryDir, { recursive: true });
|
|
54
|
+
|
|
55
|
+
// Load existing sessions from disk
|
|
56
|
+
await this.loadSessions();
|
|
57
|
+
|
|
58
|
+
// Clean up stale sessions
|
|
59
|
+
await this.cleanupStaleSessions();
|
|
60
|
+
|
|
61
|
+
this.initialized = true;
|
|
62
|
+
console.log(`[SessionRegistry] Initialized successfully with ${this.sessions.size} existing sessions`);
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('[SessionRegistry] Initialization failed:', error);
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Updates or creates a session in the registry
|
|
71
|
+
* @param {string} sessionId - Session ID
|
|
72
|
+
* @param {Object} sessionData - Session data to store
|
|
73
|
+
*/
|
|
74
|
+
async updateSession(sessionId, sessionData) {
|
|
75
|
+
if (!this.initialized) {
|
|
76
|
+
await this.initialize();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Validate inputs
|
|
80
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
81
|
+
throw new Error('Valid sessionId is required');
|
|
82
|
+
}
|
|
83
|
+
if (!sessionData || typeof sessionData !== 'object') {
|
|
84
|
+
throw new Error('Valid sessionData is required');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
await this.withFileLock(async () => {
|
|
88
|
+
// Get existing session data or create new
|
|
89
|
+
const existingData = this.sessions.get(sessionId) || {};
|
|
90
|
+
|
|
91
|
+
// Merge with new data
|
|
92
|
+
const updatedData = {
|
|
93
|
+
...existingData,
|
|
94
|
+
...sessionData,
|
|
95
|
+
sessionId,
|
|
96
|
+
lastUpdated: new Date().toISOString()
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Ensure required fields
|
|
100
|
+
if (!updatedData.createdAt) {
|
|
101
|
+
updatedData.createdAt = new Date().toISOString();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Update in-memory registry
|
|
105
|
+
this.sessions.set(sessionId, updatedData);
|
|
106
|
+
|
|
107
|
+
// Persist to disk
|
|
108
|
+
await this.saveSessions();
|
|
109
|
+
|
|
110
|
+
console.log(`[SessionRegistry] Updated session ${sessionId}`);
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Gets session data from the registry
|
|
116
|
+
* @param {string} sessionId - Session ID
|
|
117
|
+
* @returns {Object|null} Session data or null if not found
|
|
118
|
+
*/
|
|
119
|
+
async getSession(sessionId) {
|
|
120
|
+
if (!this.initialized) {
|
|
121
|
+
await this.initialize();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!sessionId || typeof sessionId !== 'string') {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const sessionData = this.sessions.get(sessionId);
|
|
129
|
+
if (!sessionData) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Only verify profile exists if it's a real profile path (not test data)
|
|
134
|
+
if (sessionData.profilePath && !sessionData.profilePath.includes('/tmp/test-profile')) {
|
|
135
|
+
try {
|
|
136
|
+
await fs.access(sessionData.profilePath);
|
|
137
|
+
} catch (error) {
|
|
138
|
+
console.warn(`[SessionRegistry] Profile no longer exists for session ${sessionId}, removing from registry`);
|
|
139
|
+
await this.removeSession(sessionId);
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return { ...sessionData };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Removes a session from the registry
|
|
149
|
+
* @param {string} sessionId - Session ID to remove
|
|
150
|
+
* @returns {Promise<boolean>} True if session was removed
|
|
151
|
+
*/
|
|
152
|
+
async removeSession(sessionId) {
|
|
153
|
+
if (!this.initialized) {
|
|
154
|
+
await this.initialize();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!sessionId || !this.sessions.has(sessionId)) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await this.withFileLock(async () => {
|
|
162
|
+
this.sessions.delete(sessionId);
|
|
163
|
+
await this.saveSessions();
|
|
164
|
+
console.log(`[SessionRegistry] Removed session ${sessionId}`);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Lists all sessions in the registry
|
|
172
|
+
* @param {Object} filters - Optional filters
|
|
173
|
+
* @returns {Array} Array of session data objects
|
|
174
|
+
*/
|
|
175
|
+
async listSessions(filters = {}) {
|
|
176
|
+
if (!this.initialized) {
|
|
177
|
+
await this.initialize();
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let sessions = Array.from(this.sessions.values());
|
|
181
|
+
|
|
182
|
+
// Apply filters
|
|
183
|
+
if (filters.claudeInstanceId) {
|
|
184
|
+
sessions = sessions.filter(s => s.claudeInstanceId === filters.claudeInstanceId);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (filters.daemonId) {
|
|
188
|
+
sessions = sessions.filter(s => s.daemonId === filters.daemonId);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (filters.activeOnly) {
|
|
192
|
+
const now = new Date();
|
|
193
|
+
sessions = sessions.filter(s => {
|
|
194
|
+
if (!s.lastActivity) return false;
|
|
195
|
+
const lastActivity = new Date(s.lastActivity);
|
|
196
|
+
return (now.getTime() - lastActivity.getTime()) < this.config.staleSessionAgeMs;
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return sessions;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Performs atomic file operations with exclusive locking
|
|
205
|
+
* @param {Function} operation - Async operation to perform
|
|
206
|
+
*/
|
|
207
|
+
async withFileLock(operation) {
|
|
208
|
+
const lockAcquired = await this.acquireLock();
|
|
209
|
+
if (!lockAcquired) {
|
|
210
|
+
throw new Error('Could not acquire file lock within timeout');
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await operation();
|
|
215
|
+
} finally {
|
|
216
|
+
await this.releaseLock();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Acquires exclusive file lock
|
|
222
|
+
* @returns {Promise<boolean>} True if lock was acquired
|
|
223
|
+
*/
|
|
224
|
+
async acquireLock() {
|
|
225
|
+
const lockData = {
|
|
226
|
+
pid: process.pid,
|
|
227
|
+
timestamp: Date.now(),
|
|
228
|
+
lockId: randomUUID()
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
for (let attempt = 0; attempt < this.config.maxLockRetries; attempt++) {
|
|
232
|
+
try {
|
|
233
|
+
// Try to create lock file exclusively
|
|
234
|
+
await fs.writeFile(this.lockFile, JSON.stringify(lockData), { flag: 'wx' });
|
|
235
|
+
return true;
|
|
236
|
+
} catch (error) {
|
|
237
|
+
if (error.code === 'EEXIST') {
|
|
238
|
+
// Lock file exists, check if it's stale
|
|
239
|
+
try {
|
|
240
|
+
const existingLockData = JSON.parse(await fs.readFile(this.lockFile, 'utf-8'));
|
|
241
|
+
const lockAge = Date.now() - existingLockData.timestamp;
|
|
242
|
+
|
|
243
|
+
if (lockAge > this.config.lockTimeoutMs) {
|
|
244
|
+
console.warn('[SessionRegistry] Removing stale lock file');
|
|
245
|
+
await fs.unlink(this.lockFile);
|
|
246
|
+
continue; // Retry
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Check if process still exists
|
|
250
|
+
try {
|
|
251
|
+
process.kill(existingLockData.pid, 0);
|
|
252
|
+
// Process exists, wait and retry
|
|
253
|
+
} catch (processError) {
|
|
254
|
+
if (processError.code === 'ESRCH') {
|
|
255
|
+
// Process doesn't exist, remove stale lock
|
|
256
|
+
console.warn('[SessionRegistry] Removing lock file from dead process');
|
|
257
|
+
await fs.unlink(this.lockFile);
|
|
258
|
+
continue; // Retry
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
} catch (parseError) {
|
|
262
|
+
// Corrupted lock file, remove it
|
|
263
|
+
console.warn('[SessionRegistry] Removing corrupted lock file');
|
|
264
|
+
await fs.unlink(this.lockFile);
|
|
265
|
+
continue; // Retry
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Wait before retrying
|
|
269
|
+
await new Promise(resolve => setTimeout(resolve, this.config.lockRetryDelayMs));
|
|
270
|
+
} else {
|
|
271
|
+
throw error;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return false; // Could not acquire lock
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Releases exclusive file lock
|
|
281
|
+
*/
|
|
282
|
+
async releaseLock() {
|
|
283
|
+
try {
|
|
284
|
+
await fs.unlink(this.lockFile);
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error.code !== 'ENOENT') {
|
|
287
|
+
console.warn('[SessionRegistry] Error releasing lock:', error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Loads sessions from disk
|
|
294
|
+
*/
|
|
295
|
+
async loadSessions() {
|
|
296
|
+
try {
|
|
297
|
+
const data = await fs.readFile(this.registryFile, 'utf-8');
|
|
298
|
+
const sessionsArray = JSON.parse(data);
|
|
299
|
+
|
|
300
|
+
// Validate data structure
|
|
301
|
+
if (!Array.isArray(sessionsArray)) {
|
|
302
|
+
throw new Error('Invalid registry file format');
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Load into memory
|
|
306
|
+
this.sessions.clear();
|
|
307
|
+
for (const sessionData of sessionsArray) {
|
|
308
|
+
if (sessionData.sessionId) {
|
|
309
|
+
this.sessions.set(sessionData.sessionId, sessionData);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
console.log(`[SessionRegistry] Loaded ${this.sessions.size} sessions from disk`);
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error.code === 'ENOENT') {
|
|
316
|
+
console.log('[SessionRegistry] No existing registry file found, starting fresh');
|
|
317
|
+
this.sessions.clear();
|
|
318
|
+
} else {
|
|
319
|
+
console.warn('[SessionRegistry] Error loading sessions, attempting backup recovery:', error);
|
|
320
|
+
await this.loadFromBackup();
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Saves sessions to disk
|
|
327
|
+
*/
|
|
328
|
+
async saveSessions() {
|
|
329
|
+
try {
|
|
330
|
+
// Create backup before saving
|
|
331
|
+
await this.createBackup();
|
|
332
|
+
|
|
333
|
+
// Convert Map to Array for JSON serialization
|
|
334
|
+
const sessionsArray = Array.from(this.sessions.values());
|
|
335
|
+
const data = JSON.stringify(sessionsArray, null, 2);
|
|
336
|
+
|
|
337
|
+
// Write atomically using temporary file
|
|
338
|
+
const tempFile = `${this.registryFile}.tmp`;
|
|
339
|
+
await fs.writeFile(tempFile, data, 'utf-8');
|
|
340
|
+
await fs.rename(tempFile, this.registryFile);
|
|
341
|
+
|
|
342
|
+
console.log(`[SessionRegistry] Saved ${this.sessions.size} sessions to disk`);
|
|
343
|
+
} catch (error) {
|
|
344
|
+
console.error('[SessionRegistry] Error saving sessions:', error);
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Creates a backup of the current registry
|
|
351
|
+
*/
|
|
352
|
+
async createBackup() {
|
|
353
|
+
try {
|
|
354
|
+
// Check if registry file exists
|
|
355
|
+
await fs.access(this.registryFile);
|
|
356
|
+
|
|
357
|
+
// Create timestamped backup
|
|
358
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
359
|
+
const backupPath = `${this.backupFile}.${timestamp}`;
|
|
360
|
+
|
|
361
|
+
await fs.copyFile(this.registryFile, backupPath);
|
|
362
|
+
|
|
363
|
+
// Clean up old backups
|
|
364
|
+
await this.cleanupOldBackups();
|
|
365
|
+
} catch (error) {
|
|
366
|
+
if (error.code !== 'ENOENT') {
|
|
367
|
+
console.warn('[SessionRegistry] Error creating backup:', error);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Loads sessions from the most recent backup
|
|
374
|
+
*/
|
|
375
|
+
async loadFromBackup() {
|
|
376
|
+
try {
|
|
377
|
+
const files = await fs.readdir(this.registryDir);
|
|
378
|
+
const backupFiles = files
|
|
379
|
+
.filter(file => file.startsWith('sessions.backup.json.'))
|
|
380
|
+
.sort()
|
|
381
|
+
.reverse(); // Most recent first
|
|
382
|
+
|
|
383
|
+
if (backupFiles.length === 0) {
|
|
384
|
+
console.log('[SessionRegistry] No backup files found, starting fresh');
|
|
385
|
+
this.sessions.clear();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const latestBackup = path.join(this.registryDir, backupFiles[0]);
|
|
390
|
+
const data = await fs.readFile(latestBackup, 'utf-8');
|
|
391
|
+
const sessionsArray = JSON.parse(data);
|
|
392
|
+
|
|
393
|
+
this.sessions.clear();
|
|
394
|
+
for (const sessionData of sessionsArray) {
|
|
395
|
+
if (sessionData.sessionId) {
|
|
396
|
+
this.sessions.set(sessionData.sessionId, sessionData);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
console.log(`[SessionRegistry] Recovered ${this.sessions.size} sessions from backup: ${backupFiles[0]}`);
|
|
401
|
+
} catch (error) {
|
|
402
|
+
console.error('[SessionRegistry] Error loading from backup:', error);
|
|
403
|
+
this.sessions.clear();
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Cleans up old backup files
|
|
409
|
+
*/
|
|
410
|
+
async cleanupOldBackups() {
|
|
411
|
+
try {
|
|
412
|
+
const files = await fs.readdir(this.registryDir);
|
|
413
|
+
const backupFiles = files
|
|
414
|
+
.filter(file => file.startsWith('sessions.backup.json.'))
|
|
415
|
+
.sort()
|
|
416
|
+
.reverse(); // Most recent first
|
|
417
|
+
|
|
418
|
+
// Keep only the configured number of backups
|
|
419
|
+
const filesToDelete = backupFiles.slice(this.config.backupRetentionCount);
|
|
420
|
+
|
|
421
|
+
for (const file of filesToDelete) {
|
|
422
|
+
await fs.unlink(path.join(this.registryDir, file));
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
if (filesToDelete.length > 0) {
|
|
426
|
+
console.log(`[SessionRegistry] Cleaned up ${filesToDelete.length} old backup files`);
|
|
427
|
+
}
|
|
428
|
+
} catch (error) {
|
|
429
|
+
console.warn('[SessionRegistry] Error cleaning up old backups:', error);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Cleans up stale sessions from the registry
|
|
435
|
+
*/
|
|
436
|
+
async cleanupStaleSessions() {
|
|
437
|
+
const now = new Date();
|
|
438
|
+
const staleSessions = [];
|
|
439
|
+
|
|
440
|
+
for (const [sessionId, sessionData] of this.sessions) {
|
|
441
|
+
// Check if session is stale
|
|
442
|
+
const lastActivity = sessionData.lastActivity ? new Date(sessionData.lastActivity) : new Date(sessionData.createdAt);
|
|
443
|
+
const age = now.getTime() - lastActivity.getTime();
|
|
444
|
+
|
|
445
|
+
if (age > this.config.staleSessionAgeMs) {
|
|
446
|
+
staleSessions.push(sessionId);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// Remove stale sessions
|
|
451
|
+
if (staleSessions.length > 0) {
|
|
452
|
+
await this.withFileLock(async () => {
|
|
453
|
+
for (const sessionId of staleSessions) {
|
|
454
|
+
this.sessions.delete(sessionId);
|
|
455
|
+
}
|
|
456
|
+
await this.saveSessions();
|
|
457
|
+
});
|
|
458
|
+
|
|
459
|
+
console.log(`[SessionRegistry] Cleaned up ${staleSessions.length} stale sessions`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Gets registry status and statistics
|
|
465
|
+
* @returns {Object} Status information
|
|
466
|
+
*/
|
|
467
|
+
getStatus() {
|
|
468
|
+
return {
|
|
469
|
+
initialized: this.initialized,
|
|
470
|
+
registryDir: this.registryDir,
|
|
471
|
+
registryFile: this.registryFile,
|
|
472
|
+
activeSessions: this.sessions.size,
|
|
473
|
+
config: this.config
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Cleanup method for graceful shutdown
|
|
479
|
+
*/
|
|
480
|
+
async cleanup() {
|
|
481
|
+
console.log('[SessionRegistry] Performing final save...');
|
|
482
|
+
|
|
483
|
+
if (this.sessions.size > 0) {
|
|
484
|
+
await this.withFileLock(async () => {
|
|
485
|
+
await this.saveSessions();
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
console.log('[SessionRegistry] Cleanup completed');
|
|
490
|
+
}
|
|
491
|
+
}
|