All files / apps/host/src/contexts AuthContext.tsx

100% Statements 37/37
100% Branches 24/24
100% Functions 10/10
100% Lines 34/34

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                                1x 9x   9x 2x 2x 2x 2x         9x 10x 8x 8x 8x     9x 7x 7x   7x   7x                         9x   10x 10x   10x 1x 1x     9x     7x 7x   7x                   6x                           7x   2x 1x 1x             9x     1x  
import { useCallback, useRef } from 'react';
import type { FirebaseAuthTypes } from '@react-native-firebase/auth';
import { getAuth, reload } from '@react-native-firebase/auth';
 
import { FIREBASE_ERROR_CODES } from '@repo/constants/error';
 
import { CoreAuthProvider, useCoreAuth } from '@repo/core/auth/CoreAuthProvider';
 
import { safeLaunchDarklyOperation } from '@/utils/launchdarklyHelpers';
 
import { launchdarklyService } from '@/services/launchdarklyService';
import { resetTokenRegistrationCache } from '@/services/notification/notificationApi';
import { notificationService } from '@/services/notification/notificationService';
import { queryClient } from '@/services/queryClient';
import { sentryService } from '@/services/sentryService';
 
export const AuthProvider = ({ children }: { children: React.ReactNode }) => {
  const currentUidRef = useRef<string | null>(null);
 
  const clearUserData = useCallback(async () => {
    notificationService.clearTokenCache();
    resetTokenRegistrationCache();
    sentryService.clearAuthenticatedUser();
    await safeLaunchDarklyOperation(() => launchdarklyService.clearUser(), 'clear_user');
  }, []);
 
  // Query keys are not scoped by uid, so anything cached for the previous
  // principal would be served to the next one on the same device.
  const clearCachedServerState = useCallback(async (nextUid: string | null) => {
    if (currentUidRef.current === nextUid) return;
    currentUidRef.current = nextUid;
    await queryClient.cancelQueries().catch(() => {});
    queryClient.clear();
  }, []);
 
  const identifyLaunchDarklyUser = useCallback((firebaseUser: FirebaseAuthTypes.User) => {
    const providerData = firebaseUser.providerData[0];
    const provider = providerData?.providerId?.replace('.com', '') || 'unknown';
 
    safeLaunchDarklyOperation(
      () =>
        launchdarklyService.identify({
          key: firebaseUser.uid,
          email: firebaseUser.email || undefined,
          name: firebaseUser.displayName || undefined,
          custom: {
            provider: provider === 'google' ? 'google' : provider,
            emailVerified: firebaseUser.emailVerified ? 'true' : 'false',
          },
        }),
      'identify',
    );
  }, []);
 
  const handleAuthStateChange = useCallback(
    async (firebaseUser: FirebaseAuthTypes.User | null) => {
      try {
        await clearCachedServerState(firebaseUser?.uid ?? null);
 
        if (!firebaseUser) {
          await clearUserData();
          return;
        }
 
        await reload(firebaseUser);
 
        // Set Sentry user context
        const providerData = firebaseUser.providerData[0];
        const provider = providerData?.providerId?.replace('.com', '') || 'unknown';
 
        sentryService.setAuthenticatedUser({
          id: firebaseUser.uid,
          email: firebaseUser.email,
          username: firebaseUser.displayName,
          provider: provider === 'google' ? 'google' : provider,
          feature: 'auth_state_change',
          additionalContext: {
            google: {
              photoUrl: firebaseUser.photoURL || null,
              emailVerified: firebaseUser.emailVerified,
              providerData: firebaseUser.providerData.map(p => ({
                providerId: p.providerId,
                uid: p.uid,
              })),
            },
            user: {
              creationTime: firebaseUser.metadata.creationTime,
              lastSignInTime: firebaseUser.metadata.lastSignInTime,
              isEmailVerified: firebaseUser.emailVerified,
            },
          },
        });
 
        // Identify user with LaunchDarkly
        identifyLaunchDarklyUser(firebaseUser);
      } catch (error: any) {
        if (error.code === FIREBASE_ERROR_CODES.AUTH_DISABLED) {
          await getAuth().signOut();
          await clearUserData();
        }
      }
    },
    [clearCachedServerState, clearUserData, identifyLaunchDarklyUser],
  );
 
  return <CoreAuthProvider onAuthStateChange={handleAuthStateChange}>{children}</CoreAuthProvider>;
};
 
export const useAuth = () => useCoreAuth();