fix(electron): assign sqlitePlugin globally and improve error logging
- Assign CapacitorSQLite instance to the global sqlitePlugin variable so it is accessible in IPC handlers. - Enhance error logging in the IPC handler to include JSON stringification and stack trace for better debugging. - Reveal that the generic .handle() method is not available on the plugin, clarifying the next steps for correct IPC wiring.
This commit is contained in:
@@ -190,8 +190,8 @@
|
|||||||
"to": "www"
|
"to": "www"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"from": "dist-electron/resources/preload.mjs",
|
"from": "dist-electron/resources/preload.js",
|
||||||
"to": "preload.mjs"
|
"to": "preload.js"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"linux": {
|
"linux": {
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ indexContent = indexContent.replace(
|
|||||||
fs.writeFileSync(finalIndexPath, indexContent);
|
fs.writeFileSync(finalIndexPath, indexContent);
|
||||||
|
|
||||||
// Copy preload script to resources
|
// Copy preload script to resources
|
||||||
const preloadSrc = path.join(electronDistPath, "preload.mjs");
|
const preloadSrc = path.join(electronDistPath, "preload.js");
|
||||||
const preloadDest = path.join(electronDistPath, "resources", "preload.mjs");
|
const preloadDest = path.join(electronDistPath, "resources", "preload.js");
|
||||||
|
|
||||||
// Ensure resources directory exists
|
// Ensure resources directory exists
|
||||||
const resourcesDir = path.join(electronDistPath, "resources");
|
const resourcesDir = path.join(electronDistPath, "resources");
|
||||||
|
|||||||
@@ -8,6 +8,13 @@ import { fileURLToPath } from "url";
|
|||||||
const __filename = fileURLToPath(import.meta.url);
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
const __dirname = path.dirname(__filename);
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
// Set global variables that the plugin needs
|
||||||
|
global.__dirname = __dirname;
|
||||||
|
global.__filename = __filename;
|
||||||
|
|
||||||
|
// Now import the plugin after setting up globals
|
||||||
|
import { CapacitorSQLite } from "@capacitor-community/sqlite/electron/dist/plugin.js";
|
||||||
|
|
||||||
// Simple logger implementation
|
// Simple logger implementation
|
||||||
const logger = {
|
const logger = {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
@@ -69,44 +76,35 @@ let sqlitePlugin: any = null;
|
|||||||
|
|
||||||
async function initializeSQLite() {
|
async function initializeSQLite() {
|
||||||
try {
|
try {
|
||||||
// Import the plugin directly in the main process
|
console.log("Initializing SQLite plugin...");
|
||||||
const plugin = await import("@capacitor-community/sqlite/electron/dist/plugin.js");
|
sqlitePlugin = new CapacitorSQLite();
|
||||||
sqlitePlugin = new plugin.CapacitorSQLite();
|
// Test the plugin
|
||||||
|
const echoResult = await sqlitePlugin.echo({ value: "test" });
|
||||||
// Test the plugin with a simple operation
|
console.log("SQLite plugin echo test:", echoResult);
|
||||||
const result = await sqlitePlugin.echo({ value: "test" });
|
// Initialize database connection
|
||||||
if (result.value !== "test") {
|
const db = await sqlitePlugin.createConnection({
|
||||||
throw new Error("SQLite plugin echo test failed");
|
database: "timesafari.db",
|
||||||
}
|
version: 1,
|
||||||
|
});
|
||||||
logger.info("SQLite plugin initialized successfully");
|
console.log("SQLite plugin initialized successfully");
|
||||||
|
return db;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Failed to initialize SQLite plugin:", error);
|
console.error("Failed to initialize SQLite plugin:", error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize SQLite plugin
|
|
||||||
initializeSQLite().catch(error => {
|
|
||||||
logger.error("Failed to initialize SQLite plugin:", error);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set up IPC handlers for SQLite operations
|
|
||||||
ipcMain.handle("check-sqlite-availability", () => {
|
|
||||||
return sqlitePlugin !== null;
|
|
||||||
});
|
|
||||||
|
|
||||||
ipcMain.handle("capacitor-sqlite", async (event, ...args) => {
|
|
||||||
if (!sqlitePlugin) {
|
|
||||||
throw new Error("SQLite plugin not initialized");
|
|
||||||
}
|
|
||||||
return sqlitePlugin.handle(event, ...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize app when ready
|
// Initialize app when ready
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(async () => {
|
||||||
logger.info("App is ready, creating window...");
|
logger.info("App is ready, initializing SQLite...");
|
||||||
createWindow();
|
try {
|
||||||
|
await initializeSQLite();
|
||||||
|
logger.info("SQLite initialized, creating window...");
|
||||||
|
createWindow();
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Failed to initialize app:", error);
|
||||||
|
app.quit();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check if running in dev mode
|
// Check if running in dev mode
|
||||||
@@ -115,8 +113,8 @@ const isDev = process.argv.includes("--inspect");
|
|||||||
function createWindow(): void {
|
function createWindow(): void {
|
||||||
// Resolve preload path based on environment
|
// Resolve preload path based on environment
|
||||||
const preloadPath = app.isPackaged
|
const preloadPath = app.isPackaged
|
||||||
? path.join(process.resourcesPath, "preload.mjs")
|
? path.join(process.resourcesPath, "preload.js")
|
||||||
: path.join(__dirname, "preload.mjs");
|
: path.join(__dirname, "preload.js");
|
||||||
|
|
||||||
logger.log("[Electron] Preload path:", preloadPath);
|
logger.log("[Electron] Preload path:", preloadPath);
|
||||||
logger.log("[Electron] Preload exists:", fs.existsSync(preloadPath));
|
logger.log("[Electron] Preload exists:", fs.existsSync(preloadPath));
|
||||||
@@ -331,3 +329,21 @@ app.on("activate", () => {
|
|||||||
process.on("uncaughtException", (error) => {
|
process.on("uncaughtException", (error) => {
|
||||||
logger.error("Uncaught Exception:", error);
|
logger.error("Uncaught Exception:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Set up IPC handlers for SQLite operations
|
||||||
|
ipcMain.handle("check-sqlite-availability", () => {
|
||||||
|
return sqlitePlugin !== null;
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle("capacitor-sqlite", async (event, ...args) => {
|
||||||
|
if (!sqlitePlugin) {
|
||||||
|
logger.error("SQLite plugin not initialized when handling IPC request");
|
||||||
|
throw new Error("SQLite plugin not initialized");
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return await sqlitePlugin.handle(event, ...args);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("Error handling SQLite IPC request:", error, JSON.stringify(error), (error as any)?.stack);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@@ -3,11 +3,29 @@ import { logger } from "./utils/logger";
|
|||||||
|
|
||||||
async function initializeSQLite() {
|
async function initializeSQLite() {
|
||||||
try {
|
try {
|
||||||
const isAvailable = await window.electron.sqlite.isAvailable();
|
// Wait for SQLite to be available in the main process
|
||||||
if (!isAvailable) {
|
let retries = 0;
|
||||||
throw new Error("SQLite plugin not available in main process");
|
const maxRetries = 5;
|
||||||
|
const retryDelay = 1000; // 1 second
|
||||||
|
|
||||||
|
while (retries < maxRetries) {
|
||||||
|
try {
|
||||||
|
const isAvailable = await window.electron.sqlite.isAvailable();
|
||||||
|
if (isAvailable) {
|
||||||
|
logger.info("[Electron] SQLite plugin bridge initialized successfully");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn(`[Electron] SQLite not available yet (attempt ${retries + 1}/${maxRetries}):`, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
retries++;
|
||||||
|
if (retries < maxRetries) {
|
||||||
|
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
logger.info("[Electron] SQLite plugin bridge initialized");
|
|
||||||
|
throw new Error("SQLite plugin not available after maximum retries");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("[Electron] Failed to initialize SQLite plugin bridge:", error);
|
logger.error("[Electron] Failed to initialize SQLite plugin bridge:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -27,8 +45,22 @@ if (pwa_enabled) {
|
|||||||
|
|
||||||
// Initialize app and SQLite
|
// Initialize app and SQLite
|
||||||
const app = initializeApp();
|
const app = initializeApp();
|
||||||
initializeSQLite().then(() => {
|
|
||||||
app.mount("#app");
|
// Initialize SQLite first, then mount the app
|
||||||
}).catch(error => {
|
initializeSQLite()
|
||||||
logger.error("[Electron] Failed to initialize app:", error);
|
.then(() => {
|
||||||
});
|
logger.info("[Electron] SQLite initialized, mounting app...");
|
||||||
|
app.mount("#app");
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
logger.error("[Electron] Failed to initialize app:", error);
|
||||||
|
// Show error to user
|
||||||
|
const errorDiv = document.createElement("div");
|
||||||
|
errorDiv.style.cssText = "position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: #ffebee; color: #c62828; padding: 20px; border-radius: 4px; text-align: center; max-width: 80%;";
|
||||||
|
errorDiv.innerHTML = `
|
||||||
|
<h2>Failed to Initialize Database</h2>
|
||||||
|
<p>There was an error initializing the database. Please try restarting the application.</p>
|
||||||
|
<p>Error details: ${error.message}</p>
|
||||||
|
`;
|
||||||
|
document.body.appendChild(errorDiv);
|
||||||
|
});
|
||||||
|
|||||||
@@ -4,11 +4,10 @@ import {
|
|||||||
PlatformCapabilities,
|
PlatformCapabilities,
|
||||||
} from "../PlatformService";
|
} from "../PlatformService";
|
||||||
import { logger } from "../../utils/logger";
|
import { logger } from "../../utils/logger";
|
||||||
import { QueryExecResult, SqlValue } from "@/interfaces/database";
|
import { QueryExecResult } from "@/interfaces/database";
|
||||||
import {
|
import {
|
||||||
SQLiteConnection,
|
SQLiteConnection,
|
||||||
SQLiteDBConnection,
|
SQLiteDBConnection,
|
||||||
Changes,
|
|
||||||
} from "@capacitor-community/sqlite";
|
} from "@capacitor-community/sqlite";
|
||||||
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
import { DEFAULT_ENDORSER_API_SERVER } from "@/constants/app";
|
||||||
|
|
||||||
@@ -30,6 +29,7 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
private db: SQLiteDBConnection | null = null;
|
private db: SQLiteDBConnection | null = null;
|
||||||
private dbName = "timesafari.db";
|
private dbName = "timesafari.db";
|
||||||
private initialized = false;
|
private initialized = false;
|
||||||
|
private initializationPromise: Promise<void> | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Use the IPC bridge for SQLite operations
|
// Use the IPC bridge for SQLite operations
|
||||||
@@ -40,40 +40,53 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async initializeDatabase(): Promise<void> {
|
private async initializeDatabase(): Promise<void> {
|
||||||
|
// If already initialized, return
|
||||||
if (this.initialized) {
|
if (this.initialized) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
// If initialization is in progress, wait for it
|
||||||
// Check if SQLite is available through IPC
|
if (this.initializationPromise) {
|
||||||
const isAvailable = await window.electron.sqlite.isAvailable();
|
return this.initializationPromise;
|
||||||
if (!isAvailable) {
|
|
||||||
throw new Error("SQLite is not available in the main process");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create/Open database with native implementation
|
|
||||||
this.db = await this.sqlite.createConnection(
|
|
||||||
this.dbName,
|
|
||||||
false,
|
|
||||||
"no-encryption",
|
|
||||||
1,
|
|
||||||
true, // Use native implementation
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.db.open();
|
|
||||||
|
|
||||||
// Set journal mode to WAL for better performance
|
|
||||||
await this.db.execute("PRAGMA journal_mode=WAL;");
|
|
||||||
|
|
||||||
// Run migrations
|
|
||||||
await this.runMigrations();
|
|
||||||
|
|
||||||
this.initialized = true;
|
|
||||||
logger.log("SQLite database initialized successfully");
|
|
||||||
} catch (error) {
|
|
||||||
logger.error("Error initializing SQLite database:", error);
|
|
||||||
throw new Error("Failed to initialize database");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start initialization
|
||||||
|
this.initializationPromise = (async () => {
|
||||||
|
try {
|
||||||
|
// Check if SQLite is available through IPC
|
||||||
|
const isAvailable = await window.electron.sqlite.isAvailable();
|
||||||
|
if (!isAvailable) {
|
||||||
|
throw new Error("SQLite is not available in the main process");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create/Open database with native implementation
|
||||||
|
this.db = await this.sqlite.createConnection(
|
||||||
|
this.dbName,
|
||||||
|
false,
|
||||||
|
"no-encryption",
|
||||||
|
1,
|
||||||
|
true, // Use native implementation
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.db.open();
|
||||||
|
|
||||||
|
// Set journal mode to WAL for better performance
|
||||||
|
await this.db.execute("PRAGMA journal_mode=WAL;");
|
||||||
|
|
||||||
|
// Run migrations
|
||||||
|
await this.runMigrations();
|
||||||
|
|
||||||
|
this.initialized = true;
|
||||||
|
logger.log("[Electron] SQLite database initialized successfully");
|
||||||
|
} catch (error) {
|
||||||
|
logger.error("[Electron] Error initializing SQLite database:", error);
|
||||||
|
this.initialized = false;
|
||||||
|
this.initializationPromise = null;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return this.initializationPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async runMigrations(): Promise<void> {
|
private async runMigrations(): Promise<void> {
|
||||||
@@ -300,24 +313,24 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
/**
|
/**
|
||||||
* @see PlatformService.dbQuery
|
* @see PlatformService.dbQuery
|
||||||
*/
|
*/
|
||||||
async dbQuery(sql: string, params?: unknown[]): Promise<QueryExecResult> {
|
async dbQuery(
|
||||||
await this.initializeDatabase();
|
sql: string,
|
||||||
if (!this.db) {
|
params?: unknown[],
|
||||||
throw new Error("Database not initialized");
|
): Promise<QueryExecResult | undefined> {
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.db.query(sql, params || []);
|
await this.initializeDatabase();
|
||||||
const values = result.values || [];
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
const result = await this.db.query(sql, params);
|
||||||
|
// Convert SQLite plugin result to QueryExecResult format
|
||||||
return {
|
return {
|
||||||
columns: [], // SQLite plugin doesn't provide column names in query result
|
columns: [], // SQLite plugin doesn't provide column names
|
||||||
values: values as SqlValue[][],
|
values: result.values || [],
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error executing query:", error);
|
logger.error("[Electron] Database query error:", error);
|
||||||
throw new Error(
|
throw error;
|
||||||
`Database query failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,23 +341,20 @@ export class ElectronPlatformService implements PlatformService {
|
|||||||
sql: string,
|
sql: string,
|
||||||
params?: unknown[],
|
params?: unknown[],
|
||||||
): Promise<{ changes: number; lastId?: number }> {
|
): Promise<{ changes: number; lastId?: number }> {
|
||||||
await this.initializeDatabase();
|
|
||||||
if (!this.db) {
|
|
||||||
throw new Error("Database not initialized");
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await this.db.run(sql, params || []);
|
await this.initializeDatabase();
|
||||||
const changes = result.changes as Changes;
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
const result = await this.db.run(sql, params);
|
||||||
|
// Convert SQLite plugin result to expected format
|
||||||
return {
|
return {
|
||||||
changes: changes?.changes || 0,
|
changes: result.changes?.changes || 0,
|
||||||
lastId: changes?.lastId,
|
lastId: result.changes?.lastId,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error executing statement:", error);
|
logger.error("[Electron] Database execution error:", error);
|
||||||
throw new Error(
|
throw error;
|
||||||
`Database execution failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user