@ -44,6 +44,26 @@ export interface SQLiteQueryResult {
changes ? : { changes : number ; lastId? : number } ;
changes ? : { changes : number ; lastId? : number } ;
}
}
/ * *
* Shared SQLite initialization state
* Used to coordinate initialization between main and service
*
* @author Matthew Raymer
* /
export interface SQLiteInitState {
isReady : boolean ;
isInitializing : boolean ;
error? : Error ;
lastReadyCheck? : number ;
}
// Singleton instance for shared state
const sqliteInitState : SQLiteInitState = {
isReady : false ,
isInitializing : false ,
lastReadyCheck : 0
} ;
/ * *
/ * *
* Platform service implementation for Electron ( desktop ) platform .
* Platform service implementation for Electron ( desktop ) platform .
* Provides native desktop functionality through Electron and Capacitor plugins for :
* Provides native desktop functionality through Electron and Capacitor plugins for :
@ -51,6 +71,8 @@ export interface SQLiteQueryResult {
* - Camera integration ( TODO )
* - Camera integration ( TODO )
* - SQLite database operations
* - SQLite database operations
* - System - level features ( TODO )
* - System - level features ( TODO )
*
* @author Matthew Raymer
* /
* /
export class ElectronPlatformService implements PlatformService {
export class ElectronPlatformService implements PlatformService {
private sqlite : any ;
private sqlite : any ;
@ -58,53 +80,152 @@ export class ElectronPlatformService implements PlatformService {
private isInitialized = false ;
private isInitialized = false ;
private dbFatalError = false ;
private dbFatalError = false ;
private sqliteReadyPromise : Promise < void > | null = null ;
private sqliteReadyPromise : Promise < void > | null = null ;
private initializationTimeout : NodeJS.Timeout | null = null ;
// SQLite initialization configuration
private static readonly SQLITE_CONFIG = {
INITIALIZATION : {
TIMEOUT_MS : 10000 , // 10 seconds for initial setup
RETRY_ATTEMPTS : 3 ,
RETRY_DELAY_MS : 1000 ,
READY_CHECK_INTERVAL_MS : 100 // How often to check if SQLite is already ready
}
} ;
constructor ( ) {
constructor ( ) {
this . sqliteReadyPromise = new Promise < void > ( async ( resolve , reject ) = > {
this . sqliteReadyPromise = new Promise < void > ( ( resolve , reject ) = > {
try {
let retryCount = 0 ;
// Verify Electron API exposure first
await verifyElectronAPI ( ) ;
logger . info ( '[ElectronPlatformService] Electron API verification successful' ) ;
if ( ! window . electron ? . ipcRenderer ) {
const cleanup = ( ) = > {
logger . warn ( '[ElectronPlatformService] IPC renderer not available' ) ;
if ( this . initializationTimeout ) {
reject ( new Error ( 'IPC renderer not available' ) ) ;
clearTimeout ( this . initializationTimeout ) ;
this . initializationTimeout = null ;
}
} ;
const checkExistingReadiness = async ( ) : Promise < boolean > = > {
try {
if ( ! window . electron ? . ipcRenderer ) {
return false ;
}
// Check if SQLite is already available
const isAvailable = await window . electron . ipcRenderer . invoke ( 'sqlite-is-available' ) ;
if ( ! isAvailable ) {
return false ;
}
// Check if database is already open
const isOpen = await window . electron . ipcRenderer . invoke ( 'sqlite-is-db-open' , {
database : this.dbName
} ) ;
if ( isOpen ) {
logger . info ( '[ElectronPlatformService] SQLite is already ready and database is open' ) ;
sqliteInitState . isReady = true ;
sqliteInitState . isInitializing = false ;
sqliteInitState . lastReadyCheck = Date . now ( ) ;
return true ;
}
return false ;
} catch ( error ) {
logger . warn ( '[ElectronPlatformService] Error checking existing readiness:' , error ) ;
return false ;
}
} ;
const attemptInitialization = async ( ) = > {
cleanup ( ) ; // Clear any existing timeout
// Check if SQLite is already ready
if ( await checkExistingReadiness ( ) ) {
this . isInitialized = true ;
resolve ( ) ;
return ;
return ;
}
}
const timeout = setTimeout ( ( ) = > {
// If someone else is initializing, wait for them
reject ( new Error ( 'SQLite initialization timeout' ) ) ;
if ( sqliteInitState . isInitializing ) {
} , 30000 ) ;
logger . info ( '[ElectronPlatformService] Another initialization in progress, waiting...' ) ;
setTimeout ( attemptInitialization , ElectronPlatformService . SQLITE_CONFIG . INITIALIZATION . READY_CHECK_INTERVAL_MS ) ;
return ;
}
window . electron . ipcRenderer . once ( 'sqlite-ready' , async ( ) = > {
try {
clearTimeout ( timeout ) ;
sqliteInitState . isInitializing = true ;
logger . info ( '[ElectronPlatformService] Received SQLite ready signal' ) ;
try {
// Verify Electron API exposure first
// Test SQLite operations after receiving ready signal
await verifyElectronAPI ( ) ;
await testSQLiteOperations ( ) ;
logger . info ( '[ElectronPlatformService] Electron API verification successful' ) ;
logger . info ( '[ElectronPlatformService] SQLite operations test successful' ) ;
this . isInitialized = true ;
if ( ! window . electron ? . ipcRenderer ) {
resolve ( ) ;
logger . warn ( '[ElectronPlatformService] IPC renderer not available' ) ;
} catch ( error ) {
reject ( new Error ( 'IPC renderer not available' ) ) ;
logger . error ( '[ElectronPlatformService] SQLite operations test failed:' , error ) ;
return ;
reject ( error ) ;
}
}
} ) ;
window . electron . ipcRenderer . once ( 'database-status' , ( . . . args : unknown [ ] ) = > {
// Set timeout for this attempt
clearTimeout ( timeout ) ;
this . initializationTimeout = setTimeout ( ( ) = > {
const status = args [ 0 ] as { status : string ; error? : string } ;
if ( retryCount < ElectronPlatformService . SQLITE_CONFIG . INITIALIZATION . RETRY_ATTEMPTS ) {
if ( status . status === 'error' ) {
retryCount ++ ;
this . dbFatalError = true ;
logger . warn ( ` [ElectronPlatformService] SQLite initialization attempt ${ retryCount } timed out, retrying... ` ) ;
reject ( new Error ( status . error || 'Database initialization failed' ) ) ;
setTimeout ( attemptInitialization , ElectronPlatformService . SQLITE_CONFIG . INITIALIZATION . RETRY_DELAY_MS ) ;
}
} else {
} ) ;
cleanup ( ) ;
} catch ( error ) {
sqliteInitState . isInitializing = false ;
logger . error ( '[ElectronPlatformService] Initialization failed:' , error ) ;
sqliteInitState . error = new Error ( 'SQLite initialization timeout after all retries' ) ;
reject ( error ) ;
logger . error ( '[ElectronPlatformService] SQLite initialization failed after all retries' ) ;
}
reject ( sqliteInitState . error ) ;
}
} , ElectronPlatformService . SQLITE_CONFIG . INITIALIZATION . TIMEOUT_MS ) ;
// Set up ready signal handler
window . electron . ipcRenderer . once ( 'sqlite-ready' , async ( ) = > {
cleanup ( ) ;
logger . info ( '[ElectronPlatformService] Received SQLite ready signal' ) ;
try {
// Test SQLite operations after receiving ready signal
await testSQLiteOperations ( ) ;
logger . info ( '[ElectronPlatformService] SQLite operations test successful' ) ;
this . isInitialized = true ;
sqliteInitState . isReady = true ;
sqliteInitState . isInitializing = false ;
sqliteInitState . lastReadyCheck = Date . now ( ) ;
resolve ( ) ;
} catch ( error ) {
sqliteInitState . error = error as Error ;
sqliteInitState . isInitializing = false ;
logger . error ( '[ElectronPlatformService] SQLite operations test failed:' , error ) ;
reject ( error ) ;
}
} ) ;
// Set up error handler
window . electron . ipcRenderer . once ( 'database-status' , ( . . . args : unknown [ ] ) = > {
cleanup ( ) ;
const status = args [ 0 ] as { status : string ; error? : string } ;
if ( status . status === 'error' ) {
this . dbFatalError = true ;
sqliteInitState . error = new Error ( status . error || 'Database initialization failed' ) ;
sqliteInitState . isInitializing = false ;
reject ( sqliteInitState . error ) ;
}
} ) ;
} catch ( error ) {
cleanup ( ) ;
sqliteInitState . error = error as Error ;
sqliteInitState . isInitializing = false ;
logger . error ( '[ElectronPlatformService] Initialization failed:' , error ) ;
reject ( error ) ;
}
} ;
// Start first initialization attempt
attemptInitialization ( ) ;
} ) ;
} ) ;
}
}