UNPKG

electron-license-kit

Version:

Secure licensing, storage, and utilities for Electron apps with Supabase

1,332 lines (1,299 loc) 39.2 kB
"use strict"; var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( // If the importer is in node compatibility mode or this is not an ESM // file that has been converted to a CommonJS file using a Babel- // compatible transform (i.e. "__esModule" has not been set), then set // "default" to the CommonJS "module.exports" for node compatibility. isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, mod )); var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { AutoUpdater: () => AutoUpdater, CrashReporter: () => CrashReporter, KeyManager: () => KeyManager, LicenseCache: () => LicenseCache, LicenseService: () => LicenseService, Logger: () => Logger, SecureStorage: () => SecureStorage, VERSION: () => VERSION, createLogger: () => createLogger, createMainWindow: () => createMainWindow, createNavigationGuard: () => createNavigationGuard, defaultConfig: () => defaultConfig, defaultTheme: () => defaultTheme, defineConfig: () => defineConfig, enforceSingleInstance: () => enforceSingleInstance, generateCSSVariables: () => generateCSSVariables, generateHWID: () => generateHWID, generateStyleTag: () => generateStyleTag, getAppPaths: () => getAppPaths, getCrashReporter: () => getCrashReporter, getHWIDComponents: () => getHWIDComponents, injectTheme: () => injectTheme, injectThemeOnReady: () => injectThemeOnReady, loadAppConfig: () => loadAppConfig, loadEnvFile: () => loadEnvFile, loadEnvFiles: () => loadEnvFiles, presets: () => presets, registerUpdaterHandlers: () => registerUpdaterHandlers, registerWindowControls: () => registerWindowControls, setupSecurityPolicy: () => setupSecurityPolicy, unregisterWindowControls: () => unregisterWindowControls, validateIpcChannel: () => validateIpcChannel }); module.exports = __toCommonJS(index_exports); // src/branding/define-config.ts var defaultTheme = { primary: "#6366f1", primaryHover: "#4f46e5", primaryText: "#ffffff", background: "#0a0a0f", backgroundSecondary: "#111118", backgroundTertiary: "#1a1a24", text: "#ffffff", textSecondary: "#a1a1aa", textTertiary: "#71717a", success: "#22c55e", warning: "#f59e0b", error: "#ef4444", info: "#3b82f6", border: "#27272a", divider: "#3f3f46", titlebar: { background: "#0a0a0f", text: "#ffffff", buttonHover: "#27272a", buttonClose: "#ef4444" } }; var defaultConfig = { app: { name: "My App", id: "com.example.myapp", version: "1.0.0" }, branding: {}, theme: defaultTheme, license: { keyPrefix: "APP", keyPattern: /^APP-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/, offlineGraceDays: 3, checkIntervalHours: 24 }, supabase: { tableName: "licenses", claimFunction: "claim_license" }, window: { width: 800, height: 600, minWidth: 700, minHeight: 500, frame: false, resizable: true }, features: { autoUpdater: true, crashReporter: true, singleInstance: true, devTools: false } }; function defineConfig(config) { return { app: { ...defaultConfig.app, ...config.app }, branding: { ...defaultConfig.branding, ...config.branding }, theme: { ...defaultConfig.theme, ...config.theme, titlebar: { ...defaultConfig.theme.titlebar, ...config.theme?.titlebar } }, license: { ...defaultConfig.license, ...config.license }, supabase: { ...defaultConfig.supabase, ...config.supabase }, window: { ...defaultConfig.window, ...config.window }, features: { ...defaultConfig.features, ...config.features } }; } // src/branding/presets.ts var darkPreset = { primary: "#6366f1", primaryHover: "#4f46e5", primaryText: "#ffffff", background: "#0a0a0f", backgroundSecondary: "#111118", backgroundTertiary: "#1a1a24", text: "#ffffff", textSecondary: "#a1a1aa", textTertiary: "#71717a", border: "#27272a", divider: "#3f3f46" }; var lightPreset = { primary: "#4f46e5", primaryHover: "#4338ca", primaryText: "#ffffff", background: "#ffffff", backgroundSecondary: "#f4f4f5", backgroundTertiary: "#e4e4e7", text: "#18181b", textSecondary: "#52525b", textTertiary: "#a1a1aa", border: "#e4e4e7", divider: "#d4d4d8" }; var midnightPreset = { primary: "#3b82f6", primaryHover: "#2563eb", primaryText: "#ffffff", background: "#0f172a", backgroundSecondary: "#1e293b", backgroundTertiary: "#334155", text: "#f1f5f9", textSecondary: "#94a3b8", textTertiary: "#64748b", border: "#334155", divider: "#475569" }; var forestPreset = { primary: "#22c55e", primaryHover: "#16a34a", primaryText: "#ffffff", background: "#0a0f0a", backgroundSecondary: "#111811", backgroundTertiary: "#1a241a", text: "#f0fdf4", textSecondary: "#86efac", textTertiary: "#4ade80", border: "#27372a", divider: "#3f4f46" }; var presets = { dark: darkPreset, light: lightPreset, midnight: midnightPreset, forest: forestPreset }; // src/branding/css-generator.ts function hexToRgb(hex) { const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); if (!result) return "0, 0, 0"; const r = result[1] ?? "00"; const g = result[2] ?? "00"; const b = result[3] ?? "00"; return `${parseInt(r, 16)}, ${parseInt(g, 16)}, ${parseInt(b, 16)}`; } function generateCSSVariables(theme) { return `:root { /* Primary */ --elk-primary: ${theme.primary}; --elk-primary-hover: ${theme.primaryHover}; --elk-primary-text: ${theme.primaryText}; --elk-primary-rgb: ${hexToRgb(theme.primary)}; /* Backgrounds */ --elk-bg: ${theme.background}; --elk-bg-secondary: ${theme.backgroundSecondary}; --elk-bg-tertiary: ${theme.backgroundTertiary}; /* Text */ --elk-text: ${theme.text}; --elk-text-secondary: ${theme.textSecondary}; --elk-text-tertiary: ${theme.textTertiary}; /* Semantic */ --elk-success: ${theme.success}; --elk-warning: ${theme.warning}; --elk-error: ${theme.error}; --elk-info: ${theme.info}; /* Border */ --elk-border: ${theme.border}; --elk-divider: ${theme.divider}; /* Titlebar */ --elk-titlebar-bg: ${theme.titlebar.background}; --elk-titlebar-text: ${theme.titlebar.text}; --elk-titlebar-btn-hover: ${theme.titlebar.buttonHover}; --elk-titlebar-btn-close: ${theme.titlebar.buttonClose}; /* Computed */ --elk-shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.5); --elk-shadow-md: 0 4px 6px rgba(0, 0, 0, 0.5); --elk-shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.5); --elk-radius-sm: 4px; --elk-radius-md: 8px; --elk-radius-lg: 12px; --elk-radius-full: 9999px; }`; } function generateStyleTag(theme) { return `<style id="elk-theme"> ${generateCSSVariables(theme)} </style>`; } // src/branding/theme-injector.ts async function injectTheme(window, theme) { const css = generateCSSVariables(theme); await window.webContents.executeJavaScript(` (function() { const existing = document.getElementById('elk-theme'); if (existing) existing.remove(); const style = document.createElement('style'); style.id = 'elk-theme'; style.textContent = ${JSON.stringify(css)}; document.head.appendChild(style); })(); `); } function injectThemeOnReady(window, theme) { window.webContents.on("dom-ready", () => { void injectTheme(window, theme); }); } // src/license/hwid-generator.ts var crypto = __toESM(require("crypto")); var os = __toESM(require("os")); var import_node_machine_id = require("node-machine-id"); function getMachineId() { try { return (0, import_node_machine_id.machineIdSync)(true); } catch { return os.hostname() + os.userInfo().username; } } function getCPUModel() { const cpus2 = os.cpus(); const first = cpus2[0]; return first?.model ?? "unknown"; } function getHWIDComponents() { return { machineId: getMachineId(), platform: os.platform(), arch: os.arch(), cpuModel: getCPUModel() }; } function generateHWID() { const components = getHWIDComponents(); const combined = [ components.machineId, components.platform, components.arch, components.cpuModel ].join("|"); const hash = crypto.createHash("sha256"); hash.update(combined); return hash.digest("hex"); } // src/storage/secure-storage.ts var crypto2 = __toESM(require("crypto")); var fs = __toESM(require("fs")); var path = __toESM(require("path")); var os2 = __toESM(require("os")); var ALGORITHM = "aes-256-gcm"; var SecureStorage = class { storageDir; filePath; encryptionKey; constructor(config) { const appName = config.appName ?? "ElectronApp"; this.storageDir = config.storageDir ?? path.join(os2.homedir(), "AppData", "Roaming", appName); this.filePath = path.join(this.storageDir, config.fileName); this.encryptionKey = config.encryptionKey ?? this.deriveKey(); } deriveKey() { const hwid = generateHWID(); return crypto2.createHash("sha256").update(hwid).digest(); } encrypt(data) { const iv = crypto2.randomBytes(16); const cipher = crypto2.createCipheriv(ALGORITHM, this.encryptionKey, iv); let encrypted = cipher.update(JSON.stringify(data), "utf8", "hex"); encrypted += cipher.final("hex"); const authTag = cipher.getAuthTag(); return { iv: iv.toString("hex"), data: encrypted, tag: authTag.toString("hex") }; } decrypt(encrypted) { try { const iv = Buffer.from(encrypted.iv, "hex"); const authTag = Buffer.from(encrypted.tag, "hex"); const decipher = crypto2.createDecipheriv(ALGORITHM, this.encryptionKey, iv); decipher.setAuthTag(authTag); let decrypted = decipher.update(encrypted.data, "hex", "utf8"); decrypted += decipher.final("utf8"); return JSON.parse(decrypted); } catch (error) { console.error("Decryption failed:", error); return null; } } save(data) { try { fs.mkdirSync(this.storageDir, { recursive: true }); const encrypted = this.encrypt(data); fs.writeFileSync(this.filePath, JSON.stringify(encrypted), "utf8"); return true; } catch (error) { console.error("Failed to save:", error); return false; } } load() { try { if (!fs.existsSync(this.filePath)) { return null; } const encrypted = JSON.parse(fs.readFileSync(this.filePath, "utf8")); return this.decrypt(encrypted); } catch (error) { console.error("Failed to load:", error); return null; } } exists() { try { return fs.existsSync(this.filePath) && this.load() !== null; } catch { return false; } } clear() { try { if (fs.existsSync(this.filePath)) { fs.unlinkSync(this.filePath); } return true; } catch (error) { console.error("Failed to clear:", error); return false; } } getPath() { return this.filePath; } }; // src/license/license-cache.ts var DEFAULT_MAX_AGE_DAYS = 3; var LicenseCache = class { storage; maxAgeDays; constructor(config) { this.storage = new SecureStorage({ fileName: config?.cacheFileName ?? "license.dat", appName: config?.appName, storageDir: config?.cacheDir }); this.maxAgeDays = config?.maxAgeDays ?? DEFAULT_MAX_AGE_DAYS; } save(data) { return this.storage.save(data); } load() { return this.storage.load(); } isValid() { const data = this.load(); if (!data) return false; if (data.lastCheck) { const lastCheck = new Date(data.lastCheck); const now = /* @__PURE__ */ new Date(); const daysSinceCheck = (now.getTime() - lastCheck.getTime()) / (1e3 * 60 * 60 * 24); if (daysSinceCheck > this.maxAgeDays) { return false; } } if (data.tier !== "lifetime" && data.expiresAt) { const expires = new Date(data.expiresAt); if (expires < /* @__PURE__ */ new Date()) { return false; } } return true; } clear() { return this.storage.clear(); } getDaysRemaining() { const data = this.load(); if (!data?.expiresAt) return null; if (data.tier === "lifetime") return Infinity; const expires = new Date(data.expiresAt); const now = /* @__PURE__ */ new Date(); const days = Math.ceil((expires.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24)); return Math.max(0, days); } }; // src/license/license-service.ts var import_supabase_js = require("@supabase/supabase-js"); // src/storage/key-manager.ts var KeyManager = class { storage; constructor(appName) { this.storage = new SecureStorage({ fileName: "key.dat", appName }); } save(licenseKey) { if (!licenseKey || typeof licenseKey !== "string") { console.error("Invalid license key"); return false; } return this.storage.save({ licenseKey, savedAt: (/* @__PURE__ */ new Date()).toISOString() }); } load() { const data = this.storage.load(); return data?.licenseKey ?? null; } exists() { return this.storage.exists(); } clear() { return this.storage.clear(); } getKeyPath() { return this.storage.getPath(); } }; // src/license/license-service.ts var DEFAULT_KEY_PATTERN = /^[A-Z]{2}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/; var LicenseService = class { supabase; cache; keyManager; keyPattern; tableName; claimRpcName; currentUser = null; currentLicense = null; constructor(config) { this.validateKey(config.supabaseAnonKey); this.supabase = (0, import_supabase_js.createClient)(config.supabaseUrl, config.supabaseAnonKey); this.cache = new LicenseCache({ appName: config.appName }); this.keyManager = new KeyManager(config.appName); this.keyPattern = config.licenseKeyPattern ?? DEFAULT_KEY_PATTERN; this.tableName = config.tableName ?? "licenses"; this.claimRpcName = config.claimRpcName ?? "claim_license"; } validateKey(key) { try { const parts = key.split("."); if (parts.length < 2) { return; } const payloadSegment = parts[1]; if (!payloadSegment) { return; } const payloadJson = Buffer.from(payloadSegment, "base64").toString(); const payload = JSON.parse(payloadJson); if (payload.role === "service_role") { throw new Error("SECURITY: Service role key detected. Use anon key only!"); } } catch (e) { if (e instanceof Error && e.message.includes("SECURITY")) throw e; } } async register(email, password, licenseKey) { if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { throw new Error("Invalid email address"); } if (!password || password.length < 8) { throw new Error("Password must be at least 8 characters"); } if (!licenseKey || !this.keyPattern.test(licenseKey.toUpperCase())) { throw new Error("Invalid license key format"); } const normalizedKey = licenseKey.toUpperCase(); const { data: license, error: licenseError } = await this.supabase.from(this.tableName).select("*").eq("license_key", normalizedKey).single(); if (licenseError) { throw new Error(licenseError.message); } if (!license) { throw new Error("License key not found"); } if (license.user_id) { throw new Error("License already activated"); } if (license.status !== "active") { throw new Error(`License is ${license.status}`); } const { data: authData, error: authError } = await this.supabase.auth.signUp({ email, password }); if (authError) { throw new Error(authError.message); } if (!authData.user) { throw new Error("Registration failed"); } const hwid = generateHWID(); const { data: claimResult, error: claimError } = await this.supabase.rpc(this.claimRpcName, { p_license_key: normalizedKey, p_user_id: authData.user.id, p_hwid: hwid }); if (claimError) { throw new Error(claimError.message); } if (!claimResult?.success) { throw new Error(claimResult?.error ?? "License activation failed"); } this.cache.save({ email, tier: license.tier, expiresAt: license.expires_at, lastCheck: (/* @__PURE__ */ new Date()).toISOString(), hwid }); this.keyManager.save(normalizedKey); this.currentUser = { id: authData.user.id, email }; this.currentLicense = { ...license, hwid, user_id: authData.user.id }; return { user: this.currentUser, license: this.currentLicense }; } async login(email, password) { if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { throw new Error("Invalid email address"); } if (!password) { throw new Error("Password is required"); } const { data: authData, error: authError } = await this.supabase.auth.signInWithPassword({ email, password }); if (authError) { throw new Error(authError.message); } if (!authData.user) { throw new Error("Login failed"); } const { data: license, error: licenseError } = await this.supabase.from(this.tableName).select("*").eq("user_id", authData.user.id).eq("status", "active").single(); if (licenseError) { throw new Error(licenseError.message); } if (!license) { throw new Error("No active license found"); } const hwid = generateHWID(); if (license.hwid && license.hwid !== hwid) { throw new Error("License bound to different computer"); } await this.supabase.from(this.tableName).update({ last_check: (/* @__PURE__ */ new Date()).toISOString(), hwid: license.hwid ?? hwid }).eq("id", license.id); this.cache.save({ email, tier: license.tier, expiresAt: license.expires_at, lastCheck: (/* @__PURE__ */ new Date()).toISOString(), hwid }); if (license.license_key) { this.keyManager.save(license.license_key); } this.currentUser = { id: authData.user.id, email }; this.currentLicense = license; return { user: this.currentUser, license }; } async validate() { try { const { data: sessionData } = await this.supabase.auth.getSession(); const session = sessionData.session; if (session) { const { data: license } = await this.supabase.from(this.tableName).select("*").eq("user_id", session.user.id).eq("status", "active").single(); if (license) { const hwid = generateHWID(); if (license.hwid && license.hwid !== hwid) { return { valid: false, error: "HWID mismatch" }; } this.cache.save({ email: session.user.email ?? "", tier: license.tier, expiresAt: license.expires_at, lastCheck: (/* @__PURE__ */ new Date()).toISOString(), hwid }); this.currentUser = { id: session.user.id, email: session.user.email ?? "" }; this.currentLicense = license; return { valid: true, license: this.cache.load(), user: this.currentUser, source: "online" }; } } } catch { } if (this.cache.isValid()) { return { valid: true, license: this.cache.load(), source: "cache" }; } return { valid: false, error: "No valid license found" }; } async logout() { await this.supabase.auth.signOut(); this.cache.clear(); this.keyManager.clear(); this.currentUser = null; this.currentLicense = null; } getStatus() { const cached = this.cache.load(); if (!cached) return null; return { email: cached.email, tier: cached.tier, daysRemaining: this.cache.getDaysRemaining(), isLifetime: cached.tier === "lifetime" }; } async resetPassword(email) { if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { throw new Error("Invalid email address"); } const { error } = await this.supabase.auth.resetPasswordForEmail(email); if (error) throw new Error(error.message); return true; } }; // src/window/window-manager.ts var import_electron = require("electron"); function createMainWindow(config) { const window = new import_electron.BrowserWindow({ width: config.width ?? 800, height: config.height ?? 600, minWidth: config.minWidth ?? 700, minHeight: config.minHeight ?? 500, frame: config.frame ?? false, resizable: true, webPreferences: { nodeIntegration: false, contextIsolation: true, preload: config.preloadPath, sandbox: true, webSecurity: true }, backgroundColor: config.backgroundColor ?? "#0a0a0f", show: false }); void window.loadFile(config.htmlPath); window.once("ready-to-show", () => { window.show(); }); const shouldOpenDevTools = config.devTools ?? process.argv.includes("--dev"); if (shouldOpenDevTools && !import_electron.app.isPackaged) { window.webContents.openDevTools(); } return window; } // src/window/window-controls.ts var import_electron2 = require("electron"); function registerWindowControls(window) { import_electron2.ipcMain.on("window-minimize", () => { if (window && !window.isDestroyed()) { window.minimize(); } }); import_electron2.ipcMain.on("window-maximize", () => { if (window && !window.isDestroyed()) { if (window.isMaximized()) { window.unmaximize(); } else { window.maximize(); } } }); import_electron2.ipcMain.on("window-close", () => { if (window && !window.isDestroyed()) { window.close(); } }); } function unregisterWindowControls() { import_electron2.ipcMain.removeAllListeners("window-minimize"); import_electron2.ipcMain.removeAllListeners("window-maximize"); import_electron2.ipcMain.removeAllListeners("window-close"); } // src/window/single-instance.ts var import_electron3 = require("electron"); function enforceSingleInstance(config) { const gotLock = import_electron3.app.requestSingleInstanceLock(); if (!gotLock) { return false; } import_electron3.app.on("second-instance", (_event, argv) => { if (config?.onSecondInstance) { config.onSecondInstance(argv); } const windows = import_electron3.BrowserWindow.getAllWindows(); const mainWindow = windows[0]; if (!mainWindow) { return; } if (mainWindow.isMinimized()) { mainWindow.restore(); } mainWindow.focus(); }); return true; } // src/security/index.ts var import_electron4 = require("electron"); function setupSecurityPolicy(window, config) { const allowedDomains = config?.allowedExternalDomains ?? []; window.webContents.on("will-navigate", (event, url) => { try { const parsedUrl = new URL(url); if (parsedUrl.protocol !== "file:") { event.preventDefault(); console.warn(`Blocked navigation to: ${url}`); } } catch (error) { event.preventDefault(); console.error(`Blocked malformed URL: ${url}`, error); } }); window.webContents.setWindowOpenHandler(({ url }) => { console.warn(`Blocked new window: ${url}`); return { action: "deny" }; }); import_electron4.ipcMain.handle("open-external", async (_event, url) => { try { const parsedUrl = new URL(url); if (!["http:", "https:"].includes(parsedUrl.protocol)) { console.warn(`Blocked non-http URL: ${url}`); return false; } if (allowedDomains.length > 0) { const isAllowed = allowedDomains.some( (domain) => parsedUrl.hostname === domain || parsedUrl.hostname.endsWith(`.${domain}`) ); if (!isAllowed) { console.warn(`Blocked non-whitelisted domain: ${url}`); return false; } } await import_electron4.shell.openExternal(url); return true; } catch (error) { console.error(`Failed to open external URL: ${url}`, error); return false; } }); } function createNavigationGuard(window) { window.webContents.on("will-navigate", (event, url) => { try { const parsedUrl = new URL(url); if (parsedUrl.protocol !== "file:") { event.preventDefault(); } } catch { event.preventDefault(); } }); window.webContents.setWindowOpenHandler(() => ({ action: "deny" })); } function validateIpcChannel(channel, allowedChannels) { return allowedChannels.includes(channel); } // src/logger/index.ts var fs2 = __toESM(require("fs")); var path2 = __toESM(require("path")); var os3 = __toESM(require("os")); var LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 }; var Logger = class { logDir; logFile; maxFileSize; maxFiles; minLevel; enableConsole; constructor(config) { this.logDir = config.logDir ?? path2.join(os3.homedir(), "AppData", "Roaming", config.appName, "logs"); this.logFile = path2.join(this.logDir, "app.log"); this.maxFileSize = config.maxFileSize ?? 5 * 1024 * 1024; this.maxFiles = config.maxFiles ?? 5; this.minLevel = LOG_LEVELS[config.level ?? "info"]; this.enableConsole = config.console ?? true; fs2.mkdirSync(this.logDir, { recursive: true }); } formatMessage(level, message, meta) { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const metaStr = meta ? ` ${JSON.stringify(meta)}` : ""; return `[${timestamp}] [${level.toUpperCase()}] ${message}${metaStr}`; } write(level, message, meta) { if (LOG_LEVELS[level] < this.minLevel) return; const formatted = this.formatMessage(level, message, meta); if (this.enableConsole) { const consoleFn = level === "error" || level === "fatal" ? console.error : console.warn; consoleFn(formatted); } try { fs2.appendFileSync(this.logFile, `${formatted} `); } catch (error) { console.error("Failed to write log:", error); } } debug(message, meta) { this.write("debug", message, meta); } info(message, meta) { this.write("info", message, meta); } warn(message, meta) { this.write("warn", message, meta); } error(message, meta) { this.write("error", message, meta); } fatal(message, meta) { this.write("fatal", message, meta); } rotateLogs() { try { if (!fs2.existsSync(this.logFile)) return; const stats = fs2.statSync(this.logFile); if (stats.size < this.maxFileSize) return; for (let i = this.maxFiles - 1; i >= 0; i -= 1) { const oldFile = i === 0 ? this.logFile : `${this.logFile}.${i}`; const newFile = `${this.logFile}.${i + 1}`; if (fs2.existsSync(oldFile)) { if (i === this.maxFiles - 1) { fs2.unlinkSync(oldFile); } else { fs2.renameSync(oldFile, newFile); } } } } catch (error) { console.error("Failed to rotate logs:", error); } } getLogDir() { return this.logDir; } }; function createLogger(config) { return new Logger(config); } // src/config/env-loader.ts var fs3 = __toESM(require("fs")); var path3 = __toESM(require("path")); function loadEnvFile(envPath) { if (!fs3.existsSync(envPath)) { return false; } try { const content = fs3.readFileSync(envPath, "utf8"); const lines = content.split("\n"); for (const line of lines) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#")) continue; const match = trimmed.match(/^([^=]+)=(.*)$/); if (match) { const [, rawKey = "", rawValue = ""] = match; const key = rawKey.trim(); const value = rawValue.trim(); if (!(key in process.env)) { process.env[key] = value; } } } return true; } catch (error) { console.error(`Failed to load env file: ${envPath}`, error); return false; } } function loadEnvFiles(basePath, isPackaged) { const envFiles = isPackaged ? [path3.join(basePath, ".env.production"), path3.join(basePath, ".env")] : [ path3.join(basePath, ".env.development"), path3.join(basePath, ".env.local"), path3.join(basePath, ".env") ]; for (const envPath of envFiles) { if (loadEnvFile(envPath)) { return envPath; } } return null; } // src/config/paths.ts var path4 = __toESM(require("path")); var os4 = __toESM(require("os")); var fs4 = __toESM(require("fs")); function getAppPaths(appName) { const appData = path4.join(os4.homedir(), "AppData", "Roaming", appName); const paths = { userData: appData, appData, logs: path4.join(appData, "logs"), cache: path4.join(appData, "cache"), config: path4.join(appData, "config") }; const allDirs = [ paths.userData, paths.appData, paths.logs, paths.cache, paths.config ]; for (const dir of allDirs) { fs4.mkdirSync(dir, { recursive: true }); } return paths; } // src/config/app-config-loader.ts var fs5 = __toESM(require("fs")); function loadAppConfig(configPath) { const resolvedPath = configPath ?? "./app.config.ts"; if (!fs5.existsSync(resolvedPath)) { console.warn(`Config file not found: ${resolvedPath}, using defaults`); return defaultConfig; } console.warn("Dynamic config loading not supported, import config directly"); return defaultConfig; } // src/crash/index.ts var import_electron5 = require("electron"); var fs6 = __toESM(require("fs")); var path5 = __toESM(require("path")); var CrashReporter = class { logDir; maxReports; onCrash; appName; initialized = false; constructor(config) { this.appName = config.appName; this.logDir = config.logDir ?? this.getDefaultLogDir(); this.maxReports = config.maxCrashReports ?? 10; this.onCrash = config.onCrash; } getDefaultLogDir() { try { return path5.join(import_electron5.app.getPath("userData"), "logs"); } catch { return path5.join(process.cwd(), "logs"); } } initialize() { if (this.initialized) return; fs6.mkdirSync(this.logDir, { recursive: true }); process.on("uncaughtException", (error) => { this.handleCrash("uncaughtException", error); }); process.on("unhandledRejection", (reason) => { this.handleCrash("unhandledRejection", reason); }); this.initialized = true; this.log("info", `${this.appName} crash reporter initialized`); } handleCrash(type, error) { const errorMessage = error instanceof Error ? `${error.message} ${error.stack ?? ""}` : String(error); const report = { timestamp: (/* @__PURE__ */ new Date()).toISOString(), type, message: errorMessage, version: import_electron5.app.isPackaged ? import_electron5.app.getVersion() : "development", platform: process.platform, arch: process.arch, nodeVersion: process.version, electronVersion: process.versions.electron ?? "unknown" }; const crashFile = path5.join(this.logDir, `crash-${Date.now()}.json`); try { fs6.writeFileSync(crashFile, JSON.stringify(report, null, 2)); } catch (writeError) { console.error("Failed to write crash report:", writeError); } this.log("fatal", `[${type}] ${errorMessage}`); if (this.onCrash) { this.onCrash(report); } if (import_electron5.app.isReady()) { import_electron5.dialog.showErrorBox( `${this.appName} Error`, `An unexpected error occurred: ${error instanceof Error ? error.message : String(error)} Crash report saved to: ${crashFile} Please restart the application.` ); } import_electron5.app.exit(1); } log(level, message) { const timestamp = (/* @__PURE__ */ new Date()).toISOString(); const logLine = `[${timestamp}] [${level.toUpperCase()}] ${message} `; const logFile = path5.join(this.logDir, "app.log"); try { fs6.appendFileSync(logFile, logLine); } catch (error) { console.error("Failed to write log:", error); } console.error(logLine.trim()); } rotateLogs() { const logFile = path5.join(this.logDir, "app.log"); const maxSize = 5 * 1024 * 1024; const maxFiles = 5; try { if (!fs6.existsSync(logFile)) return; const stats = fs6.statSync(logFile); if (stats.size < maxSize) return; for (let i = maxFiles - 1; i >= 0; i -= 1) { const oldFile = i === 0 ? logFile : `${logFile}.${i}`; const newFile = `${logFile}.${i + 1}`; if (fs6.existsSync(oldFile)) { if (i === maxFiles - 1) { fs6.unlinkSync(oldFile); } else { fs6.renameSync(oldFile, newFile); } } } } catch (error) { console.error("Failed to rotate logs:", error); } } cleanOldCrashReports() { try { const files = fs6.readdirSync(this.logDir).filter((f) => f.startsWith("crash-") && f.endsWith(".json")).map((f) => ({ name: f, path: path5.join(this.logDir, f), time: fs6.statSync(path5.join(this.logDir, f)).mtime.getTime() })).sort((a, b) => b.time - a.time); for (let i = this.maxReports; i < files.length; i += 1) { const file = files[i]; if (!file) { continue; } fs6.unlinkSync(file.path); } } catch (error) { console.error("Failed to clean crash reports:", error); } } }; var crashReporterInstance = null; function getCrashReporter(config) { if (!crashReporterInstance) { crashReporterInstance = new CrashReporter(config); } return crashReporterInstance; } // src/updater/index.ts var import_electron6 = require("electron"); var AutoUpdater = class { autoUpdater = null; updateAvailable = false; updateDownloaded = false; enabled = false; initialize(config) { if (!import_electron6.app.isPackaged) { console.warn("Auto-updater disabled in development mode"); return false; } try { const { autoUpdater } = require("electron-updater"); this.autoUpdater = autoUpdater; this.autoUpdater.autoDownload = config?.autoDownload ?? false; this.autoUpdater.autoInstallOnAppQuit = config?.autoInstallOnQuit ?? true; this.setupEventHandlers(); this.enabled = true; const checkInterval = config?.checkInterval ?? 5e3; setTimeout(() => { void this.checkForUpdates(); }, checkInterval); return true; } catch (error) { console.error("Failed to initialize auto-updater:", error); return false; } } setupEventHandlers() { if (!this.autoUpdater) return; this.autoUpdater.on("checking-for-update", () => { this.notifyRenderer({ status: "checking" }); }); this.autoUpdater.on("update-available", (info) => { this.updateAvailable = true; this.notifyRenderer({ status: "available", version: info.version }); }); this.autoUpdater.on("update-not-available", (info) => { this.updateAvailable = false; this.notifyRenderer({ status: "not-available", version: info.version }); }); this.autoUpdater.on("download-progress", (progress) => { this.notifyRenderer({ status: "downloading", percent: progress.percent }); }); this.autoUpdater.on("update-downloaded", (info) => { this.updateDownloaded = true; this.notifyRenderer({ status: "downloaded", version: info.version }); }); this.autoUpdater.on("error", (error) => { this.notifyRenderer({ status: "error", error: error.message }); }); } notifyRenderer(status) { const windows = import_electron6.BrowserWindow.getAllWindows(); for (const win of windows) { if (!win.isDestroyed()) { win.webContents.send("update-status", status); } } } async checkForUpdates() { if (!this.enabled || !this.autoUpdater) { return { updateAvailable: false }; } try { const result = await this.autoUpdater.checkForUpdates(); return { updateAvailable: this.updateAvailable, version: result?.updateInfo?.version }; } catch (error) { console.error("Failed to check for updates:", error); return { updateAvailable: false }; } } async downloadUpdate() { if (!this.enabled || !this.autoUpdater || !this.updateAvailable) { return false; } try { await this.autoUpdater.downloadUpdate(); return true; } catch (error) { console.error("Failed to download update:", error); return false; } } installUpdate() { if (!this.enabled || !this.autoUpdater || !this.updateDownloaded) { return; } this.autoUpdater.quitAndInstall(false, true); } getStatus() { return { enabled: this.enabled, updateAvailable: this.updateAvailable, updateDownloaded: this.updateDownloaded }; } }; function registerUpdaterHandlers() { const updater = new AutoUpdater(); import_electron6.ipcMain.handle("get-version", () => ({ version: import_electron6.app.getVersion(), isPackaged: import_electron6.app.isPackaged })); import_electron6.ipcMain.handle("check-for-updates", async () => updater.checkForUpdates()); import_electron6.ipcMain.handle("download-update", async () => updater.downloadUpdate()); import_electron6.ipcMain.handle("install-update", () => { updater.installUpdate(); }); import_electron6.ipcMain.handle("get-update-status", () => updater.getStatus()); } // src/index.ts var VERSION = "0.1.0"; // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { AutoUpdater, CrashReporter, KeyManager, LicenseCache, LicenseService, Logger, SecureStorage, VERSION, createLogger, createMainWindow, createNavigationGuard, defaultConfig, defaultTheme, defineConfig, enforceSingleInstance, generateCSSVariables, generateHWID, generateStyleTag, getAppPaths, getCrashReporter, getHWIDComponents, injectTheme, injectThemeOnReady, loadAppConfig, loadEnvFile, loadEnvFiles, presets, registerUpdaterHandlers, registerWindowControls, setupSecurityPolicy, unregisterWindowControls, validateIpcChannel });