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. 165
      experiment.sh
  2. 507
      src/electron/main.ts

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
# 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

507
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 <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);
// 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);
});
const exists = fs.existsSync(indexPath);
logger.info(`[Electron] fs.existsSync for index.html: ${exists}`);
// Add right after creating the BrowserWindow
mainWindow.webContents.on(
"did-fail-load",
(_event, errorCode, errorDescription) => {
logger.error("Page failed to load:", errorCode, errorDescription);
},
);
if (!exists) {
throw new Error(`index.html not found at path: ${indexPath}`);
}
mainWindow.webContents.on("preload-error", (_event, preloadPath, error) => {
logger.error("Preload script error:", preloadPath, error);
});
// 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.on(
"console-message",
(_event, _level, message, line, sourceId) => {
logger.log("Renderer Console:", line, sourceId, message);
},
);
// 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);
}
mainWindow.webContents.openDevTools({ mode: "detach" });
mainWindow.webContents.once("devtools-opened", () => {
if (mainWindow.webContents.devToolsWebContents) {
mainWindow.webContents.devToolsWebContents.focus();
// 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" });
}
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;
}
});

Loading…
Cancel
Save