Skip to content

Commit

Permalink
feat: ios vanilla non ringing push
Browse files Browse the repository at this point in the history
  • Loading branch information
santhoshvai committed Oct 2, 2023
1 parent 2dfefb9 commit 8f5976a
Show file tree
Hide file tree
Showing 19 changed files with 308 additions and 377 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -70,15 +70,28 @@ The **google-services.json** file contains unique and non-secret identifiers of

## iOS-specific setup

### Configure Firebase
### Disable Firebase installation

Follow [this guide at react-native-firebase documentation](https://rnfirebase.io/#3-react-native-cli---ios-setup) to generate credentials for your iOS app and configure Firebase.
We don't use Firebase cloud messaging for iOS in the SDK. Unless Firebase is used for other purposes in your app, you can safely remove it from being installed by iOS and avoid the auto-linking. To do that create a file named `react-native.config.js` in the root of your project and add the following contents:

:::note

We don't use Firebase cloud messaging for iOS in the SDK. But the `react-native-firebase` library will throw an error if iOS credentials are not added.
```js title="react-native.config.js"
module.exports = {
dependencies: {
'@react-native-firebase/app': {
platforms: {
ios: null,
},
},
'@react-native-firebase/messaging': {
platforms: {
ios: null,
},
},
},
};
```

:::
Once this is done, `pod install` must be run again to remove the installed pods.

### Link required libraries for react native callkeep library

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,30 +68,28 @@ The **google-services.json** file contains unique and non-secret identifiers of

## iOS-specific setup

### Configure Firebase
### Disable Firebase installation

Follow [this guide at react-native-firebase documentation](https://rnfirebase.io/#3-react-native-cli---ios-setup) to generate credentials for your iOS app and configure Firebase.
We don't use Firebase cloud messaging for iOS in the SDK. Unless Firebase is used for other purposes in your app, you can safely remove it from being installed by iOS and avoid the auto-linking. To do that create a file named `react-native.config.js` in the root of your project and add the following contents:

:::note

We don't use Firebase cloud messaging for iOS in the SDK. But the `react-native-firebase` library will throw an error if iOS credentials are not added.

:::

### Link required libraries for react native callkeep library

1. In Xcode: Click on `Build Phases` tab, then open `Link Binary With Libraries`.
2. Add `CallKit.framework`
3. Add `Intents.framework` (and mark it Optional).

![Example of how to use link libraries required for callkeep library](../../../assets/06-advanced/04-push-notifications/ios-callkit-libraries-link.png)

### Add header search path for react native callkeep library

1. In Xcode: Click on `Build Settings` tab, then search for `Header Search Paths`.
2. Add `$(SRCROOT)/../node_modules/react-native-callkeep/ios/RNCallKeep`.
```js title="react-native.config.js"
module.exports = {
dependencies: {
'@react-native-firebase/app': {
platforms: {
ios: null,
},
},
'@react-native-firebase/messaging': {
platforms: {
ios: null,
},
},
},
};
```

![Example of how to add header search paths that are required for callkeep library](../../../assets/06-advanced/04-push-notifications/ios-search-paths.png)
Once this is done, `pod install` must be run again to remove the installed pods.

### Add background modes

Expand All @@ -107,93 +105,6 @@ In Xcode: Open `Info.plist` file and add the following in `UIBackgroundModes`. B
### Enable push notifications

To receive push notifications, enable the Push Notifications capability in the Xcode `Project` > `Signing & Capabilities` pane.

### Update AppDelegate

Update `AppDelegate.m` or `AppDelegate.mm` in Xcode with the following parts for iOS support.

#### Add headers

At the top of the file, right after '#import "AppDelegate.h"', add the following headers to import and invoke the methods for the required libraries.

```c
// highlight-start
#import <Firebase.h>
#import "RNCallKeep.h"
#import <PushKit/PushKit.h>
#import "RNVoipPushNotificationManager.h"
// highlight-end
```

#### Initialize on app launch

We need to configure the Firebase library, set up the callkeep library and register VoIP at the app launch. To do this, add the following methods to your existing `didFinishLaunchingWithOptions` method,

```c

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// highlight-next-line
[FIRApp configure];

// highlight-start
NSString *localizedAppName = [[[NSBundle mainBundle] localizedInfoDictionary] objectForKey:@"CFBundleDisplayName"];
NSString *appName = [[[NSBundle mainBundle] infoDictionary]objectForKey :@"CFBundleDisplayName"];
[RNCallKeep setup:@{
@"appName": localizedAppName != nil ? localizedAppName : appName,
@"supportsVideo": @YES,
// pass @YES here if you want the call to be shown in calls history in the built-in dialer app
@"includesCallsInRecents": @NO,
}];
// highlight-end

// highlight-next-line
[RNVoipPushNotificationManager voipRegistration];

// the rest
}
```

#### Add PushKit methods

Add the following method to process the VoIP token from iOS and send it to the `react-native-voip-push-notification` library.

```c
// handle updated push credentials
- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(PKPushType)type {
[RNVoipPushNotificationManager didUpdatePushCredentials:credentials forType:(NSString *)type];
}
```

The final method to add is the one that gets invoked when there is a VoIP push notification from Stream. When there is a push notification and if the app is in the background, we want to display an incoming call notification. Add the following method to achieve this,

```c
// handle incoming pushes
- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(PKPushType)type withCompletionHandler:(void (^)(void))completion {
// send event to JS
[RNVoipPushNotificationManager didReceiveIncomingPushWithPayload:payload forType:(NSString *)type];

// process the payload
NSDictionary *stream = payload.dictionaryPayload[@"stream"];
NSString *uuid = [[NSUUID UUID] UUIDString];
NSString *createdCallerName = stream[@"created_by_display_name"];

// display the incoming call notification
[RNCallKeep reportNewIncomingCall: uuid
handle: createdCallerName
handleType: @"generic"
hasVideo: YES
localizedCallerName: createdCallerName
supportsHolding: YES
supportsDTMF: YES
supportsGrouping: YES
supportsUngrouping: YES
fromPushKit: YES
payload: stream
withCompletionHandler: completion];
}
```

## 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,
Expand Down
5 changes: 5 additions & 0 deletions packages/react-native-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"peerDependencies": {
"@notifee/react-native": ">=7.8.0",
"@react-native-community/netinfo": ">=9.0.0",
"@react-native-community/push-notification-ios": ">=1.11.0",
"@react-native-firebase/app": ">=17.5.0",
"@react-native-firebase/messaging": ">=17.5.0",
"@stream-io/react-native-webrtc": ">=104.0.1",
Expand All @@ -71,6 +72,9 @@
"react-native-voip-push-notification": ">=3.3.1"
},
"peerDependenciesMeta": {
"@react-native-community/push-notification-ios": {
"optional": true
},
"@react-native-firebase/app": {
"optional": true
},
Expand Down Expand Up @@ -102,6 +106,7 @@
"@notifee/react-native": "7.8.0",
"@react-native-community/eslint-config": "^3.2.0",
"@react-native-community/netinfo": "9.3.9",
"@react-native-community/push-notification-ios": "1.11.0",
"@react-native-firebase/app": "17.5.0",
"@react-native-firebase/messaging": "17.5.0",
"@stream-io/react-native-webrtc": "104.0.1",
Expand Down
91 changes: 56 additions & 35 deletions packages/react-native-sdk/src/utils/push/ios.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,35 @@ import {
pushNonRingingCallData$,
} from './rxSubjects';
import { processCallFromPushInBackground } from './utils';
import { getExpoNotificationsLib } from './libs';
import { getExpoNotificationsLib, getPushNotificationIosLib } from './libs';
import { StreamVideoClient } from '@stream-io/video-client';
import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
import notifee, { EventType } from '@notifee/react-native';

type PushConfig = NonNullable<StreamVideoConfig['push']>;

type StreamPayload =
| {
call_cid: string;
type: 'call.ring' | NonRingingPushEvent;
sender: string;
}
| undefined;

function processNonRingingNotificationStreamPayload(
streamPayload: StreamPayload,
) {
if (
streamPayload?.sender === 'stream.video' &&
streamPayload?.type !== 'call.ring'
) {
const cid = streamPayload.call_cid;
const type = streamPayload.type;
pushNonRingingCallData$.next({ cid, type });
return { cid, type };
}
}

export const iosCallkeepAcceptCall = (
call_cid: string | undefined,
callUUIDFromCallkeep: string,
Expand Down Expand Up @@ -69,29 +91,21 @@ export const setupRemoteNotificationsHandleriOS = (pushConfig: PushConfig) => {
if (Platform.OS !== 'ios') {
return;
}
notifee.onForegroundEvent(({ type, detail }) => {
if (type === EventType.PRESS) {
const streamPayload = detail.notification?.data?.stream as
| StreamPayload
| undefined;
const result = processNonRingingNotificationStreamPayload(streamPayload);
if (result) {
pushConfig.onTapNonRingingCallNotification?.(result.cid, result.type);
}
}
});
if (pushConfig.isExpo) {
const Notifications = getExpoNotificationsLib();

notifee.onForegroundEvent(({ type, detail }) => {
if (type === EventType.PRESS) {
const streamPayload = detail.notification?.data?.stream as
| { call_cid: string; type: string; sender: string }
| undefined;
if (streamPayload) {
if (
streamPayload.sender === 'stream.video' &&
streamPayload.type !== 'call.ring'
) {
const cid = streamPayload.call_cid;
const _type = streamPayload.type as NonRingingPushEvent;
pushNonRingingCallData$.next({ cid, type: _type });
pushConfig.onTapNonRingingCallNotification?.(cid, _type);
}
}
}
});

// foreground handler
// foreground handler (just to show the notifications on foreground)
Notifications.setNotificationHandler({
handleNotification: async () => {
return {
Expand Down Expand Up @@ -133,26 +147,33 @@ export async function initIosNonVoipToken(
expoNotificationsLib.addNotificationReceivedListener((event) => {
// listen to foreground notifications
if (event.request.trigger.type === 'push') {
const streamPayload = event.request.trigger.payload?.stream as
| { call_cid: string; type: string; sender: string }
| undefined;
if (streamPayload) {
if (
streamPayload.sender === 'stream.video' &&
streamPayload.type !== 'call.ring'
) {
const cid = streamPayload.call_cid;
const type = streamPayload.type as NonRingingPushEvent;
pushNonRingingCallData$.next({ cid, type });
}
}
const streamPayload = event.request.trigger.payload
?.stream as StreamPayload;
processNonRingingNotificationStreamPayload(streamPayload);
}
});
setUnsubscribeListener(() => {
subscription.remove();
subscriptionForReceive.remove();
});
} else {
// TODO: apn lib for ios
console.log('register ios notifications');
const pushNotificationIosLib = getPushNotificationIosLib();
pushNotificationIosLib.addEventListener('register', (token) => {
setDeviceToken(token);
console.log({ token });
});
pushNotificationIosLib.addEventListener('notification', (notification) => {
const data = notification.getData();
console.log('normal ios notification', { data });
const streamPayload = data?.stream as StreamPayload;
// listen to foreground notifications
processNonRingingNotificationStreamPayload(streamPayload);
});
setUnsubscribeListener(() => {
console.log('unsubscribe ios notifications');
pushNotificationIosLib.removeEventListener('register');
pushNotificationIosLib.removeEventListener('notification');
});
}
}
19 changes: 18 additions & 1 deletion packages/react-native-sdk/src/utils/push/libs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ export type VoipPushNotificationType =
typeof import('react-native-voip-push-notification').default;
export type ExpoNotificationsLib = typeof import('expo-notifications');
export type ExpoTaskManagerLib = typeof import('expo-task-manager');
export type PushNotificationIosLib =
typeof import('@react-native-community/push-notification-ios').default;

let callkeep: RNCallKeepType | undefined;
let messaging: FirebaseMessagingType | undefined;
let voipPushNotification: VoipPushNotificationType | undefined;
let expoNotificationsLib: ExpoNotificationsLib | undefined;
let expoTaskManagerLib: ExpoTaskManagerLib | undefined;
let pushNotificationIosLib: PushNotificationIosLib | undefined;

try {
callkeep = require('react-native-callkeep').default;
Expand All @@ -33,6 +36,11 @@ try {
expoTaskManagerLib = require('expo-task-manager');
} catch (e) {}

try {
pushNotificationIosLib =
require('@react-native-community/push-notification-ios').default;
} catch (e) {}

export function getExpoNotificationsLib() {
if (!expoNotificationsLib) {
throw Error(
Expand All @@ -51,6 +59,15 @@ export function getExpoTaskManagerLib() {
return expoTaskManagerLib;
}

export function getPushNotificationIosLib() {
if (!pushNotificationIosLib) {
throw Error(
'@react-native-community/push-notification-ios library is not installed. Please install it using "yarn add @react-native-community/push-notification-ios" or "npm i @react-native-community/push-notification-ios --save"',
);
}
return pushNotificationIosLib;
}

export function getCallKeepLib() {
if (!callkeep) {
throw Error(
Expand All @@ -72,7 +89,7 @@ export function getFirebaseMessagingLib() {
export function getVoipPushNotificationLib() {
if (!voipPushNotification) {
throw Error(
"react-native-voip-push-notification library is not installed. Please install it using 'yarn add react-native-voip-push-notification' or 'npm install react-native-voip-push-notification'",
"react-native-voip-push-notification library is not installed. Please install it using 'yarn add react-native-voip-push-notification' or 'npm i react-native-voip-push-notification --save'",
);
}
return voipPushNotification;
Expand Down
Loading

0 comments on commit 8f5976a

Please sign in to comment.