Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make push notification listeners more flexible for easier debugging #1542

Merged
merged 13 commits into from
Nov 13, 2024
4 changes: 2 additions & 2 deletions packages/react-native-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,8 @@
"@notifee/react-native": "7.8.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",
"@react-native-firebase/app": "19.2.2",
"@react-native-firebase/messaging": "19.2.2",
"@react-native/eslint-config": "^0.74.84",
"@stream-io/react-native-webrtc": "118.1.0",
"@stream-io/video-filters-react-native": "workspace:^",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import { useEffect } from 'react';
import {
voipCallkeepCallOnForegroundMap$,
voipPushNotificationCallCId$,
} from '../../utils/push/rxSubjects';
} from '../../utils/push/internal/rxSubjects';
import { RxUtils } from '@stream-io/video-client';
import {
iosCallkeepAcceptCall,
iosCallkeepRejectCall,
} from '../../utils/push/ios';
import { getCallKeepLib } from '../../utils/push/libs';
import { StreamVideoRN } from '../../utils';
import { StreamVideoRN } from '../../utils/StreamVideoRN';
import type { StreamVideoConfig } from '../../utils/StreamVideoRN/types';
import {
clearPushWSEventSubscriptions,
processCallFromPushInBackground,
} from '../../utils/push/internal/utils';
import {
pushAcceptedIncomingCallCId$,
voipCallkeepAcceptedCallOnNativeDialerMap$,
} from '../../utils/push/internal/rxSubjects';
import { Platform } from 'react-native';

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

/**
* This hook is used to listen to callkeep events and do the necessary actions
*/
Expand Down Expand Up @@ -62,3 +69,52 @@ export const useIosCallKeepEventsSetupEffect = () => {
};
}, []);
};

const iosCallkeepAcceptCall = (
call_cid: string | undefined,
callUUIDFromCallkeep: string
) => {
if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) {
return;
}
clearPushWSEventSubscriptions();
// to call end callkeep later if ended in app and not through callkeep
voipCallkeepAcceptedCallOnNativeDialerMap$.next({
uuid: callUUIDFromCallkeep,
cid: call_cid,
});
// to process the call in the app
pushAcceptedIncomingCallCId$.next(call_cid);
// no need to keep these references anymore
voipCallkeepCallOnForegroundMap$.next(undefined);
};

const iosCallkeepRejectCall = async (
call_cid: string | undefined,
callUUIDFromCallkeep: string,
pushConfig: PushConfig
) => {
if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) {
return;
}
clearPushWSEventSubscriptions();
// no need to keep these references anymore
voipCallkeepAcceptedCallOnNativeDialerMap$.next(undefined);
voipCallkeepCallOnForegroundMap$.next(undefined);
voipPushNotificationCallCId$.next(undefined);
await processCallFromPushInBackground(pushConfig, call_cid, 'decline');
};

/**
* Helper function to determine if the answer/end call event from callkeep must be processed
* Just checks if we have a valid call_cid and acts as a type guard for call_cid
*/
const shouldProcessCallFromCallkeep = (
call_cid: string | undefined,
callUUIDFromCallkeep: string
): call_cid is string => {
if (!call_cid || !callUUIDFromCallkeep) {
return false;
}
return true;
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getCallKeepLib } from '../../utils/push/libs';
import {
voipCallkeepAcceptedCallOnNativeDialerMap$,
voipCallkeepCallOnForegroundMap$,
} from '../../utils/push/rxSubjects';
} from '../../utils/push/internal/rxSubjects';

const isNonActiveCallingState = (callingState: CallingState) => {
return (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ import { NativeModules } from 'react-native';
import {
canAddPushWSSubscriptionsRef,
shouldCallBeEnded,
} from '../../utils/push/utils';
} from '../../utils/push/internal/utils';
import {
pushUnsubscriptionCallbacks$,
voipPushNotificationCallCId$,
} from '../../utils/push/rxSubjects';
} from '../../utils/push/internal/rxSubjects';
import { RxUtils, getLogger } from '@stream-io/video-client';

let lastVoipToken = { token: '', userId: '' };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {
pushAndroidBackgroundDeliveredIncomingCallCId$,
pushRejectedIncomingCallCId$,
pushTappedIncomingCallCId$,
} from '../../utils/push/rxSubjects';
} from '../../utils/push/internal/rxSubjects';
import { useEffect } from 'react';
import { StreamVideoRN } from '../../utils';
import {
Expand All @@ -12,7 +12,7 @@ import {
} from '@stream-io/video-react-bindings';
import { BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { processCallFromPush } from '../../utils/push/utils';
import { processCallFromPush } from '../../utils/push/internal/utils';
import { StreamVideoClient } from '@stream-io/video-client';
import type { StreamVideoConfig } from '../../utils/StreamVideoRN/types';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { pushNonRingingCallData$ } from '../../utils/push/rxSubjects';
import { pushNonRingingCallData$ } from '../../utils/push/internal/rxSubjects';
import { useEffect } from 'react';
import { StreamVideoRN } from '../../utils';
import {
useConnectedUser,
useStreamVideoClient,
} from '@stream-io/video-react-bindings';
import { filter } from 'rxjs/operators';
import { processNonIncomingCallFromPush } from '../../utils/push/utils';
import { processNonIncomingCallFromPush } from '../../utils/push/internal/utils';

/**
* This hook is used to process the non ringing call data via push notifications using the relevant rxjs subject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ async function startForegroundService(call_cid: string) {
);
return;
}
// channel id is not required for notifee as its only used on android 7 and below here
await notifeeLib.default.displayNotification({
id: call_cid,
title,
Expand Down
2 changes: 1 addition & 1 deletion packages/react-native-sdk/src/providers/StreamCall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useIosCallkeepWithCallingStateEffect } from '../hooks/push/useIosCallke
import {
canAddPushWSSubscriptionsRef,
clearPushWSEventSubscriptions,
} from '../utils/push/utils';
} from '../utils/push/internal/utils';
import { useAndroidKeepCallAliveEffect } from '../hooks/useAndroidKeepCallAliveEffect';
import { AppState, NativeModules, Platform } from 'react-native';
import { shouldDisableIOSLocalVideoOnBackgroundRef } from '../utils/internal/shouldDisableIOSLocalVideoOnBackground';
Expand Down
6 changes: 0 additions & 6 deletions packages/react-native-sdk/src/utils/StreamVideoRN/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { setupFirebaseHandlerAndroid } from '../push/android';
import { StreamVideoConfig } from './types';
import pushLogoutCallbacks from '../internal/pushLogoutCallback';
import { setupRemoteNotificationsHandleriOS } from '../push/ios';
import newNotificationCallbacks, {
NewCallNotificationCallback,
} from '../internal/newNotificationCallbacks';
Expand Down Expand Up @@ -67,10 +65,6 @@ export class StreamVideoRN {
return;
}
this.config.push = pushConfig;
// After getting the config we should setup callkeep events, firebase handler asap to handle incoming calls from a dead state
setupFirebaseHandlerAndroid(pushConfig);
// setup ios handler for non-voip push notifications asap
setupRemoteNotificationsHandleriOS(pushConfig);
}
Comment on lines -70 to -73
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

main effect is here..

SDK will not automatically add listeners, integrators need to add them


static getConfig() {
Expand Down
1 change: 1 addition & 0 deletions packages/react-native-sdk/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ export const getInitialsOfName = (name: string) => {
return initials;
};

export * from './push/index';
export * from './enterPiPAndroid';
export * from './StreamVideoRN';
124 changes: 26 additions & 98 deletions packages/react-native-sdk/src/utils/push/android.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import type {
} from '../StreamVideoRN/types';
import {
getFirebaseMessagingLib,
getFirebaseMessagingLibNoThrow,
getExpoNotificationsLib,
getExpoTaskManagerLib,
getNotifeeLibThrowIfNotInstalledForPush,
NotifeeLib,
} from './libs';
Expand All @@ -25,15 +23,16 @@ import {
pushNonRingingCallData$,
pushUnsubscriptionCallbacks$,
pushAndroidBackgroundDeliveredIncomingCallCId$,
} from './rxSubjects';
} from './internal/rxSubjects';
import {
canAddPushWSSubscriptionsRef,
clearPushWSEventSubscriptions,
processCallFromPushInBackground,
shouldCallBeEnded,
} from './utils';
} from './internal/utils';
import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
import { getAndroidDefaultRingtoneUrl } from '../getAndroidDefaultRingtoneUrl';
import { StreamVideoRN } from '../StreamVideoRN';

const ACCEPT_CALL_ACTION_ID = 'accept';
const DECLINE_CALL_ACTION_ID = 'decline';
Expand All @@ -48,89 +47,6 @@ type Event = Parameters<onBackgroundEventFunctionParams>[0];

let lastFirebaseToken = { token: '', userId: '' };

// EventType = NotifeeLib['EventType'];

/** Setup Firebase push message handler **/
export function setupFirebaseHandlerAndroid(pushConfig: PushConfig) {
if (Platform.OS !== 'android') {
return;
}
if (pushConfig.isExpo) {
const messaging = getFirebaseMessagingLibNoThrow(true);
if (messaging) {
// handles on app killed state in expo, expo-notifications cannot handle that
messaging().setBackgroundMessageHandler(
async (msg) =>
await firebaseMessagingOnMessageHandler(msg.data, pushConfig)
);
messaging().onMessage((msg) =>
firebaseMessagingOnMessageHandler(msg.data, pushConfig)
); // this is to listen to foreground messages, which we dont need for now
} else {
const Notifications = getExpoNotificationsLib();
const TaskManager = getExpoTaskManagerLib();
const BACKGROUND_NOTIFICATION_TASK =
'STREAM-VIDEO-SDK-INTERNAL-BACKGROUND-NOTIFICATION-TASK';

TaskManager.defineTask(
BACKGROUND_NOTIFICATION_TASK,
({ data, error }) => {
if (error) {
return;
}
// @ts-ignore
const dataToProcess = data.notification?.data;
firebaseMessagingOnMessageHandler(dataToProcess, pushConfig);
}
);
// background handler (does not handle on app killed state)
Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);
// foreground handler
Notifications.setNotificationHandler({
handleNotification: async (notification) => {
// @ts-ignore
const trigger = notification?.request?.trigger;
if (trigger.type === 'push') {
const data = trigger?.remoteMessage?.data;
if (data?.sender === 'stream.video') {
await firebaseMessagingOnMessageHandler(data, pushConfig);
return {
shouldShowAlert: false,
shouldPlaySound: false,
shouldSetBadge: false,
};
}
}
return {
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: false,
};
},
});
}
} else {
const messaging = getFirebaseMessagingLib();
messaging().setBackgroundMessageHandler(
async (msg) =>
await firebaseMessagingOnMessageHandler(msg.data, pushConfig)
);
messaging().onMessage((msg) =>
firebaseMessagingOnMessageHandler(msg.data, pushConfig)
); // this is to listen to foreground messages, which we dont need for now
}

// the notification tap handlers are always registered with notifee for both expo and non-expo in android
const notifeeLib = getNotifeeLibThrowIfNotInstalledForPush();
const notifee = notifeeLib.default;
notifee.onBackgroundEvent(async (event) => {
await onNotifeeEvent(event, pushConfig, true);
});
notifee.onForegroundEvent((event) => {
onNotifeeEvent(event, pushConfig, false);
});
}

/** Send token to stream, create notification channel, */
export async function initAndroidPushToken(
client: StreamVideoClient,
Expand Down Expand Up @@ -184,10 +100,14 @@ export async function initAndroidPushToken(
}
}

const firebaseMessagingOnMessageHandler = async (
data: FirebaseMessagingTypes.RemoteMessage['data'],
pushConfig: PushConfig
/**
* Creates notification from the push message data.
* For Ringing and Non-Ringing calls.
*/
export const firebaseMessagingOnMessageHandler = async (
message: FirebaseMessagingTypes.RemoteMessage
) => {
const data = message.data;
/* Example data from firebase
"message": {
"data": {
Expand All @@ -203,7 +123,8 @@ const firebaseMessagingOnMessageHandler = async (
// other stuff
}
*/
if (!data || data.sender !== 'stream.video') {
const pushConfig = StreamVideoRN.getConfig().push;
if (!pushConfig || !data || data.sender !== 'stream.video') {
return;
}

Expand Down Expand Up @@ -377,16 +298,24 @@ const firebaseMessagingOnMessageHandler = async (
}
};

const onNotifeeEvent = async (
event: Event,
pushConfig: PushConfig,
isBackground: boolean
) => {
export const onAndroidNotifeeEvent = async ({
event,
isBackground,
}: {
event: Event;
isBackground: boolean;
}) => {
const { type, detail } = event;
const { notification, pressAction } = detail;
const notificationId = notification?.id;
const data = notification?.data;
if (!data || !notificationId || data.sender !== 'stream.video') {
const pushConfig = StreamVideoRN.getConfig().push;
if (
!pushConfig ||
!data ||
!notificationId ||
data.sender !== 'stream.video'
) {
return;
}

Expand Down Expand Up @@ -443,7 +372,6 @@ const onNotifeeEvent = async (
} else {
const notifeeLib = getNotifeeLibThrowIfNotInstalledForPush();
if (type === notifeeLib.EventType.PRESS) {
pushTappedIncomingCallCId$.next(call_cid);
pushConfig.onTapNonRingingCallNotification?.(
call_cid,
data.type as NonRingingPushEvent
Expand Down
3 changes: 3 additions & 0 deletions packages/react-native-sdk/src/utils/push/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './android';
export * from './ios';
export * from './utils';
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BehaviorSubject } from 'rxjs';
import { NonRingingPushEvent } from '../StreamVideoRN/types';
import { NonRingingPushEvent } from '../../StreamVideoRN/types';

/**
* This rxjs subject is used to store the call cid of the accepted incoming call from push notification
Expand Down
Loading
Loading