Skip to content

Commit 1e73517

Browse files
authored
fix: support scenario of accept second call when there is ongoing first call (#1939)
### 💡 Overview Currently, on RN push, we only support rejecting a incoming call when already there is an ongoing ringing type call. This PR makes sure that the opposite is supported. This enables rejecting the ongoing call and accepting the incoming one.
1 parent dad4c03 commit 1e73517

File tree

7 files changed

+61
-61
lines changed

7 files changed

+61
-61
lines changed

packages/react-native-sdk/src/providers/StreamCall/index.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ import { StreamCallProvider } from '@stream-io/video-react-bindings';
22
import React, { type PropsWithChildren, useEffect } from 'react';
33
import { Call } from '@stream-io/video-client';
44
import { useIosCallkeepWithCallingStateEffect } from '../../hooks/push/useIosCallkeepWithCallingStateEffect';
5-
import {
6-
canAddPushWSSubscriptionsRef,
7-
clearPushWSEventSubscriptions,
8-
} from '../../utils/push/internal/utils';
5+
import { canAddPushWSSubscriptionsRef } from '../../utils/push/internal/utils';
96
import { useAndroidKeepCallAliveEffect } from '../../hooks/useAndroidKeepCallAliveEffect';
107
import { AppStateListener } from './AppStateListener';
118
import { DeviceStats } from './DeviceStats';
9+
import { pushUnsubscriptionCallbacks } from '../../utils/push/internal/constants';
1210

1311
// const PIP_CHANGE_EVENT = 'StreamVideoReactNative_PIP_CHANGE_EVENT';
1412

@@ -68,7 +66,11 @@ const IosInformCallkeepCallEnd = () => {
6866
*/
6967
const ClearPushWSSubscriptions = () => {
7068
useEffect(() => {
71-
clearPushWSEventSubscriptions();
69+
// clear all the push ws event subscriptions
70+
pushUnsubscriptionCallbacks.forEach((cbArray) =>
71+
cbArray.forEach((cb) => cb()),
72+
);
73+
pushUnsubscriptionCallbacks.clear();
7274
canAddPushWSSubscriptionsRef.current = false;
7375
return () => {
7476
canAddPushWSSubscriptionsRef.current = true;

packages/react-native-sdk/src/utils/push/android.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
Call,
33
CallingState,
44
getLogger,
5-
RxUtils,
65
StreamVideoClient,
76
} from '@stream-io/video-client';
87
import { AppState, Platform } from 'react-native';
@@ -26,8 +25,8 @@ import {
2625
pushNonRingingCallData$,
2726
pushRejectedIncomingCallCId$,
2827
pushTappedIncomingCallCId$,
29-
pushUnsubscriptionCallbacks$,
3028
} from './internal/rxSubjects';
29+
import { pushUnsubscriptionCallbacks } from './internal/constants';
3130
import {
3231
canAddPushWSSubscriptionsRef,
3332
clearPushWSEventSubscriptions,
@@ -253,12 +252,8 @@ export const firebaseDataHandler = async (
253252
);
254253
unsubscribeFunctions.push(unsubscribe);
255254
unsubscribeFunctions.push(() => subscription.unsubscribe());
256-
const unsubscriptionCallbacks =
257-
RxUtils.getCurrentValue(pushUnsubscriptionCallbacks$) ?? [];
258-
pushUnsubscriptionCallbacks$.next([
259-
...unsubscriptionCallbacks,
260-
...unsubscribeFunctions,
261-
]);
255+
pushUnsubscriptionCallbacks.get(call_cid)?.forEach((cb) => cb());
256+
pushUnsubscriptionCallbacks.set(call_cid, unsubscribeFunctions);
262257
});
263258
});
264259
}
@@ -459,7 +454,7 @@ export const onAndroidNotifeeEvent = async ({
459454
'debug',
460455
`clearPushWSEventSubscriptions for callCId: ${call_cid} mustAccept: ${mustAccept} mustDecline: ${mustDecline}`,
461456
);
462-
clearPushWSEventSubscriptions();
457+
clearPushWSEventSubscriptions(call_cid);
463458
notifee.stopForegroundService();
464459
}
465460

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
type UnsubscribeCallback = () => void;
2+
3+
/**
4+
* This map is used to store the unsubscribe callbacks (if any) of the push notification processing
5+
* Note: it should be used to clear it when app processes push notification from foreground
6+
*/
7+
export const pushUnsubscriptionCallbacks = new Map<
8+
string,
9+
UnsubscribeCallback[]
10+
>();

packages/react-native-sdk/src/utils/push/internal/ios.ts

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
1-
import { getLogger, RxUtils } from '@stream-io/video-client';
1+
import { getLogger } from '@stream-io/video-client';
22
import { AppState, NativeModules, Platform } from 'react-native';
33
import { getCallKeepLib, getVoipPushNotificationLib } from '../libs';
4-
import {
5-
pushUnsubscriptionCallbacks$,
6-
voipPushNotificationCallCId$,
7-
} from './rxSubjects';
4+
import { voipPushNotificationCallCId$ } from './rxSubjects';
5+
import { pushUnsubscriptionCallbacks } from './constants';
86
import { canAddPushWSSubscriptionsRef, shouldCallBeEnded } from './utils';
97
import { StreamVideoConfig } from '../../StreamVideoRN/types';
108

@@ -124,12 +122,9 @@ export const onVoipNotificationReceived = async (
124122
unsubscribe();
125123
}
126124
});
127-
const unsubscriptionCallbacks =
128-
RxUtils.getCurrentValue(pushUnsubscriptionCallbacks$) ?? [];
129-
pushUnsubscriptionCallbacks$.next([
130-
...unsubscriptionCallbacks,
131-
unsubscribe,
132-
]);
125+
126+
pushUnsubscriptionCallbacks.get(call_cid)?.forEach((cb) => cb());
127+
pushUnsubscriptionCallbacks.set(call_cid, [unsubscribe]);
133128
}
134129
// send the info to this subject, it is listened by callkeep events
135130
// callkeep events will then accept/reject the call

packages/react-native-sdk/src/utils/push/internal/rxSubjects.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,3 @@ export const voipCallkeepCallOnForegroundMap$ = new BehaviorSubject<
6969
export const voipCallkeepAcceptedCallOnNativeDialerMap$ = new BehaviorSubject<
7070
CallkeepMap | undefined
7171
>(undefined);
72-
73-
type UnsubscribeCallback = () => void;
74-
75-
/**
76-
* This rxjs subject is used to store the unsubscribe callbacks (if any) of the push notification processing
77-
* Note: it should be used to clear it when app processes push notification from foreground
78-
*/
79-
export const pushUnsubscriptionCallbacks$ = new BehaviorSubject<
80-
UnsubscribeCallback[] | undefined
81-
>(undefined);

packages/react-native-sdk/src/utils/push/internal/utils.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,14 @@ import {
22
Call,
33
CallingState,
44
getLogger,
5-
RxUtils,
65
StreamVideoClient,
76
} from '@stream-io/video-client';
87
import type {
98
NonRingingPushEvent,
109
StreamVideoConfig,
1110
} from '../../StreamVideoRN/types';
1211
import { onNewCallNotification } from '../../internal/newNotificationCallbacks';
13-
import { pushUnsubscriptionCallbacks$ } from './rxSubjects';
12+
import { pushUnsubscriptionCallbacks } from './constants';
1413

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

@@ -174,14 +173,12 @@ export const processNonIncomingCallFromPush = async (
174173
* This function is used to clear all the push related WS subscriptions
175174
* note: events are subscribed in push for accept/decline through WS
176175
*/
177-
export const clearPushWSEventSubscriptions = () => {
178-
const unsubscriptionCallbacks = RxUtils.getCurrentValue(
179-
pushUnsubscriptionCallbacks$,
180-
);
176+
export const clearPushWSEventSubscriptions = (call_cid: string) => {
177+
const unsubscriptionCallbacks = pushUnsubscriptionCallbacks.get(call_cid);
181178
if (unsubscriptionCallbacks) {
182179
unsubscriptionCallbacks.forEach((cb) => cb());
180+
pushUnsubscriptionCallbacks.delete(call_cid);
183181
}
184-
pushUnsubscriptionCallbacks$.next(undefined);
185182
};
186183

187184
/**

packages/react-native-sdk/src/utils/push/setupIosCallKeepEvents.ts

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -37,24 +37,29 @@ export function setupIosCallKeepEvents(
3737
const callkeep = getCallKeepLib();
3838

3939
async function getCallCid(callUUID: string): Promise<string | undefined> {
40-
let call_cid = RxUtils.getCurrentValue(voipPushNotificationCallCId$);
41-
if (!call_cid) {
42-
// if call_cid is not available, try to get it from native module
43-
try {
44-
call_cid =
45-
await NativeModules?.StreamVideoReactNative?.getIncomingCallCid(
46-
callUUID,
47-
);
48-
voipPushNotificationCallCId$.next(call_cid);
49-
} catch (error) {
40+
try {
41+
const call_cid =
42+
await NativeModules.StreamVideoReactNative.getIncomingCallCid(callUUID);
43+
// in a case that voipPushNotificationCallCId$ is empty (this should not happen as voipPushNotificationCallCId$ is updated in push reception)]
44+
// update it with this call_cid
45+
const voipPushNotificationCallCId = RxUtils.getCurrentValue(
46+
voipPushNotificationCallCId$,
47+
);
48+
if (!voipPushNotificationCallCId) {
5049
logger(
5150
'debug',
52-
'Error in getting call cid from native module - probably the call was already processed, so ignoring this callkeep event',
53-
error,
51+
`voipPushNotificationCallCId$ is empty, updating it with the call_cid: ${call_cid} for callUUID: ${callUUID}`,
5452
);
53+
voipPushNotificationCallCId$.next(call_cid);
5554
}
55+
return call_cid;
56+
} catch {
57+
logger(
58+
'debug',
59+
`Error in getting call cid from native module for callUUID: ${callUUID} - probably the call was already processed, so ignoring this callkeep event`,
60+
);
5661
}
57-
return call_cid;
62+
return undefined;
5863
}
5964

6065
function answerCall(callUUID: string) {
@@ -150,7 +155,7 @@ const iosCallkeepAcceptCall = (
150155
if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) {
151156
return;
152157
}
153-
clearPushWSEventSubscriptions();
158+
clearPushWSEventSubscriptions(call_cid);
154159
// to call end callkeep later if ended in app and not through callkeep
155160
voipCallkeepAcceptedCallOnNativeDialerMap$.next({
156161
uuid: callUUIDFromCallkeep,
@@ -170,11 +175,17 @@ const iosCallkeepRejectCall = async (
170175
if (!shouldProcessCallFromCallkeep(call_cid, callUUIDFromCallkeep)) {
171176
return;
172177
}
173-
clearPushWSEventSubscriptions();
174-
// no need to keep these references anymore
175-
voipCallkeepAcceptedCallOnNativeDialerMap$.next(undefined);
176-
voipCallkeepCallOnForegroundMap$.next(undefined);
177-
voipPushNotificationCallCId$.next(undefined);
178+
clearPushWSEventSubscriptions(call_cid);
179+
// remove the references if the call_cid matches
180+
const voipPushNotificationCallCId = RxUtils.getCurrentValue(
181+
voipPushNotificationCallCId$,
182+
);
183+
if (voipPushNotificationCallCId === call_cid) {
184+
voipCallkeepAcceptedCallOnNativeDialerMap$.next(undefined);
185+
voipCallkeepCallOnForegroundMap$.next(undefined);
186+
voipPushNotificationCallCId$.next(undefined);
187+
}
188+
178189
await processCallFromPushInBackground(pushConfig, call_cid, 'decline');
179190
await NativeModules.StreamVideoReactNative?.removeIncomingCall(call_cid);
180191
};

0 commit comments

Comments
 (0)