Browse Source

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.
pull/134/head
Matthew Raymer 4 weeks ago
parent
commit
3b4ef908f3
  1. 163
      experiment.sh
  2. 491
      src/electron/main.ts

163
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 # Exit on any error
set -e 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 # Function to check if a command exists
check_command() { check_command() {
if ! command -v $1 &> /dev/null; then if ! command -v "$1" &> /dev/null; then
echo "Error: $1 is required but not installed." log_error "$1 is required but not installed."
exit 1 exit 1
fi 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 # Check required commands
log_info "Checking required dependencies..."
check_command node check_command node
check_command npm check_command npm
# Create application data directory
log_info "Setting up application directories..."
mkdir -p ~/.local/share/TimeSafari/timesafari mkdir -p ~/.local/share/TimeSafari/timesafari
# Clean up previous builds # Clean up previous builds
echo "Cleaning previous builds..." log_info "Cleaning previous builds..."
rm -rf dist* rm -rf dist* || log_warn "No previous builds to clean"
# Set environment variables for the build # Set environment variables for the build
echo "Setting up environment variables..." log_info "Configuring build environment..."
export VITE_PLATFORM=electron export VITE_PLATFORM=electron
export VITE_PWA_ENABLED=false export VITE_PWA_ENABLED=false
export VITE_DISABLE_PWA=true export VITE_DISABLE_PWA=true
# Ensure TypeScript is installed # Ensure TypeScript is installed
echo "Checking TypeScript installation..." log_info "Verifying TypeScript installation..."
if [ ! -f "./node_modules/.bin/tsc" ]; then if [ ! -f "./node_modules/.bin/tsc" ]; then
echo "Installing TypeScript..." log_info "Installing TypeScript..."
npm install --save-dev typescript@~5.2.2 if ! npm install --save-dev typescript@~5.2.2; then
log_error "TypeScript installation failed!"
exit 2
fi
# Verify installation # Verify installation
if [ ! -f "./node_modules/.bin/tsc" ]; then if [ ! -f "./node_modules/.bin/tsc" ]; then
echo "TypeScript installation failed!" log_error "TypeScript installation verification failed!"
exit 1 exit 2
fi fi
log_success "TypeScript installed successfully"
else
log_info "TypeScript already installed"
fi fi
# Get git hash for versioning # Get git hash for versioning
GIT_HASH=$(git log -1 --pretty=format:%h) GIT_HASH=$(git log -1 --pretty=format:%h)
log_info "Using git hash: ${GIT_HASH}"
# Build web assets # Build web assets
echo "Building web assets..." log_info "Building web assets with Vite..."
VITE_GIT_HASH=$GIT_HASH npx vite build --config vite.config.app.electron.mts --mode electron 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 # TypeScript compilation
echo "Running TypeScript compilation..." log_info "Compiling TypeScript..."
if ! ./node_modules/.bin/tsc -p tsconfig.electron.json; then if ! measure_time ./node_modules/.bin/tsc -p tsconfig.electron.json; then
echo "TypeScript compilation failed!" log_error "TypeScript compilation failed!"
exit 1 exit 3
fi fi
# Build electron main process # Build electron main process
echo "Building electron main process..." log_info "Building electron main process..."
VITE_GIT_HASH=$GIT_HASH npx vite build --config vite.config.electron.mts --mode electron 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 # Organize files
echo "Organizing build files..." log_info "Organizing build artifacts..."
mkdir -p dist-electron/www 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 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 # Build the AppImage
echo "Building AppImage..." log_info "Building AppImage package..."
npx electron-builder --linux AppImage 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!" # Exit with success
exit 0

491
src/electron/main.ts

@ -71,18 +71,114 @@ try {
throw error; 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 // Database path logic
let dbPath: string; let dbPath: string;
let dbDir: string; let dbDir: string;
app.whenReady().then(() => { // Initialize database paths
const basePath = app.getPath('userData'); const initializeDatabasePaths = (): void => {
try {
const basePath = getAppDataPath();
dbDir = path.join(basePath, 'timesafari'); dbDir = path.join(basePath, 'timesafari');
dbPath = path.join(dbDir, 'timesafari.db'); dbPath = path.join(dbDir, 'timesafari.db');
if (!fs.existsSync(dbDir)) {
fs.mkdirSync(dbDir, { recursive: true }); // 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}`);
}
} }
console.log('[Main] [Electron] Resolved dbPath:', dbPath); }
} catch (error) {
logger.error("[Electron] Failed to initialize database paths:", error);
throw error;
}
};
// Initialize paths when app is ready
app.whenReady().then(() => {
initializeDatabasePaths();
}); });
// Initialize SQLite plugin // Initialize SQLite plugin
@ -90,41 +186,134 @@ let sqlitePlugin: any = null;
async function initializeSQLite() { async function initializeSQLite() {
try { try {
console.log("Initializing SQLite plugin..."); logger.info("[Electron] Initializing SQLite plugin...");
sqlitePlugin = new CapacitorSQLite(); sqlitePlugin = new CapacitorSQLite();
// Test the plugin // Test the plugin
const echoResult = await sqlitePlugin.echo({ value: "test" }); 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 // Initialize database connection using absolute dbPath
const db = await sqlitePlugin.createConnection({ const connectionOptions = {
database: dbPath, // This is now guaranteed to be a valid absolute path
version: 1,
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, database: dbPath,
version: 1, version: 1,
readOnly: false
}); });
console.log("SQLite plugin initialized successfully");
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; return db;
} catch (error) { } catch (error) {
console.error("Failed to initialize SQLite plugin:", error); logger.error("[Electron] Failed to initialize SQLite plugin:", error);
throw error; throw error;
} }
} }
// Initialize app when ready // Initialize app when ready
app.whenReady().then(async () => { app.whenReady().then(async () => {
logger.info("App is ready, initializing SQLite..."); logger.info("App is ready, starting initialization...");
try {
await initializeSQLite(); // Create window first
logger.info("SQLite initialized, creating window..."); const mainWindow = createWindow();
createWindow();
} catch (error) { // Initialize database in background
logger.error("Failed to initialize app:", error); initializeSQLite().catch((error) => {
app.quit(); 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 // Check if running in dev mode
const isDev = process.argv.includes("--inspect"); const isDev = process.argv.includes("--inspect");
function createWindow(): void { function createWindow(): BrowserWindow {
// Resolve preload path based on environment // Resolve preload path based on environment
const preloadPath = app.isPackaged const preloadPath = app.isPackaged
? path.join(process.resourcesPath, "preload.js") ? path.join(process.resourcesPath, "preload.js")
@ -153,174 +342,127 @@ function createWindow(): void {
logger.error("Error reading directories:", e); logger.error("Error reading directories:", e);
} }
// Create the browser window. // Create the browser window with proper error handling
const mainWindow = new BrowserWindow({ const mainWindow = new BrowserWindow({
width: 1200, width: 1200,
height: 800, height: 800,
show: false, // Don't show until ready
webPreferences: { webPreferences: {
nodeIntegration: false, nodeIntegration: false,
contextIsolation: true, contextIsolation: true,
sandbox: false, sandbox: false,
preload: preloadPath, preload: preloadPath,
webSecurity: true,
allowRunningInsecureContent: false
}, },
}); });
// Track DevTools state // Show window when ready
mainWindow.webContents.on("devtools-opened", () => { mainWindow.once('ready-to-show', () => {
logger.info("[Electron] DevTools opened"); logger.info("[Electron] Window ready to show");
mainWindow.show();
}); });
mainWindow.webContents.on("devtools-closed", () => { // Handle window errors
logger.warn("[Electron] DevTools closed - reopening"); mainWindow.webContents.on('render-process-gone', (event, details) => {
mainWindow.webContents.openDevTools(); logger.error("[Electron] Render process gone:", details);
}); });
// Force DevTools to stay open mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription) => {
const forceDevTools = () => { logger.error("[Electron] Page failed to load:", errorCode, errorDescription);
logger.info("[Electron] Forcing DevTools open"); logger.error("[Electron] Failed URL:", mainWindow.webContents.getURL());
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 <all_urls> 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());
}
// Load the index.html
let indexPath: string; let indexPath: string;
let fileUrl: string;
if (app.isPackaged) { if (app.isPackaged) {
// In production, files are inside the asar archive or extraResources
indexPath = path.join(process.resourcesPath, "www", "index.html"); indexPath = path.join(process.resourcesPath, "www", "index.html");
logger.info( fileUrl = `file://${indexPath}`;
"[Electron] App is packaged. Using process.resourcesPath for index.html", logger.info("[Electron] App is packaged. Using process.resourcesPath for index.html");
);
} else { } else {
// In dev, use the local path
indexPath = path.resolve(__dirname, "www", "index.html"); indexPath = path.resolve(__dirname, "www", "index.html");
logger.info( fileUrl = `file://${indexPath}`;
"[Electron] App is not packaged. Using __dirname for index.html", logger.info("[Electron] App is not packaged. Using __dirname for index.html");
);
} }
logger.info("[Electron] Resolved index.html path:", indexPath); logger.info("[Electron] Resolved index.html path:", indexPath);
logger.info("[Electron] Using file URL:", fileUrl);
// Load the index.html with retry logic
const loadIndexHtml = async (retryCount = 0) => {
try { try {
if (mainWindow.isDestroyed()) {
logger.error("[Electron] Window was destroyed before loading index.html");
return;
}
const exists = fs.existsSync(indexPath); const exists = fs.existsSync(indexPath);
logger.info(`[Electron] fs.existsSync for index.html: ${exists}`); logger.info(`[Electron] fs.existsSync for index.html: ${exists}`);
} catch (e) {
logger.error("[Electron] Error checking fs.existsSync for index.html:", e); if (!exists) {
} throw new Error(`index.html not found at path: ${indexPath}`);
// 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,
);
} }
});
// Listen for console messages from the renderer // Try to read the file to verify it's accessible
mainWindow.webContents.on("console-message", (_event, _level, message) => { const stats = fs.statSync(indexPath);
logger.log("Renderer Console:", message); logger.info("[Electron] index.html stats:", {
size: stats.size,
mode: stats.mode,
uid: stats.uid,
gid: stats.gid
}); });
// Add right after creating the BrowserWindow // Try loadURL first
mainWindow.webContents.on( try {
"did-fail-load", logger.info("[Electron] Attempting to load index.html via loadURL");
(_event, errorCode, errorDescription) => { await mainWindow.loadURL(fileUrl);
logger.error("Page failed to load:", errorCode, errorDescription); 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);
}
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => { // If we've exhausted retries, show error in window
logger.error("Preload script error:", preloadPath, error); if (!mainWindow.isDestroyed()) {
}); const errorHtml = `
<html>
<body style="font-family: sans-serif; padding: 20px;">
<h1>Error Loading Application</h1>
<p>Failed to load the application after multiple attempts.</p>
<pre style="background: #f0f0f0; padding: 10px; border-radius: 4px;">${errorMessage}</pre>
</body>
</html>
`;
await mainWindow.loadURL(`data:text/html,${encodeURIComponent(errorHtml)}`);
}
}
};
mainWindow.webContents.on( // Start loading the index.html
"console-message", loadIndexHtml().catch(error => {
(_event, _level, message, line, sourceId) => { logger.error("[Electron] Fatal error loading index.html:", error);
logger.log("Renderer Console:", line, sourceId, message); });
},
);
// Only open DevTools if not in production
if (!app.isPackaged) {
mainWindow.webContents.openDevTools({ mode: "detach" }); mainWindow.webContents.openDevTools({ mode: "detach" });
mainWindow.webContents.once("devtools-opened", () => {
if (mainWindow.webContents.devToolsWebContents) {
mainWindow.webContents.devToolsWebContents.focus();
} }
});
return mainWindow;
} }
// Handle app ready // Handle app ready
@ -360,9 +502,54 @@ ipcMain.handle("sqlite-echo", async (_event, value) => {
ipcMain.handle("sqlite-create-connection", async (_event, options) => { ipcMain.handle("sqlite-create-connection", async (_event, options) => {
try { 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) { } 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; throw error;
} }
}); });

Loading…
Cancel
Save