// Minimal Electron stub for headless Node.js deployment // Stubs out all Electron APIs so the Koa proxy boots without a display. const EventEmitter = require('events'); const path = require('path'); const noop = () => {}; const noopAsync = () => Promise.resolve(); const dataDir = process.env.CHAT2API_DATA_DIR || path.join(require('os').homedir(), '.chat2api'); const app = Object.assign(new EventEmitter(), { getPath: (name) => { const map = { userData: dataDir, appData: dataDir, temp: '/tmp', home: require('os').homedir(), logs: dataDir + '/logs' }; return map[name] || dataDir; }, getVersion: () => '1.0.0', getName: () => 'Chat2API', quit: noop, exit: noop, isPackaged: false, isReady: () => true, whenReady: () => Promise.resolve(), requestSingleInstanceLock: () => true, setAppUserModelId: noop, setLoginItemSettings: noop, dock: { setIcon: noop, hide: noop }, }); // Fire 'ready' asynchronously so listeners registered after require() still fire setImmediate(() => app.emit('ready', {}, {})); const ipcMain = Object.assign(new EventEmitter(), { handle: noop, handleOnce: noop, removeHandler: noop, }); class BrowserWindow extends EventEmitter { constructor() { super(); this.webContents = { send: noop, on: noop, once: noop, session: { webRequest: { onBeforeSendHeaders: noop, onHeadersReceived: noop } } }; } loadURL() { return Promise.resolve(); } loadFile() { return Promise.resolve(); } show() {} hide() {} close() {} destroy() {} isDestroyed() { return false; } setIcon() {} setTitle() {} setMenu() {} center() {} focus() {} static getAllWindows() { return []; } static fromWebContents() { return null; } } class Tray extends EventEmitter { constructor() { super(); } setToolTip() {} setContextMenu() {} setImage() {} destroy() {} } const Menu = { buildFromTemplate: () => ({}), setApplicationMenu: noop, getApplicationMenu: () => null }; class MenuItem {} const nativeImage = { createFromPath: () => ({ isEmpty: () => false }), createEmpty: () => ({ isEmpty: () => true }), createFromDataURL: () => ({}) }; const screen = { getPrimaryDisplay: () => ({ workAreaSize: { width: 1920, height: 1080 }, scaleFactor: 1 }), on: noop }; const globalShortcut = { register: noop, unregisterAll: noop, isRegistered: () => false }; const powerMonitor = new EventEmitter(); const dialog = { showMessageBox: noopAsync, showMessageBoxSync: () => 0, showErrorBox: noop, showOpenDialog: noopAsync }; const shell = { openExternal: noopAsync, openPath: noopAsync }; const clipboard = { writeText: noop, readText: () => '' }; const autoUpdater = Object.assign(new EventEmitter(), { setFeedURL: noop, checkForUpdates: noop, quitAndInstall: noop }); const net = { request: () => { const e = new EventEmitter(); e.end = noop; return e; } }; const session = { defaultSession: { webRequest: { onBeforeSendHeaders: noop } } }; const protocol = { registerFileProtocol: noop, registerHttpProtocol: noop }; const contextBridge = { exposeInMainWorld: noop }; const ipcRenderer = Object.assign(new EventEmitter(), { invoke: noopAsync, send: noop, sendSync: () => null, on: (e,f) => ipcRenderer, once: (e,f) => ipcRenderer }); const crashReporter = { start: noop }; const desktopCapturer = { getSources: noopAsync }; const powerSaveBlocker = { start: () => 1, stop: noop }; module.exports = { app, ipcMain, ipcRenderer, BrowserWindow, Tray, Menu, MenuItem, nativeImage, screen, globalShortcut, powerMonitor, dialog, shell, clipboard, autoUpdater, net, session, protocol, contextBridge, crashReporter, desktopCapturer, powerSaveBlocker, };