import path from 'node:path'; import { app, BrowserWindow } from 'electron'; import { getAppSemanticVersion } from '../versionInfo'; let bootSplashRef: BrowserWindow | null = null; export function getBootSplashWindow(): BrowserWindow | null { return bootSplashRef; } function loadBootPage(win: BrowserWindow): void { const dev = process.env.VITE_DEV_SERVER_URL; if (dev) { void win.loadURL(new URL('boot.html', dev).toString()); return; } const htmlPath = path.join(app.getAppPath(), 'dist', 'renderer', 'boot.html'); void win.loadFile(htmlPath); } /** Без preload: только статический экран; статус задаётся из main через executeJavaScript. */ function bootWebPreferences(): Electron.WebPreferences { const dev = Boolean(process.env.VITE_DEV_SERVER_URL); return { contextIsolation: true, sandbox: dev ? true : process.platform !== 'win32', nodeIntegration: false, devTools: dev || process.env.DND_OPEN_DEVTOOLS === '1', webSecurity: dev, }; } /** * Окно без системного заголовка: логотип, название, строка статуса. * Показывать после `waitForBootWindowReady`. */ export function createBootWindow(): BrowserWindow { const win = new BrowserWindow({ width: 440, height: 420, show: false, frame: false, resizable: false, maximizable: false, minimizable: false, closable: false, center: true, transparent: false, backgroundColor: '#09090B', roundedCorners: true, webPreferences: bootWebPreferences(), }); bootSplashRef = win; win.once('closed', () => { if (bootSplashRef === win) { bootSplashRef = null; } }); loadBootPage(win); return win; } /** * Закрыть splash: при `closable: false` на Windows `close()` из main часто не срабатывает — используем `destroy()`. */ export function destroyBootWindow(win: BrowserWindow): void { if (win.isDestroyed()) return; win.destroy(); } export function setBootWindowStatus(win: BrowserWindow, text: string): void { if (win.isDestroyed()) return; const escaped = JSON.stringify(text); void win.webContents.executeJavaScript( `(() => { const el = document.getElementById('boot-status'); if (el) el.textContent = ${escaped}; })()`, ); } export function applyBootWindowBranding(win: BrowserWindow): void { if (win.isDestroyed()) return; const name = app.getName(); const version = getAppSemanticVersion(); const versionLabel = version.trim().length > 0 ? `v${version.trim()}` : ''; void win.webContents.executeJavaScript( `(() => { const t = document.querySelector('[data-boot-title]'); if (t) t.textContent = ${JSON.stringify(name)}; const v = document.querySelector('[data-boot-version]'); if (v) v.textContent = ${JSON.stringify(versionLabel)}; })()`, ); } /** Дождаться загрузки разметки экрана загрузки. */ export function waitForBootWindowReady(win: BrowserWindow): Promise { return new Promise((resolve, reject) => { if (win.isDestroyed()) { reject(new Error('boot window destroyed')); return; } const onFail = (): void => { reject(new Error('boot window failed to load')); }; win.webContents.once('did-fail-load', onFail); win.webContents.once('did-finish-load', () => { win.webContents.removeListener('did-fail-load', onFail); applyBootWindowBranding(win); resolve(); }); }); }