@ -28,50 +28,81 @@ interface SQLiteConnectionOptions {
[ key : string ] : unknown ; // Allow other properties
}
// Create a proxy for the CapacitorSQLite plugin
// Define valid channels for security
const VALID_CHANNELS = {
send : [ 'toMain' , 'sqlite-status' ] ,
receive : [ 'fromMain' , 'sqlite-ready' , 'database-status' ] ,
invoke : [ 'sqlite-is-available' , 'sqlite-echo' , 'sqlite-create-connection' , 'sqlite-execute' , 'sqlite-query' , 'sqlite-run' , 'sqlite-close-connection' , 'get-path' , 'get-base-path' ]
} ;
// Create a secure IPC bridge
const createSecureIPCBridge = ( ) = > {
return {
send : ( channel : string , data : unknown ) = > {
if ( VALID_CHANNELS . send . includes ( channel ) ) {
ipcRenderer . send ( channel , data ) ;
} else {
logger . warn ( ` [Preload] Attempted to send on invalid channel: ${ channel } ` ) ;
}
} ,
receive : ( channel : string , func : ( . . . args : unknown [ ] ) = > void ) = > {
if ( VALID_CHANNELS . receive . includes ( channel ) ) {
ipcRenderer . on ( channel , ( _event , . . . args ) = > func ( . . . args ) ) ;
} else {
logger . warn ( ` [Preload] Attempted to receive on invalid channel: ${ channel } ` ) ;
}
} ,
once : ( channel : string , func : ( . . . args : unknown [ ] ) = > void ) = > {
if ( VALID_CHANNELS . receive . includes ( channel ) ) {
ipcRenderer . once ( channel , ( _event , . . . args ) = > func ( . . . args ) ) ;
} else {
logger . warn ( ` [Preload] Attempted to receive once on invalid channel: ${ channel } ` ) ;
}
} ,
invoke : async ( channel : string , . . . args : unknown [ ] ) = > {
if ( VALID_CHANNELS . invoke . includes ( channel ) ) {
return await ipcRenderer . invoke ( channel , . . . args ) ;
} else {
logger . warn ( ` [Preload] Attempted to invoke on invalid channel: ${ channel } ` ) ;
throw new Error ( ` Invalid channel: ${ channel } ` ) ;
}
}
} ;
} ;
// Create SQLite proxy with retry logic
const createSQLiteProxy = ( ) = > {
const MAX_RETRIES = 3 ;
const RETRY_DELAY = 1000 ; // 1 second
const RETRY_DELAY = 1000 ;
const withRetry = async < T > ( operation : ( . . . args : unknown [ ] ) = > Promise < T > , . . . args : unknown [ ] ) : Promise < T > = > {
let lastError : Error | null = null ;
const withRetry = async ( operation : ( . . . args : unknown [ ] ) = > Promise < unknown > , . . . args : unknown [ ] ) = > {
let lastError ;
for ( let attempt = 1 ; attempt <= MAX_RETRIES ; attempt ++ ) {
try {
return await operation ( . . . args ) ;
} catch ( error ) {
lastError = error instanceof Error ? error : new Error ( String ( error ) ) ;
lastError = error ;
if ( attempt < MAX_RETRIES ) {
logger . warn ( ` [CapacitorSQLite] SQLite operation failed (attempt ${ attempt } / ${ MAX_RETRIES } ), retrying... ` , error ) ;
logger . warn ( ` [Preload ] 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' } ` ) ;
throw new Error ( ` SQLite operation failed after ${ MAX_RETRIES } attempts: ${ lastError ? . message || "Unknown error" } ` ) ;
} ;
const wrapOperation = ( method : string ) = > {
return async ( . . . args : unknown [ ] ) : Promise < unknown > = > {
return async ( . . . args : unknown [ ] ) = > {
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 ) ;
const handlerName = ` sqlite- ${ method } ` ;
return await withRetry ( ipcRenderer . invoke , handlerName , . . . args ) ;
} catch ( error ) {
logger . error ( ` [CapacitorSQLite] SQLite ${ method } failed: ` , error ) ;
throw new Error ( ` [CapacitorSQLite] Database operation failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
logger . error ( ` [Preload] SQLite ${ method } failed: ` , error ) ;
throw error ;
}
} ;
} ;
// Create a proxy that matches the CapacitorSQLite interface
return {
echo : wrapOperation ( 'echo' ) ,
createConnection : wrapOperation ( 'create-connection' ) ,
@ -80,24 +111,25 @@ const createSQLiteProxy = () => {
query : wrapOperation ( 'query' ) ,
run : wrapOperation ( 'run' ) ,
isAvailable : wrapOperation ( 'is-available' ) ,
getPlatform : ( ) = > Promise . resolve ( 'electron' ) ,
// Add other methods as needed
getPlatform : ( ) = > Promise . resolve ( 'electron' )
} ;
} ;
// 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
} ) ;
try {
// Expose the secure IPC bridge and SQLite proxy
contextBridge . exposeInMainWorld ( 'electron' , {
ipcRenderer : createSecureIPCBridge ( ) ,
sqlite : createSQLiteProxy ( ) ,
env : {
platform : 'electron' ,
isDev : process.env.NODE_ENV === 'development'
}
} ) ;
// Expose CapacitorSQLite proxy as before
contextBridge . exposeInMainWorld ( 'CapacitorSQLite' , createSQLiteProxy ( ) ) ;
logger . info ( '[Preload] IPC bridge and SQLite proxy initialized successfully' ) ;
} catch ( error ) {
logger . error ( '[Preload] Failed to initialize IPC bridge:' , error ) ;
}
// Log startup
logger . log ( '[CapacitorSQLite] Preload script starting...' ) ;