Fix mobile white screen when localStorage throws on access (#54).
Unit Tests / test (pull_request) Successful in 25s

Guard JWT token reads/writes so auth bootstrap no longer crashes React when mobile browsers block storage.
This commit is contained in:
2026-07-29 04:41:51 -05:00
parent 799d3d591f
commit 4d00131854
+43 -11
View File
@@ -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<void>}
*/
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<void>}
*/
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 {