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.
- package/.dashcam/cli-config.json +3 -0
- package/.dashcam/recording.log +135 -0
- package/.dashcam/web-config.json +11 -0
- package/.github/RELEASE.md +59 -0
- package/.github/workflows/build.yml +103 -0
- package/.github/workflows/publish.yml +43 -0
- package/.github/workflows/release.yml +107 -0
- package/LOG_TRACKING_GUIDE.md +225 -0
- package/README.md +709 -155
- package/bin/dashcam.cjs +8 -0
- package/bin/dashcam.js +542 -0
- package/bin/index.js +63 -0
- package/examples/execute-script.js +152 -0
- package/examples/simple-test.js +37 -0
- package/lib/applicationTracker.js +311 -0
- package/lib/auth.js +222 -0
- package/lib/binaries.js +21 -0
- package/lib/config.js +34 -0
- package/lib/extension-logs/helpers.js +182 -0
- package/lib/extension-logs/index.js +347 -0
- package/lib/extension-logs/manager.js +344 -0
- package/lib/ffmpeg.js +156 -0
- package/lib/logTracker.js +23 -0
- package/lib/logger.js +118 -0
- package/lib/logs/index.js +432 -0
- package/lib/permissions.js +85 -0
- package/lib/processManager.js +255 -0
- package/lib/recorder.js +675 -0
- package/lib/store.js +58 -0
- package/lib/tracking/FileTracker.js +98 -0
- package/lib/tracking/FileTrackerManager.js +62 -0
- package/lib/tracking/LogsTracker.js +147 -0
- package/lib/tracking/active-win.js +212 -0
- package/lib/tracking/icons/darwin.js +39 -0
- package/lib/tracking/icons/index.js +167 -0
- package/lib/tracking/icons/windows.js +27 -0
- package/lib/tracking/idle.js +82 -0
- package/lib/tracking.js +23 -0
- package/lib/uploader.js +449 -0
- package/lib/utilities/jsonl.js +77 -0
- package/lib/webLogsDaemon.js +234 -0
- package/lib/websocket/server.js +223 -0
- package/package.json +50 -21
- package/recording.log +814 -0
- package/sea-bundle.mjs +34595 -0
- package/test-page.html +15 -0
- package/test.log +1 -0
- package/test_run.log +48 -0
- package/test_workflow.sh +80 -0
- package/examples/crash-test.js +0 -11
- package/examples/github-issue.sh +0 -1
- package/examples/protocol.html +0 -22
- package/index.js +0 -177
- package/lib.js +0 -199
- 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;
|