dashcam 1.0.1-beta.22 โ†’ 1.0.1-beta.24

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
@@ -417,30 +417,29 @@ program
417
417
 
418
418
  console.log('Recording stopped successfully');
419
419
 
420
+ // Wait a moment for upload to complete (background process handles this)
421
+ logger.debug('Waiting for background upload to complete...');
422
+ await new Promise(resolve => setTimeout(resolve, 5000));
423
+
424
+ // Try to read the upload result from the background process
425
+ const uploadResult = processManager.readUploadResult();
426
+ logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
427
+
428
+ if (uploadResult && uploadResult.shareLink) {
429
+ console.log('๐Ÿ“น Watch your recording:', uploadResult.shareLink);
430
+ // Clean up the result file now that we've read it
431
+ processManager.cleanup();
432
+ process.exit(0);
433
+ }
434
+
420
435
  // Check if files still exist - if not, background process already uploaded
421
436
  const filesExist = fs.existsSync(result.outputPath) &&
422
437
  (!result.gifPath || fs.existsSync(result.gifPath)) &&
423
438
  (!result.snapshotPath || fs.existsSync(result.snapshotPath));
424
439
 
425
440
  if (!filesExist) {
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
-
441
+ console.log('โœ… Recording uploaded by background process');
442
+ logger.info('Files were cleaned up by background process');
444
443
  process.exit(0);
445
444
  }
446
445
 
package/lib/auth.js CHANGED
@@ -18,7 +18,7 @@ const auth = {
18
18
  });
19
19
 
20
20
  // Exchange API key for token
21
- const { token } = await got.post('https://api.testdriver.ai/auth/exchange-api-key', {
21
+ const { token } = await got.post('https://testdriver-api.onrender.com/auth/exchange-api-key', {
22
22
  json: { apiKey },
23
23
  timeout: 30000 // 30 second timeout
24
24
  }).json();
@@ -34,7 +34,7 @@ const auth = {
34
34
 
35
35
  // Get user info to verify the token works
36
36
  logger.debug('Fetching user information to validate token...');
37
- const user = await got.get('https://api.testdriver.ai/api/v1/whoami', {
37
+ const user = await got.get('https://testdriver-api.onrender.com/api/v1/whoami', {
38
38
  headers: {
39
39
  Authorization: `Bearer ${token}`
40
40
  },
@@ -105,7 +105,7 @@ const auth = {
105
105
  const token = await this.getToken();
106
106
 
107
107
  try {
108
- const response = await got.get('https://api.testdriver.ai/api/v1/projects', {
108
+ const response = await got.get('https://testdriver-api.onrender.com/api/v1/projects', {
109
109
  headers: {
110
110
  Authorization: `Bearer ${token}`
111
111
  },
@@ -160,7 +160,7 @@ const auth = {
160
160
  requestBody.project = replayData.project;
161
161
  }
162
162
 
163
- const response = await got.post('https://api.testdriver.ai/api/v1/replay/upload', {
163
+ const response = await got.post('https://testdriver-api.onrender.com/api/v1/replay/upload', {
164
164
  headers: {
165
165
  Authorization: `Bearer ${token}`
166
166
  },
@@ -188,7 +188,7 @@ const auth = {
188
188
  const token = await this.getToken();
189
189
 
190
190
  try {
191
- const response = await got.post('https://api.testdriver.ai/api/v1/logs', {
191
+ const response = await got.post('https://testdriver-api.onrender.com/api/v1/logs', {
192
192
  headers: {
193
193
  Authorization: `Bearer ${token}`
194
194
  },
package/lib/config.js CHANGED
@@ -17,7 +17,7 @@ export const auth0Config = {
17
17
  export const apiEndpoints = {
18
18
  development: process.env.API_ENDPOINT || 'http://localhost:3000',
19
19
  staging: 'https://replayable-api-staging.herokuapp.com',
20
- production: 'https://api.testdriver.ai'
20
+ production: 'https://testdriver-api.onrender.com'
21
21
  };
22
22
 
23
23
  export const API_ENDPOINT = apiEndpoints[ENV];
package/lib/recorder.js CHANGED
@@ -531,20 +531,20 @@ export async function stopRecording() {
531
531
  // Wait for FFmpeg to finish gracefully with realtime encoding
532
532
  const gracefulTimeout = setTimeout(() => {
533
533
  if (currentRecording && !currentRecording.killed) {
534
- logger.warn('FFmpeg did not exit gracefully after 5s, sending SIGTERM...');
534
+ logger.warn('FFmpeg did not exit gracefully after 10s, sending SIGTERM...');
535
535
  // If still running, try SIGTERM
536
536
  process.kill(currentRecording.pid, 'SIGTERM');
537
537
  }
538
- }, 5000); // Faster with realtime encoding
538
+ }, 10000); // Wait longer for graceful shutdown
539
539
 
540
- // Wait up to 10 seconds for SIGTERM to work
540
+ // Wait up to 20 seconds for SIGTERM to work
541
541
  const hardKillTimeout = setTimeout(() => {
542
542
  if (currentRecording && !currentRecording.killed) {
543
543
  logger.error('FFmpeg still running after SIGTERM, using SIGKILL...');
544
544
  // If still not dead, use SIGKILL as last resort
545
545
  process.kill(currentRecording.pid, 'SIGKILL');
546
546
  }
547
- }, 10000); // Faster timeout
547
+ }, 20000); // Longer timeout to avoid killing prematurely
548
548
 
549
549
  // Wait for the process to fully exit
550
550
  if (currentRecording) {
@@ -555,10 +555,30 @@ export async function stopRecording() {
555
555
  clearTimeout(gracefulTimeout);
556
556
  clearTimeout(hardKillTimeout);
557
557
 
558
- // Additional wait to ensure filesystem is synced
559
- // Realtime encoding has smaller buffers so this can be shorter
560
- logger.debug('Waiting for filesystem sync...');
561
- await new Promise(resolve => setTimeout(resolve, 1000)); // Reduced from 3s with realtime encoding
558
+ // Wait for filesystem to sync and file to be written
559
+ // Poll for the file to exist with content
560
+ logger.debug('Waiting for temp file to be written...');
561
+ const maxWaitTime = 10000; // Wait up to 10 seconds
562
+ const startWait = Date.now();
563
+ let tempFileReady = false;
564
+
565
+ while (!tempFileReady && (Date.now() - startWait) < maxWaitTime) {
566
+ const tempFile = currentTempFile;
567
+ if (tempFile && fs.existsSync(tempFile)) {
568
+ const stats = fs.statSync(tempFile);
569
+ if (stats.size > 0) {
570
+ logger.debug('Temp file is ready', { size: stats.size });
571
+ tempFileReady = true;
572
+ break;
573
+ }
574
+ }
575
+ // Wait a bit before checking again
576
+ await new Promise(resolve => setTimeout(resolve, 500));
577
+ }
578
+
579
+ if (!tempFileReady) {
580
+ logger.warn('Temp file not ready after waiting, proceeding anyway');
581
+ }
562
582
 
563
583
  // Read temp file path from disk (for cross-process access)
564
584
  let tempFile = currentTempFile; // Try in-memory first
@@ -47,7 +47,7 @@ export class FileTracker {
47
47
  fs.writeFileSync(this.trackedFile, '', 'utf8');
48
48
  }
49
49
 
50
- this.tail = new Tail(this.trackedFile, { encoding: 'ascii' });
50
+ this.tail = new Tail(this.trackedFile, { encoding: 'utf8' });
51
51
  this.tail.on('line', (line) => {
52
52
  const time = Date.now();
53
53
  this.eventTimes.push(time);
@@ -47,6 +47,63 @@ const findLinuxIcon = async (appName) => {
47
47
  return null;
48
48
  };
49
49
 
50
+ /**
51
+ * Calculate string similarity (Levenshtein distance based)
52
+ * Returns a value between 0 (no match) and 1 (perfect match)
53
+ */
54
+ const calculateSimilarity = (str1, str2) => {
55
+ const s1 = str1.toLowerCase();
56
+ const s2 = str2.toLowerCase();
57
+
58
+ // Exact match
59
+ if (s1 === s2) return 1.0;
60
+
61
+ // Check if one contains the other
62
+ if (s1.includes(s2) || s2.includes(s1)) {
63
+ return 0.8;
64
+ }
65
+
66
+ // Simple Levenshtein-based similarity
67
+ const longer = s1.length > s2.length ? s1 : s2;
68
+ const shorter = s1.length > s2.length ? s2 : s1;
69
+
70
+ if (longer.length === 0) return 1.0;
71
+
72
+ const editDistance = levenshteinDistance(s1, s2);
73
+ return (longer.length - editDistance) / longer.length;
74
+ };
75
+
76
+ /**
77
+ * Calculate Levenshtein distance between two strings
78
+ */
79
+ const levenshteinDistance = (str1, str2) => {
80
+ const matrix = [];
81
+
82
+ for (let i = 0; i <= str2.length; i++) {
83
+ matrix[i] = [i];
84
+ }
85
+
86
+ for (let j = 0; j <= str1.length; j++) {
87
+ matrix[0][j] = j;
88
+ }
89
+
90
+ for (let i = 1; i <= str2.length; i++) {
91
+ for (let j = 1; j <= str1.length; j++) {
92
+ if (str2.charAt(i - 1) === str1.charAt(j - 1)) {
93
+ matrix[i][j] = matrix[i - 1][j - 1];
94
+ } else {
95
+ matrix[i][j] = Math.min(
96
+ matrix[i - 1][j - 1] + 1, // substitution
97
+ matrix[i][j - 1] + 1, // insertion
98
+ matrix[i - 1][j] + 1 // deletion
99
+ );
100
+ }
101
+ }
102
+ }
103
+
104
+ return matrix[str2.length][str1.length];
105
+ };
106
+
50
107
  /**
51
108
  * Find .desktop file for an application
52
109
  */
@@ -61,11 +118,12 @@ const findDesktopFile = async (appName) => {
61
118
  for (const dir of desktopDirs) {
62
119
  const desktopFile = path.join(dir, `${appName}.desktop`);
63
120
  if (fs.existsSync(desktopFile)) {
121
+ logger.debug("Found desktop file (exact match)", { appName, desktopFile });
64
122
  return desktopFile;
65
123
  }
66
124
  }
67
125
 
68
- // Try case-insensitive search
126
+ // Try case-insensitive exact match
69
127
  for (const dir of desktopDirs) {
70
128
  try {
71
129
  if (!fs.existsSync(dir)) continue;
@@ -75,6 +133,7 @@ const findDesktopFile = async (appName) => {
75
133
  (f) => f.toLowerCase() === `${appName.toLowerCase()}.desktop`
76
134
  );
77
135
  if (match) {
136
+ logger.debug("Found desktop file (case-insensitive)", { appName, match });
78
137
  return path.join(dir, match);
79
138
  }
80
139
  } catch (error) {
@@ -82,6 +141,40 @@ const findDesktopFile = async (appName) => {
82
141
  }
83
142
  }
84
143
 
144
+ // Try fuzzy matching - find best match based on string similarity
145
+ let bestMatch = null;
146
+ let bestScore = 0.6; // Minimum similarity threshold
147
+
148
+ for (const dir of desktopDirs) {
149
+ try {
150
+ if (!fs.existsSync(dir)) continue;
151
+
152
+ const files = fs.readdirSync(dir).filter(f => f.endsWith('.desktop'));
153
+
154
+ for (const file of files) {
155
+ const baseName = file.replace('.desktop', '');
156
+ const similarity = calculateSimilarity(appName, baseName);
157
+
158
+ if (similarity > bestScore) {
159
+ bestScore = similarity;
160
+ bestMatch = path.join(dir, file);
161
+ }
162
+ }
163
+ } catch (error) {
164
+ logger.debug("Error in fuzzy desktop file search", { dir, error: error.message });
165
+ }
166
+ }
167
+
168
+ if (bestMatch) {
169
+ logger.debug("Found desktop file (fuzzy match)", {
170
+ appName,
171
+ desktopFile: bestMatch,
172
+ similarity: bestScore.toFixed(2)
173
+ });
174
+ return bestMatch;
175
+ }
176
+
177
+ logger.debug("No desktop file found", { appName });
85
178
  return null;
86
179
  };
87
180
 
package/lib/uploader.js CHANGED
@@ -255,7 +255,7 @@ export async function upload(filePath, metadata = {}) {
255
255
 
256
256
  let newReplay;
257
257
  try {
258
- newReplay = await got.post('https://api.testdriver.ai/api/v1/replay', {
258
+ newReplay = await got.post('https://testdriver-api.onrender.com/api/v1/replay', {
259
259
  headers: {
260
260
  Authorization: `Bearer ${token}`
261
261
  },
@@ -430,7 +430,7 @@ export async function upload(filePath, metadata = {}) {
430
430
 
431
431
  // Publish the replay (like the desktop app does)
432
432
  logger.debug('Publishing replay...');
433
- await got.post('https://api.testdriver.ai/api/v1/replay/publish', {
433
+ await got.post('https://testdriver-api.onrender.com/api/v1/replay/publish', {
434
434
  headers: {
435
435
  Authorization: `Bearer ${token}`
436
436
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.22",
3
+ "version": "1.0.1-beta.24",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
package/test_workflow.sh CHANGED
@@ -46,7 +46,7 @@ RECORDING_START=$(date +%s)
46
46
  echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
47
47
  echo "๐Ÿ”ด EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
48
  echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
49
- echo "[EVENT 1] Recording started at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
49
+ echo "[EVENT 1] ๐Ÿ”ด Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
50
 
51
51
  # Verify recording is actually running
52
52
  if ps -p $RECORD_PID > /dev/null; then
@@ -71,25 +71,25 @@ sleep 3
71
71
  # Event 2 - after 3 seconds
72
72
  echo ""
73
73
  echo "๐ŸŸก EVENT 2: 3 seconds mark at $(date '+%H:%M:%S')"
74
- echo "[EVENT 2] 3 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
74
+ echo "[EVENT 2] ๐ŸŸก 3 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
75
75
  sleep 3
76
76
 
77
77
  # Event 3 - after 6 seconds
78
78
  echo ""
79
79
  echo "๐ŸŸข EVENT 3: 6 seconds mark at $(date '+%H:%M:%S')"
80
- echo "[EVENT 3] 6 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
80
+ echo "[EVENT 3] ๐ŸŸข 6 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
81
81
  sleep 3
82
82
 
83
83
  # Event 4 - after 9 seconds
84
84
  echo ""
85
85
  echo "๐Ÿ”ต EVENT 4: 9 seconds mark at $(date '+%H:%M:%S')"
86
- echo "[EVENT 4] 9 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
86
+ echo "[EVENT 4] ๐Ÿ”ต 9 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
87
87
  sleep 3
88
88
 
89
89
  # Event 5 - after 12 seconds
90
90
  echo ""
91
91
  echo "๐ŸŸฃ EVENT 5: 12 seconds mark at $(date '+%H:%M:%S')"
92
- echo "[EVENT 5] 12 seconds elapsed at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
92
+ echo "[EVENT 5] ๐ŸŸฃ 12 seconds elapsed with emoji at $(date '+%H:%M:%S')" >> "$TEMP_FILE"
93
93
  sleep 3
94
94
 
95
95
  # Event 6 - before ending
@@ -98,7 +98,7 @@ echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”
98
98
  echo "โšซ EVENT 6: Recording END at $(date '+%H:%M:%S')"
99
99
  echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”"
100
100
  RECORDING_END=$(date +%s)
101
- echo "[EVENT 6] Recording ending at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_END" >> "$TEMP_FILE"
101
+ echo "[EVENT 6] โšซ Recording ending with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_END" >> "$TEMP_FILE"
102
102
 
103
103
  DURATION=$((RECORDING_END - RECORDING_START))
104
104
  echo ""
@@ -112,8 +112,13 @@ sleep 2
112
112
  # 6. Stop recording and upload (this will kill the background recording process)
113
113
  echo ""
114
114
  echo "6. Stopping recording and uploading..."
115
- ./bin/dashcam.js stop
116
- echo "โœ… Recording stopped and uploaded"
115
+ # Check if recording is still active
116
+ if ./bin/dashcam.js status | grep -q "Recording in progress"; then
117
+ ./bin/dashcam.js stop
118
+ echo "โœ… Recording stopped and uploaded"
119
+ else
120
+ echo "โš ๏ธ Recording already completed (this is expected with background mode)"
121
+ fi
117
122
 
118
123
  echo ""
119
124
  echo "๐Ÿงน Cleaning up..."
@@ -140,12 +145,12 @@ echo "3. Verify these events appear at the correct times:"
140
145
  echo ""
141
146
  echo " Time | Terminal Display | Log Entry"
142
147
  echo " -------|---------------------------|---------------------------"
143
- echo " 0:00 | ๐Ÿ”ด EVENT 1 | [EVENT 1] Recording started"
144
- echo " 0:03 | ๐ŸŸก EVENT 2 | [EVENT 2] 3 seconds elapsed"
145
- echo " 0:06 | ๐ŸŸข EVENT 3 | [EVENT 3] 6 seconds elapsed"
146
- echo " 0:09 | ๐Ÿ”ต EVENT 4 | [EVENT 4] 9 seconds elapsed"
147
- echo " 0:12 | ๐ŸŸฃ EVENT 5 | [EVENT 5] 12 seconds elapsed"
148
- echo " 0:15 | โšซ EVENT 6 | [EVENT 6] Recording ending"
148
+ echo " 0:00 | ๐Ÿ”ด EVENT 1 | [EVENT 1] ๐Ÿ”ด Recording started"
149
+ echo " 0:03 | ๐ŸŸก EVENT 2 | [EVENT 2] ๐ŸŸก 3 seconds elapsed"
150
+ echo " 0:06 | ๐ŸŸข EVENT 3 | [EVENT 3] ๐ŸŸข 6 seconds elapsed"
151
+ echo " 0:09 | ๐Ÿ”ต EVENT 4 | [EVENT 4] ๐Ÿ”ต 9 seconds elapsed"
152
+ echo " 0:12 | ๐ŸŸฃ EVENT 5 | [EVENT 5] ๐ŸŸฃ 12 seconds elapsed"
153
+ echo " 0:15 | โšซ EVENT 6 | [EVENT 6] โšซ Recording ending"
149
154
  echo ""
150
155
  echo "4. The log timestamps should match the video timeline exactly"
151
156
  echo "5. Each colored event marker should appear in the video"