classify daemon login errors and surface localised dialogs

The daemon returns gRPC errors whose message is a wrapped mgm + JWT
stack (e.g. "invalid jwt token, err: token could not be parsed: ...").
Showing that in a native dialog is unreadable. Connection now maps the
substrings it recognises to a ClientError{code, short, long} so the UI
can render a localised summary plus a Details: block carrying the raw
daemon text. formatErrorMessage on the TS side reads the structured
payload from Wails' Error.cause (or the JSON-stringified Error.message)
and falls back to plain Error.message for callers not yet migrated.

Also bumps Wails to v3.0.0-alpha.95.
This commit is contained in:
Zoltan Papp
2026-05-20 19:13:13 +02:00
parent 341848b1ae
commit d3b660afba
16 changed files with 262 additions and 36 deletions
@@ -16,6 +16,7 @@ import {
WindowManager,
} from "@bindings/services";
import { useAutoSizeWindow } from "@/lib/useAutoSizeWindow";
import { formatErrorMessage } from "@/lib/errors.ts";
const DEFAULT_SECONDS = 360;
const WINDOW_WIDTH = 360;
@@ -63,7 +64,7 @@ export default function SessionAboutToExpireDialog() {
if (busy) return;
setBusy(true);
try {
const start = await Session.RequestExtend({});
const start = await Session.RequestExtend({ hint: "" });
const uri = start.verificationUriComplete || start.verificationUri;
if (uri) {
try {
@@ -80,7 +81,7 @@ export default function SessionAboutToExpireDialog() {
} catch (e) {
await Dialogs.Error({
Title: t("sessionAboutToExpire.extendFailedTitle"),
Message: e instanceof Error ? e.message : String(e),
Message: formatErrorMessage(e),
});
} finally {
setBusy(false);
@@ -6,6 +6,7 @@ import {
} from "@bindings/services";
import type { DebugBundleResult } from "@bindings/services/models.js";
import i18next from "@/lib/i18n";
import { formatErrorMessage } from "@/lib/errors.ts";
import { useProfile } from "@/modules/profile/ProfileContext.tsx";
const NETBIRD_UPLOAD_URL = "https://upload.debug.netbird.io/upload-url";
@@ -158,7 +159,7 @@ export const useDebugBundle = () => {
setStage({ kind: "idle" });
await Dialogs.Error({
Title: i18next.t("settings.error.debugBundleTitle"),
Message: e instanceof Error ? e.message : String(e),
Message: formatErrorMessage(e),
});
} finally {
if (abortRef.current === ctrl) abortRef.current = null;
@@ -11,6 +11,7 @@ import { HelpText } from "@/components/HelpText";
import { Label } from "@/components/Label";
import { loadLanguages } from "@/lib/i18n";
import { cn } from "@/lib/cn";
import { formatErrorMessage } from "@/lib/errors";
// Flags live alongside the rest of the SVG flag library under
// assets/flags/1x1 and are filename-matched to the language code
@@ -91,7 +92,7 @@ export function LanguagePicker() {
} catch (e) {
await Dialogs.Error({
Title: t("settings.error.saveTitle"),
Message: e instanceof Error ? e.message : String(e),
Message: formatErrorMessage(e),
});
} finally {
setBusy(false);
@@ -13,9 +13,7 @@ import type { Config } from "@bindings/services/models.js";
import i18next from "@/lib/i18n";
import { useProfile } from "@/modules/profile/ProfileContext.tsx";
import { SkeletonSettings } from "@/modules/skeletons/SkeletonSettings.tsx";
const errorMessage = (e: unknown) =>
e instanceof Error ? e.message : String(e);
import { formatErrorMessage as errorMessage } from "@/lib/errors.ts";
const SAVE_DEBOUNCE_MS = 400;
@@ -13,6 +13,7 @@ import i18next from "@/lib/i18n";
import { useProfile } from "@/modules/profile/ProfileContext";
import { SectionGroup } from "@/modules/settings/SettingsSection.tsx";
import { cn } from "@/lib/cn";
import { formatErrorMessage } from "@/lib/errors";
const DEFAULT_PROFILE = "default";
@@ -45,7 +46,7 @@ export function SettingsProfiles() {
} catch (e) {
await Dialogs.Error({
Title: title,
Message: e instanceof Error ? e.message : String(e),
Message: formatErrorMessage(e),
});
} finally {
setBusy(false);
@@ -90,7 +91,7 @@ export function SettingsProfiles() {
} catch (e) {
await Dialogs.Error({
Title: i18next.t("profile.error.createTitle"),
Message: e instanceof Error ? e.message : String(e),
Message: formatErrorMessage(e),
});
}
};