mirror of
https://github.com/zadam/trilium.git
synced 2026-01-18 20:44:26 +01:00
265 lines
8.2 KiB
TypeScript
265 lines
8.2 KiB
TypeScript
// public/local-server-worker.js
|
|
// This will eventually import your core server and DB provider.
|
|
// import { createCoreServer } from "@trilium/core"; (bundled)
|
|
|
|
import BrowserExecutionContext from './lightweight/cls_provider';
|
|
import BrowserCryptoProvider from './lightweight/crypto_provider';
|
|
import BrowserSqlProvider from './lightweight/sql_provider';
|
|
|
|
// 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");
|
|
|
|
// Shared SQL provider instance
|
|
const sqlProvider = new BrowserSqlProvider();
|
|
|
|
// Core module and initialization state
|
|
let coreModule: typeof import("@triliumnext/core") | null = null;
|
|
let initPromise: Promise<void> | null = null;
|
|
let initError: Error | null = null;
|
|
|
|
/**
|
|
* Initialize SQLite WASM and load the core module.
|
|
* This happens once at worker startup.
|
|
*/
|
|
async function initialize(): Promise<void> {
|
|
if (initPromise) {
|
|
return initPromise; // Already initializing
|
|
}
|
|
if (initError) {
|
|
throw initError; // Failed before, don't retry
|
|
}
|
|
|
|
initPromise = (async () => {
|
|
try {
|
|
console.log("[Worker] Initializing SQLite WASM...");
|
|
await sqlProvider.initWasm();
|
|
sqlProvider.loadFromMemory();
|
|
console.log("[Worker] Database loaded");
|
|
|
|
console.log("[Worker] Loading @triliumnext/core...");
|
|
coreModule = await import("@triliumnext/core");
|
|
coreModule.initializeCore({
|
|
executionContext: new BrowserExecutionContext(),
|
|
crypto: new BrowserCryptoProvider(),
|
|
dbConfig: {
|
|
provider: sqlProvider,
|
|
isReadOnly: false,
|
|
onTransactionCommit: () => {
|
|
// No-op for now
|
|
},
|
|
onTransactionRollback: () => {
|
|
// No-op for now
|
|
}
|
|
}
|
|
});
|
|
console.log("[Worker] Initialization complete");
|
|
} catch (error) {
|
|
initError = error instanceof Error ? error : new Error(String(error));
|
|
console.error("[Worker] Initialization failed:", initError);
|
|
throw initError;
|
|
}
|
|
})();
|
|
|
|
return initPromise;
|
|
}
|
|
|
|
/**
|
|
* Ensure the worker is initialized before processing requests.
|
|
* Returns the core module if initialization was successful.
|
|
*/
|
|
async function ensureInitialized() {
|
|
await initialize();
|
|
if (!coreModule) {
|
|
throw new Error("Core module not loaded");
|
|
}
|
|
return coreModule;
|
|
}
|
|
|
|
const encoder = new TextEncoder();
|
|
|
|
function jsonResponse(obj: unknown, status = 200, extraHeaders = {}) {
|
|
const body = encoder.encode(JSON.stringify(obj)).buffer;
|
|
return {
|
|
status,
|
|
headers: { "content-type": "application/json; charset=utf-8", ...extraHeaders },
|
|
body
|
|
};
|
|
}
|
|
|
|
function textResponse(text: string, status = 200, extraHeaders = {}) {
|
|
const body = encoder.encode(text).buffer;
|
|
return {
|
|
status,
|
|
headers: { "content-type": "text/plain; charset=utf-8", ...extraHeaders },
|
|
body
|
|
};
|
|
}
|
|
|
|
// Example: your /bootstrap handler placeholder
|
|
async function handleBootstrap() {
|
|
console.log("[Worker] Bootstrap request received");
|
|
|
|
// Try to initialize with timeout
|
|
let dbInfo: Record<string, unknown> = { dbStatus: 'not initialized' };
|
|
|
|
try {
|
|
// Wait for initialization (with timeout)
|
|
await Promise.race([
|
|
ensureInitialized(),
|
|
new Promise((_, reject) => setTimeout(() => reject(new Error("Initialization timeout")), 10000))
|
|
]);
|
|
|
|
// Query the database
|
|
const stmt = sqlProvider.prepare('SELECT * FROM options');
|
|
const rows = stmt.all() as Array<{ name: string; value: string }>;
|
|
const options: Record<string, string> = {};
|
|
for (const row of rows) {
|
|
options[row.name] = row.value;
|
|
}
|
|
|
|
dbInfo = {
|
|
sqliteVersion: sqlProvider.version?.libVersion,
|
|
optionsFromDB: options,
|
|
dbStatus: 'connected'
|
|
};
|
|
} catch (e) {
|
|
console.error("[Worker] Error during bootstrap:", e);
|
|
dbInfo = { dbStatus: 'error', error: String(e) };
|
|
}
|
|
|
|
console.log("[Worker] Sending bootstrap response");
|
|
|
|
// Later: return real globals from your core state/config.
|
|
return jsonResponse({
|
|
assetPath: "./",
|
|
baseApiUrl: "../api/",
|
|
themeCssUrl: null,
|
|
themeUseNextAsBase: "next",
|
|
iconPackCss: "",
|
|
device: "desktop",
|
|
headingStyle: "default",
|
|
layoutOrientation: "vertical",
|
|
platform: "web",
|
|
isElectron: false,
|
|
hasNativeTitleBar: false,
|
|
hasBackgroundEffects: true,
|
|
currentLocale: { id: "en", rtl: false },
|
|
// Add SQLite info for testing
|
|
sqlite: dbInfo
|
|
});
|
|
}
|
|
|
|
interface LocalRequest {
|
|
method: string;
|
|
url: string;
|
|
}
|
|
|
|
// Main dispatch
|
|
async function dispatch(request: LocalRequest) {
|
|
const url = new URL(request.url);
|
|
|
|
console.log("[Worker] Dispatch:", url.pathname);
|
|
// NOTE: your core router will do this later.
|
|
if (request.method === "GET" && url.pathname === "/bootstrap") {
|
|
return handleBootstrap();
|
|
}
|
|
|
|
// Ensure initialization is complete before accessing routes
|
|
const core = await ensureInitialized();
|
|
|
|
if (request.method === "GET" && url.pathname === "/api/options") {
|
|
return jsonResponse(core.routes.optionsApiRoute.getOptions());
|
|
}
|
|
|
|
if (request.method === "GET" && url.pathname === "/api/tree") {
|
|
return jsonResponse(core.routes.treeApiRoute.getTree({
|
|
query: {
|
|
subTreeNoteId: url.searchParams.get("subTreeNoteId") || undefined
|
|
}
|
|
}));
|
|
}
|
|
|
|
if (url.pathname.startsWith("/api/echo")) {
|
|
return jsonResponse({ ok: true, method: request.method, url: request.url });
|
|
}
|
|
|
|
return textResponse("Not found", 404);
|
|
}
|
|
|
|
// Start initialization immediately when the worker loads
|
|
console.log("[Worker] Starting initialization...");
|
|
initialize().catch(err => {
|
|
console.error("[Worker] Initialization failed:", err);
|
|
// Post error to main thread
|
|
self.postMessage({
|
|
type: "WORKER_ERROR",
|
|
error: {
|
|
message: String(err?.message || err),
|
|
stack: err?.stack
|
|
}
|
|
});
|
|
});
|
|
|
|
self.onmessage = async (event) => {
|
|
const msg = event.data;
|
|
if (!msg || msg.type !== "LOCAL_REQUEST") return;
|
|
|
|
const { id, request } = msg;
|
|
console.log("[Worker] Received LOCAL_REQUEST:", id, request.method, request.url);
|
|
|
|
try {
|
|
const response = await dispatch(request);
|
|
console.log("[Worker] Dispatch completed, sending response:", id);
|
|
|
|
// Transfer body back (if any) - use options object for proper typing
|
|
(self as unknown as Worker).postMessage({
|
|
type: "LOCAL_RESPONSE",
|
|
id,
|
|
response
|
|
}, { transfer: response.body ? [response.body] : [] });
|
|
} catch (e) {
|
|
console.error("[Worker] Dispatch error:", e);
|
|
(self as unknown as Worker).postMessage({
|
|
type: "LOCAL_RESPONSE",
|
|
id,
|
|
error: String((e as Error)?.message || e)
|
|
});
|
|
}
|
|
};
|