From 2c822f8007d23d7fcc9597a85ff9d4b15d5bf02d Mon Sep 17 00:00:00 2001 From: Stefan Mogeritsch Date: Mon, 16 Mar 2026 10:26:41 +0100 Subject: [PATCH] refactor: streamline deep link handling and improve sqlite worker initialization - Simplified `DeepLinkHandler` logic by removing redundant return values and enhancing route parsing with `ifBlank()`. - Refactored `sqlite.worker.js` for better modularity and error handling. - Added helper methods for script imports and initialization error management. Signed-off-by: Stefan Mogeritsch --- .../src/jsMain/resources/sqlite.worker.js | 137 ++++++++++-------- .../core/navigation/DeepLinkHandler.kt | 15 +- 2 files changed, 86 insertions(+), 66 deletions(-) diff --git a/frontend/core/local-db/src/jsMain/resources/sqlite.worker.js b/frontend/core/local-db/src/jsMain/resources/sqlite.worker.js index 64b5ee3a..0bf119a1 100644 --- a/frontend/core/local-db/src/jsMain/resources/sqlite.worker.js +++ b/frontend/core/local-db/src/jsMain/resources/sqlite.worker.js @@ -28,10 +28,24 @@ function processMessage(event) { const data = event.data; if (!data) return; + let result; try { - switch (data.action) { + result = executeAction(data); + } catch (err) { + console.error('[sqlite.worker] Error processing message:', err); + postMessage({id: data.id, error: err?.message ?? String(err)}); + return; + } + postMessage({id: data.id, ...result}); +} + +function executeAction(data) { + switch (data.action) { case 'exec': { - if (!data.sql) throw new Error('exec: Missing sql string'); + if (!data.sql) { + postMessage({id: data.id, error: 'exec: Missing sql string'}); + return null; + } const rows = []; db.exec({ sql: data.sql, @@ -39,84 +53,93 @@ function processMessage(event) { rowMode: 'array', callback: (row) => rows.push(row), }); - postMessage({id: data.id, results: {values: rows}}); - break; + return {results: {values: rows}}; } case 'begin_transaction': db.exec('BEGIN TRANSACTION;'); - postMessage({id: data.id, results: []}); - break; + return {results: []}; case 'end_transaction': db.exec('END TRANSACTION;'); - postMessage({id: data.id, results: []}); - break; + return {results: []}; case 'rollback_transaction': db.exec('ROLLBACK TRANSACTION;'); - postMessage({id: data.id, results: []}); - break; + return {results: []}; default: - throw new Error(`Unsupported action: ${data.action}`); - } - } catch (err) { - console.error('[sqlite.worker] Error processing message:', err); - postMessage({id: data.id, error: err?.message ?? String(err)}); + postMessage({id: data.id, error: `Unsupported action: ${data.action}`}); + return null; } } // --- Initialization --- async function init() { - try { - // Load sqlite3.js via importScripts (avoids Webpack bundling issues) - try { - importScripts('sqlite3.js'); - } catch (e) { - throw new Error(`importScripts('sqlite3.js') failed – is the file served at root? ${e.message}`); - } + // Load sqlite3.js via importScripts (avoids Webpack bundling issues) + const loadResult = tryImportScripts(); + if (loadResult !== null) { + handleInitError(loadResult); + return; + } - if (typeof self.sqlite3InitModule !== 'function') { - throw new Error('sqlite3InitModule not found after importScripts – sqlite3.js may be corrupt or wrong version.'); - } + if (typeof self.sqlite3InitModule !== 'function') { + handleInitError('sqlite3InitModule not found after importScripts – sqlite3.js may be corrupt or wrong version.'); + return; + } - // Fetch WASM binary manually so we control the URL and can detect failures early - const wasmResponse = await fetch('sqlite3.wasm'); - if (!wasmResponse.ok) { - throw new Error(`Failed to fetch sqlite3.wasm: ${wasmResponse.status} ${wasmResponse.statusText}`); - } - const wasmBinary = await wasmResponse.arrayBuffer(); + // Fetch WASM binary manually so we control the URL and can detect failures early + let wasmBinary; + try { + const wasmResponse = await fetch('sqlite3.wasm'); + if (!wasmResponse.ok) { + handleInitError(`Failed to fetch sqlite3.wasm: ${wasmResponse.status} ${wasmResponse.statusText}`); + return; + } + wasmBinary = await wasmResponse.arrayBuffer(); + } catch (e) { + handleInitError(`Failed to fetch sqlite3.wasm: ${e.message}`); + return; + } - const sqlite3 = await self.sqlite3InitModule({ - print: console.log, - printErr: console.error, - wasmBinary, - }); + try { + const sqlite3 = await self.sqlite3InitModule({ + print: console.log, + printErr: console.error, + wasmBinary, + }); - const opfsAvailable = 'opfs' in sqlite3; - const dbName = 'app.db'; + const dbName = 'app.db'; + if ('opfs' in sqlite3) { + console.log(`[sqlite.worker] Using persistent OPFS database: ${dbName}`); + db = new sqlite3.oo1.OpfsDb(dbName); + } else { + console.warn('[sqlite.worker] OPFS not available – falling back to in-memory database'); + db = new sqlite3.oo1.DB(dbName); + } - if (opfsAvailable) { - console.log(`[sqlite.worker] Using persistent OPFS database: ${dbName}`); - db = new sqlite3.oo1.OpfsDb(dbName); - } else { - console.warn('[sqlite.worker] OPFS not available – falling back to in-memory database'); - db = new sqlite3.oo1.DB(dbName); - } - - // Flush buffered messages - isReady = true; - console.log(`[sqlite.worker] Ready. Flushing ${messageQueue.length} buffered message(s).`); - while (messageQueue.length > 0) { - processMessage(messageQueue.shift()); - } + // Flush buffered messages + isReady = true; + console.log(`[sqlite.worker] Ready. Flushing ${messageQueue.length} buffered message(s).`); + while (messageQueue.length > 0) { + processMessage(messageQueue.shift()); + } + } catch (e) { + handleInitError(e?.message ?? String(e)); + } +} +function tryImportScripts() { + try { + importScripts('sqlite3.js'); + return null; } catch (e) { - initError = e?.message ?? String(e); - console.error('[sqlite.worker] Initialization failed:', e); + return `importScripts('sqlite3.js') failed – is the file served at root? ${e.message}`; + } +} - // Notify all buffered callers so they don't hang indefinitely - while (messageQueue.length > 0) { +function handleInitError(message) { + initError = message; + console.error('[sqlite.worker] Initialization failed:', message); + while (messageQueue.length > 0) { const queued = messageQueue.shift(); postMessage({id: queued.data?.id, error: `Worker initialization failed: ${initError}`}); - } } } diff --git a/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandler.kt b/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandler.kt index dacbb5e2..b2775dd5 100644 --- a/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandler.kt +++ b/frontend/core/navigation/src/commonMain/kotlin/at/mocode/frontend/core/navigation/DeepLinkHandler.kt @@ -22,10 +22,11 @@ class DeepLinkHandler( fun handleDeepLink(url: String): Boolean { val parsed = parseDeepLink(url) ?: return false - return processDeepLink(parsed) + processDeepLink(parsed) + return true } - private fun processDeepLink(deepLink: DeepLink): Boolean { + private fun processDeepLink(deepLink: DeepLink) { val route = cleanRoute(deepLink.route) // If the route requires auth and the user is missing → redirect to log in @@ -33,20 +34,19 @@ class DeepLinkHandler( val user = currentUserProvider.getCurrentUser() if (user == null) { navigation.navigateTo(config.loginRoute) - return true + return } // Admin section guard: requires ADMIN role if (requiresAdmin(route)) { val isAdmin = user.roles.contains(AppRoles.ADMIN) if (!isAdmin) { navigation.navigateTo(Routes.HOME) - return true + return } } } navigation.navigateTo(deepLink.route) - return true } private fun parseDeepLink(url: String): DeepLink? { @@ -62,7 +62,7 @@ class DeepLinkHandler( val parts = withoutScheme.split("/") if (parts.isEmpty() || parts[0] != config.host) return null val path = "/" + parts.drop(1).joinToString("/") - val route = if (path.isBlank()) Routes.HOME else path + val route = path.ifBlank { Routes.HOME } return DeepLink(DeepLinkType.CUSTOM_SCHEME, route, url) } @@ -86,7 +86,4 @@ class DeepLinkHandler( } private fun requiresAdmin(route: String): Boolean = route.startsWith("${Routes.Admin.ROOT}/") - - fun generateDeepLink(route: String, useCustomScheme: Boolean = true): String = - if (useCustomScheme) "${config.scheme}://${config.host}$route" else "https://${config.allowedDomains.first()}$route" }