/** * Preload script for Electron * Sets up secure IPC communication between renderer and main process * * @author Matthew Raymer */ import { contextBridge, ipcRenderer } from 'electron'; // Simple logger for preload script const logger = { log: (...args: unknown[]) => console.log('[Preload]', ...args), error: (...args: unknown[]) => console.error('[Preload]', ...args), info: (...args: unknown[]) => console.info('[Preload]', ...args), warn: (...args: unknown[]) => console.warn('[Preload]', ...args), debug: (...args: unknown[]) => console.debug('[Preload]', ...args), }; // Types for SQLite connection options interface SQLiteConnectionOptions { database: string; version?: number; readOnly?: boolean; readonly?: boolean; // Handle both cases encryption?: string; mode?: string; useNative?: boolean; [key: string]: unknown; // Allow other properties } // Create a proxy for the CapacitorSQLite plugin const createSQLiteProxy = () => { const MAX_RETRIES = 3; const RETRY_DELAY = 1000; // 1 second const withRetry = async (operation: (...args: unknown[]) => Promise, ...args: unknown[]): Promise => { let lastError: Error | null = null; for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) { try { return await operation(...args); } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt < MAX_RETRIES) { logger.warn(`[CapacitorSQLite] SQLite operation failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`, error); await new Promise(resolve => setTimeout(resolve, RETRY_DELAY)); } } } throw new Error(`[CapacitorSQLite] SQLite operation failed after ${MAX_RETRIES} attempts: ${lastError?.message || 'Unknown error'}`); }; const wrapOperation = (method: string) => { return async (...args: unknown[]): Promise => { try { // For createConnection, ensure readOnly is false if (method === 'create-connection') { const options = args[0] as SQLiteConnectionOptions; if (options && typeof options === 'object') { // Set readOnly to false and ensure mode is rwc options.readOnly = false; options.mode = 'rwc'; // Remove any lowercase readonly property if it exists delete options.readonly; } } return await withRetry(ipcRenderer.invoke, 'sqlite-' + method, ...args); } catch (error) { logger.error(`[CapacitorSQLite] SQLite ${method} failed:`, error); throw new Error(`[CapacitorSQLite] Database operation failed: ${error instanceof Error ? error.message : 'Unknown error'}`); } }; }; // Create a proxy that matches the CapacitorSQLite interface return { echo: wrapOperation('echo'), createConnection: wrapOperation('create-connection'), closeConnection: wrapOperation('close-connection'), execute: wrapOperation('execute'), query: wrapOperation('query'), run: wrapOperation('run'), isAvailable: wrapOperation('is-available'), getPlatform: () => Promise.resolve('electron'), // Add other methods as needed }; }; // Expose the Electron IPC API contextBridge.exposeInMainWorld('electron', { ipcRenderer: { on: (channel: string, func: (...args: unknown[]) => void) => ipcRenderer.on(channel, (event, ...args) => func(...args)), once: (channel: string, func: (...args: unknown[]) => void) => ipcRenderer.once(channel, (event, ...args) => func(...args)), send: (channel: string, data: unknown) => ipcRenderer.send(channel, data), invoke: (channel: string, ...args: unknown[]) => ipcRenderer.invoke(channel, ...args), }, // Add other APIs as needed }); // Expose CapacitorSQLite proxy as before contextBridge.exposeInMainWorld('CapacitorSQLite', createSQLiteProxy()); // Log startup logger.log('[CapacitorSQLite] Preload script starting...'); // Handle window load window.addEventListener('load', () => { logger.log('[CapacitorSQLite] Preload script complete'); });