From e99ddf245cd76f1883c2b0c5e33179e7bcab1c66 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Fri, 8 Nov 2024 15:44:01 +0100 Subject: [PATCH] update documentation --- .../call/ringing-call-content.mdx | 10 +- .../reactnative/06-advanced/03-ringing.mdx | 77 +++--- .../03-ringing-setup/01-react-native.mdx | 249 ++++++++++-------- .../03-ringing-setup/02-expo.mdx | 131 ++++++--- .../01-react-native.mdx | 144 +++++++++- .../04-other-than-ringing-setup/02-expo.mdx | 225 +++++++++++++++- .../src/utils/push/android.ts | 7 +- .../react-native-sdk/src/utils/push/ios.ts | 5 +- .../react-native-sdk/src/utils/push/utils.ts | 8 +- .../src/utils/setFirebaseListeners.android.ts | 6 +- .../utils/setFirebaseListeners.android.ts | 6 +- 11 files changed, 679 insertions(+), 189 deletions(-) diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx index be92bd4ba9..fcb63b1c18 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/04-ui-components/call/ringing-call-content.mdx @@ -49,15 +49,21 @@ To use the `RingingCallContent` you can do the following: ```tsx {11} import { + StreamCall, useCalls, RingingCallContent, } from '@stream-io/video-react-native-sdk'; const Call = () => { - const calls = useCalls(); + // filter for ringing calls + const calls = useCalls().filter( + (c) => c.state.callingState === CallingState.RINGING, + ); + const call = calls[0]; + if (!call) return null; return ( - + ); diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/03-ringing.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/03-ringing.mdx index 0192a3c50b..6d0334974e 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/03-ringing.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/03-ringing.mdx @@ -44,50 +44,65 @@ The caller will automatically join the call once the first callee accepts the ca The call will automatically stop if every callee rejects the call. :::note -When ring is true, a push notification will be sent to the members, provided their app have the required setup. +When ring is true, a **push notification** will be sent to the members, provided their app have the required setup. For more details around push notifications, please check [this page](../../advanced/push-notifications/overview). ::: ## Watch for incoming and outgoing calls -The easiest way to watch for incoming and outgoing calls is to use the `useCalls` hook. +The easiest way to watch for incoming and outgoing calls is to use the `useCalls` hook and the [`RingingCallContent`](../../ui-components/call/ringing-call-content) component. -```tsx -import { useCalls, CallingState } from '@stream-io/video-react-native-sdk'; +**Important**: Make sure that the ringing calls are watched in the root component of your app. This makes sure that in whichever screen the user is in, or if the app was opened from a push notification it is shown. Below is an example of how to watch for ringing calls in the root component of your App. -export const MyCallUI = () => { - const calls = useCalls(); +```ts +import { SafeAreaView, StyleSheet } from 'react-native'; +import { + StreamCall, + StreamVideo, + useCalls, + RingingCallContent, + StreamVideoClient, + User, +} from '@stream-io/video-react-native-sdk'; - // handle incoming ring calls - const incomingCalls = calls.filter( - (call) => - call.isCreatedByMe === false && - call.state.callingState === CallingState.RINGING, +const user: User = { + id: 'sara', +}; +const apiKey = ''; +const tokenProvider = () => Promise.resolve(''); +const client = StreamVideoClient.getOrCreateInstance({ apiKey, tokenProvider, user }); + +const RingingCalls = () => { + // filter for ringing calls + const calls = useCalls().filter( + (c) => c.state.callingState === CallingState.RINGING, ); - - const [incomingCall] = incomingCalls; - if (incomingCall) { - // render the incoming call UI - return ; - } - - // handle outgoing ring calls - const outgoingCalls = calls.filter( - (call) => - call.isCreatedByMe === true && - call.state.callingState === CallingState.RINGING, + const call = calls[0]; + if (!call) return null; + + return ( + + + + + + ); +} + +const App = () => { + return ( + + + + ); - - const [outgoingCall] = outgoingCalls; - if (outgoingCall) { - // render the outgoing call UI - return ; - } - - return null; }; + +export default App; ``` +In the above example, the component `RingingCalls` renders over the rest of the App whenever there is a incoming or outgoing call. Alternatively you can use a Modal view or Dialog to show there is a ringing call over the rest of your app. + ## Canceling a call A caller can cancel an outgoing call until the first callee accepts the call. Canceling a call will stop the signaling flow. diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/01-react-native.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/01-react-native.mdx index ef629e8509..ef29b9a05b 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/01-react-native.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/01-react-native.mdx @@ -38,65 +38,6 @@ So what did we install precisely? - `react-native-voip-push-notification` for handling incoming [PushKit](https://developer.apple.com/documentation/pushkit) notifications on iOS. - `react-native-callkeep` for reporting incoming calls to iOS [CallKit](https://developer.apple.com/documentation/callkit). -## Android-specific setup - -1. To create a Firebase project, go to the [Firebase console](https://console.firebase.google.com/) and click on **Add project**. - -2. In the console, click the setting icon next to **Project overview** and open **Project settings**. Then, under **Your apps**, click the Android icon to open **Add Firebase to your Android app** and follow the steps. **Make sure that the Android package name you enter is the same as the value of** `android.package` **from your app.json.** - -3. After registering the app, download the **google-services.json** file and place it inside of your project at the following location: `/android/app/google-services.json.` - -4. To allow Firebase on Android to use the credentials, the `google-services` plugin must be enabled on the project. This requires modification to two files in the Android directory. Add the highlighted lines in the relevant files: - -```groovy title="/android/build.gradle" -buildscript { - dependencies { - // ... other dependencies - // highlight-next-line - classpath 'com.google.gms:google-services:4.3.15' - } -} -``` - -```groovy title="/android/build.gradle" -apply plugin: 'com.android.application' -// highlight-next-line -apply plugin: 'com.google.gms.google-services' -``` - -:::note - -The **google-services.json** file contains unique and non-secret identifiers of your Firebase project. For more information, see [Understand Firebase Projects](https://firebase.google.com/docs/projects/learn-more#config-files-objects). - -::: - -### Add declarations in AndroidManifest - -Add the following in `AndroidManifest.xml`: - -```xml title="AndroidManifest.xml" - - - - - -``` - -### Request for notification permissions - -At an appropriate place in your app, request for notification permissions from the user. Below is a small example of how to request permissions using [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) library: - - -```js -import { requestNotifications } from 'react-native-permissions'; - -await requestNotifications(['alert', 'sound']); -``` - ## iOS-specific setup ### Disable Firebase installation @@ -239,56 +180,150 @@ The final method to add is the one that gets invoked when there is a VoIP push n } ``` -## Setup the push config for the SDK +## Android-specific setup + +1. To create a Firebase project, go to the [Firebase console](https://console.firebase.google.com/) and click on **Add project**. + +2. In the console, click the setting icon next to **Project overview** and open **Project settings**. Then, under **Your apps**, click the Android icon to open **Add Firebase to your Android app** and follow the steps. **Make sure that the Android package name you enter is the same as the value of** `android.package` **from your app.json.** + +3. After registering the app, download the **google-services.json** file and place it inside of your project at the following location: `/android/app/google-services.json.` + +4. To allow Firebase on Android to use the credentials, the `google-services` plugin must be enabled on the project. This requires modification to two files in the Android directory. Add the highlighted lines in the relevant files: + +```groovy title="/android/build.gradle" +buildscript { + dependencies { + // ... other dependencies + // highlight-next-line + classpath 'com.google.gms:google-services:4.3.15' + } +} +``` + +```groovy title="/android/build.gradle" +apply plugin: 'com.android.application' +// highlight-next-line +apply plugin: 'com.google.gms.google-services' +``` -The SDK automatically processes the incoming push notifications once the setup above is done if the push config has been set using `StreamVideoRN.setPushConfig`. To do this follow the steps below, +:::note -### Add the ability to statically navigate to screens in your app +The **google-services.json** file contains unique and non-secret identifiers of your Firebase project. For more information, see [Understand Firebase Projects](https://firebase.google.com/docs/projects/learn-more#config-files-objects). -When a user taps on the push notification and the JS engine is not ready, they should still be able to navigate to the screen that shows the active call. You can achieve this by adding the ability to [navigate without the navigation property in the react-navigation library](https://reactnavigation.org/docs/navigating-without-navigation-prop/). +::: -The following is an example implementation of a utility file that has helpers to statically navigate in the app: +### Add declarations in AndroidManifest -```ts title="src/utils/staticNavigation.ts" -import { createNavigationContainerRef } from '@react-navigation/native'; +Add the following in `AndroidManifest.xml`: -import { RootStackParamList } from '../navigation/types'; +```xml title="AndroidManifest.xml" + + + -export const navigationRef = createNavigationContainerRef(); + +``` -/** - * This is used to run the navigation logic from root level even before the navigation is ready - */ -export const staticNavigate = ( - ...navigationArgs: Parameters -) => { - // note the use of setInterval, it is responsible for constantly checking if requirements are met and then navigating - // highlight-start - const intervalId = setInterval(async () => { - // run only when the navigation is ready and add any other requirements (like authentication) - if (navigationRef.isReady() && GlobalState.hasAuthentication) { - clearInterval(intervalId); - navigationRef.navigate(...navigationArgs); +### Request for notification permissions + +At an appropriate place in your app, request for notification permissions from the user. Below is a small example of how to request permissions using [`react-native-permissions`](https://github.com/zoontek/react-native-permissions) library: + + +```js +import { requestNotifications } from 'react-native-permissions'; + +await requestNotifications(['alert', 'sound']); +``` + + +### Add Firebase message handlers +To process the incoming push notifications, the SDK provides the utility functions that you must add to your existing or new Firebase notification listeners. Below is the snippet of how to add the firebase listeners: + +```ts title="src/utils/setFirebaseListeners.ts" +import messaging from '@react-native-firebase/messaging'; +import { + isFirebaseStreamVideoMessage, + firebaseDataHandler, + onAndroidNotifeeEvent, + isNotifeeStreamVideoEvent, +} from '@stream-io/video-react-native-sdk'; + +export const setFirebaseListeners = () => { + // Set up the background message handler + messaging().setBackgroundMessageHandler(async (msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + await firebaseDataHandler(msg.data); + } else { + // your other background notifications (if any) } - }, 300); - // highlight-end + }); + + // on press handlers of background notifications + notifee.onBackgroundEvent(async (event) => { + if (isNotifeeStreamVideoEvent(event)) { + await onAndroidNotifeeEvent({ event, isBackground: true }); + } else { + // your other background notifications (if any) + } + }); + + // Optionally: set up the foreground message handler + messaging().onMessage((msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + firebaseDataHandler(msg.data); + } else { + // your other foreground notifications (if any) + } + }); + // Optionally: on press handlers of foreground notifications + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + onAndroidNotifeeEvent({ event, isBackground: false }); + } else { + // your other foreground notifications (if any) + } + }); }; ``` -When doing this it is _very important_ to set the `navigationRef` in your navigation container as shown below: +**The Firebase message handlers** +* The `onMessage` handler should not be added if you do not want notifications to show up when the app is in the foreground. When the app is in foreground, you would automatically see the incoming call screen. +* The `isFirebaseStreamVideoMessage` method is used to check if this push message is a video related message. And only this needs to be processed by the SDK. +* The `firebaseDataHandler` method is the callback to be invoked to process the message. This callback reads the message and uses the `@notifee/react-native` library to display push notifications. -```ts -import { navigationRef } from './src/utils/staticNavigationUtils'; +**The Notifee event handlers** +* The `onForegroundEvent` handler should not be added if you did not add foreground notifications above. +* The `isNotifeeStreamVideoEvent` method is used to check if the event was a video related notifee event. And only this needs to be processed by the SDK. +* The `onAndroidNotifeeEvent` method is the callback to be invoked to process the event. This callback reads the event and makes sure that the call is accepted or declined. + +:::infoNOTE +If you had disabled the installation of Firebase on iOS, add the above method only for Android using the Platform-specific extensions for React Native. + +For example, say you add the following files in your project: -// highlight-next-line - - -; +``` +setFirebaseListeners.android.ts +setFirebaseListeners.ts ``` -### Setup the push config +The method above must only be added to the file that `.android` extension. The other file must add the method but do nothing like below: -Once we have set up the methods to navigate the app from a static method we are ready to call the `StreamVideoRN.setPushConfig` method. Below is an example of how this method can be called, +```ts title="setFirebaseListeners.ts" +export const setFirebaseListeners = () => { + // do nothing +}; +``` + +This is to ensure that `@react-native-firebase/messaging` is only imported on the Android platform. +::: + +## Setup the push notifications configuration for the SDK + +The SDK automatically processes the incoming push notifications once the setup above is done if the push notifications configuration has been set using `StreamVideoRN.setPushConfig`. Below is an example of how this method can be called, ```ts title="src/utils/setPushConfig.ts" import { @@ -298,7 +333,6 @@ import { import { AndroidImportance } from '@notifee/react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { STREAM_API_KEY } from '../../constants'; -import { staticNavigate } from './staticNavigationUtils'; export function setPushConfig() { StreamVideoRN.setPushConfig({ @@ -329,15 +363,6 @@ export function setPushConfig() { getBody: (_createdUserName: string) => 'Tap to answer the call', }, }, - // add the callback to be executed a call is accepted, used for navigation - navigateAcceptCall: () => { - staticNavigate({ name: 'ActiveCallScreen', params: undefined }); - }, - // add the callback to be executed when a notification is tapped, - // but the user did not press accept or decline, used for navigation - navigateToIncomingCall: () => { - staticNavigate({ name: 'IncomingCallScreen', params: undefined }); - }, // add the async callback to create a video client // for incoming calls in the background on a push notification createStreamVideoClient: async () => { @@ -359,16 +384,26 @@ export function setPushConfig() { } ``` -Now, call the method outside of your application cycle. That is, alongside your `AppRegistry.registerComponent()` method call at the entry point of your application code. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the config as soon as the JS bridge is initialized. Following is an example, +## Call the created methods outside of the application lifecycle + +Call the methods we have created outside of your application cycle. That is, alongside your `AppRegistry.registerComponent()` method call at the entry point of your application code. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the configuration and notification callbacks as soon as the JS bridge is initialized. + +Following is an example, ```js title="index.js" import { AppRegistry } from 'react-native'; +// highlight-next-line import { setPushConfig } from 'src/utils/setPushConfig'; +// highlight-next-line +import { setFirebaseListeners } from 'src/utils/setFirebaseListeners'; import App from './App'; // Set push config // highlight-next-line setPushConfig(); +// Set the firebase listeners +// highlight-next-line +setFirebaseListeners(); AppRegistry.registerComponent('app', () => App); ``` @@ -408,8 +443,16 @@ For all apps being installed on Android 14 and above, the Google Play Store revo If the `USE_FULL_SCREEN_INTENT` permission is not granted, the notification will show up as an expanded heads up notification on the lock screen. ::: +## Show the incoming and outgoing call UI when app is on the foreground + +The last part of the setup for ringing calls is to show the incoming and outgoing call UIs in the app whenever there is a ringing call. If this was not implemented before, please headover to [this page](../../../ringing-calls/#watch-for-incoming-and-outgoing-calls) of our documentation to implement that. + ## Troubleshooting - During development, you may be facing a situation where push notification is shown but its events like accepting or rejecting a call don't work. This is because, during hot module reloading the global event listeners may get de-registered. To properly test during development, make sure that you fully restart the app or test in release mode without the metro packager. - You can check the "Webhook & Push Logs" section in the [Stream Dashboard](https://dashboard.getstream.io/) to see if Notifications were sent by Stream. - If you are still having trouble with Push Notifications, please submit a ticket to us at [support](https://getstream.io/contact/support/). + +### Closed notification behavior on Android + +On Android, users can set certain OS-level settings, usually revolving around performance and battery optimization, that can prevent notifications from being delivered when the app is in a killed state. For example, one such setting is the **Deep Clear** option on OnePlus devices using Android 9 and lower versions. diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/02-expo.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/02-expo.mdx index c04853beb9..8d301dc6b5 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/02-expo.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/03-ringing-setup/02-expo.mdx @@ -148,37 +148,88 @@ React Native Firebase Messaging automatically registers the device with APNs to } ``` -## Setup the push config for the SDK +## Add Firebase message handlers +To process the incoming push notifications, the SDK provides the utility functions that you must add to your existing or new Firebase notification listeners. Below is the snippet of how to add the firebase listeners: -The SDK automatically processes the incoming push notifications once the setup above is done if the push config has been set using `StreamVideoRN.setPushConfig`. To do this follow the steps below, +```ts title="src/utils/setFirebaseListeners.ts" +import messaging from '@react-native-firebase/messaging'; +import { + isFirebaseStreamVideoMessage, + firebaseDataHandler, + onAndroidNotifeeEvent, + isNotifeeStreamVideoEvent, +} from '@stream-io/video-react-native-sdk'; -### Add the ability to statically navigate to screens in your app +export const setFirebaseListeners = () => { + // Set up the background message handler + messaging().setBackgroundMessageHandler(async (msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + await firebaseDataHandler(msg.data); + } else { + // your other background notifications (if any) + } + }); -When a user taps on the push notification and the JS engine is not ready, they should still be able to navigate to the screen that shows the active call. You can achieve this by using [imperative navigation in the expo router](https://docs.expo.dev/routing/navigating-pages/#imperative-navigation). + // on press handlers of background notifications + notifee.onBackgroundEvent(async (event) => { + if (isNotifeeStreamVideoEvent(event)) { + await onAndroidNotifeeEvent({ event, isBackground: true }); + } else { + // your other background notifications (if any) + } + }); -The following is an example implementation of a utility file that has helpers to statically navigate in the app: + // Optionally: set up the foreground message handler + messaging().onMessage((msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + firebaseDataHandler(msg.data); + } else { + // your other foreground notifications (if any) + } + }); + // Optionally: on press handlers of foreground notifications + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + onAndroidNotifeeEvent({ event, isBackground: false }); + } else { + // your other foreground notifications (if any) + } + }); +}; +``` -```ts title="src/utils/staticNavigation.ts" -import { User } from '@stream-io/video-react-native-sdk'; -import { router } from 'expo-router'; +**The Firebase message handlers** +* The `onMessage` handler should not be added if you do not want notifications to show up when the app is in the foreground. When the app is in foreground, you would automatically see the incoming call screen. +* The `isFirebaseStreamVideoMessage` method is used to check if this push message is a video related message. And only this needs to be processed by the SDK. +* The `firebaseDataHandler` method is the callback to be invoked to process the message. This callback reads the message and uses the `@notifee/react-native` library to display push notifications. -/** - * This is used to run the navigation logic from root level - */ -export const staticNavigateToRingingCall = () => { - const intervalId = setInterval(async () => { - // add any requirements here (like authentication) - if (GlobalState.hasAuthentication) { - clearInterval(intervalId); - router.push('/ringing'); - } - }, 300); +**The Notifee event handlers** +* The `onForegroundEvent` handler should not be added if you did not add foreground notifications above. +* The `isNotifeeStreamVideoEvent` method is used to check if the event was a video related notifee event. And only this needs to be processed by the SDK. +* The `onAndroidNotifeeEvent` method is the callback to be invoked to process the event. This callback reads the event and makes sure that the call is accepted or declined. + +:::infoNOTE +If you had disabled the initialization of Firebase on iOS, add the above method only for Android using the Platform-specific extensions for React Native. + +For example, say you add the following files in your project: + +``` +setFirebaseListeners.android.ts +setFirebaseListeners.ts +``` + +The method above must only be added to the file that `.android` extension. The other file must add the method but do nothing like below: + +```ts title="setFirebaseListeners.ts" +export const setFirebaseListeners = () => { + // do nothing }; ``` +::: -### Setup the push config +## Setup the push notifications configuration for the SDK -Once we have set up the methods to navigate the app from a static method we are ready to call the `StreamVideoRN.setPushConfig` method. Below is an example of how this method can be called, +The SDK automatically processes the incoming push notifications once the setup above is done if the push notifications configuration has been set using `StreamVideoRN.setPushConfig`. Below is an example of how this method can be called, ```ts title="src/utils/setPushConfig.ts" import { @@ -188,7 +239,6 @@ import { import { AndroidImportance } from '@notifee/react-native'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { STREAM_API_KEY } from '../../constants'; -import { staticNavigateToRingingCall } from './staticNavigationUtils'; export function setPushConfig() { StreamVideoRN.setPushConfig({ @@ -221,15 +271,6 @@ export function setPushConfig() { getBody: (_createdUserName: string) => 'Tap to answer the call', }, }, - // add the callback to be executed a call is accepted, used for navigation - navigateAcceptCall: () => { - staticNavigateToRingingCall(); - }, - // add the callback to be executed when a notification is tapped, - // but the user did not press accept or decline, used for navigation - navigateToIncomingCall: () => { - staticNavigateToRingingCall(); - }, // add the async callback to create a video client // for incoming calls in the background on a push notification createStreamVideoClient: async () => { @@ -251,18 +292,30 @@ export function setPushConfig() { } ``` -Now, call the method outside of your application cycle. That is inside `index.js`. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the config as soon as the JS bridge is initialized. Following is an example, +## Call the created methods outside of the application lifecycle + +Call the methods we have created outside of your application cycle. That is inside `index.js` or the equivalent entry point file. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the configuration and notification callbacks as soon as the JS bridge is initialized. + +Following is an example, ```js title="index.js" import 'expo-router/entry'; -import { setPushConfig } from './utils/setPushConfig'; +// highlight-next-line +import { setPushConfig } from 'src/utils/setPushConfig'; +// highlight-next-line +import { setFirebaseListeners } from 'src/utils/setFirebaseListeners'; +// Set push config +// highlight-next-line setPushConfig(); +// Set the firebase listeners +// highlight-next-line +setFirebaseListeners(); ``` -## Request for notification permissions +## Request for notification permissions in Android -At an appropriate place in your app, request for notification permissions from the user. Below is a small example of how to request permissions in Expo: +At an appropriate place in your app, request for notification permissions from the user on Android. Below is a small example of how to request permissions in Expo: ```js import {PermissionsAndroid} from 'react-native'; @@ -291,8 +344,16 @@ For all apps being installed on Android 14 and above, the Google Play Store revo If the `USE_FULL_SCREEN_INTENT` permission is not granted, the notification will show up as an expanded heads up notification on the lock screen. ::: +## Show the incoming and outgoing call UI when app is on the foreground + +The last part of the setup for ringing calls is to show the incoming and outgoing call UIs in the app whenever there is a ringing call. If this was not implemented before, please headover to [this page](../../../ringing-calls/#watch-for-incoming-and-outgoing-calls) of our documentation to implement that. + ## Troubleshooting - During development, you may be facing a situation where push notification is shown but its events like accepting or rejecting a call don't work. This is because, during hot module reloading the global event listeners may get de-registered. To properly test during development, make sure that you fully restart the app or test in release mode without the metro packager. - You can check the "Webhook & Push Logs" section in the [Stream Dashboard](https://dashboard.getstream.io/) to see if Notifications were sent by Stream. - If you are still having trouble with Push Notifications, please submit a ticket to us at [support](https://getstream.io/contact/support/). + +### Closed notification behavior on Android + +On Android, users can set certain OS-level settings, usually revolving around performance and battery optimization, that can prevent notifications from being delivered when the app is in a killed state. For example, one such setting is the **Deep Clear** option on OnePlus devices using Android 9 and lower versions. \ No newline at end of file diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/01-react-native.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/01-react-native.mdx index 92f8cb4bf7..d0ea800118 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/01-react-native.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/01-react-native.mdx @@ -98,6 +98,10 @@ In Xcode: Open `Info.plist` file and add the following in `UIBackgroundModes`. B ``` +### Enable push notifications capability + +Enable the Push Notifications capability in the Xcode `Project` > `Signing & Capabilities` pane. + ### Update `AppDelegate.h` At the top of the file, add: @@ -182,6 +186,129 @@ Then add the following lines to `didFinishLaunchingWithOptions`: ### Enable push notifications To receive push notifications, enable the Push Notifications capability in the Xcode `Project` > `Signing & Capabilities` pane. + +## Add Push message handlers +To process the incoming push notifications, the SDK provides the utility functions that you must add to your existing or new notification listeners. + +### Add callbacks to process notifications and displaying it + +When Firebase a push message, it must be processed first. For this we expose handler function from the SDK which reads the message and displays it using the `@notifee/react-native` library. Below is the snippet to add message handlers: + +```ts title="src/utils/setPushMessageHandlers.ts" +import messaging from '@react-native-firebase/messaging'; +import { + isFirebaseStreamVideoMessage, + firebaseDataHandler, +} from '@stream-io/video-react-native-sdk'; + +export const setFirebaseListeners = () => { + // Set up the background message handler for Android + messaging().setBackgroundMessageHandler(async (msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + await firebaseDataHandler(msg.data); + } else { + // your other messages (if any) + } + }); + // Set up the foreground message handler for Android + messaging().onMessage((msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + firebaseDataHandler(msg.data); + } else { + // your other messages (if any) + } + }); +}; +``` + +**The Firebase message handlers** +* The `isFirebaseStreamVideoMessage` method is used to check if this push message is a video related message. And only this needs to be processed by the SDK. +* The `firebaseDataHandler` method is the callback to be invoked to process the message. This callback reads the message and uses the `@notifee/react-native` library to display push notifications. + +:::infoNOTE +If you had disabled the installation of Firebase on iOS, add the above method only for Android using the Platform-specific extensions for React Native. + +For example, say you add the following files in your project: + +``` +setFirebaseListeners.android.ts +setFirebaseListeners.ts +``` + +The method above must only be added to the file that `.android` extension. The other file must add the method but do nothing like below: + +```ts title="setPushMessageListeners.ts" +export const setFirebaseListeners = () => { + // do nothing +}; +``` + +This is to ensure that `@react-native-firebase/messaging` is only imported on the Android platform. +::: + +The `firebaseDataHandler` method is the callback to be invoked to process the message. This callback reads the message and uses the `@notifee/react-native` library to display push notifications. + +### Add notification onPress listeners + +Below is the snippet of how to add the notification onPress listeners for Android using `@notifee/react-native` library: + +```ts title="src/utils/setNotifeeListeners.ts" +import { + isNotifeeStreamVideoEvent, + onAndroidNotifeeEvent, +} from '@stream-io/video-react-native-sdk'; +import { Platform } from 'react-native'; +import notifee from '@notifee/react-native'; + +export const setNotifeeListeners = () => { + // on press handlers of background notifications for Android + notifee.onBackgroundEvent(async (event) => { + if (isNotifeeStreamVideoEvent(event)) { + await onAndroidNotifeeEvent({ event, isBackground: true }); + } else { + // your other notifications (if any) + } + }); + // on press handlers of foreground notifications for Android + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + onAndroidNotifeeEvent({ event, isBackground: false }); + } else { + // your other notifications (if any) + } + }); +}; +``` + +**The Notifee event handlers** +* The `isNotifeeStreamVideoEvent` method is used to check if the event was a video related notifee event. And only this needs to be processed by the SDK. +* The `onAndroidNotifeeEvent` method is the callback to be invoked to process the event. This callback reads the event and makes sure that the call is accepted or declined. + +**Adding handler for iOS** + +Below is the snippet of how to add the notification onPress listeners for iOS using `@react-native-community/push-notification-ios` library. Add the following `useEffect` in the root component of your App, this is most likely in `App.tsx`. + +```ts +import PushNotificationIOS from '@react-native-community/push-notification-ios'; +import { + isPushNotificationiOSStreamVideoEvent, + onPushNotificationiOSStreamVideoEvent, +} from '@stream-io/video-react-native-sdk'; + +useEffect(() => { + PushNotificationIOS.addEventListener('notification', (notification) => { + if (isPushNotificationiOSStreamVideoEvent(notification)) { + onPushNotificationiOSStreamVideoEvent(notification); + } else { + // any other APN notifications + } + }); + return () => { + PushNotificationIOS.removeEventListener('notification'); + }; +}, []); +``` + ## Setup the push config for the SDK The SDK automatically processes the incoming push notifications once the setup above is done if the push config has been set using `StreamVideoRN.setPushConfig`. To do this follow the steps below, @@ -307,11 +434,20 @@ export function setPushConfig() { } ``` -Now, call the method outside of your application cycle. That is, alongside your `AppRegistry.registerComponent()` method call at the entry point of your application code. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the config as soon as the JS bridge is initialized. Following is an example, +## Call the created methods outside of the application lifecycle + +Call the methods we have created outside of your application cycle. That is, alongside your `AppRegistry.registerComponent()` method call at the entry point of your application code. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the config as soon as the JS bridge is initialized. Following is an example, + +Following is an example, ```js title="index.js" import { AppRegistry } from 'react-native'; +// highlight-next-line import { setPushConfig } from 'src/utils/setPushConfig'; +// highlight-next-line +import { setNotifeeListeners } from 'src/utils/setNotifeeListeners'; +// highlight-next-line +import { setFirebaseListeners } from 'src/utils/setFirebaseListeners'; import App from './App'; // Set push config @@ -346,4 +482,8 @@ await StreamVideoRN.onPushLogout(); - During development, you may be facing a situation where push notification is shown but its events like accepting or rejecting a call don't work. This is because, during hot module reloading the global event listeners may get de-registered. To properly test during development, make sure that you fully restart the app or test in release mode without the metro packager. - You can check the "Webhook & Push Logs" section in the [Stream Dashboard](https://dashboard.getstream.io/) to see if Notifications were sent by Stream. -- If you are still having trouble with Push Notifications, please submit a ticket to us at [support](https://getstream.io/contact/support/). \ No newline at end of file +- If you are still having trouble with Push Notifications, please submit a ticket to us at [support](https://getstream.io/contact/support/). + +### Closed notification behavior on Android + +On Android, users can set certain OS-level settings, usually revolving around performance and battery optimization, that can prevent notifications from being delivered when the app is in a killed state. For example, one such setting is the **Deep Clear** option on OnePlus devices using Android 9 and lower versions. \ No newline at end of file diff --git a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/02-expo.mdx b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/02-expo.mdx index 037db7a945..6c84e04f3e 100644 --- a/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/02-expo.mdx +++ b/packages/react-native-sdk/docusaurus/docs/reactnative/06-advanced/04-push-notifications/04-other-than-ringing-setup/02-expo.mdx @@ -71,6 +71,205 @@ In **app.json**, in the `plugins` field, add true to the `enableNonRingingPushNo If Expo EAS build is not used, please do `npx expo prebuild --clean` to generate the native directories again after adding the config plugins. +## Add Push message handlers +To process the incoming push notifications, the SDK provides the utility functions that you must add to your existing or new notification listeners. + +### Add callbacks to process notifications and displaying it + +To process notifications on Android, we can use either `@react-native-firebase` library or `expo-task-manager`. The disadvantage of `expo-task-manager` is that it does not work when push is delivered to an app that has its underlying process in a killed state. So we recommend using the `@react-native-firebase` library. For iOS, we only need the `expo-notifications` library. + + + + +First we have to install the `react-native-firebase` library. + +```bash title=Terminal +yarn add @react-native-firebase/app +yarn add @react-native-firebase/messaging +``` + +Below is the snippet to add message handlers: + +```ts title="src/utils/setPushMessageHandlers.ts" +import messaging from '@react-native-firebase/messaging'; +import { + isFirebaseStreamVideoMessage, + firebaseDataHandler, +} from '@stream-io/video-react-native-sdk'; +import { Platform } from 'react-native'; +import * as Notifications from 'expo-notifications'; + +export const setPushMessageListeners = () => { + // Set up the background message handler for Android + messaging().setBackgroundMessageHandler(async (msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + await firebaseDataHandler(msg.data); + } else { + // your other messages (if any) + } + }); + // Set up the foreground message handler for Android + messaging().onMessage((msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + firebaseDataHandler(msg.data); + } else { + // your other messages (if any) + } + }); + + if (Platform.OS === 'ios') { + // show notification on foreground on iOS + Notifications.setNotificationHandler({ + // example configuration below to show alert and play sound + handleNotification: async (notification) => ({ + shouldShowAlert: true, + shouldPlaySound: true, + shouldSetBadge: false, + }), + }); + } +}; +``` + +**The Firebase message handlers** +* The `isFirebaseStreamVideoMessage` method is used to check if this push message is a video related message. And only this needs to be processed by the SDK. +* The `firebaseDataHandler` method is the callback to be invoked to process the message. This callback reads the message and uses the `@notifee/react-native` library to display push notifications. + +**Disable Firebase initialisation on iOS** + +React Native Firebase Messaging automatically registers the device with APNs to receive remote messages. But since we do not use Firebase on iOS, we can disable it via the `firebase.json` file that we can newly create: + +```js title="/firebase.json" +{ + "react-native": { + "messaging_ios_auto_register_for_remote_messages": false + } +} +``` + + + +First we have to install the `expo-task-manager` library. + +```bash title=Terminal +npx expo install expo-task-manager +``` + +Below is the snippet to add message handlers: + +```ts title="src/utils/setPushMessageHandlers.ts" +const BACKGROUND_NOTIFICATION_TASK = + 'STREAM-VIDEO-BACKGROUND-NOTIFICATION-TASK'; + +import { + isFirebaseStreamVideoMessage, + firebaseDataHandler, + isExpoNotificationStreamVideoEvent, +} from '@stream-io/video-react-native-sdk'; +import { Platform } from 'react-native'; +import * as Notifications from 'expo-notifications'; + +export const setPushMessageListeners = () => { + TaskManager.defineTask( + BACKGROUND_NOTIFICATION_TASK, + ({ data, error }) => { + if (error) { + return; + } + // @ts-ignore + const dataToProcess = data.notification?.data; + if (data?.sender === 'stream.video'} { + firebaseDataHandler(dataToProcess); + } + } + ); + // background handler (does not handle on app killed state) + Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK); + // foreground handler + Notifications.setNotificationHandler({ + handleNotification: async (notification) => { + if (Platform.OS === 'android' && isExpoNotificationStreamVideoEvent(notification)) { + const data = notification.request.trigger.remoteMessage?.data!; + await firebaseDataHandler(data, pushConfig); + // do not show this message, it processed by the above handler + return { shouldShowAlert: false, shouldPlaySound: false, shouldSetBadge: false }; + } else { + // configuration for iOS call notification && your other messages, example below to show alert and play sound + return { shouldShowAlert: true, shouldPlaySound: true, shouldSetBadge: false }; + } + }, + }); +}; +``` + +The `firebaseDataHandler` method is the callback to be invoked to process the message. This callback reads the message and uses the `@notifee/react-native` library to display push notifications. + + + + + +### Add notification button listeners + +Below is the snippet of how to add the notification button listeners: + +```ts title="src/utils/setNotifeeListeners.ts" +import { + isNotifeeStreamVideoEvent, + onAndroidNotifeeEvent, + oniOSNotifeeEvent, +} from '@stream-io/video-react-native-sdk'; +import { Platform } from 'react-native'; +import notifee from '@notifee/react-native'; + +export const setNotifeeListeners = () => { + // on press handlers of background notifications for Android + notifee.onBackgroundEvent(async (event) => { + if (isNotifeeStreamVideoEvent(event)) { + await onAndroidNotifeeEvent({ event, isBackground: true }); + } else { + // your other notifications (if any) + } + }); + // on press handlers of foreground notifications for Android + notifee.onForegroundEvent((event) => { + if (Platform.OS === "android" && isNotifeeStreamVideoEvent(event)) { + onAndroidNotifeeEvent({ event, isBackground: false }); + } else { + // your other notifications (if any) + } + }); +}; +``` + +**The Notifee event handlers** +* The `isNotifeeStreamVideoEvent` method is used to check if the event was a video related notifee event. And only this needs to be processed by the SDK. +* The `onAndroidNotifeeEvent` method is the callback to be invoked to process the event. This callback reads the event and makes sure that the call is accepted or declined. + +**Adding handler for iOS** + +Add the following `useEffect` in the root component of your App, this is most likely in `App.tsx`. + +```ts +import * as Notifications from 'expo-notifications'; + +useEffect(() => { + if (Platform.OS === 'ios') { + const subscription = Notifications.addNotificationReceivedListener( + (notification) => { + if (isExpoNotificationStreamVideoEvent(notification)) { + oniOSExpoNotificationEvent(notification); + } else { + // your other notifications (if any) + } + }, + ); + return () => { + subscription.remove(); + }; + } +}, []); +``` + ## Setup the push config for the SDK The SDK automatically processes the non ringing call push notifications once the setup above is done if the push config has been set using `StreamVideoRN.setPushConfig`. To do this follow the steps below, @@ -188,13 +387,27 @@ export function setPushConfig() { } ``` -Now, call the method outside of your application cycle. That is inside `index.js`. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the config as soon as the JS bridge is initialized. Following is an example, +## Call the created methods outside of the application lifecycle + +Call the methods we have created outside of your application cycle. That is inside `index.js` or the equivalent entry point file. This is because the app can be opened from a dead state through a push notification and in that case, we need to use the configuration and notification callbacks as soon as the JS bridge is initialized. + +Following is an example, ```js title="index.js" import 'expo-router/entry'; -import { setPushConfig } from './utils/setPushConfig'; - +// highlight-next-line +import { setPushConfig } from 'src/utils/setPushConfig'; +// highlight-next-line +import { setNotifeeListeners } from 'src/utils/setNotifeeListeners'; +// highlight-next-line +import { setPushMessageListeners } from 'src/utils/setPushMessageListeners'; + +// highlight-next-line setPushConfig(); +// highlight-next-line +setNotifeeListeners(); +// highlight-next-line +setPushMessageListeners(); ``` ## Request for notification permissions @@ -220,4 +433,8 @@ await StreamVideoRN.onPushLogout(); - During development, you may be facing a situation where push notification is shown but its events like accepting or rejecting a call don't work. This is because, during hot module reloading the global event listeners may get de-registered. To properly test during development, make sure that you fully restart the app or test in release mode without the metro packager. - You can check the "Webhook & Push Logs" section in the [Stream Dashboard](https://dashboard.getstream.io/) to see if Notifications were sent by Stream. -- If you are still having trouble with Push Notifications, please submit a ticket to us at [support](https://getstream.io/contact/support/). \ No newline at end of file +- If you are still having trouble with Push Notifications, please submit a ticket to us at [support](https://getstream.io/contact/support/). + +### Closed notification behavior on Android + +On Android, users can set certain OS-level settings, usually revolving around performance and battery optimization, that can prevent notifications from being delivered when the app is in a killed state. For example, one such setting is the **Deep Clear** option on OnePlus devices using Android 9 and lower versions. \ No newline at end of file diff --git a/packages/react-native-sdk/src/utils/push/android.ts b/packages/react-native-sdk/src/utils/push/android.ts index e1c8634aa6..b7b05663a0 100644 --- a/packages/react-native-sdk/src/utils/push/android.ts +++ b/packages/react-native-sdk/src/utils/push/android.ts @@ -104,10 +104,10 @@ export async function initAndroidPushToken( * Creates notification from the push message data. * For Ringing and Non-Ringing calls. */ -export const firebaseMessagingOnMessageHandler = async ( - message: FirebaseMessagingTypes.RemoteMessage +export const firebaseDataHandler = async ( + data: FirebaseMessagingTypes.RemoteMessage['data'] ) => { - const data = message.data; + if (Platform.OS !== 'android') return; /* Example data from firebase "message": { "data": { @@ -305,6 +305,7 @@ export const onAndroidNotifeeEvent = async ({ event: Event; isBackground: boolean; }) => { + if (Platform.OS !== 'android') return; const { type, detail } = event; const { notification, pressAction } = detail; const notificationId = notification?.id; diff --git a/packages/react-native-sdk/src/utils/push/ios.ts b/packages/react-native-sdk/src/utils/push/ios.ts index e9e00f71a3..1d30609355 100644 --- a/packages/react-native-sdk/src/utils/push/ios.ts +++ b/packages/react-native-sdk/src/utils/push/ios.ts @@ -48,6 +48,7 @@ export const oniOSNotifeeEvent = ({ event: Event; isBackground: boolean; }) => { + if (Platform.OS !== 'ios') return; const pushConfig = StreamVideoRN.getConfig().push; const { type, detail } = event; if (pushConfig && type === EventType.PRESS) { @@ -66,13 +67,13 @@ export function onPushNotificationiOSStreamVideoEvent( ) { const pushNotificationIosLib = getPushNotificationIosLib(); const data = notification.getData(); + const streamPayload = data?.stream as StreamPushPayload; const isClicked = data.userInteraction === 1; const pushConfig = StreamVideoRN.getConfig().push; - if (!isClicked || !pushConfig) { + if (!streamPayload || !isClicked || !pushConfig) { notification.finish(pushNotificationIosLib.FetchResult.NoData); return; } - const streamPayload = data?.stream as StreamPushPayload; // listen to foreground notifications const result = processNonRingingNotificationStreamPayload(streamPayload); if (result) { diff --git a/packages/react-native-sdk/src/utils/push/utils.ts b/packages/react-native-sdk/src/utils/push/utils.ts index 3f636a7c91..ded014fb2b 100644 --- a/packages/react-native-sdk/src/utils/push/utils.ts +++ b/packages/react-native-sdk/src/utils/push/utils.ts @@ -26,9 +26,15 @@ export function isNotifeeStreamVideoEvent(event: Event) { export function isExpoNotificationStreamVideoEvent(event: ExpoNotification) { if (event.request.trigger.type === 'push') { + // iOS const streamPayload = event.request.trigger.payload ?.stream as StreamPushPayload; - return streamPayload?.sender === 'stream.video'; + // Android + const remoteMessageData = event.request.trigger.remoteMessage?.data; + return ( + streamPayload?.sender === 'stream.video' || + remoteMessageData?.sender === 'stream.video' + ); } } diff --git a/sample-apps/react-native/dogfood/src/utils/setFirebaseListeners.android.ts b/sample-apps/react-native/dogfood/src/utils/setFirebaseListeners.android.ts index ae6544f3e8..4f539d709c 100644 --- a/sample-apps/react-native/dogfood/src/utils/setFirebaseListeners.android.ts +++ b/sample-apps/react-native/dogfood/src/utils/setFirebaseListeners.android.ts @@ -1,7 +1,7 @@ import messaging from '@react-native-firebase/messaging'; import { isFirebaseStreamVideoMessage, - firebaseMessagingOnMessageHandler, + firebaseDataHandler, } from '@stream-io/video-react-native-sdk'; export const setFirebaseListeners = () => { @@ -10,7 +10,7 @@ export const setFirebaseListeners = () => { // 2. non-ringing notifications messaging().setBackgroundMessageHandler(async (msg) => { if (isFirebaseStreamVideoMessage(msg)) { - await firebaseMessagingOnMessageHandler(msg); + await firebaseDataHandler(msg.data); } }); // Set up the foreground message handler for @@ -18,7 +18,7 @@ export const setFirebaseListeners = () => { // 2. non-ringing notifications messaging().onMessage((msg) => { if (isFirebaseStreamVideoMessage(msg)) { - firebaseMessagingOnMessageHandler(msg); + firebaseDataHandler(msg.data); } }); }; diff --git a/sample-apps/react-native/expo-video-sample/utils/setFirebaseListeners.android.ts b/sample-apps/react-native/expo-video-sample/utils/setFirebaseListeners.android.ts index ae6544f3e8..4f539d709c 100644 --- a/sample-apps/react-native/expo-video-sample/utils/setFirebaseListeners.android.ts +++ b/sample-apps/react-native/expo-video-sample/utils/setFirebaseListeners.android.ts @@ -1,7 +1,7 @@ import messaging from '@react-native-firebase/messaging'; import { isFirebaseStreamVideoMessage, - firebaseMessagingOnMessageHandler, + firebaseDataHandler, } from '@stream-io/video-react-native-sdk'; export const setFirebaseListeners = () => { @@ -10,7 +10,7 @@ export const setFirebaseListeners = () => { // 2. non-ringing notifications messaging().setBackgroundMessageHandler(async (msg) => { if (isFirebaseStreamVideoMessage(msg)) { - await firebaseMessagingOnMessageHandler(msg); + await firebaseDataHandler(msg.data); } }); // Set up the foreground message handler for @@ -18,7 +18,7 @@ export const setFirebaseListeners = () => { // 2. non-ringing notifications messaging().onMessage((msg) => { if (isFirebaseStreamVideoMessage(msg)) { - firebaseMessagingOnMessageHandler(msg); + firebaseDataHandler(msg.data); } }); };