diff --git a/app/src/api/directus.ts b/app/src/api/directus.ts index 7b32f8b42..13bb5f816 100644 --- a/app/src/api/directus.ts +++ b/app/src/api/directus.ts @@ -1,7 +1,8 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ /* eslint-disable @typescript-eslint/require-await */ /* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable no-catch-all/no-catch-all */ +/* eslint-disable no-console */ import { createDirectus, rest, authentication } from '@directus/sdk' import type { AuthenticationData, AuthenticationStorage } from '@directus/sdk' @@ -74,24 +75,36 @@ export const authLocalStorage = (mainKey = 'directus_storage') => ({ // implementation of get, here return json parsed data from localStorage at mainKey (or null if not found) get: async () => { - const data = window.localStorage.getItem(mainKey) - if (data) { - return JSON.parse(data) + try { + const data = window.localStorage.getItem(mainKey) + if (data) { + return JSON.parse(data) + } + return null + } catch (error) { + // Handle SecurityError when localStorage is not available (e.g., in private browsing mode) + console.warn('localStorage not available:', error) + return null } - return null }, // implementation of set, here set the value at mainKey in localStorage, or remove it if value is null set: async (value: AuthenticationData | null) => { - if (!value) { - return window.localStorage.removeItem(mainKey) + try { + if (!value) { + return window.localStorage.removeItem(mainKey) + } + return window.localStorage.setItem(mainKey, JSON.stringify(value)) + } catch (error) { + // Handle SecurityError when localStorage is not available (e.g., in private browsing mode) + console.warn('localStorage not available:', error) + // Silently fail - authentication will fall back to memory-only storage } - return window.localStorage.setItem(mainKey, JSON.stringify(value)) }, }) as AuthenticationStorage export async function getRefreshToken() { const auth = await authLocalStorage().get() - return auth!.refresh_token + return auth?.refresh_token ?? null } export const directusClient = createDirectus('https://api.utopia-lab.org/') diff --git a/app/src/api/userApi.ts b/app/src/api/userApi.ts index c63e1baa1..fc114b6c5 100644 --- a/app/src/api/userApi.ts +++ b/app/src/api/userApi.ts @@ -4,6 +4,7 @@ /* eslint-disable no-console */ /* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-unsafe-return */ +/* eslint-disable no-catch-all/no-catch-all */ import { createUser, passwordRequest, passwordReset, readMe, updateMe } from '@directus/sdk' import { directusClient } from './directus' @@ -78,9 +79,9 @@ export class UserApi { const token = await directusClient.getToken() return token } catch (error: any) { - console.log(error) - if (error.errors[0].message) throw error.errors[0].message - else throw error + console.warn('Failed to get token:', error) + // Don't throw error - return null instead to allow graceful fallback + return null } } diff --git a/lib/src/Components/AppShell/hooks/useTheme.tsx b/lib/src/Components/AppShell/hooks/useTheme.tsx index 367d8fd3d..254996b9f 100644 --- a/lib/src/Components/AppShell/hooks/useTheme.tsx +++ b/lib/src/Components/AppShell/hooks/useTheme.tsx @@ -1,12 +1,14 @@ import { useEffect } from 'react' +import { safeLocalStorage } from '#utils/localStorage' + export const useTheme = (defaultTheme = 'default') => { useEffect(() => { - const savedTheme = localStorage.getItem('theme') + const savedTheme = safeLocalStorage.getItem('theme') const initialTheme = savedTheme ? (JSON.parse(savedTheme) as string) : defaultTheme if (initialTheme !== 'default') { document.documentElement.setAttribute('data-theme', defaultTheme) - localStorage.setItem('theme', JSON.stringify(initialTheme)) + safeLocalStorage.setItem('theme', JSON.stringify(initialTheme)) } }, [defaultTheme]) } diff --git a/lib/src/Components/Auth/useAuth.tsx b/lib/src/Components/Auth/useAuth.tsx index 413e88f58..14cdbe0cf 100644 --- a/lib/src/Components/Auth/useAuth.tsx +++ b/lib/src/Components/Auth/useAuth.tsx @@ -64,10 +64,13 @@ export const AuthProvider = ({ userApi, children }: AuthProviderProps) => { setLoading(false) return me } else { + setLoading(false) return undefined } // eslint-disable-next-line no-catch-all/no-catch-all } catch (error) { + // eslint-disable-next-line no-console + console.warn('Failed to load user token:', error) setLoading(false) return undefined } finally { diff --git a/lib/src/Components/Templates/ThemeControl.tsx b/lib/src/Components/Templates/ThemeControl.tsx index 6ed593fcd..28cc2c7a5 100644 --- a/lib/src/Components/Templates/ThemeControl.tsx +++ b/lib/src/Components/Templates/ThemeControl.tsx @@ -1,5 +1,7 @@ import { useState, useEffect } from 'react' +import { safeLocalStorage } from '#utils/localStorage' + const themes = [ 'default', 'light', @@ -15,14 +17,16 @@ const themes = [ export const ThemeControl = () => { const [theme, setTheme] = useState(() => { - const savedTheme = localStorage.getItem('theme') + const savedTheme = safeLocalStorage.getItem('theme') return savedTheme ? (JSON.parse(savedTheme) as string) : 'default' }) useEffect(() => { if (theme !== 'default') { - localStorage.setItem('theme', JSON.stringify(theme)) - } else localStorage.removeItem('theme') + safeLocalStorage.setItem('theme', JSON.stringify(theme)) + } else { + safeLocalStorage.removeItem('theme') + } document.documentElement.setAttribute('data-theme', theme) }, [theme]) diff --git a/lib/src/Utils/localStorage.ts b/lib/src/Utils/localStorage.ts new file mode 100644 index 000000000..f3590493c --- /dev/null +++ b/lib/src/Utils/localStorage.ts @@ -0,0 +1,35 @@ +/* eslint-disable no-catch-all/no-catch-all */ +/* eslint-disable no-console */ + +/** + * Safe localStorage utility that handles SecurityError gracefully + * when localStorage is not available (e.g., private browsing mode) + */ +export const safeLocalStorage = { + getItem: (key: string): string | null => { + try { + return localStorage.getItem(key) + } catch (error) { + console.warn(`localStorage.getItem failed for key "${key}":`, error) + return null + } + }, + + setItem: (key: string, value: string): void => { + try { + localStorage.setItem(key, value) + } catch (error) { + console.warn(`localStorage.setItem failed for key "${key}":`, error) + // Silently fail - functionality will work in memory-only mode + } + }, + + removeItem: (key: string): void => { + try { + localStorage.removeItem(key) + } catch (error) { + console.warn(`localStorage.removeItem failed for key "${key}":`, error) + // Silently fail + } + }, +}