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.
This commit is contained in:
165
experiment.sh
165
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
|
||||
|
||||
echo "Build completed successfully!"
|
||||
# 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"
|
||||
|
||||
# Exit with success
|
||||
exit 0
|
||||
@@ -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 <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());
|
||||
}
|
||||
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);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
const exists = fs.existsSync(indexPath);
|
||||
logger.info(`[Electron] 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] index.html stats:", {
|
||||
size: stats.size,
|
||||
mode: stats.mode,
|
||||
uid: stats.uid,
|
||||
gid: stats.gid
|
||||
});
|
||||
|
||||
// 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 = `
|
||||
<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)}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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" });
|
||||
}
|
||||
|
||||
// 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
|
||||
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);
|
||||
},
|
||||
);
|
||||
|
||||
mainWindow.webContents.openDevTools({ mode: "detach" });
|
||||
mainWindow.webContents.once("devtools-opened", () => {
|
||||
if (mainWindow.webContents.devToolsWebContents) {
|
||||
mainWindow.webContents.devToolsWebContents.focus();
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user