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 +14 -4
- package/lib/auth.js +5 -5
- package/lib/config.js +1 -1
- package/lib/processManager.js +3 -2
- package/lib/systemInfo.js +141 -0
- package/lib/tracking/icons/linux.js +94 -1
- package/lib/uploader.js +13 -7
- package/package.json +2 -1
package/bin/dashcam.js
CHANGED
|
@@ -417,12 +417,22 @@ program
|
|
|
417
417
|
|
|
418
418
|
console.log('Recording stopped successfully');
|
|
419
419
|
|
|
420
|
-
// Wait
|
|
420
|
+
// Wait for upload to complete (background process handles this)
|
|
421
421
|
logger.debug('Waiting for background upload to complete...');
|
|
422
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
20
|
+
production: 'https://testdriver-api.onrender.com'
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
export const API_ENDPOINT = apiEndpoints[ENV];
|
package/lib/processManager.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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"
|