@carchalabs/seal-electron 0.0.0-stage → 0.5.0

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.
@@ -0,0 +1,103 @@
1
+ import { execFile } from 'node:child_process';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { cpus, hostname, networkInterfaces, platform } from 'node:os';
4
+ import { win32 } from 'node:path';
5
+ import { promisify } from 'node:util';
6
+
7
+ const execFileAsync = promisify(execFile);
8
+
9
+ /**
10
+ * The OS tools are run by absolute path, never looked up through PATH: an app started from a terminal inherits the
11
+ * user's PATH, and a fake `ioreg` or `reg` placed first in it could report another computer's id.
12
+ */
13
+ export const IOREG = '/usr/sbin/ioreg';
14
+ export const REG_EXE = [
15
+ 'C:\\Windows\\System32\\reg.exe',
16
+ win32.join(process.env.SystemRoot || 'C:\\Windows', 'System32', 'reg.exe'),
17
+ ].filter((file, index, all) => all.indexOf(file) === index);
18
+ export const LINUX_ID_FILES = ['/etc/machine-id', '/var/lib/dbus/machine-id'];
19
+
20
+ export interface MachineIdDeps {
21
+ platform?: NodeJS.Platform;
22
+ /** Attempts to read the OS id before giving up (default 3). */
23
+ attempts?: number;
24
+ /** Pause between attempts (default 250 ms, doubled each time). */
25
+ delayMs?: number;
26
+ /**
27
+ * Use hostname + CPU + MAC when the OS id cannot be read. Off by default: that value differs from the OS id, so the
28
+ * same computer would look like a new device and take another seat.
29
+ */
30
+ allowFallback?: boolean;
31
+ run?: (file: string, args: string[], options: { timeout: number; windowsHide: boolean }) => Promise<{ stdout: string }>;
32
+ readText?: (file: string) => Promise<string>;
33
+ }
34
+
35
+ /** The OS id could not be read; licensing on this computer cannot continue until it can. */
36
+ export class MachineIdUnavailableError extends Error {
37
+ readonly code = 'machine_id_unavailable';
38
+ constructor() {
39
+ super('This computer\'s id could not be read. Try again; if it keeps failing, contact the app\'s support.');
40
+ this.name = 'MachineIdUnavailableError';
41
+ }
42
+ }
43
+
44
+ /** Reads a stable, OS-provided machine id, retrying a few times. Throws MachineIdUnavailableError unless allowFallback. */
45
+ export async function readMachineId(deps: MachineIdDeps = {}): Promise<string> {
46
+ const attempts = Math.max(1, deps.attempts ?? 3);
47
+ let delay = deps.delayMs ?? 250;
48
+ for (let attempt = 1; attempt <= attempts; attempt++) {
49
+ const id = await readOsMachineId(deps);
50
+ if (id) return id;
51
+ if (attempt < attempts) {
52
+ await new Promise((resolve) => setTimeout(resolve, delay));
53
+ delay *= 2;
54
+ }
55
+ }
56
+ if (deps.allowFallback) return `fallback:${fallbackTraits()}`;
57
+ throw new MachineIdUnavailableError();
58
+ }
59
+
60
+ /** One attempt; null when the OS id is not available. */
61
+ async function readOsMachineId(deps: MachineIdDeps): Promise<string | null> {
62
+ const os = deps.platform ?? platform();
63
+ const run = deps.run ?? ((file, args, options) => execFileAsync(file, args, options));
64
+ const readText = deps.readText ?? ((file) => readFile(file, 'utf8'));
65
+ try {
66
+ if (os === 'darwin') {
67
+ const { stdout } = await run(IOREG, ['-rd1', '-c', 'IOPlatformExpertDevice'], { timeout: 3000, windowsHide: true });
68
+ const m = stdout.match(/"IOPlatformUUID"\s*=\s*"([^"]+)"/);
69
+ if (m) return `mac:${m[1]}`;
70
+ } else if (os === 'win32') {
71
+ for (const reg of REG_EXE) {
72
+ try {
73
+ // /reg:64 reads the 64-bit registry view, so a 32-bit build sees the same MachineGuid.
74
+ const { stdout } = await run(reg, ['query', 'HKLM\\SOFTWARE\\Microsoft\\Cryptography', '/v', 'MachineGuid', '/reg:64'], {
75
+ timeout: 3000,
76
+ windowsHide: true,
77
+ });
78
+ const m = stdout.match(/MachineGuid\s+REG_SZ\s+([\w-]+)/i);
79
+ if (m) return `win:${m[1]}`;
80
+ } catch {
81
+ /* try the next location */
82
+ }
83
+ }
84
+ } else {
85
+ for (const f of LINUX_ID_FILES) {
86
+ const id = (await readText(f).catch(() => '')).trim();
87
+ if (id) return `linux:${id}`;
88
+ }
89
+ }
90
+ } catch {
91
+ /* fall through */
92
+ }
93
+ return null;
94
+ }
95
+
96
+ function fallbackTraits(): string {
97
+ const macs = Object.values(networkInterfaces())
98
+ .flat()
99
+ .filter((i) => i && !i.internal && i.mac && i.mac !== '00:00:00:00:00:00')
100
+ .map((i) => i!.mac)
101
+ .sort();
102
+ return [hostname(), cpus()[0]?.model ?? '', macs[0] ?? ''].join('|');
103
+ }