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,
|
...details.responseHeaders,
|
||||||
'Content-Security-Policy': [
|
'Content-Security-Policy': [
|
||||||
// Base CSP for both dev and prod
|
// Base CSP for both dev and prod
|
||||||
`default-src ${customScheme}://* 'unsafe-inline' data:;`,
|
`default-src ${customScheme}://*;`,
|
||||||
// Allow Google Fonts
|
// Script sources
|
||||||
`style-src ${customScheme}://* 'unsafe-inline' https://fonts.googleapis.com;`,
|
`script-src ${customScheme}://* 'self' 'unsafe-inline'${electronIsDev ? " 'unsafe-eval'" : ''};`,
|
||||||
`font-src ${customScheme}://* https://fonts.gstatic.com;`,
|
// Style sources
|
||||||
// Allow images and media
|
`style-src ${customScheme}://* 'self' 'unsafe-inline' https://fonts.googleapis.com;`,
|
||||||
`img-src ${customScheme}://* data: https:;`,
|
// Font sources
|
||||||
// Allow connections to HTTPS resources
|
`font-src ${customScheme}://* 'self' https://fonts.gstatic.com;`,
|
||||||
`connect-src ${customScheme}://* https:;`,
|
// Image sources
|
||||||
// Add dev-specific policies
|
`img-src ${customScheme}://* 'self' data: https:;`,
|
||||||
...(electronIsDev ? [
|
// Connect sources (for API calls)
|
||||||
`script-src ${customScheme}://* 'unsafe-inline' 'unsafe-eval' devtools://*;`,
|
`connect-src ${customScheme}://* 'self' https:;`,
|
||||||
`default-src ${customScheme}://* 'unsafe-inline' devtools://* 'unsafe-eval' data:;`
|
// 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(' ')
|
].join(' ')
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import {
|
|||||||
} from "../PlatformService";
|
} from "../PlatformService";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
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
|
// Type for the electron window object
|
||||||
declare global {
|
declare global {
|
||||||
@@ -54,29 +55,51 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
private sqliteReadyPromise: Promise<void> | null = null;
|
private sqliteReadyPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.sqliteReadyPromise = new Promise<void>((resolve, reject) => {
|
this.sqliteReadyPromise = new Promise<void>(async (resolve, reject) => {
|
||||||
if (!window.electron?.ipcRenderer) {
|
try {
|
||||||
logger.warn('[ElectronPlatformService] IPC renderer not available');
|
// Verify Electron API exposure first
|
||||||
reject(new Error('IPC renderer not available'));
|
await verifyElectronAPI();
|
||||||
return;
|
logger.info('[ElectronPlatformService] Electron API verification successful');
|
||||||
}
|
|
||||||
const timeout = setTimeout(() => {
|
if (!window.electron?.ipcRenderer) {
|
||||||
reject(new Error('SQLite initialization timeout'));
|
logger.warn('[ElectronPlatformService] IPC renderer not available');
|
||||||
}, 30000);
|
reject(new Error('IPC renderer not available'));
|
||||||
window.electron.ipcRenderer.once('sqlite-ready', () => {
|
return;
|
||||||
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'));
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
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