refactor(electron): enhance SQLite integration and debug logging
Current Status: - SQLite plugin successfully initializes in main process - Preload script and context bridge working correctly - IPC handlers for SQLite operations not registered - Type definitions out of sync with implementation Changes Made: - Added comprehensive debug logging in preload script - Implemented retry logic for SQLite operations (3 attempts, 1s delay) - Added proper type definitions for SQLite connection options - Defined strict channel validation for IPC communication - Enhanced error handling and logging throughout Type Definitions Updates: - Aligned ElectronAPI interface with actual implementation - Added proper typing for SQLite operations - Structured IPC renderer interface with correct method signatures Next Steps: - Register missing IPC handlers in main process - Update type definitions to match implementation - Add proper error recovery for SQLite operations - Address Content Security Policy warnings Affected Files: - electron/src/preload.ts - src/types/electron.d.ts - src/utils/debug-electron.ts - src/services/platforms/ElectronPlatformService.ts Note: This is a transitional commit. While the structure is improved, database operations are not yet functional due to missing IPC handlers.
This commit is contained in:
@@ -232,19 +232,35 @@ export function setupContentSecurityPolicy(customScheme: string): void {
|
||||
...details.responseHeaders,
|
||||
'Content-Security-Policy': [
|
||||
// Base CSP for both dev and prod
|
||||
`default-src ${customScheme}://* 'unsafe-inline' data:;`,
|
||||
// Allow Google Fonts
|
||||
`style-src ${customScheme}://* 'unsafe-inline' https://fonts.googleapis.com;`,
|
||||
`font-src ${customScheme}://* https://fonts.gstatic.com;`,
|
||||
// Allow images and media
|
||||
`img-src ${customScheme}://* data: https:;`,
|
||||
// Allow connections to HTTPS resources
|
||||
`connect-src ${customScheme}://* https:;`,
|
||||
// Add dev-specific policies
|
||||
...(electronIsDev ? [
|
||||
`script-src ${customScheme}://* 'unsafe-inline' 'unsafe-eval' devtools://*;`,
|
||||
`default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:;`
|
||||
] : [])
|
||||
`default-src ${customScheme}://*;`,
|
||||
// Script sources
|
||||
`script-src ${customScheme}://* 'self' 'unsafe-inline'${electronIsDev ? " 'unsafe-eval'" : ''};`,
|
||||
// Style sources
|
||||
`style-src ${customScheme}://* 'self' 'unsafe-inline' https://fonts.googleapis.com;`,
|
||||
// Font sources
|
||||
`font-src ${customScheme}://* 'self' https://fonts.gstatic.com;`,
|
||||
// Image sources
|
||||
`img-src ${customScheme}://* 'self' data: https:;`,
|
||||
// Connect sources (for API calls)
|
||||
`connect-src ${customScheme}://* 'self' https:;`,
|
||||
// Worker sources
|
||||
`worker-src ${customScheme}://* 'self' blob:;`,
|
||||
// Frame sources
|
||||
`frame-src ${customScheme}://* 'self';`,
|
||||
// Media sources
|
||||
`media-src ${customScheme}://* 'self' data:;`,
|
||||
// Object sources
|
||||
`object-src 'none';`,
|
||||
// Base URI
|
||||
`base-uri 'self';`,
|
||||
// Form action
|
||||
`form-action ${customScheme}://* 'self';`,
|
||||
// Frame ancestors
|
||||
`frame-ancestors 'none';`,
|
||||
// Upgrade insecure requests
|
||||
'upgrade-insecure-requests;',
|
||||
// Block mixed content
|
||||
'block-all-mixed-content;'
|
||||
].join(' ')
|
||||
},
|
||||
});
|
||||
|
||||
@@ -6,7 +6,8 @@ import {
|
||||
} from "../PlatformService";
|
||||
import { logger } from "../../utils/logger";
|
||||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
||||
import { DatabaseConnectionPool } from "../database/ConnectionPool";
|
||||
|
||||
import { verifyElectronAPI, testSQLiteOperations } from "../../utils/debug-electron";
|
||||
|
||||
// Type for the electron window object
|
||||
declare global {
|
||||
@@ -54,29 +55,51 @@ export class ElectronPlatformService implements PlatformService {
|
||||
private sqliteReadyPromise: Promise<void> | null = null;
|
||||
|
||||
constructor() {
|
||||
this.sqliteReadyPromise = new Promise<void>((resolve, reject) => {
|
||||
if (!window.electron?.ipcRenderer) {
|
||||
logger.warn('[ElectronPlatformService] IPC renderer not available');
|
||||
reject(new Error('IPC renderer not available'));
|
||||
return;
|
||||
}
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('SQLite initialization timeout'));
|
||||
}, 30000);
|
||||
window.electron.ipcRenderer.once('sqlite-ready', () => {
|
||||
clearTimeout(timeout);
|
||||
logger.info('[ElectronPlatformService] Received SQLite ready signal');
|
||||
this.isInitialized = true;
|
||||
resolve();
|
||||
});
|
||||
window.electron.ipcRenderer.once('database-status', (...args: unknown[]) => {
|
||||
clearTimeout(timeout);
|
||||
const status = args[0] as { status: string; error?: string };
|
||||
if (status.status === 'error') {
|
||||
this.dbFatalError = true;
|
||||
reject(new Error(status.error || 'Database initialization failed'));
|
||||
this.sqliteReadyPromise = new Promise<void>(async (resolve, reject) => {
|
||||
try {
|
||||
// Verify Electron API exposure first
|
||||
await verifyElectronAPI();
|
||||
logger.info('[ElectronPlatformService] Electron API verification successful');
|
||||
|
||||
if (!window.electron?.ipcRenderer) {
|
||||
logger.warn('[ElectronPlatformService] IPC renderer not available');
|
||||
reject(new Error('IPC renderer not available'));
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('SQLite initialization timeout'));
|
||||
}, 30000);
|
||||
|
||||
window.electron.ipcRenderer.once('sqlite-ready', async () => {
|
||||
clearTimeout(timeout);
|
||||
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;
|
||||
resolve();
|
||||
} catch (error) {
|
||||
logger.error('[ElectronPlatformService] SQLite operations test failed:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
|
||||
window.electron.ipcRenderer.once('database-status', (...args: unknown[]) => {
|
||||
clearTimeout(timeout);
|
||||
const status = args[0] as { status: string; error?: string };
|
||||
if (status.status === 'error') {
|
||||
this.dbFatalError = true;
|
||||
reject(new Error(status.error || 'Database initialization failed'));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error('[ElectronPlatformService] Initialization failed:', error);
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
123
src/utils/debug-electron.ts
Normal file
123
src/utils/debug-electron.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Debug utilities for Electron integration
|
||||
* Helps verify the context bridge and SQLite functionality
|
||||
*/
|
||||
|
||||
const debugLogger = {
|
||||
log: (...args: unknown[]) => console.log('[Debug]', ...args),
|
||||
error: (...args: unknown[]) => console.error('[Debug]', ...args),
|
||||
info: (...args: unknown[]) => console.info('[Debug]', ...args),
|
||||
warn: (...args: unknown[]) => console.warn('[Debug]', ...args),
|
||||
debug: (...args: unknown[]) => console.debug('[Debug]', ...args)
|
||||
};
|
||||
|
||||
export async function verifyElectronAPI(): Promise<void> {
|
||||
debugLogger.info('Verifying Electron API exposure...');
|
||||
|
||||
// Check if window.electron exists
|
||||
if (!window.electron) {
|
||||
throw new Error('window.electron is not defined');
|
||||
}
|
||||
debugLogger.info('window.electron is available');
|
||||
|
||||
// Verify IPC renderer
|
||||
if (!window.electron.ipcRenderer) {
|
||||
throw new Error('IPC renderer is not available');
|
||||
}
|
||||
debugLogger.info('IPC renderer is available with methods:', {
|
||||
hasOn: typeof window.electron.ipcRenderer.on === 'function',
|
||||
hasOnce: typeof window.electron.ipcRenderer.once === 'function',
|
||||
hasSend: typeof window.electron.ipcRenderer.send === 'function',
|
||||
hasInvoke: typeof window.electron.ipcRenderer.invoke === 'function'
|
||||
});
|
||||
|
||||
// Verify SQLite API
|
||||
if (!window.electron.sqlite) {
|
||||
throw new Error('SQLite API is not available');
|
||||
}
|
||||
debugLogger.info('SQLite API is available with methods:', {
|
||||
hasIsAvailable: typeof window.electron.sqlite.isAvailable === 'function',
|
||||
hasEcho: typeof window.electron.sqlite.echo === 'function',
|
||||
hasCreateConnection: typeof window.electron.sqlite.createConnection === 'function',
|
||||
hasCloseConnection: typeof window.electron.sqlite.closeConnection === 'function',
|
||||
hasQuery: typeof window.electron.sqlite.query === 'function',
|
||||
hasRun: typeof window.electron.sqlite.run === 'function',
|
||||
hasExecute: typeof window.electron.sqlite.execute === 'function',
|
||||
hasGetPlatform: typeof window.electron.sqlite.getPlatform === 'function'
|
||||
});
|
||||
|
||||
// Test SQLite availability
|
||||
try {
|
||||
const isAvailable = await window.electron.sqlite.isAvailable();
|
||||
debugLogger.info('SQLite availability check:', { isAvailable });
|
||||
} catch (error) {
|
||||
debugLogger.error('SQLite availability check failed:', error);
|
||||
}
|
||||
|
||||
// Test echo functionality
|
||||
try {
|
||||
const echoResult = await window.electron.sqlite.echo('test');
|
||||
debugLogger.info('SQLite echo test:', echoResult);
|
||||
} catch (error) {
|
||||
debugLogger.error('SQLite echo test failed:', error);
|
||||
}
|
||||
|
||||
// Verify environment
|
||||
debugLogger.info('Environment:', {
|
||||
platform: window.electron.env.platform,
|
||||
isDev: window.electron.env.isDev
|
||||
});
|
||||
|
||||
debugLogger.info('Electron API verification complete');
|
||||
}
|
||||
|
||||
// Export a function to test SQLite operations
|
||||
export async function testSQLiteOperations(): Promise<void> {
|
||||
debugLogger.info('Testing SQLite operations...');
|
||||
|
||||
try {
|
||||
// Test connection creation
|
||||
debugLogger.info('Creating test connection...');
|
||||
await window.electron.sqlite.createConnection({
|
||||
database: 'test',
|
||||
version: 1,
|
||||
readOnly: false
|
||||
});
|
||||
debugLogger.info('Test connection created successfully');
|
||||
|
||||
// Test query
|
||||
debugLogger.info('Testing query operation...');
|
||||
const queryResult = await window.electron.sqlite.query({
|
||||
statement: 'SELECT 1 as test'
|
||||
});
|
||||
debugLogger.info('Query test result:', queryResult);
|
||||
|
||||
// Test run
|
||||
debugLogger.info('Testing run operation...');
|
||||
const runResult = await window.electron.sqlite.run({
|
||||
statement: 'CREATE TABLE IF NOT EXISTS test_table (id INTEGER PRIMARY KEY)'
|
||||
});
|
||||
debugLogger.info('Run test result:', runResult);
|
||||
|
||||
// Test execute
|
||||
debugLogger.info('Testing execute operation...');
|
||||
const executeResult = await window.electron.sqlite.execute({
|
||||
statements: [
|
||||
{ statement: 'INSERT INTO test_table (id) VALUES (1)' },
|
||||
{ statement: 'SELECT * FROM test_table' }
|
||||
]
|
||||
});
|
||||
debugLogger.info('Execute test result:', executeResult);
|
||||
|
||||
// Clean up
|
||||
debugLogger.info('Closing test connection...');
|
||||
await window.electron.sqlite.closeConnection({ database: 'test' });
|
||||
debugLogger.info('Test connection closed');
|
||||
|
||||
} catch (error) {
|
||||
debugLogger.error('SQLite operation test failed:', error);
|
||||
throw error;
|
||||
}
|
||||
|
||||
debugLogger.info('SQLite operations test complete');
|
||||
}
|
||||
Reference in New Issue
Block a user