diff --git a/client/App.tsx b/client/App.tsx index f16d5e6..a60420a 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -1,6 +1,10 @@ import React from "react"; -import { StyleSheet } from "react-native"; -import { NavigationContainer } from "@react-navigation/native"; +import { Linking, Platform, StyleSheet } from "react-native"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { + NavigationContainer, + type InitialState, +} from "@react-navigation/native"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { KeyboardProvider } from "react-native-keyboard-controller"; import { SafeAreaProvider } from "react-native-safe-area-context"; @@ -12,8 +16,67 @@ import { queryClient } from "@/lib/query-client"; import RootStackNavigator from "@/navigation/RootStackNavigator"; import { ErrorBoundary } from "@/components/ErrorBoundary"; import { AuthProvider } from "@/contexts/AuthContext"; +import { + AUTH_USER_CACHE_KEY, + parseCachedAuthUser, +} from "@/lib/auth-cache-core"; + +const NAVIGATION_STATE_KEY = "@ironlog/navigation_state_v1"; + +type RootNavigationState = { + index?: number; + routes?: { name?: string }[]; +}; + +function getActiveRootRouteName(state: RootNavigationState | undefined) { + if (!state?.routes?.length) return undefined; + const index = state.index ?? 0; + return state.routes[index]?.name; +} export default function App() { + const [navigationReady, setNavigationReady] = React.useState( + Platform.OS === "web", + ); + const [initialNavigationState, setInitialNavigationState] = + React.useState(); + + React.useEffect(() => { + if (navigationReady) return; + + async function restoreNavigationState() { + try { + const initialUrl = await Linking.getInitialURL(); + if (initialUrl) return; + + const cachedAuthUser = parseCachedAuthUser( + await AsyncStorage.getItem(AUTH_USER_CACHE_KEY), + ); + if (!cachedAuthUser) { + await AsyncStorage.removeItem(NAVIGATION_STATE_KEY); + return; + } + + const storedState = await AsyncStorage.getItem(NAVIGATION_STATE_KEY); + if (!storedState) return; + + try { + setInitialNavigationState(JSON.parse(storedState) as InitialState); + } catch { + await AsyncStorage.removeItem(NAVIGATION_STATE_KEY); + } + } finally { + setNavigationReady(true); + } + } + + restoreNavigationState(); + }, [navigationReady]); + + if (!navigationReady) { + return null; + } + return ( @@ -21,7 +84,30 @@ export default function App() { - + { + if (Platform.OS === "web") return; + + if (getActiveRootRouteName(state) !== "Main") { + AsyncStorage.removeItem(NAVIGATION_STATE_KEY).catch( + (error) => + console.error( + "Error clearing navigation state:", + error, + ), + ); + return; + } + + AsyncStorage.setItem( + NAVIGATION_STATE_KEY, + JSON.stringify(state), + ).catch((error) => + console.error("Error saving navigation state:", error), + ); + }} + > diff --git a/client/contexts/AuthContext.tsx b/client/contexts/AuthContext.tsx index aef21d4..d774b39 100644 --- a/client/contexts/AuthContext.tsx +++ b/client/contexts/AuthContext.tsx @@ -6,7 +6,13 @@ import React, { useCallback, ReactNode, } from "react"; +import type { User } from "@supabase/supabase-js"; import { supabase } from "@/lib/supabase"; +import { + clearCachedAuthUser, + readCachedAuthUser, + writeCachedAuthUser, +} from "@/lib/auth-cache"; import { getCurrentProfile, upsertCurrentProfile } from "@/lib/profile"; import { flushQueue } from "@/lib/write-queue"; @@ -20,6 +26,8 @@ export interface AuthUser { interface AuthContextType { user: AuthUser | null; isLoading: boolean; + authHydrated: boolean; + isCheckingAuth: boolean; isAuthenticated: boolean; login: (email: string, password: string) => Promise; signup: ( @@ -37,42 +45,120 @@ interface AuthContextType { const AuthContext = createContext(undefined); +function toFallbackAuthUser(user: User): AuthUser { + return { + id: user.id, + email: user.email ?? "", + displayName: + typeof user.user_metadata.displayName === "string" + ? user.user_metadata.displayName + : "Athlete", + units: "lbs", + }; +} + +async function clearCachedAuthUserSafely() { + try { + await clearCachedAuthUser(); + } catch (error) { + console.error("Error clearing cached auth user:", error); + } +} + +async function writeCachedAuthUserSafely(user: AuthUser) { + try { + await writeCachedAuthUser(user); + } catch (error) { + console.error("Error caching auth user:", error); + } +} + export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); - const [isLoading, setIsLoading] = useState(true); + const [authHydrated, setAuthHydrated] = useState(false); + const [isCheckingAuth, setIsCheckingAuth] = useState(false); const refreshUser = useCallback(async () => { + setIsCheckingAuth(true); try { + const { + data: { session }, + } = await supabase.auth.getSession(); + + if (!session) { + await clearCachedAuthUserSafely(); + setUser(null); + return; + } + const { data, error } = await supabase.auth.getUser(); if (error || !data.user) { + await clearCachedAuthUserSafely(); + await supabase.auth.signOut(); setUser(null); return; } - const profile = await getCurrentProfile(data.user); + let profile: AuthUser; + try { + profile = await getCurrentProfile(data.user); + } catch (profileError) { + console.error("Error refreshing profile:", profileError); + setUser((currentUser) => currentUser ?? toFallbackAuthUser(data.user)); + return; + } + + await writeCachedAuthUserSafely(profile); setUser(profile); flushQueue().catch((e) => console.error("Queue flush error:", e)); } catch (error) { console.error("Error refreshing user:", error); + await clearCachedAuthUserSafely(); setUser(null); } finally { - setIsLoading(false); + setIsCheckingAuth(false); } }, []); useEffect(() => { - refreshUser(); + let active = true; + + async function hydrateCachedUser() { + try { + const cachedUser = await readCachedAuthUser(); + if (!active) return; + setUser(cachedUser); + } catch (error) { + console.error("Error hydrating cached auth user:", error); + if (!active) return; + setUser(null); + } finally { + if (active) { + setAuthHydrated(true); + refreshUser(); + } + } + } + + hydrateCachedUser(); const { data: { subscription }, - } = supabase.auth.onAuthStateChange(() => { + } = supabase.auth.onAuthStateChange((event, session) => { setTimeout(() => { + if (event === "SIGNED_OUT" || !session) { + clearCachedAuthUserSafely(); + setUser(null); + return; + } + refreshUser(); }, 0); }); return () => { + active = false; subscription.unsubscribe(); }; }, [refreshUser]); @@ -89,7 +175,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (data.user) { const profile = await getCurrentProfile(data.user); + await writeCachedAuthUserSafely(profile); setUser(profile); + setAuthHydrated(true); flushQueue().catch((e) => console.error("Queue flush error:", e)); } }, []); @@ -116,7 +204,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { displayName: name, units: "lbs", }); + await writeCachedAuthUserSafely(profile); setUser(profile); + setAuthHydrated(true); } }, [], @@ -127,7 +217,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { if (error) { throw error; } + await clearCachedAuthUserSafely(); setUser(null); + setAuthHydrated(true); }, []); const updateProfile = useCallback( @@ -142,6 +234,7 @@ export function AuthProvider({ children }: { children: ReactNode }) { displayName: updates.displayName ?? user?.displayName, units: updates.units ?? user?.units, }); + await writeCachedAuthUserSafely(userData); setUser(userData); }, [user], @@ -151,7 +244,9 @@ export function AuthProvider({ children }: { children: ReactNode }) { ; + setItem(key: string, value: string): Promise; + removeItem(key: string): Promise; +} + +function isString(value: unknown): value is string { + return typeof value === "string"; +} + +export function parseCachedAuthUser( + value: string | null, +): AuthUserSnapshot | null { + if (!value) return null; + + try { + const parsed = JSON.parse(value) as Partial; + if ( + isString(parsed.id) && + isString(parsed.email) && + isString(parsed.displayName) && + isString(parsed.units) + ) { + return { + id: parsed.id, + email: parsed.email, + displayName: parsed.displayName, + units: parsed.units, + }; + } + } catch { + return null; + } + + return null; +} + +export function serializeAuthUser(user: AuthUserSnapshot): string { + return JSON.stringify({ + id: user.id, + email: user.email, + displayName: user.displayName, + units: user.units, + }); +} + +export async function readCachedAuthUserFromStorage( + storage: KeyValueStorage, + key = AUTH_USER_CACHE_KEY, +): Promise { + const value = await storage.getItem(key); + const user = parseCachedAuthUser(value); + + if (value && !user) { + await storage.removeItem(key); + } + + return user; +} + +export async function writeCachedAuthUserToStorage( + storage: KeyValueStorage, + user: AuthUserSnapshot, + key = AUTH_USER_CACHE_KEY, +): Promise { + await storage.setItem(key, serializeAuthUser(user)); +} + +export async function clearCachedAuthUserFromStorage( + storage: KeyValueStorage, + key = AUTH_USER_CACHE_KEY, +): Promise { + await storage.removeItem(key); +} + +export function getStartupAuthRoute( + authHydrated: boolean, + user: AuthUserSnapshot | null, +): StartupAuthRoute { + if (!authHydrated) return "pending"; + return user ? "main" : "auth"; +} diff --git a/client/lib/auth-cache.ts b/client/lib/auth-cache.ts new file mode 100644 index 0000000..f23ef8f --- /dev/null +++ b/client/lib/auth-cache.ts @@ -0,0 +1,19 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import type { AuthUser } from "@/contexts/AuthContext"; +import { + clearCachedAuthUserFromStorage, + readCachedAuthUserFromStorage, + writeCachedAuthUserToStorage, +} from "@/lib/auth-cache-core"; + +export function readCachedAuthUser(): Promise { + return readCachedAuthUserFromStorage(AsyncStorage); +} + +export function writeCachedAuthUser(user: AuthUser): Promise { + return writeCachedAuthUserToStorage(AsyncStorage, user); +} + +export function clearCachedAuthUser(): Promise { + return clearCachedAuthUserFromStorage(AsyncStorage); +} diff --git a/client/navigation/RootStackNavigator.tsx b/client/navigation/RootStackNavigator.tsx index 467309d..d857f94 100644 --- a/client/navigation/RootStackNavigator.tsx +++ b/client/navigation/RootStackNavigator.tsx @@ -1,12 +1,11 @@ import React from "react"; -import { View, ActivityIndicator, StyleSheet } from "react-native"; import { createNativeStackNavigator } from "@react-navigation/native-stack"; import MainTabNavigator from "@/navigation/MainTabNavigator"; import LoginScreen from "@/screens/LoginScreen"; import SignupScreen from "@/screens/SignupScreen"; import { useAuth } from "@/contexts/AuthContext"; import { useScreenOptions } from "@/hooks/useScreenOptions"; -import { useTheme } from "@/hooks/useTheme"; +import { getStartupAuthRoute } from "@/lib/auth-cache-core"; export type RootStackParamList = { Main: undefined; @@ -34,19 +33,15 @@ function AuthNavigator() { } export default function RootStackNavigator() { - const { isAuthenticated, isLoading } = useAuth(); + const { authHydrated, user } = useAuth(); const screenOptions = useScreenOptions(); - const { theme } = useTheme(); + const startupRoute = getStartupAuthRoute(authHydrated, user); - if (isLoading) { - return ( - - - - ); + if (startupRoute === "pending") { + return null; } - if (!isAuthenticated) { + if (startupRoute === "auth") { return ; } @@ -60,11 +55,3 @@ export default function RootStackNavigator() { ); } - -const styles = StyleSheet.create({ - loading: { - flex: 1, - justifyContent: "center", - alignItems: "center", - }, -}); diff --git a/docs/auth-context.md b/docs/auth-context.md new file mode 100644 index 0000000..e977f18 --- /dev/null +++ b/docs/auth-context.md @@ -0,0 +1,38 @@ +# Auth Launch + +IronLog starts from local state first. Network auth validation is authoritative, +but it no longer blocks the first usable screen. + +The background auth check starts after local hydration, not because the auth +stack rendered. + +## Guarantees + +- No loading screen is shown between local auth hydration and network validation. +- Cached auth stores only profile display fields, not Supabase tokens. +- A failed background auth check clears the cached user and returns to auth. + +## Launch Flow + +```mermaid +flowchart TD + A[App launch] --> B[Hydrate local startup state] + B --> C{Cached user?} + C -- Yes --> D[Show saved app state] + C -- No --> E[Show auth stack] + B --> F[Start one background Supabase auth check] + F --> G{Session and user valid?} + G -- Yes --> H[Refresh profile cache] + H --> I[Flush queued writes] + G -- No --> J[Clear cache and sign out] + J --> K[Stay on or return to auth stack] +``` + +## Code Map + +- `client/App.tsx` restores navigation state after confirming a cached user. +- `client/contexts/AuthContext.tsx` hydrates the cached user, then validates + Supabase auth in the background. +- `client/navigation/RootStackNavigator.tsx` chooses the app or auth stack from + the hydrated local decision. +- `client/lib/auth-cache-core.ts` owns cache parsing and startup route decisions. diff --git a/docs/local-development.md b/docs/local-development.md index 22a7379..42a155b 100644 --- a/docs/local-development.md +++ b/docs/local-development.md @@ -52,6 +52,11 @@ Scan the QR code with your iPhone camera to open in Expo Go. ## Data Flow - Supabase Auth owns signup, login, logout, and session persistence. +- App launch reads a non-token cached auth profile from AsyncStorage first so + the last app/auth shell can render without waiting on Supabase. +- Supabase auth validation runs in the background after local hydration. If the + session is missing or invalid, the cached profile is cleared and the app + routes to login without showing an intermediate loading screen. - `public.profiles` stores display name and units. - Active workout writes commit to `AsyncStorage` first. - Supabase writes are attempted after local persistence and queued when offline diff --git a/package.json b/package.json index 4adafbe..fb40e27 100644 --- a/package.json +++ b/package.json @@ -23,11 +23,11 @@ "@react-navigation/native-stack": "^7.15.1", "@supabase/supabase-js": "2.105.4", "@tanstack/react-query": "^5.100.14", - "expo": "^54.0.34", + "expo": "^54.0.35", "expo-blur": "^15.0.8", "expo-build-properties": "~1.0.10", "expo-constants": "~18.0.13", - "expo-font": "~14.0.11", + "expo-font": "~14.0.12", "expo-glass-effect": "~0.1.10", "expo-haptics": "~15.0.8", "expo-image": "~3.0.11", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 71f39da..40ceefd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -13,7 +13,7 @@ importers: version: 0.4.2 '@expo/vector-icons': specifier: ^15.1.1 - version: 15.1.1(expo-font@14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 15.1.1(expo-font@14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@react-native-async-storage/async-storage': specifier: ^2.2.0 version: 2.2.0(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) @@ -36,50 +36,50 @@ importers: specifier: ^5.100.14 version: 5.100.14(react@19.1.0) expo: - specifier: ^54.0.34 - version: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + specifier: ^54.0.35 + version: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) expo-blur: specifier: ^15.0.8 - version: 15.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 15.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-build-properties: specifier: ~1.0.10 - version: 1.0.10(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) + version: 1.0.10(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) expo-constants: specifier: ~18.0.13 - version: 18.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 18.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) expo-font: - specifier: ~14.0.11 - version: 14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + specifier: ~14.0.12 + version: 14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-glass-effect: specifier: ~0.1.10 - version: 0.1.10(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 0.1.10(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-haptics: specifier: ~15.0.8 - version: 15.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) + version: 15.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) expo-image: specifier: ~3.0.11 - version: 3.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 3.0.11(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-linking: specifier: ~8.0.12 - version: 8.0.12(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 8.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-splash-screen: specifier: ~31.0.13 - version: 31.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3) + version: 31.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3) expo-sqlite: specifier: ^16.0.10 - version: 16.0.10(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + version: 16.0.10(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-status-bar: specifier: ~3.0.9 version: 3.0.9(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) expo-symbols: specifier: ~1.0.8 - version: 1.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 1.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) expo-system-ui: specifier: ~6.0.9 - version: 6.0.9(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 6.0.9(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) expo-web-browser: specifier: ~15.0.11 - version: 15.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + version: 15.0.11(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) fuse.js: specifier: ^7.3.0 version: 7.3.0 @@ -911,8 +911,8 @@ packages: resolution: {integrity: sha512-+Lla7nYSiHZirgK+U/uYzsLv/X+HaJienbD5AKX1UQZHYfWaP+9uuQluRB4GrEVWF0GZ7vEVp/jzaOT9k/SQlg==} engines: {node: '>=0.10.0'} - '@expo/cli@54.0.24': - resolution: {integrity: sha512-5xse1bEgnVUBhOrtttc6xTNJVvjyTRavpzuF0/0nuj+312vfSbk7EiRbG+xJ2pW/iZxnhLPJkFCrPYG0nmheAQ==} + '@expo/cli@54.0.25': + resolution: {integrity: sha512-WnUqIb8oMBhtwSfIqdCHCzcaDIpLNXItRVd5miuvWi4GO0SGo89PSsAkbVJ+LJgcaY+v5rbgMELJS9I/CqOulA==} hasBin: true peerDependencies: expo: '*' @@ -997,6 +997,9 @@ packages: '@expo/json-file@10.0.14': resolution: {integrity: sha512-yWwBFywFv+SxkJp/pIzzA416JVYflNUh7pqQzgaA6nXDqRyK7KfrqVzk8PdUfDnqbBcaZZxpzNssfQZzp5KHrA==} + '@expo/json-file@10.0.16': + resolution: {integrity: sha512-fcVkWEj+hLuP2yt5W0aw6LmDRqSPWDLUSxOMcmFeV+algmIF59sQVKCwB9btjQLd4V6x9N0pISkQEkBubUHrCw==} + '@expo/json-file@10.1.1': resolution: {integrity: sha512-rmkjHrYLdfhGGW1TINHwJ/TIcKgtd+1iV+uTycEB74RWSax6U2klRiXXGgudKH6j2OrDSCm3edYTPukSVZtsIQ==} @@ -1012,8 +1015,8 @@ packages: '@expo/logger@18.5.0': resolution: {integrity: sha512-KYMvfgsgkBQOaWVILokKJBhtuY9My4WozTm+N9tyjNvJ5eg23DhqWIvJpHnneg+KHLJaeDjbtaQ1/YkFreHU5g==} - '@expo/metro-config@54.0.15': - resolution: {integrity: sha512-SqIya4VZ9KHM1S9g+xR0A+QKw1Tfs7Gacx6bQNJ98vs4+O7I5+QP5mHZIB0QSZLUV8opiXebHYTiTu+0OAsIUw==} + '@expo/metro-config@54.0.16': + resolution: {integrity: sha512-3LLb9ZQl0VlqSlsalJ7+CYjfz60PBoSDHvpE1UF71aTM1Nx0Vb4LhXo7bCCC+PYP9q/GPB58LLbIROQ8PjKX2w==} peerDependencies: expo: '*' peerDependenciesMeta: @@ -1052,6 +1055,9 @@ packages: '@expo/plist@0.4.8': resolution: {integrity: sha512-pfNtErGGzzRwHP+5+RqswzPDKkZrx+Cli0mzjQaus1ZWFsog5ibL+nVT3NcporW51o8ggnt7x813vtRbPiyOrQ==} + '@expo/plist@0.4.9': + resolution: {integrity: sha512-MPVpmKGfnQEnrCzgxuXcmPP/y/t6AVm+DcSb2Myp21LKWv1N3l8uFxMggesfF4ixAxkRlGmMMx9GyDC9M+XklQ==} + '@expo/plist@0.5.3': resolution: {integrity: sha512-jz5oPcPDd3fygwVxwSwmO6wodTwm0Qa14NUyPy0ka7H8sFmCtNZUI2+DzVe/EXjOhq1FbEjrwl89gdlWYOnVjQ==} @@ -1986,8 +1992,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-expo@54.0.10: - resolution: {integrity: sha512-wTt7POavLFypLcPW/uC5v8y+mtQKDJiyGLzYCjqr9tx0Qc3vCXcDKk1iCFIj/++Iy5CWhhTflEa7VvVPNWeCfw==} + babel-preset-expo@54.0.11: + resolution: {integrity: sha512-dEpeFDtYEFzmWtWVwvt7sUCZH0fxXPfbJlgXd7XNZSQDa/Ki/hTOj9exMTzqR2oyPHDNcE9VxYCJ4oS6xw4Pjg==} peerDependencies: '@babel/runtime': ^7.20.0 expo: '*' @@ -2719,14 +2725,14 @@ packages: expo: '*' react-native: '*' - expo-file-system@19.0.22: - resolution: {integrity: sha512-l9pgahSc7sJD0bP9vBNeXvZjy8QKDpVHVxWmei/ESQOrzmoj5BidziqLVsyZdxsi+PfdbTtttLTAmddH/JafYA==} + expo-file-system@19.0.23: + resolution: {integrity: sha512-MeGkid9OeNILfT/qonaXHp4f2c15xaB28U/bcN7pqZej0Kx0+6+V7e9ZIXpPHm07zVatxA+QkMTPQEGfmvVOxA==} peerDependencies: expo: '*' react-native: '*' - expo-font@14.0.11: - resolution: {integrity: sha512-ga0q61ny4s/kr4k8JX9hVH69exVSIfcIc19+qZ7gt71Mqtm7xy2c6kwsPTCyhBW2Ro5yXTT8EaZOpuRi35rHbg==} + expo-font@14.0.12: + resolution: {integrity: sha512-QQzunE2Mxk45AsCWm3tK7OpVljbtVnKD58q4/qliev+cbye1IOduUnRIdD+P7DyButw17G9MTX795kgaQiz5hQ==} peerDependencies: expo: '*' react: '*' @@ -2767,8 +2773,8 @@ packages: react: '*' react-native: '*' - expo-modules-autolinking@3.0.25: - resolution: {integrity: sha512-YmHWctJlwvOuLZccg3cOXvSiXVJrPMKl7g2YR0YHWoGL9v2RvcmgaPJWPSLVW+voNEgEPsbo5UmUrAqbnYcBeg==} + expo-modules-autolinking@3.0.26: + resolution: {integrity: sha512-WOaud6UKg16ciCOj8raKcMOoKFMHLXKI29U29yhgu1lf+Y7VxJyCktUcYo6AM+ccZ7zLD1uWZdMtgnpf+95OXA==} hasBin: true expo-modules-core@3.0.30: @@ -2777,8 +2783,8 @@ packages: react: '*' react-native: '*' - expo-server@1.0.6: - resolution: {integrity: sha512-vb5TBtskvEdzYuW79lATXutOEBfW5m6U4EFpNjCVZTnI7S//SAsLQkYEpn+EDfn84m6VQfzSGkIVR6YPaScKFA==} + expo-server@1.0.7: + resolution: {integrity: sha512-mcmyML3oXcqFUXUxtdtCL1O00ztNI2v76d+MdniXRUgHNxIcHZ05zo+DqBaOOT6LQnPk4vA4YHqQl7iGUfRb3g==} engines: {node: '>=20.16.0'} expo-splash-screen@31.0.13: @@ -2821,8 +2827,8 @@ packages: expo: '*' react-native: '*' - expo@54.0.34: - resolution: {integrity: sha512-XkVHguZZDC8BcTQxHAd14/TQFbDp1Wt0Z/KApO9t68Ll5A127hLCPzU+a9gytfCIiyL/V1IpF1vIcOLKEVAoNQ==} + expo@54.0.35: + resolution: {integrity: sha512-E+tXpQwjGm5fK/uwa55p0Xx/kuo5dXDKfVJ95IargTNa5KiFt26lSTXXa9KnHbI4EDLwFD38/xTKZvzPTlGTdg==} hasBin: true peerDependencies: '@expo/dom-webview': '*' @@ -6113,7 +6119,7 @@ snapshots: dependencies: uuid: 8.3.2 - '@expo/cli@54.0.24(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3)': + '@expo/cli@54.0.25(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3)': dependencies: '@0no-co/graphql.web': 1.2.0(graphql@16.8.1) '@expo/code-signing-certificates': 0.0.6 @@ -6124,11 +6130,11 @@ snapshots: '@expo/image-utils': 0.8.14(typescript@5.9.3) '@expo/json-file': 10.1.1 '@expo/metro': 54.2.0 - '@expo/metro-config': 54.0.15(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) + '@expo/metro-config': 54.0.16(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) '@expo/osascript': 2.5.1 '@expo/package-manager': 1.11.1 - '@expo/plist': 0.4.8 - '@expo/prebuild-config': 54.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3) + '@expo/plist': 0.4.9 + '@expo/prebuild-config': 54.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3) '@expo/schema-utils': 0.1.8 '@expo/spawn-async': 1.8.0 '@expo/ws-tunnel': 1.0.6 @@ -6147,8 +6153,8 @@ snapshots: connect: 3.7.0 debug: 4.4.3(supports-color@8.1.1) env-editor: 0.4.2 - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-server: 1.0.6 + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-server: 1.0.7 freeport-async: 2.0.0 getenv: 2.0.0 glob: 13.0.6 @@ -6387,7 +6393,7 @@ snapshots: '@expo/image-utils@0.6.5': dependencies: - '@expo/spawn-async': 1.7.2 + '@expo/spawn-async': 1.8.0 chalk: 4.1.2 fs-extra: 9.0.0 getenv: 1.0.0 @@ -6416,6 +6422,11 @@ snapshots: '@babel/code-frame': 7.29.0 json5: 2.2.3 + '@expo/json-file@10.0.16': + dependencies: + '@babel/code-frame': 7.10.4 + json5: 2.2.3 + '@expo/json-file@10.1.1': dependencies: '@babel/code-frame': 7.29.0 @@ -6443,14 +6454,14 @@ snapshots: '@types/bunyan': 1.8.11 bunyan: 1.8.15 - '@expo/metro-config@54.0.15(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))': + '@expo/metro-config@54.0.16(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))': dependencies: '@babel/code-frame': 7.29.0 '@babel/core': 7.29.0 '@babel/generator': 7.29.1 '@expo/config': 12.0.13 '@expo/env': 2.0.11 - '@expo/json-file': 10.0.14 + '@expo/json-file': 10.0.16 '@expo/metro': 54.2.0 '@expo/spawn-async': 1.8.0 browserslist: 4.28.2 @@ -6467,7 +6478,7 @@ snapshots: postcss: 8.4.49 resolve-from: 5.0.0 optionalDependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - bufferutil - supports-color @@ -6547,6 +6558,12 @@ snapshots: base64-js: 1.5.1 xmlbuilder: 15.1.1 + '@expo/plist@0.4.9': + dependencies: + '@xmldom/xmldom': 0.8.13 + base64-js: 1.5.1 + xmlbuilder: 15.1.1 + '@expo/plist@0.5.3': dependencies: '@xmldom/xmldom': 0.8.13 @@ -6579,7 +6596,7 @@ snapshots: - supports-color - typescript - '@expo/prebuild-config@54.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3)': + '@expo/prebuild-config@54.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3)': dependencies: '@expo/config': 12.0.13 '@expo/config-plugins': 54.0.4 @@ -6588,7 +6605,7 @@ snapshots: '@expo/json-file': 10.1.1 '@react-native/normalize-colors': 0.81.5 debug: 4.4.3(supports-color@8.1.1) - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) resolve-from: 5.0.0 semver: 7.8.0 xml2js: 0.6.0 @@ -6669,11 +6686,11 @@ snapshots: '@expo/turtle-spawn@18.5.0': dependencies: '@expo/logger': 18.5.0 - '@expo/spawn-async': 1.7.2 + '@expo/spawn-async': 1.8.0 - '@expo/vector-icons@15.1.1(expo-font@14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': + '@expo/vector-icons@15.1.1(expo-font@14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)': dependencies: - expo-font: 14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-font: 14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) @@ -7767,7 +7784,7 @@ snapshots: '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) - babel-preset-expo@54.0.10(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-refresh@0.14.2): + babel-preset-expo@54.0.11(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-refresh@0.14.2): dependencies: '@babel/helper-module-imports': 7.28.6 '@babel/plugin-proposal-decorators': 7.29.0(@babel/core@7.29.0) @@ -7794,7 +7811,7 @@ snapshots: resolve-from: 5.0.0 optionalDependencies: '@babel/runtime': 7.29.2 - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - '@babel/core' - supports-color @@ -8701,76 +8718,76 @@ snapshots: exec-async@2.2.0: {} - expo-asset@12.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): + expo-asset@12.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): dependencies: '@expo/image-utils': 0.8.14(typescript@5.9.3) - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-constants: 18.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-constants: 18.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - supports-color - typescript - expo-blur@15.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-blur@15.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - expo-build-properties@1.0.10(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)): + expo-build-properties@1.0.10(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)): dependencies: ajv: 8.20.0 - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) semver: 7.8.0 - expo-constants@18.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-constants@18.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: '@expo/config': 12.0.13 '@expo/env': 2.0.11 - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) transitivePeerDependencies: - supports-color - expo-file-system@19.0.22(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-file-system@19.0.23(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - expo-font@14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-font@14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) fontfaceobserver: 2.3.0 react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - expo-glass-effect@0.1.10(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-glass-effect@0.1.10(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - expo-haptics@15.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)): + expo-haptics@15.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-image@3.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-image@3.0.11(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) optionalDependencies: react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) - expo-keep-awake@15.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react@19.1.0): + expo-keep-awake@15.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react@19.1.0): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 - expo-linking@8.0.12(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-linking@8.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: - expo-constants: 18.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + expo-constants: 18.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) invariant: 2.2.4 react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) @@ -8778,7 +8795,7 @@ snapshots: - expo - supports-color - expo-modules-autolinking@3.0.25: + expo-modules-autolinking@3.0.26: dependencies: '@expo/spawn-async': 1.8.0 chalk: 4.1.2 @@ -8792,20 +8809,20 @@ snapshots: react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - expo-server@1.0.6: {} + expo-server@1.0.7: {} - expo-splash-screen@31.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3): + expo-splash-screen@31.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3): dependencies: - '@expo/prebuild-config': 54.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3) - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + '@expo/prebuild-config': 54.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) transitivePeerDependencies: - supports-color - typescript - expo-sqlite@16.0.10(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): + expo-sqlite@16.0.10(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: await-lock: 2.2.2 - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react: 19.1.0 react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) @@ -8815,47 +8832,47 @@ snapshots: react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) react-native-is-edge-to-edge: 1.3.1(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-symbols@1.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-symbols@1.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) sf-symbols-typescript: 2.2.0 - expo-system-ui@6.0.9(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-system-ui@6.0.9(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native-web@0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: '@react-native/normalize-colors': 0.81.5 debug: 4.4.3(supports-color@8.1.1) - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) optionalDependencies: react-native-web: 0.21.2(react-dom@19.1.0(react@19.1.0))(react@19.1.0) transitivePeerDependencies: - supports-color - expo-web-browser@15.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): + expo-web-browser@15.0.11(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)): dependencies: - expo: 54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo: 54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) react-native: 0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0) - expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): + expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3): dependencies: '@babel/runtime': 7.29.2 - '@expo/cli': 54.0.24(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3) + '@expo/cli': 54.0.25(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(typescript@5.9.3) '@expo/config': 12.0.13 '@expo/config-plugins': 54.0.4 '@expo/devtools': 0.1.8(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@expo/fingerprint': 0.15.5 '@expo/metro': 54.2.0 - '@expo/metro-config': 54.0.15(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) - '@expo/vector-icons': 15.1.1(expo-font@14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + '@expo/metro-config': 54.0.16(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3)) + '@expo/vector-icons': 15.1.1(expo-font@14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) '@ungap/structured-clone': 1.3.1 - babel-preset-expo: 54.0.10(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-refresh@0.14.2) - expo-asset: 12.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) - expo-constants: 18.0.13(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) - expo-file-system: 19.0.22(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) - expo-font: 14.0.11(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) - expo-keep-awake: 15.0.8(expo@54.0.34(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react@19.1.0) - expo-modules-autolinking: 3.0.25 + babel-preset-expo: 54.0.11(@babel/core@7.29.0)(@babel/runtime@7.29.2)(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-refresh@0.14.2) + expo-asset: 12.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3) + expo-constants: 18.0.13(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + expo-file-system: 19.0.23(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0)) + expo-font: 14.0.12(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) + expo-keep-awake: 15.0.8(expo@54.0.35(@babel/core@7.29.0)(graphql@16.8.1)(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0)(typescript@5.9.3))(react@19.1.0) + expo-modules-autolinking: 3.0.26 expo-modules-core: 3.0.30(react-native@0.81.5(@babel/core@7.29.0)(@types/react@19.1.17)(react@19.1.0))(react@19.1.0) pretty-format: 29.7.0 react: 19.1.0 @@ -9149,7 +9166,7 @@ snapshots: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.3.2 - ignore: 5.3.0 + ignore: 5.3.2 merge2: 1.4.1 slash: 3.0.0 @@ -10274,7 +10291,7 @@ snapshots: dependencies: hosted-git-info: 7.0.2 proc-log: 4.2.0 - semver: 7.5.4 + semver: 7.8.0 validate-npm-package-name: 5.0.1 nth-check@2.1.1: diff --git a/tests/auth-cache.test.ts b/tests/auth-cache.test.ts new file mode 100644 index 0000000..796cacd --- /dev/null +++ b/tests/auth-cache.test.ts @@ -0,0 +1,97 @@ +import { test } from "node:test"; +import assert from "node:assert/strict"; +import { + AUTH_USER_CACHE_KEY, + clearCachedAuthUserFromStorage, + getStartupAuthRoute, + parseCachedAuthUser, + readCachedAuthUserFromStorage, + serializeAuthUser, + writeCachedAuthUserToStorage, + type AuthUserSnapshot, + type KeyValueStorage, +} from "../client/lib/auth-cache-core"; + +function createStorage(initial: Record = {}) { + const values = new Map(Object.entries(initial)); + const calls: string[] = []; + + const storage: KeyValueStorage = { + async getItem(key) { + calls.push(`get:${key}`); + return values.get(key) ?? null; + }, + async setItem(key, value) { + calls.push(`set:${key}`); + values.set(key, value); + }, + async removeItem(key) { + calls.push(`remove:${key}`); + values.delete(key); + }, + }; + + return { storage, values, calls }; +} + +const cachedUser: AuthUserSnapshot = { + id: "user-1", + email: "athlete@example.com", + displayName: "Athlete", + units: "lbs", +}; + +test("parseCachedAuthUser accepts complete cached auth users", () => { + assert.deepEqual(parseCachedAuthUser(JSON.stringify(cachedUser)), cachedUser); +}); + +test("parseCachedAuthUser rejects corrupt or incomplete cached auth users", () => { + assert.equal(parseCachedAuthUser("{"), null); + assert.equal( + parseCachedAuthUser(JSON.stringify({ id: "user-1", email: "a@b.com" })), + null, + ); +}); + +test("auth cache storage round-trips only non-token user fields", async () => { + const { storage, values } = createStorage(); + await writeCachedAuthUserToStorage(storage, { + ...cachedUser, + extra: "ignored", + } as AuthUserSnapshot & { extra: string }); + + assert.equal(values.get(AUTH_USER_CACHE_KEY), serializeAuthUser(cachedUser)); + assert.deepEqual(await readCachedAuthUserFromStorage(storage), cachedUser); + + await clearCachedAuthUserFromStorage(storage); + assert.equal(values.has(AUTH_USER_CACHE_KEY), false); +}); + +test("readCachedAuthUserFromStorage clears corrupt cached values", async () => { + const { storage, values, calls } = createStorage({ + [AUTH_USER_CACHE_KEY]: "not-json", + }); + + assert.equal(await readCachedAuthUserFromStorage(storage), null); + assert.equal(values.has(AUTH_USER_CACHE_KEY), false); + assert.deepEqual(calls, [ + `get:${AUTH_USER_CACHE_KEY}`, + `remove:${AUTH_USER_CACHE_KEY}`, + ]); +}); + +test("getStartupAuthRoute prevents auth flash while local hydration is pending", () => { + assert.equal(getStartupAuthRoute(false, null), "pending"); + assert.equal(getStartupAuthRoute(false, cachedUser), "pending"); + assert.equal(getStartupAuthRoute(true, cachedUser), "main"); + assert.equal(getStartupAuthRoute(true, null), "auth"); +}); + +test("local auth hydration only touches storage", async () => { + const { storage, calls } = createStorage({ + [AUTH_USER_CACHE_KEY]: JSON.stringify(cachedUser), + }); + + assert.deepEqual(await readCachedAuthUserFromStorage(storage), cachedUser); + assert.deepEqual(calls, [`get:${AUTH_USER_CACHE_KEY}`]); +});