From 475715ec0f5d2cbb88727de74b16c9e2e18e3b41 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Mon, 29 Jul 2024 15:29:09 +0200 Subject: [PATCH 01/11] init expo android push utils --- packages/react-native-sdk/package.json | 4 +- packages/react-native-sdk/src/utils/index.ts | 1 + .../src/utils/push/android.ts | 2 +- sample-apps/react-native/dogfood/package.json | 4 +- yarn.lock | 66 ++----------------- 5 files changed, 11 insertions(+), 66 deletions(-) diff --git a/packages/react-native-sdk/package.json b/packages/react-native-sdk/package.json index 854826496c..807ad5a34a 100644 --- a/packages/react-native-sdk/package.json +++ b/packages/react-native-sdk/package.json @@ -118,8 +118,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:^", diff --git a/packages/react-native-sdk/src/utils/index.ts b/packages/react-native-sdk/src/utils/index.ts index d39fb87e1d..066661a00e 100644 --- a/packages/react-native-sdk/src/utils/index.ts +++ b/packages/react-native-sdk/src/utils/index.ts @@ -38,5 +38,6 @@ export const getInitialsOfName = (name: string) => { return initials; }; +export * from './push/android'; export * from './enterPiPAndroid'; export * from './StreamVideoRN'; diff --git a/packages/react-native-sdk/src/utils/push/android.ts b/packages/react-native-sdk/src/utils/push/android.ts index 970a4e106a..42f452dba1 100644 --- a/packages/react-native-sdk/src/utils/push/android.ts +++ b/packages/react-native-sdk/src/utils/push/android.ts @@ -166,7 +166,7 @@ export async function initAndroidPushToken( } } -const firebaseMessagingOnMessageHandler = async ( +export const firebaseMessagingOnMessageHandler = async ( data: FirebaseMessagingTypes.RemoteMessage['data'], pushConfig: PushConfig ) => { diff --git a/sample-apps/react-native/dogfood/package.json b/sample-apps/react-native/dogfood/package.json index caae5ff32d..b0ea4fd531 100644 --- a/sample-apps/react-native/dogfood/package.json +++ b/sample-apps/react-native/dogfood/package.json @@ -24,8 +24,8 @@ "@react-native-clipboard/clipboard": "^1.11.1", "@react-native-community/netinfo": "11.3.0", "@react-native-community/push-notification-ios": "^1.11.0", - "@react-native-firebase/app": "18.8.0", - "@react-native-firebase/messaging": "18.8.0", + "@react-native-firebase/app": "19.2.2", + "@react-native-firebase/messaging": "19.2.2", "@react-native-google-signin/google-signin": "^11.0.0", "@react-navigation/native": "^6.1.10", "@react-navigation/native-stack": "^6.9.18", diff --git a/yarn.lock b/yarn.lock index 87883156b1..ff28f36028 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5419,40 +5419,6 @@ __metadata: languageName: node linkType: hard -"@react-native-firebase/app@npm:17.5.0": - version: 17.5.0 - resolution: "@react-native-firebase/app@npm:17.5.0" - dependencies: - opencollective-postinstall: ^2.0.1 - superstruct: ^0.6.2 - peerDependencies: - expo: ">=47.0.0" - react: "*" - react-native: "*" - peerDependenciesMeta: - expo: - optional: true - checksum: 7d86f43a063561971edf328f7a2a1928660beb9617cbc3c5ba73ab65f359e0cadbc5e276e5654cfa0ef2d25c29f3f98587ebfd58f193be51f472facff3c3024f - languageName: node - linkType: hard - -"@react-native-firebase/app@npm:18.8.0": - version: 18.8.0 - resolution: "@react-native-firebase/app@npm:18.8.0" - dependencies: - opencollective-postinstall: ^2.0.3 - superstruct: ^0.6.2 - peerDependencies: - expo: ">=47.0.0" - react: "*" - react-native: "*" - peerDependenciesMeta: - expo: - optional: true - checksum: 10b4e5aec0dd18a690bc2ba07fe95f0d414f2904574dda930efd050b113629469d2c01e1d849b129ce575e84bbf89b53ffbeee55c51716c4a78e36be463c1fff - languageName: node - linkType: hard - "@react-native-firebase/app@npm:19.2.2": version: 19.2.2 resolution: "@react-native-firebase/app@npm:19.2.2" @@ -5470,28 +5436,6 @@ __metadata: languageName: node linkType: hard -"@react-native-firebase/messaging@npm:17.5.0": - version: 17.5.0 - resolution: "@react-native-firebase/messaging@npm:17.5.0" - peerDependencies: - "@react-native-firebase/app": 17.5.0 - checksum: 296dad0335d18e262569c4edc6b62d009fc15887b3a9801f7d49a64eb091cd86548b940cbd1e993d649fb7e14282b1e910b221e701adb8213305580db578411a - languageName: node - linkType: hard - -"@react-native-firebase/messaging@npm:18.8.0": - version: 18.8.0 - resolution: "@react-native-firebase/messaging@npm:18.8.0" - peerDependencies: - "@react-native-firebase/app": 18.8.0 - expo: ">=47.0.0" - peerDependenciesMeta: - expo: - optional: true - checksum: 4a6e744579954310557a56c843049283e8478185609d78409ad3949dbfe0e01007b6fb724496308dec9131266a55d068a0828587476df54ddfc3adc8a1013d29 - languageName: node - linkType: hard - "@react-native-firebase/messaging@npm:19.2.2": version: 19.2.2 resolution: "@react-native-firebase/messaging@npm:19.2.2" @@ -7205,8 +7149,8 @@ __metadata: "@react-native-clipboard/clipboard": ^1.11.1 "@react-native-community/netinfo": 11.3.0 "@react-native-community/push-notification-ios": ^1.11.0 - "@react-native-firebase/app": 18.8.0 - "@react-native-firebase/messaging": 18.8.0 + "@react-native-firebase/app": 19.2.2 + "@react-native-firebase/messaging": 19.2.2 "@react-native-google-signin/google-signin": ^11.0.0 "@react-native/babel-preset": 0.73.21 "@react-native/eslint-config": 0.74.84 @@ -7266,8 +7210,8 @@ __metadata: "@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-client": "workspace:^" @@ -20431,7 +20375,7 @@ __metadata: languageName: node linkType: hard -"opencollective-postinstall@npm:^2.0.1, opencollective-postinstall@npm:^2.0.3": +"opencollective-postinstall@npm:^2.0.3": version: 2.0.3 resolution: "opencollective-postinstall@npm:2.0.3" bin: From bf0f8996a9e3dd9e8477836088e39baea65e9f09 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Mon, 29 Jul 2024 15:35:20 +0200 Subject: [PATCH 02/11] revert - test import --- .../dogfood/src/components/ActiveCall.tsx | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index 07ec207fbe..9fcbf6bdb3 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -3,7 +3,10 @@ import { useCall, CallContent, CallControlProps, + StreamVideoRN, + firebaseMessagingOnMessageHandler, } from '@stream-io/video-react-native-sdk'; + import { ActivityIndicator, StyleSheet } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { ParticipantsInfoList } from './ParticipantsInfoList'; @@ -12,6 +15,14 @@ import { CallControlsComponentProps, } from './CallControlsComponent'; import { useOrientation } from '../hooks/useOrientation'; +import messaging from '@react-native-firebase/messaging'; + +messaging().setBackgroundMessageHandler(async (msg) => { + const push = StreamVideoRN.getConfig().push; + if (push) { + await firebaseMessagingOnMessageHandler(msg.data, push); + } +}); type ActiveCallProps = CallControlsComponentProps & { onBackPressed?: () => void; From 1f865acb8c7e26135bc15a873931ed4efba67e92 Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Tue, 29 Oct 2024 21:31:02 +0100 Subject: [PATCH 03/11] refactored --- .../push/useIosCallKeepEventsSetupEffect.ts | 68 +++++- .../useIosCallkeepWithCallingStateEffect.ts | 2 +- .../push/useIosVoipPushEventsSetupEffect.ts | 4 +- .../hooks/push/useProcessPushCallEffect.ts | 4 +- .../useProcessPushNonRingingCallEffect.ts | 4 +- .../hooks/useAndroidKeepCallAliveEffect.ts | 1 + .../src/providers/StreamCall.tsx | 2 +- .../src/utils/StreamVideoRN/index.ts | 6 - packages/react-native-sdk/src/utils/index.ts | 2 +- .../src/utils/push/android.ts | 122 +++------- .../react-native-sdk/src/utils/push/index.ts | 3 + .../utils/push/{ => internal}/rxSubjects.ts | 2 +- .../src/utils/push/internal/utils.ts | 174 +++++++++++++++ .../react-native-sdk/src/utils/push/ios.ts | 164 +++++--------- .../src/utils/push/libs/expoNotifications.ts | 4 + .../utils/push/libs/iosPushNotification.ts | 4 + .../react-native-sdk/src/utils/push/utils.ts | 211 ++++-------------- sample-apps/react-native/dogfood/App.tsx | 16 ++ .../dogfood/src/components/ActiveCall.tsx | 10 - .../dogfood/src/utils/setPushConfig.ts | 49 ++++ .../expo-video-sample/app/index.tsx | 8 +- .../expo-video-sample/utils/setPushConfig.ts | 61 ++++- 22 files changed, 507 insertions(+), 414 deletions(-) create mode 100644 packages/react-native-sdk/src/utils/push/index.ts rename packages/react-native-sdk/src/utils/push/{ => internal}/rxSubjects.ts (97%) create mode 100644 packages/react-native-sdk/src/utils/push/internal/utils.ts diff --git a/packages/react-native-sdk/src/hooks/push/useIosCallKeepEventsSetupEffect.ts b/packages/react-native-sdk/src/hooks/push/useIosCallKeepEventsSetupEffect.ts index 88e2527837..3781630505 100644 --- a/packages/react-native-sdk/src/hooks/push/useIosCallKeepEventsSetupEffect.ts +++ b/packages/react-native-sdk/src/hooks/push/useIosCallKeepEventsSetupEffect.ts @@ -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; + /** * This hook is used to listen to callkeep events and do the necessary actions */ @@ -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; +}; diff --git a/packages/react-native-sdk/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts b/packages/react-native-sdk/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts index 6e48e1678e..ccdf7a6312 100644 --- a/packages/react-native-sdk/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts +++ b/packages/react-native-sdk/src/hooks/push/useIosCallkeepWithCallingStateEffect.ts @@ -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 ( diff --git a/packages/react-native-sdk/src/hooks/push/useIosVoipPushEventsSetupEffect.ts b/packages/react-native-sdk/src/hooks/push/useIosVoipPushEventsSetupEffect.ts index 7335ef15bd..d5d02773ad 100644 --- a/packages/react-native-sdk/src/hooks/push/useIosVoipPushEventsSetupEffect.ts +++ b/packages/react-native-sdk/src/hooks/push/useIosVoipPushEventsSetupEffect.ts @@ -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: string | undefined = ''; diff --git a/packages/react-native-sdk/src/hooks/push/useProcessPushCallEffect.ts b/packages/react-native-sdk/src/hooks/push/useProcessPushCallEffect.ts index 7941d92a2f..3cb6c465f9 100644 --- a/packages/react-native-sdk/src/hooks/push/useProcessPushCallEffect.ts +++ b/packages/react-native-sdk/src/hooks/push/useProcessPushCallEffect.ts @@ -3,7 +3,7 @@ import { pushRejectedIncomingCallCId$, pushTappedIncomingCallCId$, pushAndroidBackgroundDeliveredIncomingCallCId$, -} from '../../utils/push/rxSubjects'; +} from '../../utils/push/internal/rxSubjects'; import { useEffect } from 'react'; import { StreamVideoRN } from '../../utils'; import { @@ -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'; diff --git a/packages/react-native-sdk/src/hooks/push/useProcessPushNonRingingCallEffect.ts b/packages/react-native-sdk/src/hooks/push/useProcessPushNonRingingCallEffect.ts index 7f8a5d3b11..2d2b27688a 100644 --- a/packages/react-native-sdk/src/hooks/push/useProcessPushNonRingingCallEffect.ts +++ b/packages/react-native-sdk/src/hooks/push/useProcessPushNonRingingCallEffect.ts @@ -1,4 +1,4 @@ -import { pushNonRingingCallData$ } from '../../utils/push/rxSubjects'; +import { pushNonRingingCallData$ } from '../../utils/push/internal/rxSubjects'; import { useEffect } from 'react'; import { StreamVideoRN } from '../../utils'; import { @@ -6,7 +6,7 @@ import { 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 diff --git a/packages/react-native-sdk/src/hooks/useAndroidKeepCallAliveEffect.ts b/packages/react-native-sdk/src/hooks/useAndroidKeepCallAliveEffect.ts index dd985eac71..c3c8eaa6c3 100644 --- a/packages/react-native-sdk/src/hooks/useAndroidKeepCallAliveEffect.ts +++ b/packages/react-native-sdk/src/hooks/useAndroidKeepCallAliveEffect.ts @@ -32,6 +32,7 @@ async function startForegroundService(call_cid: string) { ); return; } + // NOTE: no need for channel-id here as we are only doing this on Android 7 and below await notifee.displayNotification({ id: call_cid, title, diff --git a/packages/react-native-sdk/src/providers/StreamCall.tsx b/packages/react-native-sdk/src/providers/StreamCall.tsx index 29e103769c..1d1412ae3f 100644 --- a/packages/react-native-sdk/src/providers/StreamCall.tsx +++ b/packages/react-native-sdk/src/providers/StreamCall.tsx @@ -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'; diff --git a/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts b/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts index f115c37d25..72b0a98137 100644 --- a/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts +++ b/packages/react-native-sdk/src/utils/StreamVideoRN/index.ts @@ -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'; @@ -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); } static getConfig() { diff --git a/packages/react-native-sdk/src/utils/index.ts b/packages/react-native-sdk/src/utils/index.ts index 066661a00e..9f15b7f81c 100644 --- a/packages/react-native-sdk/src/utils/index.ts +++ b/packages/react-native-sdk/src/utils/index.ts @@ -38,6 +38,6 @@ export const getInitialsOfName = (name: string) => { return initials; }; -export * from './push/android'; +export * from './push/index'; export * from './enterPiPAndroid'; export * from './StreamVideoRN'; diff --git a/packages/react-native-sdk/src/utils/push/android.ts b/packages/react-native-sdk/src/utils/push/android.ts index 42f452dba1..9e2494e71c 100644 --- a/packages/react-native-sdk/src/utils/push/android.ts +++ b/packages/react-native-sdk/src/utils/push/android.ts @@ -3,7 +3,6 @@ import notifee, { Event, AndroidCategory, } from '@notifee/react-native'; -import { FirebaseMessagingTypes } from '@react-native-firebase/messaging'; import { Call, RxUtils, @@ -17,9 +16,8 @@ import type { } from '../StreamVideoRN/types'; import { getFirebaseMessagingLib, - getFirebaseMessagingLibNoThrow, getExpoNotificationsLib, - getExpoTaskManagerLib, + FirebaseMessagingTypes, } from './libs'; import { pushAcceptedIncomingCallCId$, @@ -28,100 +26,22 @@ 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'; type PushConfig = NonNullable; -/** 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 - 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, @@ -166,10 +86,14 @@ export async function initAndroidPushToken( } } +/** + * Creates notification from the push message data. + * For Ringing and Non-Ringing calls. + */ export const firebaseMessagingOnMessageHandler = async ( - data: FirebaseMessagingTypes.RemoteMessage['data'], - pushConfig: PushConfig + message: FirebaseMessagingTypes.RemoteMessage ) => { + const data = message.data; /* Example data from firebase "message": { "data": { @@ -185,7 +109,8 @@ export const firebaseMessagingOnMessageHandler = async ( // other stuff } */ - if (!data || data.sender !== 'stream.video') { + const pushConfig = StreamVideoRN.getConfig().push; + if (!pushConfig || !data || data.sender !== 'stream.video') { return; } @@ -354,16 +279,24 @@ export 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; } @@ -413,7 +346,8 @@ const onNotifeeEvent = async ( } } else { if (type === EventType.PRESS) { - pushTappedIncomingCallCId$.next(call_cid); + // NOTE: the firebase handler sets pushNonRingingCallData$ so its not necessary to set here + // here we handle only the press listener, so we call the callback pushConfig.onTapNonRingingCallNotification?.( call_cid, data.type as NonRingingPushEvent diff --git a/packages/react-native-sdk/src/utils/push/index.ts b/packages/react-native-sdk/src/utils/push/index.ts new file mode 100644 index 0000000000..f73002b97f --- /dev/null +++ b/packages/react-native-sdk/src/utils/push/index.ts @@ -0,0 +1,3 @@ +export * from './android'; +export * from './ios'; +export * from './utils'; diff --git a/packages/react-native-sdk/src/utils/push/rxSubjects.ts b/packages/react-native-sdk/src/utils/push/internal/rxSubjects.ts similarity index 97% rename from packages/react-native-sdk/src/utils/push/rxSubjects.ts rename to packages/react-native-sdk/src/utils/push/internal/rxSubjects.ts index a1186c291f..cbbe61da15 100644 --- a/packages/react-native-sdk/src/utils/push/rxSubjects.ts +++ b/packages/react-native-sdk/src/utils/push/internal/rxSubjects.ts @@ -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 diff --git a/packages/react-native-sdk/src/utils/push/internal/utils.ts b/packages/react-native-sdk/src/utils/push/internal/utils.ts new file mode 100644 index 0000000000..1f8aa81941 --- /dev/null +++ b/packages/react-native-sdk/src/utils/push/internal/utils.ts @@ -0,0 +1,174 @@ +import { + Call, + RxUtils, + StreamVideoClient, + getLogger, +} from '@stream-io/video-client'; +import type { + NonRingingPushEvent, + StreamVideoConfig, +} from '../../StreamVideoRN/types'; +import { onNewCallNotification } from '../../internal/newNotificationCallbacks'; +import { pushUnsubscriptionCallbacks$ } from './rxSubjects'; + +type PushConfig = NonNullable; + +type CanAddPushWSSubscriptionsRef = { current: boolean }; + +/** + * This function is used to check if the call should be ended based on the push notification + * Useful for callkeep management to end the call if necessary (with reportEndCallWithUUID) + */ +export const shouldCallBeEnded = ( + callFromPush: Call, + created_by_id: string | undefined, + receiver_id: string | undefined +) => { + /* callkeep reasons for ending a call + FAILED: 1, + REMOTE_ENDED: 2, + UNANSWERED: 3, + ANSWERED_ELSEWHERE: 4, + DECLINED_ELSEWHERE: 5, + MISSED: 6 + */ + const callSession = callFromPush.state.session; + const rejected_by = callSession?.rejected_by; + const accepted_by = callSession?.accepted_by; + let mustEndCall = false; + let callkeepReason = 0; + if (created_by_id && rejected_by) { + if (rejected_by[created_by_id]) { + // call was cancelled by the caller + mustEndCall = true; + callkeepReason = 2; + } + } else if (receiver_id && rejected_by) { + if (rejected_by[receiver_id]) { + // call was rejected by the receiver in some other device + mustEndCall = true; + callkeepReason = 5; + } + } else if (receiver_id && accepted_by) { + if (accepted_by[receiver_id]) { + // call was accepted by the receiver in some other device + mustEndCall = true; + callkeepReason = 4; + } + } + return { mustEndCall, callkeepReason }; +}; + +/* An action for the notification or callkeep and app does not have JS context setup yet, so we need to do two steps: + 1. we need to create a new client and connect the user to decline the call + 2. this is because the app is in background state and we don't have a client to get the call and do an action +*/ +export const processCallFromPushInBackground = async ( + pushConfig: PushConfig, + call_cid: string, + action: Parameters[2] +) => { + let videoClient: StreamVideoClient | undefined; + + try { + videoClient = await pushConfig.createStreamVideoClient(); + if (!videoClient) { + return; + } + } catch (e) { + const logger = getLogger(['processCallFromPushInBackground']); + logger('error', 'failed to create video client', e); + return; + } + await processCallFromPush(videoClient, call_cid, action); +}; + +/** + * This function is used process the call from push notifications due to incoming call + * It does the following steps: + * 1. Get the call from the client if present or create a new call + * 2. Fetch the latest state of the call from the server if its not already in ringing state + * 3. Join or leave the call based on the user's action. + */ +export const processCallFromPush = async ( + client: StreamVideoClient, + call_cid: string, + action: 'accept' | 'decline' | 'pressed' | 'backgroundDelivered' +) => { + let callFromPush: Call; + try { + callFromPush = await client.onRingingCall(call_cid); + } catch (e) { + const logger = getLogger(['processCallFromPush']); + logger('error', 'failed to fetch call from push notification', e); + return; + } + // note: when action was pressed or delivered, we dont need to do anything as the only thing is to do is to get the call which adds it to the client + try { + if (action === 'accept') { + await callFromPush.join(); + } else if (action === 'decline') { + await callFromPush.leave({ reject: true }); + } + } catch (e) { + const logger = getLogger(['processCallFromPush']); + logger( + 'error', + `failed to process ${action} call from push notification`, + e + ); + } +}; + +/** + * This function is used process the call from push notifications due to non ringing calls + * It does the following steps: + * 1. Get the call from the client if present or create a new call + * 2. Fetch the latest state of the call from the server if its not already in ringing state + * 3. Call all the callbacks to inform the app about the call + */ +export const processNonIncomingCallFromPush = async ( + client: StreamVideoClient, + call_cid: string, + nonRingingNotificationType: NonRingingPushEvent +) => { + let callFromPush: Call; + try { + const _callFromPush = client.state.calls.find((c) => c.cid === call_cid); + if (_callFromPush) { + callFromPush = _callFromPush; + } else { + // if not it means that WS is not alive when receiving the push notifications and we need to fetch the call + const [callType, callId] = call_cid.split(':'); + callFromPush = client.call(callType as string, callId as string); + await callFromPush.get(); + } + } catch (e) { + const logger = getLogger(['processNonIncomingCallFromPush']); + logger('error', 'failed to fetch call from push notification', e); + return; + } + onNewCallNotification(callFromPush, nonRingingNotificationType); +}; + +/** + * This function is used to clear all the push related WS subscriptions + * note: events are subscribed in push for accept/decline through WS + */ +export const clearPushWSEventSubscriptions = () => { + const unsubscriptionCallbacks = RxUtils.getCurrentValue( + pushUnsubscriptionCallbacks$ + ); + if (unsubscriptionCallbacks) { + unsubscriptionCallbacks.forEach((cb) => cb()); + } + pushUnsubscriptionCallbacks$.next(undefined); +}; + +/** + * This ref is used to check if the push WS subscriptions can be added + * It is used to avoid adding the push WS subscriptions when the client is connected to WS in the foreground + */ +export const canAddPushWSSubscriptionsRef: CanAddPushWSSubscriptionsRef = { + current: true, +}; diff --git a/packages/react-native-sdk/src/utils/push/ios.ts b/packages/react-native-sdk/src/utils/push/ios.ts index 62d6c7303e..9ab6a7047d 100644 --- a/packages/react-native-sdk/src/utils/push/ios.ts +++ b/packages/react-native-sdk/src/utils/push/ios.ts @@ -1,36 +1,22 @@ import { Platform } from 'react-native'; -import type { - NonRingingPushEvent, - StreamVideoConfig, -} from '../StreamVideoRN/types'; +import type { StreamVideoConfig } from '../StreamVideoRN/types'; +import { pushNonRingingCallData$ } from './internal/rxSubjects'; import { - pushAcceptedIncomingCallCId$, - voipPushNotificationCallCId$, - voipCallkeepCallOnForegroundMap$, - voipCallkeepAcceptedCallOnNativeDialerMap$, - pushNonRingingCallData$, -} from './rxSubjects'; -import { - clearPushWSEventSubscriptions, - processCallFromPushInBackground, -} from './utils'; -import { getExpoNotificationsLib, getPushNotificationIosLib } from './libs'; + ExpoNotification, + getExpoNotificationsLib, + getPushNotificationIosLib, + PushNotificationiOSType, +} from './libs'; import { StreamVideoClient, getLogger } from '@stream-io/video-client'; import { setPushLogoutCallback } from '../internal/pushLogoutCallback'; -import notifee, { EventType } from '@notifee/react-native'; +import { EventType, Event } from '@notifee/react-native'; +import { StreamVideoRN } from '../StreamVideoRN'; +import { StreamPushPayload } from './utils'; type PushConfig = NonNullable; -type StreamPayload = - | { - call_cid: string; - type: 'call.ring' | NonRingingPushEvent; - sender: string; - } - | undefined; - function processNonRingingNotificationStreamPayload( - streamPayload: StreamPayload + streamPayload: StreamPushPayload ) { if ( streamPayload?.sender === 'stream.video' && @@ -43,85 +29,55 @@ function processNonRingingNotificationStreamPayload( } } -export 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); -}; - -export const iosCallkeepRejectCall = async ( - call_cid: string | undefined, - callUUIDFromCallkeep: string, - pushConfig: PushConfig -) => { - if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) { - return; +export const oniOSExpoNotificationEvent = (event: ExpoNotification) => { + const pushConfig = StreamVideoRN.getConfig().push; + if (pushConfig) { + if (event.request.trigger.type === 'push') { + const streamPayload = event.request.trigger.payload + ?.stream as StreamPushPayload; + processNonRingingNotificationStreamPayload(streamPayload); + } } - 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; +export const oniOSNotifeeEvent = ({ + event, +}: { + event: Event; + isBackground: boolean; +}) => { + const pushConfig = StreamVideoRN.getConfig().push; + const { type, detail } = event; + if (pushConfig && type === EventType.PRESS) { + const streamPayload = detail.notification?.data?.stream as + | StreamPushPayload + | undefined; + const result = processNonRingingNotificationStreamPayload(streamPayload); + if (result) { + pushConfig.onTapNonRingingCallNotification?.(result.cid, result.type); + } } - return true; }; -export const setupRemoteNotificationsHandleriOS = (pushConfig: PushConfig) => { - if (Platform.OS !== 'ios') { +export function onPushNotificationiOSStreamVideoEvent( + notification: PushNotificationiOSType +) { + const pushNotificationIosLib = getPushNotificationIosLib(); + const data = notification.getData(); + const isClicked = data.userInteraction === 1; + const pushConfig = StreamVideoRN.getConfig().push; + if (!isClicked || !pushConfig) { + notification.finish(pushNotificationIosLib.FetchResult.NoData); 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(); - - // foreground handler (just to show the notifications on foreground) - Notifications.setNotificationHandler({ - handleNotification: async () => { - return { - shouldShowAlert: true, - shouldPlaySound: true, - shouldSetBadge: false, - }; - }, - }); + const streamPayload = data?.stream as StreamPushPayload; + // listen to foreground notifications + const result = processNonRingingNotificationStreamPayload(streamPayload); + if (result) { + pushConfig.onTapNonRingingCallNotification?.(result.cid, result.type); } -}; + notification.finish(pushNotificationIosLib.FetchResult.NoData); +} /** Send token to stream */ export async function initIosNonVoipToken( @@ -158,34 +114,16 @@ export async function initIosNonVoipToken( setDeviceToken(devicePushToken.data); } ); - const subscriptionForReceive = - expoNotificationsLib.addNotificationReceivedListener((event) => { - // listen to foreground notifications - if (event.request.trigger.type === 'push') { - const streamPayload = event.request.trigger.payload - ?.stream as StreamPayload; - processNonRingingNotificationStreamPayload(streamPayload); - } - }); setUnsubscribeListener(() => { subscription.remove(); - subscriptionForReceive.remove(); }); } else { const pushNotificationIosLib = getPushNotificationIosLib(); pushNotificationIosLib.addEventListener('register', (token) => { setDeviceToken(token); }); - pushNotificationIosLib.addEventListener('notification', (notification) => { - const data = notification.getData(); - const streamPayload = data?.stream as StreamPayload; - // listen to foreground notifications - processNonRingingNotificationStreamPayload(streamPayload); - notification.finish(pushNotificationIosLib.FetchResult.NoData); - }); setUnsubscribeListener(() => { pushNotificationIosLib.removeEventListener('register'); - pushNotificationIosLib.removeEventListener('notification'); }); } } diff --git a/packages/react-native-sdk/src/utils/push/libs/expoNotifications.ts b/packages/react-native-sdk/src/utils/push/libs/expoNotifications.ts index d62caad67f..72d2a14aa1 100644 --- a/packages/react-native-sdk/src/utils/push/libs/expoNotifications.ts +++ b/packages/react-native-sdk/src/utils/push/libs/expoNotifications.ts @@ -1,5 +1,9 @@ export type ExpoNotificationsLib = typeof import('expo-notifications'); +import type { Notification } from 'expo-notifications'; + +export type ExpoNotification = Notification; + let expoNotificationsLib: ExpoNotificationsLib | undefined; try { diff --git a/packages/react-native-sdk/src/utils/push/libs/iosPushNotification.ts b/packages/react-native-sdk/src/utils/push/libs/iosPushNotification.ts index ff8bc44c87..17c3199e53 100644 --- a/packages/react-native-sdk/src/utils/push/libs/iosPushNotification.ts +++ b/packages/react-native-sdk/src/utils/push/libs/iosPushNotification.ts @@ -3,6 +3,10 @@ export type PushNotificationIosLib = let pushNotificationIosLib: PushNotificationIosLib | undefined; +import type { PushNotification } from '@react-native-community/push-notification-ios'; + +export type PushNotificationiOSType = PushNotification; + try { pushNotificationIosLib = require('@react-native-community/push-notification-ios').default; diff --git a/packages/react-native-sdk/src/utils/push/utils.ts b/packages/react-native-sdk/src/utils/push/utils.ts index f9a07db0f7..3f636a7c91 100644 --- a/packages/react-native-sdk/src/utils/push/utils.ts +++ b/packages/react-native-sdk/src/utils/push/utils.ts @@ -1,174 +1,41 @@ -import { - Call, - RxUtils, - StreamVideoClient, - getLogger, -} from '@stream-io/video-client'; -import type { - NonRingingPushEvent, - StreamVideoConfig, -} from '../StreamVideoRN/types'; -import { onNewCallNotification } from '../internal/newNotificationCallbacks'; -import { pushUnsubscriptionCallbacks$ } from './rxSubjects'; - -type PushConfig = NonNullable; - -type CanAddPushWSSubscriptionsRef = { current: boolean }; - -/** - * This function is used to check if the call should be ended based on the push notification - * Useful for callkeep management to end the call if necessary (with reportEndCallWithUUID) - */ -export const shouldCallBeEnded = ( - callFromPush: Call, - created_by_id: string | undefined, - receiver_id: string | undefined -) => { - /* callkeep reasons for ending a call - FAILED: 1, - REMOTE_ENDED: 2, - UNANSWERED: 3, - ANSWERED_ELSEWHERE: 4, - DECLINED_ELSEWHERE: 5, - MISSED: 6 - */ - const callSession = callFromPush.state.session; - const rejected_by = callSession?.rejected_by; - const accepted_by = callSession?.accepted_by; - let mustEndCall = false; - let callkeepReason = 0; - if (created_by_id && rejected_by) { - if (rejected_by[created_by_id]) { - // call was cancelled by the caller - mustEndCall = true; - callkeepReason = 2; - } - } else if (receiver_id && rejected_by) { - if (rejected_by[receiver_id]) { - // call was rejected by the receiver in some other device - mustEndCall = true; - callkeepReason = 5; - } - } else if (receiver_id && accepted_by) { - if (accepted_by[receiver_id]) { - // call was accepted by the receiver in some other device - mustEndCall = true; - callkeepReason = 4; +import { Event } from '@notifee/react-native'; +import { FirebaseMessagingTypes } from './libs/firebaseMessaging'; +import { ExpoNotification } from './libs/expoNotifications'; +import { NonRingingPushEvent } from '../StreamVideoRN/types'; +import { PushNotificationiOSType } from './libs/iosPushNotification'; + +export type StreamPushPayload = + | { + call_cid: string; + type: 'call.ring' | NonRingingPushEvent; + sender: string; } + | undefined; + +export function isFirebaseStreamVideoMessage( + message: FirebaseMessagingTypes.RemoteMessage +) { + return message.data?.sender === 'stream.video'; +} + +export function isNotifeeStreamVideoEvent(event: Event) { + const { detail } = event; + const { notification } = detail; + return notification?.data?.sender === 'stream.video'; +} + +export function isExpoNotificationStreamVideoEvent(event: ExpoNotification) { + if (event.request.trigger.type === 'push') { + const streamPayload = event.request.trigger.payload + ?.stream as StreamPushPayload; + return streamPayload?.sender === 'stream.video'; } - return { mustEndCall, callkeepReason }; -}; - -/* An action for the notification or callkeep and app does not have JS context setup yet, so we need to do two steps: - 1. we need to create a new client and connect the user to decline the call - 2. this is because the app is in background state and we don't have a client to get the call and do an action -*/ -export const processCallFromPushInBackground = async ( - pushConfig: PushConfig, - call_cid: string, - action: Parameters[2] -) => { - let videoClient: StreamVideoClient | undefined; - - try { - videoClient = await pushConfig.createStreamVideoClient(); - if (!videoClient) { - return; - } - } catch (e) { - const logger = getLogger(['processCallFromPushInBackground']); - logger('error', 'failed to create video client', e); - return; - } - await processCallFromPush(videoClient, call_cid, action); -}; - -/** - * This function is used process the call from push notifications due to incoming call - * It does the following steps: - * 1. Get the call from the client if present or create a new call - * 2. Fetch the latest state of the call from the server if its not already in ringing state - * 3. Join or leave the call based on the user's action. - */ -export const processCallFromPush = async ( - client: StreamVideoClient, - call_cid: string, - action: 'accept' | 'decline' | 'pressed' | 'backgroundDelivered' -) => { - let callFromPush: Call; - try { - callFromPush = await client.onRingingCall(call_cid); - } catch (e) { - const logger = getLogger(['processCallFromPush']); - logger('error', 'failed to fetch call from push notification', e); - return; - } - // note: when action was pressed or delivered, we dont need to do anything as the only thing is to do is to get the call which adds it to the client - try { - if (action === 'accept') { - await callFromPush.join(); - } else if (action === 'decline') { - await callFromPush.leave({ reject: true }); - } - } catch (e) { - const logger = getLogger(['processCallFromPush']); - logger( - 'error', - `failed to process ${action} call from push notification`, - e - ); - } -}; - -/** - * This function is used process the call from push notifications due to non ringing calls - * It does the following steps: - * 1. Get the call from the client if present or create a new call - * 2. Fetch the latest state of the call from the server if its not already in ringing state - * 3. Call all the callbacks to inform the app about the call - */ -export const processNonIncomingCallFromPush = async ( - client: StreamVideoClient, - call_cid: string, - nonRingingNotificationType: NonRingingPushEvent -) => { - let callFromPush: Call; - try { - const _callFromPush = client.state.calls.find((c) => c.cid === call_cid); - if (_callFromPush) { - callFromPush = _callFromPush; - } else { - // if not it means that WS is not alive when receiving the push notifications and we need to fetch the call - const [callType, callId] = call_cid.split(':'); - callFromPush = client.call(callType as string, callId as string); - await callFromPush.get(); - } - } catch (e) { - const logger = getLogger(['processNonIncomingCallFromPush']); - logger('error', 'failed to fetch call from push notification', e); - return; - } - onNewCallNotification(callFromPush, nonRingingNotificationType); -}; - -/** - * This function is used to clear all the push related WS subscriptions - * note: events are subscribed in push for accept/decline through WS - */ -export const clearPushWSEventSubscriptions = () => { - const unsubscriptionCallbacks = RxUtils.getCurrentValue( - pushUnsubscriptionCallbacks$ - ); - if (unsubscriptionCallbacks) { - unsubscriptionCallbacks.forEach((cb) => cb()); - } - pushUnsubscriptionCallbacks$.next(undefined); -}; - -/** - * This ref is used to check if the push WS subscriptions can be added - * It is used to avoid adding the push WS subscriptions when the client is connected to WS in the foreground - */ -export const canAddPushWSSubscriptionsRef: CanAddPushWSSubscriptionsRef = { - current: true, -}; +} + +export function isPushNotificationiOSStreamVideoEvent( + notification: PushNotificationiOSType +) { + const data = notification.getData(); + const streamPayload = data?.stream as StreamPushPayload; + return streamPayload?.sender === 'stream.video'; +} diff --git a/sample-apps/react-native/dogfood/App.tsx b/sample-apps/react-native/dogfood/App.tsx index 885b5409e0..6d5a3c295a 100755 --- a/sample-apps/react-native/dogfood/App.tsx +++ b/sample-apps/react-native/dogfood/App.tsx @@ -32,6 +32,11 @@ import { GestureHandlerRootView } from 'react-native-gesture-handler'; import { LogBox, StyleSheet } from 'react-native'; import { LiveStream } from './src/navigators/Livestream'; import { REACT_NATIVE_DOGFOOD_APP_ENVIRONMENT } from '@env'; +import PushNotificationIOS from '@react-native-community/push-notification-ios'; +import { + isPushNotificationiOSStreamVideoEvent, + onPushNotificationiOSStreamVideoEvent, +} from '@stream-io/video-react-native-sdk'; // only enable warning and error logs from webrtc library Logger.enable(`${Logger.ROOT_PREFIX}:(WARN|ERROR)`); @@ -57,6 +62,17 @@ const StackNavigator = () => { useProntoLinkEffect(); useSyncPermissions(); + useEffect(() => { + PushNotificationIOS.addEventListener('notification', (notification) => { + if (isPushNotificationiOSStreamVideoEvent(notification)) { + onPushNotificationiOSStreamVideoEvent(notification); + } + }); + return () => { + PushNotificationIOS.removeEventListener('notification'); + }; + }, []); + let mode; switch (appMode) { case 'Meeting': diff --git a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx index 9fcbf6bdb3..d739b28a83 100644 --- a/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx +++ b/sample-apps/react-native/dogfood/src/components/ActiveCall.tsx @@ -3,8 +3,6 @@ import { useCall, CallContent, CallControlProps, - StreamVideoRN, - firebaseMessagingOnMessageHandler, } from '@stream-io/video-react-native-sdk'; import { ActivityIndicator, StyleSheet } from 'react-native'; @@ -15,14 +13,6 @@ import { CallControlsComponentProps, } from './CallControlsComponent'; import { useOrientation } from '../hooks/useOrientation'; -import messaging from '@react-native-firebase/messaging'; - -messaging().setBackgroundMessageHandler(async (msg) => { - const push = StreamVideoRN.getConfig().push; - if (push) { - await firebaseMessagingOnMessageHandler(msg.data, push); - } -}); type ActiveCallProps = CallControlsComponentProps & { onBackPressed?: () => void; diff --git a/sample-apps/react-native/dogfood/src/utils/setPushConfig.ts b/sample-apps/react-native/dogfood/src/utils/setPushConfig.ts index afb021067d..4298d09010 100644 --- a/sample-apps/react-native/dogfood/src/utils/setPushConfig.ts +++ b/sample-apps/react-native/dogfood/src/utils/setPushConfig.ts @@ -1,12 +1,20 @@ import { StreamVideoClient, StreamVideoRN, + firebaseMessagingOnMessageHandler, + onAndroidNotifeeEvent, + isFirebaseStreamVideoMessage, + isNotifeeStreamVideoEvent, + oniOSNotifeeEvent, } from '@stream-io/video-react-native-sdk'; import { AndroidImportance } from '@notifee/react-native'; import { staticNavigate } from './staticNavigationUtils'; import { mmkvStorage } from '../contexts/createStoreContext'; import { createToken } from '../modules/helpers/createToken'; import { prontoCallId$ } from '../hooks/useProntoLinkEffect'; +import messaging from '@react-native-firebase/messaging'; +import { Platform } from 'react-native'; +import notifee from '@notifee/react-native'; export function setPushConfig() { StreamVideoRN.setPushConfig({ @@ -63,6 +71,47 @@ export function setPushConfig() { } }, }); + + if (Platform.OS === 'android') { + // Set up the background message handler for + // 1. incoming call notifications + // 2. non-ringing notifications + messaging().setBackgroundMessageHandler(async (msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + await firebaseMessagingOnMessageHandler(msg); + } + }); + // Set up the foreground message handler for + // 1. incoming call notifications + // 2. non-ringing notifications + messaging().onMessage((msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + firebaseMessagingOnMessageHandler(msg); + } + }); + + // on press handlers of background notifications + notifee.onBackgroundEvent(async (event) => { + if (isNotifeeStreamVideoEvent(event)) { + await onAndroidNotifeeEvent({ event, isBackground: true }); + } + }); + // on press handlers of foreground notifications + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + onAndroidNotifeeEvent({ event, isBackground: false }); + } + }); + } + if (Platform.OS === 'ios') { + // on press handlers of foreground notifications for iOS + // note: used only for non-ringing notifications + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + oniOSNotifeeEvent({ event, isBackground: false }); + } + }); + } } /** diff --git a/sample-apps/react-native/expo-video-sample/app/index.tsx b/sample-apps/react-native/expo-video-sample/app/index.tsx index 8728a3ed34..9c3e7bca4a 100644 --- a/sample-apps/react-native/expo-video-sample/app/index.tsx +++ b/sample-apps/react-native/expo-video-sample/app/index.tsx @@ -14,7 +14,7 @@ import * as Notifications from 'expo-notifications'; export default function CreateCallScreen() { useEffect(() => { - const run = async () => { + const requestPermissions = async () => { await Notifications.requestPermissionsAsync(); if (Platform.OS === 'android') { if (Platform.Version > 30) { @@ -24,7 +24,11 @@ export default function CreateCallScreen() { } } }; - run(); + requestPermissions(); + + if (Platform.OS === 'ios') { + Notifications.addNotificationReceivedListener((notification) => {}); + } }, []); return ( diff --git a/sample-apps/react-native/expo-video-sample/utils/setPushConfig.ts b/sample-apps/react-native/expo-video-sample/utils/setPushConfig.ts index 433f8d42c7..60ad5dbcd9 100644 --- a/sample-apps/react-native/expo-video-sample/utils/setPushConfig.ts +++ b/sample-apps/react-native/expo-video-sample/utils/setPushConfig.ts @@ -1,8 +1,16 @@ import { StreamVideoClient, StreamVideoRN, + firebaseMessagingOnMessageHandler, + onAndroidNotifeeEvent, + isFirebaseStreamVideoMessage, + oniOSNotifeeEvent, + isNotifeeStreamVideoEvent, } from '@stream-io/video-react-native-sdk'; -import { AndroidImportance } from '@notifee/react-native'; +import messaging from '@react-native-firebase/messaging'; +import { Platform } from 'react-native'; +import notifee, { AndroidImportance } from '@notifee/react-native'; +import * as Notifications from 'expo-notifications'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { staticNavigateToNonRingingCall, @@ -58,6 +66,57 @@ export function setPushConfig() { staticNavigateToNonRingingCall(); }, }); + if (Platform.OS === 'android') { + // Set up the background message handler for + // 1. incoming call notifications + // 2. non-ringing notifications + messaging().setBackgroundMessageHandler(async (msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + await firebaseMessagingOnMessageHandler(msg); + } + }); + // Set up the foreground message handler for + // 1. incoming call notifications + // 2. non-ringing notifications + messaging().onMessage((msg) => { + if (isFirebaseStreamVideoMessage(msg)) { + firebaseMessagingOnMessageHandler(msg); + } + }); + + // on press handlers of background notifications + notifee.onBackgroundEvent(async (event) => { + if (isNotifeeStreamVideoEvent(event)) { + await onAndroidNotifeeEvent({ event, isBackground: true }); + } + }); + // on press handlers of foreground notifications + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + onAndroidNotifeeEvent({ event, isBackground: false }); + } + }); + } + + if (Platform.OS === 'ios') { + // show notification on foreground + // https://docs.expo.dev/push-notifications/receiving-notifications/#foreground-notification-behavior + Notifications.setNotificationHandler({ + handleNotification: async () => ({ + shouldShowAlert: true, + shouldPlaySound: false, + shouldSetBadge: false, + }), + }); + + // on press handlers of foreground notifications for iOS + // note: used only for non-ringing notifications + notifee.onForegroundEvent((event) => { + if (isNotifeeStreamVideoEvent(event)) { + oniOSNotifeeEvent({ event, isBackground: false }); + } + }); + } } /** From 548ded8ae10f9945e42ecb8becf45c1352406a6e Mon Sep 17 00:00:00 2001 From: Santhosh Vaiyapuri Date: Tue, 29 Oct 2024 21:56:00 +0100 Subject: [PATCH 04/11] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit 1e24dafe507e846288fae3fa137a3834983a783d Author: Zachary Ebenfeld Date: Tue Oct 29 05:44:06 2024 -0400 docs: changed call[0] to calls[0] in docs (#1540) commit 4efe03b2d9d2731857abbd535ab16b9cfcaa2c88 Author: GitHub Actions Bot <> Date: Mon Oct 28 14:37:14 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.4.5 commit 55b7eacff7ce324d692133edb64ad6a761c9c315 Author: GitHub Actions Bot <> Date: Mon Oct 28 14:36:50 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.2.5 commit ba50ee72d83fd96584fbb58135815b9b53e47dea Author: GitHub Actions Bot <> Date: Mon Oct 28 14:36:31 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.7.3 commit ccbf12f50fe254fabd835bb7859ef1407063f9c4 Author: GitHub Actions Bot <> Date: Mon Oct 28 14:36:22 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.8 commit b0b1e5bdf36b728835b345b80ac23c13cba10d4a Author: GitHub Actions Bot <> Date: Mon Oct 28 14:35:58 2024 +0000 chore(@stream-io/video-client): release version 1.9.3 commit 6274cac2ecf155aa6ce0c6d764229e0e9cd39a6a Author: Matvei Andrienko Date: Mon Oct 28 15:31:31 2024 +0100 fix: make device selection by device id exact (#1538) Somewhere around Chrome v126 a new behavior was introduced, and the `deviceId: string` constraint when querying `getUserMedia` was no longer respected. The default device was always returned instead. Using the `deviceId: { exact: string }` constraint seems to solve the issue. However, it also can cause the `OvercontrainedError` exception when device id is not valid. We handle it by falling back to default device in this case. commit 30686e2db5fd9aa3f9353626cf5ba6327ca9c180 Author: GitHub Actions Bot <> Date: Tue Oct 22 12:22:14 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.4.4 commit d111ba43b18daec0531b0f42869da730d529845a Author: GitHub Actions Bot <> Date: Tue Oct 22 12:21:43 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.2.4 commit eb3afb4dc33289cde0639cc109194971d31f51e0 Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Tue Oct 22 14:18:17 2024 +0200 fix: added workaround for possible multiple createDevice calls on remounting (#1532) small workaround where SDK wont send same tokens again commit 7be61d27f15d1ef3acb5434661ee30d0b9e33e9a Author: Oliver Lazoroski Date: Tue Oct 22 13:38:35 2024 +0200 fix: don't apply participant aspect ratios in screen share mode (#1531) ### Overview Applying portrait mode aspect ratios (9/16, or any similar) breaks the screen-sharing layout (some elements go off-screen). Until we provide a proper solution, we restore to the default aspect ratio once screen sharing is started. In the long run, we should consider separating the screen share layout configuration from the regular one. Context: https://getstream.slack.com/archives/C07KX5D7MFX/p1729017984964169?thread_ts=1727981915.684569&cid=C07KX5D7MFX This PR also includes a few other low-hanging fruits. commit 82cb3e388484d36d6468e55c45d72e461ab38d5a Author: GitHub Actions Bot <> Date: Mon Oct 21 17:30:20 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.4.3 commit d1ba0146b7764d75877fdc29f43b80bdaf67a81f Author: GitHub Actions Bot <> Date: Mon Oct 21 17:29:57 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.2.3 commit a6b1db0a4cf05c05f59c29c9d3f70730a370a8e7 Author: GitHub Actions Bot <> Date: Mon Oct 21 17:29:39 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.7.2 commit c23dd0c013f035044e70db0919085cc21d006bb7 Author: GitHub Actions Bot <> Date: Mon Oct 21 17:29:30 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.7 commit f6ae46ae30523f257ab3d4034381ccb69e92c630 Author: GitHub Actions Bot <> Date: Mon Oct 21 17:29:08 2024 +0000 chore(@stream-io/video-client): release version 1.9.2 commit eac4e4ebd2575f5269f65db7173107d5cafab9bf Author: Kristian Date: Mon Oct 21 19:26:22 2024 +0200 fix(client): invoke call.reject only when reject param specified (#1530) ### Issue Rejecting a call which has already been ended results in 400 error from backend. To prevent this error we should not allow `call.reject()` to be called on `call.ended` event. ### Fix This PR updates the `call.leave` method to make sure that `reject()` can be invoked inside `leave()` only if the reject input param is set to `true`. ```ts leave = async ({ reject = false, // <-- only if this param is explicitly sent with value true reason = 'user is leaving the call', }: CallLeaveOptions = {}) => { ``` commit cc7aa3bc677d906d611f9d400a11a9d07f2b93f3 Author: GitHub Actions Bot <> Date: Fri Oct 18 13:41:06 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.4.2 commit e2076f3008c652550f33a0c8564ea8125ee8a5b5 Author: GitHub Actions Bot <> Date: Fri Oct 18 13:40:43 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.2.2 commit cd3e9e5557a233b5613f0f5558acd1d43856ad86 Author: GitHub Actions Bot <> Date: Fri Oct 18 13:40:24 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.7.1 commit d0a45aacb69c014ac1428c8a7560e9c8fc4b7181 Author: GitHub Actions Bot <> Date: Fri Oct 18 13:40:14 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.6 commit a016665528813b8c4eb219513a59989edbcfadd4 Author: GitHub Actions Bot <> Date: Fri Oct 18 13:39:51 2024 +0000 chore(@stream-io/video-client): release version 1.9.1 commit 96cadd05e995392eac4ec300828d07b287d691a0 Author: Oliver Lazoroski Date: Fri Oct 18 15:37:05 2024 +0200 fix(svc): announce downscaled layers in setPublisher (#1526) Fixes an issue where SVC layers weren't announced correctly to the SFU. Although this is harmless for customers, our internal dashboards may show incorrect data. Context: https://getstream.slack.com/archives/C04ATV49DU3/p1729242858098099 commit aaa17e638312e18f94eb561fc21c69ce5814f37c Author: Kristian Date: Fri Oct 18 14:04:25 2024 +0200 fix(rn-dogfood): Fix modals orientation bug (#1525) - Fixes the modals in dogfood app - before the fix: when the app in is landscape mode and a modal is being open the app is set to portrait mode - after the fix: the modals support landscape mode as well and work as expected in landscape mode commit b93479e6c34f5b2c45cedf7257f603970e1d432b Author: Oliver Lazoroski Date: Thu Oct 17 13:52:54 2024 +0200 fix: use debug level logs for anon/guest users commit 464a71c68d1e1d02965d1715ee1052a212246fd0 Author: GitHub Actions Bot <> Date: Thu Oct 17 10:44:32 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.4.1 commit 188961988bacdede228f800b6f8e2250666086ba Author: GitHub Actions Bot <> Date: Thu Oct 17 10:44:09 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.2.1 commit a2ae74e8097bf1e58d040e4a7696ecadfc435843 Author: Oliver Lazoroski Date: Thu Oct 17 12:41:15 2024 +0200 fix: allow specifying publish options in PN config (#1524) Follow-up to #1434. Allows specifying publish options for calls accepted through Push Notifications config. commit 146a638abf31e900ebab42fa78750721ecc2a6ce Author: GitHub Actions Bot <> Date: Wed Oct 16 12:49:31 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.4.0 commit 8415992d76ae687c8c6993a2f1957535cefce5b5 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:49:09 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.2.0 commit e05676afe419a4c1e898111cfd78608a81d67d71 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:48:51 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.7.0 commit 88ee04d6109d65a1f83ee68608601779c054fda4 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:48:42 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.5 commit cf6c58609a2a93843a1f4861a66d41d4eb062ab6 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:48:20 2024 +0000 chore(@stream-io/video-client): release version 1.9.0 commit c9c8530d48c9206dc3803e6aa6cc1859fd433920 Author: Oliver Lazoroski Date: Wed Oct 16 14:45:32 2024 +0200 feat(svc-codec): VP9 and AV1 support (#1434) ### Overview Enables support for SVC codecs (VP9 and AV1). Adds new API that allows codec overriding: - `call.updatePublishOptions({ preferredCodec: 'vp9' })` ### Implementation notes: - the SDK will now check whether the platform supports the preferred codec, otherwise, it will fall back to a known supported codec - for testing purposes, use `call.updatePublishOptions({ forceCodec: 'vp9' })` The SDK ships a predefined bitrate lookup table now; in the future, this mapping should come from our backend. The bitrates used here enable ~30-50% less network usage while maintaining the same video quality (h264, vp9). - when SVC codec is used, the SDK now supports the dynamic update of the `scalabilityMode` - in Simulcast, the SDK can turn off any layer when instructed by the SFU - this allows further CPU/bandwidth reductions - default codecs: - Firefox - always uses `vp8` as it has intermittent issues when other codecs are used - Safari - always uses `h264` as it is hardware accelerated and yields best performance - RN iOS and iPadOS - always uses `h264` as it is hardware-accelerated - RN Android - uses `vp8` by default https://github.com/GetStream/protocol/pull/776/ --------- Co-authored-by: Suchith.J.N Co-authored-by: Santhosh Vaiyapuri commit 1b8a68d47b10023e34cbdb5790cad5f2b75cd105 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:06:54 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.12 commit 9b1aa12d9ab0ab29d2b75df4cddadbb14474759d Author: GitHub Actions Bot <> Date: Wed Oct 16 12:06:32 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.6 commit 1fdbe169c0bb394aba28c46ffc12964a12a0c2b0 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:06:11 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.7 commit 2cda8a5fe8f0e25bda2e6c368dad86d3554e2ba3 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:06:00 2024 +0000 chore(@stream-io/video-styling): release version 1.1.1 commit b83cae4793b30867ce55240142edb4b32d81822b Author: GitHub Actions Bot <> Date: Wed Oct 16 12:05:52 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.4 commit bc8380fd968bcd41a733341c35e29cd12ae23831 Author: GitHub Actions Bot <> Date: Wed Oct 16 12:05:28 2024 +0000 chore(@stream-io/video-client): release version 1.8.4 commit 9eb2936379726923ee43491ce965003e0e7f2c37 Author: Matvei Andrienko Date: Wed Oct 16 13:54:31 2024 +0200 fix: PiP video placeholder (#1509) Adds a prop similar to `VideoPlaceholder`, but the provided component is rendered when the participant's video is playing in picture-in-picture mode. --------- Co-authored-by: Oliver Lazoroski commit 562b5cca77264330d08dff5305eccc489970076a Author: Matvei Andrienko Date: Wed Oct 16 13:54:04 2024 +0200 fix: ignore camera direction for desktop devices (#1521) Our default call types include a default setting for camera direction (user-facing). Having this setting applied by default is useful for mobile devices, but on desktop devices where camera direction reporting is not reliable it leads to confusion, because it can cause a device other than the system default to be used by default. commit fc20668540a96db5c7c9eff79225a54ff5d51309 Author: Oliver Lazoroski Date: Wed Oct 16 13:51:50 2024 +0200 chore: disable auto-release commit 6d25558a3839eac579c2334695fe52e96bc020b8 Author: Martin Mitrevski Date: Wed Oct 16 13:50:19 2024 +0200 docs: Added more details for anon users (#1522) Few tickets on support came up, we need to be more clear in the docs. commit 0b262dcba74ca30bc02e7d2f318b7791c5d71c81 Author: GitHub Actions Bot <> Date: Wed Oct 16 11:09:58 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.11 commit 1bed36e199b14541d687fb653686649e03d30b7f Author: GitHub Actions Bot <> Date: Wed Oct 16 11:09:35 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.5 commit 44ef7d2e69a910be45b2d3a7643c3f58e0f29803 Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Wed Oct 16 13:06:18 2024 +0200 fix(react-native): set objectFit based on actual video track dimensions (#1520) commit 35ed5238e771eaa1793eac1fc8f11e0009ac8448 Author: Kristian Date: Tue Oct 15 11:18:53 2024 +0200 chore: Upgrade to expo version 51 (#1518) - Upgrades the expo version in the `expo-video-sample app` to 51 - Fixes android startup issues related with the notifee dependency commit 4b12c61111ed8cffea82a500dfc9ce07dba1d525 Author: GitHub Actions Bot <> Date: Mon Oct 14 12:46:05 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.6 commit 9b1121966d3e3f7610fbbca386b8837563203e86 Author: Oliver Lazoroski Date: Mon Oct 14 14:43:10 2024 +0200 fix: check for user capabilities before rendering call control buttons (#1513) Adds OwnCapability checks on the Call Control buttons. They shouldn't be rendered at all when certain user capabilities are not present. ## Fixes When browser permissions are not granted, and the user doesn't have `send-video` or `send-audio` permissions, the `CallControls` component is still prompting for browser permissions. The reason for this prompt is - these buttons use `useMicrophoneState()` and `useCameraState()` hooks that trigger these prompts. Fixing this in the buttons component will require a bit of refactoring, so I'm opting for this less ideal and simple fix. commit b7906d9952eaa840453ca02269ca7387609f774a Author: GitHub Actions Bot <> Date: Thu Oct 10 15:24:50 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.10 commit 1df1fdccda45e4d497385dd1a2c74e0f57623e62 Author: GitHub Actions Bot <> Date: Thu Oct 10 15:24:27 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.4 commit 007d2d65b4613b49f4d91b8bc76374ec4ea3f259 Author: GitHub Actions Bot <> Date: Thu Oct 10 15:24:08 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.5 commit 9b1185308c3c8ee5582cb036300402672cb638ce Author: GitHub Actions Bot <> Date: Thu Oct 10 15:24:00 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.3 commit 017ff2b053f0a536fe2bf877ed1cb987323cf774 Author: GitHub Actions Bot <> Date: Thu Oct 10 15:23:36 2024 +0000 chore(@stream-io/video-client): release version 1.8.3 commit 5bfc52850c36ffe0de37e47066538a8a14dc9e01 Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Thu Oct 10 17:20:44 2024 +0200 fix: do not release track if track was not removed from stream (#1517) commit 9a8daf7ec825d047ce688c58a480fbe9ad478d0b Author: GitHub Actions Bot <> Date: Thu Oct 10 12:16:17 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.9 commit bc39dc66365b7dcb4b3db5391cbf037f1d6c5e1f Author: GitHub Actions Bot <> Date: Thu Oct 10 12:15:54 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.3 commit 40c86cca96258efd9870be5abca64bb5a4b835db Author: GitHub Actions Bot <> Date: Thu Oct 10 12:15:36 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.4 commit 14e71a043e5e966f89504d9345555decc00ab7bb Author: GitHub Actions Bot <> Date: Thu Oct 10 12:15:27 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.2 commit fde52a2703f51413cd12071832b1136c45443bad Author: GitHub Actions Bot <> Date: Thu Oct 10 12:15:04 2024 +0000 chore(@stream-io/video-client): release version 1.8.2 commit 50745101d28d0339592c22ca02b076040ad3bdeb Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Thu Oct 10 14:12:10 2024 +0200 fix: add track release for react-native whenever track stop is called (#1516) track release does the garbage collection of track natively commit 33067931449eca834486f7a3bfc9c2956a42ce61 Author: GitHub Actions Bot <> Date: Thu Oct 10 10:22:15 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.8 commit 78c379ab8a9fd8541bc9ea84450be3a79c9c28c5 Author: GitHub Actions Bot <> Date: Thu Oct 10 10:21:52 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.2 commit 1257346a691ddc0ca8b9fbe19196c4783da68078 Author: GitHub Actions Bot <> Date: Thu Oct 10 10:21:34 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.3 commit c60c0ed66f3d2513b83ede450e13a0d07247398c Author: GitHub Actions Bot <> Date: Thu Oct 10 10:21:27 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.1 commit 655b693be2572b5441c31c928b2c4022eb1c5548 Author: GitHub Actions Bot <> Date: Thu Oct 10 10:21:04 2024 +0000 chore(@stream-io/video-client): release version 1.8.1 commit b7bf90b9b1a83fb80d01a82ebee8754343963ae5 Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Thu Oct 10 12:18:13 2024 +0200 fix: mic not fully released in some cases (#1515) * RNSpeechDetector could create multiple audio streams if started multiple times - fixed * On call ended when on reconnection we did not leave - fixed commit bcbb3f9e0faf483ae5d29c2dc7f18263d2cc978a Author: GitHub Actions Bot <> Date: Mon Oct 7 13:51:49 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.2 commit 2c1c3459c531c8b083f095c9ecc37235a89127c1 Author: Oliver Lazoroski Date: Mon Oct 7 15:48:56 2024 +0200 fix: edge case with participant bar rendering (#1512) Under special circumstances - `excludeLocalParticipant = true` and the screen sharer is the only participant in the call, the participant bar didn't render, which led to an exclusion of the screen sharer's video track. Context: https://getstream.slack.com/archives/C07KX5D7MFX/p1727981915684569 commit e9ba37c908dccebc5515cc26809a147324fd95be Author: GitHub Actions Bot <> Date: Fri Oct 4 14:52:15 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.1 commit 73400414d472d39701fd31b54ac927a8a8865151 Author: Matvei Andrienko Date: Fri Oct 4 16:49:24 2024 +0200 fix: video should be enabled by default commit bf43b5915d73afb0d88fe8f4eb0943a3758678a9 Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Fri Oct 4 14:50:05 2024 +0200 docs: add new architecture explanation (#1511) Screenshot 2024-10-04 at 14 13 50 commit 2378e5fdb56b7a25bfd768510964c6845fd66c8f Author: GitHub Actions Bot <> Date: Fri Oct 4 11:45:05 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.7 commit 41a5801071822109b706c59dba734116b3c7c265 Author: GitHub Actions Bot <> Date: Fri Oct 4 11:44:41 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.1 commit ec61b32449c89885b87fe972a38d25503bab0c0f Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Fri Oct 4 13:41:47 2024 +0200 fix: clarify about USE_FULL_SCREEN_INTENT android permission (#1510) commit 03df7e4eee6f5a98503d6ea907f59b9cca7560fa Author: GitHub Actions Bot <> Date: Thu Oct 3 12:32:22 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.6.0 commit ca12dc3ba34f6dec117ae6fe75d7dbe00f297fe4 Author: Matvei Andrienko Date: Thu Oct 3 14:29:20 2024 +0200 feat: add a prop to control mirroring of local participant video (#1506) We get a lot of questions about disabling mirroring of local participant video. It's currently possible to do by overriding the `str-video__video--mirror` CSS class, but it seems that things will be easier if we have a prop to control this behavior. This PR adds an optional `mirrorLocalParticipantVideo` prop to all built-in layout components. Setting it to `false` disable local participant video mirroring. commit a2cd09ca09b729381a7de4bdfc1f9837c26ed53e Author: Santhosh Vaiyapuri <3846977+santhoshvai@users.noreply.github.com> Date: Thu Oct 3 13:56:40 2024 +0200 docs: add ui-cookbook for react native manual video quality selection (#1507) commit 0b4c9b8f7d4acdf0d6ad7a73fe9772f0a163a065 Author: Oliver Lazoroski Date: Thu Oct 3 11:18:47 2024 +0200 fix: typos, unused imports commit cbed57807369a5a88d450f3d9020a202e8df68aa Author: GitHub Actions Bot <> Date: Wed Oct 2 14:09:01 2024 +0000 chore(@stream-io/video-react-native-dogfood): release version 4.3.6 commit 16f1693783e253776a9770029e3fcc159cc24568 Author: GitHub Actions Bot <> Date: Wed Oct 2 14:08:38 2024 +0000 chore(@stream-io/video-react-native-sdk): release version 1.1.0 commit 838c3b390efe662b814314effdc48b7b6306b09c Author: GitHub Actions Bot <> Date: Wed Oct 2 14:08:20 2024 +0000 chore(@stream-io/video-react-sdk): release version 1.5.0 commit 5c707f75c3581b96479baab21c559e98dd3e0818 Author: GitHub Actions Bot <> Date: Wed Oct 2 14:08:12 2024 +0000 chore(@stream-io/video-styling): release version 1.1.0 commit 672504e3760075d5932938d8468a95aa8a4ee1b3 Author: GitHub Actions Bot <> Date: Wed Oct 2 14:08:05 2024 +0000 chore(@stream-io/video-react-bindings): release version 1.1.0 commit 0de03fb225e212f9a1a6731b7bb17679aa6f4455 Author: GitHub Actions Bot <> Date: Wed Oct 2 14:07:42 2024 +0000 chore(@stream-io/video-client): release version 1.8.0 commit 3a754afa1bd13d038b1023520ec8a5296ad2669e Author: Matvei Andrienko Date: Wed Oct 2 16:04:51 2024 +0200 feat: manual video quality selection (#1486) Adds APIs to manually override incoming video quality: ## Client ### `call.setPreferredIncomingVideoResolution(resolution?: VideoDimension, sessionIds?: string[])` Specify preference for incoming video resolution. The preference will be matched as close as possible, but actual resolution will depend on the video source quality and client network conditions. Will enable incoming video, if previously disabled. Passing `undefined` resolution clears previously set preferences. Optionally specify session ids of the participants this preference has effect on. Affects all participants by default. ### `call.setIncomingVideoEnabled(enabled: boolean)` Enables or disables incoming video from all remote call participants, and clears any preference for preferred resolution. ## React SDK New call state hook added: ### `useIncomingVideoSettings()` Returns incoming video settings for the current call, including global and per-participant manual video quality overrides. ## Demo App Video quality selector added to the settings modal in `react-dogfood`. On Pronto, it's also visible in call controls: ![](https://github.com/user-attachments/assets/8cf111d5-2924-4067-9aca-59f51dfb1a8e) ![](https://github.com/user-attachments/assets/eac779c0-d120-402d-b7b9-ef3033b56d1e) ## Internal APIs updated ### `callState.updateParticipantTracks` Call class no longer has a slightly incorrectly named `updateSubscriptionsPartial` method. We want to avoid the Call class dealing with subscriptions at all - this is a domain of the DynascaleManager. Instead, the new `updateParticipantTracks` should be used on the CallState object. ### Disabling