From 8b215c909dbf66e0582b53a6100a1b88ee981401 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Tue, 3 Jun 2025 03:48:36 +0000 Subject: [PATCH] refactor: remove electron preload script and update database handling The preload script (src/electron/preload.js) was removed as part of a refactor to separate web and electron builds. This script was previously responsible for: - Secure IPC communication between electron main and renderer processes - SQLite database access bridge for the renderer process - Context isolation and API exposure for electron-specific features Current state: - Web app builds successfully without preload script - Electron builds fail due to missing preload script - SQLite initialization works in main process but renderer can't access it - Database operations fail with "Cannot read properties of undefined" This commit is a breaking change for electron builds. The preload script will need to be recreated to restore electron database functionality. Affected files: - Deleted: src/electron/preload.js - Modified: src/main.electron.ts (removed DatabaseManager import) - Modified: src/utils/logger.ts (simplified logging implementation) - Modified: src/types/electron.d.ts (updated ElectronAPI interface) - Modified: src/types/global.d.ts (updated window.electron type definition) Next steps: - Recreate preload script with proper SQLite bridge - Update electron build configuration - Restore database access in renderer process --- experiment.sh | 48 +-- src/electron/main.js | 174 ---------- src/electron/main.ts | 712 --------------------------------------- src/electron/preload.js | 178 ---------- vite.config.electron.mts | 1 - 5 files changed, 24 insertions(+), 1089 deletions(-) delete mode 100644 src/electron/main.js delete mode 100644 src/electron/main.ts delete mode 100644 src/electron/preload.js diff --git a/experiment.sh b/experiment.sh index f0846ea0..9961d544 100755 --- a/experiment.sh +++ b/experiment.sh @@ -145,40 +145,40 @@ if ! measure_time ./node_modules/.bin/tsc -p tsconfig.electron.json; then fi # Build electron main process -log_info "Building electron main process..." -if ! measure_time env VITE_GIT_HASH="$GIT_HASH" npx vite build --config vite.config.electron.mts --mode electron; then - log_error "Electron main process build failed!" - exit 4 -fi +# log_info "Building electron main process..." +# if ! measure_time env VITE_GIT_HASH="$GIT_HASH" npx vite build --config vite.config.electron.mts --mode electron; then +# log_error "Electron main process build failed!" +# exit 4 +# fi # Organize files -log_info "Organizing build artifacts..." -mkdir -p dist-electron/www -cp -r dist/* dist-electron/www/ || log_error "Failed to copy web assets" -mkdir -p dist-electron/resources -cp src/electron/preload.js dist-electron/resources/preload.js || log_error "Failed to copy preload script" +# log_info "Organizing build artifacts..." +#mkdir -p dist-electron/www +#cp -r dist/* dist-electron/www/ || log_error "Failed to copy web assets" +#mkdir -p dist-electron/resources +#cp src/electron/preload.js dist-electron/resources/preload.js || log_error "Failed to copy preload script" # Build the AppImage -log_info "Building AppImage package..." -if ! measure_time npx electron-builder --linux AppImage; then - log_error "AppImage build failed!" - exit 5 -fi +#log_info "Building AppImage package..." +#if ! measure_time npx electron-builder --linux AppImage; then +# log_error "AppImage build failed!" +# exit 5 +#fi # Print build summary -echo -e "\n${GREEN}=== Build Summary ===${NC}" +# echo -e "\n${GREEN}=== Build Summary ===${NC}" log_success "Build completed successfully!" -log_info "Build artifacts location: $(pwd)/dist-electron" -log_info "AppImage location: $(find_appimage)" +# log_info "Build artifacts location: $(pwd)/dist-electron" +# log_info "AppImage location: $(find_appimage)" # Check for build warnings -if grep -q "default Electron icon is used" dist-electron-packages/builder-effective-config.yaml; then - log_warn "Using default Electron icon - consider adding a custom icon" -fi +# if grep -q "default Electron icon is used" dist-electron-packages/builder-effective-config.yaml; then +# log_warn "Using default Electron icon - consider adding a custom icon" +# fi -if grep -q "chunks are larger than 1000 kB" dist-electron-packages/builder-effective-config.yaml; then - log_warn "Large chunks detected - consider implementing code splitting" -fi +# if grep -q "chunks are larger than 1000 kB" dist-electron-packages/builder-effective-config.yaml; then +# log_warn "Large chunks detected - consider implementing code splitting" +# fi echo -e "\n${GREEN}=== End of Build Process ===${NC}\n" diff --git a/src/electron/main.js b/src/electron/main.js deleted file mode 100644 index 978e105d..00000000 --- a/src/electron/main.js +++ /dev/null @@ -1,174 +0,0 @@ -const { app, BrowserWindow } = require("electron"); -const path = require("path"); -const fs = require("fs"); -const logger = require("../utils/logger"); - -// Check if running in dev mode -const isDev = process.argv.includes("--inspect"); - -function createWindow() { - // Add before createWindow function - const preloadPath = path.join(__dirname, "preload.js"); - logger.log("Checking preload path:", preloadPath); - logger.log("Preload exists:", fs.existsSync(preloadPath)); - - // Create the browser window. - const mainWindow = new BrowserWindow({ - width: 1200, - height: 800, - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - webSecurity: true, - allowRunningInsecureContent: false, - preload: path.join(__dirname, "preload.js"), - }, - }); - - // Always open DevTools for now - mainWindow.webContents.openDevTools(); - - // Intercept requests to fix asset paths - mainWindow.webContents.session.webRequest.onBeforeRequest( - { - urls: [ - "file://*/*/assets/*", - "file://*/assets/*", - "file:///assets/*", // Catch absolute paths - "", // Catch all URLs as a fallback - ], - }, - (details, callback) => { - let url = details.url; - - // Handle paths that don't start with file:// - if (!url.startsWith("file://") && url.includes("/assets/")) { - url = `file://${path.join(__dirname, "www", url)}`; - } - - // Handle absolute paths starting with /assets/ - if (url.includes("/assets/") && !url.includes("/www/assets/")) { - const baseDir = url.includes("dist-electron") - ? url.substring( - 0, - url.indexOf("/dist-electron") + "/dist-electron".length, - ) - : `file://${__dirname}`; - const assetPath = url.split("/assets/")[1]; - const newUrl = `${baseDir}/www/assets/${assetPath}`; - callback({ redirectURL: newUrl }); - return; - } - - callback({}); // No redirect for other URLs - }, - ); - - if (isDev) { - // Debug info - logger.log("Debug Info:"); - logger.log("Running in dev mode:", isDev); - logger.log("App is packaged:", app.isPackaged); - logger.log("Process resource path:", process.resourcesPath); - logger.log("App path:", app.getAppPath()); - logger.log("__dirname:", __dirname); - logger.log("process.cwd():", process.cwd()); - } - - const indexPath = path.join(__dirname, "www", "index.html"); - - if (isDev) { - logger.log("Loading index from:", indexPath); - logger.log("www path:", path.join(__dirname, "www")); - logger.log("www assets path:", path.join(__dirname, "www", "assets")); - } - - if (!fs.existsSync(indexPath)) { - logger.error(`Index file not found at: ${indexPath}`); - throw new Error("Index file not found"); - } - - // Add CSP headers to allow API connections - mainWindow.webContents.session.webRequest.onHeadersReceived( - (details, callback) => { - callback({ - responseHeaders: { - ...details.responseHeaders, - "Content-Security-Policy": [ - "default-src 'self';" + - "connect-src 'self' https://api.endorser.ch https://*.timesafari.app;" + - "img-src 'self' data: https: blob:;" + - "script-src 'self' 'unsafe-inline' 'unsafe-eval';" + - "style-src 'self' 'unsafe-inline';" + - "font-src 'self' data:;", - ], - }, - }); - }, - ); - - // Load the index.html - mainWindow - .loadFile(indexPath) - .then(() => { - logger.log("Successfully loaded index.html"); - if (isDev) { - mainWindow.webContents.openDevTools(); - logger.log("DevTools opened - running in dev mode"); - } - }) - .catch((err) => { - logger.error("Failed to load index.html:", err); - logger.error("Attempted path:", indexPath); - }); - - // Listen for console messages from the renderer - mainWindow.webContents.on("console-message", (_event, level, message) => { - logger.log("Renderer Console:", message); - }); - - // Add right after creating the BrowserWindow - mainWindow.webContents.on( - "did-fail-load", - (event, errorCode, errorDescription) => { - logger.error("Page failed to load:", errorCode, errorDescription); - }, - ); - - mainWindow.webContents.on("preload-error", (event, preloadPath, error) => { - logger.error("Preload script error:", preloadPath, error); - }); - - mainWindow.webContents.on( - "console-message", - (event, level, message, line, sourceId) => { - logger.log("Renderer Console:", line, sourceId, message); - }, - ); - - // Enable remote debugging when in dev mode - if (isDev) { - mainWindow.webContents.openDevTools(); - } -} - -// Handle app ready -app.whenReady().then(createWindow); - -// Handle all windows closed -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - } -}); - -app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } -}); - -// Handle any errors -process.on("uncaughtException", (error) => { - logger.error("Uncaught Exception:", error); -}); diff --git a/src/electron/main.ts b/src/electron/main.ts deleted file mode 100644 index f799ed14..00000000 --- a/src/electron/main.ts +++ /dev/null @@ -1,712 +0,0 @@ -import { app, BrowserWindow, ipcMain } from "electron"; -import { Capacitor } from "@capacitor/core"; -import fs from "fs"; -import path from "path"; -import { fileURLToPath } from "url"; - -// Get __dirname equivalent in ES modules -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 - log: (...args: unknown[]) => console.log("[Main]", ...args), - // eslint-disable-next-line no-console - error: (...args: unknown[]) => console.error("[Main]", ...args), - // eslint-disable-next-line no-console - info: (...args: unknown[]) => console.info("[Main]", ...args), - // eslint-disable-next-line no-console - warn: (...args: unknown[]) => console.warn("[Main]", ...args), - // eslint-disable-next-line no-console - debug: (...args: unknown[]) => console.debug("[Main]", ...args), -}; - -logger.info("Starting main process initialization..."); - -// Initialize Capacitor for Electron in main process -try { - logger.info("About to initialize Capacitor..."); - logger.info("Capacitor before init:", { - hasPlatform: "platform" in Capacitor, - hasIsNativePlatform: "isNativePlatform" in Capacitor, - platformType: typeof Capacitor.platform, - isNativePlatformType: typeof Capacitor.isNativePlatform, - }); - - // Try direct assignment first - try { - (Capacitor as unknown as { platform: string }).platform = "electron"; - (Capacitor as unknown as { isNativePlatform: boolean }).isNativePlatform = - true; - logger.info("Direct assignment successful"); - } catch (e) { - logger.warn("Direct assignment failed, trying defineProperty:", e); - Object.defineProperty(Capacitor, "isNativePlatform", { - get: () => true, - configurable: true, - }); - - Object.defineProperty(Capacitor, "platform", { - get: () => "electron", - configurable: true, - }); - } - - logger.info("Capacitor after init:", { - platform: Capacitor.platform, - isNativePlatform: Capacitor.isNativePlatform, - platformType: typeof Capacitor.platform, - isNativePlatformType: typeof Capacitor.isNativePlatform, - }); -} catch (error) { - logger.error("Failed to initialize Capacitor:", error); - throw error; -} - -// Database path resolution utilities -const getAppDataPath = async (): Promise => { - try { - // Read config file directly - const configPath = path.join(__dirname, "..", "capacitor.config.json"); - const configContent = await fs.promises.readFile(configPath, "utf-8"); - const config = JSON.parse(configContent); - const linuxPath = config?.plugins?.CapacitorSQLite?.electronLinuxLocation; - - if (linuxPath) { - // Expand ~ to home directory - const expandedPath = linuxPath.replace(/^~/, process.env.HOME || ""); - logger.info("[Electron Main] Using configured database path:", expandedPath); - return expandedPath; - } - - // Fallback to app.getPath if config path is not available - const userDataPath = app.getPath("userData"); - logger.info("[Electron Main] Using fallback user data path:", userDataPath); - return userDataPath; - } catch (error) { - logger.error("[Electron Main] Error getting app data path:", error); - // Fallback to app.getPath if anything fails - const userDataPath = app.getPath("userData"); - logger.info( - "[Electron Main] Using fallback user data path after error:", - userDataPath, - ); - return userDataPath; - } -}; - -const validateAndNormalizePath = async (filePath: string): Promise => { - // Resolve any relative paths - const resolvedPath = path.resolve(filePath); - - // Ensure it's an absolute path - if (!path.isAbsolute(resolvedPath)) { - throw new Error(`Database path must be absolute: ${resolvedPath}`); - } - - // Ensure it's within the app data directory - const appDataPath = await getAppDataPath(); - if (!resolvedPath.startsWith(appDataPath)) { - throw new Error( - `Database path must be within app data directory: ${resolvedPath}`, - ); - } - - // Normalize the path - const normalizedPath = path.normalize(resolvedPath); - - logger.info("[Electron Main] Validated database path:", { - original: filePath, - resolved: resolvedPath, - normalized: normalizedPath, - appDataPath, - isAbsolute: path.isAbsolute(normalizedPath), - isWithinAppData: normalizedPath.startsWith(appDataPath), - }); - - return normalizedPath; -}; - -const ensureDirectoryExists = async (dirPath: string): Promise => { - try { - // Normalize the path first - const normalizedPath = path.normalize(dirPath); - - // Check if directory exists - if (!fs.existsSync(normalizedPath)) { - logger.info("[Electron Main] Creating database directory:", normalizedPath); - await fs.promises.mkdir(normalizedPath, { recursive: true }); - } - - // Verify directory permissions - try { - await fs.promises.access( - normalizedPath, - fs.constants.R_OK | fs.constants.W_OK, - ); - logger.info( - "[Electron Main] Database directory permissions verified:", - normalizedPath, - ); - } catch (error) { - logger.error("[Electron Main] Database directory permission error:", error); - throw new Error(`Database directory not accessible: ${normalizedPath}`); - } - - // Test write permissions - const testFile = path.join(normalizedPath, ".write-test"); - try { - await fs.promises.writeFile(testFile, "test"); - await fs.promises.unlink(testFile); - logger.info( - "[Electron Main] Database directory write test passed:", - normalizedPath, - ); - } catch (error) { - logger.error("[Electron Main] Database directory write test failed:", error); - throw new Error(`Database directory not writable: ${normalizedPath}`); - } - } catch (error) { - logger.error( - "[Electron Main] Failed to ensure database directory exists:", - error, - ); - throw error; - } -}; - -// Initialize database paths -let dbPath: string | undefined; -let dbDir: string | undefined; -let dbPathInitialized = false; -let dbPathInitializationPromise: Promise | null = null; - -const initializeDatabasePaths = async (): Promise => { - // Prevent multiple simultaneous initializations - if (dbPathInitializationPromise) { - return dbPathInitializationPromise; - } - - if (dbPathInitialized) { - return; - } - - dbPathInitializationPromise = (async () => { - try { - // Get the base directory from config - dbDir = await getAppDataPath(); - logger.info("[Electron Main] Database directory:", dbDir); - - // Ensure the directory exists and is writable - await ensureDirectoryExists(dbDir); - - // Construct the database path - dbPath = await validateAndNormalizePath( - path.join(dbDir, "timesafari.db"), - ); - logger.info("[Electron Main] Database path initialized:", dbPath); - - // Verify the database file if it exists - if (fs.existsSync(dbPath)) { - try { - await fs.promises.access( - dbPath, - fs.constants.R_OK | fs.constants.W_OK, - ); - logger.info( - "[Electron Main] Existing database file permissions verified:", - dbPath, - ); - } catch (error) { - logger.error("[Electron Main] Database file permission error:", error); - throw new Error(`Database file not accessible: ${dbPath}`); - } - } - - dbPathInitialized = true; - } catch (error) { - logger.error("[Electron Main] Failed to initialize database paths:", error); - throw error; - } finally { - dbPathInitializationPromise = null; - } - })(); - - return dbPathInitializationPromise; -}; - -// Initialize SQLite plugin -let sqlitePlugin: any = null; -let sqliteInitialized = false; -let sqliteInitializationPromise: Promise | null = null; - -async function initializeSQLite() { - // Prevent multiple simultaneous initializations - if (sqliteInitializationPromise) { - return sqliteInitializationPromise; - } - - if (sqliteInitialized) { - return; - } - - sqliteInitializationPromise = (async () => { - try { - logger.info("[Electron Main] Initializing SQLite plugin..."); - sqlitePlugin = new CapacitorSQLite(); - - // Initialize database paths first - await initializeDatabasePaths(); - - if (!dbPath) { - throw new Error("Database path not initialized"); - } - - // Test the plugin - const echoResult = await sqlitePlugin.echo({ value: "test" }); - logger.info("[Electron Main] SQLite plugin echo test:", echoResult); - - // Initialize database connection using validated dbPath - const connectionOptions = { - database: dbPath, - version: 1, - readOnly: false, - encryption: "no-encryption", - useNative: true, - mode: "rwc", // Force read-write-create mode - }; - - logger.info( - "[Electron Main] Creating initial connection with options:", - connectionOptions, - ); - - // Log the actual path being used - logger.info("[Electron Main] Using database path:", dbPath); - logger.info("[Electron Main] Path exists:", fs.existsSync(dbPath)); - logger.info("[Electron Main] Path is absolute:", path.isAbsolute(dbPath)); - - const db = await sqlitePlugin.createConnection(connectionOptions); - - if (!db || typeof db !== "object") { - throw new Error( - `Failed to create database connection - invalid response. Path used: ${dbPath}`, - ); - } - - // Wait a moment for the connection to be fully established - await new Promise((resolve) => setTimeout(resolve, 100)); - - // Verify the connection is working - try { - const result = await db.query("PRAGMA journal_mode;"); - logger.info("[Electron Main] Database connection verified:", result); - } catch (error) { - logger.error( - "[Electron Main] Database connection verification failed:", - error, - ); - throw error; - } - - sqliteInitialized = true; - logger.info("[Electron Main] SQLite plugin initialized successfully"); - } catch (error) { - logger.error("[Electron Main] Failed to initialize SQLite plugin:", error); - throw error; - } finally { - sqliteInitializationPromise = null; - } - })(); - - return sqliteInitializationPromise; -} - -// Initialize app when ready -app.whenReady().then(async () => { - logger.info("App is ready, starting initialization..."); - - // Create window first - const mainWindow = createWindow(); - - // Wait for window to be ready - await new Promise((resolve) => { - mainWindow.once('ready-to-show', () => { - logger.info("[Electron Main] Window ready to show"); - mainWindow.show(); - resolve(); - }); - }); - - // Initialize database after window is ready - try { - await initializeSQLite(); - logger.info("[Electron Main] SQLite plugin initialized successfully"); - - // Now send the ready signal since window is ready - if (!mainWindow.isDestroyed()) { - mainWindow.webContents.send('sqlite-ready'); - logger.info("[Electron Main] Sent SQLite ready signal to renderer"); - } else { - logger.error("[Electron Main] Window was destroyed before sending SQLite ready signal"); - } - } catch (error) { - logger.error( - "[Electron Main] Database initialization failed:", - error, - ); - // Notify renderer about database status - if (!mainWindow.isDestroyed()) { - mainWindow.webContents.send("database-status", { - status: "error", - error: error instanceof Error ? error.message : 'Unknown error', - }); - } - } - - // Handle window close - mainWindow.on("closed", () => { - logger.info("[Electron Main] Main window closed"); - }); - - // Handle window close request - mainWindow.on("close", (event) => { - logger.info("[Electron Main] Window close requested"); - // Prevent immediate close if we're in the middle of something - if (mainWindow.webContents.isLoading()) { - event.preventDefault(); - logger.info("[Electron Main] Deferring window close due to loading state"); - mainWindow.webContents.once("did-finish-load", () => { - mainWindow.close(); - }); - } - }); -}); - -// Check if running in dev mode -// const isDev = process.argv.includes("--inspect"); - -function createWindow(): BrowserWindow { - // Resolve preload path based on environment - const preloadPath = app.isPackaged - ? path.join(process.resourcesPath, "preload.js") - : path.join(__dirname, "preload.js"); - - logger.log("[Electron Main] Preload path:", preloadPath); - logger.log("[Electron Main] Preload exists:", fs.existsSync(preloadPath)); - - // Log environment and paths - logger.log("[Electron Main] process.cwd():", process.cwd()); - logger.log("[Electron Main] __dirname:", __dirname); - logger.log("[Electron Main] app.getAppPath():", app.getAppPath()); - logger.log("[Electron Main] app.isPackaged:", app.isPackaged); - logger.log("[Electron Main] process.resourcesPath:", process.resourcesPath); - - // List files in __dirname and __dirname/www - try { - logger.log("Files in __dirname:", fs.readdirSync(__dirname)); - const wwwDir = path.join(__dirname, "www"); - if (fs.existsSync(wwwDir)) { - logger.log("Files in www:", fs.readdirSync(wwwDir)); - } else { - logger.log("www directory does not exist in __dirname"); - } - } catch (e) { - logger.error("Error reading directories:", e); - } - - // Create the browser window with proper error handling - const mainWindow = new BrowserWindow({ - width: 1200, - height: 800, - show: false, // Don't show until ready - webPreferences: { - nodeIntegration: false, - contextIsolation: true, - sandbox: false, - preload: preloadPath, - webSecurity: true, - allowRunningInsecureContent: false, - }, - }); - - // Show window when ready - mainWindow.once("ready-to-show", () => { - logger.info("[Electron Main] Window ready to show"); - mainWindow.show(); - }); - - // Handle window errors - mainWindow.webContents.on("render-process-gone", (_event, details) => { - logger.error("[Electron Main] Render process gone:", details); - }); - - mainWindow.webContents.on( - "did-fail-load", - (_event, errorCode, errorDescription) => { - logger.error( - "[Electron Main] Page failed to load:", - errorCode, - errorDescription, - ); - logger.error("[Electron Main] Failed URL:", mainWindow.webContents.getURL()); - }, - ); - - // Load the index.html - let indexPath: string; - let fileUrl: string; - - if (app.isPackaged) { - indexPath = path.join(process.resourcesPath, "www", "index.html"); - fileUrl = `file://${indexPath}`; - logger.info( - "[Electron Main] App is packaged. Using process.resourcesPath for index.html", - ); - } else { - indexPath = path.resolve(__dirname, "www", "index.html"); - fileUrl = `file://${indexPath}`; - logger.info( - "[Electron Main] App is not packaged. Using __dirname for index.html", - ); - } - - logger.info("[Electron Main] Resolved index.html path:", indexPath); - logger.info("[Electron Main] Using file URL:", fileUrl); - - // Load the index.html with retry logic - const loadIndexHtml = async (retryCount = 0): Promise => { - try { - if (mainWindow.isDestroyed()) { - logger.error( - "[Electron Main] Window was destroyed before loading index.html", - ); - return; - } - - const exists = fs.existsSync(indexPath); - logger.info(`[Electron Main] fs.existsSync for index.html: ${exists}`); - - if (!exists) { - throw new Error(`index.html not found at path: ${indexPath}`); - } - - // Try to read the file to verify it's accessible - const stats = fs.statSync(indexPath); - logger.info("[Electron Main] index.html stats:", { - size: stats.size, - mode: stats.mode, - uid: stats.uid, - gid: stats.gid, - }); - - // Try loadURL first - try { - logger.info("[Electron Main] Attempting to load index.html via loadURL"); - await mainWindow.loadURL(fileUrl); - logger.info("[Electron Main] Successfully loaded index.html via loadURL"); - } catch (loadUrlError) { - logger.warn( - "[Electron Main] loadURL failed, trying loadFile:", - loadUrlError, - ); - // Fallback to loadFile - await mainWindow.loadFile(indexPath); - logger.info("[Electron Main] Successfully loaded index.html via loadFile"); - } - } catch (error: unknown) { - const errorMessage = - error instanceof Error ? error.message : "Unknown error occurred"; - logger.error("[Electron Main] Error loading index.html:", errorMessage); - - // Retry logic - if (retryCount < 3 && !mainWindow.isDestroyed()) { - logger.info( - `[Electron Main] Retrying index.html load (attempt ${retryCount + 1})`, - ); - await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second - return loadIndexHtml(retryCount + 1); - } - - // If we've exhausted retries, show error in window - if (!mainWindow.isDestroyed()) { - const errorHtml = ` - - -

Error Loading Application

-

Failed to load the application after multiple attempts.

-
${errorMessage}
- - - `; - await mainWindow.loadURL( - `data:text/html,${encodeURIComponent(errorHtml)}`, - ); - } - } - }; - - // Start loading the index.html - loadIndexHtml().catch((error: unknown) => { - logger.error("[Electron Main] Fatal error loading index.html:", error); - }); - - // Only open DevTools if not in production - if (!app.isPackaged) { - mainWindow.webContents.openDevTools({ mode: "detach" }); - } - - return mainWindow; -} - -// Handle app ready -app.whenReady().then(createWindow); - -// Handle all windows closed -app.on("window-all-closed", () => { - if (process.platform !== "darwin") { - app.quit(); - } -}); - -app.on("activate", () => { - if (BrowserWindow.getAllWindows().length === 0) { - createWindow(); - } -}); - -// Handle any errors -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("sqlite-echo", async (_event, value) => { - try { - return await sqlitePlugin.echo({ value }); - } catch (error) { - logger.error( - "Error in sqlite-echo:", - error, - JSON.stringify(error), - (error as any)?.stack, - ); - throw error; - } -}); - -ipcMain.handle("sqlite-create-connection", async (_event, options) => { - try { - // Ensure database is initialized - await initializeSQLite(); - - if (!dbPath) { - throw new Error("Database path not initialized"); - } - - // Override any provided database path with our resolved path - const connectionOptions = { - ...options, - database: dbPath, - readOnly: false, - mode: "rwc", // Force read-write-create mode - encryption: "no-encryption", - useNative: true, - }; - - logger.info( - "[Electron Main] Creating database connection with options:", - connectionOptions, - ); - const result = await sqlitePlugin.createConnection(connectionOptions); - - if (!result || typeof result !== "object") { - throw new Error( - "Failed to create database connection - invalid response", - ); - } - - // Wait a moment for the connection to be fully established - await new Promise((resolve) => setTimeout(resolve, 100)); - - try { - // Verify connection is not read-only - const testResult = await result.query({ - statement: "PRAGMA journal_mode;", - }); - if (testResult?.values?.[0]?.journal_mode === "off") { - logger.error( - "[Electron Main] Connection opened in read-only mode despite options", - ); - throw new Error("Database connection opened in read-only mode"); - } - } catch (queryError) { - logger.error("[Electron Main] Error verifying connection:", queryError); - throw queryError; - } - - logger.info("[Electron Main] Database connection created successfully"); - return result; - } catch (error) { - logger.error("[Electron Main] Error in sqlite-create-connection:", error); - throw error; - } -}); - -ipcMain.handle("sqlite-execute", async (_event, options) => { - try { - return await sqlitePlugin.execute(options); - } catch (error) { - logger.error( - "Error in sqlite-execute:", - error, - JSON.stringify(error), - (error as any)?.stack, - ); - throw error; - } -}); - -ipcMain.handle("sqlite-query", async (_event, options) => { - try { - return await sqlitePlugin.query(options); - } catch (error) { - logger.error( - "Error in sqlite-query:", - error, - JSON.stringify(error), - (error as any)?.stack, - ); - throw error; - } -}); - -ipcMain.handle("sqlite-close-connection", async (_event, options) => { - try { - return await sqlitePlugin.closeConnection(options); - } catch (error) { - logger.error( - "Error in sqlite-close-connection:", - error, - JSON.stringify(error), - (error as any)?.stack, - ); - throw error; - } -}); - -ipcMain.handle("sqlite-is-available", async () => { - return sqlitePlugin !== null; -}); diff --git a/src/electron/preload.js b/src/electron/preload.js deleted file mode 100644 index 7168d4f0..00000000 --- a/src/electron/preload.js +++ /dev/null @@ -1,178 +0,0 @@ -const { contextBridge, ipcRenderer } = require("electron"); - -const logger = { - log: (message, ...args) => { - // Always log in development, log with context in production - if (process.env.NODE_ENV !== "production") { - /* eslint-disable no-console */ - console.log(`[Electron Preload] ${message}`, ...args); - /* eslint-enable no-console */ - } - }, - warn: (message, ...args) => { - // Always log warnings - /* eslint-disable no-console */ - console.warn(`[Electron Preload] ${message}`, ...args); - /* eslint-enable no-console */ - }, - error: (message, ...args) => { - // Always log errors - /* eslint-disable no-console */ - console.error(`[Electron Preload] ${message}`, ...args); - /* eslint-enable no-console */ - }, - info: (message, ...args) => { - // Always log info in development, log with context in production - if (process.env.NODE_ENV !== "production") { - /* eslint-disable no-console */ - console.info(`[Electron Preload] ${message}`, ...args); - /* eslint-enable no-console */ - } - }, -}; - -// Use a more direct path resolution approach -const getPath = (pathType) => { - switch (pathType) { - case "userData": - return ( - process.env.APPDATA || - (process.platform === "darwin" - ? `${process.env.HOME}/Library/Application Support` - : `${process.env.HOME}/.local/share`) - ); - case "home": - return process.env.HOME; - case "appPath": - return process.resourcesPath; - default: - return ""; - } -}; - -logger.info("Preload script starting..."); - -// Force electron platform in the renderer process -window.process = { env: { VITE_PLATFORM: "electron" } }; - -try { - contextBridge.exposeInMainWorld("electronAPI", { - // Path utilities - getPath, - - // IPC functions - send: (channel, data) => { - const validChannels = ["toMain"]; - if (validChannels.includes(channel)) { - ipcRenderer.send(channel, data); - } - }, - receive: (channel, func) => { - const validChannels = ["fromMain"]; - if (validChannels.includes(channel)) { - ipcRenderer.on(channel, (event, ...args) => func(...args)); - } - }, - // Environment info - env: { - isElectron: true, - isDev: process.env.NODE_ENV === "development", - platform: "electron", // Explicitly set platform - }, - // Path utilities - getBasePath: () => { - return process.env.NODE_ENV === "development" ? "/" : "./"; - }, - }); - - // Create a proxy for the CapacitorSQLite plugin - const createSQLiteProxy = () => { - const MAX_RETRIES = 3; - const RETRY_DELAY = 1000; // 1 second - - const withRetry = async (operation, ...args) => { - let lastError; - for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { - try { - return await operation(...args); - } catch (error) { - lastError = error; - if (attempt < MAX_RETRIES) { - logger.warn( - `SQLite operation failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`, - error, - ); - await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY)); - } - } - } - throw new Error( - `SQLite operation failed after ${MAX_RETRIES} attempts: ${lastError?.message || "Unknown error"}`, - ); - }; - - const wrapOperation = (method) => { - return async (...args) => { - try { - return await withRetry( - ipcRenderer.invoke, - "sqlite-" + method, - ...args, - ); - } catch (error) { - logger.error(`SQLite ${method} failed:`, error); - throw new Error( - `Database operation failed: ${error.message || "Unknown error"}`, - ); - } - }; - }; - - // Create a proxy that matches the CapacitorSQLite interface - return { - echo: wrapOperation("echo"), - createConnection: wrapOperation("create-connection"), - closeConnection: wrapOperation("close-connection"), - execute: wrapOperation("execute"), - query: wrapOperation("query"), - run: wrapOperation("run"), - isAvailable: wrapOperation("is-available"), - getPlatform: () => Promise.resolve("electron"), - // Add other methods as needed - }; - }; - - // Expose only the CapacitorSQLite proxy - contextBridge.exposeInMainWorld("electron", { - sqlite: createSQLiteProxy(), - getPath: (pathType) => ipcRenderer.invoke("get-path", pathType), - send: (channel, data) => { - ipcRenderer.send(channel, data); - }, - receive: (channel, func) => { - ipcRenderer.on(channel, (event, ...args) => func(...args)); - }, - ipcRenderer: { - on: (channel, func) => { - ipcRenderer.on(channel, (event, ...args) => func(...args)); - }, - once: (channel, func) => { - ipcRenderer.once(channel, (event, ...args) => func(...args)); - }, - send: (channel, data) => { - ipcRenderer.send(channel, data); - }, - invoke: (channel, ...args) => { - return ipcRenderer.invoke(channel, ...args); - } - }, - env: { - platform: "electron", - }, - getBasePath: () => ipcRenderer.invoke("get-base-path"), - }); - - logger.info("Preload script completed successfully"); -} catch (error) { - logger.error("Error in preload script:", error); -} diff --git a/vite.config.electron.mts b/vite.config.electron.mts index cdb1fd90..83739535 100644 --- a/vite.config.electron.mts +++ b/vite.config.electron.mts @@ -7,7 +7,6 @@ export default defineConfig({ rollupOptions: { input: { main: path.resolve(__dirname, 'src/electron/main.ts'), - preload: path.resolve(__dirname, 'src/electron/preload.js'), }, external: [ // Node.js built-ins