import { initializeApp } from "./main.common"; import { logger } from "./utils/logger"; import { SQLiteQueryResult } from "./services/platforms/ElectronPlatformService"; const platform = process.env.VITE_PLATFORM; const pwa_enabled = process.env.VITE_PWA_ENABLED === "true"; logger.info("[Main Electron] Initializing app"); logger.info("[Main Electron] Platform:", { platform }); logger.info("[Main Electron] PWA enabled:", { pwa_enabled }); if (pwa_enabled) { logger.warn("[Main Electron] PWA is enabled, but not supported in electron"); } let appIsMounted = false; // Initialize app and SQLite const app = initializeApp(); /** * SQLite initialization configuration * Defines timeouts and retry settings for database operations * * @author Matthew Raymer */ const SQLITE_CONFIG = { INITIALIZATION: { TIMEOUT_MS: 10000, // 10 seconds for initial setup RETRY_ATTEMPTS: 3, RETRY_DELAY_MS: 1000 }, OPERATIONS: { TIMEOUT_MS: 5000, // 5 seconds for regular operations RETRY_ATTEMPTS: 2, RETRY_DELAY_MS: 500 }, CONNECTION: { MAX_CONNECTIONS: 5, IDLE_TIMEOUT_MS: 30000 } }; // Create a promise that resolves when SQLite is ready const sqliteReady = new Promise((resolve, reject) => { let retryCount = 0; let initializationTimeout: NodeJS.Timeout; const attemptInitialization = () => { // Clear any existing timeout if (initializationTimeout) { clearTimeout(initializationTimeout); } // Set timeout for this attempt initializationTimeout = setTimeout(() => { if (retryCount < SQLITE_CONFIG.INITIALIZATION.RETRY_ATTEMPTS) { retryCount++; logger.warn(`[Main Electron] SQLite initialization attempt ${retryCount} timed out, retrying...`); setTimeout(attemptInitialization, SQLITE_CONFIG.INITIALIZATION.RETRY_DELAY_MS); } else { logger.error("[Main Electron] SQLite initialization failed after all retries"); reject(new Error("SQLite initialization timeout after all retries")); } }, SQLITE_CONFIG.INITIALIZATION.TIMEOUT_MS); // Wait for electron bridge to be available const checkElectronBridge = () => { if (!window.electron?.ipcRenderer) { // Check again in 100ms if bridge isn't ready setTimeout(checkElectronBridge, 100); return; } // At this point we know ipcRenderer exists const ipcRenderer = window.electron.ipcRenderer; logger.info("[Main Electron] [IPC:bridge] IPC renderer bridge available"); // Listen for SQLite ready signal logger.debug("[Main Electron] [IPC:sqlite-ready] Registering listener for SQLite ready signal"); ipcRenderer.once('sqlite-ready', () => { clearTimeout(initializationTimeout); logger.info("[Main Electron] [IPC:sqlite-ready] Received SQLite ready signal"); resolve(); }); // Also listen for database errors logger.debug("[Main Electron] [IPC:database-status] Registering listener for database status"); ipcRenderer.once('database-status', (...args: unknown[]) => { clearTimeout(initializationTimeout); const status = args[0] as { status: string; error?: string }; if (status.status === 'error') { logger.error("[Main Electron] [IPC:database-status] Database error:", { error: status.error, channel: 'database-status' }); reject(new Error(status.error || 'Database initialization failed')); } }); // Check if SQLite is already available logger.debug("[Main Electron] [IPC:sqlite-is-available] Checking SQLite availability"); ipcRenderer.invoke('sqlite-is-available') .then(async (result: unknown) => { const isAvailable = Boolean(result); if (isAvailable) { logger.info("[Main Electron] [IPC:sqlite-is-available] SQLite is available"); try { // First create a database connection logger.debug("[Main Electron] [IPC:get-path] Requesting database path"); const dbPath = await ipcRenderer.invoke('get-path'); logger.info("[Main Electron] [IPC:get-path] Database path received:", { dbPath }); // Create the database connection logger.debug("[Main Electron] [IPC:sqlite-create-connection] Creating database connection"); await ipcRenderer.invoke('sqlite-create-connection', { database: 'timesafari', version: 1 }); logger.info("[Main Electron] [IPC:sqlite-create-connection] Database connection created"); // Explicitly open the database logger.debug("[Main Electron] [IPC:sqlite-open] Opening database"); await ipcRenderer.invoke('sqlite-open', { database: 'timesafari' }); logger.info("[Main Electron] [IPC:sqlite-open] Database opened successfully"); // Verify the database is open logger.debug("[Main Electron] [IPC:sqlite-is-db-open] Verifying database is open"); const isOpen = await ipcRenderer.invoke('sqlite-is-db-open', { database: 'timesafari' }); logger.info("[Main Electron] [IPC:sqlite-is-db-open] Database open status:", { isOpen }); if (!isOpen) { throw new Error('Database failed to open'); } // Now execute the test query logger.debug("[Main Electron] [IPC:sqlite-query] Executing test query"); const testQuery = await ipcRenderer.invoke('sqlite-query', { database: 'timesafari', statement: 'SELECT * FROM secret;' }) as SQLiteQueryResult; logger.info("[Main Electron] [IPC:sqlite-query] Test query successful:", { hasResults: Boolean(testQuery?.values), resultCount: testQuery?.values?.length, results: testQuery?.values }); // Close the database logger.debug("[Main Electron] [IPC:sqlite-close] Closing database"); await ipcRenderer.invoke('sqlite-close', { database: 'timesafari' }); logger.info("[Main Electron] [IPC:sqlite-close] Database closed successfully"); // Close the connection logger.debug("[Main Electron] [IPC:sqlite-close-connection] Closing database connection"); await ipcRenderer.invoke('sqlite-close-connection', { database: 'timesafari' }); logger.info("[Main Electron] [IPC:sqlite-close-connection] Database connection closed successfully"); } catch (error) { logger.error("[Main Electron] [IPC:*] SQLite test operation failed:", { error, lastOperation: 'sqlite-test-query', database: 'timesafari' }); // Try to close everything if anything was opened try { logger.debug("[Main Electron] [IPC:cleanup] Attempting database cleanup after error"); await ipcRenderer.invoke('sqlite-close', { database: 'timesafari' }).catch((closeError) => { logger.warn("[Main Electron] [IPC:sqlite-close] Failed to close database during cleanup:", closeError); }); await ipcRenderer.invoke('sqlite-close-connection', { database: 'timesafari' }).catch((closeError) => { logger.warn("[Main Electron] [IPC:sqlite-close-connection] Failed to close connection during cleanup:", closeError); }); logger.info("[Main Electron] [IPC:cleanup] Database cleanup completed after error"); } catch (closeError) { logger.error("[Main Electron] [IPC:cleanup] Failed to cleanup database:", { error: closeError, database: 'timesafari' }); } // Don't reject here - we still want to wait for the ready signal } } }) .catch((error: Error) => { logger.error("[Main Electron] [IPC:sqlite-is-available] Failed to check SQLite availability:", { error, channel: 'sqlite-is-available' }); // Don't reject here - wait for either ready signal or timeout }); }; // Start checking for bridge checkElectronBridge(); }; // Start first initialization attempt attemptInitialization(); }); // Wait for SQLite to be ready before initializing router and mounting app sqliteReady .then(async () => { logger.info("[Main Electron] SQLite ready, initializing router..."); // Initialize router after SQLite is ready const router = await import('./router').then(m => m.default); app.use(router); logger.info("[Main Electron] Router initialized"); // Now mount the app logger.info("[Main Electron] Mounting app..."); app.mount("#app"); appIsMounted = true; logger.info("[Main Electron] App mounted successfully"); }) .catch((error) => { logger.error("[Main Electron] Failed to initialize SQLite:", error instanceof Error ? error.message : 'Unknown error'); // Show error to user with retry option const errorDiv = document.createElement("div"); errorDiv.style.cssText = "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #ffebee; color: #c62828; padding: 20px; border-radius: 4px; text-align: center; max-width: 80%; z-index: 9999;"; errorDiv.innerHTML = `

Failed to Initialize Application

There was an error initializing the database. This could be due to:

Error details: ${error instanceof Error ? error.message : 'Unknown error'}

`; document.body.appendChild(errorDiv); });