diff --git a/FastNotes/libs/supabase.ts b/FastNotes/libs/supabase.ts index 346e000..36970a9 100644 --- a/FastNotes/libs/supabase.ts +++ b/FastNotes/libs/supabase.ts @@ -1,24 +1,52 @@ import { createClient } from '@supabase/supabase-js' +import AsyncStorage from '@react-native-async-storage/async-storage' import { deleteItemAsync, getItemAsync, setItemAsync } from 'expo-secure-store' import 'react-native-url-polyfill/auto' -import Constants from "expo-constants" +import Constants from 'expo-constants' import { Platform } from 'react-native' -const ExpoSecureStoreAdapter = { - getItem: (key: string) => { - return getItemAsync(key) +const REFRESH_TOKEN_STORAGE_KEY = 'supabase.auth.refresh-token' + +type PersistedSession = { + refresh_token?: string +} + +const NativeSplitStorageAdapter = { + getItem: async (key: string) => { + return AsyncStorage.getItem(key) }, - setItem: (key: string, value: string) => { - if (value.length > 2048) { - console.warn('Value being stored in SecureStore is larger than 2048 bytes and it may not be stored successfully. In a future SDK version, this call may throw an error.') + + setItem: async (key: string, value: string) => { + await AsyncStorage.setItem(key, value) + + try { + const session = JSON.parse(value) as PersistedSession + + if (session.refresh_token) { + await setItemAsync(REFRESH_TOKEN_STORAGE_KEY, session.refresh_token) + } else { + await deleteItemAsync(REFRESH_TOKEN_STORAGE_KEY) + } + } catch (error) { + console.warn('Stored Supabase session, but could not parse refresh token for SecureStore backup.', error) } - return setItemAsync(key, value) }, - removeItem: (key: string) => { - return deleteItemAsync(key) + + removeItem: async (key: string) => { + await AsyncStorage.removeItem(key) + await deleteItemAsync(REFRESH_TOKEN_STORAGE_KEY) }, } +export const hasSecureRefreshToken = async () => { + if (Platform.OS === 'web') { + return false + } + + const refreshToken = await getItemAsync(REFRESH_TOKEN_STORAGE_KEY) + return Boolean(refreshToken) +} + const extra = (Constants.expoConfig?.extra ?? Constants.manifest?.extra) as { supabaseUrl?: string supabaseKey?: string @@ -27,22 +55,17 @@ const extra = (Constants.expoConfig?.extra ?? Constants.manifest?.extra) as { const supabaseUrl = extra?.supabaseUrl const supabaseAnonKey = extra?.supabaseKey -if(!supabaseUrl || !supabaseAnonKey){ - throw new Error("Cannot read env variables") +if (!supabaseUrl || !supabaseAnonKey) { + throw new Error('Cannot read env variables') } -const storage = ( - Platform.OS === "web" - ? window.localStorage - : ExpoSecureStoreAdapter -) +const storage = Platform.OS === 'web' ? window.localStorage : NativeSplitStorageAdapter export const supabase = createClient(supabaseUrl, supabaseAnonKey, { auth: { - storage: storage as any, - autoRefreshToken: true, - persistSession: true, - detectSessionInUrl: false, - }, - } -) + storage: storage as any, + autoRefreshToken: true, + persistSession: true, + detectSessionInUrl: false, + }, +}) diff --git a/FastNotes/providers/auth-provider.tsx b/FastNotes/providers/auth-provider.tsx index 3abc1a0..9df0df8 100644 --- a/FastNotes/providers/auth-provider.tsx +++ b/FastNotes/providers/auth-provider.tsx @@ -1,5 +1,5 @@ import { AuthContext } from '@/hooks/use-auth-context' -import { supabase } from '@/libs/supabase' +import { hasSecureRefreshToken, supabase } from '@/libs/supabase' import { PropsWithChildren, useEffect, useState } from 'react' const buildClaims = (user?: { id: string; email?: string | null } | null) => @@ -26,6 +26,10 @@ export default function AuthProvider({ children }: PropsWithChildren) { console.error('Error hydrating session:', error) } + if (!session && await hasSecureRefreshToken()) { + console.warn('Found an encrypted Supabase refresh token backup, but fallback session recovery is not implemented.') + } + setClaims(buildClaims(session?.user)) setIsLoading(false) }