dashcam 0.8.4 → 1.0.1-beta.3
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 +549 -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 -158
- package/lib.js +0 -199
- package/recorder.js +0 -85
package/lib/store.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { APP } from './config.js';
|
|
4
|
+
import { logger } from './logger.js';
|
|
5
|
+
|
|
6
|
+
class Store {
|
|
7
|
+
constructor(filename) {
|
|
8
|
+
this.path = path.join(APP.configDir, `${filename}.json`);
|
|
9
|
+
this.data = this.load();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
load() {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(this.path)) {
|
|
15
|
+
const data = fs.readFileSync(this.path, 'utf8');
|
|
16
|
+
return JSON.parse(data);
|
|
17
|
+
}
|
|
18
|
+
} catch (error) {
|
|
19
|
+
logger.error('Failed to load store:', error);
|
|
20
|
+
}
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
save() {
|
|
25
|
+
try {
|
|
26
|
+
// Ensure directory exists
|
|
27
|
+
fs.mkdirSync(path.dirname(this.path), { recursive: true });
|
|
28
|
+
fs.writeFileSync(this.path, JSON.stringify(this.data, null, 2));
|
|
29
|
+
} catch (error) {
|
|
30
|
+
logger.error('Failed to save store:', error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get(key) {
|
|
35
|
+
return this.data[key];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
set(key, value) {
|
|
39
|
+
this.data[key] = value;
|
|
40
|
+
this.save();
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
delete(key) {
|
|
44
|
+
delete this.data[key];
|
|
45
|
+
this.save();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
has(key) {
|
|
49
|
+
return key in this.data;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
clear() {
|
|
53
|
+
this.data = {};
|
|
54
|
+
this.save();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export { Store };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { Tail } from 'tail';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
|
|
4
|
+
// Simple function to get stats for events in the last minute
|
|
5
|
+
function getStats(eventTimes = []) {
|
|
6
|
+
const endTime = Date.now();
|
|
7
|
+
const startTime = Date.now() - 60000;
|
|
8
|
+
|
|
9
|
+
let startIndex = 0;
|
|
10
|
+
let count = 0;
|
|
11
|
+
|
|
12
|
+
for (const time of eventTimes) {
|
|
13
|
+
if (time < startTime) startIndex++;
|
|
14
|
+
else if (time <= endTime) {
|
|
15
|
+
count++;
|
|
16
|
+
} else break;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return {
|
|
20
|
+
eventTimes: eventTimes.slice(startIndex),
|
|
21
|
+
count,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Simple ANSI escape code regex for stripping colors
|
|
26
|
+
const ansiRegex = /[\u001B\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d\\/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
27
|
+
|
|
28
|
+
function stripAnsi(string) {
|
|
29
|
+
if (typeof string !== 'string') {
|
|
30
|
+
throw new TypeError(`Expected a \`string\`, got \`${typeof string}\``);
|
|
31
|
+
}
|
|
32
|
+
return string.replace(ansiRegex, '');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export class FileTracker {
|
|
36
|
+
constructor(trackedFile, callback) {
|
|
37
|
+
this.tail = null;
|
|
38
|
+
this.eventTimes = [];
|
|
39
|
+
this.callback = callback;
|
|
40
|
+
this.trackedFile = trackedFile;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
this.tail = new Tail(this.trackedFile, { encoding: 'ascii' });
|
|
44
|
+
this.tail.on('line', (line) => {
|
|
45
|
+
const time = Date.now();
|
|
46
|
+
this.eventTimes.push(time);
|
|
47
|
+
|
|
48
|
+
// Log errors for debugging (simplified error handling)
|
|
49
|
+
if (line.toLowerCase().indexOf('error') > -1) {
|
|
50
|
+
logger.warn('Error found in log file', {
|
|
51
|
+
file: trackedFile,
|
|
52
|
+
line: stripAnsi(line).substring(0, 200)
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!this.callback) return;
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
this.callback({
|
|
60
|
+
line,
|
|
61
|
+
time,
|
|
62
|
+
logFile: this.trackedFile,
|
|
63
|
+
});
|
|
64
|
+
} catch (err) {
|
|
65
|
+
logger.error(
|
|
66
|
+
`FAILED callback for FileTracker ${this.trackedFile} with error:`,
|
|
67
|
+
err
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
this.tail.on('error', (data) => {
|
|
73
|
+
logger.error(
|
|
74
|
+
`Error in file tracker for file "${this.trackedFile}": ${data}`
|
|
75
|
+
);
|
|
76
|
+
});
|
|
77
|
+
} catch (e) {
|
|
78
|
+
logger.error('Failed to create FileTracker', { trackedFile, error: e });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
destroy() {
|
|
83
|
+
if (this.tail) {
|
|
84
|
+
this.tail.unwatch();
|
|
85
|
+
this.tail = null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
getStats() {
|
|
90
|
+
const { eventTimes, count } = getStats(this.eventTimes);
|
|
91
|
+
|
|
92
|
+
this.eventTimes = eventTimes;
|
|
93
|
+
return {
|
|
94
|
+
count,
|
|
95
|
+
item: this.trackedFile,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { logger } from '../logger.js';
|
|
2
|
+
import { FileTracker } from './FileTracker.js';
|
|
3
|
+
|
|
4
|
+
export class FileTrackerManager {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.byFilePath = {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
getStats(filePath) {
|
|
10
|
+
if (!this.byFilePath[filePath]) {
|
|
11
|
+
return { item: filePath, count: 0 };
|
|
12
|
+
}
|
|
13
|
+
return this.byFilePath[filePath].tracker.getStats();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
subscribe(filePath, callback) {
|
|
17
|
+
if (!this.byFilePath[filePath]) {
|
|
18
|
+
this.byFilePath[filePath] = {
|
|
19
|
+
callbacks: [],
|
|
20
|
+
tracker: new FileTracker(filePath, (event) => {
|
|
21
|
+
this.#sendEvent(filePath, event);
|
|
22
|
+
})
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
this.byFilePath[filePath].callbacks.push(callback);
|
|
26
|
+
return () => this.unsubscribe(filePath, callback);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
unsubscribe(filePath, callback) {
|
|
30
|
+
if (!this.byFilePath[filePath]) return;
|
|
31
|
+
|
|
32
|
+
this.byFilePath[filePath].callbacks = this.byFilePath[filePath].callbacks
|
|
33
|
+
.filter(cb => cb !== callback);
|
|
34
|
+
|
|
35
|
+
if (this.byFilePath[filePath].callbacks.length === 0) {
|
|
36
|
+
this.byFilePath[filePath].tracker.destroy();
|
|
37
|
+
delete this.byFilePath[filePath];
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
#sendEvent(filePath, event) {
|
|
42
|
+
if (!this.byFilePath[filePath]) return;
|
|
43
|
+
|
|
44
|
+
for (const callback of this.byFilePath[filePath].callbacks) {
|
|
45
|
+
try {
|
|
46
|
+
callback(event);
|
|
47
|
+
} catch (error) {
|
|
48
|
+
logger.error(
|
|
49
|
+
"Failed sending FileTracker event",
|
|
50
|
+
{ event, filePath, error }
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
destroy() {
|
|
57
|
+
for (const filePath of Object.keys(this.byFilePath)) {
|
|
58
|
+
this.byFilePath[filePath].tracker.destroy();
|
|
59
|
+
}
|
|
60
|
+
this.byFilePath = {};
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { logger } from '../logger.js';
|
|
3
|
+
import { FileTrackerManager } from './FileTrackerManager.js';
|
|
4
|
+
import { jsonl } from '../utilities/jsonl.js';
|
|
5
|
+
|
|
6
|
+
export class LogsTracker {
|
|
7
|
+
// Omitting a directory puts it in a watch only mode where it
|
|
8
|
+
// only collect per minute stats
|
|
9
|
+
constructor({ config = {}, directory, fileTrackerManager }) {
|
|
10
|
+
this.files = {};
|
|
11
|
+
this.fileIndex = 0;
|
|
12
|
+
this.fileToIndex = {};
|
|
13
|
+
this.config = config;
|
|
14
|
+
this.isWatchOnly = !directory;
|
|
15
|
+
this.fileTrackerManager = fileTrackerManager || new FileTrackerManager();
|
|
16
|
+
|
|
17
|
+
const filename = 'dashcam_logs_cli.jsonl';
|
|
18
|
+
this.fileLocation = this.isWatchOnly ? '' : path.join(directory, filename);
|
|
19
|
+
|
|
20
|
+
// Start tracking files from initial config
|
|
21
|
+
this._updateTrackedFiles();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
updateConfig(config) {
|
|
25
|
+
this.config = config;
|
|
26
|
+
this._updateTrackedFiles();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
_updateTrackedFiles() {
|
|
30
|
+
const updatedFilePaths = Object.keys(this.config);
|
|
31
|
+
updatedFilePaths.forEach((filePath) => this._startFileTracker(filePath));
|
|
32
|
+
|
|
33
|
+
Object.keys(this.files)
|
|
34
|
+
.filter((filePath) => !updatedFilePaths.includes(filePath))
|
|
35
|
+
.forEach((filePath) => this._stopFileTracker(filePath));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
_startFileTracker(filePath) {
|
|
39
|
+
if (this.files[filePath]) return;
|
|
40
|
+
|
|
41
|
+
const index = ++this.fileIndex;
|
|
42
|
+
this.fileToIndex[filePath] = index;
|
|
43
|
+
const status = {
|
|
44
|
+
item: index,
|
|
45
|
+
count: 0,
|
|
46
|
+
};
|
|
47
|
+
const callback = (event) => {
|
|
48
|
+
if (!this.fileLocation) return;
|
|
49
|
+
jsonl.append(this.fileLocation, {
|
|
50
|
+
...event,
|
|
51
|
+
logFile: filePath,
|
|
52
|
+
});
|
|
53
|
+
status.count++;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.files[filePath] = {
|
|
57
|
+
status,
|
|
58
|
+
unsubscribe: this.fileTrackerManager.subscribe(filePath, callback),
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
logger.info(`Started tracking logs for ${filePath}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_stopFileTracker(filePath) {
|
|
65
|
+
const unsubscribe = this.files[filePath]?.unsubscribe;
|
|
66
|
+
if (unsubscribe) {
|
|
67
|
+
delete this.fileToIndex[filePath];
|
|
68
|
+
unsubscribe();
|
|
69
|
+
delete this.files[filePath];
|
|
70
|
+
logger.info(`Stopped tracking logs for ${filePath}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Legacy methods for backwards compatibility
|
|
75
|
+
startTracking(filePath) {
|
|
76
|
+
if (!this.config[filePath]) {
|
|
77
|
+
this.config[filePath] = true;
|
|
78
|
+
this._updateTrackedFiles();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
stopTracking(filePath) {
|
|
83
|
+
if (this.config[filePath]) {
|
|
84
|
+
delete this.config[filePath];
|
|
85
|
+
this._updateTrackedFiles();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
startRecording(recordingPath) {
|
|
90
|
+
this.fileLocation = recordingPath;
|
|
91
|
+
logger.info(`Started recording to ${recordingPath}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
stopRecording() {
|
|
95
|
+
this.fileLocation = null;
|
|
96
|
+
logger.info('Stopped recording');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
getStatus() {
|
|
100
|
+
let items = [];
|
|
101
|
+
if (this.isWatchOnly) {
|
|
102
|
+
items = Object.keys(this.files).map((filePath) => ({
|
|
103
|
+
...this.fileTrackerManager.getStats(filePath),
|
|
104
|
+
item: this.fileToIndex[filePath],
|
|
105
|
+
}));
|
|
106
|
+
} else {
|
|
107
|
+
items = Object.values(this.files).map(({ status }) => status);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const totalCount = items.reduce((acc, status) => acc + status.count, 0);
|
|
111
|
+
|
|
112
|
+
return [
|
|
113
|
+
{
|
|
114
|
+
id: 'CLI',
|
|
115
|
+
name: 'CLI',
|
|
116
|
+
type: 'cli',
|
|
117
|
+
fileLocation: this.fileLocation,
|
|
118
|
+
items: items,
|
|
119
|
+
count: totalCount,
|
|
120
|
+
},
|
|
121
|
+
];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Legacy method for backwards compatibility
|
|
125
|
+
getStats() {
|
|
126
|
+
const status = this.getStatus();
|
|
127
|
+
return status.length > 0 ? status[0] : {
|
|
128
|
+
id: 'CLI',
|
|
129
|
+
name: 'CLI',
|
|
130
|
+
type: 'cli',
|
|
131
|
+
fileLocation: this.fileLocation,
|
|
132
|
+
items: [],
|
|
133
|
+
count: 0
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
destroy() {
|
|
138
|
+
const status = this.getStatus();
|
|
139
|
+
for (const filePath of Object.keys(this.files)) {
|
|
140
|
+
this._stopFileTracker(filePath);
|
|
141
|
+
}
|
|
142
|
+
this.fileTrackerManager.destroy();
|
|
143
|
+
this.fileLocation = null;
|
|
144
|
+
logger.info('Destroyed log tracker');
|
|
145
|
+
return status;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const { logger, logFunctionCall } = require("../logger.js");
|
|
5
|
+
const { extractIcon } = require("./icons");
|
|
6
|
+
|
|
7
|
+
// Try to load get-windows, but handle gracefully if it's not available (e.g., in pkg builds)
|
|
8
|
+
let activeWindowSync = null;
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
const getWindows = require("get-windows");
|
|
12
|
+
activeWindowSync = getWindows.activeWindowSync;
|
|
13
|
+
logger.debug("Successfully loaded get-windows module");
|
|
14
|
+
} catch (error) {
|
|
15
|
+
logger.debug("get-windows not available, application tracking will be disabled");
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const normalizeAppName = (name) => {
|
|
19
|
+
name = name.split(".exe")[0];
|
|
20
|
+
name = name.toLowerCase();
|
|
21
|
+
return name;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// Simple JSONL utilities for CLI
|
|
25
|
+
const jsonl = {
|
|
26
|
+
append: (filePath, data) => {
|
|
27
|
+
const line = JSON.stringify(data) + '\n';
|
|
28
|
+
fs.appendFileSync(filePath, line);
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
class ActiveWin {
|
|
33
|
+
constructor({ recorderId, screenId, directory, fileName }) {
|
|
34
|
+
this.recorderId = recorderId;
|
|
35
|
+
this.screenId = screenId;
|
|
36
|
+
this.directory = directory;
|
|
37
|
+
this.fileName = fileName;
|
|
38
|
+
|
|
39
|
+
this.intervalRef = null;
|
|
40
|
+
this.failedAttempts = 0;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async trackActiveWin() {
|
|
44
|
+
if (this.failedAttempts === 5) {
|
|
45
|
+
logger.warn("active-win.js failed 5 attempts, stopping interval", {
|
|
46
|
+
recorderId: this.recorderId,
|
|
47
|
+
screenId: this.screenId,
|
|
48
|
+
});
|
|
49
|
+
this.stop();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
let awin = null;
|
|
55
|
+
|
|
56
|
+
// Try native module
|
|
57
|
+
if (activeWindowSync) {
|
|
58
|
+
awin = activeWindowSync();
|
|
59
|
+
} else {
|
|
60
|
+
// No tracking method available
|
|
61
|
+
if (this.failedAttempts === 0) {
|
|
62
|
+
logger.debug("Active window tracking unavailable (get-windows not available)");
|
|
63
|
+
}
|
|
64
|
+
this.failedAttempts = 5;
|
|
65
|
+
this.stop();
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (awin) {
|
|
70
|
+
const result = {
|
|
71
|
+
title: awin.title,
|
|
72
|
+
time: Date.now(),
|
|
73
|
+
owner: {
|
|
74
|
+
id: awin.owner?.bundleId || awin.owner?.path,
|
|
75
|
+
name: normalizeAppName(awin.owner?.name),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
// Extract icon for this application
|
|
80
|
+
await extractIcon({ name: result.owner.name, id: result.owner.id });
|
|
81
|
+
|
|
82
|
+
// Write to JSONL file
|
|
83
|
+
jsonl.append(path.join(this.directory, this.fileName), result);
|
|
84
|
+
|
|
85
|
+
logger.silly("Tracked active window", {
|
|
86
|
+
app: result.owner.name,
|
|
87
|
+
title: result.title?.substring(0, 50)
|
|
88
|
+
});
|
|
89
|
+
} else {
|
|
90
|
+
logger.debug("active-win.js activeWindowSync() returned nullable value");
|
|
91
|
+
this.failedAttempts++;
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
logger.warn("active-win.js error", {
|
|
95
|
+
error: err.message,
|
|
96
|
+
attempt: this.failedAttempts + 1
|
|
97
|
+
});
|
|
98
|
+
this.failedAttempts++;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
start() {
|
|
103
|
+
const logExit = logFunctionCall('ActiveWin.start');
|
|
104
|
+
|
|
105
|
+
logger.debug("active-win.js starting tracking active win", {
|
|
106
|
+
recorderId: this.recorderId,
|
|
107
|
+
screenId: this.screenId,
|
|
108
|
+
directory: this.directory,
|
|
109
|
+
fileName: this.fileName,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (!this.screenId || !this.directory || !this.fileName) {
|
|
113
|
+
logger.warn("active-win.js missing args", {
|
|
114
|
+
recorderId: this.recorderId,
|
|
115
|
+
screenId: this.screenId,
|
|
116
|
+
});
|
|
117
|
+
logExit();
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!this.intervalRef) {
|
|
122
|
+
logger.debug("active-win.js active win start interval");
|
|
123
|
+
this.intervalRef = setInterval(() => this.trackActiveWin(), 10000);
|
|
124
|
+
} else {
|
|
125
|
+
logger.debug("active-win.js active win already started", {
|
|
126
|
+
recorderId: this.recorderId,
|
|
127
|
+
screenId: this.screenId,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
logExit();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
stop = () => {
|
|
135
|
+
const logExit = logFunctionCall('ActiveWin.stop');
|
|
136
|
+
|
|
137
|
+
logger.debug("active-win.js removing tracker for screen", {
|
|
138
|
+
recorderId: this.recorderId,
|
|
139
|
+
screenId: this.screenId,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
if (this.intervalRef) {
|
|
143
|
+
clearInterval(this.intervalRef);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
this.intervalRef = null;
|
|
147
|
+
logExit();
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
class ActiveWinManager {
|
|
152
|
+
constructor() {
|
|
153
|
+
this.activeWins = [];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
startNew({ recorderId, screenId, directory, fileName }) {
|
|
157
|
+
const logExit = logFunctionCall('ActiveWinManager.startNew');
|
|
158
|
+
|
|
159
|
+
const activeWin = new ActiveWin({
|
|
160
|
+
recorderId,
|
|
161
|
+
screenId,
|
|
162
|
+
directory,
|
|
163
|
+
fileName,
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
activeWin.start();
|
|
167
|
+
this.activeWins.push(activeWin);
|
|
168
|
+
|
|
169
|
+
logger.debug("Started new active window tracker", {
|
|
170
|
+
recorderId,
|
|
171
|
+
screenId,
|
|
172
|
+
totalTrackers: this.activeWins.length
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
logExit();
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
stop({ recorderId, screenId }) {
|
|
179
|
+
const logExit = logFunctionCall('ActiveWinManager.stop');
|
|
180
|
+
|
|
181
|
+
const index = this.activeWins.findIndex(
|
|
182
|
+
(win) => win.recorderId === recorderId && win.screenId === screenId
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
if (index > -1) {
|
|
186
|
+
const activeWin = this.activeWins[index];
|
|
187
|
+
activeWin.stop();
|
|
188
|
+
this.activeWins.splice(index, 1);
|
|
189
|
+
|
|
190
|
+
logger.debug("Stopped active window tracker", {
|
|
191
|
+
recorderId,
|
|
192
|
+
screenId,
|
|
193
|
+
remainingTrackers: this.activeWins.length
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
logExit();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
stopAll() {
|
|
201
|
+
const logExit = logFunctionCall('ActiveWinManager.stopAll');
|
|
202
|
+
|
|
203
|
+
logger.info("active-win.js stopping all active wins");
|
|
204
|
+
this.activeWins.forEach((activeWin) => activeWin.stop());
|
|
205
|
+
this.activeWins = [];
|
|
206
|
+
|
|
207
|
+
logExit();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const activeWinManager = new ActiveWinManager();
|
|
212
|
+
module.exports = activeWinManager;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { logger } from "../../logger.js";
|
|
2
|
+
|
|
3
|
+
const getIconAsBuffer = async (bundleId) => {
|
|
4
|
+
try {
|
|
5
|
+
// Try to import the file-icon package for macOS icon extraction
|
|
6
|
+
// This may fail in pkg builds where native modules don't work
|
|
7
|
+
const { fileIconToBuffer } = await import("file-icon");
|
|
8
|
+
|
|
9
|
+
logger.debug("Extracting icon for macOS app", { bundleId });
|
|
10
|
+
|
|
11
|
+
const buffer = await fileIconToBuffer(bundleId);
|
|
12
|
+
if (!buffer) {
|
|
13
|
+
logger.debug("No icon buffer returned for bundle", { bundleId });
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
logger.debug("Successfully extracted macOS icon", {
|
|
18
|
+
bundleId,
|
|
19
|
+
bufferSize: buffer.length
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
return { extension: "png", buffer };
|
|
23
|
+
} catch (error) {
|
|
24
|
+
// Don't log warnings for module loading errors in pkg builds
|
|
25
|
+
if (error.code === 'MODULE_NOT_FOUND' || error.message.includes('Dynamic require')) {
|
|
26
|
+
logger.debug("Icon extraction unavailable (native module not loaded)", {
|
|
27
|
+
isPkg: typeof process.pkg !== 'undefined'
|
|
28
|
+
});
|
|
29
|
+
} else {
|
|
30
|
+
logger.warn("Failed to extract macOS icon", {
|
|
31
|
+
bundleId,
|
|
32
|
+
error: error.message
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export { getIconAsBuffer };
|