Exemples d'Intégration React Native Firebase

Exemples d'intégration React Native Firebase incluant l'authentification, la base de données temps réel, les cloud functions, les notifications push, l'analytics et le cloud storage

Key Facts

Category
Mobile Development
Items
3
Format Families
sample

Sample Overview

Exemples d'intégration React Native Firebase incluant l'authentification, la base de données temps réel, les cloud functions, les notifications push, l'analytics et le cloud storage This sample set belongs to Mobile Development and can be used to test related workflows inside Elysia Tools.

💻 Authentification React Native Firebase typescript

🟢 simple

Implémenter une authentification Firebase complète incluant email/mot de passe, login social, authentification biométrique et gestion de session sécurisée

// firebase.config.ts
import { FirebaseOptions } from '@firebase/app';

const firebaseConfig: FirebaseOptions = {
  apiKey: "your-api-key",
  authDomain: "your-project.firebaseapp.com",
  projectId: "your-project-id",
  storageBucket: "your-project.appspot.com",
  messagingSenderId: "1234567890",
  appId: "your-app-id",
  measurementId: "your-measurement-id"
};

const defaultExport_firebaseConfig = firebaseConfig;

// src/services/AuthService.ts
import auth, {
  FirebaseAuthTypes,
  getAuth,
  signInWithEmailAndPassword,
  createUserWithEmailAndPassword,
  signOut,
  sendPasswordResetEmail,
  updatePassword,
  User,
  AuthCredential,
  GoogleAuthProvider,
  FacebookAuthProvider,
  TwitterAuthProvider,
  OAuthProvider,
  updateProfile,
  linkWithCredential,
  unlink as unlinkProvider
} from '@react-native-firebase/auth';
import { GoogleSignin } from '@react-native-google-signin/google-signin';
import { LoginManager, AccessToken } from 'react-native-fbsdk-next';
import { TwitterAuth } from '@react-native-firebase/auth';
import appleAuth, {
  AppleAuthRequestOperation,
  AppleAuthRequestScope,
  AppleAuthCredentialState,
} from '@invertase/react-native-apple-authentication';
import { TouchID, TouchIDResponse } from 'react-native-touch-id';
import { BiometryTypes } from 'react-native-biometrics';
import AsyncStorage from '@react-native-async-storage/async-storage';

export interface UserProfile {
  uid: string;
  email: string;
  displayName: string;
  photoURL?: string;
  emailVerified: boolean;
  createdAt: string;
  lastLoginAt: string;
  providers: string[];
  phoneNumber?: string;
}

export interface AuthError {
  code: string;
  message: string;
  nativeErrorMessage?: string;
}

export interface BiometricConfig {
  title: string;
  subtitle?: string;
  description?: string;
  fallbackLabel?: string;
  allowDeviceCredentials?: boolean;
}

class AuthService {
  private static instance: AuthService;
  private currentUser: User | null = null;
  private authListeners: ((user: User | null) => void)[] = [];

  private constructor() {
    this.initializeAuth();
  }

  static getInstance(): AuthService {
    if (!AuthService.instance) {
      AuthService.instance = new AuthService();
    }
    return AuthService.instance;
  }

  private initializeAuth(): void {
    // Set up authentication state listener
    auth().onAuthStateChanged((user) => {
      this.currentUser = user;
      this.notifyAuthListeners(user);

      if (user) {
        this.updateLastLogin(user.uid);
      }
    });
  }

  private async updateLastLogin(uid: string): Promise<void> {
    try {
      await AsyncStorage.setItem(`lastLogin_${uid}`, Date.now().toString());
    } catch (error) {
      console.error('Failed to update last login:', error);
    }
  }

  // Authentication State Management
  onAuthStateChanged(callback: (user: User | null) => void): () => void {
    this.authListeners.push(callback);

    // Return unsubscribe function
    return () => {
      const index = this.authListeners.indexOf(callback);
      if (index > -1) {
        this.authListeners.splice(index, 1);
      }
    };
  }

  private notifyAuthListeners(user: User | null): void {
    this.authListeners.forEach(callback => callback(user));
  }

  getCurrentUser(): User | null {
    return this.currentUser;
  }

  // Email/Password Authentication
  async signUpWithEmail(email: string, password: string, displayName?: string): Promise<User> {
    try {
      const userCredential = await createUserWithEmailAndPassword(auth(), email, password);
      const user = userCredential.user;

      if (displayName) {
        await updateProfile(user, { displayName });
      }

      await this.sendEmailVerification();
      await this.saveUserProfile(user);

      return user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async signInWithEmail(email: string, password: string): Promise<User> {
    try {
      const userCredential = await signInWithEmailAndPassword(auth(), email, password);
      await this.saveUserProfile(userCredential.user);
      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async sendEmailVerification(): Promise<void> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }
      await this.currentUser.sendEmailVerification();
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async sendPasswordResetEmail(email: string): Promise<void> {
    try {
      await sendPasswordResetEmail(auth(), email);
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async updatePassword(newPassword: string): Promise<void> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }
      await updatePassword(this.currentUser, newPassword);
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Google Authentication
  async signInWithGoogle(): Promise<User> {
    try {
      // Check if your device supports Google Play Services
      await GoogleSignin.hasPlayServices();

      // Get the user's ID token
      const { idToken } = await GoogleSignin.signIn();

      // Create a Google credential with the token
      const googleCredential = GoogleAuthProvider.credential(idToken);

      // Sign-in the user with the credential
      const userCredential = await auth().signInWithCredential(googleCredential);
      await this.saveUserProfile(userCredential.user);

      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async linkGoogleAccount(): Promise<User> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      await GoogleSignin.hasPlayServices();
      const { idToken } = await GoogleSignin.signIn();
      const googleCredential = GoogleAuthProvider.credential(idToken);

      const userCredential = await linkWithCredential(this.currentUser, googleCredential);
      await this.updateUserProfile(userCredential.user);

      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Facebook Authentication
  async signInWithFacebook(): Promise<User> {
    try {
      // Attempt login with permissions
      const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);

      if (result.isCancelled) {
        throw new Error('User cancelled the login process');
      }

      // Once signed in, get the user's accessToken
      const data = await AccessToken.getCurrentAccessToken();

      if (!data) {
        throw new Error('Something went wrong obtaining access token');
      }

      // Create a Firebase credential with the AccessToken
      const facebookCredential = FacebookAuthProvider.credential(data.accessToken);

      // Sign-in the user with the credential
      const userCredential = await auth().signInWithCredential(facebookCredential);
      await this.saveUserProfile(userCredential.user);

      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async linkFacebookAccount(): Promise<User> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      const result = await LoginManager.logInWithPermissions(['public_profile', 'email']);

      if (result.isCancelled) {
        throw new Error('User cancelled the login process');
      }

      const data = await AccessToken.getCurrentAccessToken();
      if (!data) {
        throw new Error('Something went wrong obtaining access token');
      }

      const facebookCredential = FacebookAuthProvider.credential(data.accessToken);
      const userCredential = await linkWithCredential(this.currentUser, facebookCredential);
      await this.updateUserProfile(userCredential.user);

      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Apple Authentication
  async signInWithApple(): Promise<User> {
    try {
      // Start the sign-in request
      const appleAuthRequestResponse = await appleAuth.performRequest({
        requestedOperation: AppleAuthRequestOperation.SIGN_IN,
        requestedScopes: [
          AppleAuthRequestScope.EMAIL,
          AppleAuthRequestScope.FULL_NAME,
        ],
      });

      // Ensure Apple returned a user identityToken
      if (!appleAuthRequestResponse.identityToken) {
        throw new Error('Apple Sign-In failed - no identity token returned');
      }

      // Create a Firebase credential from the response
      const { identityToken, nonce } = appleAuthRequestResponse;
      const appleCredential = OAuthProvider('apple.com').credential({
        idToken: identityToken,
        rawNonce: nonce,
      });

      // Sign the user in with the credential
      const userCredential = await auth().signInWithCredential(appleCredential);
      await this.saveUserProfile(userCredential.user);

      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Biometric Authentication
  async setupBiometricAuth(): Promise<void> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      const config: BiometricConfig = {
        title: 'Enable Biometric Authentication',
        subtitle: 'Use your fingerprint or Face ID',
        description: 'Quickly sign in with biometrics',
        allowDeviceCredentials: true,
      };

      const optionalConfigObject = {
        title: config.title,
        imageColor: '#e00606',
        imageErrorColor: '#ff0000',
        sensorDescription: config.subtitle,
        sensorErrorDescription: 'Failed',
        cancelText: 'Cancel',
        fallbackLabel: config.fallbackLabel || 'Show Password',
        unifiedErrors: false,
        passcodeFallback: config.allowDeviceCredentials || false,
      };

      const isSupported = await TouchID.isSupported();

      if (isSupported) {
        await TouchID.authenticate(
          config.description || 'Authenticate to enable biometric login',
          optionalConfigObject
        );

        // Store biometric preference
        await AsyncStorage.setItem(
          `biometricEnabled_${this.currentUser.uid}`,
          'true'
        );
      }
    } catch (error) {
      throw new Error(`Biometric setup failed: ${error}`);
    }
  }

  async signInWithBiometrics(): Promise<User> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      const biometricEnabled = await AsyncStorage.getItem(
        `biometricEnabled_${this.currentUser.uid}`
      );

      if (!biometricEnabled) {
        throw new Error('Biometric authentication not enabled');
      }

      const optionalConfigObject = {
        title: 'Biometric Login',
        imageColor: '#e00606',
        imageErrorColor: '#ff0000',
        sensorDescription: 'Touch sensor',
        sensorErrorDescription: 'Failed',
        cancelText: 'Cancel',
        fallbackLabel: 'Use Password',
        unifiedErrors: false,
        passcodeFallback: true,
      };

      await TouchID.authenticate('Authenticate to continue', optionalConfigObject);

      // Refresh the user session
      await this.currentUser.reload();
      return this.currentUser;
    } catch (error) {
      throw new Error(`Biometric authentication failed: ${error}`);
    }
  }

  // Phone Number Authentication
  async signInWithPhoneNumber(phoneNumber: string): Promise<FirebaseAuthTypes.ConfirmationResult> {
    try {
      const confirmation = await auth().signInWithPhoneNumber(phoneNumber);
      return confirmation;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async confirmPhoneNumberCode(
    confirmationResult: FirebaseAuthTypes.ConfirmationResult,
    code: string
  ): Promise<User> {
    try {
      const userCredential = await confirmationResult.confirm(code);
      await this.saveUserProfile(userCredential.user);
      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Profile Management
  async updateUserProfile(displayName?: string, photoURL?: string): Promise<void> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      await updateProfile(this.currentUser, {
        displayName,
        photoURL,
      });

      await this.updateUserProfile(this.currentUser);
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Provider Management
  async unlinkProvider(providerId: string): Promise<User> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      const userCredential = await unlinkProvider(this.currentUser, providerId);
      await this.updateUserProfile(userCredential.user);
      return userCredential.user;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async getProviders(): Promise<string[]> {
    try {
      if (!this.currentUser) {
        return [];
      }

      const userInfo = await this.currentUser.reload();
      const providerData = userInfo.providerData;

      return providerData.map(provider => provider.providerId);
    } catch (error) {
      console.error('Failed to get providers:', error);
      return [];
    }
  }

  // User Profile Storage
  private async saveUserProfile(user: User): Promise<void> {
    try {
      const profile: UserProfile = {
        uid: user.uid,
        email: user.email || '',
        displayName: user.displayName || '',
        photoURL: user.photoURL || '',
        emailVerified: user.emailVerified,
        createdAt: user.metadata.creationTime || new Date().toISOString(),
        lastLoginAt: new Date().toISOString(),
        providers: user.providerData.map(provider => provider.providerId),
        phoneNumber: user.phoneNumber || undefined,
      };

      await AsyncStorage.setItem(`userProfile_${user.uid}`, JSON.stringify(profile));
    } catch (error) {
      console.error('Failed to save user profile:', error);
    }
  }

  private async updateUserProfile(user: User): Promise<void> {
    const existingProfileStr = await AsyncStorage.getItem(`userProfile_${user.uid}`);
    const existingProfile = existingProfileStr ? JSON.parse(existingProfileStr) : {};

    const updatedProfile: UserProfile = {
      ...existingProfile,
      displayName: user.displayName || existingProfile.displayName,
      photoURL: user.photoURL || existingProfile.photoURL,
      emailVerified: user.emailVerified,
      phoneNumber: user.phoneNumber || existingProfile.phoneNumber,
      lastLoginAt: new Date().toISOString(),
      providers: user.providerData.map(provider => provider.providerId),
    };

    await AsyncStorage.setItem(
      `userProfile_${user.uid}`,
      JSON.stringify(updatedProfile)
    );
  }

  async getUserProfile(uid: string): Promise<UserProfile | null> {
    try {
      const profileStr = await AsyncStorage.getItem(`userProfile_${uid}`);
      return profileStr ? JSON.parse(profileStr) : null;
    } catch (error) {
      console.error('Failed to get user profile:', error);
      return null;
    }
  }

  // Sign Out
  async signOut(): Promise<void> {
    try {
      await signOut(auth());
      this.currentUser = null;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  // Error Handling
  private handleAuthError(error: AuthError): Error {
    const errorCode = error.code;
    let errorMessage = 'An unknown error occurred';

    switch (errorCode) {
      case 'auth/user-not-found':
        errorMessage = 'No user found with this email address';
        break;
      case 'auth/wrong-password':
        errorMessage = 'Invalid password';
        break;
      case 'auth/email-already-in-use':
        errorMessage = 'This email is already in use';
        break;
      case 'auth/weak-password':
        errorMessage = 'Password should be at least 6 characters';
        break;
      case 'auth/invalid-email':
        errorMessage = 'Invalid email address';
        break;
      case 'auth/user-disabled':
        errorMessage = 'This user account has been disabled';
        break;
      case 'auth/too-many-requests':
        errorMessage = 'Too many failed attempts. Please try again later';
        break;
      case 'auth/network-request-failed':
        errorMessage = 'Network error. Please check your connection';
        break;
      case 'auth/invalid-verification-code':
        errorMessage = 'Invalid verification code';
        break;
      case 'auth/code-expired':
        errorMessage = 'Verification code has expired';
        break;
      default:
        errorMessage = error.message || errorMessage;
    }

    return new Error(errorMessage);
  }

  // Utility Methods
  isEmailVerified(): boolean {
    return this.currentUser?.emailVerified || false;
  }

  async checkBiometricAvailability(): Promise<{
    available: boolean;
    biometryType: string | null;
  }> {
    try {
      const isSupported = await TouchID.isSupported();
      let biometryType = null;

      if (isSupported) {
        biometryType = await TouchID.isSupported();
      }

      return {
        available: !!isSupported,
        biometryType,
      };
    } catch (error) {
      return {
        available: false,
        biometryType: null,
      };
    }
  }

  async resendVerificationEmail(): Promise<void> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      if (this.currentUser.emailVerified) {
        throw new Error('Email is already verified');
      }

      await this.sendEmailVerification();
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }

  async deleteAccount(): Promise<void> {
    try {
      if (!this.currentUser) {
        throw new Error('No authenticated user');
      }

      await this.currentUser.delete();

      // Clean up stored data
      await AsyncStorage.removeItem(`userProfile_${this.currentUser.uid}`);
      await AsyncStorage.removeItem(`biometricEnabled_${this.currentUser.uid}`);

      this.currentUser = null;
    } catch (error) {
      throw this.handleAuthError(error as AuthError);
    }
  }
}

const defaultExport_AuthService = AuthService;

// src/components/AuthScreen.tsx
import React, { useState, useEffect } from 'react';
import {
  View,
  Text,
  TextInput,
  TouchableOpacity,
  StyleSheet,
  Alert,
  ActivityIndicator,
  KeyboardAvoidingView,
  Platform,
  ScrollView,
} from 'react-native';
import { GoogleSigninButton } from '@react-native-google-signin/google-signin';
import AuthService, { BiometricConfig } from '../services/AuthService';

interface AuthScreenProps {
  onAuthSuccess: () => void;
}

const AuthScreen: React.FC<AuthScreenProps> = ({ onAuthSuccess }) => {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [displayName, setDisplayName] = useState('');
  const [isSignUp, setIsSignUp] = useState(false);
  const [loading, setLoading] = useState(false);
  const [biometricEnabled, setBiometricEnabled] = useState(false);

  const authService = AuthService.getInstance();

  useEffect(() => {
    checkAuthState();
    checkBiometricStatus();
  }, []);

  const checkAuthState = () => {
    const unsubscribe = authService.onAuthStateChanged((user) => {
      if (user && authService.isEmailVerified()) {
        onAuthSuccess();
      }
    });

    return () => unsubscribe();
  };

  const checkBiometricStatus = async () => {
    if (authService.getCurrentUser()) {
      const { available } = await authService.checkBiometricAvailability();
      setBiometricEnabled(available);
    }
  };

  const handleEmailAuth = async () => {
    if (!email || !password || (isSignUp && !displayName)) {
      Alert.alert('Error', 'Please fill in all required fields');
      return;
    }

    setLoading(true);

    try {
      if (isSignUp) {
        await authService.signUpWithEmail(email, password, displayName);
        Alert.alert(
          'Success',
          'Account created! Please check your email for verification.',
          [{ text: 'OK', onPress: () => setIsSignUp(false) }]
        );
      } else {
        await authService.signInWithEmail(email, password);

        if (!authService.isEmailVerified()) {
          Alert.alert(
            'Email Not Verified',
            'Please verify your email before continuing.',
            [
              { text: 'Cancel', style: 'cancel' },
              { text: 'Resend Email', onPress: handleResendVerification }
            ]
          );
        } else {
          onAuthSuccess();
        }
      }
    } catch (error) {
      Alert.alert('Authentication Error', error.message);
    } finally {
      setLoading(false);
    }
  };

  const handleGoogleAuth = async () => {
    setLoading(true);

    try {
      await authService.signInWithGoogle();
      onAuthSuccess();
    } catch (error) {
      Alert.alert('Google Sign-In Error', error.message);
    } finally {
      setLoading(false);
    }
  };

  const handleFacebookAuth = async () => {
    setLoading(true);

    try {
      await authService.signInWithFacebook();
      onAuthSuccess();
    } catch (error) {
      Alert.alert('Facebook Sign-In Error', error.message);
    } finally {
      setLoading(false);
    }
  };

  const handleAppleAuth = async () => {
    setLoading(true);

    try {
      await authService.signInWithApple();
      onAuthSuccess();
    } catch (error) {
      Alert.alert('Apple Sign-In Error', error.message);
    } finally {
      setLoading(false);
    }
  };

  const handleBiometricAuth = async () => {
    setLoading(true);

    try {
      await authService.signInWithBiometrics();
      onAuthSuccess();
    } catch (error) {
      Alert.alert('Biometric Authentication Error', error.message);
    } finally {
      setLoading(false);
    }
  };

  const handleResendVerification = async () => {
    try {
      await authService.resendVerificationEmail();
      Alert.alert('Success', 'Verification email sent!');
    } catch (error) {
      Alert.alert('Error', error.message);
    }
  };

  const handlePasswordReset = async () => {
    if (!email) {
      Alert.alert('Error', 'Please enter your email address');
      return;
    }

    try {
      await authService.sendPasswordResetEmail(email);
      Alert.alert('Success', 'Password reset email sent!');
    } catch (error) {
      Alert.alert('Error', error.message);
    }
  };

  return (
    <KeyboardAvoidingView
      style={styles.container}
      behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
    >
      <ScrollView contentContainerStyle={styles.scrollContainer}>
        <View style={styles.header}>
          <Text style={styles.title}>Welcome to Firebase Auth</Text>
          <Text style={styles.subtitle}>
            {isSignUp ? 'Create your account' : 'Sign in to continue'}
          </Text>
        </View>

        <View style={styles.form}>
          {isSignUp && (
            <TextInput
              style={styles.input}
              placeholder="Display Name"
              value={displayName}
              onChangeText={setDisplayName}
              autoCapitalize="words"
            />
          )}

          <TextInput
            style={styles.input}
            placeholder="Email"
            value={email}
            onChangeText={setEmail}
            keyboardType="email-address"
            autoCapitalize="none"
            autoCorrect={false}
          />

          <TextInput
            style={styles.input}
            placeholder="Password"
            value={password}
            onChangeText={setPassword}
            secureTextEntry
          />

          <TouchableOpacity style={styles.primaryButton} onPress={handleEmailAuth}>
            {loading ? (
              <ActivityIndicator color="#fff" />
            ) : (
              <Text style={styles.buttonText}>
                {isSignUp ? 'Sign Up' : 'Sign In'}
              </Text>
            )}
          </TouchableOpacity>

          {!isSignUp && (
            <TouchableOpacity style={styles.secondaryButton} onPress={handlePasswordReset}>
              <Text style={styles.secondaryButtonText}>Forgot Password?</Text>
            </TouchableOpacity>
          )}
        </View>

        <View style={styles.divider}>
          <View style={styles.dividerLine} />
          <Text style={styles.dividerText}>OR</Text>
          <View style={styles.dividerLine} />
        </View>

        <View style={styles.socialButtons}>
          <GoogleSigninButton
            style={styles.googleButton}
            size={GoogleSigninButton.Size.Wide}
            color={GoogleSigninButton.Color.Dark}
            onPress={handleGoogleAuth}
            disabled={loading}
          />

          <TouchableOpacity
            style={[styles.socialButton, { backgroundColor: '#1877F2' }]}
            onPress={handleFacebookAuth}
            disabled={loading}
          >
            <Text style={styles.socialButtonText}>Continue with Facebook</Text>
          </TouchableOpacity>

          {Platform.OS === 'ios' && (
            <TouchableOpacity
              style={[styles.socialButton, { backgroundColor: '#000' }]}
              onPress={handleAppleAuth}
              disabled={loading}
            >
              <Text style={styles.socialButtonText}>Continue with Apple</Text>
            </TouchableOpacity>
          )}

          {biometricEnabled && (
            <TouchableOpacity
              style={[styles.socialButton, { backgroundColor: '#4CAF50' }]}
              onPress={handleBiometricAuth}
              disabled={loading}
            >
              <Text style={styles.socialButtonText}>Use Biometric Login</Text>
            </TouchableOpacity>
          )}
        </View>

        <TouchableOpacity
          style={styles.switchMode}
          onPress={() => setIsSignUp(!isSignUp)}
        >
          <Text style={styles.switchModeText}>
            {isSignUp
              ? 'Already have an account? Sign In'
              : "Don't have an account? Sign Up"}
          </Text>
        </TouchableOpacity>
      </ScrollView>
    </KeyboardAvoidingView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f5f5',
  },
  scrollContainer: {
    flexGrow: 1,
    justifyContent: 'center',
    padding: 20,
  },
  header: {
    alignItems: 'center',
    marginBottom: 40,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
  },
  form: {
    marginBottom: 30,
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    borderRadius: 8,
    padding: 15,
    fontSize: 16,
    marginBottom: 15,
    backgroundColor: '#fff',
  },
  primaryButton: {
    backgroundColor: '#007AFF',
    borderRadius: 8,
    padding: 15,
    alignItems: 'center',
  },
  buttonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  secondaryButton: {
    marginTop: 10,
    alignItems: 'center',
  },
  secondaryButtonText: {
    color: '#007AFF',
    fontSize: 14,
  },
  divider: {
    flexDirection: 'row',
    alignItems: 'center',
    marginVertical: 20,
  },
  dividerLine: {
    flex: 1,
    height: 1,
    backgroundColor: '#ddd',
  },
  dividerText: {
    marginHorizontal: 10,
    color: '#666',
  },
  socialButtons: {
    marginBottom: 30,
  },
  googleButton: {
    width: '100%',
    height: 48,
    marginBottom: 15,
  },
  socialButton: {
    borderRadius: 8,
    padding: 15,
    alignItems: 'center',
    marginBottom: 15,
  },
  socialButtonText: {
    color: '#fff',
    fontSize: 16,
    fontWeight: '600',
  },
  switchMode: {
    alignItems: 'center',
  },
  switchModeText: {
    color: '#007AFF',
    fontSize: 14,
  },
});

const defaultExport_AuthScreen = AuthScreen;

💻 Base de Données Temps Réel React Native Firebase typescript

🟡 intermediate

Construire des applications collaboratives en temps réel avec Firebase Realtime Database incluant le support offline, la synchronisation de données et les requêtes complexes

// src/services/RealtimeDatabaseService.ts
import database, {
  DatabaseReference,
  DataSnapshot,
  Query,
  push,
  remove,
  set,
  update,
  get,
  child,
  orderByChild,
  orderByKey,
  orderByValue,
  limitToFirst,
  limitToLast,
  startAt,
  endAt,
  equalTo,
  onValue,
  onChildAdded,
  onChildChanged,
  onChildRemoved,
  onChildMoved,
  serverTimestamp,
  DatabaseReferenceType,
} from '@react-native-firebase/database';
import { Alert } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

export interface RealtimeData {
  [key: string]: any;
}

export interface DatabaseNode {
  key: string;
  value: any;
  timestamp?: number;
}

export interface QueryOptions {
  orderBy?: 'child' | 'key' | 'value';
  orderByChild?: string;
  limitToFirst?: number;
  limitToLast?: number;
  startAt?: any;
  startAfter?: any;
  endAt?: any;
  endBefore?: any;
  equalTo?: any;
}

export interface TransactionResult {
  committed: boolean;
  snapshot?: DataSnapshot;
}

class RealtimeDatabaseService {
  private static instance: RealtimeDatabaseService;
  private database: DatabaseReference;
  private offlineCache: Map<string, any> = new Map();

  private constructor() {
    this.database = database().ref();
    this.initializeOfflineSupport();
  }

  static getInstance(): RealtimeDatabaseService {
    if (!RealtimeDatabaseService.instance) {
      RealtimeDatabaseService.instance = new RealtimeDatabaseService();
    }
    return RealtimeDatabaseService.instance;
  }

  private async initializeOfflineSupport(): Promise<void> {
    try {
      // Enable offline capabilities
      await database().setPersistenceEnabled(true);

      // Load cached data
      await this.loadOfflineCache();
    } catch (error) {
      console.error('Failed to initialize offline support:', error);
    }
  }

  private async loadOfflineCache(): Promise<void> {
    try {
      const cachedKeys = await AsyncStorage.getAllKeys();
      const dbKeys = cachedKeys.filter(key => key.startsWith('db_cache_'));

      for (const key of dbKeys) {
        const value = await AsyncStorage.getItem(key);
        if (value) {
          const path = key.replace('db_cache_', '');
          this.offlineCache.set(path, JSON.parse(value));
        }
      }
    } catch (error) {
      console.error('Failed to load offline cache:', error);
    }
  }

  private async cacheOfflineData(path: string, data: any): Promise<void> {
    try {
      this.offlineCache.set(path, data);
      await AsyncStorage.setItem(`db_cache_${path}`, JSON.stringify(data));
    } catch (error) {
      console.error('Failed to cache offline data:', error);
    }
  }

  // Basic CRUD Operations
  async createData(path: string, data: any, withTimestamp: boolean = true): Promise<string> {
    try {
      const finalData = withTimestamp
        ? { ...data, createdAt: serverTimestamp() }
        : data;

      const newRef = push(this.database.child(path), finalData);

      // Cache the new data
      await this.cacheOfflineData(`${path}/${newRef.key}`, finalData);

      return newRef.key || '';
    } catch (error) {
      console.error('Failed to create data:', error);
      throw new Error(`Failed to create data: ${error.message}`);
    }
  }

  async readData(path: string): Promise<RealtimeData | null> {
    try {
      // Try offline cache first
      const cachedData = this.offlineCache.get(path);
      if (cachedData) {
        return cachedData;
      }

      const snapshot: DataSnapshot = await get(this.database.child(path));
      const data = snapshot.val();

      if (data) {
        await this.cacheOfflineData(path, data);
      }

      return data;
    } catch (error) {
      console.error('Failed to read data:', error);

      // Fallback to offline cache
      const cachedData = this.offlineCache.get(path);
      if (cachedData) {
        return cachedData;
      }

      throw new Error(`Failed to read data: ${error.message}`);
    }
  }

  async updateData(path: string, data: any): Promise<void> {
    try {
      const updateData = {
        ...data,
        updatedAt: serverTimestamp(),
      };

      await update(this.database.child(path), updateData);

      // Update cache
      const existingData = this.offlineCache.get(path) || {};
      await this.cacheOfflineData(path, { ...existingData, ...updateData });
    } catch (error) {
      console.error('Failed to update data:', error);
      throw new Error(`Failed to update data: ${error.message}`);
    }
  }

  async setData(path: string, data: any, withTimestamp: boolean = true): Promise<void> {
    try {
      const finalData = withTimestamp
        ? { ...data, updatedAt: serverTimestamp() }
        : data;

      await set(this.database.child(path), finalData);

      // Update cache
      await this.cacheOfflineData(path, finalData);
    } catch (error) {
      console.error('Failed to set data:', error);
      throw new Error(`Failed to set data: ${error.message}`);
    }
  }

  async deleteData(path: string): Promise<void> {
    try {
      await remove(this.database.child(path));

      // Remove from cache
      this.offlineCache.delete(path);
      await AsyncStorage.removeItem(`db_cache_${path}`);
    } catch (error) {
      console.error('Failed to delete data:', error);
      throw new Error(`Failed to delete data: ${error.message}`);
    }
  }

  // Query Operations
  private buildQuery(path: string, options: QueryOptions): Query {
    let query: Query = this.database.child(path);

    // Order by
    if (options.orderBy === 'child' && options.orderByChild) {
      query = orderByChild(query, options.orderByChild);
    } else if (options.orderBy === 'key') {
      query = orderByKey(query);
    } else if (options.orderBy === 'value') {
      query = orderByValue(query);
    }

    // Limit
    if (options.limitToFirst) {
      query = limitToFirst(query, options.limitToFirst);
    }
    if (options.limitToLast) {
      query = limitToLast(query, options.limitToLast);
    }

    // Range
    if (options.startAt !== undefined) {
      query = startAt(query, options.startAt);
    }
    if (options.startAfter !== undefined) {
      query = startAt(query, options.startAfter);
    }
    if (options.endAt !== undefined) {
      query = endAt(query, options.endAt);
    }
    if (options.endBefore !== undefined) {
      query = endAt(query, options.endBefore);
    }

    // Equality
    if (options.equalTo !== undefined) {
      query = equalTo(query, options.equalTo);
    }

    return query;
  }

  async queryData(path: string, options: QueryOptions): Promise<RealtimeData[]> {
    try {
      const query = this.buildQuery(path, options);
      const snapshot: DataSnapshot = await get(query);

      const results: RealtimeData[] = [];
      snapshot.forEach((childSnapshot) => {
        results.push({
          key: childSnapshot.key || '',
          value: childSnapshot.val(),
        });
      });

      return results;
    } catch (error) {
      console.error('Failed to query data:', error);
      throw new Error(`Failed to query data: ${error.message}`);
    }
  }

  // Real-time Listeners
  onValueChange(
    path: string,
    callback: (data: RealtimeData | null) => void,
    options?: QueryOptions
  ): () => void {
    const query = options ? this.buildQuery(path, options) : this.database.child(path);

    return onValue(query, (snapshot: DataSnapshot) => {
      const data = snapshot.val();
      callback(data);

      // Update cache
      if (data) {
        this.cacheOfflineData(path, data);
      }
    });
  }

  onChildAdded(
    path: string,
    callback: (child: DatabaseNode) => void,
    options?: QueryOptions
  ): () => void {
    const query = options ? this.buildQuery(path, options) : this.database.child(path);

    return onChildAdded(query, (snapshot: DataSnapshot, previousChildKey?: string) => {
      const child = {
        key: snapshot.key || '',
        value: snapshot.val(),
        previousChildKey,
      };
      callback(child);
    });
  }

  onChildChanged(
    path: string,
    callback: (child: DatabaseNode) => void,
    options?: QueryOptions
  ): () => void {
    const query = options ? this.buildQuery(path, options) : this.database.child(path);

    return onChildChanged(query, (snapshot: DataSnapshot, previousChildKey?: string) => {
      const child = {
        key: snapshot.key || '',
        value: snapshot.val(),
        previousChildKey,
      };
      callback(child);
    });
  }

  onChildRemoved(
    path: string,
    callback: (child: DatabaseNode) => void,
    options?: QueryOptions
  ): () => void {
    const query = options ? this.buildQuery(path, options) : this.database.child(path);

    return onChildRemoved(query, (snapshot: DataSnapshot) => {
      const child = {
        key: snapshot.key || '',
        value: snapshot.val(),
      };
      callback(child);
    });
  }

  // Transaction Operations
  async runTransaction(
    path: string,
    transactionUpdate: (currentData: any) => any
  ): Promise<TransactionResult> {
    try {
      const result = await this.database.child(path).transaction(transactionUpdate);

      // Update cache if transaction was successful
      if (result.committed && result.snapshot?.exists()) {
        await this.cacheOfflineData(path, result.snapshot.val());
      }

      return result;
    } catch (error) {
      console.error('Transaction failed:', error);
      throw new Error(`Transaction failed: ${error.message}`);
    }
  }

  // Batch Operations
  async batchUpdate(updates: { [path: string]: any }): Promise<void> {
    try {
      const timestampedUpdates: { [path: string]: any } = {};

      for (const [path, data] of Object.entries(updates)) {
        timestampedUpdates[path] = {
          ...data,
          updatedAt: serverTimestamp(),
        };
      }

      await update(this.database, timestampedUpdates);

      // Update cache
      for (const [path, data] of Object.entries(timestampedUpdates)) {
        await this.cacheOfflineData(path, data);
      }
    } catch (error) {
      console.error('Batch update failed:', error);
      throw new Error(`Batch update failed: ${error.message}`);
    }
  }

  // Advanced Operations
  async searchByField(
    path: string,
    fieldName: string,
    searchValue: any
  ): Promise<RealtimeData[]> {
    return this.queryData(path, {
      orderBy: 'child',
      orderByChild: fieldName,
      equalTo: searchValue,
    });
  }

  async paginateResults(
    path: string,
    pageSize: number,
    startAfter?: string,
    orderByField?: string
  ): Promise<RealtimeData[]> {
    const options: QueryOptions = {
      limitToFirst: pageSize + 1, // Get one extra to check if there are more pages
    };

    if (orderByField) {
      options.orderBy = 'child';
      options.orderByChild = orderByField;
    } else {
      options.orderBy = 'key';
    }

    if (startAfter) {
      options.startAfter = startAfter;
    }

    const results = await this.queryData(path, options);

    // Remove the extra item if it exists
    if (results.length > pageSize) {
      return results.slice(0, pageSize);
    }

    return results;
  }

  // Utility Methods
  getReference(path: string): DatabaseReference {
    return this.database.child(path);
  }

  async exists(path: string): Promise<boolean> {
    try {
      const snapshot = await get(this.database.child(path));
      return snapshot.exists();
    } catch (error) {
      console.error('Failed to check existence:', error);
      return false;
    }
  }

  async getTimestamp(): Promise<number> {
    // Get server timestamp
    const timestampRef = push(this.database, { timestamp: serverTimestamp() });
    const snapshot = await get(timestampRef);
    return snapshot.val().timestamp;
  }

  generatePushKey(): string {
    return push(this.database).key || '';
  }

  // Cache Management
  clearCache(): void {
    this.offlineCache.clear();
  }

  async clearOfflineStorage(): Promise<void> {
    try {
      this.clearCache();
      const keys = await AsyncStorage.getAllKeys();
      const dbKeys = keys.filter(key => key.startsWith('db_cache_'));
      await AsyncStorage.multiRemove(dbKeys);
    } catch (error) {
      console.error('Failed to clear offline storage:', error);
    }
  }

  async syncOfflineChanges(): Promise<void> {
    try {
      // This would sync any offline changes with the server
      // Implementation depends on your specific offline sync strategy
      console.log('Syncing offline changes...');
    } catch (error) {
      console.error('Failed to sync offline changes:', error);
    }
  }
}

const defaultExport_RealtimeDatabaseService = RealtimeDatabaseService;

// src/services/ChatService.ts
import RealtimeDatabaseService, { DatabaseNode } from './RealtimeDatabaseService';

export interface Message {
  id: string;
  text: string;
  senderId: string;
  senderName: string;
  timestamp: number;
  type: 'text' | 'image' | 'file';
  imageUrl?: string;
  fileName?: string;
  fileSize?: number;
  readBy: string[];
  replyTo?: string;
  reactions?: { [emoji: string]: string[] };
}

export interface ChatRoom {
  id: string;
  name: string;
  description?: string;
  participants: string[];
  createdAt: number;
  lastMessage?: Message;
  unreadCount: { [userId: string]: number };
  typing: { [userId: string]: boolean };
}

export interface User {
  id: string;
  name: string;
  email: string;
  avatar?: string;
  isOnline: boolean;
  lastSeen: number;
}

class ChatService {
  private static instance: ChatService;
  private db: RealtimeDatabaseService;
  private currentUserId: string;

  constructor(userId: string) {
    this.currentUserId = userId;
    this.db = RealtimeDatabaseService.getInstance();
  }

  static getInstance(userId: string): ChatService {
    if (!ChatService.instance || ChatService.instance['currentUserId'] !== userId) {
      ChatService.instance = new ChatService(userId);
    }
    return ChatService.instance;
  }

  // Chat Room Management
  async createChatRoom(
    name: string,
    participants: string[],
    description?: string
  ): Promise<string> {
    const chatRoom: Omit<ChatRoom, 'id'> = {
      name,
      description,
      participants: [...participants, this.currentUserId],
      createdAt: Date.now(),
      unreadCount: {},
      typing: {},
    };

    const roomId = await this.db.createData('chatRooms', chatRoom);

    // Initialize unread counts
    for (const participant of chatRoom.participants) {
      await this.db.updateData(`chatRooms/${roomId}/unreadCount/${participant}`, 0);
    }

    return roomId;
  }

  async getChatRoom(roomId: string): Promise<ChatRoom | null> {
    return await this.db.readData(`chatRooms/${roomId}`);
  }

  async getUserChatRooms(): Promise<ChatRoom[]> {
    const userRooms = await this.db.searchByField(
      'chatRooms',
      'participants',
      this.currentUserId
    );

    return userRooms.map(room => ({ ...room.value, id: room.key }));
  }

  async updateChatRoom(roomId: string, updates: Partial<ChatRoom>): Promise<void> {
    await this.db.updateData(`chatRooms/${roomId}`, updates);
  }

  async leaveChatRoom(roomId: string): Promise<void> {
    const room = await this.getChatRoom(roomId);
    if (room) {
      const updatedParticipants = room.participants.filter(id => id !== this.currentUserId);
      await this.db.updateData(`chatRooms/${roomId}`, {
        participants: updatedParticipants,
      });
    }
  }

  // Message Management
  async sendMessage(
    roomId: string,
    text: string,
    type: 'text' | 'image' | 'file' = 'text',
    options?: {
      imageUrl?: string;
      fileName?: string;
      fileSize?: number;
      replyTo?: string;
    }
  ): Promise<string> {
    const message: Omit<Message, 'id'> = {
      text,
      senderId: this.currentUserId,
      senderName: await this.getUserDisplayName(this.currentUserId),
      timestamp: Date.now(),
      type,
      imageUrl: options?.imageUrl,
      fileName: options?.fileName,
      fileSize: options?.fileSize,
      replyTo: options?.replyTo,
      readBy: [this.currentUserId],
      reactions: {},
    };

    const messageId = await this.db.createData(`messages/${roomId}`, message);

    // Update chat room last message
    const room = await this.getChatRoom(roomId);
    if (room) {
      await this.db.updateData(`chatRooms/${roomId}`, {
        lastMessage: { ...message, id: messageId },
      });

      // Increment unread counts for other participants
      for (const participant of room.participants) {
        if (participant !== this.currentUserId) {
          await this.runTransaction(`chatRooms/${roomId}/unreadCount/${participant}`, (currentCount) => {
            return (currentCount || 0) + 1;
          });
        }
      }
    }

    return messageId;
  }

  async editMessage(roomId: string, messageId: string, newText: string): Promise<void> {
    await this.db.updateData(`messages/${roomId}/${messageId}`, {
      text: newText,
      edited: true,
      editedAt: Date.now(),
    });
  }

  async deleteMessage(roomId: string, messageId: string): Promise<void> {
    await this.db.deleteData(`messages/${roomId}/${messageId}`);
  }

  async markMessageAsRead(roomId: string, messageId: string): Promise<void> {
    await this.db.runTransaction(`messages/${roomId}/${messageId}/readBy`, (readBy) => {
      const currentReadBy = readBy || [];
      if (!currentReadBy.includes(this.currentUserId)) {
        return [...currentReadBy, this.currentUserId];
      }
      return currentReadBy;
    });
  }

  async markAllMessagesAsRead(roomId: string): Promise<void> {
    // Reset unread count
    await this.db.setData(`chatRooms/${roomId}/unreadCount/${this.currentUserId}`, 0);
  }

  // Typing Indicators
  async setTyping(roomId: string, isTyping: boolean): Promise<void> {
    await this.db.setData(`chatRooms/${roomId}/typing/${this.currentUserId}`, isTyping);

    // Auto-remove typing indicator after 3 seconds
    if (isTyping) {
      setTimeout(async () => {
        await this.db.setData(`chatRooms/${roomId}/typing/${this.currentUserId}`, false);
      }, 3000);
    }
  }

  // Reactions
  async addReaction(roomId: string, messageId: string, emoji: string): Promise<void> {
    await this.db.runTransaction(`messages/${roomId}/${messageId}/reactions/${emoji}`, (currentReactors) => {
      const reactors = currentReactors || [];
      if (!reactors.includes(this.currentUserId)) {
        return [...reactors, this.currentUserId];
      }
      return reactors;
    });
  }

  async removeReaction(roomId: string, messageId: string, emoji: string): Promise<void> {
    await this.db.runTransaction(`messages/${roomId}/${messageId}/reactions/${emoji}`, (currentReactors) => {
      const reactors = currentReactors || [];
      return reactors.filter((id: string) => id !== this.currentUserId);
    });
  }

  // Message Listeners
  onNewMessage(roomId: string, callback: (message: Message) => void): () => void {
    return this.db.onChildAdded(`messages/${roomId}`, (node) => {
      const message: Message = {
        ...node.value,
        id: node.key,
      };
      callback(message);
    });
  }

  onMessageUpdated(roomId: string, callback: (message: Message) => void): () => void {
    return this.db.onChildChanged(`messages/${roomId}`, (node) => {
      const message: Message = {
        ...node.value,
        id: node.key,
      };
      callback(message);
    });
  }

  onMessageDeleted(roomId: string, callback: (messageId: string) => void): () => void {
    return this.db.onChildRemoved(`messages/${roomId}`, (node) => {
      callback(node.key);
    });
  }

  onTypingUsers(roomId: string, callback: (typingUsers: string[]) => void): () => void {
    return this.db.onValueChange(`chatRooms/${roomId}/typing`, (typingData) => {
      if (typingData) {
        const typingUsers = Object.entries(typingData)
          .filter(([userId, isTyping]) => isTyping && userId !== this.currentUserId)
          .map(([userId]) => userId);
        callback(typingUsers);
      }
    });
  }

  // User Management
  async getUserProfile(userId: string): Promise<User | null> {
    return await this.db.readData(`users/${userId}`);
  }

  async updateUserProfile(updates: Partial<User>): Promise<void> {
    await this.db.updateData(`users/${this.currentUserId}`, updates);
  }

  async setUserOnline(isOnline: boolean): Promise<void> {
    await this.db.updateData(`users/${this.currentUserId}`, {
      isOnline,
      lastSeen: Date.now(),
    });
  }

  async getUserDisplayName(userId: string): Promise<string> {
    const user = await this.getUserProfile(userId);
    return user?.name || 'Unknown User';
  }

  onUserOnlineStatus(userId: string, callback: (isOnline: boolean) => void): () => void {
    return this.db.onValueChange(`users/${userId}/isOnline`, callback);
  }

  // Utility Methods
  private async runTransaction(path: string, updateFunction: (currentData: any) => any): Promise<void> {
    await this.db.runTransaction(path, updateFunction);
  }

  async searchMessages(roomId: string, searchTerm: string): Promise<Message[]> {
    // This is a simplified search - in production, you might want to use
    // a dedicated search service like Algolia or Elasticsearch
    const messages = await this.db.queryData(`messages/${roomId}`, {
      orderBy: 'key',
    });

    return messages
      .map(msg => ({ ...msg.value, id: msg.key }))
      .filter(msg =>
        msg.text.toLowerCase().includes(searchTerm.toLowerCase()) ||
        msg.senderName.toLowerCase().includes(searchTerm.toLowerCase())
      );
  }

  async getMessageHistory(roomId: string, limit: number = 50, startAfter?: string): Promise<Message[]> {
    const messages = await this.db.paginateResults(
      `messages/${roomId}`,
      limit,
      startAfter,
      'timestamp'
    );

    return messages.map(msg => ({ ...msg.value, id: msg.key }));
  }

  async getUnreadCount(roomId: string): Promise<number> {
    const unreadCount = await this.db.readData(`chatRooms/${roomId}/unreadCount/${this.currentUserId}`);
    return unreadCount || 0;
  }

  async getTotalUnreadCount(): Promise<number> {
    const rooms = await this.getUserChatRooms();
    let total = 0;

    for (const room of rooms) {
      const unreadCount = await this.getUnreadCount(room.id);
      total += unreadCount;
    }

    return total;
  }
}

const defaultExport_ChatService = ChatService;

💻 Intégration Cloud Functions React Native Firebase typescript

🔴 complex

Implémenter une logique backend serverless avec Firebase Cloud Functions incluant les triggers, les tâches planifiées et les endpoints API personnalisés

// Firebase Cloud Functions (Node.js - functions/index.js)
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const express = require('express');
const cors = require('cors');
const sgMail = require('@sendgrid/mail');
const Stripe = require('stripe');
const sharp = require('sharp');
const path = require('path');

// Initialize Firebase Admin
admin.initializeApp();

// Initialize external services
const stripe = Stripe(functions.config().stripe.secret_key);
sgMail.setApiKey(functions.config().sendgrid.api_key);

// Express app for HTTP functions
const app = express();
app.use(cors({ origin: true }));

// Cloud Function: Send Email Notification
exports.sendEmailNotification = functions.https.onCall(async (data, context) => {
  // Check if user is authenticated
  if (!context.auth) {
    throw new functions.https.HttpsError(
      'unauthenticated',
      'The function must be called while authenticated.'
    );
  }

  const { to, subject, text, html, templateData } = data;

  try {
    const msg = {
      to,
      from: '[email protected]',
      subject,
      text,
      html,
      templateId: templateData?.templateId,
      dynamicTemplateData: templateData?.data,
    };

    await sgMail.send(msg);

    return { success: true, message: 'Email sent successfully' };
  } catch (error) {
    console.error('Email sending error:', error);
    throw new functions.https.HttpsError(
      'internal',
      'Failed to send email'
    );
  }
});

// Cloud Function: Process Payment with Stripe
exports.processPayment = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Authentication required');
  }

  const { amount, currency = 'usd', paymentMethodId, description } = data;

  try {
    // Create a payment intent
    const paymentIntent = await stripe.paymentIntents.create({
      amount,
      currency,
      payment_method: paymentMethodId,
      confirmation_method: 'manual',
      confirm: true,
      description,
      metadata: {
        userId: context.auth.uid,
      },
    });

    // Record payment in Firestore
    const paymentRecord = {
      userId: context.auth.uid,
      stripePaymentId: paymentIntent.id,
      amount,
      currency,
      status: paymentIntent.status,
      createdAt: admin.firestore.FieldValue.serverTimestamp(),
    };

    await admin.firestore().collection('payments').doc(paymentIntent.id).set(paymentRecord);

    return {
      success: true,
      clientSecret: paymentIntent.client_secret,
      status: paymentIntent.status,
    };
  } catch (error) {
    console.error('Payment processing error:', error);
    throw new functions.https.HttpsError('internal', 'Payment processing failed');
  }
});

// Cloud Function: Generate Thumbnail
exports.generateThumbnail = functions.storage.object().onFinalize(async (object) => {
  const fileBucket = object.bucket;
  const filePath = object.name;
  const contentType = object.contentType;

  // Exit if this is triggered on a file that is not an image.
  if (!contentType.startsWith('image/')) {
    return console.log('This is not an image.');
  }

  // Exit if the image is already a thumbnail.
  if (filePath.includes('thumbnail_')) {
    return console.log('Already a thumbnail.');
  }

  // Download file from bucket.
  const bucket = admin.storage().bucket(fileBucket);
  const tempFilePath = path.join(os.tmpdir(), filePath);
  const thumbnailFilePath = path.join(path.dirname(filePath), `thumbnail_${path.basename(filePath)}`);

  await bucket.file(filePath).download({
    destination: tempFilePath,
  });

  // Generate a thumbnail using Sharp.
  const thumbnailBuffer = await sharp(tempFilePath)
    .resize(200, 200, {
      fit: 'inside',
      withoutEnlargement: true,
    })
    .toBuffer();

  // Upload thumbnail to Storage.
  const thumbnailFile = bucket.file(thumbnailFilePath);
  await thumbnailFile.save(thumbnailBuffer, {
    metadata: {
      contentType: contentType,
    },
  });

  // Clean up the temporary file.
  return fs.unlinkSync(tempFilePath);
});

// Cloud Function: User Activity Analytics
exports.trackUserActivity = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Authentication required');
  }

  const { action, properties = {} } = data;
  const userId = context.auth.uid;

  try {
    const activity = {
      userId,
      action,
      properties,
      timestamp: admin.firestore.FieldValue.serverTimestamp(),
      userAgent: context.rawRequest.headers['user-agent'],
      ip: context.rawRequest.ip,
    };

    await admin.firestore().collection('userActivity').add(activity);

    // Update user analytics
    const userRef = admin.firestore().collection('users').doc(userId);
    await userRef.update({
      lastActivity: admin.firestore.FieldValue.serverTimestamp(),
      totalActivities: admin.firestore.FieldValue.increment(1),
    });

    return { success: true };
  } catch (error) {
    console.error('Activity tracking error:', error);
    throw new functions.https.HttpsError('internal', 'Failed to track activity');
  }
});

// Cloud Function: Send Push Notification
exports.sendPushNotification = functions.https.onCall(async (data, context) => {
  if (!context.auth) {
    throw new functions.https.HttpsError('unauthenticated', 'Authentication required');
  }

  const { userIds, title, body, data: notificationData, imageUrl } = data;

  try {
    // Get user FCM tokens
    const userDocs = await admin.firestore()
      .collection('users')
      .where(admin.firestore.FieldPath.documentId(), 'in', userIds)
      .get();

    const tokens = [];
    userDocs.forEach(doc => {
      const userData = doc.data();
      if (userData.fcmToken) {
        tokens.push(userData.fcmToken);
      }
    });

    if (tokens.length === 0) {
      return { success: false, message: 'No valid FCM tokens found' };
    }

    const message = {
      notification: {
        title,
        body,
        ...(imageUrl && { imageUrl }),
      },
      data: notificationData || {},
      tokens,
    };

    const response = await admin.messaging().sendMulticast(message);

    // Handle failed tokens
    if (response.failureCount > 0) {
      const failedTokens = [];
      response.responses.forEach((resp, idx) => {
        if (!resp.success) {
          failedTokens.push(tokens[idx]);
        }
      });

      // Remove invalid tokens from user documents
      for (const token of failedTokens) {
        await admin.firestore()
          .collection('users')
          .where('fcmToken', '==', token)
          .limit(1)
          .get()
          .then(querySnapshot => {
            querySnapshot.forEach(doc => {
              doc.ref.update({ fcmToken: admin.firestore.FieldValue.delete() });
            });
          });
      }
    }

    return {
      success: true,
      successCount: response.successCount,
      failureCount: response.failureCount,
    };
  } catch (error) {
    console.error('Push notification error:', error);
    throw new functions.https.HttpsError('internal', 'Failed to send push notification');
  }
});

// Scheduled Cloud Function: Daily Digest
exports.sendDailyDigest = functions.pubsub
  .schedule('0 9 * * *') // Every day at 9 AM
  .timeZone('America/New_York')
  .onRun(async (context) => {
    try {
      // Get all users who opted in for daily digest
      const usersSnapshot = await admin.firestore()
        .collection('users')
        .where('dailyDigest', '==', true)
        .get();

      const batchPromises = [];

      usersSnapshot.forEach(async (doc) => {
        const user = doc.data();

        // Get user's activity from last 24 hours
        const yesterday = new Date();
        yesterday.setDate(yesterday.getDate() - 1);

        const activitySnapshot = await admin.firestore()
          .collection('userActivity')
          .where('userId', '==', doc.id)
          .where('timestamp', '>=', yesterday)
          .get();

        if (activitySnapshot.empty) {
          return; // Skip if no activity
        }

        // Generate digest content
        const activities = activitySnapshot.docs.map(doc => doc.data());
        const digestContent = generateDigestContent(activities);

        // Send email digest
        const msg = {
          to: user.email,
          from: '[email protected]',
          subject: 'Your Daily Digest',
          html: digestContent,
        };

        batchPromises.push(sgMail.send(msg));
      });

      await Promise.all(batchPromises);

      console.log('Daily digest sent successfully');
      return null;
    } catch (error) {
      console.error('Daily digest error:', error);
      return null;
    }
  });

// Realtime Database Trigger: User Presence
exports.updateUserPresence = functions.database
  .ref('/users/{userId}/online')
  .onUpdate(async (change, context) => {
    const { userId } = context.params;
    const isOnline = change.after.val();

    if (isOnline) {
      // User came online
      await admin.firestore().collection('users').doc(userId).update({
        online: true,
        lastSeen: admin.firestore.FieldValue.serverTimestamp(),
      });
    } else {
      // User went offline
      await admin.firestore().collection('users').doc(userId).update({
        online: false,
        lastSeen: admin.firestore.FieldValue.serverTimestamp(),
      });
    }
  });

// Firestore Trigger: User Welcome
exports.sendWelcomeEmail = functions.firestore
  .document('users/{userId}')
  .onCreate(async (snapshot, context) => {
    const user = snapshot.data();

    if (user.email) {
      const msg = {
        to: user.email,
        from: '[email protected]',
        subject: 'Welcome to Our App!',
        templateId: 'd-welcome-template',
        dynamicTemplateData: {
          name: user.displayName || 'User',
        },
      };

      try {
        await sgMail.send(msg);
        console.log('Welcome email sent to:', user.email);
      } catch (error) {
        console.error('Welcome email error:', error);
      }
    }
  });

// Helper function to generate digest content
function generateDigestContent(activities) {
  const groupedActivities = activities.reduce((acc, activity) => {
    const action = activity.action;
    if (!acc[action]) {
      acc[action] = [];
    }
    acc[action].push(activity);
    return acc;
  }, {});

  let html = '<h2>Your Daily Activity Summary</h2>';

  for (const [action, items] of Object.entries(groupedActivities)) {
    html += `<h3>${action.charAt(0).toUpperCase() + action.slice(1)} (${items.length})</h3>`;
    html += '<ul>';

    items.forEach(item => {
      const time = new Date(item.timestamp.toDate()).toLocaleTimeString();
      html += `<li>${time} - ${item.properties.description || 'No description'}</li>`;
    });

    html += '</ul>';
  }

  return html;
}

// src/services/CloudFunctionsService.ts
import { https } from '@react-native-firebase/functions';
import { Alert } from 'react-native';

export interface CloudFunctionResponse<T = any> {
  success: boolean;
  data?: T;
  error?: string;
}

export interface EmailNotificationData {
  to: string;
  subject: string;
  text?: string;
  html?: string;
  templateData?: {
    templateId: string;
    data: Record<string, any>;
  };
}

export interface PaymentData {
  amount: number;
  currency?: string;
  paymentMethodId: string;
  description?: string;
}

export interface UserActivityData {
  action: string;
  properties?: Record<string, any>;
}

export interface PushNotificationData {
  userIds: string[];
  title: string;
  body: string;
  data?: Record<string, any>;
  imageUrl?: string;
}

class CloudFunctionsService {
  private static instance: CloudFunctionsService;

  static getInstance(): CloudFunctionsService {
    if (!CloudFunctionsService.instance) {
      CloudFunctionsService.instance = new CloudFunctionsService();
    }
    return CloudFunctionsService.instance;
  }

  private async callFunction<T>(functionName: string, data: any): Promise<CloudFunctionResponse<T>> {
    try {
      const result = await https().call(functionName, data);

      return {
        success: true,
        data: result.data,
      };
    } catch (error) {
      console.error(`Cloud function error (${functionName}):`, error);

      let errorMessage = 'An unknown error occurred';
      if (error.details) {
        errorMessage = error.details;
      } else if (error.message) {
        errorMessage = error.message;
      }

      return {
        success: false,
        error: errorMessage,
      };
    }
  }

  // Email Notifications
  async sendEmailNotification(data: EmailNotificationData): Promise<CloudFunctionResponse> {
    return await this.callFunction('sendEmailNotification', data);
  }

  // Payment Processing
  async processPayment(data: PaymentData): Promise<CloudFunctionResponse> {
    const response = await this.callFunction('processPayment', data);

    if (!response.success) {
      Alert.alert('Payment Error', response.error || 'Payment processing failed');
    }

    return response;
  }

  // User Activity Tracking
  async trackUserActivity(data: UserActivityData): Promise<CloudFunctionResponse> {
    return await this.callFunction('trackUserActivity', data);
  }

  // Push Notifications
  async sendPushNotification(data: PushNotificationData): Promise<CloudFunctionResponse> {
    return await this.callFunction('sendPushNotification', data);
  }

  // Custom Functions
  async callCustomFunction<T>(functionName: string, data: any): Promise<CloudFunctionResponse<T>> {
    return await this.callFunction<T>(functionName, data);
  }

  // Utility Methods
  async sendWelcomeEmail(email: string, displayName: string): Promise<CloudFunctionResponse> {
    return await this.sendEmailNotification({
      to: email,
      subject: 'Welcome to Our App!',
      templateData: {
        templateId: 'd-welcome-template',
        data: { name: displayName || 'User' },
      },
    });
  }

  async sendPasswordResetEmail(email: string, resetLink: string): Promise<CloudFunctionResponse> {
    return await this.sendEmailNotification({
      to: email,
      subject: 'Password Reset Request',
      html: `
        <h2>Password Reset</h2>
        <p>You requested to reset your password. Click the link below to reset it:</p>
        <a href="${resetLink}">Reset Password</a>
        <p>If you didn't request this, please ignore this email.</p>
      `,
    });
  }

  async sendOrderConfirmation(email: string, orderDetails: any): Promise<CloudFunctionResponse> {
    return await this.sendEmailNotification({
      to: email,
      subject: 'Order Confirmation',
      templateData: {
        templateId: 'd-order-confirmation',
        data: orderDetails,
      },
    });
  }

  async trackPageView(pageName: string, properties?: Record<string, any>): Promise<void> {
    await this.trackUserActivity({
      action: 'page_view',
      properties: {
        page: pageName,
        ...properties,
      },
    });
  }

  async trackPurchase(productId: string, amount: number, currency: string): Promise<void> {
    await this.trackUserActivity({
      action: 'purchase',
      properties: {
        productId,
        amount,
        currency,
        timestamp: Date.now(),
      },
    });
  }

  async trackFeatureUsage(featureName: string, properties?: Record<string, any>): Promise<void> {
    await this.trackUserActivity({
      action: 'feature_usage',
      properties: {
        feature: featureName,
        ...properties,
      },
    });
  }
}

const defaultExport_CloudFunctionsService = CloudFunctionsService;

// src/services/AnalyticsService.ts
import analytics from '@react-native-firebase/analytics';
import CloudFunctionsService from './CloudFunctionsService';

export interface AnalyticsEvent {
  name: string;
  parameters?: Record<string, any>;
}

export interface UserProperties {
  [key: string]: string | number | boolean;
}

class AnalyticsService {
  private static instance: AnalyticsService;
  private cloudFunctions: CloudFunctionsService;

  private constructor() {
    this.cloudFunctions = CloudFunctionsService.getInstance();
  }

  static getInstance(): AnalyticsService {
    if (!AnalyticsService.instance) {
      AnalyticsService.instance = new AnalyticsService();
    }
    return AnalyticsService.instance;
  }

  // Basic Analytics
  async logEvent(name: string, parameters?: Record<string, any>): Promise<void> {
    try {
      await analytics().logEvent(name, parameters);

      // Also send to custom analytics if needed
      await this.cloudFunctions.trackUserActivity({
        action: 'analytics_event',
        properties: {
          eventName: name,
          parameters,
        },
      });
    } catch (error) {
      console.error('Analytics logging error:', error);
    }
  }

  async setUserId(userId: string): Promise<void> {
    try {
      await analytics().setUserId(userId);
    } catch (error) {
      console.error('Set user ID error:', error);
    }
  }

  async setUserProperties(properties: UserProperties): Promise<void> {
    try {
      await analytics().setUserProperties(properties);
    } catch (error) {
      console.error('Set user properties error:', error);
    }
  }

  // Standard E-commerce Events
  async logPurchase(transactionId: string, value: number, currency: string, item?: any): Promise<void> {
    const parameters: any = {
      transaction_id: transactionId,
      value,
      currency,
    };

    if (item) {
      parameters.items = [item];
    }

    await this.logEvent('purchase', parameters);
  }

  async logViewItem(itemId: string, itemName: string, itemCategory: string, price?: number): Promise<void> {
    await this.logEvent('view_item', {
      item_id: itemId,
      item_name: itemName,
      item_category: itemCategory,
      value: price,
    });
  }

  async logAddToCart(itemId: string, itemName: string, itemCategory: string, quantity: number, price: number): Promise<void> {
    await this.logEvent('add_to_cart', {
      item_id: itemId,
      item_name: itemName,
      item_category: itemCategory,
      quantity,
      value: price * quantity,
    });
  }

  async logBeginCheckout(items: any[], value: number, currency: string): Promise<void> {
    await this.logEvent('begin_checkout', {
      items,
      value,
      currency,
    });
  }

  // App Events
  async logAppOpen(): Promise<void> {
    await this.logEvent('app_open');
  }

  async logScreenView(screenName: string, screenClass?: string): Promise<void> {
    await analytics().logScreenView({
      screen_name: screenName,
      screen_class: screenClass || screenName,
    });
  }

  async logSearch(searchTerm: string): Promise<void> {
    await this.logEvent('search', {
      search_term: searchTerm,
    });
  }

  async logShare(contentType: string, itemId: string): Promise<void> {
    await this.logEvent('share', {
      content_type: contentType,
      item_id: itemId,
    });
  }

  async logSignUp(method: string): Promise<void> {
    await this.logEvent('sign_up', {
      method,
    });
  }

  async logLogin(method: string): Promise<void> {
    await this.logEvent('login', {
      method,
    });
  }

  // Custom Events
  async trackCustomEvent(eventName: string, parameters?: Record<string, any>): Promise<void> {
    await this.logEvent(eventName, parameters);
  }

  // Session Tracking
  async setSessionTimeoutDuration(duration: number): Promise<void> {
    try {
      await analytics().setAnalyticsCollectionEnabled(true);
      // Note: Session timeout duration setting might not be available in React Native
      // This is typically configured in Firebase console
    } catch (error) {
      console.error('Session timeout error:', error);
    }
  }

  async enableAnalytics(): Promise<void> {
    try {
      await analytics().setAnalyticsCollectionEnabled(true);
    } catch (error) {
      console.error('Enable analytics error:', error);
    }
  }

  async disableAnalytics(): Promise<void> {
    try {
      await analytics().setAnalyticsCollectionEnabled(false);
    } catch (error) {
      console.error('Disable analytics error:', error);
    }
  }

  // Error Tracking
  async trackError(error: Error, context?: Record<string, any>): Promise<void> {
    await this.logEvent('app_error', {
      error_message: error.message,
      error_stack: error.stack,
      context,
    });
  }
}

const defaultExport_AnalyticsService = AnalyticsService;