@web-auto/camo 0.2.0 → 0.2.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/LICENSE +21 -21
- package/README.md +586 -586
- package/bin/browser-service.mjs +11 -11
- package/bin/camo.mjs +22 -22
- package/package.json +48 -48
- package/scripts/build.mjs +19 -19
- package/scripts/bump-version.mjs +34 -34
- package/scripts/check-file-size.mjs +80 -80
- package/scripts/file-size-policy.json +12 -2
- package/scripts/install.mjs +76 -76
- package/scripts/release.sh +54 -54
- package/src/autoscript/action-providers/index.mjs +6 -6
- package/src/autoscript/impact-engine.mjs +78 -78
- package/src/autoscript/runtime.mjs +1017 -1017
- package/src/autoscript/schema.mjs +376 -376
- package/src/cli.mjs +405 -405
- package/src/commands/attach.mjs +141 -141
- package/src/commands/autoscript.mjs +1011 -1011
- package/src/commands/browser.mjs +1255 -1255
- package/src/commands/container.mjs +401 -401
- package/src/commands/cookies.mjs +69 -69
- package/src/commands/create.mjs +98 -98
- package/src/commands/devtools.mjs +349 -349
- package/src/commands/events.mjs +152 -152
- package/src/commands/highlight-mode.mjs +24 -24
- package/src/commands/init.mjs +68 -68
- package/src/commands/lifecycle.mjs +275 -275
- package/src/commands/mouse.mjs +45 -45
- package/src/commands/profile.mjs +46 -46
- package/src/commands/record.mjs +115 -115
- package/src/commands/system.mjs +14 -14
- package/src/commands/window.mjs +123 -123
- package/src/container/change-notifier.mjs +362 -362
- package/src/container/element-filter.mjs +143 -143
- package/src/container/index.mjs +3 -3
- package/src/container/runtime-core/checkpoint.mjs +209 -209
- package/src/container/runtime-core/index.mjs +21 -21
- package/src/container/runtime-core/operations/index.mjs +774 -774
- package/src/container/runtime-core/operations/selector-scripts.mjs +277 -277
- package/src/container/runtime-core/operations/tab-pool.mjs +746 -746
- package/src/container/runtime-core/operations/viewport.mjs +189 -189
- package/src/container/runtime-core/search.mjs +190 -190
- package/src/container/runtime-core/subscription.mjs +224 -224
- package/src/container/runtime-core/utils.mjs +94 -94
- package/src/container/runtime-core/validation.mjs +127 -127
- package/src/container/runtime-core.mjs +1 -1
- package/src/container/subscription-registry.mjs +459 -459
- package/src/core/actions.mjs +561 -561
- package/src/core/browser.mjs +266 -266
- package/src/core/index.mjs +52 -52
- package/src/core/utils.mjs +91 -91
- package/src/events/daemon-entry.mjs +33 -33
- package/src/events/daemon.mjs +80 -80
- package/src/events/progress-log.mjs +109 -109
- package/src/events/ws-server.mjs +239 -239
- package/src/lib/client.mjs +200 -200
- package/src/lifecycle/cleanup.mjs +83 -83
- package/src/lifecycle/lock.mjs +126 -126
- package/src/lifecycle/session-registry.mjs +279 -279
- package/src/lifecycle/session-view.mjs +76 -76
- package/src/lifecycle/session-watchdog.mjs +281 -281
- package/src/services/browser-service/index.js +671 -671
- package/src/services/browser-service/internal/BrowserSession.input.test.js +389 -389
- package/src/services/browser-service/internal/BrowserSession.js +325 -304
- package/src/services/browser-service/internal/ElementRegistry.js +60 -60
- package/src/services/browser-service/internal/ProfileLock.js +84 -84
- package/src/services/browser-service/internal/SessionManager.js +184 -184
- package/src/services/browser-service/internal/SessionManager.test.js +39 -39
- package/src/services/browser-service/internal/browser-session/cookies.js +144 -144
- package/src/services/browser-service/internal/browser-session/input-ops.js +222 -222
- package/src/services/browser-service/internal/browser-session/input-pipeline.js +144 -144
- package/src/services/browser-service/internal/browser-session/logging.js +46 -46
- package/src/services/browser-service/internal/browser-session/navigation.js +38 -38
- package/src/services/browser-service/internal/browser-session/page-hooks.js +442 -442
- package/src/services/browser-service/internal/browser-session/page-management.js +302 -302
- package/src/services/browser-service/internal/browser-session/page-management.test.js +148 -148
- package/src/services/browser-service/internal/browser-session/recording.js +198 -198
- package/src/services/browser-service/internal/browser-session/runtime-events.js +61 -61
- package/src/services/browser-service/internal/browser-session/session-core.js +84 -84
- package/src/services/browser-service/internal/browser-session/session-state.js +38 -38
- package/src/services/browser-service/internal/browser-session/types.js +14 -14
- package/src/services/browser-service/internal/browser-session/utils.js +95 -95
- package/src/services/browser-service/internal/browser-session/viewport-manager.js +46 -46
- package/src/services/browser-service/internal/browser-session/viewport.js +215 -215
- package/src/services/browser-service/internal/container-matcher.js +851 -851
- package/src/services/browser-service/internal/container-registry.js +182 -182
- package/src/services/browser-service/internal/engine-manager.js +259 -259
- package/src/services/browser-service/internal/fingerprint.js +203 -203
- package/src/services/browser-service/internal/heartbeat.js +137 -137
- package/src/services/browser-service/internal/logging.js +46 -46
- package/src/services/browser-service/internal/page-runtime/runtime.js +1317 -1317
- package/src/services/browser-service/internal/pageRuntime.js +28 -28
- package/src/services/browser-service/internal/runtimeInjector.js +31 -31
- package/src/services/browser-service/internal/service-process-logger.js +140 -140
- package/src/services/browser-service/internal/state-bus.js +45 -45
- package/src/services/browser-service/internal/storage-paths.js +42 -42
- package/src/services/browser-service/internal/ws-server.js +1194 -1194
- package/src/services/browser-service/internal/ws-server.test.js +58 -58
- package/src/services/browser-service/server.mjs +6 -6
- package/src/services/controller/cli-bridge.js +93 -93
- package/src/services/controller/container-index.js +50 -50
- package/src/services/controller/container-storage.js +36 -36
- package/src/services/controller/controller-actions.js +207 -207
- package/src/services/controller/controller.js +1138 -1138
- package/src/services/controller/selectors.js +54 -54
- package/src/services/controller/transport.js +125 -125
- package/src/utils/args.mjs +26 -26
- package/src/utils/browser-service.mjs +544 -544
- package/src/utils/command-log.mjs +64 -64
- package/src/utils/config.mjs +214 -214
- package/src/utils/fingerprint.mjs +181 -181
- package/src/utils/help.mjs +216 -216
- package/src/utils/js-policy.mjs +13 -13
- package/src/utils/ws-client.mjs +30 -30
package/src/commands/cookies.mjs
CHANGED
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
import { getDefaultProfile } from '../utils/config.mjs';
|
|
2
|
-
import { callAPI, ensureBrowserService } from '../utils/browser-service.mjs';
|
|
3
|
-
import { getPositionals } from '../utils/args.mjs';
|
|
4
|
-
|
|
5
|
-
export async function handleCookiesCommand(args) {
|
|
6
|
-
await ensureBrowserService();
|
|
7
|
-
|
|
8
|
-
const sub = args[1];
|
|
9
|
-
const profileId = getPositionals(args, 2)[0] || getDefaultProfile();
|
|
10
|
-
if (!profileId) throw new Error('No profile specified and no default profile set');
|
|
11
|
-
|
|
12
|
-
switch (sub) {
|
|
13
|
-
case 'get':
|
|
14
|
-
await handleGetCookies(profileId);
|
|
15
|
-
break;
|
|
16
|
-
case 'save':
|
|
17
|
-
await handleSaveCookies(profileId, args);
|
|
18
|
-
break;
|
|
19
|
-
case 'load':
|
|
20
|
-
await handleLoadCookies(profileId, args);
|
|
21
|
-
break;
|
|
22
|
-
case 'auto':
|
|
23
|
-
await handleAutoCookies(profileId, args);
|
|
24
|
-
break;
|
|
25
|
-
default:
|
|
26
|
-
throw new Error('Usage: camo cookies <get|save|load|auto> [profileId] [--path <path>] [--interval <ms>]');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
async function handleGetCookies(profileId) {
|
|
31
|
-
const result = await callAPI('getCookies', { profileId });
|
|
32
|
-
console.log(JSON.stringify(result, null, 2));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function handleSaveCookies(profileId, args) {
|
|
36
|
-
const pathIdx = args.indexOf('--path');
|
|
37
|
-
if (pathIdx === -1) throw new Error('--path <file> is required for save');
|
|
38
|
-
const cookiePath = args[pathIdx + 1];
|
|
39
|
-
const result = await callAPI('saveCookies', { profileId, path: cookiePath });
|
|
40
|
-
console.log(JSON.stringify(result, null, 2));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async function handleLoadCookies(profileId, args) {
|
|
44
|
-
const pathIdx = args.indexOf('--path');
|
|
45
|
-
if (pathIdx === -1) throw new Error('--path <file> is required for load');
|
|
46
|
-
const cookiePath = args[pathIdx + 1];
|
|
47
|
-
const result = await callAPI('loadCookies', { profileId, path: cookiePath });
|
|
48
|
-
console.log(JSON.stringify(result, null, 2));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function handleAutoCookies(profileId, args) {
|
|
52
|
-
const action = args[2];
|
|
53
|
-
if (!action) throw new Error('Usage: camo cookies auto <start|stop|status> [profileId] [--interval <ms>]');
|
|
54
|
-
|
|
55
|
-
if (action === 'start') {
|
|
56
|
-
const intervalIdx = args.indexOf('--interval');
|
|
57
|
-
const intervalMs = intervalIdx >= 0 ? parseInt(args[intervalIdx + 1]) : 2500;
|
|
58
|
-
const result = await callAPI('autoCookies:start', { profileId, intervalMs });
|
|
59
|
-
console.log(JSON.stringify(result, null, 2));
|
|
60
|
-
} else if (action === 'stop') {
|
|
61
|
-
const result = await callAPI('autoCookies:stop', { profileId });
|
|
62
|
-
console.log(JSON.stringify(result, null, 2));
|
|
63
|
-
} else if (action === 'status') {
|
|
64
|
-
const result = await callAPI('autoCookies:status', { profileId });
|
|
65
|
-
console.log(JSON.stringify(result, null, 2));
|
|
66
|
-
} else {
|
|
67
|
-
throw new Error('auto subcommand must be start, stop, or status');
|
|
68
|
-
}
|
|
69
|
-
}
|
|
1
|
+
import { getDefaultProfile } from '../utils/config.mjs';
|
|
2
|
+
import { callAPI, ensureBrowserService } from '../utils/browser-service.mjs';
|
|
3
|
+
import { getPositionals } from '../utils/args.mjs';
|
|
4
|
+
|
|
5
|
+
export async function handleCookiesCommand(args) {
|
|
6
|
+
await ensureBrowserService();
|
|
7
|
+
|
|
8
|
+
const sub = args[1];
|
|
9
|
+
const profileId = getPositionals(args, 2)[0] || getDefaultProfile();
|
|
10
|
+
if (!profileId) throw new Error('No profile specified and no default profile set');
|
|
11
|
+
|
|
12
|
+
switch (sub) {
|
|
13
|
+
case 'get':
|
|
14
|
+
await handleGetCookies(profileId);
|
|
15
|
+
break;
|
|
16
|
+
case 'save':
|
|
17
|
+
await handleSaveCookies(profileId, args);
|
|
18
|
+
break;
|
|
19
|
+
case 'load':
|
|
20
|
+
await handleLoadCookies(profileId, args);
|
|
21
|
+
break;
|
|
22
|
+
case 'auto':
|
|
23
|
+
await handleAutoCookies(profileId, args);
|
|
24
|
+
break;
|
|
25
|
+
default:
|
|
26
|
+
throw new Error('Usage: camo cookies <get|save|load|auto> [profileId] [--path <path>] [--interval <ms>]');
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function handleGetCookies(profileId) {
|
|
31
|
+
const result = await callAPI('getCookies', { profileId });
|
|
32
|
+
console.log(JSON.stringify(result, null, 2));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function handleSaveCookies(profileId, args) {
|
|
36
|
+
const pathIdx = args.indexOf('--path');
|
|
37
|
+
if (pathIdx === -1) throw new Error('--path <file> is required for save');
|
|
38
|
+
const cookiePath = args[pathIdx + 1];
|
|
39
|
+
const result = await callAPI('saveCookies', { profileId, path: cookiePath });
|
|
40
|
+
console.log(JSON.stringify(result, null, 2));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function handleLoadCookies(profileId, args) {
|
|
44
|
+
const pathIdx = args.indexOf('--path');
|
|
45
|
+
if (pathIdx === -1) throw new Error('--path <file> is required for load');
|
|
46
|
+
const cookiePath = args[pathIdx + 1];
|
|
47
|
+
const result = await callAPI('loadCookies', { profileId, path: cookiePath });
|
|
48
|
+
console.log(JSON.stringify(result, null, 2));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function handleAutoCookies(profileId, args) {
|
|
52
|
+
const action = args[2];
|
|
53
|
+
if (!action) throw new Error('Usage: camo cookies auto <start|stop|status> [profileId] [--interval <ms>]');
|
|
54
|
+
|
|
55
|
+
if (action === 'start') {
|
|
56
|
+
const intervalIdx = args.indexOf('--interval');
|
|
57
|
+
const intervalMs = intervalIdx >= 0 ? parseInt(args[intervalIdx + 1]) : 2500;
|
|
58
|
+
const result = await callAPI('autoCookies:start', { profileId, intervalMs });
|
|
59
|
+
console.log(JSON.stringify(result, null, 2));
|
|
60
|
+
} else if (action === 'stop') {
|
|
61
|
+
const result = await callAPI('autoCookies:stop', { profileId });
|
|
62
|
+
console.log(JSON.stringify(result, null, 2));
|
|
63
|
+
} else if (action === 'status') {
|
|
64
|
+
const result = await callAPI('autoCookies:status', { profileId });
|
|
65
|
+
console.log(JSON.stringify(result, null, 2));
|
|
66
|
+
} else {
|
|
67
|
+
throw new Error('auto subcommand must be start, stop, or status');
|
|
68
|
+
}
|
|
69
|
+
}
|
package/src/commands/create.mjs
CHANGED
|
@@ -1,98 +1,98 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import os from 'node:os';
|
|
4
|
-
import { CONFIG_DIR, PROFILES_DIR, ensureDir, createProfile, listProfiles } from '../utils/config.mjs';
|
|
5
|
-
import { generateFingerprint, listAvailableRegions, listAvailableOS, GEOIP_REGIONS, OS_OPTIONS } from '../utils/fingerprint.mjs';
|
|
6
|
-
|
|
7
|
-
const FINGERPRINTS_DIR = path.join(CONFIG_DIR, 'fingerprints');
|
|
8
|
-
|
|
9
|
-
export async function handleCreateCommand(args) {
|
|
10
|
-
const what = args[1];
|
|
11
|
-
|
|
12
|
-
if (what === 'fingerprint') {
|
|
13
|
-
await handleCreateFingerprint(args);
|
|
14
|
-
return;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (what === 'profile') {
|
|
18
|
-
const profileId = args[2];
|
|
19
|
-
if (!profileId) throw new Error('Usage: camo create profile <profileId>');
|
|
20
|
-
createProfile(profileId);
|
|
21
|
-
console.log(`Created profile: ${profileId}`);
|
|
22
|
-
return;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
throw new Error('Usage: camo create <profile|fingerprint> [options]');
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
async function handleCreateFingerprint(args) {
|
|
29
|
-
// Parse options
|
|
30
|
-
const osIdx = args.indexOf('--os');
|
|
31
|
-
const regionIdx = args.indexOf('--region');
|
|
32
|
-
const outputIdx = args.indexOf('--output');
|
|
33
|
-
const nameIdx = args.indexOf('--name');
|
|
34
|
-
|
|
35
|
-
const osKey = osIdx >= 0 ? args[osIdx + 1] : 'mac';
|
|
36
|
-
const regionKey = regionIdx >= 0 ? args[regionIdx + 1] : 'us';
|
|
37
|
-
const output = outputIdx >= 0 ? args[outputIdx + 1] : null;
|
|
38
|
-
const name = nameIdx >= 0 ? args[nameIdx + 1] : null;
|
|
39
|
-
|
|
40
|
-
// Validate options
|
|
41
|
-
if (!OS_OPTIONS[osKey]) {
|
|
42
|
-
console.error(`Invalid OS: ${osKey}`);
|
|
43
|
-
console.error('Available:', Object.keys(OS_OPTIONS).join(', '));
|
|
44
|
-
process.exit(1);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (!GEOIP_REGIONS[regionKey]) {
|
|
48
|
-
console.error(`Invalid region: ${regionKey}`);
|
|
49
|
-
console.error('Available:', Object.keys(GEOIP_REGIONS).join(', '));
|
|
50
|
-
process.exit(1);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Generate fingerprint
|
|
54
|
-
const fingerprint = generateFingerprint({ os: osKey, region: regionKey });
|
|
55
|
-
|
|
56
|
-
// Add metadata
|
|
57
|
-
fingerprint.name = name || `fingerprint-${osKey}-${regionKey}-${Date.now()}`;
|
|
58
|
-
fingerprint.createdAt = new Date().toISOString();
|
|
59
|
-
fingerprint.id = `fp_${Buffer.from(fingerprint.name).toString('base64').slice(0, 12)}`;
|
|
60
|
-
|
|
61
|
-
// Save or output
|
|
62
|
-
if (output) {
|
|
63
|
-
fs.writeFileSync(output, JSON.stringify(fingerprint, null, 2));
|
|
64
|
-
console.log(`Fingerprint saved to: ${output}`);
|
|
65
|
-
} else {
|
|
66
|
-
// Save to fingerprints directory
|
|
67
|
-
ensureDir(FINGERPRINTS_DIR);
|
|
68
|
-
const fpPath = path.join(FINGERPRINTS_DIR, `${fingerprint.id}.json`);
|
|
69
|
-
fs.writeFileSync(fpPath, JSON.stringify(fingerprint, null, 2));
|
|
70
|
-
console.log(JSON.stringify({
|
|
71
|
-
ok: true,
|
|
72
|
-
fingerprint,
|
|
73
|
-
path: fpPath,
|
|
74
|
-
}, null, 2));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
export function listFingerprints() {
|
|
79
|
-
if (!fs.existsSync(FINGERPRINTS_DIR)) return [];
|
|
80
|
-
return fs.readdirSync(FINGERPRINTS_DIR, { withFileTypes: true })
|
|
81
|
-
.filter((f) => f.isFile() && f.name.endsWith('.json'))
|
|
82
|
-
.map((f) => {
|
|
83
|
-
try {
|
|
84
|
-
const content = fs.readFileSync(path.join(FINGERPRINTS_DIR, f.name), 'utf8');
|
|
85
|
-
const fp = JSON.parse(content);
|
|
86
|
-
return { id: fp.id, name: fp.name, os: fp.os, country: fp.country };
|
|
87
|
-
} catch {
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
})
|
|
91
|
-
.filter(Boolean);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export function getFingerprint(id) {
|
|
95
|
-
const fpPath = path.join(FINGERPRINTS_DIR, `${id}.json`);
|
|
96
|
-
if (!fs.existsSync(fpPath)) return null;
|
|
97
|
-
return JSON.parse(fs.readFileSync(fpPath, 'utf8'));
|
|
98
|
-
}
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import { CONFIG_DIR, PROFILES_DIR, ensureDir, createProfile, listProfiles } from '../utils/config.mjs';
|
|
5
|
+
import { generateFingerprint, listAvailableRegions, listAvailableOS, GEOIP_REGIONS, OS_OPTIONS } from '../utils/fingerprint.mjs';
|
|
6
|
+
|
|
7
|
+
const FINGERPRINTS_DIR = path.join(CONFIG_DIR, 'fingerprints');
|
|
8
|
+
|
|
9
|
+
export async function handleCreateCommand(args) {
|
|
10
|
+
const what = args[1];
|
|
11
|
+
|
|
12
|
+
if (what === 'fingerprint') {
|
|
13
|
+
await handleCreateFingerprint(args);
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if (what === 'profile') {
|
|
18
|
+
const profileId = args[2];
|
|
19
|
+
if (!profileId) throw new Error('Usage: camo create profile <profileId>');
|
|
20
|
+
createProfile(profileId);
|
|
21
|
+
console.log(`Created profile: ${profileId}`);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
throw new Error('Usage: camo create <profile|fingerprint> [options]');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function handleCreateFingerprint(args) {
|
|
29
|
+
// Parse options
|
|
30
|
+
const osIdx = args.indexOf('--os');
|
|
31
|
+
const regionIdx = args.indexOf('--region');
|
|
32
|
+
const outputIdx = args.indexOf('--output');
|
|
33
|
+
const nameIdx = args.indexOf('--name');
|
|
34
|
+
|
|
35
|
+
const osKey = osIdx >= 0 ? args[osIdx + 1] : 'mac';
|
|
36
|
+
const regionKey = regionIdx >= 0 ? args[regionIdx + 1] : 'us';
|
|
37
|
+
const output = outputIdx >= 0 ? args[outputIdx + 1] : null;
|
|
38
|
+
const name = nameIdx >= 0 ? args[nameIdx + 1] : null;
|
|
39
|
+
|
|
40
|
+
// Validate options
|
|
41
|
+
if (!OS_OPTIONS[osKey]) {
|
|
42
|
+
console.error(`Invalid OS: ${osKey}`);
|
|
43
|
+
console.error('Available:', Object.keys(OS_OPTIONS).join(', '));
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!GEOIP_REGIONS[regionKey]) {
|
|
48
|
+
console.error(`Invalid region: ${regionKey}`);
|
|
49
|
+
console.error('Available:', Object.keys(GEOIP_REGIONS).join(', '));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Generate fingerprint
|
|
54
|
+
const fingerprint = generateFingerprint({ os: osKey, region: regionKey });
|
|
55
|
+
|
|
56
|
+
// Add metadata
|
|
57
|
+
fingerprint.name = name || `fingerprint-${osKey}-${regionKey}-${Date.now()}`;
|
|
58
|
+
fingerprint.createdAt = new Date().toISOString();
|
|
59
|
+
fingerprint.id = `fp_${Buffer.from(fingerprint.name).toString('base64').slice(0, 12)}`;
|
|
60
|
+
|
|
61
|
+
// Save or output
|
|
62
|
+
if (output) {
|
|
63
|
+
fs.writeFileSync(output, JSON.stringify(fingerprint, null, 2));
|
|
64
|
+
console.log(`Fingerprint saved to: ${output}`);
|
|
65
|
+
} else {
|
|
66
|
+
// Save to fingerprints directory
|
|
67
|
+
ensureDir(FINGERPRINTS_DIR);
|
|
68
|
+
const fpPath = path.join(FINGERPRINTS_DIR, `${fingerprint.id}.json`);
|
|
69
|
+
fs.writeFileSync(fpPath, JSON.stringify(fingerprint, null, 2));
|
|
70
|
+
console.log(JSON.stringify({
|
|
71
|
+
ok: true,
|
|
72
|
+
fingerprint,
|
|
73
|
+
path: fpPath,
|
|
74
|
+
}, null, 2));
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function listFingerprints() {
|
|
79
|
+
if (!fs.existsSync(FINGERPRINTS_DIR)) return [];
|
|
80
|
+
return fs.readdirSync(FINGERPRINTS_DIR, { withFileTypes: true })
|
|
81
|
+
.filter((f) => f.isFile() && f.name.endsWith('.json'))
|
|
82
|
+
.map((f) => {
|
|
83
|
+
try {
|
|
84
|
+
const content = fs.readFileSync(path.join(FINGERPRINTS_DIR, f.name), 'utf8');
|
|
85
|
+
const fp = JSON.parse(content);
|
|
86
|
+
return { id: fp.id, name: fp.name, os: fp.os, country: fp.country };
|
|
87
|
+
} catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
})
|
|
91
|
+
.filter(Boolean);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function getFingerprint(id) {
|
|
95
|
+
const fpPath = path.join(FINGERPRINTS_DIR, `${id}.json`);
|
|
96
|
+
if (!fs.existsSync(fpPath)) return null;
|
|
97
|
+
return JSON.parse(fs.readFileSync(fpPath, 'utf8'));
|
|
98
|
+
}
|