dashcam 1.4.3-beta → 1.4.5-beta
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/PERFORMANCE_TRACKING.md +139 -0
- package/bin/dashcam-background.js +53 -26
- package/bin/dashcam.js +174 -4
- package/lib/auth.js +8 -7
- package/lib/config.js +2 -1
- package/lib/performanceTracker.js +487 -0
- package/lib/recorder.js +37 -9
- package/lib/topProcesses.js +128 -0
- package/lib/uploader.js +18 -6
- package/package.json +2 -1
- package/test-perf-tracker.js +52 -0
- package/test-performance-tracking.js +108 -0
- package/test-top-processes.js +23 -0
- package/test_workflow.sh +16 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# Performance Tracking
|
|
2
|
+
|
|
3
|
+
The Dashcam CLI now includes comprehensive performance tracking during recordings. This feature monitors CPU and memory usage throughout the recording session and includes the data in the log files.
|
|
4
|
+
|
|
5
|
+
## What's Tracked
|
|
6
|
+
|
|
7
|
+
### 1. Dashcam Process Metrics
|
|
8
|
+
- **CPU Usage**: Percentage of CPU used by the Dashcam process
|
|
9
|
+
- **Memory Usage**: Memory consumed by the Dashcam process in bytes and MB
|
|
10
|
+
- **Process Info**: PID, parent PID, CPU time, elapsed time
|
|
11
|
+
|
|
12
|
+
### 2. System-Wide Metrics
|
|
13
|
+
- **Total Memory**: System total and free memory
|
|
14
|
+
- **Memory Usage Percentage**: Overall system memory utilization
|
|
15
|
+
- **CPU Count**: Number of CPU cores available
|
|
16
|
+
|
|
17
|
+
### 3. Top Processes
|
|
18
|
+
- **Top 10 by CPU**: The most CPU-intensive processes running on the system
|
|
19
|
+
- **Top 10 by Memory**: The most memory-intensive processes running on the system
|
|
20
|
+
- **Process Details**: For each top process, tracks:
|
|
21
|
+
- Process name
|
|
22
|
+
- PID
|
|
23
|
+
- CPU usage percentage
|
|
24
|
+
- Memory usage in bytes
|
|
25
|
+
- Parent process ID
|
|
26
|
+
- CPU time and elapsed time
|
|
27
|
+
|
|
28
|
+
## Sampling Frequency
|
|
29
|
+
|
|
30
|
+
Performance metrics are sampled every **1 second** (1000ms) during the recording session.
|
|
31
|
+
|
|
32
|
+
## Output Format
|
|
33
|
+
|
|
34
|
+
Performance data is included in the recording result with the following structure:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
{
|
|
38
|
+
performance: {
|
|
39
|
+
samples: [
|
|
40
|
+
{
|
|
41
|
+
timestamp: 1700000000000,
|
|
42
|
+
elapsedMs: 1000,
|
|
43
|
+
system: {
|
|
44
|
+
totalMemory: 17179869184,
|
|
45
|
+
freeMemory: 8589934592,
|
|
46
|
+
usedMemory: 8589934592,
|
|
47
|
+
memoryUsagePercent: 50.0,
|
|
48
|
+
cpuCount: 8
|
|
49
|
+
},
|
|
50
|
+
process: {
|
|
51
|
+
cpu: 15.5,
|
|
52
|
+
memory: 134217728,
|
|
53
|
+
pid: 12345,
|
|
54
|
+
ppid: 1,
|
|
55
|
+
ctime: 5000,
|
|
56
|
+
elapsed: 10000
|
|
57
|
+
},
|
|
58
|
+
topProcesses: [
|
|
59
|
+
{
|
|
60
|
+
pid: 54321,
|
|
61
|
+
name: "ffmpeg",
|
|
62
|
+
cpu: 85.2,
|
|
63
|
+
memory: 268435456,
|
|
64
|
+
ppid: 12345,
|
|
65
|
+
ctime: 8500,
|
|
66
|
+
elapsed: 10000
|
|
67
|
+
},
|
|
68
|
+
// ... up to 10 processes
|
|
69
|
+
],
|
|
70
|
+
totalProcesses: 342
|
|
71
|
+
}
|
|
72
|
+
// ... one sample per second
|
|
73
|
+
],
|
|
74
|
+
summary: {
|
|
75
|
+
durationMs: 10000,
|
|
76
|
+
sampleCount: 10,
|
|
77
|
+
monitorInterval: 1000,
|
|
78
|
+
avgProcessCPU: 12.3,
|
|
79
|
+
maxProcessCPU: 18.7,
|
|
80
|
+
avgProcessMemoryBytes: 134217728,
|
|
81
|
+
avgProcessMemoryMB: 128.0,
|
|
82
|
+
maxProcessMemoryBytes: 201326592,
|
|
83
|
+
maxProcessMemoryMB: 192.0,
|
|
84
|
+
avgSystemMemoryUsagePercent: 52.5,
|
|
85
|
+
maxSystemMemoryUsagePercent: 55.3,
|
|
86
|
+
totalSystemMemoryBytes: 17179869184,
|
|
87
|
+
totalSystemMemoryGB: 16.0
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Summary Statistics
|
|
94
|
+
|
|
95
|
+
The performance tracker calculates summary statistics including:
|
|
96
|
+
- **Average and Max Process CPU**: How much CPU the Dashcam process used
|
|
97
|
+
- **Average and Max Process Memory**: Memory consumed by the Dashcam process
|
|
98
|
+
- **Average and Max System Memory Usage**: Overall system memory pressure
|
|
99
|
+
- **Total Duration**: How long the tracking ran
|
|
100
|
+
- **Sample Count**: Number of samples collected
|
|
101
|
+
|
|
102
|
+
## Logging
|
|
103
|
+
|
|
104
|
+
Performance data is logged to the standard Dashcam log files:
|
|
105
|
+
- `~/.dashcam/logs/combined.log` - All log levels including performance samples
|
|
106
|
+
- `~/.dashcam/logs/debug.log` - Debug-level information
|
|
107
|
+
- Console output when running with `--verbose` flag
|
|
108
|
+
|
|
109
|
+
## Testing
|
|
110
|
+
|
|
111
|
+
Run the performance tracking test:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
node test-performance-tracking.js
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
This will:
|
|
118
|
+
1. Start a 10-second recording
|
|
119
|
+
2. Collect performance samples every second
|
|
120
|
+
3. Display detailed performance statistics
|
|
121
|
+
4. Show the top 10 processes at the end of the recording
|
|
122
|
+
|
|
123
|
+
## Use Cases
|
|
124
|
+
|
|
125
|
+
Performance tracking helps with:
|
|
126
|
+
- **Debugging Performance Issues**: Identify when recordings are resource-intensive
|
|
127
|
+
- **Optimization**: Track the impact of code changes on resource usage
|
|
128
|
+
- **System Monitoring**: Understand what other processes are running during recordings
|
|
129
|
+
- **Troubleshooting**: Correlate performance issues with specific applications or system states
|
|
130
|
+
- **Capacity Planning**: Understand resource requirements for different recording scenarios
|
|
131
|
+
|
|
132
|
+
## Implementation Details
|
|
133
|
+
|
|
134
|
+
The performance tracker uses:
|
|
135
|
+
- `pidusage` - For detailed per-process CPU and memory statistics
|
|
136
|
+
- `ps-list` - For listing all system processes
|
|
137
|
+
- `os` module - For system-wide memory and CPU information
|
|
138
|
+
|
|
139
|
+
The tracker runs in parallel with the recording and has minimal overhead (~1% CPU on average).
|
|
@@ -161,37 +161,64 @@ async function runBackgroundRecording() {
|
|
|
161
161
|
}
|
|
162
162
|
isShuttingDown = true;
|
|
163
163
|
|
|
164
|
-
logger.info(`Received ${signal},
|
|
165
|
-
console.log('[Background] Received stop signal,
|
|
164
|
+
logger.info(`Received ${signal}, stopping recording and collecting data`);
|
|
165
|
+
console.log('[Background] Received stop signal, stopping recording...');
|
|
166
166
|
|
|
167
|
-
// Kill any child processes (ffmpeg, etc.)
|
|
168
167
|
try {
|
|
169
|
-
//
|
|
170
|
-
const {
|
|
171
|
-
const
|
|
168
|
+
// Stop the recording properly to collect all tracking data
|
|
169
|
+
const { stopRecording } = await import('../lib/recorder.js');
|
|
170
|
+
const result = await stopRecording();
|
|
172
171
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
172
|
+
logger.info('Recording stopped successfully', {
|
|
173
|
+
outputPath: result.outputPath,
|
|
174
|
+
duration: result.duration,
|
|
175
|
+
performanceSamples: result.performance?.summary?.sampleCount || 0
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Write the complete result to file for the stop command to read
|
|
179
|
+
const RECORDING_RESULT_FILE = path.join(PROCESS_DIR, 'recording-result.json');
|
|
180
|
+
fs.writeFileSync(RECORDING_RESULT_FILE, JSON.stringify({
|
|
181
|
+
...result,
|
|
182
|
+
timestamp: Date.now()
|
|
183
|
+
}, null, 2));
|
|
184
|
+
|
|
185
|
+
logger.info('Saved recording result to file', {
|
|
186
|
+
path: RECORDING_RESULT_FILE,
|
|
187
|
+
performanceSamples: result.performance?.summary?.sampleCount || 0
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// Update status to indicate recording is complete
|
|
191
|
+
writeStatus({
|
|
192
|
+
isRecording: false,
|
|
193
|
+
completedAt: Date.now(),
|
|
194
|
+
outputPath: result.outputPath,
|
|
195
|
+
duration: result.duration
|
|
196
|
+
});
|
|
190
197
|
|
|
191
|
-
// Give it a moment to clean up
|
|
192
|
-
await new Promise(resolve => setTimeout(resolve, 500));
|
|
193
198
|
} catch (error) {
|
|
194
|
-
logger.error('Error during
|
|
199
|
+
logger.error('Error stopping recording during shutdown', { error: error.message });
|
|
200
|
+
|
|
201
|
+
// Still try to kill child processes
|
|
202
|
+
try {
|
|
203
|
+
const { exec } = await import('child_process');
|
|
204
|
+
const platform = process.platform;
|
|
205
|
+
|
|
206
|
+
if (platform === 'darwin' || platform === 'linux') {
|
|
207
|
+
exec(`pkill -P ${process.pid}`, (error) => {
|
|
208
|
+
if (error && error.code !== 1) {
|
|
209
|
+
logger.warn('Failed to kill child processes', { error: error.message });
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
} else if (platform === 'win32') {
|
|
213
|
+
exec(`taskkill /F /T /PID ${process.pid}`, (error) => {
|
|
214
|
+
if (error) {
|
|
215
|
+
logger.warn('Failed to kill child processes on Windows', { error: error.message });
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
} catch (cleanupError) {
|
|
220
|
+
logger.error('Error during cleanup', { error: cleanupError.message });
|
|
221
|
+
}
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
logger.info('Background process exiting');
|
package/bin/dashcam.js
CHANGED
|
@@ -25,6 +25,57 @@ if (!fs.existsSync(APP.recordingsDir)) {
|
|
|
25
25
|
fs.mkdirSync(APP.recordingsDir, { recursive: true });
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
// Handle graceful shutdown on terminal close or process termination
|
|
29
|
+
// This prevents orphaned FFmpeg processes and ensures clean exit
|
|
30
|
+
let isShuttingDown = false;
|
|
31
|
+
|
|
32
|
+
async function gracefulShutdown(signal) {
|
|
33
|
+
if (isShuttingDown) {
|
|
34
|
+
logger.debug('Shutdown already in progress, ignoring additional signal', { signal });
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
isShuttingDown = true;
|
|
39
|
+
logger.info('Received shutdown signal, cleaning up...', { signal });
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Check if there's an active recording and stop it
|
|
43
|
+
if (processManager.isRecordingActive()) {
|
|
44
|
+
logger.info('Stopping active recording before exit...');
|
|
45
|
+
await processManager.stopActiveRecording().catch(err => {
|
|
46
|
+
logger.warn('Failed to stop recording during shutdown', { error: err.message });
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
logger.info('Cleanup complete, exiting gracefully');
|
|
51
|
+
process.exit(0);
|
|
52
|
+
} catch (error) {
|
|
53
|
+
logger.error('Error during graceful shutdown', { error: error.message });
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Register signal handlers for graceful shutdown
|
|
59
|
+
process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
|
|
60
|
+
process.on('SIGINT', () => gracefulShutdown('SIGINT'));
|
|
61
|
+
process.on('SIGHUP', () => gracefulShutdown('SIGHUP'));
|
|
62
|
+
|
|
63
|
+
// Handle uncaught errors to prevent crashes
|
|
64
|
+
process.on('uncaughtException', (error) => {
|
|
65
|
+
logger.error('Uncaught exception', {
|
|
66
|
+
error: error.message,
|
|
67
|
+
stack: error.stack
|
|
68
|
+
});
|
|
69
|
+
gracefulShutdown('uncaughtException');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
73
|
+
logger.error('Unhandled promise rejection', {
|
|
74
|
+
reason: reason instanceof Error ? reason.message : reason,
|
|
75
|
+
stack: reason instanceof Error ? reason.stack : undefined
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
28
79
|
program
|
|
29
80
|
.name('dashcam')
|
|
30
81
|
.description('Capture the steps to reproduce every bug.')
|
|
@@ -469,6 +520,24 @@ program
|
|
|
469
520
|
console.log('Recording stopped successfully');
|
|
470
521
|
logger.debug('Stop result:', result);
|
|
471
522
|
|
|
523
|
+
// Try to read the recording result saved by the background process
|
|
524
|
+
// This includes performance data and other tracking information
|
|
525
|
+
const RECORDING_RESULT_FILE = path.join(os.homedir(), '.dashcam-cli', 'recording-result.json');
|
|
526
|
+
let backgroundResult = null;
|
|
527
|
+
if (fs.existsSync(RECORDING_RESULT_FILE)) {
|
|
528
|
+
try {
|
|
529
|
+
const resultData = fs.readFileSync(RECORDING_RESULT_FILE, 'utf8');
|
|
530
|
+
backgroundResult = JSON.parse(resultData);
|
|
531
|
+
logger.info('Loaded recording result from background process', {
|
|
532
|
+
outputPath: backgroundResult.outputPath,
|
|
533
|
+
duration: backgroundResult.duration,
|
|
534
|
+
performanceSamples: backgroundResult.performance?.summary?.sampleCount || 0
|
|
535
|
+
});
|
|
536
|
+
} catch (error) {
|
|
537
|
+
logger.warn('Failed to read background recording result', { error: error.message });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
472
541
|
// Reconstruct recording data from status and fix video with FFmpeg
|
|
473
542
|
console.log('Processing recording...');
|
|
474
543
|
logger.debug('Reconstructing recording data from status file');
|
|
@@ -660,6 +729,87 @@ program
|
|
|
660
729
|
logger.warn('Failed to collect log tracking results', { error: error.message });
|
|
661
730
|
}
|
|
662
731
|
|
|
732
|
+
// Read performance data from file if available
|
|
733
|
+
let performanceData = null;
|
|
734
|
+
try {
|
|
735
|
+
const performanceFile = path.join(recordingDir, 'performance.jsonl');
|
|
736
|
+
if (fs.existsSync(performanceFile)) {
|
|
737
|
+
logger.info('Found performance file, reading data', { file: performanceFile });
|
|
738
|
+
|
|
739
|
+
// Read the file directly and parse samples
|
|
740
|
+
const fileContent = fs.readFileSync(performanceFile, 'utf8');
|
|
741
|
+
const lines = fileContent.trim().split('\n').filter(line => line.length > 0);
|
|
742
|
+
const samples = lines.map(line => JSON.parse(line));
|
|
743
|
+
|
|
744
|
+
logger.info('Parsed performance samples from file', { sampleCount: samples.length });
|
|
745
|
+
|
|
746
|
+
// Calculate summary statistics
|
|
747
|
+
if (samples.length > 0) {
|
|
748
|
+
const firstSample = samples[0];
|
|
749
|
+
const lastSample = samples[samples.length - 1];
|
|
750
|
+
|
|
751
|
+
let totalProcessCPU = 0;
|
|
752
|
+
let totalProcessMemory = 0;
|
|
753
|
+
let totalSystemMemoryUsage = 0;
|
|
754
|
+
let maxProcessCPU = 0;
|
|
755
|
+
let maxProcessMemory = 0;
|
|
756
|
+
let maxSystemMemoryUsage = 0;
|
|
757
|
+
|
|
758
|
+
samples.forEach(sample => {
|
|
759
|
+
const processCPU = sample.process?.cpu || 0;
|
|
760
|
+
const processMemory = sample.process?.memory || 0;
|
|
761
|
+
const systemMemoryUsage = sample.system?.memoryUsagePercent || 0;
|
|
762
|
+
|
|
763
|
+
totalProcessCPU += processCPU;
|
|
764
|
+
totalProcessMemory += processMemory;
|
|
765
|
+
totalSystemMemoryUsage += systemMemoryUsage;
|
|
766
|
+
|
|
767
|
+
maxProcessCPU = Math.max(maxProcessCPU, processCPU);
|
|
768
|
+
maxProcessMemory = Math.max(maxProcessMemory, processMemory);
|
|
769
|
+
maxSystemMemoryUsage = Math.max(maxSystemMemoryUsage, systemMemoryUsage);
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
const count = samples.length;
|
|
773
|
+
const finalSample = samples[samples.length - 1];
|
|
774
|
+
const totalBytesReceived = finalSample.network?.bytesReceived || 0;
|
|
775
|
+
const totalBytesSent = finalSample.network?.bytesSent || 0;
|
|
776
|
+
|
|
777
|
+
const summary = {
|
|
778
|
+
durationMs: lastSample.timestamp - firstSample.timestamp,
|
|
779
|
+
sampleCount: count,
|
|
780
|
+
monitorInterval: 5000,
|
|
781
|
+
avgProcessCPU: totalProcessCPU / count,
|
|
782
|
+
maxProcessCPU,
|
|
783
|
+
avgProcessMemoryBytes: totalProcessMemory / count,
|
|
784
|
+
avgProcessMemoryMB: (totalProcessMemory / count) / (1024 * 1024),
|
|
785
|
+
maxProcessMemoryBytes: maxProcessMemory,
|
|
786
|
+
maxProcessMemoryMB: maxProcessMemory / (1024 * 1024),
|
|
787
|
+
avgSystemMemoryUsagePercent: totalSystemMemoryUsage / count,
|
|
788
|
+
maxSystemMemoryUsagePercent: maxSystemMemoryUsage,
|
|
789
|
+
totalSystemMemoryBytes: firstSample.system?.totalMemory || 0,
|
|
790
|
+
totalSystemMemoryGB: (firstSample.system?.totalMemory || 0) / (1024 * 1024 * 1024),
|
|
791
|
+
totalBytesReceived,
|
|
792
|
+
totalBytesSent,
|
|
793
|
+
totalMBReceived: totalBytesReceived / (1024 * 1024),
|
|
794
|
+
totalMBSent: totalBytesSent / (1024 * 1024)
|
|
795
|
+
};
|
|
796
|
+
|
|
797
|
+
performanceData = { samples, summary };
|
|
798
|
+
|
|
799
|
+
logger.info('Calculated performance summary', {
|
|
800
|
+
sampleCount: summary.sampleCount,
|
|
801
|
+
avgCPU: summary.avgProcessCPU.toFixed(1),
|
|
802
|
+
maxCPU: summary.maxProcessCPU.toFixed(1),
|
|
803
|
+
avgMemoryMB: summary.avgProcessMemoryMB.toFixed(1)
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
} else {
|
|
807
|
+
logger.debug('No performance file found', { expectedPath: performanceFile });
|
|
808
|
+
}
|
|
809
|
+
} catch (error) {
|
|
810
|
+
logger.warn('Failed to load performance data', { error: error.message, stack: error.stack });
|
|
811
|
+
}
|
|
812
|
+
|
|
663
813
|
// The recording is on disk and can be uploaded with full metadata
|
|
664
814
|
const recordingResult = {
|
|
665
815
|
outputPath,
|
|
@@ -668,14 +818,22 @@ program
|
|
|
668
818
|
duration: result.duration,
|
|
669
819
|
fileSize: fs.statSync(outputPath).size,
|
|
670
820
|
clientStartDate: activeStatus.startTime,
|
|
671
|
-
apps: appTrackingResults.apps,
|
|
672
|
-
icons: appTrackingResults.icons,
|
|
673
|
-
logs: logTrackingResults,
|
|
821
|
+
apps: backgroundResult?.apps || appTrackingResults.apps,
|
|
822
|
+
icons: backgroundResult?.icons || appTrackingResults.icons,
|
|
823
|
+
logs: backgroundResult?.logs || logTrackingResults,
|
|
824
|
+
performance: performanceData || backgroundResult?.performance || null, // Prefer file data, fallback to background result
|
|
674
825
|
title: activeStatus?.options?.title,
|
|
675
826
|
description: activeStatus?.options?.description,
|
|
676
827
|
project: activeStatus?.options?.project
|
|
677
828
|
};
|
|
678
829
|
|
|
830
|
+
logger.info('Recording result prepared for upload', {
|
|
831
|
+
hasPerformanceData: !!recordingResult.performance,
|
|
832
|
+
performanceSamples: recordingResult.performance?.summary?.sampleCount || 0,
|
|
833
|
+
avgCPU: recordingResult.performance?.summary?.avgProcessCPU?.toFixed(1) || 'N/A',
|
|
834
|
+
maxCPU: recordingResult.performance?.summary?.maxProcessCPU?.toFixed(1) || 'N/A'
|
|
835
|
+
});
|
|
836
|
+
|
|
679
837
|
if (!recordingResult || !recordingResult.outputPath) {
|
|
680
838
|
console.error('Failed to process recording');
|
|
681
839
|
logger.error('No recording result', { recordingResult });
|
|
@@ -695,6 +853,7 @@ program
|
|
|
695
853
|
apps: recordingResult.apps,
|
|
696
854
|
icons: recordingResult.icons,
|
|
697
855
|
logs: recordingResult.logs,
|
|
856
|
+
performance: recordingResult.performance, // Include performance data
|
|
698
857
|
gifPath: recordingResult.gifPath,
|
|
699
858
|
snapshotPath: recordingResult.snapshotPath
|
|
700
859
|
});
|
|
@@ -702,8 +861,19 @@ program
|
|
|
702
861
|
console.log('Watch your recording:', uploadResult.shareLink);
|
|
703
862
|
logger.info('Upload succeeded');
|
|
704
863
|
|
|
705
|
-
// Clean up the result file
|
|
864
|
+
// Clean up the result file and recording result file
|
|
706
865
|
processManager.cleanup();
|
|
866
|
+
|
|
867
|
+
// Also clean up recording result file from background process
|
|
868
|
+
const RECORDING_RESULT_FILE = path.join(os.homedir(), '.dashcam-cli', 'recording-result.json');
|
|
869
|
+
if (fs.existsSync(RECORDING_RESULT_FILE)) {
|
|
870
|
+
try {
|
|
871
|
+
fs.unlinkSync(RECORDING_RESULT_FILE);
|
|
872
|
+
logger.debug('Cleaned up recording result file');
|
|
873
|
+
} catch (error) {
|
|
874
|
+
logger.warn('Failed to clean up recording result file', { error: error.message });
|
|
875
|
+
}
|
|
876
|
+
}
|
|
707
877
|
} catch (uploadError) {
|
|
708
878
|
console.error('Upload failed:', uploadError.message);
|
|
709
879
|
logger.error('Upload error details:', {
|
package/lib/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import got from 'got';
|
|
2
|
-
import { auth0Config } from './config.js';
|
|
2
|
+
import { auth0Config, API_ENDPOINT } from './config.js';
|
|
3
3
|
import { logger, logFunctionCall } from './logger.js';
|
|
4
4
|
import { Store } from './store.js';
|
|
5
5
|
|
|
@@ -14,11 +14,12 @@ const auth = {
|
|
|
14
14
|
logger.info('Authenticating with API key');
|
|
15
15
|
logger.verbose('Starting API key exchange', {
|
|
16
16
|
apiKeyLength: apiKey?.length,
|
|
17
|
-
hasApiKey: !!apiKey
|
|
17
|
+
hasApiKey: !!apiKey,
|
|
18
|
+
apiEndpoint: API_ENDPOINT
|
|
18
19
|
});
|
|
19
20
|
|
|
20
21
|
// Exchange API key for token
|
|
21
|
-
const { token } = await got.post(
|
|
22
|
+
const { token } = await got.post(`${API_ENDPOINT}/auth/exchange-api-key`, {
|
|
22
23
|
json: { apiKey },
|
|
23
24
|
timeout: 30000 // 30 second timeout
|
|
24
25
|
}).json();
|
|
@@ -34,7 +35,7 @@ const auth = {
|
|
|
34
35
|
|
|
35
36
|
// Get user info to verify the token works
|
|
36
37
|
logger.debug('Fetching user information to validate token...');
|
|
37
|
-
const user = await got.get(
|
|
38
|
+
const user = await got.get(`${API_ENDPOINT}/api/v1/whoami`, {
|
|
38
39
|
headers: {
|
|
39
40
|
Authorization: `Bearer ${token}`
|
|
40
41
|
},
|
|
@@ -105,7 +106,7 @@ const auth = {
|
|
|
105
106
|
const token = await this.getToken();
|
|
106
107
|
|
|
107
108
|
try {
|
|
108
|
-
const response = await got.get(
|
|
109
|
+
const response = await got.get(`${API_ENDPOINT}/api/v1/projects`, {
|
|
109
110
|
headers: {
|
|
110
111
|
Authorization: `Bearer ${token}`
|
|
111
112
|
},
|
|
@@ -160,7 +161,7 @@ const auth = {
|
|
|
160
161
|
requestBody.project = replayData.project;
|
|
161
162
|
}
|
|
162
163
|
|
|
163
|
-
const response = await got.post(
|
|
164
|
+
const response = await got.post(`${API_ENDPOINT}/api/v1/replay/upload`, {
|
|
164
165
|
headers: {
|
|
165
166
|
Authorization: `Bearer ${token}`
|
|
166
167
|
},
|
|
@@ -188,7 +189,7 @@ const auth = {
|
|
|
188
189
|
const token = await this.getToken();
|
|
189
190
|
|
|
190
191
|
try {
|
|
191
|
-
const response = await got.post(
|
|
192
|
+
const response = await got.post(`${API_ENDPOINT}/api/v1/logs`, {
|
|
192
193
|
headers: {
|
|
193
194
|
Authorization: `Bearer ${token}`
|
|
194
195
|
},
|
package/lib/config.js
CHANGED
|
@@ -26,7 +26,8 @@ export const apiEndpoints = {
|
|
|
26
26
|
production: 'https://testdriver-api.onrender.com'
|
|
27
27
|
};
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
// Allow TD_API_ROOT to override the endpoint
|
|
30
|
+
export const API_ENDPOINT = process.env.TD_API_ROOT || apiEndpoints[ENV];
|
|
30
31
|
|
|
31
32
|
// App configuration
|
|
32
33
|
export const APP = {
|