npm package: @questlabs/react-native-notifications
Description: A React Native SDK for managing push notifications with Firebase Cloud Messaging (FCM) and Notifee, featuring token handling, event listeners, and QuestLabs API integration. 🚀
Author: Vivek Sharma
Latest version: 1.0.0

# Quest Notifications for React Native

> QuestNotifications is a React Native SDK that allows you to receive push notifications from campaigns created on the [QuestLabs Dashboard](https://app.questlabs.ai). Simply integrate this package into your app, and it will handle incoming notifications for all campaigns configured through the dashboard.

## Installation

QuestNotifications relies on Firebase Cloud Messaging (FCM) for receiving remote notifications and Notifee for displaying them. You must install these packages as well.


```sh
npm install @questlabs/react-native-notifications @notifee/react-native @react-native-firebase/app @react-native-firebase/messaging
```

## Configuration

Before using the QuestNotifications SDK, you need to configure Firebase Cloud Messaging (FCM) properly. FCM is essential for handling remote notifications, and without the correct setup, notifications won't work.

### Firebase Setup

1. **Create a Firebase Project:**
   - Go to the [Firebase Console](https://console.firebase.google.com/).
   - Click on "Add Project" and follow the prompts to create a new Firebase project.
   - Once your project is created, click on the "Settings" gear icon and select "Project settings."
   - In the "Your apps" section, click on the Android or iOS icon to add your app to Firebase.
   - Follow the instructions to download the `google-services.json` file for Android or the `GoogleService-Info.plist` file for iOS.

2. **Configure Service Account on QuestLabs Dashboard:**
   - In the Firebase Console, go to "Project settings" and select the "Service accounts" tab.
   - Click on "Generate new private key" to download a JSON file containing your service account credentials.
   - Now go to [QuestLabs Dashboard Integrations](https://app.questlabs.ai/admin/integrations) and add service account credentials there. Or you can use existing integrated service account on the dashboard.

3. **Service Account Authorization:**
    - Service account must have roles `Firebase Admin` and `Firebase messaging Admin`. 
    - To add roles to service account, go to [Cloud Console IAM](https://console.cloud.google.com/iam-admin) and add required roles.

### React Native CLI
This SDK is primarily designed for React Native CLI, but it can still be used with Expo.
#### Android

   - Place the `google-services.json` file in the `android/app` directory of your React Native project.
   - Add the following lines to your `android/build.gradle` file:
   ```gradle
    buildscript {
        dependencies {
            // ... other dependencies
            classpath 'com.google.gms:google-services:4.4.2' // Add this line
        }
    }
   ```
   - Add the following line to your `android/app/build.gradle` file:
   ```gradle
    apply plugin: 'com.google.gms.google-services' // Add this line
   ```

#### IOS

   - Using Xcode, open the projects `/ios/{projectName}.xcodeproj` file (or `/ios/{projectName}.xcworkspace` using Pods). 
   - Right click on the project name and "Add files" to the project.
   - Select the `GoogleService-Info.plist` file and add it to the project.
   - Add the following lines to your `/ios/{projectName}/AppDelegate.swift` file (React Native 0.77+):
   ```swift
    // ... other imports
    import Firebase // Add this line after `import ReactAppDependencyProvider`

    override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure() // Add this line at the top
        // ... other code
    }
   ```
   - Add the following lines to your `/ios/{projectName}/AppDelegate.m` file (React Native 0.76 and below):
   ```swift
    // ... other imports
    #import <Firebase.h> // Add this line after `#import "AppDelegate.h"`

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
        [FIRApp configure]; // Add this line at the top
        // ... other code
    }
   ```
   - Altering CocoaPods to use frameworks
        - Beginning with firebase-ios-sdk v9+ (react-native-firebase v15+) you must tell CocoaPods to use frameworks.
        - Add the following line to your `./ios/Podfile`:

        ```ruby
        use_frameworks! :linkage => :static # Add this line after the `use_react_native`
        $RNFirebaseAsStaticFramework = true # Add this line to use Static Frameworks on iOS
        ```
        > **Notes**: React-Native-Firebase uses use_frameworks, which has compatibility issues with Flipper & Fabric.
        >
        > **Flipper**: use_frameworks is not compatible with Flipper. You must disable Flipper by commenting out the :flipper_configuration line in your Podfile. Flipper is deprecated in the react-native community and this will not be fixed - Flipper and react-native-firebase will never work together on iOS.
        >
        > **New Architecture**: Fabric is partially compatible with use_frameworks!. If you enable the bridged / compatibility mode, react-native-firebase will compile correctly and be usable.
### Expo
Expo requires additional configuration to use this package, and it works only with development builds.
#### Android

- Place the `google-services.json` file in the root directory of your Expo project.
- Enter file path in your `app.json > expo > android > googleServicesFile`
- Add `@react-native-firebase/app` to your `app.json > expo > plugins`

```js
{
  "expo": {
    // ... other configurations
    "android": {
      // ... other android configurations
      "googleServicesFile": "./google-services.json"
    },
    "plugins": [
      // ... other plugins
      "@react-native-firebase/app"
    ]
  }
}
```

> **Note:** You need to run your app in a development build to use this package. Installing Notifee package could cause build failure for expo applications because it doesn't have plugins to configure maven directory. If it doesnt work for you, you may try this solution:
>
> Create a custom plugin file in root directory of your project `/plugins/notifee.js`
> ```js
> const { withProjectBuildGradle } = require('@expo/config-plugins');
> 
> const notifee = (config) => {
>   return withProjectBuildGradle(config, (config) => {
>     if (config.modResults.contents.includes('@notifee/react-native')) {
>       return config;
>     }
>     config.modResults.contents = config.modResults.contents.replace(
>       /allprojects\s*{[^}]+repositories\s*{/,
>       (match) => `${match}\n        maven { url "\${rootDir}/../node_modules/@notifee/react-native/android/libs" }`
>     );
> 
>     return config;
>   });
> };
> 
> module.exports = notifee;
> ```
> Add this plugin to your `app.json > expo > plugins`
> 
>```js
> {
>   "expo": {
>     // ... other configurations
>     "plugins": [
>       // ... other plugins
>       "./plugins/notifee"
>     ]
>   }
> }
>```

#### IOS
   - Place the `GoogleService-Info.plist` file in the root directory of your Expo project.
   - Install `npx expo install expo-build-properties`
   - Update your `app.json` file with the following configuration:
   ```js
   {
     "expo": {
       // ... other configurations
       "ios": {
         // ... other ios configurations
         "bundleIdentifier": "<YOUR_BUNDLE_IDENTIFIER>",
         "appleTeamId": "<YOUR_APPLE_TEAM_ID>",
         "googleServicesFile": "./GoogleService-Info.plist"
         "entitlements": {
            "aps-environment": "development" | "production"
         },
         "infoPlist": {
            "UIBackgroundModes": [
                "fetch",
                "remote-notification"
            ]
         }
       },
       "plugins": [
         // ... other plugins
         "@react-native-firebase/app",
         [
            "expo-build-properties",
            {
              "ios": {
                "useFrameworks": "static"
              }
            }
         ]
       ]
     }
   }
   ```

### IOS Additional Configuration
Now we have to generate certificates and update it in firebase console to enable your app to receive remote notifications.
- You must have an active [Apple Developer Account](https://idmsa.apple.com/IDMSWebAuth/signin?appIdKey=891bd3417a7776362562d2197f89480a8547b108fd934911bcbea0110d07f757&path=%2Faccount%2F&rv=1)
- Open your project's workspace file via Xcode, and navigate to `Project > Targets > Signing & Capabilities` tab. 
- Click on the `+ Capability` button to add a new capability. Select `Push Notifications` & `Background Modes` from the list (If no option appears when searching, the capability may already be enabled).\
- Then expand the `Background Modes` capability and enable `Background fetch` and the `Remote notifications`.

- Linking APNs with FCM required following steps: 
    - Registering a key.
    - Go to [Apple Developer Account Keys](https://developer.apple.com/account/resources/authkeys/list) and create a new key. Select `Apple Push Notifications service (APNs)` from the list.
    - Download the key and save it in a safe place.
    - Now go to [Firebase Console](https://console.firebase.google.com/) and go to `Project settings > Cloud Messaging`.
    - Select `Apple App` and Upload the key you downloaded earlier and save it.
- Registering an App Identifier.
    - Go to [Apple Developer Account Identifiers](htthttps://developer.apple.com/account/resources/identifiers/list) and create a new App Identifier. Select `App IDs` from the list.
    - Select `Explicit` and enter your Bundle ID.
    - In Capabilities section, enable `Push Notifications` & `Push Notifications > Broadcast Compatibility`.
    - Save the App Identifier.
    - Now go to Xcode and select your project. Go to `Project > Targets > Signing & Capabilities` tab. enter your App Identifier in the `Bundle Identifier` field.
- Generating a provisioning profile (Optional).
    - Go to [Apple Developer Account Provisioning Profiles](https://developer.apple.com/account/resources/profiles/list) and create a new provisioning profile. Select `iOS App Development` from the list.
    - Select your App Id and your Development Certificate, select your device.
    - Download the provisioning profile and save it in a safe place.
    - Now go to Xcode and select your project. Go to `Project > Targets > Signing & Capabilities` tab. Select your provisioning profile in the `Provisioning Profile` field.
Follow the [official Firebase documentation](https://rnfirebase.io/) for more detailed instructions.

## Usage

First, you need to create a Push Notification campaign on the [QuestLabs Dashboard](https://app.questlabs.ai/admin/push-notification) with your target audience to receive the notifications.


You have to initialize the SDK in your app's entry point, most likely `index.js` or `App.js` (required for background notifications):

```js
import { AppRegistry } from 'react-native';
import App from './App';
import { name as appName } from './app.json';
import { initialize } from '@questlabs/react-native-notifications';

// Initialize the SDK
// Note: You can create multiple instances of the SDK with different configurations, the default instance id is 'default'

initialize({
    apiKey: '<YOUR_API_KEY>',
    entityId: '<YOUR_ENTITYID>', 
    apiType: '<STAGING | PRODUCTION>',
    questUserId: '<QUEST_USER_ID>',
    authenticationToken: '<YOUR_AUTHENTICATION-_TOKEN>'
});


AppRegistry.registerComponent(appName, () => App);
```

Then you have to register the user to receive notifications. You can do this by calling the `register` method on the `notifications` object. You can also unregister the user by calling the `unregister` method.
```js

import { useEffect, View, Text, Button } from 'react-native'
import { notifications } from '@questlabs/react-native-notifications';

function App(){
    const [user, setUser] = useState(null)

    const signIn = async () => {
        const user = { id: 1, name: 'John Doe' }

        // These configurations are optional, you can also pass them in the initialize method
        // notifications().configure({
        //     questUserId: user.id, // Required if externalUserId is absent
        //     externalUserId: user.id, // Required if questUserId is absent
        // })

        await notifications().register() // (Make sure register every instance one by one)
        setUser({ name: 'John Doe' })
    }

    const signOut = async () => {
        await notifications().unregister()
        setUser(null)
    }
    
    return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
            {user ? (
                <>
                    <Text>Welcome, {user.name}!</Text>
                    <Button 
                        title="Sign Out"
                        onPress={signOut} 
                    />
                </>
            ) : (
                <Button 
                    title="Sign In"
                    onPress={signIn} 
                />
            )}
        </View>
    )
}
```
## API Reference

The QuestNotifications SDK provides the following methods:

| **Method**           | **Description** |
|----------------------|----------------|
| `initialize(configuration, instanceId?)` | Initializes the QuestNotifications instance with the provided configuration, and returns an instance of the QuestNotifications. |
| `isInitialized(instanceId?)` | Checks if the QuestNotifications instance is initialized. |
| `destroy(instanceId?)` | Destroys the QuestNotifications instance. |
| `notifications(instanceId?)` | Returns an instance of the QuestNotifications. |


## Configuration Properties  

The SDK initialization requires a configuration object with the following properties:  

| **Property**                  | **Type**                                      | **Description**                                          | **Default**            |
|--------------------------------|-----------------------------------------------|----------------------------------------------------------|------------------------|
| `apiKey`                       | `string`                                     | API key for authentication (Required).                   | _None_ (Required)      |
| `apiType`                      | `'STAGING'` \| `'PRODUCTION'`                | Specifies the API environment.                           | `'PRODUCTION'`         |
| `entityId`                     | `string`                                     | Unique identifier for the entity (Required).             | _None_ (Required)      |
| `authenticationToken`          | `string`                                     | Token used for authentication (Required).                | _None_ (Required)      |
| `questUserId`                  | `string`                                     | Internal user ID (Required if `externalUserId` is absent). | _None_ (Required)*     |
| `externalUserId`               | `string`                                     | External user ID (Required if `questUserId` is absent).  | _None_ (Required)*     |
| `defaultNotificationOptions`   | `Notification`                               | Default options for notifications.                       | `{ title: 'QuestLabs', body: 'No body', android: { channelId: 'default' }, ios: { summaryArgument: 'QuestLabs', summaryArgumentCount: 1, foregroundPresentationOptions: { alert: true, badge: true, banner: true, sound: true } } }`    |
| `defaultChannelOptions`        | `AndroidChannel`                             | Default options for Android notification channels.       | `{ id: 'default', name: 'Default Channel', importance: HIGH }` |
| `defaultCategoryOptions`       | `IOSNotificationCategory`                    | Default options for iOS notification categories.         | `{ id: 'default', summaryFormat: 'You have %u+ unread messages from %@' }` |
| `settings.disableTracking`     | `boolean`                                    | Whether to disable tracking.                             | `false`               |
| `settings.disableNotificationDisplay` | `boolean`                            | Whether to prevent notifications from being displayed.   | `false`               |
| `settings.registerDeviceForRemoteNotifications` | `boolean` | Whether to register the device for remote notifications, Not required with auto-linking. (iOS only) | `false` |

> **Note:** Either `questUserId` or `externalUserId` must be provided, but **not both**.  

## Methods  

The following methods are provided by notifications instance:  

| **Method**           | **Description** |
|----------------------|----------------|
| `configure(config)` | Updates the SDK configuration with the provided values. |
| `requestPermissions()` | Requests permissions for push notifications. Returns `true` if granted, otherwise `false`. |
| `getToken()` | Retrieves the FCM token for the user. Returns a string. |
| `register()` | Registers the user for FCM push notifications. Returns a `Promise<void>`. |
| `unregister()` | Unregisters the user from FCM push notifications. Returns a `Promise<void>`. |
| `destroy()` | Destroys the SDK instance. |
| `on(type, callback)` | Adds an event listener for a specific event type. Returns an unsubscribe function. |
| `__updateToken(token?)` | Updates the user's FCM token in QuestLabs backend. Rarely required to be called manually. |
| `__removeToken(token?)` | Removes the user's FCM token from QuestLabs backend. Rarely required to be called manually. |
| `__displayNotification(remoteMessage)` | Displays a notification using Notifee. Rarely required to be called manually. |
| `__flush()` | Flushes all cleanup functions. Rarely required to be called manually. |
| `__emitEvent(type, eventDetail)` | Emits an event to all event listeners for a specific event type. Rarely required to be called manually. |
| `__notifeeEventHandler()` | Handles Notifee events. Rarely required to be called manually. |
| `__fcmEventHandler()` | Handles FCM events. Rarely required to be called manually. |

> **Note:** Methods that start with `__` are **not private**, but users generally **don't need to call them manually**. They are only useful in rare cases where deeper customization is needed.

## Contact Us
If you encounter any issues or have questions while using the QuestNotifications React SDK, please don't hesitate to reach out to us:

Shubham - [shubham@questapp.xyz](mailto:shubham@questapp.xyz)

Deb - [shubham@questapp.xyz](mailto:deb@questapp.xyz)

## License

This project is licensed under the [MIT License](LICENSE).