dashcam 0.8.4 → 1.0.1-beta.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.
Files changed (55) hide show
  1. package/.dashcam/cli-config.json +3 -0
  2. package/.dashcam/recording.log +135 -0
  3. package/.dashcam/web-config.json +11 -0
  4. package/.github/RELEASE.md +59 -0
  5. package/.github/workflows/build.yml +103 -0
  6. package/.github/workflows/publish.yml +43 -0
  7. package/.github/workflows/release.yml +107 -0
  8. package/LOG_TRACKING_GUIDE.md +225 -0
  9. package/README.md +709 -155
  10. package/bin/dashcam.cjs +8 -0
  11. package/bin/dashcam.js +542 -0
  12. package/bin/index.js +63 -0
  13. package/examples/execute-script.js +152 -0
  14. package/examples/simple-test.js +37 -0
  15. package/lib/applicationTracker.js +311 -0
  16. package/lib/auth.js +222 -0
  17. package/lib/binaries.js +21 -0
  18. package/lib/config.js +34 -0
  19. package/lib/extension-logs/helpers.js +182 -0
  20. package/lib/extension-logs/index.js +347 -0
  21. package/lib/extension-logs/manager.js +344 -0
  22. package/lib/ffmpeg.js +156 -0
  23. package/lib/logTracker.js +23 -0
  24. package/lib/logger.js +118 -0
  25. package/lib/logs/index.js +432 -0
  26. package/lib/permissions.js +85 -0
  27. package/lib/processManager.js +255 -0
  28. package/lib/recorder.js +675 -0
  29. package/lib/store.js +58 -0
  30. package/lib/tracking/FileTracker.js +98 -0
  31. package/lib/tracking/FileTrackerManager.js +62 -0
  32. package/lib/tracking/LogsTracker.js +147 -0
  33. package/lib/tracking/active-win.js +212 -0
  34. package/lib/tracking/icons/darwin.js +39 -0
  35. package/lib/tracking/icons/index.js +167 -0
  36. package/lib/tracking/icons/windows.js +27 -0
  37. package/lib/tracking/idle.js +82 -0
  38. package/lib/tracking.js +23 -0
  39. package/lib/uploader.js +449 -0
  40. package/lib/utilities/jsonl.js +77 -0
  41. package/lib/webLogsDaemon.js +234 -0
  42. package/lib/websocket/server.js +223 -0
  43. package/package.json +50 -21
  44. package/recording.log +814 -0
  45. package/sea-bundle.mjs +34595 -0
  46. package/test-page.html +15 -0
  47. package/test.log +1 -0
  48. package/test_run.log +48 -0
  49. package/test_workflow.sh +80 -0
  50. package/examples/crash-test.js +0 -11
  51. package/examples/github-issue.sh +0 -1
  52. package/examples/protocol.html +0 -22
  53. package/index.js +0 -158
  54. package/lib.js +0 -199
  55. package/recorder.js +0 -85
@@ -0,0 +1,255 @@
1
+ import { spawn } from 'child_process';
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import os from 'os';
5
+ import { logger } from './logger.js';
6
+
7
+ // Use a fixed directory in the user's home directory for cross-process communication
8
+ const PROCESS_DIR = path.join(os.homedir(), '.dashcam-cli');
9
+ const PID_FILE = path.join(PROCESS_DIR, 'recording.pid');
10
+ const STATUS_FILE = path.join(PROCESS_DIR, 'status.json');
11
+
12
+ // Ensure process directory exists
13
+ if (!fs.existsSync(PROCESS_DIR)) {
14
+ fs.mkdirSync(PROCESS_DIR, { recursive: true });
15
+ }
16
+
17
+ class ProcessManager {
18
+ constructor() {
19
+ this.isBackgroundMode = false;
20
+ this.isStopping = false;
21
+ }
22
+
23
+ setBackgroundMode(enabled = true) {
24
+ this.isBackgroundMode = enabled;
25
+ }
26
+
27
+ writeStatus(status) {
28
+ try {
29
+ fs.writeFileSync(STATUS_FILE, JSON.stringify({
30
+ ...status,
31
+ timestamp: Date.now(),
32
+ pid: process.pid
33
+ }, null, 2));
34
+ } catch (error) {
35
+ logger.error('Failed to write status file', { error });
36
+ }
37
+ }
38
+
39
+ readStatus() {
40
+ try {
41
+ if (!fs.existsSync(STATUS_FILE)) return null;
42
+ const data = fs.readFileSync(STATUS_FILE, 'utf8');
43
+ return JSON.parse(data);
44
+ } catch (error) {
45
+ logger.error('Failed to read status file', { error });
46
+ return null;
47
+ }
48
+ }
49
+
50
+ writePid(pid = process.pid) {
51
+ try {
52
+ fs.writeFileSync(PID_FILE, pid.toString());
53
+ } catch (error) {
54
+ logger.error('Failed to write PID file', { error });
55
+ }
56
+ }
57
+
58
+ readPid() {
59
+ try {
60
+ if (!fs.existsSync(PID_FILE)) return null;
61
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim());
62
+ return isNaN(pid) ? null : pid;
63
+ } catch (error) {
64
+ return null;
65
+ }
66
+ }
67
+
68
+ isProcessRunning(pid) {
69
+ if (!pid) return false;
70
+ try {
71
+ process.kill(pid, 0); // Signal 0 just checks if process exists
72
+ return true;
73
+ } catch (error) {
74
+ return false;
75
+ }
76
+ }
77
+
78
+ isRecordingActive() {
79
+ const pid = this.readPid();
80
+ const status = this.readStatus();
81
+
82
+ if (!pid || !this.isProcessRunning(pid)) {
83
+ this.cleanup();
84
+ return false;
85
+ }
86
+
87
+ return status && status.isRecording;
88
+ }
89
+
90
+ getActiveStatus() {
91
+ if (!this.isRecordingActive()) return null;
92
+ return this.readStatus();
93
+ }
94
+
95
+ cleanup() {
96
+ try {
97
+ if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
98
+ if (fs.existsSync(STATUS_FILE)) fs.unlinkSync(STATUS_FILE);
99
+ } catch (error) {
100
+ logger.error('Failed to cleanup process files', { error });
101
+ }
102
+ }
103
+
104
+ async stopActiveRecording() {
105
+ if (this.isStopping) {
106
+ logger.info('Stop already in progress, ignoring additional stop request');
107
+ return false;
108
+ }
109
+
110
+ this.isStopping = true;
111
+
112
+ try {
113
+ const pid = this.readPid();
114
+ const status = this.readStatus();
115
+
116
+ if (!pid || !this.isProcessRunning(pid)) {
117
+ logger.warn('No active recording process found');
118
+ return false;
119
+ }
120
+
121
+ // Recording is active, send SIGINT to trigger graceful shutdown
122
+ logger.info('Stopping active recording process', { pid });
123
+ process.kill(pid, 'SIGINT');
124
+
125
+ // Wait for the process to actually finish
126
+ const maxWaitTime = 30000; // 30 seconds max
127
+ const startWait = Date.now();
128
+
129
+ while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
130
+ await new Promise(resolve => setTimeout(resolve, 500));
131
+ }
132
+
133
+ if (this.isProcessRunning(pid)) {
134
+ logger.warn('Process did not stop within timeout, forcing termination');
135
+ process.kill(pid, 'SIGKILL');
136
+ await new Promise(resolve => setTimeout(resolve, 1000));
137
+ }
138
+
139
+ // Call the actual recorder's stopRecording function to get complete results
140
+ if (status) {
141
+ try {
142
+ const { stopRecording } = await import('./recorder.js');
143
+ const result = await stopRecording();
144
+
145
+ logger.info('Recording stopped successfully via recorder', {
146
+ outputPath: result.outputPath,
147
+ duration: result.duration,
148
+ hasLogs: result.logs?.length > 0,
149
+ hasApps: result.apps?.length > 0
150
+ });
151
+
152
+ // Cleanup process files
153
+ this.cleanup();
154
+
155
+ return result;
156
+ } catch (recorderError) {
157
+ logger.warn('Failed to stop via recorder, falling back to basic result', { error: recorderError.message });
158
+
159
+ // Fallback to basic result if recorder fails
160
+ const basePath = status.outputPath.substring(0, status.outputPath.lastIndexOf('.'));
161
+ const result = {
162
+ outputPath: status.outputPath,
163
+ gifPath: `${basePath}.gif`,
164
+ snapshotPath: `${basePath}.png`,
165
+ duration: Date.now() - status.startTime,
166
+ clientStartDate: status.startTime,
167
+ apps: [],
168
+ logs: []
169
+ };
170
+
171
+ this.cleanup();
172
+ return result;
173
+ }
174
+ } else {
175
+ throw new Error('No status information available for active recording');
176
+ }
177
+ } catch (error) {
178
+ logger.error('Failed to stop recording', { error });
179
+ throw error;
180
+ } finally {
181
+ this.isStopping = false;
182
+ }
183
+ }
184
+
185
+ async startRecording(options) {
186
+ // Check if recording is already active
187
+ if (this.isRecordingActive()) {
188
+ throw new Error('Recording already in progress');
189
+ }
190
+
191
+ try {
192
+ // Import and call the recording function directly
193
+ const { startRecording } = await import('./recorder.js');
194
+
195
+ const recordingOptions = {
196
+ fps: parseInt(options.fps) || 10,
197
+ includeAudio: options.audio || false,
198
+ customOutputPath: options.output || null
199
+ };
200
+
201
+ logger.info('Starting recording directly', { options: recordingOptions });
202
+
203
+ const result = await startRecording(recordingOptions);
204
+
205
+ // Write status to track the recording
206
+ this.writePid(process.pid);
207
+ this.writeStatus({
208
+ isRecording: true,
209
+ startTime: result.startTime, // Use actual recording start time from recorder
210
+ options,
211
+ pid: process.pid,
212
+ outputPath: result.outputPath
213
+ });
214
+
215
+ logger.info('Recording started successfully', {
216
+ outputPath: result.outputPath,
217
+ startTime: result.startTime
218
+ });
219
+
220
+ return {
221
+ pid: process.pid,
222
+ outputPath: result.outputPath,
223
+ startTime: result.startTime
224
+ };
225
+ } catch (error) {
226
+ logger.error('Failed to start recording', { error });
227
+ throw error;
228
+ }
229
+ }
230
+
231
+ async gracefulExit() {
232
+ logger.info('Graceful exit requested');
233
+
234
+ // If we're currently recording, stop it properly
235
+ if (this.isRecordingActive()) {
236
+ try {
237
+ logger.info('Stopping active recording before exit');
238
+ await this.stopActiveRecording();
239
+ logger.info('Recording stopped successfully during graceful exit');
240
+ } catch (error) {
241
+ logger.error('Failed to stop recording during graceful exit', { error });
242
+ this.cleanup(); // Fallback cleanup
243
+ }
244
+ } else {
245
+ // Just cleanup if no recording is active
246
+ this.cleanup();
247
+ }
248
+
249
+ process.exit(0);
250
+ }
251
+ }
252
+
253
+ const processManager = new ProcessManager();
254
+
255
+ export { processManager };