dashcam 1.0.1-beta.2 → 1.0.1-beta.21

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/bin/dashcam.js CHANGED
@@ -26,7 +26,7 @@ if (!fs.existsSync(APP.recordingsDir)) {
26
26
 
27
27
  program
28
28
  .name('dashcam')
29
- .description('CLI version of Dashcam screen recorder')
29
+ .description('Capture the steps to reproduce every bug.')
30
30
  .version(APP.version)
31
31
  .option('-v, --verbose', 'Enable verbose logging output')
32
32
  .hook('preAction', (thisCommand) => {
@@ -39,8 +39,8 @@ program
39
39
 
40
40
  program
41
41
  .command('auth')
42
- .description('Authenticate with TestDriver using an API key')
43
- .argument('<apiKey>', 'Your TestDriver API key')
42
+ .description("Authenticate the dashcam desktop using a team's apiKey")
43
+ .argument('<api-key>', 'Your team API key')
44
44
  .action(async (apiKey, options, command) => {
45
45
  try {
46
46
  logger.verbose('Starting authentication process', {
@@ -75,121 +75,199 @@ program
75
75
  }
76
76
  });
77
77
 
78
+ // Shared recording action to avoid duplication
79
+ async function recordingAction(options, command) {
80
+ try {
81
+ const silent = options.silent;
82
+ const log = (...args) => { if (!silent) console.log(...args); };
83
+ const logError = (...args) => { if (!silent) console.error(...args); };
84
+
85
+ // Check if recording is already active
86
+ if (processManager.isRecordingActive()) {
87
+ const status = processManager.getActiveStatus();
88
+ const duration = ((Date.now() - status.startTime) / 1000).toFixed(1);
89
+ log('Recording already in progress');
90
+ log(`Duration: ${duration} seconds`);
91
+ log(`PID: ${status.pid}`);
92
+ log('Use "dashcam stop" to stop the recording');
93
+ process.exit(0);
94
+ }
95
+
96
+ // Check authentication
97
+ if (!await auth.isAuthenticated()) {
98
+ log('You need to login first. Run: dashcam auth <api-key>');
99
+ process.exit(1);
100
+ }
101
+
102
+ // Check for piped input (description from stdin) if description option not set
103
+ let description = options.description;
104
+ if (!description && !process.stdin.isTTY) {
105
+ const chunks = [];
106
+ for await (const chunk of process.stdin) {
107
+ chunks.push(chunk);
108
+ }
109
+ description = Buffer.concat(chunks).toString('utf-8');
110
+ }
111
+
112
+ // Check screen recording permissions (macOS only)
113
+ const { ensurePermissions } = await import('../lib/permissions.js');
114
+ const hasPermissions = await ensurePermissions();
115
+ if (!hasPermissions) {
116
+ log('\n⚠️ Cannot start recording without screen recording permission.');
117
+ process.exit(1);
118
+ }
119
+
120
+ // Start recording in background mode
121
+ log('Starting recording in background...');
122
+
123
+ try {
124
+ const result = await processManager.startRecording({
125
+ fps: parseInt(options.fps) || 30,
126
+ audio: options.audio,
127
+ output: options.output,
128
+ title: options.title,
129
+ description: description,
130
+ project: options.project || options.k // Support both -p and -k for project
131
+ });
132
+
133
+ log(`✅ Recording started successfully (PID: ${result.pid})`);
134
+ log(`Output: ${result.outputPath}`);
135
+ log('');
136
+ log('Use "dashcam status" to check progress');
137
+ log('Use "dashcam stop" to stop recording and upload');
138
+
139
+ process.exit(0);
140
+ } catch (error) {
141
+ logError('Failed to start recording:', error.message);
142
+ process.exit(1);
143
+ }
144
+ } catch (error) {
145
+ logger.error('Failed to start recording:', error);
146
+ if (!options.silent) console.error('Failed to start recording:', error.message);
147
+ process.exit(1);
148
+ }
149
+ }
150
+
151
+ // 'create' command - creates a clip from current recording (like stop but with more options)
78
152
  program
79
- .command('record')
80
- .description('Start a background screen recording')
81
- .option('-a, --audio', 'Include audio in the recording')
82
- .option('-f, --fps <fps>', 'Frames per second (default: 30)', '30')
83
- .option('-o, --output <path>', 'Custom output path')
84
- .option('-t, --title <title>', 'Title for the recording')
85
- .option('-d, --description <description>', 'Description for the recording (supports markdown)')
86
- .option('-p, --project <project>', 'Project ID to upload the recording to')
87
- .action(async (options, command) => {
153
+ .command('create')
154
+ .description('Create a clip and output the resulting url or markdown. Will launch desktop app for local editing before publishing.')
155
+ .option('-t, --title <string>', 'Title of the replay. Automatically generated if not supplied.')
156
+ .option('-d, --description [text]', 'Replay markdown body. This may also be piped in: `cat README.md | dashcam create`')
157
+ .option('--md', 'Returns code for a rich markdown image link.')
158
+ .option('-k, --project <project>', 'Project ID to publish to')
159
+ .action(async (options) => {
88
160
  try {
89
- // Check if recording is already active
90
- if (processManager.isRecordingActive()) {
91
- const status = processManager.getActiveStatus();
92
- const duration = ((Date.now() - status.startTime) / 1000).toFixed(1);
93
- console.log('Recording already in progress');
94
- console.log(`Duration: ${duration} seconds`);
95
- console.log(`PID: ${status.pid}`);
96
- console.log('Use "dashcam stop" to stop the recording');
97
- process.exit(0);
161
+ // Check for piped input (description from stdin)
162
+ let description = options.description;
163
+ if (!description && !process.stdin.isTTY) {
164
+ const chunks = [];
165
+ for await (const chunk of process.stdin) {
166
+ chunks.push(chunk);
167
+ }
168
+ description = Buffer.concat(chunks).toString('utf-8');
98
169
  }
99
170
 
100
- // Check authentication
101
- if (!await auth.isAuthenticated()) {
102
- console.log('You need to login first. Run: dashcam auth <api-key>');
103
- process.exit(1);
171
+ if (!processManager.isRecordingActive()) {
172
+ console.log('No active recording to create clip from');
173
+ console.log('Start a recording first with "dashcam record" or "dashcam start"');
174
+ process.exit(0);
104
175
  }
105
176
 
106
- // Check screen recording permissions (macOS only)
107
- const { ensurePermissions } = await import('../lib/permissions.js');
108
- const hasPermissions = await ensurePermissions();
109
- if (!hasPermissions) {
110
- console.log('\n⚠️ Cannot start recording without screen recording permission.');
177
+ const activeStatus = processManager.getActiveStatus();
178
+
179
+ console.log('Creating clip from recording...');
180
+
181
+ const result = await processManager.stopActiveRecording();
182
+
183
+ if (!result) {
184
+ console.log('Failed to stop recording');
111
185
  process.exit(1);
112
186
  }
113
187
 
114
- // Always use background mode
115
- console.log('Starting recording...');
188
+ console.log('Recording stopped successfully');
116
189
 
190
+ // Upload the recording
191
+ console.log('Uploading clip...');
117
192
  try {
118
- const result = await processManager.startRecording({
119
- fps: parseInt(options.fps) || 30,
120
- audio: options.audio,
121
- output: options.output,
122
- title: options.title,
123
- description: options.description,
124
- project: options.project
193
+ const uploadResult = await upload(result.outputPath, {
194
+ title: options.title || activeStatus?.options?.title || 'Dashcam Recording',
195
+ description: description || activeStatus?.options?.description,
196
+ project: options.project || options.k || activeStatus?.options?.project,
197
+ duration: result.duration,
198
+ clientStartDate: result.clientStartDate,
199
+ apps: result.apps,
200
+ icons: result.icons,
201
+ gifPath: result.gifPath,
202
+ snapshotPath: result.snapshotPath
125
203
  });
126
-
127
- console.log(`Recording started successfully (PID: ${result.pid})`);
128
- console.log(`Output: ${result.outputPath}`);
129
- console.log('Use "dashcam status" to check progress');
130
- console.log('Use "dashcam stop" to stop recording and upload');
131
-
132
- // Keep this process alive for background recording
133
- console.log('Recording is running in background...');
134
-
135
- // Set up signal handlers for graceful shutdown
136
- let isShuttingDown = false;
137
- const handleShutdown = async (signal) => {
138
- if (isShuttingDown) {
139
- console.log('Shutdown already in progress...');
140
- return;
141
- }
142
- isShuttingDown = true;
143
-
144
- console.log(`\nReceived ${signal}, stopping background recording...`);
145
- try {
146
- // Stop the recording using the recorder directly (not processManager)
147
- const { stopRecording } = await import('../lib/recorder.js');
148
- const stopResult = await stopRecording();
149
-
150
- if (stopResult) {
151
- console.log('Recording stopped:', stopResult.outputPath);
152
-
153
- // Import and call upload function with the correct format
154
- const { upload } = await import('../lib/uploader.js');
155
-
156
- console.log('Starting upload...');
157
- await upload(stopResult.outputPath, {
158
- title: options.title || 'Dashcam Recording',
159
- description: options.description || 'Recorded with Dashcam CLI',
160
- project: options.project,
161
- duration: stopResult.duration,
162
- clientStartDate: stopResult.clientStartDate,
163
- apps: stopResult.apps,
164
- logs: stopResult.logs,
165
- gifPath: stopResult.gifPath,
166
- snapshotPath: stopResult.snapshotPath
167
- });
168
-
169
- console.log('Upload completed successfully!');
170
- }
171
-
172
- // Clean up process files
173
- processManager.cleanup();
174
- } catch (error) {
175
- console.error('Error during shutdown:', error.message);
176
- logger.error('Error during shutdown:', error);
177
- }
178
- process.exit(0);
179
- };
180
-
181
- process.on('SIGINT', () => handleShutdown('SIGINT'));
182
- process.on('SIGTERM', () => handleShutdown('SIGTERM'));
183
204
 
184
- // Keep the process alive
185
- await new Promise(() => {});
186
- } catch (error) {
187
- console.error('Failed to start recording:', error.message);
205
+ // Output based on format option
206
+ if (options.md) {
207
+ const replayId = uploadResult.replay?.id;
208
+ const shareKey = uploadResult.shareLink.split('share=')[1];
209
+ console.log(`[![Dashcam - ${options.title || 'New Replay'}](https://replayable-api-production.herokuapp.com/replay/${replayId}/gif?shareKey=${shareKey})](${uploadResult.shareLink})`);
210
+ console.log('');
211
+ console.log(`Watch [Dashcam - ${options.title || 'New Replay'}](${uploadResult.shareLink}) on Dashcam`);
212
+ } else {
213
+ console.log(uploadResult.shareLink);
214
+ }
215
+ } catch (uploadError) {
216
+ console.error('Upload failed:', uploadError.message);
217
+ console.log('Recording saved locally:', result.outputPath);
218
+ }
219
+
220
+ process.exit(0);
221
+ } catch (error) {
222
+ logger.error('Error creating clip:', error);
223
+ console.error('Failed to create clip:', error.message);
224
+ process.exit(1);
225
+ }
226
+ });
227
+
228
+ // 'record' command - the main recording command with all options
229
+ program
230
+ .command('record')
231
+ .description('Start a recording terminal to be included in your dashcam video recording')
232
+ .option('-a, --audio', 'Include audio in the recording')
233
+ .option('-f, --fps <fps>', 'Frames per second (default: 30)', '30')
234
+ .option('-o, --output <path>', 'Custom output path')
235
+ .option('-t, --title <title>', 'Title for the recording')
236
+ .option('-d, --description <description>', 'Description for the recording (supports markdown)')
237
+ .option('-p, --project <project>', 'Project ID to upload the recording to')
238
+ .option('-s, --silent', 'Silent mode - suppress all output')
239
+ .action(recordingAction);
240
+
241
+ program
242
+ .command('pipe')
243
+ .description('Pipe command output to dashcam to be included in recorded video')
244
+ .action(async () => {
245
+ try {
246
+ // Check if recording is active
247
+ if (!processManager.isRecordingActive()) {
248
+ console.error('No active recording. Start a recording first with "dashcam record" or "dashcam start"');
188
249
  process.exit(1);
189
250
  }
251
+
252
+ // Read from stdin
253
+ const chunks = [];
254
+ for await (const chunk of process.stdin) {
255
+ chunks.push(chunk);
256
+ // Also output to stdout so pipe continues to work
257
+ process.stdout.write(chunk);
258
+ }
259
+ const content = Buffer.concat(chunks).toString('utf-8');
260
+
261
+ // Import the log tracker to add the piped content
262
+ const { logsTrackerManager } = await import('../lib/logs/index.js');
263
+
264
+ // Add piped content as a log entry
265
+ logsTrackerManager.addPipedLog(content);
266
+
267
+ process.exit(0);
190
268
  } catch (error) {
191
- logger.error('Failed to start recording:', error);
192
- console.error('Failed to start recording:', error.message);
269
+ logger.error('Failed to pipe content:', error);
270
+ console.error('Failed to pipe content:', error.message);
193
271
  process.exit(1);
194
272
  }
195
273
  });
@@ -216,43 +294,94 @@ program
216
294
 
217
295
 
218
296
 
297
+ // 'start' command - alias for record with simple instant replay mode
298
+ program
299
+ .command('start')
300
+ .description('Start instant replay recording on dashcam')
301
+ .action(async () => {
302
+ // Call recordingAction with minimal options for instant replay
303
+ await recordingAction({
304
+ fps: '30',
305
+ audio: false,
306
+ silent: false
307
+ }, null);
308
+ });
309
+
219
310
  program
220
311
  .command('track')
221
- .description('Track logs from web URLs or application files')
222
- .option('--web <pattern>', 'Web URL pattern to track (can use wildcards like *)')
223
- .option('--app <pattern>', 'Application file pattern to track (can use wildcards like *)')
224
- .option('--name <name>', 'Name for the tracking configuration')
312
+ .description('Add a logs config to Dashcam')
313
+ .option('--name <name>', 'Name for the tracking configuration (required)')
314
+ .option('--type <type>', 'Type of tracker: "application" or "web" (required)')
315
+ .option('--pattern <pattern>', 'Pattern to track (can be used multiple times)', (value, previous) => {
316
+ return previous ? previous.concat([value]) : [value];
317
+ })
318
+ .option('--web <pattern>', 'Web URL pattern to track (can use wildcards like *) - deprecated, use --type=web --pattern instead')
319
+ .option('--app <pattern>', 'Application file pattern to track (can use wildcards like *) - deprecated, use --type=application --pattern instead')
225
320
  .action(async (options) => {
226
321
  try {
227
- // Validate that at least one pattern is provided
228
- if (!options.web && !options.app) {
229
- console.error('Error: Must provide either --web or --app pattern');
230
- process.exit(1);
231
- }
232
-
233
- if (options.web) {
234
- const config = {
235
- name: options.name || 'Web Pattern',
236
- type: 'web',
237
- patterns: [options.web],
238
- enabled: true
239
- };
322
+ // Support both old and new syntax
323
+ // New syntax: --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"
324
+ // Old syntax: --web <pattern> --app <pattern>
325
+
326
+ if (options.type && options.pattern) {
327
+ // New syntax validation
328
+ if (!options.name) {
329
+ console.error('Error: --name is required when using --type and --pattern');
330
+ console.log('Example: dashcam track --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
331
+ process.exit(1);
332
+ }
333
+
334
+ if (options.type !== 'web' && options.type !== 'application') {
335
+ console.error('Error: --type must be either "web" or "application"');
336
+ process.exit(1);
337
+ }
240
338
 
241
- await createPattern(config);
242
- console.log('Web tracking pattern added successfully:', options.web);
243
- }
244
-
245
- if (options.app) {
246
339
  const config = {
247
- name: options.name || 'App Pattern',
248
- type: 'application',
249
- patterns: [options.app],
340
+ name: options.name,
341
+ type: options.type,
342
+ patterns: options.pattern,
250
343
  enabled: true
251
344
  };
252
345
 
253
346
  await createPattern(config);
254
- console.log('Application tracking pattern added successfully:', options.app);
347
+ console.log(`${options.type === 'web' ? 'Web' : 'Application'} tracking pattern added successfully:`, options.name);
348
+ console.log('Patterns:', options.pattern.join(', '));
349
+
350
+ } else if (options.web || options.app) {
351
+ // Old syntax for backward compatibility
352
+ if (options.web) {
353
+ const config = {
354
+ name: options.name || 'Web Pattern',
355
+ type: 'web',
356
+ patterns: [options.web],
357
+ enabled: true
358
+ };
359
+
360
+ await createPattern(config);
361
+ console.log('Web tracking pattern added successfully:', options.web);
362
+ }
363
+
364
+ if (options.app) {
365
+ const config = {
366
+ name: options.name || 'App Pattern',
367
+ type: 'application',
368
+ patterns: [options.app],
369
+ enabled: true
370
+ };
371
+
372
+ await createPattern(config);
373
+ console.log('Application tracking pattern added successfully:', options.app);
374
+ }
375
+ } else {
376
+ console.error('Error: Must provide either:');
377
+ console.log(' --name --type --pattern (new syntax)');
378
+ console.log(' --web or --app (old syntax)');
379
+ console.log('\nExamples:');
380
+ console.log(' dashcam track --name=social --type=web --pattern="*facebook.com*" --pattern="*twitter.com*"');
381
+ console.log(' dashcam track --web "*facebook.com*"');
382
+ process.exit(1);
255
383
  }
384
+
256
385
  process.exit(0);
257
386
  } catch (error) {
258
387
  console.error('Failed to add tracking pattern:', error.message);
@@ -287,7 +416,6 @@ program
287
416
  }
288
417
 
289
418
  console.log('Recording stopped successfully');
290
- console.log('Output saved to:', result.outputPath);
291
419
 
292
420
  // Check if files still exist - if not, background process already uploaded
293
421
  const filesExist = fs.existsSync(result.outputPath) &&
@@ -295,8 +423,24 @@ program
295
423
  (!result.snapshotPath || fs.existsSync(result.snapshotPath));
296
424
 
297
425
  if (!filesExist) {
298
- console.log('✅ Recording was already uploaded by background process');
299
- console.log('✅ Recording stopped and uploaded');
426
+ // Files were deleted, meaning background process uploaded
427
+ // Wait for the upload result to be written
428
+ logger.debug('Waiting for upload result from background process');
429
+ await new Promise(resolve => setTimeout(resolve, 2000));
430
+
431
+ // Try to read the upload result from the background process
432
+ const uploadResult = processManager.readUploadResult();
433
+ logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
434
+
435
+ if (uploadResult && uploadResult.shareLink) {
436
+ console.log('📹 Watch your recording:', uploadResult.shareLink);
437
+ // Clean up the result file now that we've read it
438
+ processManager.cleanup();
439
+ } else {
440
+ console.log('✅ Recording uploaded (share link not available)');
441
+ logger.warn('Upload result not available from background process');
442
+ }
443
+
300
444
  process.exit(0);
301
445
  }
302
446
 
@@ -315,7 +459,7 @@ program
315
459
  snapshotPath: result.snapshotPath
316
460
  });
317
461
 
318
- console.log(' Upload complete! Share link:', uploadResult.shareLink);
462
+ console.log('📹 Watch your recording:', uploadResult.shareLink);
319
463
  } catch (uploadError) {
320
464
  console.error('Upload failed:', uploadError.message);
321
465
  console.log('Recording saved locally:', result.outputPath);
package/lib/ffmpeg.js CHANGED
@@ -18,8 +18,7 @@ export async function createSnapshot(inputVideoPath, outputSnapshotPath, snapsho
18
18
  '-i', inputVideoPath,
19
19
  '-frames:v', '1',
20
20
  '-vf', 'scale=640:-1:force_original_aspect_ratio=decrease:eval=frame',
21
- '-pred', 'mixed',
22
- '-compression_level', '100',
21
+ '-compression_level', '6', // Use default compression (was 100, which is extremely slow)
23
22
  outputSnapshotPath,
24
23
  '-y',
25
24
  '-hide_banner'
package/lib/logs/index.js CHANGED
@@ -15,7 +15,13 @@ const CLI_CONFIG_FILE = path.join(process.cwd(), '.dashcam', 'cli-config.json');
15
15
 
16
16
  // Simple trim function for CLI (adapted from desktop app)
17
17
  async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipId) {
18
- logger.info('Trimming logs', { count: groupLogStatuses.length });
18
+ logger.info('Trimming logs', {
19
+ count: groupLogStatuses.length,
20
+ startMS,
21
+ endMS,
22
+ clientStartDate,
23
+ clientStartDateReadable: new Date(clientStartDate).toISOString()
24
+ });
19
25
 
20
26
  const REPLAY_DIR = path.join(os.tmpdir(), 'dashcam', 'recordings');
21
27
 
@@ -36,12 +42,24 @@ async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipI
36
42
  let events = content;
37
43
 
38
44
  // Convert events to relative time
39
- let relativeEvents = events.map((event) => {
45
+ let relativeEvents = events.map((event, index) => {
46
+ const originalTime = event.time;
40
47
  event.time = parseInt(event.time + '') - startMS;
41
48
  // Check if it's not already relative time
42
49
  if (event.time > 1_000_000_000_000) {
43
50
  // relative time = absolute time - clip start time
44
51
  event.time = event.time - clientStartDate;
52
+ if (index === 0) {
53
+ // Log first event for debugging
54
+ logger.info('First event timestamp conversion', {
55
+ originalTime,
56
+ clientStartDate,
57
+ relativeTime: event.time,
58
+ relativeSeconds: (event.time / 1000).toFixed(2),
59
+ startMS,
60
+ line: event.line ? event.line.substring(0, 50) : 'N/A'
61
+ });
62
+ }
45
63
  }
46
64
  return event;
47
65
  });
@@ -56,16 +74,19 @@ async function trimLogs(groupLogStatuses, startMS, endMS, clientStartDate, clipI
56
74
  });
57
75
 
58
76
  if (status.type === 'cli') {
59
- // Remap logFile indices for CLI logs
60
- let map = {};
61
- filteredEvents = filteredEvents.map((event) => {
62
- let name = map[event.logFile] ?? Object.keys(map).length + 1;
63
- if (!map[event.logFile]) map[event.logFile] = name;
64
- return {
65
- ...event,
66
- logFile: name,
67
- };
77
+ // For CLI logs, keep the file paths in logFile field for UI display
78
+ // No need to remap to numeric indices
79
+
80
+ // Create items array showing unique files and their event counts
81
+ const fileCounts = {};
82
+ filteredEvents.forEach(event => {
83
+ fileCounts[event.logFile] = (fileCounts[event.logFile] || 0) + 1;
68
84
  });
85
+
86
+ status.items = Object.entries(fileCounts).map(([filePath, count]) => ({
87
+ item: filePath,
88
+ count
89
+ }));
69
90
  }
70
91
  } else if (status.type === 'web' && !webHandled) {
71
92
  logger.debug('Found web groupLog, handling all web groupLogs at once');
@@ -243,6 +264,41 @@ class LogsTrackerManager {
243
264
  this.removeCliTrackedPath(filePath);
244
265
  }
245
266
 
267
+ addPipedLog(content) {
268
+ // Find active recording instance
269
+ const activeInstances = Object.values(this.instances);
270
+ if (activeInstances.length === 0) {
271
+ logger.warn('No active recording to add piped content to');
272
+ return;
273
+ }
274
+
275
+ // Add to the most recent active instance (last one)
276
+ const instance = activeInstances[activeInstances.length - 1];
277
+ const timestamp = Date.now();
278
+
279
+ // Write to CLI tracker's temp file
280
+ if (instance.trackers && instance.trackers.cli) {
281
+ const pipedLog = {
282
+ time: timestamp,
283
+ logFile: 'piped-input',
284
+ content: content
285
+ };
286
+
287
+ // Write to the CLI tracker's output file
288
+ try {
289
+ const outputFile = path.join(instance.directory, 'cli-logs.jsonl');
290
+ const logLine = JSON.stringify(pipedLog) + '\n';
291
+ fs.appendFileSync(outputFile, logLine);
292
+ logger.info('Added piped content to recording', {
293
+ recorderId: instance.recorderId,
294
+ contentLength: content.length
295
+ });
296
+ } catch (error) {
297
+ logger.error('Failed to write piped content', { error });
298
+ }
299
+ }
300
+ }
301
+
246
302
  removeTracker(id) {
247
303
  // Try removing from web trackers first
248
304
  if (this.webLogsConfig[id]) {