fix: handle localStorage SecurityError gracefully

- Add try-catch blocks around localStorage access in authLocalStorage
- Return null instead of throwing when localStorage is unavailable
- Prevent endless loading screen in private browsing/strict privacy modes
- Add proper error logging with console.warn
- Fix nullish coalescing operator usage

Fixes #212

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Anton Tranelis 2025-08-04 22:49:40 +02:00
parent 7e0d44dac8
commit 743620280f
3 changed files with 28 additions and 12 deletions

View File

@ -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<MyCollections>('https://api.utopia-lab.org/')

View File

@ -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
}
}

View File

@ -64,10 +64,12 @@ 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) {
console.warn('Failed to load user token:', error)
setLoading(false)
return undefined
} finally {