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) => (