React Native Firebase Integrationsbeispiele

React Native Firebase Integrationsbeispiele einschließlich Authentifizierung, Echtzeit-Datenbank, Cloud Functions, Push-Benachrichtigungen, Analytics und Cloud Storage

💻 React Native Firebase Authentifizierung typescript

🟢 simple

Umfassende Firebase Authentifizierung implementieren inklusive E-Mail/Passwort, Social Login, biometrischer Authentifizierung und sicherer Session-Verwaltung

// 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"
};

export default 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);
    }
  }
}

export default 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,
  },
});

export default AuthScreen;

💻 React Native Firebase Echtzeit-Datenbank typescript

🟡 intermediate

Kollaborative Echtzeit-Anwendungen mit Firebase Realtime Database erstellen inklusive Offline-Unterstützung, Datensynchronisation und komplexen Abfragen

// 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);
    }
  }
}

export default 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;
  }
}

export default ChatService;

💻 React Native Firebase Cloud Functions Integration typescript

🔴 complex

Serverless Backend-Logik mit Firebase Cloud Functions implementieren inklusive Triggern, geplanten Aufgaben und benutzerdefinierten API-Endpunkten

// 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,
      },
    });
  }
}

export default 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,
    });
  }
}

export default AnalyticsService;