From 0da777683fa9215d3022a2b399c322b9d017cc58 Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Wed, 8 Apr 2026 03:15:42 -0300 Subject: [PATCH] feat(login): show Node.js version incompatibility warning on login page When running OmniRoute with Node.js >=24, the better-sqlite3 native module fails to load, causing a confusing 'not a valid Win32 application' or 'Module did not self-register' error at the login screen. This change adds proactive Node.js version detection: - API: /api/settings/require-login now returns nodeVersion and nodeCompatible fields, computed before any SQLite access so they work even when the DB fails - UI: A prominent red warning banner appears on the login page when an incompatible Node.js version is detected, showing the current version and instructions to install Node 22 LTS via nvm - i18n: Added 4 new translation keys for the warning banner Closes #1060, Closes #1040 --- src/app/api/settings/require-login/route.ts | 12 ++++- src/app/login/page.tsx | 51 +++++++++++++++++++-- src/i18n/messages/en.json | 6 ++- 3 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/app/api/settings/require-login/route.ts b/src/app/api/settings/require-login/route.ts index 547ac96859..6b33fadb21 100644 --- a/src/app/api/settings/require-login/route.ts +++ b/src/app/api/settings/require-login/route.ts @@ -4,17 +4,25 @@ import bcrypt from "bcryptjs"; import { updateRequireLoginSchema } from "@/shared/validation/schemas"; import { isValidationFailure, validateBody } from "@/shared/validation/helpers"; +// Node.js compatibility check — better-sqlite3 requires Node <24 +function getNodeCompatibility() { + const nodeVersion = process.version; + const major = parseInt(nodeVersion.replace("v", "").split(".")[0], 10); + return { nodeVersion, nodeCompatible: major >= 18 && major < 24 }; +} + export async function GET() { + const nodeInfo = getNodeCompatibility(); try { const settings = await getSettings(); const requireLogin = settings.requireLogin !== false; const hasPassword = !!settings.password || !!process.env.INITIAL_PASSWORD; const setupComplete = !!settings.setupComplete; - return NextResponse.json({ requireLogin, hasPassword, setupComplete }); + return NextResponse.json({ requireLogin, hasPassword, setupComplete, ...nodeInfo }); } catch (error) { console.error("[API] Error fetching require-login settings:", error); return NextResponse.json( - { requireLogin: true, hasPassword: true, setupComplete: true }, + { requireLogin: true, hasPassword: true, setupComplete: true, ...nodeInfo }, { status: 200 } ); } diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index e17e0c53b3..edd0f8228a 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -14,6 +14,8 @@ export default function LoginPage() { const [hasPassword, setHasPassword] = useState(null); const [setupComplete, setSetupComplete] = useState(null); const [mounted, setMounted] = useState(false); + const [nodeVersion, setNodeVersion] = useState(null); + const [nodeCompatible, setNodeCompatible] = useState(true); const router = useRouter(); useEffect(() => { @@ -31,6 +33,8 @@ export default function LoginPage() { if (res.ok) { const data = await res.json(); + if (data.nodeVersion) setNodeVersion(data.nodeVersion); + if (data.nodeCompatible === false) setNodeCompatible(false); if (data.requireLogin === false) { router.push("/dashboard"); router.refresh(); @@ -83,9 +87,39 @@ export default function LoginPage() { } }; + const nodeWarningBanner = !nodeCompatible && nodeVersion ? ( +
+
+
+
+ error +
+
+

{t("nodeIncompatibleTitle")}

+

+ {t("nodeIncompatibleDesc", { version: nodeVersion })} +

+
+
+ terminal + {t("nodeIncompatibleFixLabel")} +
+ nvm install 22 && nvm use 22 +
+

+ info + {t("nodeIncompatibleHint")} +

+
+
+
+
+ ) : null; + if (hasPassword === null || setupComplete === null) { return ( -
+
+ {nodeWarningBanner}
@@ -99,7 +133,8 @@ export default function LoginPage() { if (!hasPassword && !setupComplete) { return ( -
+
+ {nodeWarningBanner}
@@ -136,7 +171,8 @@ export default function LoginPage() { if (!hasPassword && setupComplete) { return ( -
+
+ {nodeWarningBanner}
@@ -174,7 +210,13 @@ export default function LoginPage() { } return ( -
+
+ {nodeWarningBanner && ( +
+ {nodeWarningBanner} +
+ )} +
+
); } diff --git a/src/i18n/messages/en.json b/src/i18n/messages/en.json index ff5a6d501b..bb36fe5ecb 100644 --- a/src/i18n/messages/en.json +++ b/src/i18n/messages/en.json @@ -2709,7 +2709,11 @@ "waitingForOpenAIAuthorization": "Waiting for OpenAI authorization...", "waitingForAntigravityAuthorization": "Waiting for Antigravity authorization...", "waitingForQoderAuthorization": "Waiting for Qoder authorization...", - "exchangingCodeForTokens": "Exchanging code for tokens..." + "exchangingCodeForTokens": "Exchanging code for tokens...", + "nodeIncompatibleTitle": "Incompatible Node.js Version", + "nodeIncompatibleDesc": "You are running Node.js {version}, which is not compatible with OmniRoute. The native SQLite module (better-sqlite3) requires Node.js 18–22 LTS.", + "nodeIncompatibleFixLabel": "Fix: install Node.js 22 LTS", + "nodeIncompatibleHint": "OmniRoute requires Node.js ≥18 and <24. Node 22 LTS is recommended for stability." }, "landing": { "brandName": "OmniRoute",