From 25dc9201bf3feadb14b72a8c1eded79e94ac78f1 Mon Sep 17 00:00:00 2001 From: Elian Doran Date: Tue, 6 Jan 2026 20:27:35 +0200 Subject: [PATCH] feat(client/lightweight): improve error handling --- apps/client/src/local-bridge.ts | 24 ++++++- apps/client/src/local-server-worker.ts | 94 ++++++++++++++++++++++---- 2 files changed, 104 insertions(+), 14 deletions(-) diff --git a/apps/client/src/local-bridge.ts b/apps/client/src/local-bridge.ts index 45dae6d01..f96e39a04 100644 --- a/apps/client/src/local-bridge.ts +++ b/apps/client/src/local-bridge.ts @@ -1,5 +1,5 @@ // public/local-bridge.js -let localWorker = null; +let localWorker: Worker | null = null; const pending = new Map(); export function startLocalServerWorker() { @@ -7,8 +7,30 @@ export function startLocalServerWorker() { localWorker = new Worker(new URL("./local-server-worker.js", import.meta.url), { type: "module" }); + // Handle worker errors during initialization + localWorker.onerror = (event) => { + console.error("[LocalBridge] Worker error:", event); + // Reject all pending requests + for (const [id, resolver] of pending) { + resolver.reject(new Error(`Worker error: ${event.message}`)); + } + pending.clear(); + }; + localWorker.onmessage = (event) => { const msg = event.data; + + // Handle worker error reports + if (msg?.type === "WORKER_ERROR") { + console.error("[LocalBridge] Worker reported error:", msg.error); + // Reject all pending requests with the error + for (const [id, resolver] of pending) { + resolver.reject(new Error(msg.error?.message || "Unknown worker error")); + } + pending.clear(); + return; + } + if (!msg || msg.type !== "LOCAL_RESPONSE") return; const { id, response, error } = msg; diff --git a/apps/client/src/local-server-worker.ts b/apps/client/src/local-server-worker.ts index a8fa4d3da..6fb15ac27 100644 --- a/apps/client/src/local-server-worker.ts +++ b/apps/client/src/local-server-worker.ts @@ -2,9 +2,66 @@ // This will eventually import your core server and DB provider. // import { createCoreServer } from "@trilium/core"; (bundled) -import { resizeMultipleElements } from '@excalidraw/excalidraw/element/resizeElements'; import sqlite3InitModule from '@sqlite.org/sqlite-wasm'; -import { routes } from "@triliumnext/core"; + +// Global error handlers - MUST be set up before any async imports +self.onerror = (message, source, lineno, colno, error) => { + console.error("[Worker] Uncaught error:", message, source, lineno, colno, error); + // Try to notify the main thread about the error + try { + self.postMessage({ + type: "WORKER_ERROR", + error: { + message: String(message), + source, + lineno, + colno, + stack: error?.stack + } + }); + } catch (e) { + // Can't even post message, just log + console.error("[Worker] Failed to report error:", e); + } + return false; // Don't suppress the error +}; + +self.onunhandledrejection = (event) => { + console.error("[Worker] Unhandled rejection:", event.reason); + try { + self.postMessage({ + type: "WORKER_ERROR", + error: { + message: String(event.reason?.message || event.reason), + stack: event.reason?.stack + } + }); + } catch (e) { + console.error("[Worker] Failed to report rejection:", e); + } +}; + +console.log("[Worker] Error handlers installed"); + +// Deferred import for @triliumnext/core to catch initialization errors +let coreModule: typeof import("@triliumnext/core") | null = null; +let coreInitError: Error | null = null; + +async function loadCoreModule() { + if (coreModule) return coreModule; + if (coreInitError) throw coreInitError; + + try { + console.log("[Worker] Loading @triliumnext/core..."); + coreModule = await import("@triliumnext/core"); + console.log("[Worker] @triliumnext/core loaded successfully"); + return coreModule; + } catch (e) { + coreInitError = e instanceof Error ? e : new Error(String(e)); + console.error("[Worker] Failed to load @triliumnext/core:", coreInitError); + throw coreInitError; + } +} const encoder = new TextEncoder(); @@ -151,17 +208,28 @@ async function dispatch(request) { } if (request.method === "GET" && url.pathname === "/api/options") { - // console.log("Options route", routes); - // console.log("Got options request"); - // try { - // // console.log(routes.optionsApiRoute.getOptions()); - // // return jsonResponse(routes.optionsApiRoute.getOptions()); - // } catch (e) { - // return jsonResponse({ ok: true, method: request.method, url: request.url }); - // } - // console.log("Got ", routes.optionsApiRoute.getOptions()); - // // return routes.optionsApiRoute.getOptions(); - return jsonResponse("Hi"); + try { + // Use dynamic import to defer loading until after initialization + const core = await loadCoreModule(); + console.log("[Worker] Options route - core module loaded"); + + // Note: core.routes.optionsApiRoute.getOptions() requires + // initializeCore() to be called first with proper db/crypto config + console.log("[Worker] Available routes:", Object.keys(core.routes)); + + // For now, return a placeholder until core is properly initialized + return jsonResponse({ + message: "Core module loaded successfully", + availableRoutes: Object.keys(core.routes) + }); + } catch (e) { + console.error("[Worker] Error loading core module:", e); + return jsonResponse({ + error: "Failed to load core module", + details: e instanceof Error ? e.message : String(e), + stack: e instanceof Error ? e.stack : undefined + }, 500); + } } if (url.pathname.startsWith("/api/echo")) {