From c329118e50bbade04f89add14db5993888fa4bf6 Mon Sep 17 00:00:00 2001 From: Pas <74743263+Pasithea0@users.noreply.github.com> Date: Mon, 17 Nov 2025 13:30:25 -0700 Subject: [PATCH] Handle device name decryption errors gracefully Added error handling for device name decryption in Avatar, LinksDropdown, Settings, and DeviceListPart components. If decryption fails, a fallback 'Unknown device' message is shown using a new translation key. This improves user experience by preventing crashes or blank fields when device name decryption fails. --- src/assets/locales/en.json | 1 + src/components/Avatar.tsx | 14 +++++++++++++- src/components/LinksDropdown.tsx | 12 +++++++++++- src/pages/Settings.tsx | 10 ++++++++-- src/pages/parts/settings/DeviceListPart.tsx | 19 ++++++++++++++----- 5 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/assets/locales/en.json b/src/assets/locales/en.json index eb878262..d5d7e2bd 100644 --- a/src/assets/locales/en.json +++ b/src/assets/locales/en.json @@ -981,6 +981,7 @@ }, "devices": { "deviceNameLabel": "Device name", + "unknownDevice": "Unknown device, error decrypting name", "failed": "Failed to load sessions", "removeDevice": "Remove", "title": "Devices" diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index ec883def..7772abec 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,5 +1,6 @@ import classNames from "classnames"; import { useMemo } from "react"; +import { useTranslation } from "react-i18next"; import { base64ToBuffer, decryptData } from "@/backend/accounts/crypto"; import { Icon, Icons } from "@/components/Icon"; @@ -55,11 +56,22 @@ export function UserAvatar(props: { : null, [auth], ); + const { t } = useTranslation(); if (!auth.account || auth.account === null) return null; const deviceName = bufferSeed - ? decryptData(auth.account.deviceName, bufferSeed) + ? (() => { + try { + return decryptData(auth.account.deviceName, bufferSeed); + } catch (error) { + console.warn( + "Failed to decrypt device name in Avatar, using fallback:", + error, + ); + return t("settings.account.devices.unknownDevice"); + } + })() : "..."; return ( diff --git a/src/components/LinksDropdown.tsx b/src/components/LinksDropdown.tsx index 4300d6fa..d4338b4b 100644 --- a/src/components/LinksDropdown.tsx +++ b/src/components/LinksDropdown.tsx @@ -257,7 +257,17 @@ export function LinksDropdown(props: { children: React.ReactNode }) { {deviceName && bufferSeed ? ( - {decryptData(deviceName, bufferSeed)} + {(() => { + try { + return decryptData(deviceName, bufferSeed); + } catch (error) { + console.warn( + "Failed to decrypt device name in LinksDropdown, using fallback:", + error, + ); + return t("settings.account.unknownDevice"); + } + })()} ) : ( diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index e85d4364..1b1987d5 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -490,8 +490,14 @@ export function SettingsPage() { const updateNickname = useAuthStore((s) => s.setAccountNickname); const decryptedName = useMemo(() => { if (!account) return ""; - return decryptData(account.deviceName, base64ToBuffer(account.seed)); - }, [account]); + try { + return decryptData(account.deviceName, base64ToBuffer(account.seed)); + } catch (error) { + console.warn("Failed to decrypt device name, using fallback:", error); + // Return a fallback device name if decryption fails + return t("settings.account.devices.unknownDevice"); + } + }, [account, t]); const backendUrl = useBackendUrl(); diff --git a/src/pages/parts/settings/DeviceListPart.tsx b/src/pages/parts/settings/DeviceListPart.tsx index 6f12d66e..1917ae69 100644 --- a/src/pages/parts/settings/DeviceListPart.tsx +++ b/src/pages/parts/settings/DeviceListPart.tsx @@ -75,7 +75,16 @@ export function DeviceListPart(props: { const deviceListSorted = useMemo(() => { if (!seed) return []; let list = sessions.map((session) => { - const decryptedName = decryptData(session.device, base64ToBuffer(seed)); + let decryptedName: string; + try { + decryptedName = decryptData(session.device, base64ToBuffer(seed)); + } catch (error) { + console.warn( + `Failed to decrypt device name for session ${session.id}:`, + error, + ); + decryptedName = t("settings.account.devices.unknownDevice"); + } return { current: session.id === currentSessionId, id: session.id, @@ -88,7 +97,7 @@ export function DeviceListPart(props: { return a.name.localeCompare(b.name); }); return list; - }, [seed, sessions, currentSessionId]); + }, [seed, sessions, currentSessionId, t]); if (!seed) return null; return ( @@ -96,10 +105,10 @@ export function DeviceListPart(props: { {t("settings.account.devices.title")} - {props.error ? ( -

{t("settings.account.devices.failed")}

- ) : props.loading ? ( + {props.loading ? ( + ) : props.error && deviceListSorted.length === 0 ? ( +

{t("settings.account.devices.failed")}

) : (
{deviceListSorted.map((session) => (