diff --git a/package.json b/package.json index d3f65809..b70052f2 100644 --- a/package.json +++ b/package.json @@ -190,8 +190,8 @@ "to": "www" }, { - "from": "dist-electron/resources/preload.mjs", - "to": "preload.mjs" + "from": "dist-electron/resources/preload.js", + "to": "preload.js" } ], "linux": { diff --git a/scripts/build-electron.js b/scripts/build-electron.js index f9c61381..0a601184 100644 --- a/scripts/build-electron.js +++ b/scripts/build-electron.js @@ -49,8 +49,8 @@ indexContent = indexContent.replace( fs.writeFileSync(finalIndexPath, indexContent); // Copy preload script to resources -const preloadSrc = path.join(electronDistPath, "preload.mjs"); -const preloadDest = path.join(electronDistPath, "resources", "preload.mjs"); +const preloadSrc = path.join(electronDistPath, "preload.js"); +const preloadDest = path.join(electronDistPath, "resources", "preload.js"); // Ensure resources directory exists const resourcesDir = path.join(electronDistPath, "resources"); diff --git a/src/electron/main.ts b/src/electron/main.ts index bca2aec8..d0a84c2b 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -8,6 +8,13 @@ import { fileURLToPath } from "url"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); +// Set global variables that the plugin needs +global.__dirname = __dirname; +global.__filename = __filename; + +// Now import the plugin after setting up globals +import { CapacitorSQLite } from "@capacitor-community/sqlite/electron/dist/plugin.js"; + // Simple logger implementation const logger = { // eslint-disable-next-line no-console @@ -69,44 +76,35 @@ let sqlitePlugin: any = null; async function initializeSQLite() { try { - // Import the plugin directly in the main process - const plugin = await import("@capacitor-community/sqlite/electron/dist/plugin.js"); - sqlitePlugin = new plugin.CapacitorSQLite(); - - // Test the plugin with a simple operation - const result = await sqlitePlugin.echo({ value: "test" }); - if (result.value !== "test") { - throw new Error("SQLite plugin echo test failed"); - } - - logger.info("SQLite plugin initialized successfully"); + console.log("Initializing SQLite plugin..."); + sqlitePlugin = new CapacitorSQLite(); + // Test the plugin + const echoResult = await sqlitePlugin.echo({ value: "test" }); + console.log("SQLite plugin echo test:", echoResult); + // Initialize database connection + const db = await sqlitePlugin.createConnection({ + database: "timesafari.db", + version: 1, + }); + console.log("SQLite plugin initialized successfully"); + return db; } catch (error) { - logger.error("Failed to initialize SQLite plugin:", error); + console.error("Failed to initialize SQLite plugin:", error); throw error; } } -// Initialize SQLite plugin -initializeSQLite().catch(error => { - logger.error("Failed to initialize SQLite plugin:", error); -}); - -// Set up IPC handlers for SQLite operations -ipcMain.handle("check-sqlite-availability", () => { - return sqlitePlugin !== null; -}); - -ipcMain.handle("capacitor-sqlite", async (event, ...args) => { - if (!sqlitePlugin) { - throw new Error("SQLite plugin not initialized"); - } - return sqlitePlugin.handle(event, ...args); -}); - // Initialize app when ready -app.whenReady().then(() => { - logger.info("App is ready, creating window..."); - createWindow(); +app.whenReady().then(async () => { + logger.info("App is ready, initializing SQLite..."); + try { + await initializeSQLite(); + logger.info("SQLite initialized, creating window..."); + createWindow(); + } catch (error) { + logger.error("Failed to initialize app:", error); + app.quit(); + } }); // Check if running in dev mode @@ -115,8 +113,8 @@ const isDev = process.argv.includes("--inspect"); function createWindow(): void { // Resolve preload path based on environment const preloadPath = app.isPackaged - ? path.join(process.resourcesPath, "preload.mjs") - : path.join(__dirname, "preload.mjs"); + ? path.join(process.resourcesPath, "preload.js") + : path.join(__dirname, "preload.js"); logger.log("[Electron] Preload path:", preloadPath); logger.log("[Electron] Preload exists:", fs.existsSync(preloadPath)); @@ -331,3 +329,21 @@ app.on("activate", () => { process.on("uncaughtException", (error) => { logger.error("Uncaught Exception:", error); }); + +// Set up IPC handlers for SQLite operations +ipcMain.handle("check-sqlite-availability", () => { + return sqlitePlugin !== null; +}); + +ipcMain.handle("capacitor-sqlite", async (event, ...args) => { + if (!sqlitePlugin) { + logger.error("SQLite plugin not initialized when handling IPC request"); + throw new Error("SQLite plugin not initialized"); + } + try { + return await sqlitePlugin.handle(event, ...args); + } catch (error) { + logger.error("Error handling SQLite IPC request:", error, JSON.stringify(error), (error as any)?.stack); + throw error; + } +}); diff --git a/src/main.electron.ts b/src/main.electron.ts index 5404f722..48d3edbd 100644 --- a/src/main.electron.ts +++ b/src/main.electron.ts @@ -3,11 +3,29 @@ import { logger } from "./utils/logger"; async function initializeSQLite() { try { - const isAvailable = await window.electron.sqlite.isAvailable(); - if (!isAvailable) { - throw new Error("SQLite plugin not available in main process"); + // Wait for SQLite to be available in the main process + let retries = 0; + const maxRetries = 5; + const retryDelay = 1000; // 1 second + + while (retries < maxRetries) { + try { + const isAvailable = await window.electron.sqlite.isAvailable(); + if (isAvailable) { + logger.info("[Electron] SQLite plugin bridge initialized successfully"); + return true; + } + } catch (error) { + logger.warn(`[Electron] SQLite not available yet (attempt ${retries + 1}/${maxRetries}):`, error); + } + + retries++; + if (retries < maxRetries) { + await new Promise(resolve => setTimeout(resolve, retryDelay)); + } } - logger.info("[Electron] SQLite plugin bridge initialized"); + + throw new Error("SQLite plugin not available after maximum retries"); } catch (error) { logger.error("[Electron] Failed to initialize SQLite plugin bridge:", error); throw error; @@ -27,8 +45,22 @@ if (pwa_enabled) { // Initialize app and SQLite const app = initializeApp(); -initializeSQLite().then(() => { - app.mount("#app"); -}).catch(error => { - logger.error("[Electron] Failed to initialize app:", error); -}); + +// Initialize SQLite first, then mount the app +initializeSQLite() + .then(() => { + logger.info("[Electron] SQLite initialized, mounting app..."); + app.mount("#app"); + }) + .catch(error => { + logger.error("[Electron] Failed to initialize app:", error); + // Show error to user + 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%;"; + errorDiv.innerHTML = ` +

Failed to Initialize Database

+

There was an error initializing the database. Please try restarting the application.

+

Error details: ${error.message}

+ `; + document.body.appendChild(errorDiv); + }); diff --git a/src/services/platforms/ElectronPlatformService.ts b/src/services/platforms/ElectronPlatformService.ts index 3e2baccd..a70a001c 100644 --- a/src/services/platforms/ElectronPlatformService.ts +++ b/src/services/platforms/ElectronPlatformService.ts @@ -4,11 +4,10 @@ import { PlatformCapabilities, } from "../PlatformService"; import { logger } from "../../utils/logger"; -import { QueryExecResult, SqlValue } from "@/interfaces/database"; +import { QueryExecResult } from "@/interfaces/database"; import { SQLiteConnection, SQLiteDBConnection, - Changes, } from "@capacitor-community/sqlite"; import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app"; @@ -30,6 +29,7 @@ export class ElectronPlatformService implements PlatformService { private db: SQLiteDBConnection | null = null; private dbName = "timesafari.db"; private initialized = false; + private initializationPromise: Promise | null = null; constructor() { // Use the IPC bridge for SQLite operations @@ -40,40 +40,53 @@ export class ElectronPlatformService implements PlatformService { } private async initializeDatabase(): Promise { + // If already initialized, return if (this.initialized) { return; } - try { - // Check if SQLite is available through IPC - const isAvailable = await window.electron.sqlite.isAvailable(); - if (!isAvailable) { - throw new Error("SQLite is not available in the main process"); - } - - // Create/Open database with native implementation - this.db = await this.sqlite.createConnection( - this.dbName, - false, - "no-encryption", - 1, - true, // Use native implementation - ); - - await this.db.open(); - - // Set journal mode to WAL for better performance - await this.db.execute("PRAGMA journal_mode=WAL;"); + // If initialization is in progress, wait for it + if (this.initializationPromise) { + return this.initializationPromise; + } - // Run migrations - await this.runMigrations(); + // Start initialization + this.initializationPromise = (async () => { + try { + // Check if SQLite is available through IPC + const isAvailable = await window.electron.sqlite.isAvailable(); + if (!isAvailable) { + throw new Error("SQLite is not available in the main process"); + } + + // Create/Open database with native implementation + this.db = await this.sqlite.createConnection( + this.dbName, + false, + "no-encryption", + 1, + true, // Use native implementation + ); + + await this.db.open(); + + // Set journal mode to WAL for better performance + await this.db.execute("PRAGMA journal_mode=WAL;"); + + // Run migrations + await this.runMigrations(); + + this.initialized = true; + logger.log("[Electron] SQLite database initialized successfully"); + } catch (error) { + logger.error("[Electron] Error initializing SQLite database:", error); + this.initialized = false; + this.initializationPromise = null; + throw error; + } + })(); - this.initialized = true; - logger.log("SQLite database initialized successfully"); - } catch (error) { - logger.error("Error initializing SQLite database:", error); - throw new Error("Failed to initialize database"); - } + return this.initializationPromise; } private async runMigrations(): Promise { @@ -300,24 +313,24 @@ export class ElectronPlatformService implements PlatformService { /** * @see PlatformService.dbQuery */ - async dbQuery(sql: string, params?: unknown[]): Promise { - await this.initializeDatabase(); - if (!this.db) { - throw new Error("Database not initialized"); - } - + async dbQuery( + sql: string, + params?: unknown[], + ): Promise { try { - const result = await this.db.query(sql, params || []); - const values = result.values || []; + await this.initializeDatabase(); + if (!this.db) { + throw new Error("Database not initialized"); + } + const result = await this.db.query(sql, params); + // Convert SQLite plugin result to QueryExecResult format return { - columns: [], // SQLite plugin doesn't provide column names in query result - values: values as SqlValue[][], + columns: [], // SQLite plugin doesn't provide column names + values: result.values || [], }; } catch (error) { - logger.error("Error executing query:", error); - throw new Error( - `Database query failed: ${error instanceof Error ? error.message : String(error)}`, - ); + logger.error("[Electron] Database query error:", error); + throw error; } } @@ -328,23 +341,20 @@ export class ElectronPlatformService implements PlatformService { sql: string, params?: unknown[], ): Promise<{ changes: number; lastId?: number }> { - await this.initializeDatabase(); - if (!this.db) { - throw new Error("Database not initialized"); - } - try { - const result = await this.db.run(sql, params || []); - const changes = result.changes as Changes; + await this.initializeDatabase(); + if (!this.db) { + throw new Error("Database not initialized"); + } + const result = await this.db.run(sql, params); + // Convert SQLite plugin result to expected format return { - changes: changes?.changes || 0, - lastId: changes?.lastId, + changes: result.changes?.changes || 0, + lastId: result.changes?.lastId, }; } catch (error) { - logger.error("Error executing statement:", error); - throw new Error( - `Database execution failed: ${error instanceof Error ? error.message : String(error)}`, - ); + logger.error("[Electron] Database execution error:", error); + throw error; } } }