fix linting
This commit is contained in:
@@ -75,27 +75,30 @@ try {
|
||||
const getAppDataPath = async (): Promise<string> => {
|
||||
try {
|
||||
// Read config file directly
|
||||
const configPath = path.join(__dirname, '..', 'capacitor.config.json');
|
||||
const configContent = await fs.promises.readFile(configPath, 'utf-8');
|
||||
const configPath = path.join(__dirname, "..", "capacitor.config.json");
|
||||
const configContent = await fs.promises.readFile(configPath, "utf-8");
|
||||
const config = JSON.parse(configContent);
|
||||
const linuxPath = config?.plugins?.CapacitorSQLite?.electronLinuxLocation;
|
||||
|
||||
|
||||
if (linuxPath) {
|
||||
// Expand ~ to home directory
|
||||
const expandedPath = linuxPath.replace(/^~/, process.env.HOME || '');
|
||||
const expandedPath = linuxPath.replace(/^~/, process.env.HOME || "");
|
||||
logger.info("[Electron] Using configured database path:", expandedPath);
|
||||
return expandedPath;
|
||||
}
|
||||
|
||||
|
||||
// Fallback to app.getPath if config path is not available
|
||||
const userDataPath = app.getPath('userData');
|
||||
const userDataPath = app.getPath("userData");
|
||||
logger.info("[Electron] Using fallback user data path:", userDataPath);
|
||||
return userDataPath;
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Error getting app data path:", error);
|
||||
// Fallback to app.getPath if anything fails
|
||||
const userDataPath = app.getPath('userData');
|
||||
logger.info("[Electron] Using fallback user data path after error:", userDataPath);
|
||||
const userDataPath = app.getPath("userData");
|
||||
logger.info(
|
||||
"[Electron] Using fallback user data path after error:",
|
||||
userDataPath,
|
||||
);
|
||||
return userDataPath;
|
||||
}
|
||||
};
|
||||
@@ -103,30 +106,32 @@ const getAppDataPath = async (): Promise<string> => {
|
||||
const validateAndNormalizePath = async (filePath: string): Promise<string> => {
|
||||
// Resolve any relative paths
|
||||
const resolvedPath = path.resolve(filePath);
|
||||
|
||||
|
||||
// Ensure it's an absolute path
|
||||
if (!path.isAbsolute(resolvedPath)) {
|
||||
throw new Error(`Database path must be absolute: ${resolvedPath}`);
|
||||
}
|
||||
|
||||
|
||||
// Ensure it's within the app data directory
|
||||
const appDataPath = await getAppDataPath();
|
||||
if (!resolvedPath.startsWith(appDataPath)) {
|
||||
throw new Error(`Database path must be within app data directory: ${resolvedPath}`);
|
||||
throw new Error(
|
||||
`Database path must be within app data directory: ${resolvedPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Normalize the path
|
||||
const normalizedPath = path.normalize(resolvedPath);
|
||||
|
||||
|
||||
logger.info("[Electron] Validated database path:", {
|
||||
original: filePath,
|
||||
resolved: resolvedPath,
|
||||
normalized: normalizedPath,
|
||||
appDataPath,
|
||||
isAbsolute: path.isAbsolute(normalizedPath),
|
||||
isWithinAppData: normalizedPath.startsWith(appDataPath)
|
||||
isWithinAppData: normalizedPath.startsWith(appDataPath),
|
||||
});
|
||||
|
||||
|
||||
return normalizedPath;
|
||||
};
|
||||
|
||||
@@ -134,34 +139,46 @@ const ensureDirectoryExists = async (dirPath: string): Promise<void> => {
|
||||
try {
|
||||
// Normalize the path first
|
||||
const normalizedPath = path.normalize(dirPath);
|
||||
|
||||
|
||||
// Check if directory exists
|
||||
if (!fs.existsSync(normalizedPath)) {
|
||||
logger.info("[Electron] Creating database directory:", normalizedPath);
|
||||
await fs.promises.mkdir(normalizedPath, { recursive: true });
|
||||
}
|
||||
|
||||
|
||||
// Verify directory permissions
|
||||
try {
|
||||
await fs.promises.access(normalizedPath, fs.constants.R_OK | fs.constants.W_OK);
|
||||
logger.info("[Electron] Database directory permissions verified:", normalizedPath);
|
||||
await fs.promises.access(
|
||||
normalizedPath,
|
||||
fs.constants.R_OK | fs.constants.W_OK,
|
||||
);
|
||||
logger.info(
|
||||
"[Electron] Database directory permissions verified:",
|
||||
normalizedPath,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Database directory permission error:", error);
|
||||
throw new Error(`Database directory not accessible: ${normalizedPath}`);
|
||||
}
|
||||
|
||||
|
||||
// Test write permissions
|
||||
const testFile = path.join(normalizedPath, '.write-test');
|
||||
const testFile = path.join(normalizedPath, ".write-test");
|
||||
try {
|
||||
await fs.promises.writeFile(testFile, 'test');
|
||||
await fs.promises.writeFile(testFile, "test");
|
||||
await fs.promises.unlink(testFile);
|
||||
logger.info("[Electron] Database directory write test passed:", normalizedPath);
|
||||
logger.info(
|
||||
"[Electron] Database directory write test passed:",
|
||||
normalizedPath,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Database directory write test failed:", error);
|
||||
throw new Error(`Database directory not writable: ${normalizedPath}`);
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Failed to ensure database directory exists:", error);
|
||||
logger.error(
|
||||
"[Electron] Failed to ensure database directory exists:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
@@ -177,35 +194,43 @@ const initializeDatabasePaths = async (): Promise<void> => {
|
||||
if (dbPathInitializationPromise) {
|
||||
return dbPathInitializationPromise;
|
||||
}
|
||||
|
||||
|
||||
if (dbPathInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
dbPathInitializationPromise = (async () => {
|
||||
try {
|
||||
// Get the base directory from config
|
||||
dbDir = await getAppDataPath();
|
||||
logger.info("[Electron] Database directory:", dbDir);
|
||||
|
||||
|
||||
// Ensure the directory exists and is writable
|
||||
await ensureDirectoryExists(dbDir);
|
||||
|
||||
|
||||
// Construct the database path
|
||||
dbPath = await validateAndNormalizePath(path.join(dbDir, 'timesafari.db'));
|
||||
dbPath = await validateAndNormalizePath(
|
||||
path.join(dbDir, "timesafari.db"),
|
||||
);
|
||||
logger.info("[Electron] Database path initialized:", dbPath);
|
||||
|
||||
|
||||
// Verify the database file if it exists
|
||||
if (fs.existsSync(dbPath)) {
|
||||
try {
|
||||
await fs.promises.access(dbPath, fs.constants.R_OK | fs.constants.W_OK);
|
||||
logger.info("[Electron] Existing database file permissions verified:", dbPath);
|
||||
await fs.promises.access(
|
||||
dbPath,
|
||||
fs.constants.R_OK | fs.constants.W_OK,
|
||||
);
|
||||
logger.info(
|
||||
"[Electron] Existing database file permissions verified:",
|
||||
dbPath,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Database file permission error:", error);
|
||||
throw new Error(`Database file not accessible: ${dbPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dbPathInitialized = true;
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Failed to initialize database paths:", error);
|
||||
@@ -214,7 +239,7 @@ const initializeDatabasePaths = async (): Promise<void> => {
|
||||
dbPathInitializationPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
return dbPathInitializationPromise;
|
||||
};
|
||||
|
||||
@@ -228,27 +253,27 @@ async function initializeSQLite() {
|
||||
if (sqliteInitializationPromise) {
|
||||
return sqliteInitializationPromise;
|
||||
}
|
||||
|
||||
|
||||
if (sqliteInitialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
sqliteInitializationPromise = (async () => {
|
||||
try {
|
||||
logger.info("[Electron] Initializing SQLite plugin...");
|
||||
sqlitePlugin = new CapacitorSQLite();
|
||||
|
||||
|
||||
// Initialize database paths first
|
||||
await initializeDatabasePaths();
|
||||
|
||||
|
||||
if (!dbPath) {
|
||||
throw new Error("Database path not initialized");
|
||||
}
|
||||
|
||||
|
||||
// Test the plugin
|
||||
const echoResult = await sqlitePlugin.echo({ value: "test" });
|
||||
logger.info("[Electron] SQLite plugin echo test:", echoResult);
|
||||
|
||||
|
||||
// Initialize database connection using validated dbPath
|
||||
const connectionOptions = {
|
||||
database: dbPath,
|
||||
@@ -256,34 +281,42 @@ async function initializeSQLite() {
|
||||
readOnly: false,
|
||||
encryption: "no-encryption",
|
||||
useNative: true,
|
||||
mode: "rwc" // Force read-write-create mode
|
||||
mode: "rwc", // Force read-write-create mode
|
||||
};
|
||||
|
||||
logger.info("[Electron] Creating initial connection with options:", connectionOptions);
|
||||
|
||||
|
||||
logger.info(
|
||||
"[Electron] Creating initial connection with options:",
|
||||
connectionOptions,
|
||||
);
|
||||
|
||||
// Log the actual path being used
|
||||
logger.info("[Electron] Using database path:", dbPath);
|
||||
logger.info("[Electron] Path exists:", fs.existsSync(dbPath));
|
||||
logger.info("[Electron] Path is absolute:", path.isAbsolute(dbPath));
|
||||
|
||||
|
||||
const db = await sqlitePlugin.createConnection(connectionOptions);
|
||||
|
||||
if (!db || typeof db !== 'object') {
|
||||
throw new Error(`Failed to create database connection - invalid response. Path used: ${dbPath}`);
|
||||
|
||||
if (!db || typeof db !== "object") {
|
||||
throw new Error(
|
||||
`Failed to create database connection - invalid response. Path used: ${dbPath}`,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Wait a moment for the connection to be fully established
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
// Verify the connection is working
|
||||
try {
|
||||
const result = await db.query("PRAGMA journal_mode;");
|
||||
logger.info("[Electron] Database connection verified:", result);
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Database connection verification failed:", error);
|
||||
logger.error(
|
||||
"[Electron] Database connection verification failed:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
||||
sqliteInitialized = true;
|
||||
logger.info("[Electron] SQLite plugin initialized successfully");
|
||||
} catch (error) {
|
||||
@@ -293,42 +326,45 @@ async function initializeSQLite() {
|
||||
sqliteInitializationPromise = null;
|
||||
}
|
||||
})();
|
||||
|
||||
|
||||
return sqliteInitializationPromise;
|
||||
}
|
||||
|
||||
// Initialize app when ready
|
||||
app.whenReady().then(async () => {
|
||||
logger.info("App is ready, starting initialization...");
|
||||
|
||||
|
||||
// Create window first
|
||||
const mainWindow = createWindow();
|
||||
|
||||
|
||||
// Initialize database in background
|
||||
initializeSQLite().catch((error) => {
|
||||
logger.error("[Electron] Database initialization failed, but continuing:", error);
|
||||
logger.error(
|
||||
"[Electron] Database initialization failed, but continuing:",
|
||||
error,
|
||||
);
|
||||
// Notify renderer about database status
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('database-status', {
|
||||
status: 'error',
|
||||
error: error.message
|
||||
mainWindow.webContents.send("database-status", {
|
||||
status: "error",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Handle window close
|
||||
mainWindow.on('closed', () => {
|
||||
mainWindow.on("closed", () => {
|
||||
logger.info("[Electron] Main window closed");
|
||||
});
|
||||
|
||||
// Handle window close request
|
||||
mainWindow.on('close', (event) => {
|
||||
mainWindow.on("close", (event) => {
|
||||
logger.info("[Electron] Window close requested");
|
||||
// Prevent immediate close if we're in the middle of something
|
||||
if (mainWindow.webContents.isLoading()) {
|
||||
event.preventDefault();
|
||||
logger.info("[Electron] Deferring window close due to loading state");
|
||||
mainWindow.webContents.once('did-finish-load', () => {
|
||||
mainWindow.webContents.once("did-finish-load", () => {
|
||||
mainWindow.close();
|
||||
});
|
||||
}
|
||||
@@ -378,25 +414,32 @@ function createWindow(): BrowserWindow {
|
||||
sandbox: false,
|
||||
preload: preloadPath,
|
||||
webSecurity: true,
|
||||
allowRunningInsecureContent: false
|
||||
allowRunningInsecureContent: false,
|
||||
},
|
||||
});
|
||||
|
||||
// Show window when ready
|
||||
mainWindow.once('ready-to-show', () => {
|
||||
mainWindow.once("ready-to-show", () => {
|
||||
logger.info("[Electron] Window ready to show");
|
||||
mainWindow.show();
|
||||
});
|
||||
|
||||
// Handle window errors
|
||||
mainWindow.webContents.on('render-process-gone', (_event, details) => {
|
||||
mainWindow.webContents.on("render-process-gone", (_event, details) => {
|
||||
logger.error("[Electron] Render process gone:", details);
|
||||
});
|
||||
|
||||
mainWindow.webContents.on('did-fail-load', (_event, errorCode, errorDescription) => {
|
||||
logger.error("[Electron] Page failed to load:", errorCode, errorDescription);
|
||||
logger.error("[Electron] Failed URL:", mainWindow.webContents.getURL());
|
||||
});
|
||||
mainWindow.webContents.on(
|
||||
"did-fail-load",
|
||||
(_event, errorCode, errorDescription) => {
|
||||
logger.error(
|
||||
"[Electron] Page failed to load:",
|
||||
errorCode,
|
||||
errorDescription,
|
||||
);
|
||||
logger.error("[Electron] Failed URL:", mainWindow.webContents.getURL());
|
||||
},
|
||||
);
|
||||
|
||||
// Load the index.html
|
||||
let indexPath: string;
|
||||
@@ -405,11 +448,15 @@ function createWindow(): BrowserWindow {
|
||||
if (app.isPackaged) {
|
||||
indexPath = path.join(process.resourcesPath, "www", "index.html");
|
||||
fileUrl = `file://${indexPath}`;
|
||||
logger.info("[Electron] App is packaged. Using process.resourcesPath for index.html");
|
||||
logger.info(
|
||||
"[Electron] App is packaged. Using process.resourcesPath for index.html",
|
||||
);
|
||||
} else {
|
||||
indexPath = path.resolve(__dirname, "www", "index.html");
|
||||
fileUrl = `file://${indexPath}`;
|
||||
logger.info("[Electron] App is not packaged. Using __dirname for index.html");
|
||||
logger.info(
|
||||
"[Electron] App is not packaged. Using __dirname for index.html",
|
||||
);
|
||||
}
|
||||
|
||||
logger.info("[Electron] Resolved index.html path:", indexPath);
|
||||
@@ -417,14 +464,16 @@ function createWindow(): BrowserWindow {
|
||||
|
||||
// Load the index.html with retry logic
|
||||
const loadIndexHtml = async (retryCount = 0): Promise<void> => {
|
||||
try {
|
||||
try {
|
||||
if (mainWindow.isDestroyed()) {
|
||||
logger.error("[Electron] Window was destroyed before loading index.html");
|
||||
logger.error(
|
||||
"[Electron] Window was destroyed before loading index.html",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const exists = fs.existsSync(indexPath);
|
||||
logger.info(`[Electron] fs.existsSync for index.html: ${exists}`);
|
||||
const exists = fs.existsSync(indexPath);
|
||||
logger.info(`[Electron] fs.existsSync for index.html: ${exists}`);
|
||||
|
||||
if (!exists) {
|
||||
throw new Error(`index.html not found at path: ${indexPath}`);
|
||||
@@ -436,7 +485,7 @@ function createWindow(): BrowserWindow {
|
||||
size: stats.size,
|
||||
mode: stats.mode,
|
||||
uid: stats.uid,
|
||||
gid: stats.gid
|
||||
gid: stats.gid,
|
||||
});
|
||||
|
||||
// Try loadURL first
|
||||
@@ -445,22 +494,28 @@ function createWindow(): BrowserWindow {
|
||||
await mainWindow.loadURL(fileUrl);
|
||||
logger.info("[Electron] Successfully loaded index.html via loadURL");
|
||||
} catch (loadUrlError) {
|
||||
logger.warn("[Electron] loadURL failed, trying loadFile:", loadUrlError);
|
||||
logger.warn(
|
||||
"[Electron] loadURL failed, trying loadFile:",
|
||||
loadUrlError,
|
||||
);
|
||||
// Fallback to loadFile
|
||||
await mainWindow.loadFile(indexPath);
|
||||
logger.info("[Electron] Successfully loaded index.html via loadFile");
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
|
||||
const errorMessage =
|
||||
error instanceof Error ? error.message : "Unknown error occurred";
|
||||
logger.error("[Electron] Error loading index.html:", errorMessage);
|
||||
|
||||
|
||||
// Retry logic
|
||||
if (retryCount < 3 && !mainWindow.isDestroyed()) {
|
||||
logger.info(`[Electron] Retrying index.html load (attempt ${retryCount + 1})`);
|
||||
await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
|
||||
logger.info(
|
||||
`[Electron] Retrying index.html load (attempt ${retryCount + 1})`,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000)); // Wait 1 second
|
||||
return loadIndexHtml(retryCount + 1);
|
||||
}
|
||||
|
||||
|
||||
// If we've exhausted retries, show error in window
|
||||
if (!mainWindow.isDestroyed()) {
|
||||
const errorHtml = `
|
||||
@@ -472,7 +527,9 @@ function createWindow(): BrowserWindow {
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
await mainWindow.loadURL(`data:text/html,${encodeURIComponent(errorHtml)}`);
|
||||
await mainWindow.loadURL(
|
||||
`data:text/html,${encodeURIComponent(errorHtml)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -520,7 +577,12 @@ ipcMain.handle("sqlite-echo", async (_event, value) => {
|
||||
try {
|
||||
return await sqlitePlugin.echo({ value });
|
||||
} catch (error) {
|
||||
logger.error("Error in sqlite-echo:", error, JSON.stringify(error), (error as any)?.stack);
|
||||
logger.error(
|
||||
"Error in sqlite-echo:",
|
||||
error,
|
||||
JSON.stringify(error),
|
||||
(error as any)?.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -529,11 +591,11 @@ ipcMain.handle("sqlite-create-connection", async (_event, options) => {
|
||||
try {
|
||||
// Ensure database is initialized
|
||||
await initializeSQLite();
|
||||
|
||||
|
||||
if (!dbPath) {
|
||||
throw new Error("Database path not initialized");
|
||||
}
|
||||
|
||||
|
||||
// Override any provided database path with our resolved path
|
||||
const connectionOptions = {
|
||||
...options,
|
||||
@@ -541,31 +603,40 @@ ipcMain.handle("sqlite-create-connection", async (_event, options) => {
|
||||
readOnly: false,
|
||||
mode: "rwc", // Force read-write-create mode
|
||||
encryption: "no-encryption",
|
||||
useNative: true
|
||||
useNative: true,
|
||||
};
|
||||
|
||||
logger.info("[Electron] Creating database connection with options:", connectionOptions);
|
||||
|
||||
logger.info(
|
||||
"[Electron] Creating database connection with options:",
|
||||
connectionOptions,
|
||||
);
|
||||
const result = await sqlitePlugin.createConnection(connectionOptions);
|
||||
|
||||
if (!result || typeof result !== 'object') {
|
||||
throw new Error("Failed to create database connection - invalid response");
|
||||
|
||||
if (!result || typeof result !== "object") {
|
||||
throw new Error(
|
||||
"Failed to create database connection - invalid response",
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// Wait a moment for the connection to be fully established
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
|
||||
try {
|
||||
// Verify connection is not read-only
|
||||
const testResult = await result.query({ statement: "PRAGMA journal_mode;" });
|
||||
const testResult = await result.query({
|
||||
statement: "PRAGMA journal_mode;",
|
||||
});
|
||||
if (testResult?.values?.[0]?.journal_mode === "off") {
|
||||
logger.error("[Electron] Connection opened in read-only mode despite options");
|
||||
logger.error(
|
||||
"[Electron] Connection opened in read-only mode despite options",
|
||||
);
|
||||
throw new Error("Database connection opened in read-only mode");
|
||||
}
|
||||
} catch (queryError) {
|
||||
logger.error("[Electron] Error verifying connection:", queryError);
|
||||
throw queryError;
|
||||
}
|
||||
|
||||
|
||||
logger.info("[Electron] Database connection created successfully");
|
||||
return result;
|
||||
} catch (error) {
|
||||
@@ -578,7 +649,12 @@ ipcMain.handle("sqlite-execute", async (_event, options) => {
|
||||
try {
|
||||
return await sqlitePlugin.execute(options);
|
||||
} catch (error) {
|
||||
logger.error("Error in sqlite-execute:", error, JSON.stringify(error), (error as any)?.stack);
|
||||
logger.error(
|
||||
"Error in sqlite-execute:",
|
||||
error,
|
||||
JSON.stringify(error),
|
||||
(error as any)?.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -587,7 +663,12 @@ ipcMain.handle("sqlite-query", async (_event, options) => {
|
||||
try {
|
||||
return await sqlitePlugin.query(options);
|
||||
} catch (error) {
|
||||
logger.error("Error in sqlite-query:", error, JSON.stringify(error), (error as any)?.stack);
|
||||
logger.error(
|
||||
"Error in sqlite-query:",
|
||||
error,
|
||||
JSON.stringify(error),
|
||||
(error as any)?.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
@@ -596,7 +677,12 @@ ipcMain.handle("sqlite-close-connection", async (_event, options) => {
|
||||
try {
|
||||
return await sqlitePlugin.closeConnection(options);
|
||||
} catch (error) {
|
||||
logger.error("Error in sqlite-close-connection:", error, JSON.stringify(error), (error as any)?.stack);
|
||||
logger.error(
|
||||
"Error in sqlite-close-connection:",
|
||||
error,
|
||||
JSON.stringify(error),
|
||||
(error as any)?.stack,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,44 +98,55 @@ try {
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
if (attempt < MAX_RETRIES) {
|
||||
logger.warn(`SQLite operation failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`, error);
|
||||
await new Promise(resolve => setTimeout(resolve, RETRY_DELAY));
|
||||
logger.warn(
|
||||
`SQLite operation failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`,
|
||||
error,
|
||||
);
|
||||
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error(`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) => {
|
||||
return async (...args) => {
|
||||
try {
|
||||
return await withRetry(ipcRenderer.invoke, 'sqlite-' + method, ...args);
|
||||
return await withRetry(
|
||||
ipcRenderer.invoke,
|
||||
"sqlite-" + method,
|
||||
...args,
|
||||
);
|
||||
} catch (error) {
|
||||
logger.error(`SQLite ${method} failed:`, error);
|
||||
throw new Error(`Database operation failed: ${error.message || 'Unknown error'}`);
|
||||
throw new Error(
|
||||
`Database operation failed: ${error.message || "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Create a proxy that matches the CapacitorSQLite interface
|
||||
return {
|
||||
echo: wrapOperation('echo'),
|
||||
createConnection: wrapOperation('create-connection'),
|
||||
closeConnection: wrapOperation('close-connection'),
|
||||
execute: wrapOperation('execute'),
|
||||
query: wrapOperation('query'),
|
||||
run: wrapOperation('run'),
|
||||
isAvailable: wrapOperation('is-available'),
|
||||
getPlatform: () => Promise.resolve('electron'),
|
||||
echo: wrapOperation("echo"),
|
||||
createConnection: wrapOperation("create-connection"),
|
||||
closeConnection: wrapOperation("close-connection"),
|
||||
execute: wrapOperation("execute"),
|
||||
query: wrapOperation("query"),
|
||||
run: wrapOperation("run"),
|
||||
isAvailable: wrapOperation("is-available"),
|
||||
getPlatform: () => Promise.resolve("electron"),
|
||||
// Add other methods as needed
|
||||
};
|
||||
};
|
||||
|
||||
// Expose only the CapacitorSQLite proxy
|
||||
contextBridge.exposeInMainWorld('CapacitorSQLite', createSQLiteProxy());
|
||||
contextBridge.exposeInMainWorld("CapacitorSQLite", createSQLiteProxy());
|
||||
|
||||
// Remove the duplicate electron.sqlite bridge
|
||||
contextBridge.exposeInMainWorld('electron', {
|
||||
contextBridge.exposeInMainWorld("electron", {
|
||||
// Keep other electron APIs but remove sqlite
|
||||
getPath,
|
||||
send: (channel, data) => {
|
||||
|
||||
@@ -12,22 +12,30 @@ async function initializeSQLite() {
|
||||
try {
|
||||
const isAvailable = await window.CapacitorSQLite.isAvailable();
|
||||
if (isAvailable) {
|
||||
logger.info("[Electron] SQLite plugin bridge initialized successfully");
|
||||
logger.info(
|
||||
"[Electron] SQLite plugin bridge initialized successfully",
|
||||
);
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
logger.warn(`[Electron] SQLite not available yet (attempt ${retries + 1}/${maxRetries}):`, error);
|
||||
logger.warn(
|
||||
`[Electron] SQLite not available yet (attempt ${retries + 1}/${maxRetries}):`,
|
||||
error,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
retries++;
|
||||
if (retries < maxRetries) {
|
||||
await new Promise(resolve => setTimeout(resolve, retryDelay));
|
||||
await new Promise((resolve) => setTimeout(resolve, retryDelay));
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error("SQLite plugin not available after maximum retries");
|
||||
} catch (error) {
|
||||
logger.error("[Electron] Failed to initialize SQLite plugin bridge:", error);
|
||||
logger.error(
|
||||
"[Electron] Failed to initialize SQLite plugin bridge:",
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -52,11 +60,12 @@ initializeSQLite()
|
||||
logger.info("[Electron] SQLite initialized, mounting app...");
|
||||
app.mount("#app");
|
||||
})
|
||||
.catch(error => {
|
||||
.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.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>
|
||||
|
||||
@@ -49,7 +49,7 @@ export class ElectronPlatformService implements PlatformService {
|
||||
try {
|
||||
await this.db.close();
|
||||
} catch (e) {
|
||||
logger.warn('Error closing existing connection:', e);
|
||||
logger.warn("Error closing existing connection:", e);
|
||||
}
|
||||
this.db = null;
|
||||
}
|
||||
@@ -61,9 +61,9 @@ export class ElectronPlatformService implements PlatformService {
|
||||
this.dbConnectionErrorLogged = false;
|
||||
|
||||
// Wait a moment for cleanup
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
} catch (error) {
|
||||
logger.error('Error resetting connection:', error);
|
||||
logger.error("Error resetting connection:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export class ElectronPlatformService implements PlatformService {
|
||||
private async initializeDatabase(): Promise<void> {
|
||||
// If we have a fatal error, try to recover
|
||||
if (this.dbFatalError) {
|
||||
logger.info('Attempting to recover from fatal error state...');
|
||||
logger.info("Attempting to recover from fatal error state...");
|
||||
await this.resetConnection();
|
||||
}
|
||||
|
||||
@@ -99,30 +99,32 @@ export class ElectronPlatformService implements PlatformService {
|
||||
logger.info("Calling createConnection with:", {
|
||||
dbName: this.dbName,
|
||||
readOnly: false,
|
||||
encryption: 'no-encryption',
|
||||
encryption: "no-encryption",
|
||||
version: 1,
|
||||
useNative: true
|
||||
useNative: true,
|
||||
});
|
||||
|
||||
// Create connection
|
||||
this.db = await this.sqlite.createConnection(
|
||||
this.dbName, // database name
|
||||
false, // readOnly
|
||||
'no-encryption', // encryption
|
||||
1, // version
|
||||
true // useNative
|
||||
this.dbName, // database name
|
||||
false, // readOnly
|
||||
"no-encryption", // encryption
|
||||
1, // version
|
||||
true, // useNative
|
||||
);
|
||||
|
||||
logger.info("createConnection result:", this.db);
|
||||
|
||||
if (!this.db || typeof this.db.execute !== 'function') {
|
||||
if (!this.db || typeof this.db.execute !== "function") {
|
||||
throw new Error("Failed to create a valid database connection");
|
||||
}
|
||||
|
||||
// Verify connection is not read-only
|
||||
const journalMode = await this.db.query('PRAGMA journal_mode;');
|
||||
if (journalMode?.values?.[0]?.journal_mode === 'off') {
|
||||
throw new Error('Database opened in read-only mode despite options');
|
||||
const journalMode = await this.db.query("PRAGMA journal_mode;");
|
||||
if (journalMode?.values?.[0]?.journal_mode === "off") {
|
||||
throw new Error(
|
||||
"Database opened in read-only mode despite options",
|
||||
);
|
||||
}
|
||||
|
||||
// Run migrations
|
||||
@@ -133,14 +135,18 @@ export class ElectronPlatformService implements PlatformService {
|
||||
this.dbConnectionErrorLogged = false;
|
||||
this.initialized = true;
|
||||
return;
|
||||
|
||||
} catch (error) {
|
||||
lastError = error instanceof Error ? error : new Error(String(error));
|
||||
retryCount++;
|
||||
|
||||
if (retryCount < this.MAX_RETRIES) {
|
||||
logger.warn(`Database initialization attempt ${retryCount}/${this.MAX_RETRIES} failed:`, error);
|
||||
await new Promise(resolve => setTimeout(resolve, this.RETRY_DELAY));
|
||||
logger.warn(
|
||||
`Database initialization attempt ${retryCount}/${this.MAX_RETRIES} failed:`,
|
||||
error,
|
||||
);
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, this.RETRY_DELAY),
|
||||
);
|
||||
await this.resetConnection();
|
||||
}
|
||||
}
|
||||
@@ -149,12 +155,18 @@ export class ElectronPlatformService implements PlatformService {
|
||||
// If we get here, all retries failed
|
||||
this.dbFatalError = true;
|
||||
if (!this.dbConnectionErrorLogged) {
|
||||
logger.error("[Electron] Error initializing SQLite database after all retries:", lastError);
|
||||
logger.error(
|
||||
"[Electron] Error initializing SQLite database after all retries:",
|
||||
lastError,
|
||||
);
|
||||
this.dbConnectionErrorLogged = true;
|
||||
}
|
||||
this.initialized = false;
|
||||
this.initializationPromise = null;
|
||||
throw lastError || new Error("Failed to initialize database after all retries");
|
||||
throw (
|
||||
lastError ||
|
||||
new Error("Failed to initialize database after all retries")
|
||||
);
|
||||
})();
|
||||
|
||||
return this.initializationPromise;
|
||||
@@ -413,7 +425,9 @@ export class ElectronPlatformService implements PlatformService {
|
||||
params?: unknown[],
|
||||
): Promise<{ changes: number; lastId?: number }> {
|
||||
if (this.dbFatalError) {
|
||||
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||
throw new Error(
|
||||
"Database is in a fatal error state. Please restart the app.",
|
||||
);
|
||||
}
|
||||
try {
|
||||
await this.initializeDatabase();
|
||||
@@ -440,22 +454,26 @@ export class ElectronPlatformService implements PlatformService {
|
||||
try {
|
||||
await this.initializeDatabase();
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize database:', error);
|
||||
throw new Error(`Database initialization failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
logger.error("Failed to initialize database:", error);
|
||||
throw new Error(
|
||||
`Database initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async query<T>(sql: string, params: any[] = []): Promise<T[]> {
|
||||
if (this.dbFatalError) {
|
||||
throw new Error("Database is in a fatal error state. Please restart the app.");
|
||||
throw new Error(
|
||||
"Database is in a fatal error state. Please restart the app.",
|
||||
);
|
||||
}
|
||||
if (!this.initialized) {
|
||||
throw new Error('Database not initialized. Call initialize() first.');
|
||||
throw new Error("Database not initialized. Call initialize() first.");
|
||||
}
|
||||
|
||||
return this.initializeDatabase().then(() => {
|
||||
if (!this.db) {
|
||||
throw new Error('Database not initialized after initialization');
|
||||
throw new Error("Database not initialized after initialization");
|
||||
}
|
||||
return this.db.query(sql, params).then((result) => {
|
||||
if (!result?.values) {
|
||||
@@ -468,7 +486,7 @@ export class ElectronPlatformService implements PlatformService {
|
||||
|
||||
async execute(sql: string, params: any[] = []): Promise<void> {
|
||||
if (!this.initialized) {
|
||||
throw new Error('Database not initialized. Call initialize() first.');
|
||||
throw new Error("Database not initialized. Call initialize() first.");
|
||||
}
|
||||
|
||||
await this.initializeDatabase().then(() => {
|
||||
@@ -486,8 +504,10 @@ export class ElectronPlatformService implements PlatformService {
|
||||
this.initialized = false;
|
||||
this.db = null;
|
||||
} catch (error) {
|
||||
console.error('Failed to close database:', error);
|
||||
throw new Error(`Failed to close database: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||
logger.error("Failed to close database:", error);
|
||||
throw new Error(
|
||||
`Failed to close database: ${error instanceof Error ? error.message : "Unknown error"}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user