import { execSync, execFileSync } from "child_process"; import { existsSync, readFileSync } from "fs"; /** * Get raw machine ID using OS-specific methods. * * We use try/catch waterfall: try each OS method and fall through * to the next on failure. Platform checks are INSIDE try blocks so they * run at RUNTIME (not build time), avoiding Next.js SWC dead-code elimination. * * On Linux: skips Windows (REG.exe) and macOS (ioreg) strategies entirely. */ function getMachineIdRaw(): string { // Strategy 1: Windows — REG.exe query for MachineGuid try { if (process.platform !== "win32") { throw new Error("Not Windows"); } const sysRoot = process.env.SystemRoot || process.env.windir || "C:\\Windows"; const regPath = `${sysRoot}\\System32\\REG.exe`; if (existsSync(regPath)) { const output = execFileSync( regPath, ["QUERY", "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Cryptography", "/v", "MachineGuid"], { encoding: "utf8", timeout: 5000 } ); const id = output .split("REG_SZ")[1] ?.replace(/\r+|\n+|\s+/gi, "") ?.toLowerCase(); if (id && id.length > 8) return id; } } catch { // Not Windows or REG.exe failed — continue } // Strategy 2: macOS — ioreg IOPlatformUUID try { if (process.platform !== "darwin") { throw new Error("Not macOS"); } const output = execSync("ioreg -rd1 -c IOPlatformExpertDevice", { encoding: "utf8", timeout: 5000, }); if (output.includes("IOPlatformUUID")) { const id = output .split("IOPlatformUUID")[1] ?.split("\n")[0] ?.replace(/=|\s+|"/gi, "") ?.toLowerCase(); if (id && id.length > 8) return id; } } catch { // Not macOS or ioreg not available — continue } // Strategy 3: Linux — read machine-id files directly (no `head` or pipe) try { for (const filePath of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) { if (existsSync(filePath)) { const content = readFileSync(filePath, "utf8").trim().toLowerCase(); if (content.length > 8) return content; } } } catch { // Files not readable — continue } // Strategy 4: Hostname fallback (works on all platforms) try { const hostname = execSync("hostname", { encoding: "utf8", timeout: 5000 }); const id = hostname.trim().toLowerCase(); if (id) return id; } catch { // hostname failed — continue } // Strategy 5: Node.js os.hostname() (no exec needed) try { const os = require("os"); return os.hostname().toLowerCase(); } catch { // Final fallback } return "unknown-machine"; } /** * Get consistent machine ID using native registry/OS query with salt * This ensures the same physical machine gets the same ID across runs * * @param {string} salt - Optional salt to use (defaults to environment variable) * @returns {Promise} Machine ID (16-character base32) */ export async function getConsistentMachineId(salt = null) { const saltValue = salt || process.env.MACHINE_ID_SALT || "endpoint-proxy-salt"; try { const rawMachineId = getMachineIdRaw(); // Create consistent ID using salt const crypto = await import("crypto"); const hashedMachineId = crypto .createHash("sha256") .update(rawMachineId + saltValue) .digest("hex"); // Return only first 16 characters for brevity return hashedMachineId.substring(0, 16); } catch (error) { console.log("Error getting machine ID:", error); // Fallback to random ID if node-machine-id fails try { const cryptoFallback = await import("crypto"); return cryptoFallback.randomUUID(); } catch { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { const r = (Math.random() * 16) | 0; const v = c == "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } } } /** * Get raw machine ID without hashing (for debugging purposes) * @returns {Promise} Raw machine ID */ export async function getRawMachineId() { try { return getMachineIdRaw(); } catch (error) { console.log("Error getting raw machine ID:", error); // Fallback to random ID if node-machine-id fails try { const cryptoFallback = await import("crypto"); return cryptoFallback.randomUUID(); } catch { return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) { const r = (Math.random() * 16) | 0; const v = c == "x" ? r : (r & 0x3) | 0x8; return v.toString(16); }); } } } /** * Check if we're running in browser or server environment * @returns {boolean} True if in browser, false if in server */ export function isBrowser() { return typeof window !== "undefined"; }