dashcam 1.0.1-beta.23 → 1.0.1-beta.25

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,12 +417,22 @@ program
417
417
 
418
418
  console.log('Recording stopped successfully');
419
419
 
420
- // Wait a moment for upload to complete (background process handles this)
420
+ // Wait for upload to complete (background process handles this)
421
421
  logger.debug('Waiting for background upload to complete...');
422
- await new Promise(resolve => setTimeout(resolve, 5000));
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
+ }
423
435
 
424
- // Try to read the upload result from the background process
425
- const uploadResult = processManager.readUploadResult();
426
436
  logger.debug('Upload result read attempt', { found: !!uploadResult, shareLink: uploadResult?.shareLink });
427
437
 
428
438
  if (uploadResult && uploadResult.shareLink) {
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];
@@ -160,8 +160,9 @@ class ProcessManager {
160
160
  logger.info('Stopping active recording process', { pid });
161
161
  process.kill(pid, 'SIGINT');
162
162
 
163
- // Wait for the process to actually finish
164
- const maxWaitTime = 30000; // 30 seconds max
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
165
166
  const startWait = Date.now();
166
167
 
167
168
  while (this.isProcessRunning(pid) && (Date.now() - startWait) < maxWaitTime) {
@@ -0,0 +1,141 @@
1
+ import si from 'systeminformation';
2
+ import { logger } from './logger.js';
3
+
4
+ /**
5
+ * Collects comprehensive system information including CPU, memory, OS, and graphics data.
6
+ * This matches the data format expected by the Dashcam backend (same as desktop app).
7
+ *
8
+ * @returns {Promise<Object>} System information object
9
+ */
10
+ export async function getSystemInfo() {
11
+ try {
12
+ logger.debug('Collecting system information...');
13
+
14
+ // Collect only essential system information quickly
15
+ // Graphics info can be very slow, so we skip it or use a short timeout
16
+ const [cpu, mem, osInfo, system] = await Promise.all([
17
+ si.cpu(),
18
+ si.mem(),
19
+ si.osInfo(),
20
+ si.system()
21
+ ]);
22
+
23
+ // Try to get graphics info with a very short timeout (optional)
24
+ let graphics = { controllers: [], displays: [] };
25
+ try {
26
+ graphics = await Promise.race([
27
+ si.graphics(),
28
+ new Promise((_, reject) => setTimeout(() => reject(new Error('Graphics timeout')), 2000))
29
+ ]);
30
+ } catch (error) {
31
+ logger.debug('Graphics info timed out, using empty graphics data');
32
+ }
33
+
34
+ const systemInfo = {
35
+ cpu: {
36
+ manufacturer: cpu.manufacturer,
37
+ brand: cpu.brand,
38
+ vendor: cpu.vendor,
39
+ family: cpu.family,
40
+ model: cpu.model,
41
+ stepping: cpu.stepping,
42
+ revision: cpu.revision,
43
+ voltage: cpu.voltage,
44
+ speed: cpu.speed,
45
+ speedMin: cpu.speedMin,
46
+ speedMax: cpu.speedMax,
47
+ cores: cpu.cores,
48
+ physicalCores: cpu.physicalCores,
49
+ processors: cpu.processors,
50
+ socket: cpu.socket,
51
+ cache: cpu.cache
52
+ },
53
+ mem: {
54
+ total: mem.total,
55
+ free: mem.free,
56
+ used: mem.used,
57
+ active: mem.active,
58
+ available: mem.available,
59
+ swaptotal: mem.swaptotal,
60
+ swapused: mem.swapused,
61
+ swapfree: mem.swapfree
62
+ },
63
+ os: {
64
+ platform: osInfo.platform,
65
+ distro: osInfo.distro,
66
+ release: osInfo.release,
67
+ codename: osInfo.codename,
68
+ kernel: osInfo.kernel,
69
+ arch: osInfo.arch,
70
+ hostname: osInfo.hostname,
71
+ fqdn: osInfo.fqdn,
72
+ codepage: osInfo.codepage,
73
+ logofile: osInfo.logofile,
74
+ build: osInfo.build,
75
+ servicepack: osInfo.servicepack,
76
+ uefi: osInfo.uefi
77
+ },
78
+ graphics: {
79
+ controllers: graphics.controllers?.map(controller => ({
80
+ vendor: controller.vendor,
81
+ model: controller.model,
82
+ bus: controller.bus,
83
+ vram: controller.vram,
84
+ vramDynamic: controller.vramDynamic
85
+ })),
86
+ displays: graphics.displays?.map(display => ({
87
+ vendor: display.vendor,
88
+ model: display.model,
89
+ main: display.main,
90
+ builtin: display.builtin,
91
+ connection: display.connection,
92
+ resolutionX: display.resolutionX,
93
+ resolutionY: display.resolutionY,
94
+ sizeX: display.sizeX,
95
+ sizeY: display.sizeY,
96
+ pixelDepth: display.pixelDepth,
97
+ currentResX: display.currentResX,
98
+ currentResY: display.currentResY,
99
+ currentRefreshRate: display.currentRefreshRate
100
+ }))
101
+ },
102
+ system: {
103
+ manufacturer: system.manufacturer,
104
+ model: system.model,
105
+ version: system.version,
106
+ serial: system.serial,
107
+ uuid: system.uuid,
108
+ sku: system.sku,
109
+ virtual: system.virtual
110
+ }
111
+ };
112
+
113
+ logger.verbose('System information collected', {
114
+ cpuBrand: cpu.brand,
115
+ totalMemoryGB: (mem.total / (1024 * 1024 * 1024)).toFixed(2),
116
+ os: `${osInfo.distro} ${osInfo.release}`,
117
+ graphicsControllers: graphics.controllers?.length || 0,
118
+ displays: graphics.displays?.length || 0
119
+ });
120
+
121
+ return systemInfo;
122
+ } catch (error) {
123
+ logger.error('Failed to collect system information:', {
124
+ message: error.message,
125
+ stack: error.stack
126
+ });
127
+
128
+ // Return minimal system info as fallback
129
+ return {
130
+ cpu: { brand: 'Unknown', cores: 0 },
131
+ mem: { total: 0 },
132
+ os: {
133
+ platform: process.platform,
134
+ arch: process.arch,
135
+ release: process.version
136
+ },
137
+ graphics: { controllers: [], displays: [] },
138
+ system: { manufacturer: 'Unknown', model: 'Unknown' }
139
+ };
140
+ }
141
+ }
@@ -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
@@ -5,6 +5,7 @@ import { logger, logFunctionCall } from './logger.js';
5
5
  import path from 'path';
6
6
  import { auth } from './auth.js';
7
7
  import got from 'got';
8
+ import { getSystemInfo } from './systemInfo.js';
8
9
 
9
10
  class Uploader {
10
11
  constructor() {
@@ -200,6 +201,15 @@ export async function upload(filePath, metadata = {}) {
200
201
  hasProject: !!metadata.project
201
202
  });
202
203
 
204
+ // Collect system information
205
+ logger.debug('Collecting system information...');
206
+ const systemInfo = await getSystemInfo();
207
+ logger.verbose('System information collected for upload', {
208
+ cpuBrand: systemInfo.cpu?.brand,
209
+ osDistro: systemInfo.os?.distro,
210
+ totalMemGB: systemInfo.mem?.total ? (systemInfo.mem.total / (1024 * 1024 * 1024)).toFixed(2) : 'unknown'
211
+ });
212
+
203
213
  // Handle project ID - use provided project or fetch first available project
204
214
  let projectId = metadata.project;
205
215
  if (!projectId) {
@@ -229,11 +239,7 @@ export async function upload(filePath, metadata = {}) {
229
239
  duration: metadata.duration || 0,
230
240
  apps: metadata.apps && metadata.apps.length > 0 ? metadata.apps : ['Screen Recording'], // Use tracked apps or fallback
231
241
  title: metadata.title || defaultTitle,
232
- system: {
233
- platform: process.platform,
234
- arch: process.arch,
235
- nodeVersion: process.version
236
- },
242
+ system: systemInfo, // Include system information
237
243
  clientStartDate: metadata.clientStartDate || Date.now() // Use actual recording start time
238
244
  };
239
245
 
@@ -255,7 +261,7 @@ export async function upload(filePath, metadata = {}) {
255
261
 
256
262
  let newReplay;
257
263
  try {
258
- newReplay = await got.post('https://api.testdriver.ai/api/v1/replay', {
264
+ newReplay = await got.post('https://testdriver-api.onrender.com/api/v1/replay', {
259
265
  headers: {
260
266
  Authorization: `Bearer ${token}`
261
267
  },
@@ -430,7 +436,7 @@ export async function upload(filePath, metadata = {}) {
430
436
 
431
437
  // Publish the replay (like the desktop app does)
432
438
  logger.debug('Publishing replay...');
433
- await got.post('https://api.testdriver.ai/api/v1/replay/publish', {
439
+ await got.post('https://testdriver-api.onrender.com/api/v1/replay/publish', {
434
440
  headers: {
435
441
  Authorization: `Bearer ${token}`
436
442
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dashcam",
3
- "version": "1.0.1-beta.23",
3
+ "version": "1.0.1-beta.25",
4
4
  "description": "Minimal CLI version of Dashcam desktop app",
5
5
  "main": "bin/index.js",
6
6
  "bin": {
@@ -47,6 +47,7 @@
47
47
  "mask-sensitive-data": "^0.11.5",
48
48
  "node-fetch": "^2.7.0",
49
49
  "open": "^9.1.0",
50
+ "systeminformation": "^5.18.15",
50
51
  "tail": "^2.2.6",
51
52
  "winston": "^3.11.0",
52
53
  "ws": "^8.18.3"