dashcam 0.8.3 → 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 -177
  54. package/lib.js +0 -199
  55. package/recorder.js +0 -85
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Example: Execute JavaScript on a webpage through the Chrome extension
5
+ *
6
+ * This demonstrates how to:
7
+ * 1. Connect to the Chrome extension via WebSocket
8
+ * 2. Send JavaScript code to execute on the active tab
9
+ * 3. Receive the execution results back
10
+ */
11
+
12
+ import { server } from '../lib/websocket/server.js';
13
+ import { WebTrackerManager } from '../lib/extension-logs/manager.js';
14
+ import { logger } from '../lib/logger.js';
15
+
16
+ async function main() {
17
+ console.log('╔════════════════════════════════════════════════════════════════╗');
18
+ console.log('║ Chrome Extension Script Execution Example ║');
19
+ console.log('╚════════════════════════════════════════════════════════════════╝\n');
20
+ console.log('📋 Prerequisites:');
21
+ console.log(' 1. Chrome extension must be installed and loaded');
22
+ console.log(' 2. Have a regular webpage open (NOT chrome:// or chrome-extension://)');
23
+ console.log(' 3. For best results, navigate to a simple page like example.com\n');
24
+
25
+ console.log('Starting WebSocket server...');
26
+
27
+ // Start the WebSocket server
28
+ await server.start();
29
+ console.log('✓ WebSocket server started on port:', server.port);
30
+
31
+ // Create the WebTrackerManager
32
+ const manager = new WebTrackerManager(server);
33
+
34
+ // Wait for the Chrome extension to connect
35
+ console.log('\nWaiting for Chrome extension to connect...');
36
+ const connected = await new Promise((resolve) => {
37
+ const timeout = setTimeout(() => {
38
+ console.log('⏱️ Timeout waiting for connection');
39
+ resolve(false);
40
+ }, 10000);
41
+
42
+ const cleanup = server.on('connection', (client) => {
43
+ console.log('✓ Chrome extension connected!');
44
+ clearTimeout(timeout);
45
+ cleanup();
46
+ resolve(true);
47
+ });
48
+ });
49
+
50
+ if (!connected) {
51
+ console.error('\n❌ Chrome extension did not connect.');
52
+ console.error(' Make sure the extension is installed and running.');
53
+ console.error(' Check chrome://extensions to verify it\'s loaded.');
54
+ process.exit(1);
55
+ }
56
+
57
+ // Give it a moment to fully initialize
58
+ await new Promise(resolve => setTimeout(resolve, 500));
59
+
60
+ console.log('\n--- Example 1: Get page title ---');
61
+ try {
62
+ const result = await manager.executeScript({
63
+ code: 'return document.title;'
64
+ });
65
+ console.log('✓ Page title:', result);
66
+ } catch (error) {
67
+ console.error('❌ Error:', error.message);
68
+ }
69
+
70
+ console.log('\n--- Example 2: Get current URL ---');
71
+ try {
72
+ const result = await manager.executeScript({
73
+ code: 'return window.location.href;'
74
+ });
75
+ console.log('Current URL:', result);
76
+ } catch (error) {
77
+ console.error('Error:', error.message);
78
+ }
79
+
80
+ console.log('\n--- Example 3: Change page background color ---');
81
+ try {
82
+ const result = await manager.executeScript({
83
+ code: `
84
+ document.body.style.backgroundColor = '#ffcccc';
85
+ return 'Background color changed!';
86
+ `
87
+ });
88
+ console.log('Result:', result);
89
+ } catch (error) {
90
+ console.error('Error:', error.message);
91
+ }
92
+
93
+ console.log('\n--- Example 4: Insert a message on the page ---');
94
+ try {
95
+ const result = await manager.executeScript({
96
+ code: `
97
+ const div = document.createElement('div');
98
+ div.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #4CAF50; color: white; padding: 20px; border-radius: 8px; font-family: Arial; z-index: 10000; box-shadow: 0 4px 6px rgba(0,0,0,0.1);';
99
+ div.textContent = 'Hello from CLI! 👋';
100
+ document.body.appendChild(div);
101
+
102
+ // Remove after 5 seconds
103
+ setTimeout(() => div.remove(), 5000);
104
+
105
+ return 'Message inserted successfully!';
106
+ `
107
+ });
108
+ console.log('Result:', result);
109
+ } catch (error) {
110
+ console.error('Error:', error.message);
111
+ }
112
+
113
+ console.log('\n--- Example 5: Get all links on the page ---');
114
+ try {
115
+ const result = await manager.executeScript({
116
+ code: `
117
+ const links = Array.from(document.querySelectorAll('a'))
118
+ .map(a => ({ text: a.textContent.trim().substring(0, 50), href: a.href }))
119
+ .slice(0, 5);
120
+ return links;
121
+ `
122
+ });
123
+ console.log('First 5 links:', JSON.stringify(result, null, 2));
124
+ } catch (error) {
125
+ console.error('Error:', error.message);
126
+ }
127
+
128
+ console.log('\n--- Example 6: Execute with error handling ---');
129
+ try {
130
+ const result = await manager.executeScript({
131
+ code: `
132
+ // This will cause an error
133
+ nonExistentVariable.doSomething();
134
+ return 'This will not be returned';
135
+ `
136
+ });
137
+ console.log('Result:', result);
138
+ } catch (error) {
139
+ console.error('Expected error caught:', error.message);
140
+ }
141
+
142
+ console.log('\n--- All examples completed! ---');
143
+
144
+ // Clean up
145
+ await server.stop();
146
+ process.exit(0);
147
+ }
148
+
149
+ main().catch(error => {
150
+ console.error('Fatal error:', error);
151
+ process.exit(1);
152
+ });
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { server } from '../lib/websocket/server.js';
4
+ import { WebTrackerManager } from '../lib/extension-logs/manager.js';
5
+
6
+ async function main() {
7
+ console.log('Simple test - make sure you have example.com or any webpage open\n');
8
+
9
+ await server.start();
10
+ console.log('Server started on port:', server.port);
11
+
12
+ const manager = new WebTrackerManager(server);
13
+
14
+ console.log('Waiting for extension...');
15
+ await new Promise((resolve) => {
16
+ const cleanup = server.on('connection', () => {
17
+ console.log('Connected!\n');
18
+ cleanup();
19
+ resolve();
20
+ });
21
+ });
22
+
23
+ await new Promise(resolve => setTimeout(resolve, 500));
24
+
25
+ console.log('Test: Get page DOM');
26
+ const result = await manager.getPageDOM();
27
+ console.log('Title:', result.title);
28
+ console.log('URL:', result.url);
29
+ console.log('HTML length:', result.html.length, 'characters\n');
30
+
31
+ console.log(result.html)
32
+
33
+ await server.stop();
34
+ process.exit(0);
35
+ }
36
+
37
+ main().catch(console.error);
@@ -0,0 +1,311 @@
1
+ import { logger, logFunctionCall } from './logger.js';
2
+ import { extractIcon, getIconData } from './tracking/icons/index.js';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import os from 'os';
6
+
7
+ // Lazy-loaded get-windows module (will be loaded on first use)
8
+ let activeWindowSync = null;
9
+ let getWindowsLoadAttempted = false;
10
+ let usingSystemTracker = false;
11
+
12
+ async function loadGetWindows() {
13
+ if (getWindowsLoadAttempted) {
14
+ return activeWindowSync;
15
+ }
16
+
17
+ getWindowsLoadAttempted = true;
18
+
19
+ try {
20
+ // Try to import get-windows - will work if node_modules is available
21
+ // or if running from source
22
+ const getWindows = await import('get-windows');
23
+ activeWindowSync = getWindows.activeWindowSync;
24
+ logger.info('Successfully loaded get-windows for application tracking');
25
+ return activeWindowSync;
26
+ } catch (error) {
27
+ const isPkg = typeof process.pkg !== 'undefined';
28
+
29
+ if (isPkg) {
30
+ logger.info('Using system-based window tracking (pkg build fallback)');
31
+ usingSystemTracker = true;
32
+ } else {
33
+ logger.warn('get-windows not available, falling back to system commands', {
34
+ error: error.message
35
+ });
36
+ usingSystemTracker = true;
37
+ }
38
+ return null;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Enhanced Application tracker for CLI
44
+ * Uses desktop app patterns for tracking active applications and extracting icons
45
+ */
46
+ class ApplicationTracker {
47
+ constructor() {
48
+ this.isTracking = false;
49
+ this.trackingInterval = null;
50
+ this.trackedApps = new Set();
51
+ this.appEvents = [];
52
+ this.failedAttempts = 0;
53
+ this.maxFailedAttempts = 5;
54
+
55
+ // Create a temporary directory for tracking logs
56
+ this.trackingDir = path.join(os.tmpdir(), 'dashcam-cli-tracking');
57
+ this.logFile = path.join(this.trackingDir, 'active-win.jsonl');
58
+
59
+ // Ensure tracking directory exists
60
+ if (!fs.existsSync(this.trackingDir)) {
61
+ fs.mkdirSync(this.trackingDir, { recursive: true });
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Start tracking active applications
67
+ */
68
+ start() {
69
+ const logExit = logFunctionCall('ApplicationTracker.start');
70
+
71
+ if (this.isTracking) {
72
+ logger.debug('Application tracking already started');
73
+ logExit();
74
+ return;
75
+ }
76
+
77
+ logger.debug('Starting enhanced application tracking', {
78
+ trackingDir: this.trackingDir,
79
+ logFile: this.logFile
80
+ });
81
+
82
+ this.isTracking = true;
83
+ this.trackedApps.clear();
84
+ this.appEvents = [];
85
+ this.failedAttempts = 0;
86
+
87
+ // Clear previous tracking log
88
+ if (fs.existsSync(this.logFile)) {
89
+ fs.unlinkSync(this.logFile);
90
+ }
91
+
92
+ // Track active window every second (same as desktop app)
93
+ this.trackingInterval = setInterval(() => {
94
+ this.trackActiveWindow();
95
+ }, 10000);
96
+
97
+ logExit();
98
+ }
99
+
100
+ /**
101
+ * Stop tracking active applications
102
+ */
103
+ stop() {
104
+ const logExit = logFunctionCall('ApplicationTracker.stop');
105
+
106
+ if (!this.isTracking) {
107
+ logger.debug('Application tracking not started');
108
+ logExit();
109
+ return this.getResults();
110
+ }
111
+
112
+ logger.debug('Stopping enhanced application tracking');
113
+ this.isTracking = false;
114
+
115
+ if (this.trackingInterval) {
116
+ clearInterval(this.trackingInterval);
117
+ this.trackingInterval = null;
118
+ }
119
+
120
+ const results = this.getResults();
121
+ logger.info('Enhanced application tracking stopped', {
122
+ uniqueApps: results.apps.length,
123
+ totalEvents: this.appEvents.length,
124
+ iconsExtracted: results.icons.filter(icon => icon.file).length
125
+ });
126
+
127
+ logExit();
128
+ return results;
129
+ }
130
+
131
+ /**
132
+ * Track the currently active window (using desktop app patterns)
133
+ */
134
+ async trackActiveWindow() {
135
+ // Lazy load get-windows on first use
136
+ const getWindowsFn = await loadGetWindows();
137
+
138
+ if (this.failedAttempts >= this.maxFailedAttempts) {
139
+ logger.warn('Too many failed attempts, stopping application tracking');
140
+ this.stop();
141
+ return;
142
+ }
143
+
144
+ try {
145
+ let activeWindow = null;
146
+
147
+ // Try native module
148
+ if (getWindowsFn) {
149
+ activeWindow = getWindowsFn();
150
+ } else {
151
+ if (this.failedAttempts === 0) {
152
+ logger.info('Application tracking unavailable (get-windows module not available)');
153
+ }
154
+ this.failedAttempts = this.maxFailedAttempts;
155
+ this.stop();
156
+ return;
157
+ }
158
+
159
+ if (activeWindow) {
160
+ const appName = this.normalizeAppName(activeWindow.owner?.name);
161
+ const appId = activeWindow.owner?.bundleId || activeWindow.owner?.path;
162
+
163
+ if (appName) {
164
+ // Track unique apps
165
+ this.trackedApps.add(appName);
166
+
167
+ // Create event object (same format as desktop app)
168
+ const event = {
169
+ title: activeWindow.title,
170
+ time: Date.now(),
171
+ owner: {
172
+ id: appId,
173
+ name: appName
174
+ }
175
+ };
176
+
177
+ this.appEvents.push(event);
178
+
179
+ // Extract icon for this application (async, non-blocking)
180
+ if (appId) {
181
+ extractIcon({ name: appName, id: appId }).catch(error => {
182
+ logger.debug('Icon extraction failed', {
183
+ app: appName,
184
+ error: error.message
185
+ });
186
+ });
187
+ }
188
+
189
+ // Log to JSONL file (same as desktop app)
190
+ this.appendToLog(event);
191
+
192
+ // Reset failed attempts on success
193
+ this.failedAttempts = 0;
194
+
195
+ logger.silly('Tracked active window with icon extraction', {
196
+ app: appName,
197
+ title: activeWindow.title?.substring(0, 50),
198
+ hasId: !!appId
199
+ });
200
+ }
201
+ } else {
202
+ logger.debug('No active window detected');
203
+ this.failedAttempts++;
204
+ }
205
+ } catch (error) {
206
+ this.failedAttempts++;
207
+ logger.warn('Failed to get active window', {
208
+ error: error.message,
209
+ attempt: this.failedAttempts
210
+ });
211
+ }
212
+ }
213
+
214
+ /**
215
+ * Append event to JSONL log file
216
+ */
217
+ appendToLog(event) {
218
+ try {
219
+ const line = JSON.stringify(event) + '\n';
220
+ fs.appendFileSync(this.logFile, line);
221
+ } catch (error) {
222
+ logger.warn('Failed to write to tracking log', {
223
+ error: error.message,
224
+ logFile: this.logFile
225
+ });
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Normalize application name (same logic as desktop app)
231
+ */
232
+ normalizeAppName(name) {
233
+ if (!name) return null;
234
+
235
+ // Remove .exe extension and convert to lowercase
236
+ name = name.split('.exe')[0];
237
+ name = name.toLowerCase();
238
+ return name;
239
+ }
240
+
241
+ /**
242
+ * Get tracked applications and their icons (enhanced with actual icon data)
243
+ */
244
+ getResults() {
245
+ const apps = Array.from(this.trackedApps);
246
+
247
+ // Extract actual icon data for each app
248
+ const icons = apps.map(appName => {
249
+ const iconData = getIconData(appName, false);
250
+
251
+ if (iconData) {
252
+ return {
253
+ name: appName,
254
+ extension: iconData.extension,
255
+ file: iconData.file // Actual file path to extracted icon
256
+ };
257
+ } else {
258
+ // Fallback for apps without extracted icons
259
+ return {
260
+ name: appName,
261
+ extension: 'png',
262
+ file: null
263
+ };
264
+ }
265
+ });
266
+
267
+ return {
268
+ apps,
269
+ icons,
270
+ events: this.appEvents,
271
+ logFile: this.logFile // Include path to JSONL log
272
+ };
273
+ }
274
+
275
+ /**
276
+ * Get current tracking status
277
+ */
278
+ getStatus() {
279
+ return {
280
+ isTracking: this.isTracking,
281
+ uniqueApps: this.trackedApps.size,
282
+ totalEvents: this.appEvents.length,
283
+ trackingDir: this.trackingDir,
284
+ logFile: this.logFile
285
+ };
286
+ }
287
+
288
+ /**
289
+ * Clean up tracking files
290
+ */
291
+ cleanup() {
292
+ const logExit = logFunctionCall('ApplicationTracker.cleanup');
293
+
294
+ try {
295
+ if (fs.existsSync(this.logFile)) {
296
+ fs.unlinkSync(this.logFile);
297
+ logger.debug('Cleaned up tracking log file');
298
+ }
299
+ } catch (error) {
300
+ logger.warn('Failed to cleanup tracking files', { error: error.message });
301
+ }
302
+
303
+ logExit();
304
+ }
305
+ }
306
+
307
+ // Create singleton instance
308
+ const applicationTracker = new ApplicationTracker();
309
+
310
+ export { applicationTracker };
311
+ export default applicationTracker;