You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
301 lines
11 KiB
301 lines
11 KiB
import { initializeApp } from "./main.common";
|
|
import { logger } from "./utils/logger";
|
|
import { SQLiteQueryResult } from "./services/platforms/ElectronPlatformService";
|
|
|
|
const platform = process.env.VITE_PLATFORM;
|
|
const pwa_enabled = process.env.VITE_PWA_ENABLED === "true";
|
|
|
|
logger.info("[Main Electron] Initializing app");
|
|
logger.info("[Main Electron] Platform:", { platform });
|
|
logger.info("[Main Electron] PWA enabled:", { pwa_enabled });
|
|
|
|
if (pwa_enabled) {
|
|
logger.warn("[Main Electron] PWA is enabled, but not supported in electron");
|
|
}
|
|
|
|
// Initialize app and SQLite
|
|
const app = initializeApp();
|
|
|
|
// Create a promise that resolves when SQLite is ready
|
|
const sqliteReady = new Promise<void>((resolve, reject) => {
|
|
let retryCount = 0;
|
|
let initializationTimeout: NodeJS.Timeout;
|
|
|
|
const attemptInitialization = () => {
|
|
// Clear any existing timeout
|
|
if (initializationTimeout) {
|
|
clearTimeout(initializationTimeout);
|
|
}
|
|
|
|
// Set timeout for this attempt
|
|
initializationTimeout = setTimeout(() => {
|
|
if (retryCount < 3) {
|
|
// Use same retry count as ElectronPlatformService
|
|
retryCount++;
|
|
logger.warn(
|
|
`[Main Electron] SQLite initialization attempt ${retryCount} timed out, retrying...`,
|
|
);
|
|
setTimeout(attemptInitialization, 1000); // Use same delay as ElectronPlatformService
|
|
} else {
|
|
logger.error(
|
|
"[Main Electron] SQLite initialization failed after all retries",
|
|
);
|
|
reject(new Error("SQLite initialization timeout after all retries"));
|
|
}
|
|
}, 10000); // Use same timeout as ElectronPlatformService
|
|
|
|
// Wait for electron bridge to be available
|
|
const checkElectronBridge = () => {
|
|
if (!window.electron?.ipcRenderer) {
|
|
// Check again in 100ms if bridge isn't ready
|
|
setTimeout(checkElectronBridge, 100);
|
|
return;
|
|
}
|
|
|
|
// At this point we know ipcRenderer exists
|
|
const ipcRenderer = window.electron.ipcRenderer;
|
|
|
|
logger.info("[Main Electron] [IPC:bridge] IPC renderer bridge available");
|
|
|
|
// Listen for SQLite ready signal
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-ready] Registering listener for SQLite ready signal",
|
|
);
|
|
ipcRenderer.once("sqlite-ready", () => {
|
|
clearTimeout(initializationTimeout);
|
|
logger.info(
|
|
"[Main Electron] [IPC:sqlite-ready] Received SQLite ready signal",
|
|
);
|
|
resolve();
|
|
});
|
|
|
|
// Also listen for database errors
|
|
logger.debug(
|
|
"[Main Electron] [IPC:database-status] Registering listener for database status",
|
|
);
|
|
ipcRenderer.once("database-status", (...args: unknown[]) => {
|
|
clearTimeout(initializationTimeout);
|
|
const status = args[0] as { status: string; error?: string };
|
|
if (status.status === "error") {
|
|
logger.error(
|
|
"[Main Electron] [IPC:database-status] Database error:",
|
|
{
|
|
error: status.error,
|
|
channel: "database-status",
|
|
},
|
|
);
|
|
reject(new Error(status.error || "Database initialization failed"));
|
|
}
|
|
});
|
|
|
|
// Check if SQLite is already available
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-is-available] Checking SQLite availability",
|
|
);
|
|
ipcRenderer
|
|
.invoke("sqlite-is-available")
|
|
.then(async (result: unknown) => {
|
|
const isAvailable = Boolean(result);
|
|
if (isAvailable) {
|
|
logger.info(
|
|
"[Main Electron] [IPC:sqlite-is-available] SQLite is available",
|
|
);
|
|
|
|
try {
|
|
// First create a database connection
|
|
logger.debug(
|
|
"[Main Electron] [IPC:get-path] Requesting database path",
|
|
);
|
|
const dbPath = await ipcRenderer.invoke("get-path");
|
|
logger.info(
|
|
"[Main Electron] [IPC:get-path] Database path received:",
|
|
{ dbPath },
|
|
);
|
|
|
|
// Create the database connection
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-create-connection] Creating database connection",
|
|
);
|
|
await ipcRenderer.invoke("sqlite-create-connection", {
|
|
database: "timesafari",
|
|
version: 1,
|
|
});
|
|
logger.info(
|
|
"[Main Electron] [IPC:sqlite-create-connection] Database connection created",
|
|
);
|
|
|
|
// Explicitly open the database
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-open] Opening database",
|
|
);
|
|
await ipcRenderer.invoke("sqlite-open", {
|
|
database: "timesafari",
|
|
});
|
|
logger.info(
|
|
"[Main Electron] [IPC:sqlite-open] Database opened successfully",
|
|
);
|
|
|
|
// Verify the database is open
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-is-db-open] Verifying database is open",
|
|
);
|
|
const isOpen = await ipcRenderer.invoke("sqlite-is-db-open", {
|
|
database: "timesafari",
|
|
});
|
|
logger.info(
|
|
"[Main Electron] [IPC:sqlite-is-db-open] Database open status:",
|
|
{ isOpen },
|
|
);
|
|
|
|
if (!isOpen) {
|
|
throw new Error("Database failed to open");
|
|
}
|
|
|
|
// Now execute the test query
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-query] Executing test query",
|
|
);
|
|
const testQuery = (await ipcRenderer.invoke("sqlite-query", {
|
|
database: "timesafari",
|
|
statement: "SELECT 1 as test;", // Safe test query
|
|
})) as SQLiteQueryResult;
|
|
logger.info(
|
|
"[Main Electron] [IPC:sqlite-query] Test query successful:",
|
|
{
|
|
hasResults: Boolean(testQuery?.values),
|
|
resultCount: testQuery?.values?.length,
|
|
},
|
|
);
|
|
|
|
// Signal that SQLite is ready - database stays open
|
|
logger.debug(
|
|
"[Main Electron] [IPC:sqlite-status] Sending SQLite ready status",
|
|
);
|
|
await ipcRenderer.invoke("sqlite-status", {
|
|
status: "ready",
|
|
database: "timesafari",
|
|
timestamp: Date.now(),
|
|
});
|
|
logger.info(
|
|
"[Main Electron] SQLite ready status sent, database connection maintained",
|
|
);
|
|
|
|
// Remove the close operations - database stays open for component use
|
|
// Database will be closed during app shutdown
|
|
} catch (error) {
|
|
logger.error(
|
|
"[Main Electron] [IPC:*] SQLite test operation failed:",
|
|
{
|
|
error,
|
|
lastOperation: "sqlite-test-query",
|
|
database: "timesafari",
|
|
},
|
|
);
|
|
|
|
// Try to close everything if anything was opened
|
|
try {
|
|
logger.debug(
|
|
"[Main Electron] [IPC:cleanup] Attempting database cleanup after error",
|
|
);
|
|
await ipcRenderer
|
|
.invoke("sqlite-close", {
|
|
database: "timesafari",
|
|
})
|
|
.catch((closeError) => {
|
|
logger.warn(
|
|
"[Main Electron] [IPC:sqlite-close] Failed to close database during cleanup:",
|
|
closeError,
|
|
);
|
|
});
|
|
|
|
await ipcRenderer
|
|
.invoke("sqlite-close-connection", {
|
|
database: "timesafari",
|
|
})
|
|
.catch((closeError) => {
|
|
logger.warn(
|
|
"[Main Electron] [IPC:sqlite-close-connection] Failed to close connection during cleanup:",
|
|
closeError,
|
|
);
|
|
});
|
|
|
|
logger.info(
|
|
"[Main Electron] [IPC:cleanup] Database cleanup completed after error",
|
|
);
|
|
} catch (closeError) {
|
|
logger.error(
|
|
"[Main Electron] [IPC:cleanup] Failed to cleanup database:",
|
|
{
|
|
error: closeError,
|
|
database: "timesafari",
|
|
},
|
|
);
|
|
}
|
|
// Don't reject here - we still want to wait for the ready signal
|
|
}
|
|
}
|
|
})
|
|
.catch((error: Error) => {
|
|
logger.error(
|
|
"[Main Electron] [IPC:sqlite-is-available] Failed to check SQLite availability:",
|
|
{
|
|
error,
|
|
channel: "sqlite-is-available",
|
|
},
|
|
);
|
|
// Don't reject here - wait for either ready signal or timeout
|
|
});
|
|
};
|
|
|
|
// Start checking for bridge
|
|
checkElectronBridge();
|
|
};
|
|
|
|
// Start first initialization attempt
|
|
attemptInitialization();
|
|
});
|
|
|
|
// Wait for SQLite to be ready before initializing router and mounting app
|
|
sqliteReady
|
|
.then(async () => {
|
|
logger.info("[Main Electron] SQLite ready, initializing router...");
|
|
|
|
// Initialize router after SQLite is ready
|
|
const router = await import("./router").then((m) => m.default);
|
|
app.use(router);
|
|
logger.info("[Main Electron] Router initialized");
|
|
|
|
// Now mount the app
|
|
logger.info("[Main Electron] Mounting app...");
|
|
app.mount("#app");
|
|
logger.info("[Main Electron] App mounted successfully");
|
|
})
|
|
.catch((error) => {
|
|
logger.error(
|
|
"[Main Electron] Failed to initialize SQLite:",
|
|
error instanceof Error ? error.message : "Unknown error",
|
|
);
|
|
// Show error to user with retry option
|
|
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%; z-index: 9999;";
|
|
errorDiv.innerHTML = `
|
|
<h2>Failed to Initialize Application</h2>
|
|
<p>There was an error initializing the database. This could be due to:</p>
|
|
<ul style="text-align: left; margin: 10px 0;">
|
|
<li>Database file is locked by another process</li>
|
|
<li>Insufficient permissions to access the database</li>
|
|
<li>Database file is corrupted</li>
|
|
</ul>
|
|
<p>Error details: ${error instanceof Error ? error.message : "Unknown error"}</p>
|
|
<div style="margin-top: 15px;">
|
|
<button onclick="window.location.reload()" style="margin: 0 5px; padding: 8px 16px; background: #c62828; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
|
Retry
|
|
</button>
|
|
<button onclick="window.electron.ipcRenderer.send('sqlite-status', { action: 'reset' })" style="margin: 0 5px; padding: 8px 16px; background: #f57c00; color: white; border: none; border-radius: 4px; cursor: pointer;">
|
|
Reset Database
|
|
</button>
|
|
</div>
|
|
`;
|
|
document.body.appendChild(errorDiv);
|
|
});
|
|
|