fix(electron): correct SQLite IPC bridge implementation
- Replace generic execute method with specific IPC handlers - Fix database operations by using proper IPC methods (createConnection, query, run, execute) - Update type definitions to match actual IPC bridge interface - Fix "Must provide statements" error by using correct method signatures This change ensures proper communication between renderer and main processes for SQLite operations, resolving database initialization and query issues.
This commit is contained in:
14
electron/src/rt/sqlite-error.ts
Normal file
14
electron/src/rt/sqlite-error.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Custom error class for SQLite operations
|
||||
* Provides additional context and error tracking for SQLite operations
|
||||
*/
|
||||
export class SQLiteError extends Error {
|
||||
constructor(
|
||||
message: string,
|
||||
public operation: string,
|
||||
public cause?: unknown
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'SQLiteError';
|
||||
}
|
||||
}
|
||||
@@ -266,12 +266,16 @@ const verifyTransactionState = async (database: string): Promise<boolean> => {
|
||||
// Check if we're in a transaction
|
||||
const isActive = await pluginState.instance.isTransactionActive({ database });
|
||||
|
||||
transactionState.isActive = isActive;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = database;
|
||||
// Only update state if it's different
|
||||
if (isActive !== transactionState.isActive || transactionState.database !== database) {
|
||||
transactionState.isActive = isActive;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = isActive ? database : null;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
// Reset state on error
|
||||
transactionState.isActive = false;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = null;
|
||||
@@ -896,4 +900,71 @@ export function setupSQLiteHandlers(): void {
|
||||
});
|
||||
|
||||
logger.info('SQLite IPC handlers setup complete');
|
||||
}
|
||||
}
|
||||
|
||||
// Update transaction management to be more careful
|
||||
const beginTransaction = async (database: string): Promise<void> => {
|
||||
if (!pluginState.instance || !pluginState.isAvailable) {
|
||||
throw new SQLiteError('Database not available', 'beginTransaction');
|
||||
}
|
||||
|
||||
// Verify current state first
|
||||
await verifyTransactionState(database);
|
||||
if (transactionState.isActive) {
|
||||
throw new SQLiteError('Transaction already active', 'beginTransaction');
|
||||
}
|
||||
|
||||
try {
|
||||
await pluginState.instance.beginTransaction({ database });
|
||||
transactionState.isActive = true;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = database;
|
||||
} catch (error) {
|
||||
transactionState.isActive = false;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = null;
|
||||
throw new SQLiteError('Failed to begin transaction', 'beginTransaction', error);
|
||||
}
|
||||
};
|
||||
|
||||
const commitTransaction = async (database: string): Promise<void> => {
|
||||
if (!pluginState.instance || !pluginState.isAvailable) {
|
||||
throw new SQLiteError('Database not available', 'commitTransaction');
|
||||
}
|
||||
|
||||
// Verify current state first
|
||||
await verifyTransactionState(database);
|
||||
if (!transactionState.isActive || transactionState.database !== database) {
|
||||
throw new SQLiteError('No active transaction', 'commitTransaction');
|
||||
}
|
||||
|
||||
try {
|
||||
await pluginState.instance.commitTransaction({ database });
|
||||
transactionState.isActive = false;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = null;
|
||||
} catch (error) {
|
||||
// Don't reset state on error - let rollback handle it
|
||||
throw new SQLiteError('Failed to commit transaction', 'commitTransaction', error);
|
||||
}
|
||||
};
|
||||
|
||||
const rollbackTransaction = async (database: string): Promise<void> => {
|
||||
if (!pluginState.instance || !pluginState.isAvailable) {
|
||||
return; // Just return if plugin not available
|
||||
}
|
||||
|
||||
// Only attempt rollback if we think we're in a transaction
|
||||
if (transactionState.isActive && transactionState.database === database) {
|
||||
try {
|
||||
await pluginState.instance.rollbackTransaction({ database });
|
||||
} catch (error) {
|
||||
logger.error('Rollback failed:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Always reset state after rollback attempt
|
||||
transactionState.isActive = false;
|
||||
transactionState.lastVerified = new Date();
|
||||
transactionState.database = null;
|
||||
};
|
||||
@@ -646,17 +646,29 @@ export async function saveNewIdentity(
|
||||
const secrets = await platformService.dbQuery(
|
||||
`SELECT secretBase64 FROM secret`,
|
||||
);
|
||||
|
||||
// If no secret exists, create one
|
||||
let secretBase64: string;
|
||||
if (!secrets?.values?.length || !secrets.values[0]?.length) {
|
||||
throw new Error(
|
||||
"No initial encryption supported. We recommend you clear your data and start over.",
|
||||
// Generate a new secret
|
||||
const randomBytes = crypto.getRandomValues(new Uint8Array(32));
|
||||
secretBase64 = arrayBufferToBase64(randomBytes);
|
||||
|
||||
// Store the new secret
|
||||
await platformService.dbExec(
|
||||
`INSERT INTO secret (id, secretBase64) VALUES (1, ?)`,
|
||||
[secretBase64]
|
||||
);
|
||||
} else {
|
||||
secretBase64 = secrets.values[0][0] as string;
|
||||
}
|
||||
const secretBase64 = secrets.values[0][0] as string;
|
||||
|
||||
const secret = base64ToArrayBuffer(secretBase64);
|
||||
const encryptedIdentity = await simpleEncrypt(identity, secret);
|
||||
const encryptedMnemonic = await simpleEncrypt(mnemonic, secret);
|
||||
const encryptedIdentityBase64 = arrayBufferToBase64(encryptedIdentity);
|
||||
const encryptedMnemonicBase64 = arrayBufferToBase64(encryptedMnemonic);
|
||||
|
||||
await platformService.dbExec(
|
||||
`INSERT INTO accounts (dateCreated, derivationPath, did, identityEncrBase64, mnemonicEncrBase64, publicKeyHex)
|
||||
VALUES (?, ?, ?, ?, ?, ?)`,
|
||||
|
||||
@@ -288,21 +288,38 @@ export class ElectronPlatformService implements PlatformService {
|
||||
if (this.isInitialized) return;
|
||||
if (this.sqliteReadyPromise) await this.sqliteReadyPromise;
|
||||
|
||||
if (!window.CapacitorSQLite) {
|
||||
throw new Error("CapacitorSQLite not available");
|
||||
if (!window.electron?.sqlite) {
|
||||
throw new Error("SQLite IPC bridge not available");
|
||||
}
|
||||
|
||||
this.sqlite = window.CapacitorSQLite as unknown as SQLiteOperations;
|
||||
// Use IPC bridge with specific methods
|
||||
this.sqlite = {
|
||||
createConnection: async (options) => {
|
||||
await window.electron.ipcRenderer.invoke('sqlite-create-connection', options);
|
||||
},
|
||||
query: async (options) => {
|
||||
return await window.electron.ipcRenderer.invoke('sqlite-query', options);
|
||||
},
|
||||
run: async (options) => {
|
||||
return await window.electron.ipcRenderer.invoke('sqlite-run', options);
|
||||
},
|
||||
execute: async (options) => {
|
||||
await window.electron.ipcRenderer.invoke('sqlite-execute', {
|
||||
database: options.database,
|
||||
statements: [{ statement: options.statements }]
|
||||
});
|
||||
}
|
||||
} as SQLiteOperations;
|
||||
|
||||
// Create the connection (idempotent)
|
||||
await this.sqlite.createConnection({
|
||||
await this.sqlite!.createConnection({
|
||||
database: this.dbName,
|
||||
encrypted: false,
|
||||
mode: "no-encryption",
|
||||
});
|
||||
|
||||
// Optionally, test the connection
|
||||
await this.sqlite.query({
|
||||
await this.sqlite!.query({
|
||||
database: this.dbName,
|
||||
statement: "SELECT 1",
|
||||
});
|
||||
|
||||
2
src/types/global.d.ts
vendored
2
src/types/global.d.ts
vendored
@@ -16,7 +16,7 @@ declare global {
|
||||
electron: {
|
||||
sqlite: {
|
||||
isAvailable: () => Promise<boolean>;
|
||||
execute: (method: string, ...args: unknown[]) => Promise<unknown>;
|
||||
execute: (options: { method: string; database?: string; statement?: string; values?: unknown[]; statements?: string; encrypted?: boolean; mode?: string }) => Promise<unknown>;
|
||||
};
|
||||
// Add other electron IPC methods as needed
|
||||
getPath: (pathType: string) => string;
|
||||
|
||||
Reference in New Issue
Block a user