@dynamicu/chromedebug-mcp 2.7.0 → 2.7.2

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.
@@ -24,7 +24,7 @@ class DataBuffer {
24
24
 
25
25
  request.onsuccess = () => {
26
26
  this.db = request.result;
27
- console.log('[DataBuffer] IndexedDB initialized');
27
+ // console.log('[DataBuffer] IndexedDB initialized');
28
28
  this.calculateCurrentSize();
29
29
  resolve();
30
30
  };
@@ -304,7 +304,7 @@ class DataBuffer {
304
304
  async checkBufferLimits() {
305
305
  // Implement FIFO circular buffer strategy
306
306
  if (this.currentSize > this.maxBufferSize) {
307
- console.log('[DataBuffer] Buffer limit exceeded, removing oldest batches');
307
+ // console.log('[DataBuffer] Buffer limit exceeded, removing oldest batches');
308
308
  await this.removeOldestBatches();
309
309
  }
310
310
  }
@@ -329,7 +329,7 @@ class DataBuffer {
329
329
  cursor.continue();
330
330
  } else {
331
331
  this.currentSize -= deletedSize;
332
- console.log(`[DataBuffer] Freed ${deletedSize} bytes`);
332
+ // console.log(`[DataBuffer] Freed ${deletedSize} bytes`);
333
333
  resolve();
334
334
  }
335
335
  };
@@ -367,7 +367,7 @@ class DataBuffer {
367
367
  }
368
368
 
369
369
  this.currentSize = totalSize;
370
- console.log(`[DataBuffer] Current buffer size: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
370
+ // console.log(`[DataBuffer] Current buffer size: ${(totalSize / 1024 / 1024).toFixed(2)}MB`);
371
371
  };
372
372
  }
373
373
 
@@ -400,7 +400,7 @@ class DataBuffer {
400
400
  return new Promise((resolve) => {
401
401
  transaction.oncomplete = () => {
402
402
  this.currentSize = 0;
403
- console.log('[DataBuffer] Buffer cleared');
403
+ // console.log('[DataBuffer] Buffer cleared');
404
404
  resolve();
405
405
  };
406
406
  });
@@ -33,12 +33,12 @@ class DOMTracker {
33
33
  if (options.maxBufferSize) this.maxBufferSize = options.maxBufferSize;
34
34
  if (options.mutationThrottle) this.mutationThrottle = options.mutationThrottle;
35
35
 
36
- console.log('[DOMTracker] Initialized with options:', {
37
- recordingId: this.recordingId,
38
- bufferFlushDelay: this.bufferFlushDelay,
39
- maxBufferSize: this.maxBufferSize,
40
- mutationThrottle: this.mutationThrottle
41
- });
36
+ // console.log('[DOMTracker] Initialized with options:', {
37
+ // recordingId: this.recordingId,
38
+ // bufferFlushDelay: this.bufferFlushDelay,
39
+ // maxBufferSize: this.maxBufferSize,
40
+ // mutationThrottle: this.mutationThrottle
41
+ // });
42
42
  }
43
43
 
44
44
  startTracking() {
@@ -69,7 +69,7 @@ class DOMTracker {
69
69
  this.isTracking = true;
70
70
  this.startBufferFlusher();
71
71
 
72
- console.log('[DOMTracker] Started DOM mutation tracking');
72
+ // console.log('[DOMTracker] Started DOM mutation tracking');
73
73
 
74
74
  // Record performance event
75
75
  if (this.performanceMonitor) {
@@ -96,7 +96,7 @@ class DOMTracker {
96
96
  this.flushBuffer(); // Flush any remaining data
97
97
 
98
98
  this.isTracking = false;
99
- console.log('[DOMTracker] Stopped DOM mutation tracking');
99
+ // console.log('[DOMTracker] Stopped DOM mutation tracking');
100
100
 
101
101
  // Record performance event
102
102
  if (this.performanceMonitor) {
@@ -334,7 +334,7 @@ class DOMTracker {
334
334
  const events = [...this.eventBuffer];
335
335
  this.eventBuffer = [];
336
336
 
337
- console.log(`[DOMTracker] Flushing ${events.length} DOM mutations`);
337
+ // console.log(`[DOMTracker] Flushing ${events.length} DOM mutations`);
338
338
 
339
339
  // Send data via callback
340
340
  if (this.onDataReady) {
@@ -12,7 +12,7 @@ class FirebaseLicenseClient {
12
12
  this.functionsUrl = FUNCTIONS_URL;
13
13
  this.cacheKey = 'chromedebug_license_cache';
14
14
  this.instanceIdKey = 'chromedebug_instance_id';
15
- console.log('[License] Initialized with FUNCTIONS_URL:', this.functionsUrl);
15
+ // console.log('[License] Initialized with FUNCTIONS_URL:', this.functionsUrl);
16
16
  }
17
17
 
18
18
  /**
@@ -101,8 +101,8 @@ class FirebaseLicenseClient {
101
101
  const deviceInfo = this.getDeviceInfo();
102
102
 
103
103
  try {
104
- console.log(`[License] Activating license ${licenseKey} with device ${deviceId}`);
105
- console.log('[License] Device info:', deviceInfo);
104
+ // console.log(`[License] Activating license ${licenseKey} with device ${deviceId}`);
105
+ // console.log('[License] Device info:', deviceInfo);
106
106
 
107
107
  const response = await fetch(`${this.functionsUrl}/activateLicense`, {
108
108
  method: 'POST',
@@ -125,7 +125,7 @@ class FirebaseLicenseClient {
125
125
  }
126
126
 
127
127
  const data = await response.json();
128
- console.log('[License] Activation response:', data);
128
+ // console.log('[License] Activation response:', data);
129
129
 
130
130
  // If activation successful, save the LemonSqueezy instance ID
131
131
  if (data.activated && data.instance) {
@@ -134,7 +134,7 @@ class FirebaseLicenseClient {
134
134
  'ls_license_key': licenseKey.trim(),
135
135
  'activated_at': Date.now()
136
136
  });
137
- console.log(`[License] Saved LS instance ID: ${data.instance.id}`);
137
+ // console.log(`[License] Saved LS instance ID: ${data.instance.id}`);
138
138
  }
139
139
 
140
140
  return data;
@@ -161,7 +161,7 @@ class FirebaseLicenseClient {
161
161
 
162
162
  // If no instance ID yet, this license needs activation first
163
163
  if (!instanceId) {
164
- console.log('[License] No instance ID found - activating license first');
164
+ // console.log('[License] No instance ID found - activating license first');
165
165
  const activationResult = await this.activateLicense(licenseKey);
166
166
 
167
167
  if (!activationResult.activated) {
@@ -197,7 +197,7 @@ class FirebaseLicenseClient {
197
197
  * @returns {Promise<{valid: boolean, tier?: string}>}
198
198
  */
199
199
  async validateWithInstanceId(licenseKey, instanceId) {
200
- console.log(`[License] Validating ${licenseKey} with instance ${instanceId}`);
200
+ // console.log(`[License] Validating ${licenseKey} with instance ${instanceId}`);
201
201
 
202
202
  const response = await fetch(`${this.functionsUrl}/validateLicense`, {
203
203
  method: 'POST',
@@ -210,7 +210,7 @@ class FirebaseLicenseClient {
210
210
  }
211
211
 
212
212
  const data = await response.json();
213
- console.log('[License] Validation response:', data);
213
+ // console.log('[License] Validation response:', data);
214
214
 
215
215
  // Map LemonSqueezy response to our format
216
216
  const result = {
@@ -249,7 +249,7 @@ class FirebaseLicenseClient {
249
249
 
250
250
  const url = `${this.functionsUrl}/checkUsageLimit`;
251
251
  try {
252
- console.log('[License] Calling checkUsageLimit at:', url);
252
+ // console.log('[License] Calling checkUsageLimit at:', url);
253
253
  const response = await fetch(url, {
254
254
  method: 'POST',
255
255
  headers: {'Content-Type': 'application/json'},
@@ -351,7 +351,7 @@ class FirebaseLicenseClient {
351
351
  */
352
352
  async listActivations(licenseKey) {
353
353
  try {
354
- console.log(`[License] Listing activations for ${licenseKey}`);
354
+ // console.log(`[License] Listing activations for ${licenseKey}`);
355
355
 
356
356
  const response = await fetch(`${this.functionsUrl}/listActivations`, {
357
357
  method: 'POST',
@@ -365,7 +365,7 @@ class FirebaseLicenseClient {
365
365
  }
366
366
 
367
367
  const data = await response.json();
368
- console.log('[License] Activations:', data);
368
+ // console.log('[License] Activations:', data);
369
369
 
370
370
  return data;
371
371
  } catch (error) {
@@ -382,7 +382,7 @@ class FirebaseLicenseClient {
382
382
  */
383
383
  async deactivateInstance(licenseKey, instanceId) {
384
384
  try {
385
- console.log(`[License] Deactivating instance ${instanceId}`);
385
+ // console.log(`[License] Deactivating instance ${instanceId}`);
386
386
 
387
387
  const response = await fetch(`${this.functionsUrl}/deactivateInstance`, {
388
388
  method: 'POST',
@@ -399,7 +399,7 @@ class FirebaseLicenseClient {
399
399
  }
400
400
 
401
401
  const data = await response.json();
402
- console.log('[License] Deactivation result:', data);
402
+ // console.log('[License] Deactivation result:', data);
403
403
 
404
404
  return data;
405
405
  } catch (error) {
@@ -30,7 +30,6 @@ class FrameCapture {
30
30
  this.mode = 'server';
31
31
  } else {
32
32
  this.mode = mode;
33
- console.log('[FrameCapture] Mode set to:', this.mode);
34
33
  }
35
34
  }
36
35
 
@@ -46,8 +45,15 @@ class FrameCapture {
46
45
  this.isCapturing = true;
47
46
  this.tabId = tabId;
48
47
 
49
- // Initialize lease tracking - null forces immediate first check
50
- this.lastLeaseCheck = null;
48
+ // Initialize lease tracking - set to now so first check happens after interval
49
+ this.lastLeaseCheck = Date.now();
50
+
51
+ // Emergency max duration timer (belt and suspenders - 1 hour hard stop)
52
+ this.emergencyMaxDuration = 60 * 60 * 1000; // 1 hour absolute max
53
+ this.emergencyTimer = setTimeout(() => {
54
+ console.error('[FrameCapture] EMERGENCY: Absolute max duration (1 hour) reached - force stopping');
55
+ this.stopCapture();
56
+ }, this.emergencyMaxDuration);
51
57
 
52
58
  // Update settings with passed values or apply mode-specific defaults
53
59
  if (this.mode === 'browser-only') {
@@ -66,17 +72,11 @@ class FrameCapture {
66
72
  };
67
73
  }
68
74
 
69
- console.log('Starting frame capture session:', this.sessionId);
70
- console.log('Frame capture scheduled to start at:', new Date(scheduledStartTime));
71
- console.log('Frame capture settings:', this.settings);
72
-
73
75
  // Trigger visual feedback system start
74
76
  chrome.runtime.sendMessage({
75
77
  type: 'start-screen-capture-tracking',
76
78
  sessionId: this.sessionId
77
- }).catch(() => {
78
- console.log('[FrameCapture] Could not start visual feedback tracking');
79
- });
79
+ }).catch(() => {});
80
80
 
81
81
  // Create a synthetic first frame at scheduled start time to catch early logs
82
82
  const syntheticFrame = {
@@ -87,7 +87,6 @@ class FrameCapture {
87
87
  };
88
88
  this.frames.push(syntheticFrame);
89
89
  this.totalFramesCaptured++;
90
- console.log('Created synthetic first frame at scheduled start time');
91
90
 
92
91
  // Get the media stream with resolution based on mode
93
92
  const videoConstraints = this.mode === 'browser-only'
@@ -142,10 +141,8 @@ class FrameCapture {
142
141
 
143
142
  // Wait until scheduled start time before beginning frame capture
144
143
  const waitTime = Math.max(0, this.scheduledStartTime - Date.now());
145
- console.log(`Frame capture will wait ${waitTime}ms until scheduled start time`);
146
144
 
147
145
  setTimeout(() => {
148
- console.log('Frame capture starting at scheduled time');
149
146
 
150
147
  // Capture frames at intervals (e.g., 1 frame per second)
151
148
  this.captureInterval = setInterval(async () => {
@@ -155,7 +152,6 @@ class FrameCapture {
155
152
  if (this.lastLeaseCheck && (Date.now() - this.lastLeaseCheck) > this.leaseCheckInterval) {
156
153
  const leaseValid = await this.checkLease();
157
154
  if (!leaseValid) {
158
- console.log('[FrameCapture] Lease expired, stopping capture');
159
155
  this.stopCapture();
160
156
  return;
161
157
  }
@@ -181,15 +177,11 @@ class FrameCapture {
181
177
 
182
178
  this.frames.push(frameData);
183
179
  this.totalFramesCaptured++;
184
-
185
- console.log(`Captured frame ${this.totalFramesCaptured}, size: ${blob.size} bytes`);
186
180
 
187
181
  // Trigger visual feedback for frame captured
188
182
  chrome.runtime.sendMessage({
189
183
  type: 'show-screen-capture-flash'
190
- }).catch(() => {
191
- console.log('[FrameCapture] Could not trigger frame capture flash');
192
- });
184
+ }).catch(() => {});
193
185
 
194
186
  // Send frame immediately to keep memory usage low
195
187
  if (this.frames.length % 5 === 0) { // Every 5 frames
@@ -207,9 +199,6 @@ class FrameCapture {
207
199
  }
208
200
  }, this.settings.frameRate * 1000); // Use configurable frame rate
209
201
 
210
- console.log(`Frame capture interval set to ${this.settings.frameRate * 1000}ms (${this.settings.frameRate}s)`);
211
- console.log(`Image quality set to ${this.settings.imageQuality}%`);
212
-
213
202
  // Store the media stream for cleanup
214
203
  this.mediaStream = media;
215
204
  this.videoElement = video;
@@ -219,8 +208,6 @@ class FrameCapture {
219
208
 
220
209
  // Check lease validity with background script
221
210
  async checkLease() {
222
- console.log('[FrameCapture] Lease check starting - SessionId:', this.sessionId);
223
-
224
211
  try {
225
212
  const response = await Promise.race([
226
213
  chrome.runtime.sendMessage({
@@ -235,7 +222,6 @@ class FrameCapture {
235
222
  )
236
223
  ]);
237
224
 
238
- console.log('[FrameCapture] Lease response - Success:', response?.success);
239
225
  return response?.success === true;
240
226
 
241
227
  } catch (error) {
@@ -251,6 +237,12 @@ class FrameCapture {
251
237
 
252
238
  this.isCapturing = false;
253
239
 
240
+ // Clear emergency max duration timer
241
+ if (this.emergencyTimer) {
242
+ clearTimeout(this.emergencyTimer);
243
+ this.emergencyTimer = null;
244
+ }
245
+
254
246
  // Stop interval
255
247
  if (this.captureInterval) {
256
248
  clearInterval(this.captureInterval);
@@ -275,9 +267,7 @@ class FrameCapture {
275
267
  // Stop visual feedback system
276
268
  chrome.runtime.sendMessage({
277
269
  type: 'stop-screen-capture-tracking'
278
- }).catch(() => {
279
- console.log('[FrameCapture] Could not stop visual feedback tracking');
280
- });
270
+ }).catch(() => {});
281
271
 
282
272
  // Notify completion
283
273
  chrome.runtime.sendMessage({
@@ -290,7 +280,6 @@ class FrameCapture {
290
280
  }
291
281
  });
292
282
 
293
- console.log('Frame capture stopped');
294
283
  }
295
284
 
296
285
  // Flash indicator method to show when frames are captured
@@ -315,9 +304,7 @@ class FrameCapture {
315
304
  if (!this.videoElement || !this.videoElement.videoWidth) {
316
305
  throw new Error('Video element not ready for manual snapshot');
317
306
  }
318
-
319
- console.log('Taking manual snapshot...');
320
-
307
+
321
308
  // Create a temporary canvas for the manual snapshot
322
309
  const canvas = document.createElement('canvas');
323
310
  const ctx = canvas.getContext('2d');
@@ -348,15 +335,11 @@ class FrameCapture {
348
335
 
349
336
  this.frames.push(frameData);
350
337
  this.totalFramesCaptured++;
351
-
352
- console.log(`Manual snapshot captured: frame ${this.totalFramesCaptured}, size: ${blob.size} bytes`);
353
338
 
354
339
  // Trigger visual feedback for manual snapshot
355
340
  chrome.runtime.sendMessage({
356
341
  type: 'show-screen-capture-flash'
357
- }).catch(() => {
358
- console.log('[FrameCapture] Could not trigger manual snapshot flash');
359
- });
342
+ }).catch(() => {});
360
343
 
361
344
  // Flash indicator for manual snapshot
362
345
  if (this.settings.frameFlash) {
@@ -395,7 +378,6 @@ class FrameCapture {
395
378
  });
396
379
 
397
380
  const batch = this.frames.splice(0, 5); // Send up to 5 frames at a time
398
- console.log(`[FrameCapture] Sending batch of ${batch.length} frames, ${this.frames.length} remaining`);
399
381
 
400
382
  chrome.runtime.sendMessage({
401
383
  type: 'frame-batch-ready',
@@ -29,7 +29,7 @@ const LicenseHelper = {
29
29
  try {
30
30
  // PRO version: always allow, no restrictions
31
31
  if (this.isProVersion()) {
32
- console.log('[License] PRO version - bypassing all license checks');
32
+ // console.log('[License] PRO version - bypassing all license checks');
33
33
  return {allowed: true, tier: 'pro', proVersion: true};
34
34
  }
35
35
 
@@ -41,7 +41,7 @@ const LicenseHelper = {
41
41
  // First time user - allow recording, create userId
42
42
  userId = crypto.randomUUID();
43
43
  await chrome.storage.local.set({[this.userIdKey]: userId});
44
- console.log('[License] New user created:', userId);
44
+ // console.log('[License] New user created:', userId);
45
45
  return {allowed: true, firstTime: true, userId: userId};
46
46
  }
47
47
 
@@ -49,13 +49,13 @@ const LicenseHelper = {
49
49
  const licenseStatus = await this.getCachedLicenseStatus();
50
50
 
51
51
  if (licenseStatus.valid && licenseStatus.tier === 'pro') {
52
- console.log('[License] Pro user - allowing recording');
52
+ // console.log('[License] Pro user - allowing recording');
53
53
  return {allowed: true, tier: 'pro', userId: userId};
54
54
  }
55
55
 
56
56
  // Free tier - check usage limit
57
57
  const usage = await this.checkUsageLimit(userId);
58
- console.log('[License] Usage check:', usage);
58
+ // console.log('[License] Usage check:', usage);
59
59
 
60
60
  // Handle both API response format (withinLimit) and offline format (allowed)
61
61
  const withinLimit = usage.withinLimit ?? usage.allowed ?? true;
@@ -87,7 +87,7 @@ const LicenseHelper = {
87
87
  try {
88
88
  // PRO version: skip all usage tracking
89
89
  if (this.isProVersion()) {
90
- console.log('[License] PRO version - skipping usage tracking');
90
+ // console.log('[License] PRO version - skipping usage tracking');
91
91
  return {success: true, tier: 'pro', proVersion: true, skipped: true};
92
92
  }
93
93
 
@@ -104,13 +104,13 @@ const LicenseHelper = {
104
104
  // Check if pro user (don't increment for pro)
105
105
  const licenseStatus = await this.getCachedLicenseStatus();
106
106
  if (licenseStatus.valid && licenseStatus.tier === 'pro') {
107
- console.log('[License] Pro user - skipping usage increment');
107
+ // console.log('[License] Pro user - skipping usage increment');
108
108
  return {success: true, tier: 'pro', skipped: true};
109
109
  }
110
110
 
111
111
  // Increment usage for free tier
112
112
  await this.incrementUsage(userId);
113
- console.log('[License] Usage incremented for user:', userId);
113
+ // console.log('[License] Usage incremented for user:', userId);
114
114
 
115
115
  return {success: true};
116
116
  } catch (error) {
@@ -127,8 +127,8 @@ const LicenseHelper = {
127
127
  async checkUsageLimit(userId) {
128
128
  try {
129
129
  const url = `${FUNCTIONS_URL}/checkUsageLimit`;
130
- console.log('[License Helper] FUNCTIONS_URL:', FUNCTIONS_URL);
131
- console.log('[License Helper] Full URL:', url);
130
+ // console.log('[License Helper] FUNCTIONS_URL:', FUNCTIONS_URL);
131
+ // console.log('[License Helper] Full URL:', url);
132
132
  const response = await fetch(url, {
133
133
  method: 'POST',
134
134
  headers: {'Content-Type': 'application/json'},
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifest_version": 3,
3
- "name": "ChromeDebug MCP Assistant FREE v2.6.7",
4
- "version": "2.6.7",
5
- "description": "ChromeDebug MCP visual element selector [FREE Edition] [Build: 2025-11-10-v2.6.7]",
3
+ "name": "ChromeDebug MCP Assistant FREE",
4
+ "version": "2.7.2",
5
+ "description": "AI-powered browser debugging with visual element selection and screen recording for developers",
6
6
  "permissions": [
7
7
  "activeTab",
8
8
  "scripting",
@@ -36,12 +36,12 @@ class NetworkTracker {
36
36
  if (options.maxBodySize) this.maxBodySize = options.maxBodySize;
37
37
  if (options.bufferFlushDelay) this.bufferFlushDelay = options.bufferFlushDelay;
38
38
 
39
- console.log('[NetworkTracker] Initialized with options:', {
40
- recordingId: this.recordingId,
41
- captureRequestBody: this.captureRequestBody,
42
- captureResponseBody: this.captureResponseBody,
43
- maxBodySize: this.maxBodySize
44
- });
39
+ // console.log('[NetworkTracker] Initialized with options:', {
40
+ // recordingId: this.recordingId,
41
+ // captureRequestBody: this.captureRequestBody,
42
+ // captureResponseBody: this.captureResponseBody,
43
+ // maxBodySize: this.maxBodySize
44
+ // });
45
45
  }
46
46
 
47
47
  startTracking() {
@@ -57,7 +57,7 @@ class NetworkTracker {
57
57
  this.isTracking = true;
58
58
  this.startBufferFlusher();
59
59
 
60
- console.log('[NetworkTracker] Started network request tracking');
60
+ // console.log('[NetworkTracker] Started network request tracking');
61
61
 
62
62
  // Record performance event
63
63
  if (this.performanceMonitor) {
@@ -81,7 +81,7 @@ class NetworkTracker {
81
81
  this.flushBuffer(); // Flush any remaining data
82
82
 
83
83
  this.isTracking = false;
84
- console.log('[NetworkTracker] Stopped network request tracking');
84
+ // console.log('[NetworkTracker] Stopped network request tracking');
85
85
 
86
86
  // Record performance event
87
87
  if (this.performanceMonitor) {
@@ -472,7 +472,7 @@ class NetworkTracker {
472
472
  const events = [...this.eventBuffer];
473
473
  this.eventBuffer = [];
474
474
 
475
- console.log(`[NetworkTracker] Flushing ${events.length} network requests`);
475
+ // console.log(`[NetworkTracker] Flushing ${events.length} network requests`);
476
476
 
477
477
  // Send data via callback
478
478
  if (this.onDataReady) {
@@ -152,6 +152,16 @@
152
152
  Leave empty for automatic port discovery. The server typically runs on port 3000.
153
153
  </div>
154
154
  </div>
155
+
156
+ <div class="setting-group">
157
+ <h3>Recording Safety</h3>
158
+ <label for="inactivity-timeout">Auto-Stop After Inactivity (seconds)</label>
159
+ <input type="number" id="inactivity-timeout" min="5" max="3600" value="60">
160
+ <div class="help-text">
161
+ Screen recording will automatically stop if no mouse or keyboard activity is detected.
162
+ Default: 60 seconds. Adjust for long demo recordings or presentations.
163
+ </div>
164
+ </div>
155
165
 
156
166
  <div class="setting-group">
157
167
  <label>Server Status</label>
@@ -4,10 +4,13 @@
4
4
  const DEFAULT_PORTS = CHROMEDEBUG_CONFIG?.ports?.slice(0, 8) || [3001, 3000, 3002, 3028];
5
5
 
6
6
  // Load saved settings
7
- chrome.storage.sync.get(['serverPort', 'chromePilotMode', 'chromePilotAllowedSites', 'chromePilotRestrictedSites'], (data) => {
7
+ chrome.storage.sync.get(['serverPort', 'chromePilotMode', 'chromePilotAllowedSites', 'chromePilotRestrictedSites', 'inactivityTimeout'], (data) => {
8
8
  if (data.serverPort) {
9
9
  document.getElementById('server-port').value = data.serverPort;
10
10
  }
11
+
12
+ // Load inactivity timeout (default 60 seconds)
13
+ document.getElementById('inactivity-timeout').value = data.inactivityTimeout || 60;
11
14
 
12
15
  // Load site restriction settings
13
16
  const mode = data.chromePilotMode || 'whitelist';
@@ -32,7 +35,15 @@ document.getElementById('save').addEventListener('click', async () => {
32
35
  showStatus('error', 'Port must be between 1024 and 65535');
33
36
  return;
34
37
  }
35
-
38
+
39
+ // Get inactivity timeout
40
+ const inactivityTimeoutInput = document.getElementById('inactivity-timeout');
41
+ const inactivityTimeout = parseInt(inactivityTimeoutInput.value) || 60;
42
+ if (inactivityTimeout < 5 || inactivityTimeout > 3600) {
43
+ showStatus('error', 'Inactivity timeout must be between 5 and 3600 seconds');
44
+ return;
45
+ }
46
+
36
47
  // Get site restriction settings
37
48
  const mode = document.querySelector('input[name="site-mode"]:checked').value;
38
49
  const allowedSites = document.getElementById('allowed-sites').value
@@ -44,24 +55,26 @@ document.getElementById('save').addEventListener('click', async () => {
44
55
  .map(s => s.trim())
45
56
  .filter(s => s.length > 0);
46
57
 
47
- await chrome.storage.sync.set({
58
+ await chrome.storage.sync.set({
48
59
  serverPort: port,
49
60
  chromePilotMode: mode,
50
61
  chromePilotAllowedSites: allowedSites,
51
- chromePilotRestrictedSites: restrictedSites
62
+ chromePilotRestrictedSites: restrictedSites,
63
+ inactivityTimeout: inactivityTimeout
52
64
  });
53
- showStatus('success', 'Settings saved successfully. Reload tabs to apply site restrictions.');
65
+ showStatus('success', 'Settings saved successfully. Reload tabs to apply changes.');
54
66
  });
55
67
 
56
68
  // Reset to default
57
69
  document.getElementById('reset').addEventListener('click', async () => {
58
70
  document.getElementById('server-port').value = '';
71
+ document.getElementById('inactivity-timeout').value = '60';
59
72
  document.querySelector('input[value="whitelist"]').checked = true;
60
73
  document.getElementById('allowed-sites').value = 'localhost:*\n127.0.0.1:*\n*.local\n*.test\n*.dev';
61
74
  document.getElementById('blocked-sites').value = 'youtube.com\n*.youtube.com\ngoogle.com\n*.google.com\nfacebook.com\ntwitter.com\nx.com';
62
75
  updateSiteMode();
63
-
64
- await chrome.storage.sync.remove(['serverPort', 'chromePilotMode', 'chromePilotAllowedSites', 'chromePilotRestrictedSites']);
76
+
77
+ await chrome.storage.sync.remove(['serverPort', 'chromePilotMode', 'chromePilotAllowedSites', 'chromePilotRestrictedSites', 'inactivityTimeout']);
65
78
  showStatus('info', 'Settings reset to default');
66
79
  });
67
80