/** * 设备指纹采集 — TypeScript 实现 * * 与 device.py 逻辑一致,保留原 Python 方法不变。 * 通过采集 UUID / CPU / HDD 三项硬件 ID,拼接后做 SHA256 得到 device_id。 * * 适用环境: * - Node.js / Electron(Windows):通过 child_process 执行 wmic / powershell 采集硬件 ID * - 浏览器:仅提供 SHA256 哈希工具函数,硬件采集需由上层注入 */ import * as crypto from "node:crypto"; import { spawnSync } from "node:child_process"; import { platform } from "node:os"; // ============================================================ // 类型定义 // ============================================================ /** 可采集的硬件组件类型 */ export type Component = "uuid" | "cpu" | "hdd"; /** 查询方式 */ export type QueryMethod = "auto" | "ps" | "powershell" | "wmic"; /** 内部解析后的方法 */ type ResolvedMethod = "auto" | "powershell" | "wmic"; // ============================================================ // 查询配置(对应 Python _COMPONENT_QUERIES) // ============================================================ interface ComponentQuery { powershell: string; wmicArgs: string[]; headerTokens: string[]; } const COMPONENT_QUERIES: Record = { uuid: { powershell: "Get-CimInstance -Class Win32_ComputerSystemProduct | " + "Select-Object -ExpandProperty UUID", wmicArgs: ["wmic", "csproduct", "get", "uuid"], headerTokens: ["uuid"], }, cpu: { powershell: "Get-CimInstance -Class Win32_Processor | " + "Select-Object -ExpandProperty ProcessorId", wmicArgs: ["wmic", "cpu", "get", "processorid"], headerTokens: ["processorid"], }, hdd: { powershell: "Get-CimInstance -Class Win32_DiskDrive | " + "Select-Object -First 1 -ExpandProperty SerialNumber", wmicArgs: ["wmic", "diskdrive", "get", "serialnumber"], headerTokens: ["serialnumber"], }, }; // ============================================================ // 工具函数 // ============================================================ /** 对应 Python: _normalize_identifier(value) -> str */ function normalizeIdentifier(value: string): string { if (!value) return ""; return value.replace(/[^A-Za-z0-9]/g, "").toUpperCase(); } /** 对应 Python: _first_value_from_output(raw_output, header_tokens) -> str */ function firstValueFromOutput( rawOutput: string, headerTokens: readonly string[], ): string { if (!rawOutput) return ""; const headers = new Set(headerTokens.map((t) => t.toLowerCase())); for (const line of rawOutput.split(/\r?\n/)) { const candidate = line.trim(); if (!candidate) continue; if (headers.has(candidate.toLowerCase())) continue; return candidate; } return ""; } /** 对应 Python: _resolve_method(method) -> _ResolvedMethod */ function resolveMethod(method: QueryMethod): ResolvedMethod { if (method === "ps") return "powershell"; return method; } /** 计算 SHA256 哈希(hex 大写),对应 Python hashlib.sha256(...).hexdigest().upper() */ function sha256HexUpper(input: string): string { return crypto.createHash("sha256").update(input, "utf-8").digest("hex").toUpperCase(); } // ============================================================ // 环境检测 // ============================================================ function isWindows(): boolean { return platform() === "win32"; } function isWmicAvailable(): boolean { if (!isWindows()) return false; const result = spawnSync("where", ["wmic"], { timeout: 3000, stdio: "ignore", windowsHide: true, }); return result.status === 0; } function isPowershellAvailable(): boolean { if (!isWindows()) return false; const result = spawnSync("where", ["powershell"], { timeout: 3000, stdio: "ignore", windowsHide: true, }); return result.status === 0; } // ============================================================ // 进程执行 // ============================================================ /** * 执行命令并返回 stdout(去除首尾空白)。 * 对应 Python: _run_process(args, timeout=5.0) -> str */ function runProcess(args: string[], timeoutSec: number = 5.0): string { const command = args[0]; const cmdArgs = args.slice(1); try { const result = spawnSync(command, cmdArgs, { timeout: Math.round(timeoutSec * 1000), encoding: "utf-8", stdio: ["ignore", "pipe", "pipe"], windowsHide: true, // 对应 Python STARTF_USESHOWWINDOW }); if (result.status !== 0) return ""; return (result.stdout ?? "").trim(); } catch { return ""; } } /** 对应 Python: _run_powershell(command) -> str */ function runPowershell(command: string): string { if (!isPowershellAvailable()) return ""; const args = ["powershell", "-NoProfile", "-NonInteractive", "-Command", command]; return runProcess(args); } /** 对应 Python: _run_wmic(args) -> str */ function runWmic(args: readonly string[]): string { if (!isWmicAvailable()) return ""; return runProcess([...args]); } // ============================================================ // DeviceFingerprint 类 // ============================================================ export class DeviceFingerprint { // private wmicAvailableCache: boolean | null = null; // private powershellAvailableCache: boolean | null = null; // ---------- 组件 ID 查询 ---------- /** * 查询单个硬件组件 ID。 * 对应 Python: get_component_id(component, method="auto") -> str */ getComponentId(component: Component, method: QueryMethod = "auto"): string { const query = COMPONENT_QUERIES[component]; if (!query) return ""; const resolvedMethod = resolveMethod(method); const readViaPowershell = (): string => { const rawOutput = runPowershell(query.powershell); return firstValueFromOutput(rawOutput, query.headerTokens); }; const readViaWmic = (): string => { const rawOutput = runWmic(query.wmicArgs); return firstValueFromOutput(rawOutput, query.headerTokens); }; let rawValue = ""; if (resolvedMethod === "powershell") { rawValue = readViaPowershell(); } else if (resolvedMethod === "wmic") { rawValue = readViaWmic(); if (!rawValue) { rawValue = readViaPowershell(); } } else { // auto: wmic 优先,回退 powershell rawValue = readViaWmic() || readViaPowershell(); } return normalizeIdentifier(rawValue); } /** * 查询单个硬件组件(别名)。 * 对应 Python: get_component(component_type, method="auto") -> str */ getComponent(component: Component, method: QueryMethod = "auto"): string { return this.getComponentId(component, method); } // ---------- 机器指纹 ---------- /** * 生成设备指纹 SHA256。 * 采集 UUID + HDD + CPU,拼接后做 SHA256 哈希。 * 对应 Python: fingerprint_sha256(method="auto") -> str | None */ fingerprintSha256(method: QueryMethod = "auto"): string | null { const resolvedMethod = resolveMethod(method); const wmicOk = isWmicAvailable(); const psOk = isPowershellAvailable(); if (resolvedMethod === "powershell") { if (!psOk) return null; } else { if (!wmicOk && !psOk) return null; } const uuidId = this.getComponentId("uuid", method); const diskId = this.getComponentId("hdd", method); const cpuId = this.getComponentId("cpu", method); if (!uuidId && !diskId && !cpuId) { return null; } const combined = `${uuidId}|${diskId}|${cpuId}`; const normalized = normalizeIdentifier(combined); return sha256HexUpper(normalized); } /** * 获取机器码(指纹的别名)。 * 对应 Python: get_machine_code(method="auto") -> str | None */ getMachineCode(method: QueryMethod = "auto"): string | null { return this.fingerprintSha256(method); } } // ============================================================ // 便捷函数(无需实例化) // ============================================================ const defaultFingerprint = new DeviceFingerprint(); /** 获取机器码(单例便捷调用) */ export function getMachineCode(method: QueryMethod = "auto"): string | null { return defaultFingerprint.getMachineCode(method); } /** 获取设备指纹 SHA256(单例便捷调用) */ export function fingerprintSha256(method: QueryMethod = "auto"): string | null { return defaultFingerprint.fingerprintSha256(method); } /** 查询单个硬件组件 ID(单例便捷调用) */ export function getComponentId(component: Component, method: QueryMethod = "auto"): string { return defaultFingerprint.getComponentId(component, method); }