Untitled

 avatar
unknown
plain_text
14 days ago
7.4 kB
3
Indexable
import messaging from '@react-native-firebase/messaging';
import { Platform } from 'react-native';
import * as Device from 'expo-device';

export class FirebaseMessagingService {
  private static instance: FirebaseMessagingService;
  private _fcmToken: string | null = null;

  private constructor() {}

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

  /**
   * Request permission to receive push notifications
   */
  async requestPermission(): Promise<boolean> {
    if (!Device.isDevice) {
      console.log('Must use physical device for Push Notifications');
      return false;
    }

    try {
      const authStatus = await messaging().requestPermission();
      const enabled =
        authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
        authStatus === messaging.AuthorizationStatus.PROVISIONAL;

      if (enabled) {
        console.log('Firebase messaging authorization status:', authStatus);
        return true;
      } else {
        console.log('Firebase messaging authorization denied');
        return false;
      }
    } catch (error) {
      console.error('Error requesting notification permission:', error);
      return false;
    }
  }

  /**
   * Get the FCM token for this device
   */
  async getFCMToken(): Promise<string | null> {
    try {
      // Always clear the cached token to force a fresh attempt
      this._fcmToken = null;
      
      console.log('Platform:', Platform.OS);
      
      // For iOS, we need to register for remote messages before getting the token
      if (Platform.OS === 'ios') {
        try {
          // Force iOS to use the sandbox APNs environment for development builds
          if (__DEV__) {
            console.log('Setting up Firebase Messaging for development (sandbox APNs)');
            // This is a workaround to force Firebase to use the sandbox APNs environment
            // It's not officially documented but might help in some cases
            const messagingInstance = messaging();
            (messagingInstance as any)._useSandbox = true;
          }
          
          console.log('iOS: Checking if already registered for remote messages');
          const isRegistered = await messaging().isDeviceRegisteredForRemoteMessages;
          console.log('iOS: Device registered status:', isRegistered);
          
          // Always force re-registration to ensure we have the latest registration
          console.log('iOS: Registering device for remote messages');
          await messaging().registerDeviceForRemoteMessages();
          console.log('iOS: Device successfully registered for remote messages');
          
          // Wait for APNs token to be available
          console.log('iOS: Checking for APNs token...');
          const apnsToken = await messaging().getAPNSToken();
          console.log('iOS: APNs token:', apnsToken || 'Not available');
          
          if (!apnsToken) {
            console.warn('iOS: No APNs token available');
          }
        } catch (registerError) {
          console.error('Error registering for remote messages:', registerError);
          if (registerError instanceof Error) {
            console.error('Error message:', registerError.message);
            console.error('Error stack:', registerError.stack);
          }
          throw registerError;
        }
      }

      console.log('Getting FCM token...');
      // Get the token
      const token = await messaging().getToken();
      console.log('FCM Token received:', token ? 'Yes (length: ' + token.length + ')' : 'No');
      this._fcmToken = token;
      return token;
    } catch (error) {
      console.error('Error getting FCM token:', error);
      if (error instanceof Error) {
        console.error('Error message:', error.message);
        console.error('Error stack:', error.stack);
      }
      return null;
    }
  }

  /**
   * Register for foreground notifications
   * @param callback Function to call when a notification is received in foreground
   */
  onForegroundMessage(callback: (message: any) => void) {
    console.log('Setting up Firebase foreground message handler');
    return messaging().onMessage((message) => {
      console.log('Firebase message received in foreground:', message);
      callback(message);
    });
  }

  /**
   * Register for background notifications
   * This must be called outside of any component
   * @param callback Function to call when a notification is received in background
   */
  static setBackgroundMessageHandler(callback: (message: any) => Promise<void>) {
    console.log('Setting up Firebase background message handler');
    messaging().setBackgroundMessageHandler(async (message) => {
      console.log('Firebase message received in background:', message);
      return callback(message);
    });
  }

  /**
   * Register for notification open events
   * @param callback Function to call when a notification is opened
   */
  onNotificationOpen(callback: (message: any) => void) {
    return messaging().onNotificationOpenedApp(callback);
  }

  /**
   * Check if the app was opened from a notification
   */
  async getInitialNotification() {
    return await messaging().getInitialNotification();
  }

  /**
   * Subscribe to a topic
   * @param topic Topic to subscribe to
   */
  async subscribeToTopic(topic: string): Promise<void> {
    try {
      await messaging().subscribeToTopic(topic);
      console.log(`Subscribed to topic: ${topic}`);
    } catch (error) {
      console.error(`Error subscribing to topic ${topic}:`, error);
    }
  }

  /**
   * Unsubscribe from a topic
   * @param topic Topic to unsubscribe from
   */
  async unsubscribeFromTopic(topic: string): Promise<void> {
    try {
      await messaging().unsubscribeFromTopic(topic);
      console.log(`Unsubscribed from topic: ${topic}`);
    } catch (error) {
      console.error(`Error unsubscribing from topic ${topic}:`, error);
    }
  }

  /**
   * Configure the notification channel for Android
   * This is required for Android 8.0+
   */
  async createAndroidChannel(): Promise<void> {
    if (Platform.OS === 'android') {
      try {
        // For Firebase, we'll use the Expo Notifications API to create a channel
        // that will be used by Firebase notifications
        const { Notifications } = require('expo-notifications');
        
        await Notifications.setNotificationChannelAsync('firebase-messaging-channel', {
          name: 'Firebase Notifications',
          description: 'Channel for Firebase Cloud Messaging',
          importance: Notifications.AndroidImportance.MAX,
          vibrationPattern: [0, 250, 250, 250],
          lightColor: '#FF231F7C',
          sound: 'default',
        });
        
        console.log('Android notification channel setup complete for Firebase');
      } catch (error) {
        console.error('Error creating Android notification channel:', error);
      }
    }
  }

  get fcmToken(): string | null {
    return this._fcmToken;
  }
}

// Set up background message handler
FirebaseMessagingService.setBackgroundMessageHandler(async (remoteMessage) => {
  console.log('Message handled in the background!', remoteMessage);
});

export default FirebaseMessagingService.getInstance();
Editor is loading...
Leave a Comment