From 4d00131854adb9323562dd0d08d41afe914080d8 Mon Sep 17 00:00:00 2001 From: Ryan Westfall Date: Wed, 29 Jul 2026 04:41:51 -0500 Subject: [PATCH] Fix mobile white screen when localStorage throws on access (#54). Guard JWT token reads/writes so auth bootstrap no longer crashes React when mobile browsers block storage. --- llm-fe/src/llm-fe/auth/tokenStorage.js | 54 ++++++++++++++++++++------ 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/llm-fe/src/llm-fe/auth/tokenStorage.js b/llm-fe/src/llm-fe/auth/tokenStorage.js index a4b1fd9..170279e 100644 --- a/llm-fe/src/llm-fe/auth/tokenStorage.js +++ b/llm-fe/src/llm-fe/auth/tokenStorage.js @@ -9,6 +9,21 @@ export const ACCESS_TOKEN_KEY = "access_token"; export const REFRESH_TOKEN_KEY = "refresh_token"; +/** + * Browsers can expose localStorage but throw on access (privacy mode / blocked storage). + * @returns {Storage|null} + */ +function getLocalStorageSafe() { + try { + if (typeof window === "undefined" || !window.localStorage) { + return null; + } + return window.localStorage; + } catch { + return null; + } +} + /** * @returns {{ get?: Function, set?: Function, remove?: Function } | undefined} */ @@ -24,10 +39,15 @@ function getPreferencesPlugin() { * @returns {string|null} */ export function getToken(key) { - if (typeof localStorage === "undefined") { + const storage = getLocalStorageSafe(); + if (!storage) { + return null; + } + try { + return storage.getItem(key); + } catch { return null; } - return localStorage.getItem(key); } /** @@ -51,10 +71,15 @@ export function getRefreshToken() { * @returns {Promise} */ export async function setTokens(access, refresh) { - if (typeof localStorage !== "undefined") { - localStorage.setItem(ACCESS_TOKEN_KEY, access); - if (refresh != null) { - localStorage.setItem(REFRESH_TOKEN_KEY, refresh); + const storage = getLocalStorageSafe(); + if (storage) { + try { + storage.setItem(ACCESS_TOKEN_KEY, access); + if (refresh != null) { + storage.setItem(REFRESH_TOKEN_KEY, refresh); + } + } catch { + /* localStorage unavailable */ } } @@ -75,9 +100,14 @@ export async function setTokens(access, refresh) { * @returns {Promise} */ export async function clearTokens() { - if (typeof localStorage !== "undefined") { - localStorage.removeItem(ACCESS_TOKEN_KEY); - localStorage.removeItem(REFRESH_TOKEN_KEY); + const storage = getLocalStorageSafe(); + if (storage) { + try { + storage.removeItem(ACCESS_TOKEN_KEY); + storage.removeItem(REFRESH_TOKEN_KEY); + } catch { + /* localStorage unavailable */ + } } const Preferences = getPreferencesPlugin(); @@ -110,7 +140,8 @@ export async function hydrateTokensFromNativeStorage() { try { const result = await Preferences.get({ key: ACCESS_TOKEN_KEY }); if (result?.value) { - localStorage.setItem(ACCESS_TOKEN_KEY, result.value); + const storage = getLocalStorageSafe(); + storage?.setItem(ACCESS_TOKEN_KEY, result.value); access = result.value; } } catch { @@ -122,7 +153,8 @@ export async function hydrateTokensFromNativeStorage() { try { const result = await Preferences.get({ key: REFRESH_TOKEN_KEY }); if (result?.value) { - localStorage.setItem(REFRESH_TOKEN_KEY, result.value); + const storage = getLocalStorageSafe(); + storage?.setItem(REFRESH_TOKEN_KEY, result.value); refresh = result.value; } } catch { -- 2.54.0