From 3b4ef908f3a4d7bddae154b10b9790947b2e1423 Mon Sep 17 00:00:00 2001 From: Matthew Raymer Date: Fri, 30 May 2025 09:10:01 +0000 Subject: [PATCH] feat(electron): improve window and database initialization - Make database initialization non-blocking to prevent app crashes - Add proper window lifecycle management and error handling - Implement retry logic for index.html loading - Add detailed logging for debugging - Fix type safety issues in error handling - Add proper cleanup on window close WIP: Database path resolution still needs fixing - Current issue: Path conflict between ~/.local/share and ~/.config - Database connection failing with invalid response - Multiple connection attempts occurring This commit improves app stability but database connectivity needs to be addressed in a follow-up commit. --- experiment.sh | 165 +++++++++++--- src/electron/main.ts | 509 +++++++++++++++++++++++++++++-------------- 2 files changed, 488 insertions(+), 186 deletions(-) diff --git a/experiment.sh b/experiment.sh index f8c37426..f0846ea0 100755 --- a/experiment.sh +++ b/experiment.sh @@ -1,71 +1,186 @@ -#! /bin/bash +#!/bin/bash +# experiment.sh +# Author: Matthew Raymer +# Description: Build script for TimeSafari Electron application +# This script handles the complete build process for the TimeSafari Electron app, +# including web asset compilation, TypeScript compilation, and AppImage packaging. +# It ensures all dependencies are available and provides detailed build feedback. +# +# Build Process: +# 1. Environment setup and dependency checks +# 2. Web asset compilation (Vite) +# 3. TypeScript compilation +# 4. Electron main process build +# 5. AppImage packaging +# +# Dependencies: +# - Node.js and npm +# - TypeScript +# - Vite +# - electron-builder +# +# Usage: ./experiment.sh +# +# Exit Codes: +# 1 - Required command not found +# 2 - TypeScript installation failed +# 3 - TypeScript compilation failed +# 4 - Build process failed +# 5 - AppImage build failed # Exit on any error set -e +# ANSI color codes for better output formatting +readonly RED='\033[0;31m' +readonly GREEN='\033[0;32m' +readonly YELLOW='\033[1;33m' +readonly BLUE='\033[0;34m' +readonly NC='\033[0m' # No Color + +# Logging functions +log_info() { + echo -e "${BLUE}[$(date '+%Y-%m-%d %H:%M:%S')] [INFO]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[$(date '+%Y-%m-%d %H:%M:%S')] [SUCCESS]${NC} $1" +} + +log_warn() { + echo -e "${YELLOW}[$(date '+%Y-%m-%d %H:%M:%S')] [WARN]${NC} $1" +} + +log_error() { + echo -e "${RED}[$(date '+%Y-%m-%d %H:%M:%S')] [ERROR]${NC} $1" +} + # Function to check if a command exists check_command() { - if ! command -v $1 &> /dev/null; then - echo "Error: $1 is required but not installed." + if ! command -v "$1" &> /dev/null; then + log_error "$1 is required but not installed." exit 1 fi + log_info "Found $1: $(command -v "$1")" +} + +# Function to measure and log execution time +measure_time() { + local start_time=$(date +%s) + "$@" + local end_time=$(date +%s) + local duration=$((end_time - start_time)) + log_success "Completed in ${duration} seconds" +} + +# Function to find the AppImage +find_appimage() { + local appimage_path + appimage_path=$(find dist-electron-packages -name "*.AppImage" -type f -print -quit) + if [ -n "$appimage_path" ]; then + echo "$appimage_path" + else + log_warn "AppImage not found in expected location" + echo "dist-electron-packages/*.AppImage" + fi } +# Print build header +echo -e "\n${BLUE}=== TimeSafari Electron Build Process ===${NC}\n" +log_info "Starting build process at $(date)" + # Check required commands +log_info "Checking required dependencies..." check_command node check_command npm +# Create application data directory +log_info "Setting up application directories..." mkdir -p ~/.local/share/TimeSafari/timesafari # Clean up previous builds -echo "Cleaning previous builds..." -rm -rf dist* +log_info "Cleaning previous builds..." +rm -rf dist* || log_warn "No previous builds to clean" # Set environment variables for the build -echo "Setting up environment variables..." +log_info "Configuring build environment..." export VITE_PLATFORM=electron export VITE_PWA_ENABLED=false export VITE_DISABLE_PWA=true # Ensure TypeScript is installed -echo "Checking TypeScript installation..." +log_info "Verifying TypeScript installation..." if [ ! -f "./node_modules/.bin/tsc" ]; then - echo "Installing TypeScript..." - npm install --save-dev typescript@~5.2.2 + log_info "Installing TypeScript..." + if ! npm install --save-dev typescript@~5.2.2; then + log_error "TypeScript installation failed!" + exit 2 + fi # Verify installation if [ ! -f "./node_modules/.bin/tsc" ]; then - echo "TypeScript installation failed!" - exit 1 + log_error "TypeScript installation verification failed!" + exit 2 fi + log_success "TypeScript installed successfully" +else + log_info "TypeScript already installed" fi # Get git hash for versioning GIT_HASH=$(git log -1 --pretty=format:%h) +log_info "Using git hash: ${GIT_HASH}" # Build web assets -echo "Building web assets..." -VITE_GIT_HASH=$GIT_HASH npx vite build --config vite.config.app.electron.mts --mode electron +log_info "Building web assets with Vite..." +if ! measure_time env VITE_GIT_HASH="$GIT_HASH" npx vite build --config vite.config.app.electron.mts --mode electron; then + log_error "Web asset build failed!" + exit 4 +fi # TypeScript compilation -echo "Running TypeScript compilation..." -if ! ./node_modules/.bin/tsc -p tsconfig.electron.json; then - echo "TypeScript compilation failed!" - exit 1 +log_info "Compiling TypeScript..." +if ! measure_time ./node_modules/.bin/tsc -p tsconfig.electron.json; then + log_error "TypeScript compilation failed!" + exit 3 fi # Build electron main process -echo "Building electron main process..." -VITE_GIT_HASH=$GIT_HASH npx vite build --config vite.config.electron.mts --mode electron +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 -echo "Organizing build files..." +log_info "Organizing build artifacts..." mkdir -p dist-electron/www -cp -r dist/* 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 +cp src/electron/preload.js dist-electron/resources/preload.js || log_error "Failed to copy preload script" # Build the AppImage -echo "Building AppImage..." -npx electron-builder --linux AppImage +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}" +log_success "Build completed successfully!" +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 "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" -echo "Build completed successfully!" \ No newline at end of file +# Exit with success +exit 0 \ No newline at end of file diff --git a/src/electron/main.ts b/src/electron/main.ts index 3778a5d5..c125d0ea 100644 --- a/src/electron/main.ts +++ b/src/electron/main.ts @@ -71,18 +71,114 @@ try { throw error; } +// Database path resolution utilities +const getAppDataPath = (): string => { + const userDataPath = app.getPath('userData'); + logger.info("[Electron] User data path:", userDataPath); + return userDataPath; +}; + +const validateAndNormalizePath = (filePath: string): string => { + try { + // Resolve any relative paths and normalize + const normalizedPath = path.resolve(filePath); + logger.info("[Electron] Normalized database path:", normalizedPath); + + // Verify the path is absolute + if (!path.isAbsolute(normalizedPath)) { + throw new Error(`Database path must be absolute: ${normalizedPath}`); + } + + // Verify the path is within the user data directory + const userDataPath = getAppDataPath(); + if (!normalizedPath.startsWith(userDataPath)) { + throw new Error(`Database path must be within user data directory: ${normalizedPath}`); + } + + return normalizedPath; + } catch (error) { + logger.error("[Electron] Path validation failed:", error); + throw error; + } +}; + +const ensureDirectoryExists = (dirPath: string): void => { + try { + const normalizedDir = validateAndNormalizePath(dirPath); + if (!fs.existsSync(normalizedDir)) { + fs.mkdirSync(normalizedDir, { recursive: true, mode: 0o755 }); + logger.info("[Electron] Created directory:", normalizedDir); + } + // Verify directory permissions + const stats = fs.statSync(normalizedDir); + if (!stats.isDirectory()) { + throw new Error(`Path exists but is not a directory: ${normalizedDir}`); + } + // Check if directory is writable + try { + const testFile = path.join(normalizedDir, '.write-test'); + fs.writeFileSync(testFile, 'test'); + fs.unlinkSync(testFile); + } catch (err) { + throw new Error(`Directory not writable: ${normalizedDir}`); + } + } catch (error) { + logger.error("[Electron] Directory setup failed:", error); + throw error; + } +}; + // Database path logic let dbPath: string; let dbDir: string; -app.whenReady().then(() => { - const basePath = app.getPath('userData'); - dbDir = path.join(basePath, 'timesafari'); - dbPath = path.join(dbDir, 'timesafari.db'); - if (!fs.existsSync(dbDir)) { - fs.mkdirSync(dbDir, { recursive: true }); +// Initialize database paths +const initializeDatabasePaths = (): void => { + try { + const basePath = getAppDataPath(); + dbDir = path.join(basePath, 'timesafari'); + dbPath = path.join(dbDir, 'timesafari.db'); + + // Validate and normalize paths + dbDir = validateAndNormalizePath(dbDir); + dbPath = validateAndNormalizePath(dbPath); + + // Ensure directory exists and is writable + ensureDirectoryExists(dbDir); + + logger.info("[Electron] Database directory:", dbDir); + logger.info("[Electron] Database file path:", dbPath); + + // Verify database file permissions if it exists + if (fs.existsSync(dbPath)) { + try { + // Ensure the database file is writable + fs.accessSync(dbPath, fs.constants.R_OK | fs.constants.W_OK); + // Try to open the file in write mode to verify permissions + const fd = fs.openSync(dbPath, 'r+'); + fs.closeSync(fd); + logger.info("[Electron] Database file exists and is writable"); + } catch (err) { + logger.error("[Electron] Database file exists but is not writable:", err); + // Try to fix permissions + try { + fs.chmodSync(dbPath, 0o644); + logger.info("[Electron] Fixed database file permissions"); + } catch (chmodErr) { + logger.error("[Electron] Failed to fix database permissions:", chmodErr); + throw new Error(`Database file not writable: ${dbPath}`); + } + } + } + } catch (error) { + logger.error("[Electron] Failed to initialize database paths:", error); + throw error; } - console.log('[Main] [Electron] Resolved dbPath:', dbPath); +}; + +// Initialize paths when app is ready +app.whenReady().then(() => { + initializeDatabasePaths(); }); // Initialize SQLite plugin @@ -90,41 +186,134 @@ let sqlitePlugin: any = null; async function initializeSQLite() { try { - console.log("Initializing SQLite plugin..."); + logger.info("[Electron] Initializing SQLite plugin..."); sqlitePlugin = new CapacitorSQLite(); + // Test the plugin const echoResult = await sqlitePlugin.echo({ value: "test" }); - console.log("SQLite plugin echo test:", echoResult); + logger.info("[Electron] SQLite plugin echo test:", echoResult); + // Initialize database connection using absolute dbPath - const db = await sqlitePlugin.createConnection({ - database: dbPath, + const connectionOptions = { + database: dbPath, // This is now guaranteed to be a valid absolute path version: 1, - }); - console.log("SQLite plugin initialized successfully"); + readOnly: false, + encryption: "no-encryption", + useNative: true, + mode: "rwc" // Force read-write-create mode + }; + + logger.info("[Electron] Creating initial connection with options:", connectionOptions); + + // Log the actual path being used + logger.info("[Electron] Using database path:", dbPath); + logger.info("[Electron] Path exists:", fs.existsSync(dbPath)); + logger.info("[Electron] 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)); + + try { + // Verify connection is not read-only + const result = await db.query({ statement: "PRAGMA journal_mode;" }); + logger.info("[Electron] Database journal mode:", result); + + if (result?.values?.[0]?.journal_mode === "off") { + logger.warn("[Electron] Database opened in read-only mode, attempting to fix..."); + // Try to close and reopen with explicit permissions + await db.closeConnection(); + const newDb = await sqlitePlugin.createConnection({ + ...connectionOptions, + mode: "rwc", + readOnly: false + }); + + if (!newDb || typeof newDb !== 'object') { + throw new Error(`Failed to create new database connection - invalid response. Path used: ${dbPath}`); + } + + logger.info("[Electron] Reopened database connection"); + return newDb; + } + } catch (queryError) { + logger.error("[Electron] Error verifying database connection:", queryError); + // If we can't query, try to close and reopen + try { + await db.closeConnection(); + } catch (closeError) { + logger.warn("[Electron] Error closing failed connection:", closeError); + } + + // Try one more time with basic options + const retryDb = await sqlitePlugin.createConnection({ + database: dbPath, + version: 1, + readOnly: false + }); + + if (!retryDb || typeof retryDb !== 'object') { + throw new Error(`Failed to create database connection after retry. Path used: ${dbPath}`); + } + + return retryDb; + } + + logger.info("[Electron] SQLite plugin initialized successfully"); return db; } catch (error) { - console.error("Failed to initialize SQLite plugin:", error); + logger.error("[Electron] Failed to initialize SQLite plugin:", error); throw error; } } // Initialize app when ready 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(); - } + logger.info("App is ready, starting initialization..."); + + // Create window first + const mainWindow = createWindow(); + + // Initialize database in background + initializeSQLite().catch((error) => { + logger.error("[Electron] Database initialization failed, but continuing:", error); + // Notify renderer about database status + if (!mainWindow.isDestroyed()) { + mainWindow.webContents.send('database-status', { + status: 'error', + error: error.message + }); + } + }); + + // Handle window close + mainWindow.on('closed', () => { + logger.info("[Electron] Main window closed"); + }); + + // Handle window close request + mainWindow.on('close', (event) => { + logger.info("[Electron] Window close requested"); + // Prevent immediate close if we're in the middle of something + if (mainWindow.webContents.isLoading()) { + event.preventDefault(); + logger.info("[Electron] 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(): void { +function createWindow(): BrowserWindow { // Resolve preload path based on environment const preloadPath = app.isPackaged ? path.join(process.resourcesPath, "preload.js") @@ -153,174 +342,127 @@ function createWindow(): void { logger.error("Error reading directories:", e); } - // Create the browser window. + // 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 }, }); - // Track DevTools state - mainWindow.webContents.on("devtools-opened", () => { - logger.info("[Electron] DevTools opened"); + // Show window when ready + mainWindow.once('ready-to-show', () => { + logger.info("[Electron] Window ready to show"); + mainWindow.show(); }); - mainWindow.webContents.on("devtools-closed", () => { - logger.warn("[Electron] DevTools closed - reopening"); - mainWindow.webContents.openDevTools(); + // Handle window errors + mainWindow.webContents.on('render-process-gone', (event, details) => { + logger.error("[Electron] Render process gone:", details); }); - // Force DevTools to stay open - const forceDevTools = () => { - logger.info("[Electron] Forcing DevTools open"); - mainWindow.webContents.openDevTools(); - }; - - // Open DevTools immediately and set up periodic check - forceDevTools(); - setInterval(forceDevTools, 5000); // Check every 5 seconds - - // Intercept requests to fix asset paths - mainWindow.webContents.session.webRequest.onBeforeRequest( - { - urls: [ - "file://*/*/assets/*", - "file://*/assets/*", - "file:///assets/*", - // Removed to reduce noise - ], - }, - (details, callback) => { - let url = details.url; - let wasRewritten = false; - - // Get the base directory for assets - const baseDir = app.isPackaged - ? `file://${process.resourcesPath}` - : `file://${__dirname}`; - - // Handle paths that don't start with file:// - if (!url.startsWith("file://") && url.includes("/assets/")) { - url = `${baseDir}/www${url}`; - wasRewritten = true; - logger.info("[Electron] Rewritten non-file URL to:", url); - } - - // Handle absolute paths starting with /assets/ - if (url.includes("/assets/") && !url.includes("/www/assets/")) { - const assetPath = url.split("/assets/")[1]; - const newUrl = `${baseDir}/www/assets/${assetPath}`; - wasRewritten = true; - logger.info("[Electron] Rewritten asset URL to:", newUrl); - callback({ redirectURL: newUrl }); - return; - } - - // Only log if the URL was actually rewritten - if (wasRewritten) { - logger.info("[Electron] URL rewritten:", details.url, "->", url); - } - - callback({}); - }, - ); - - 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()); - } + mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => { + logger.error("[Electron] Page failed to load:", errorCode, errorDescription); + logger.error("[Electron] Failed URL:", mainWindow.webContents.getURL()); + }); + // Load the index.html let indexPath: string; + let fileUrl: string; if (app.isPackaged) { - // In production, files are inside the asar archive or extraResources indexPath = path.join(process.resourcesPath, "www", "index.html"); - logger.info( - "[Electron] App is packaged. Using process.resourcesPath for index.html", - ); + fileUrl = `file://${indexPath}`; + logger.info("[Electron] App is packaged. Using process.resourcesPath for index.html"); } else { - // In dev, use the local path indexPath = path.resolve(__dirname, "www", "index.html"); - logger.info( - "[Electron] App is not packaged. Using __dirname for index.html", - ); + fileUrl = `file://${indexPath}`; + logger.info("[Electron] App is not packaged. Using __dirname for index.html"); } logger.info("[Electron] Resolved index.html path:", indexPath); - try { - const exists = fs.existsSync(indexPath); - logger.info(`[Electron] fs.existsSync for index.html: ${exists}`); - } catch (e) { - logger.error("[Electron] Error checking fs.existsSync for index.html:", e); - } + logger.info("[Electron] Using file URL:", fileUrl); - // Removed fs.existsSync check to allow Electron to attempt loading regardless - - logger.info( - "[Electron] Attempting to load index.html via mainWindow.loadFile", - ); - mainWindow - .loadFile(indexPath) - .then(() => { - logger.info("[Electron] Successfully loaded index.html"); - }) - .catch((err) => { - logger.error("[Electron] Failed to load index.html:", err); - logger.error("[Electron] Attempted path:", indexPath); - try { - const exists = fs.existsSync(indexPath); - logger.error( - `[Electron] fs.existsSync after loadFile error: ${exists}`, - ); - } catch (e) { - logger.error( - "[Electron] Error checking fs.existsSync after loadFile error:", - e, - ); + // Load the index.html with retry logic + const loadIndexHtml = async (retryCount = 0) => { + try { + if (mainWindow.isDestroyed()) { + logger.error("[Electron] Window was destroyed before loading index.html"); + return; } - }); - - // 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); - }, - ); + const exists = fs.existsSync(indexPath); + logger.info(`[Electron] fs.existsSync for index.html: ${exists}`); - mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => { - logger.error("Preload script error:", preloadPath, error); - }); + if (!exists) { + throw new Error(`index.html not found at path: ${indexPath}`); + } - mainWindow.webContents.on( - "console-message", - (_event, _level, message, line, sourceId) => { - logger.log("Renderer Console:", line, sourceId, message); - }, - ); + // Try to read the file to verify it's accessible + const stats = fs.statSync(indexPath); + logger.info("[Electron] index.html stats:", { + size: stats.size, + mode: stats.mode, + uid: stats.uid, + gid: stats.gid + }); - mainWindow.webContents.openDevTools({ mode: "detach" }); - mainWindow.webContents.once("devtools-opened", () => { - if (mainWindow.webContents.devToolsWebContents) { - mainWindow.webContents.devToolsWebContents.focus(); + // Try loadURL first + try { + logger.info("[Electron] Attempting to load index.html via loadURL"); + await mainWindow.loadURL(fileUrl); + logger.info("[Electron] Successfully loaded index.html via loadURL"); + } catch (loadUrlError) { + logger.warn("[Electron] loadURL failed, trying loadFile:", loadUrlError); + // Fallback to loadFile + await mainWindow.loadFile(indexPath); + logger.info("[Electron] Successfully loaded index.html via loadFile"); + } + } catch (error: unknown) { + const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; + logger.error("[Electron] Error loading index.html:", errorMessage); + + // Retry logic + if (retryCount < 3 && !mainWindow.isDestroyed()) { + logger.info(`[Electron] 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 => { + logger.error("[Electron] 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 @@ -360,9 +502,54 @@ ipcMain.handle("sqlite-echo", async (_event, value) => { ipcMain.handle("sqlite-create-connection", async (_event, options) => { try { - return await sqlitePlugin.createConnection(options); + // 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] 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] Connection opened in read-only mode despite options"); + throw new Error("Database connection opened in read-only mode"); + } + } catch (queryError) { + logger.error("[Electron] Error verifying connection:", queryError); + // If verification fails, try a simpler connection + await result.closeConnection().catch(() => {}); + const retryResult = await sqlitePlugin.createConnection({ + database: dbPath, + version: 1, + readOnly: false + }); + + if (!retryResult || typeof retryResult !== 'object') { + throw new Error("Failed to create database connection after retry"); + } + + return retryResult; + } + + logger.info("[Electron] Database connection created successfully"); + return result; } catch (error) { - logger.error("Error in sqlite-create-connection:", error, JSON.stringify(error), (error as any)?.stack); + logger.error("[Electron] Error in sqlite-create-connection:", error); throw error; } });