import { app, BrowserWindow } from 'electron'; import { getElectronUserDataPath } from '@iptvnator/shared/database'; import { autoUpdater } from 'electron-updater'; import fixPath from 'fix-path'; import App from './app/app'; import { initDatabase } from './app/database/connection'; import DatabaseEvents from './app/events/database.events'; import { resetStaleDownloads, setMainWindow as setDownloadsMainWindow, } from './app/events/database/downloads.events'; import ElectronEvents from './app/events/electron.events'; import EmbeddedMpvEvents, { shutdownEmbeddedMpv, } from './app/events/embedded-mpv.events'; import EpgEvents from './app/events/epg.events'; import AppUpdateEvents from './app/events/app-update.events'; import { shutdownMpvSession } from './app/events/mpv-session.service'; import PlayerEvents from './app/events/player.events'; import { shutdownVlcSession } from './app/events/vlc-session.service'; import PlaylistEvents from './app/events/playlist.events'; import RemoteControlEvents from './app/events/remote-control.events'; import SettingsEvents from './app/events/settings.events'; import SharedEvents from './app/events/shared.events'; import SquirrelEvents from './app/events/squirrel.events'; import StalkerEvents from './app/events/stalker.events'; import { isStartupTraceEnabled, trace } from './app/services/debug-trace'; import { AppUpdateService } from './app/services/app-update.service'; import { databaseWorkerClient } from './app/services/database-worker-client'; import WindowEvents from './app/events/window.events'; import XtreamEvents from './app/events/xtream.events'; import { environment } from './environments/environment'; app.setName('iptvnator'); const electronUserDataPath = getElectronUserDataPath(); if (electronUserDataPath) { app.setPath('userData', electronUserDataPath); } let fixPathScheduled = false; /** * Update process.env.PATH from the user's interactive login shell so that * spawned external players (MPV/VLC) can be resolved by binary name. * * Runs after window creation + IPC handler registration so the 50-300 ms * shell-spawn cost (bash/zsh -ilc env) doesn't block startup. Idempotent: * subsequent calls are no-ops. */ function scheduleDeferredFixPath(): void { if (fixPathScheduled || process.platform === 'win32') { return; } fixPathScheduled = true; setImmediate(() => { try { fixPath(); if (isStartupTraceEnabled()) { trace('startup', 'fix-path:done'); } } catch (error) { console.warn('fix-path failed:', error); } }); } export default class Main { static initialize() { if (SquirrelEvents.handleEvents()) { // squirrel event handled (except first run event) and app will exit in 1000ms, so don't do anything else app.quit(); } } static bootstrapApp() { if (isStartupTraceEnabled()) { trace('startup', 'bootstrap-app'); } App.main(app, BrowserWindow); } static async bootstrapAppEvents() { if (isStartupTraceEnabled()) { trace('startup', 'bootstrap-events:start'); } const appUpdateService = new AppUpdateService({ app, appVersion: environment.version, getMainWindow: () => App.mainWindow, updater: () => autoUpdater, }); AppUpdateEvents.bootstrapAppUpdateEvents(appUpdateService); ElectronEvents.bootstrapElectronEvents(); WindowEvents.bootstrapWindowEvents(); EmbeddedMpvEvents.bootstrapEmbeddedMpvEvents(); PlaylistEvents.bootstrapPlaylistEvents(); SharedEvents.bootstrapSharedEvents(); PlayerEvents.bootstrapPlayerEvents(); SettingsEvents.bootstrapSettingsEvents(); StalkerEvents.bootstrapStalkerEvents(); XtreamEvents.bootstrapXtreamEvents(); DatabaseEvents.bootstrapDatabaseEvents(); EpgEvents.bootstrapEpgEvents(); RemoteControlEvents.bootstrapRemoteControlEvents(); // Set main window for downloads and reset stale downloads if (App.mainWindow) { setDownloadsMainWindow(App.mainWindow); } // Load the renderer only after IPC handlers are registered. On slower // Linux CI hosts the renderer can otherwise invoke Electron bridge IPC // before the main process has installed handlers. await App.loadMainWindow(); void appUpdateService.checkForUpdatesOnStartup(); // Initialize the database after the first renderer load is underway so // Linux Electron E2E can observe a BrowserWindow even when SQLite // startup or download recovery is slow. IPC handlers call getDatabase() // lazily and share the same initialization promise. await initDatabase(); if (isStartupTraceEnabled()) { trace('startup', 'init-database:done'); } await resetStaleDownloads(); if (isStartupTraceEnabled()) { trace('startup', 'reset-stale-downloads:done'); } if (isStartupTraceEnabled()) { trace('startup', 'bootstrap-events:done'); } // Hydrate process.env.PATH from the user's login shell now — after // the window has loaded and IPC handlers are live. Fire-and-forget // (setImmediate) so it doesn't gate any user-visible work. Worst // case: the user clicks an external player within the ~100 ms it // takes to complete; the spawn would still find MPV/VLC at any of // the well-known paths checked by getDefault*Path before falling // back to bare-name PATH lookup. scheduleDeferredFixPath(); } } // handle setup events as quickly as possible Main.initialize(); // bootstrap app Main.bootstrapApp(); // Bootstrap app events after Electron app is ready app.whenReady().then(async () => { if (isStartupTraceEnabled()) { trace('startup', 'app.whenReady'); } await Main.bootstrapAppEvents(); }); app.on('before-quit', () => { shutdownEmbeddedMpv(); shutdownMpvSession(); shutdownVlcSession(); void databaseWorkerClient.shutdown(); });