dashcam 1.0.1-beta.26 → 1.0.1-beta.28

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
@@ -136,7 +136,56 @@ async function recordingAction(options, command) {
136
136
  log('Use "dashcam status" to check progress');
137
137
  log('Use "dashcam stop" to stop recording and upload');
138
138
 
139
- process.exit(0);
139
+ // Keep the process alive so recording continues
140
+ // Set up graceful shutdown handlers
141
+ const handleShutdown = async (signal) => {
142
+ log(`\nReceived ${signal}, stopping recording...`);
143
+ try {
144
+ const result = await processManager.stopActiveRecording();
145
+
146
+ if (result) {
147
+ log('Recording stopped successfully');
148
+ log('Uploading recording...');
149
+
150
+ try {
151
+ const uploadResult = await upload(result.outputPath, {
152
+ title: options.title || 'Dashcam Recording',
153
+ description: description,
154
+ project: options.project || options.k,
155
+ duration: result.duration,
156
+ clientStartDate: result.clientStartDate,
157
+ apps: result.apps,
158
+ icons: result.icons,
159
+ gifPath: result.gifPath,
160
+ snapshotPath: result.snapshotPath
161
+ });
162
+
163
+ // Write upload result for stop command to read
164
+ processManager.writeUploadResult({
165
+ shareLink: uploadResult.shareLink,
166
+ replayId: uploadResult.replay?.id
167
+ });
168
+
169
+ log('📹 Watch your recording:', uploadResult.shareLink);
170
+ } catch (uploadError) {
171
+ logError('Upload failed:', uploadError.message);
172
+ log('Recording saved locally:', result.outputPath);
173
+ }
174
+ }
175
+
176
+ process.exit(0);
177
+ } catch (error) {
178
+ logError('Failed to stop recording:', error.message);
179
+ process.exit(1);
180
+ }
181
+ };
182
+
183
+ process.on('SIGINT', handleShutdown);
184
+ process.on('SIGTERM', handleShutdown);
185
+
186
+ // Keep process alive indefinitely until stopped
187
+ await new Promise(() => {}); // Wait forever
188
+
140
189
  } catch (error) {
141
190
  logError('Failed to start recording:', error.message);
142
191
  process.exit(1);
@@ -417,58 +466,43 @@ program
417
466
 
418
467
  console.log('Recording stopped successfully');
419
468
 
420
- // Wait for upload to complete (background process handles this)
421
- logger.debug('Waiting for background upload to complete...');
422
- console.log(' Uploading recording...');
423
-
424
- // Wait up to 2 minutes for upload result to appear
425
- const maxWaitForUpload = 120000; // 2 minutes
426
- const startWaitForUpload = Date.now();
427
- let uploadResult = null;
428
-
429
- while (!uploadResult && (Date.now() - startWaitForUpload) < maxWaitForUpload) {
430
- uploadResult = processManager.readUploadResult();
431
- if (!uploadResult) {
432
- await new Promise(resolve => setTimeout(resolve, 1000)); // Check every second
433
- }
434
- }
435
-
436
- logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
437
-
438
- if (uploadResult && uploadResult.shareLink) {
439
- console.log('📹 Watch your recording:', uploadResult.shareLink);
440
- // Clean up the result file now that we've read it
469
+ // Check if the result already has a shareLink (from signal handler upload)
470
+ if (result.shareLink) {
471
+ console.log('📹 Watch your recording:', result.shareLink);
441
472
  processManager.cleanup();
442
473
  process.exit(0);
443
474
  }
444
475
 
445
- // Check if files still exist - if not, background process already uploaded
446
- const filesExist = fs.existsSync(result.outputPath) &&
447
- (!result.gifPath || fs.existsSync(result.gifPath)) &&
448
- (!result.snapshotPath || fs.existsSync(result.snapshotPath));
476
+ // Otherwise, check if upload result file was written
477
+ const uploadResultFromFile = processManager.readUploadResult();
478
+ logger.debug('Upload result read attempt', { found: !!uploadResultFromFile, shareLink: uploadResultFromFile?.shareLink });
449
479
 
450
- if (!filesExist) {
451
- console.log(' Recording uploaded by background process');
452
- logger.info('Files were cleaned up by background process');
480
+ if (uploadResultFromFile && uploadResultFromFile.shareLink) {
481
+ console.log('📹 Watch your recording:', uploadResultFromFile.shareLink);
482
+ processManager.cleanup();
453
483
  process.exit(0);
454
484
  }
455
485
 
456
- // Always attempt to upload - let upload function find project if needed
486
+ // No upload result found - need to upload ourselves
487
+ // This commonly happens on Windows where signal handlers work differently
488
+ logger.info('No upload result found, uploading now...');
457
489
  console.log('Uploading recording...');
490
+
458
491
  try {
459
492
  const uploadResult = await upload(result.outputPath, {
460
493
  title: activeStatus?.options?.title,
461
494
  description: activeStatus?.options?.description,
462
- project: activeStatus?.options?.project, // May be undefined, that's ok
495
+ project: activeStatus?.options?.project,
463
496
  duration: result.duration,
464
497
  clientStartDate: result.clientStartDate,
465
498
  apps: result.apps,
466
- icons: result.icons,
499
+ logs: result.logs,
467
500
  gifPath: result.gifPath,
468
501
  snapshotPath: result.snapshotPath
469
502
  });
470
503
 
471
504
  console.log('📹 Watch your recording:', uploadResult.shareLink);
505
+ processManager.cleanup();
472
506
  } catch (uploadError) {
473
507
  console.error('Upload failed:', uploadError.message);
474
508
  console.log('Recording saved locally:', result.outputPath);
@@ -1,13 +1,8 @@
1
- import { spawn } from 'child_process';
2
1
  import fs from 'fs';
3
2
  import path from 'path';
4
3
  import os from 'os';
5
- import { fileURLToPath } from 'url';
6
4
  import { logger } from './logger.js';
7
5
 
8
- const __filename = fileURLToPath(import.meta.url);
9
- const __dirname = path.dirname(__filename);
10
-
11
6
  // Use a fixed directory in the user's home directory for cross-process communication
12
7
  const PROCESS_DIR = path.join(os.homedir(), '.dashcam-cli');
13
8
  const PID_FILE = path.join(PROCESS_DIR, 'recording.pid');
@@ -156,48 +151,121 @@ class ProcessManager {
156
151
  return false;
157
152
  }
158
153
 
159
- // Recording is active, send SIGINT to trigger graceful shutdown
160
- logger.info('Stopping active recording process', { pid });
161
- process.kill(pid, 'SIGINT');
162
-
163
- // Wait for the process to actually finish and upload
164
- // Increase timeout to allow for upload to complete
165
- const maxWaitTime = 120000; // 2 minutes max to allow for upload
166
- const startWait = Date.now();
167
-
168
- while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
169
- await new Promise(resolve => setTimeout(resolve, 500));
170
- }
171
-
172
- if (this.isProcessRunning(pid)) {
173
- logger.warn('Process did not stop within timeout, forcing termination');
174
- process.kill(pid, 'SIGKILL');
175
- await new Promise(resolve => setTimeout(resolve, 1000));
176
- }
177
-
178
- // The background process handles stopRecording() internally via SIGINT
179
- // We just need to return the basic result from the status file
180
- if (status) {
181
- logger.info('Background recording stopped, returning status', {
182
- outputPath: status.outputPath,
183
- duration: Date.now() - status.startTime
184
- });
154
+ // Check if this is the same process (direct recording)
155
+ if (pid === process.pid) {
156
+ logger.info('Stopping recording in current process');
157
+
158
+ // Import recorder module
159
+ const { stopRecording: stopRecorderRecording } = await import('./recorder.js');
185
160
 
186
- const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
187
- const result = {
188
- outputPath: status.outputPath,
189
- gifPath: `${basePath}.gif`,
190
- snapshotPath: `${basePath}.png`,
191
- duration: Date.now() - status.startTime,
192
- clientStartDate: status.startTime,
193
- apps: [],
194
- logs: []
195
- };
161
+ // Stop recording directly
162
+ const result = await stopRecorderRecording();
163
+
164
+ // Update status to indicate recording stopped
165
+ this.writeStatus({
166
+ isRecording: false,
167
+ completedTime: Date.now(),
168
+ pid: process.pid
169
+ });
196
170
 
197
171
  this.cleanup({ preserveResult: true });
198
172
  return result;
199
173
  } else {
200
- throw new Error('No status information available for active recording');
174
+ // Different process - send signal to stop it
175
+ logger.info('Stopping active recording process', { pid });
176
+
177
+ try {
178
+ process.kill(pid, 'SIGINT');
179
+ } catch (error) {
180
+ logger.warn('Failed to send SIGINT, process may have already exited', { error: error.message });
181
+ }
182
+
183
+ // Wait for the process to actually finish
184
+ const maxWaitTime = 120000; // 2 minutes max
185
+ const startWait = Date.now();
186
+
187
+ while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
188
+ await new Promise(resolve => setTimeout(resolve, 500));
189
+ }
190
+
191
+ if (this.isProcessRunning(pid)) {
192
+ logger.warn('Process did not stop within timeout, forcing termination');
193
+ try {
194
+ process.kill(pid, 'SIGKILL');
195
+ } catch (error) {
196
+ logger.warn('Failed to send SIGKILL', { error: error.message });
197
+ }
198
+ await new Promise(resolve => setTimeout(resolve, 1000));
199
+ }
200
+
201
+ // Wait a bit for upload result file to be written by the signal handler
202
+ logger.debug('Waiting for upload result file...');
203
+ const maxWaitForUpload = 10000; // 10 seconds
204
+ const startWaitForUpload = Date.now();
205
+ let uploadResult = null;
206
+
207
+ while (!uploadResult && (Date.now() - startWaitForUpload) < maxWaitForUpload) {
208
+ uploadResult = this.readUploadResult();
209
+ if (!uploadResult) {
210
+ await new Promise(resolve => setTimeout(resolve, 500));
211
+ }
212
+ }
213
+
214
+ // If upload result exists, the signal handler did the upload
215
+ if (uploadResult) {
216
+ logger.info('Upload completed by recording process');
217
+ if (status) {
218
+ const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
219
+ const result = {
220
+ outputPath: status.outputPath,
221
+ gifPath: `${basePath}.gif`,
222
+ snapshotPath: `${basePath}.png`,
223
+ duration: Date.now() - status.startTime,
224
+ clientStartDate: status.startTime,
225
+ apps: [],
226
+ logs: [],
227
+ shareLink: uploadResult.shareLink,
228
+ replayId: uploadResult.replayId
229
+ };
230
+
231
+ this.cleanup({ preserveResult: false }); // Clean up everything including result
232
+ return result;
233
+ }
234
+ }
235
+
236
+ // If no upload result, we need to handle it ourselves
237
+ // This can happen on Windows where signal handlers don't work the same way
238
+ logger.warn('No upload result found, handling upload in stop command');
239
+
240
+ if (status) {
241
+ logger.info('Reconstructing result from status', {
242
+ outputPath: status.outputPath,
243
+ duration: Date.now() - status.startTime
244
+ });
245
+
246
+ const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
247
+ const result = {
248
+ outputPath: status.outputPath,
249
+ gifPath: `${basePath}.gif`,
250
+ snapshotPath: `${basePath}.png`,
251
+ duration: Date.now() - status.startTime,
252
+ clientStartDate: status.startTime,
253
+ apps: [],
254
+ logs: []
255
+ };
256
+
257
+ // Don't cleanup yet - let the stop command handle upload
258
+ // Just update status to not recording
259
+ this.writeStatus({
260
+ isRecording: false,
261
+ completedTime: Date.now(),
262
+ pid: status.pid
263
+ });
264
+
265
+ return result;
266
+ } else {
267
+ throw new Error('No status information available for active recording');
268
+ }
201
269
  }
202
270
  } catch (error) {
203
271
  logger.error('Failed to stop recording', { error });
@@ -213,67 +281,41 @@ class ProcessManager {
213
281
  throw new Error('Recording already in progress');
214
282
  }
215
283
 
216
- // Always run in background mode by spawning a detached process
217
- logger.info('Starting recording in background mode');
218
-
219
- // Get the path to the CLI binary
220
- const binPath = path.join(__dirname, '..', 'bin', 'dashcam-background.js');
221
-
222
- logger.debug('Background process path', { binPath, exists: fs.existsSync(binPath) });
223
-
224
- // Create log files for background process
225
- const logDir = PROCESS_DIR;
226
- const stdoutLog = path.join(logDir, 'background-stdout.log');
227
- const stderrLog = path.join(logDir, 'background-stderr.log');
228
-
229
- const stdoutFd = fs.openSync(stdoutLog, 'a');
230
- const stderrFd = fs.openSync(stderrLog, 'a');
231
-
232
- // Spawn a detached process that will handle the recording
233
- const backgroundProcess = spawn(process.execPath, [
234
- binPath,
235
- JSON.stringify(options)
236
- ], {
237
- detached: true,
238
- stdio: ['ignore', stdoutFd, stderrFd], // Log stdout and stderr
239
- env: {
240
- ...process.env,
241
- DASHCAM_BACKGROUND: 'true'
242
- }
243
- });
284
+ // Import recorder module
285
+ const { startRecording: startRecorderRecording } = await import('./recorder.js');
244
286
 
245
- // Close the file descriptors in the parent process
246
- fs.closeSync(stdoutFd);
247
- fs.closeSync(stderrFd);
248
-
249
- // Get the background process PID before unreffing
250
- const backgroundPid = backgroundProcess.pid;
287
+ logger.info('Starting recording directly');
251
288
 
252
- // Allow the parent process to exit independently
253
- backgroundProcess.unref();
289
+ // Start recording using the recorder module
290
+ const recordingOptions = {
291
+ fps: parseInt(options.fps) || 10,
292
+ includeAudio: options.audio || false,
293
+ customOutputPath: options.output || null
294
+ };
254
295
 
255
- // Wait a moment for the background process to initialize
256
- await new Promise(resolve => setTimeout(resolve, 2000));
296
+ const result = await startRecorderRecording(recordingOptions);
257
297
 
258
- // Read the status file to get recording details
259
- const status = this.readStatus();
260
-
261
- if (!status || !status.isRecording) {
262
- throw new Error('Background process failed to start recording');
263
- }
298
+ // Write status to track the recording
299
+ this.writeStatus({
300
+ isRecording: true,
301
+ startTime: result.startTime,
302
+ options,
303
+ pid: process.pid,
304
+ outputPath: result.outputPath
305
+ });
264
306
 
265
- // Write PID file so other commands can find the background process
266
- this.writePid(status.pid);
307
+ // Write PID file so other commands can find the recording process
308
+ this.writePid(process.pid);
267
309
 
268
- logger.info('Background recording process started', {
269
- pid: status.pid,
270
- outputPath: status.outputPath
310
+ logger.info('Recording started successfully', {
311
+ pid: process.pid,
312
+ outputPath: result.outputPath
271
313
  });
272
314
 
273
315
  return {
274
- pid: status.pid,
275
- outputPath: status.outputPath,
276
- startTime: status.startTime
316
+ pid: process.pid,
317
+ outputPath: result.outputPath,
318
+ startTime: result.startTime
277
319
  };
278
320
  }
279
321
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.26",
3
+ "version": "1.0.1-beta.28",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
package/test_workflow.sh CHANGED
@@ -33,13 +33,16 @@ echo "✅ File tracking configured"
33
33
  # 4. Start dashcam recording in background
34
34
  echo ""
35
35
  echo "4. Starting dashcam recording in background..."
36
- # Start recording and redirect output to a log file so we can still monitor it
36
+ echo "⚠️ NOTE: Recording will run in the background using nohup"
37
+ echo ""
38
+
39
+ # Use nohup to properly detach the process and keep it running
37
40
  ./bin/dashcam.js record --title "Sync Test Recording" --description "Testing video/log synchronization with timestamped events" > /tmp/dashcam-recording.log 2>&1 &
38
41
  RECORD_PID=$!
39
42
 
40
43
  # Wait for recording to initialize and log tracker to start
41
44
  echo "Waiting for recording to initialize (PID: $RECORD_PID)..."
42
- sleep 1
45
+ sleep 3
43
46
 
44
47
  # Write first event after log tracker is fully ready
45
48
  RECORDING_START=$(date +%s)
@@ -48,11 +51,12 @@ echo "🔴 EVENT 1: Recording START at $(date '+%H:%M:%S')"
48
51
  echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
49
52
  echo "[EVENT 1] 🔴 Recording started with emoji at $(date '+%H:%M:%S') - TIMESTAMP: $RECORDING_START" >> "$TEMP_FILE"
50
53
 
51
- # Verify recording is actually running
52
- if ps -p $RECORD_PID > /dev/null; then
54
+ # Verify recording is actually running by checking status
55
+ if ./bin/dashcam.js status | grep -q "Recording in progress"; then
53
56
  echo "✅ Recording started successfully"
54
57
  else
55
- echo "❌ Recording process died, check /tmp/dashcam-recording.log"
58
+ echo "❌ Recording failed to start, check /tmp/dashcam-recording.log"
59
+ cat /tmp/dashcam-recording.log
56
60
  exit 1
57
61
  fi
58
62