@stream-io/video-react-native-sdk 1.44.0 → 2.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +26 -3
  2. package/android/src/main/java/com/streamvideo/reactnative/keepalive/StreamCallKeepAliveHeadlessService.kt +112 -26
  3. package/android/src/main/java/com/streamvideo/reactnative/util/CallAlivePermissionsHelper.kt +68 -37
  4. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js +12 -6
  5. package/dist/commonjs/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
  6. package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js +38 -11
  7. package/dist/commonjs/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
  8. package/dist/commonjs/hooks/useLoopbackRecording.js +10 -127
  9. package/dist/commonjs/hooks/useLoopbackRecording.js.map +1 -1
  10. package/dist/commonjs/utils/StreamVideoRN/index.js +2 -0
  11. package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
  12. package/dist/commonjs/utils/internal/callingx/callingx.js +38 -56
  13. package/dist/commonjs/utils/internal/callingx/callingx.js.map +1 -1
  14. package/dist/commonjs/utils/keepCallAliveHeadlessTask.js +43 -4
  15. package/dist/commonjs/utils/keepCallAliveHeadlessTask.js.map +1 -1
  16. package/dist/commonjs/utils/push/android.js +7 -167
  17. package/dist/commonjs/utils/push/android.js.map +1 -1
  18. package/dist/commonjs/utils/push/internal/android.js +204 -0
  19. package/dist/commonjs/utils/push/internal/android.js.map +1 -0
  20. package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js +9 -1
  21. package/dist/commonjs/utils/push/libs/firebaseMessaging/index.js.map +1 -1
  22. package/dist/commonjs/utils/push/libs/firebaseMessaging/lib.js +1 -1
  23. package/dist/commonjs/utils/push/libs/firebaseMessaging/lib.js.map +1 -1
  24. package/dist/commonjs/utils/push/setupAndroidPushEvents.js +25 -0
  25. package/dist/commonjs/utils/push/setupAndroidPushEvents.js.map +1 -0
  26. package/dist/commonjs/utils/push/setupCallingExpEvents.js +6 -2
  27. package/dist/commonjs/utils/push/setupCallingExpEvents.js.map +1 -1
  28. package/dist/commonjs/utils/push/setupIosVoipPushEvents.js +6 -7
  29. package/dist/commonjs/utils/push/setupIosVoipPushEvents.js.map +1 -1
  30. package/dist/commonjs/utils/push/utils.js.map +1 -1
  31. package/dist/commonjs/version.js +1 -1
  32. package/dist/commonjs/version.js.map +1 -1
  33. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js +12 -6
  34. package/dist/module/hooks/push/useCallingExpWithCallingStateEffect.js.map +1 -1
  35. package/dist/module/hooks/useAndroidKeepCallAliveEffect.js +39 -12
  36. package/dist/module/hooks/useAndroidKeepCallAliveEffect.js.map +1 -1
  37. package/dist/module/hooks/useLoopbackRecording.js +10 -127
  38. package/dist/module/hooks/useLoopbackRecording.js.map +1 -1
  39. package/dist/module/utils/StreamVideoRN/index.js +2 -0
  40. package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
  41. package/dist/module/utils/internal/callingx/callingx.js +38 -56
  42. package/dist/module/utils/internal/callingx/callingx.js.map +1 -1
  43. package/dist/module/utils/keepCallAliveHeadlessTask.js +41 -3
  44. package/dist/module/utils/keepCallAliveHeadlessTask.js.map +1 -1
  45. package/dist/module/utils/push/android.js +9 -169
  46. package/dist/module/utils/push/android.js.map +1 -1
  47. package/dist/module/utils/push/internal/android.js +200 -0
  48. package/dist/module/utils/push/internal/android.js.map +1 -0
  49. package/dist/module/utils/push/libs/firebaseMessaging/index.js +9 -1
  50. package/dist/module/utils/push/libs/firebaseMessaging/index.js.map +1 -1
  51. package/dist/module/utils/push/libs/firebaseMessaging/lib.js +1 -1
  52. package/dist/module/utils/push/libs/firebaseMessaging/lib.js.map +1 -1
  53. package/dist/module/utils/push/setupAndroidPushEvents.js +21 -0
  54. package/dist/module/utils/push/setupAndroidPushEvents.js.map +1 -0
  55. package/dist/module/utils/push/setupCallingExpEvents.js +6 -2
  56. package/dist/module/utils/push/setupCallingExpEvents.js.map +1 -1
  57. package/dist/module/utils/push/setupIosVoipPushEvents.js +6 -7
  58. package/dist/module/utils/push/setupIosVoipPushEvents.js.map +1 -1
  59. package/dist/module/utils/push/utils.js.map +1 -1
  60. package/dist/module/version.js +1 -1
  61. package/dist/module/version.js.map +1 -1
  62. package/dist/typescript/hooks/push/useCallingExpWithCallingStateEffect.d.ts.map +1 -1
  63. package/dist/typescript/hooks/useAndroidKeepCallAliveEffect.d.ts.map +1 -1
  64. package/dist/typescript/hooks/useLoopbackRecording.d.ts +12 -69
  65. package/dist/typescript/hooks/useLoopbackRecording.d.ts.map +1 -1
  66. package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
  67. package/dist/typescript/utils/StreamVideoRN/types.d.ts +1 -2
  68. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  69. package/dist/typescript/utils/internal/callingx/callingx.d.ts.map +1 -1
  70. package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts +9 -0
  71. package/dist/typescript/utils/keepCallAliveHeadlessTask.d.ts.map +1 -1
  72. package/dist/typescript/utils/push/android.d.ts +3 -4
  73. package/dist/typescript/utils/push/android.d.ts.map +1 -1
  74. package/dist/typescript/utils/push/internal/android.d.ts +8 -0
  75. package/dist/typescript/utils/push/internal/android.d.ts.map +1 -0
  76. package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts +6 -4
  77. package/dist/typescript/utils/push/libs/firebaseMessaging/index.d.ts.map +1 -1
  78. package/dist/typescript/utils/push/libs/firebaseMessaging/lib.d.ts +2 -2
  79. package/dist/typescript/utils/push/libs/firebaseMessaging/lib.d.ts.map +1 -1
  80. package/dist/typescript/utils/push/setupAndroidPushEvents.d.ts +3 -0
  81. package/dist/typescript/utils/push/setupAndroidPushEvents.d.ts.map +1 -0
  82. package/dist/typescript/utils/push/setupCallingExpEvents.d.ts.map +1 -1
  83. package/dist/typescript/utils/push/setupIosVoipPushEvents.d.ts.map +1 -1
  84. package/dist/typescript/utils/push/utils.d.ts +2 -2
  85. package/dist/typescript/utils/push/utils.d.ts.map +1 -1
  86. package/dist/typescript/version.d.ts +1 -1
  87. package/dist/typescript/version.d.ts.map +1 -1
  88. package/expo-config-plugin/dist/withAndroidPermissions.js +1 -1
  89. package/ios/TracksRecorder/RecorderVideoSink.swift +92 -5
  90. package/package.json +4 -4
  91. package/src/hooks/push/useCallingExpWithCallingStateEffect.ts +25 -11
  92. package/src/hooks/useAndroidKeepCallAliveEffect.ts +59 -21
  93. package/src/hooks/useLoopbackRecording.ts +36 -248
  94. package/src/utils/StreamVideoRN/index.ts +2 -0
  95. package/src/utils/StreamVideoRN/types.ts +1 -2
  96. package/src/utils/internal/callingx/callingx.ts +49 -68
  97. package/src/utils/keepCallAliveHeadlessTask.ts +45 -3
  98. package/src/utils/push/android.ts +12 -237
  99. package/src/utils/push/internal/android.ts +273 -0
  100. package/src/utils/push/libs/firebaseMessaging/index.ts +16 -5
  101. package/src/utils/push/libs/firebaseMessaging/lib.ts +3 -3
  102. package/src/utils/push/setupAndroidPushEvents.ts +25 -0
  103. package/src/utils/push/setupCallingExpEvents.ts +6 -3
  104. package/src/utils/push/setupIosVoipPushEvents.ts +8 -7
  105. package/src/utils/push/utils.ts +2 -4
  106. package/src/version.ts +1 -1
@@ -1,16 +1,8 @@
1
- import {
2
- CallingState,
3
- StreamVideoClient,
4
- videoLoggerSystem,
5
- } from '@stream-io/video-client';
6
- import { AppState, Platform } from 'react-native';
1
+ import { StreamVideoClient, videoLoggerSystem } from '@stream-io/video-client';
2
+ import { Platform } from 'react-native';
7
3
  import type { StreamVideoConfig } from '../StreamVideoRN/types';
8
- import { type FirebaseMessagingTypes, getFirebaseMessagingLib } from './libs';
9
- import { pushUnsubscriptionCallbacks } from './internal/constants';
10
- import { canListenToWS, shouldCallBeClosed } from './internal/utils';
4
+ import { getFirebaseMessagingLib } from './libs';
11
5
  import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
12
- import { StreamVideoRN } from '../StreamVideoRN';
13
- import { getCallingxLib } from './libs/callingx';
14
6
 
15
7
  type PushConfig = NonNullable<StreamVideoConfig['push']>;
16
8
 
@@ -66,234 +58,17 @@ export async function initAndroidPushToken(
66
58
  await setDeviceToken(token);
67
59
  }
68
60
 
61
+ let firebaseDataHandlerDeprecationLogged = false;
69
62
  /**
70
- * Creates notification from the push message data.
71
- * For Ringing and Non-Ringing calls.
63
+ * @deprecated Ring notifications are now handled by the SDK internally. This method is a no-op;
64
+ * you can safely remove `firebaseDataHandler(...)` wiring from your Firebase messaging handlers.
72
65
  */
73
-
74
- export const firebaseDataHandler = async (
75
- data: FirebaseMessagingTypes.RemoteMessage['data'],
76
- ) => {
77
- /* Example data from firebase
78
- "message": {
79
- "data": {
80
- call_cid: 'audio_room:dcc1638c-e90d-4dcb-bf3b-8fa7767bfbb0',
81
- call_display_name: '',
82
- created_by_display_name: 'tommaso',
83
- created_by_id: 'tommaso-03dcddb7-e9e2-42ec-b2f3-5043aac666ee',
84
- receiver_id: 'martin-21824f17-319b-401b-a61b-fcab646f0d3f',
85
- sender: 'stream.video',
86
- type: 'call.live_started',
87
- version: 'v2'
88
- },
89
- // other stuff
90
- }
91
- */
92
- if (Platform.OS !== 'android') return;
93
-
94
- const logger = videoLoggerSystem.getLogger('firebaseDataHandler');
95
- const pushConfig = StreamVideoRN.getConfig().push;
96
- if (!pushConfig || !data || data.sender !== 'stream.video') {
97
- return;
98
- }
99
-
100
- if (data.type === 'call.ring') {
101
- const call_cid = data.call_cid as string;
102
- const callingx = getCallingxLib();
103
-
104
- const client = await pushConfig.createStreamVideoClient();
105
- if (!client) {
106
- logger.debug(
107
- `video client not found, skipping the call.ring notification`,
108
- );
109
- await callingx.stopService();
110
- return;
111
- }
112
-
113
- const asForegroundService = canListenToWS();
114
-
115
- if (asForegroundService) {
116
- // Listen to call events from WS with headless task, bound to call service.
117
- // By this moment the call service is already running, so we can acquire the background task.
118
- const backgroundTaskOwner = `push:${call_cid}`;
119
- await callingx.acquireBackgroundTask(backgroundTaskOwner).catch((e) => {
120
- logger.error(
121
- `Failed to acquire background task for callCid: ${call_cid} error: ${e}`,
122
- );
123
- });
124
-
125
- const finishBackgroundTask = () => {
126
- callingx.log(
127
- `Finishing background task for callCid: ${call_cid}`,
128
- 'debug',
129
- );
130
- callingx.releaseBackgroundTask(backgroundTaskOwner);
131
- };
132
-
133
- (async () => {
134
- try {
135
- const _client = await pushConfig.createStreamVideoClient();
136
- if (!_client) {
137
- logger.debug(
138
- `Closing fg service as there is no client to create from push config`,
139
- );
140
- finishBackgroundTask();
141
- return;
142
- }
143
-
144
- const callFromPush = await _client.onRingingCall(call_cid);
145
- const { mustEndCall, endCallReason } = shouldCallBeClosed(
146
- callFromPush,
147
- data,
148
- );
149
- if (mustEndCall) {
150
- logger.debug(
151
- `Closing fg service callCid: ${call_cid} endCallReason: ${endCallReason}`,
152
- );
153
-
154
- callingx.log(
155
- `Ending call with callCid: ${call_cid} endCallReason: ${endCallReason}`,
156
- 'debug',
157
- );
158
- callingx.endCallWithReason(call_cid, endCallReason);
159
- callFromPush.leave({ reject: false }).catch((error) => {
160
- logger.error(
161
- `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
162
- );
163
- });
164
- finishBackgroundTask();
165
- return;
166
- }
167
-
168
- const unsubscribeFunctions: Array<() => void> = [];
169
- // check if service needs to be closed if accept/decline event was done on another device
170
- const unsubscribe = callFromPush.on('all', (event) => {
171
- const _canListenToWS = canListenToWS();
172
- if (!_canListenToWS) {
173
- logger.debug(
174
- `Closing fg service from event callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
175
- { event },
176
- );
177
- unsubscribeFunctions.forEach((fn) => fn());
178
-
179
- finishBackgroundTask();
180
- return;
181
- }
182
-
183
- const {
184
- mustEndCall: mustEndCallFromEvent,
185
- endCallReason: endCallReasonFromEvent,
186
- } = shouldCallBeClosed(callFromPush, data);
187
- if (mustEndCallFromEvent) {
188
- logger.debug(
189
- `Closing fg service from event callCid: ${call_cid} canListenToWS: ${_canListenToWS} shouldCallBeClosed`,
190
- { event },
191
- );
192
- unsubscribeFunctions.forEach((fn) => fn());
193
-
194
- callingx.endCallWithReason(call_cid, endCallReasonFromEvent);
195
- finishBackgroundTask();
196
- }
197
- });
198
-
199
- // check if service needs to be closed if call was left
200
- const stateSubscription = callFromPush.state.callingState$.subscribe(
201
- (callingState) => {
202
- if (
203
- callingState === CallingState.IDLE ||
204
- callingState === CallingState.LEFT
205
- ) {
206
- logger.debug(
207
- `Closing fg service from callingState callCid: ${call_cid} callingState: ${callingState}`,
208
- );
209
- unsubscribeFunctions.forEach((fn) => fn());
210
- callingx.log(
211
- `Ending call with callCid: ${call_cid} callingState: ${callingState}`,
212
- 'debug',
213
- );
214
- finishBackgroundTask();
215
- }
216
- },
217
- );
218
-
219
- const endCallSubscription = callingx.addEventListener(
220
- 'endCall',
221
- async ({ callId }: { callId: string }) => {
222
- unsubscribeFunctions.forEach((fn) => fn());
223
- try {
224
- await callFromPush.leave({
225
- reject:
226
- callFromPush.state.callingState === CallingState.RINGING,
227
- reason: 'decline',
228
- });
229
- } catch (error) {
230
- logger.error(
231
- `Failed to leave call with callCid: ${call_cid} error: ${error}`,
232
- );
233
- } finally {
234
- callingx.log(
235
- `Ending call with callCid: ${call_cid} callId: ${callId}`,
236
- 'debug',
237
- );
238
- finishBackgroundTask();
239
- }
240
- },
241
- );
242
-
243
- //stop background task when app comes to foreground
244
- const appStateSubscription = AppState.addEventListener(
245
- 'change',
246
- (nextAppState) => {
247
- const _canListenToWS = canListenToWS();
248
- callingx.log(
249
- `AppState changed to: ${nextAppState} for callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
250
- 'debug',
251
- );
252
- if (!_canListenToWS) {
253
- unsubscribeFunctions.forEach((fn) => fn());
254
- finishBackgroundTask();
255
- return;
256
- }
257
- },
258
- );
259
-
260
- unsubscribeFunctions.push(unsubscribe);
261
- unsubscribeFunctions.push(() => stateSubscription.unsubscribe());
262
- unsubscribeFunctions.push(() => endCallSubscription.remove());
263
- unsubscribeFunctions.push(() => appStateSubscription.remove());
264
- pushUnsubscriptionCallbacks.get(call_cid)?.forEach((cb) => cb());
265
- pushUnsubscriptionCallbacks.set(call_cid, unsubscribeFunctions);
266
- } catch (error) {
267
- callingx.log(
268
- `Failed to start background task with callCid: ${call_cid} error: ${error}`,
269
- 'error',
270
- );
271
- finishBackgroundTask();
272
- }
273
- })();
274
- }
275
-
276
- if (asForegroundService) {
277
- // no need to check if call has be closed as that will be handled by the fg service
278
- return;
279
- }
280
-
281
- const callFromPush = await client.onRingingCall(call_cid);
282
-
283
- const { mustEndCall, endCallReason } = shouldCallBeClosed(
284
- callFromPush,
285
- data,
66
+ export const firebaseDataHandler = async (data?: any) => {
67
+ void data;
68
+ if (!firebaseDataHandlerDeprecationLogged) {
69
+ firebaseDataHandlerDeprecationLogged = true;
70
+ console.warn(
71
+ '[@stream-io/video-react-native-sdk] `firebaseDataHandler` is deprecated. Ring notifications are now handled by the SDK internally — you can remove calls to it from your Firebase messaging handlers.',
286
72
  );
287
- if (mustEndCall) {
288
- logger.debug(
289
- `Removing incoming call notification immediately with callCid: ${call_cid} as it should be closed`,
290
- );
291
- callingx.endCallWithReason(call_cid, endCallReason);
292
- callFromPush.leave({ reject: false }).catch((error) => {
293
- logger.error(
294
- `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
295
- );
296
- });
297
- }
298
73
  }
299
74
  };
@@ -0,0 +1,273 @@
1
+ import {
2
+ AllCallEvents,
3
+ AllClientEventTypes,
4
+ CallingState,
5
+ StreamVideoClient,
6
+ } from '@stream-io/video-client';
7
+ import { AppState, Platform } from 'react-native';
8
+ import {
9
+ type FirebaseRemoteMessage,
10
+ getCallingxLib,
11
+ getCallingxLibIfAvailable,
12
+ } from '../libs';
13
+ import { StreamVideoRN } from '../../StreamVideoRN';
14
+ import { pushUnsubscriptionCallbacks } from './constants';
15
+ import { canListenToWS, shouldCallBeClosed } from './utils';
16
+
17
+ /**
18
+ * Handles a Stream Video `call.ring` push payload: connects the WS to watch the ringing call
19
+ * and auto-dismisses it if it has already ended/been handled elsewhere. Non-ringing types are
20
+ * ignored (their display is app responsibility). Android-only; a no-op on other platforms.
21
+ */
22
+ export const onRingNotificationReceived = async (
23
+ data: FirebaseRemoteMessage['data'],
24
+ ) => {
25
+ /* Example data from firebase
26
+ "message": {
27
+ "data": {
28
+ call_cid: 'audio_room:dcc1638c-e90d-4dcb-bf3b-8fa7767bfbb0',
29
+ call_display_name: '',
30
+ created_by_display_name: 'tommaso',
31
+ created_by_id: 'tommaso-03dcddb7-e9e2-42ec-b2f3-5043aac666ee',
32
+ receiver_id: 'martin-21824f17-319b-401b-a61b-fcab646f0d3f',
33
+ sender: 'stream.video',
34
+ type: 'call.live_started',
35
+ version: 'v2'
36
+ },
37
+ // other stuff
38
+ }
39
+ */
40
+ if (Platform.OS !== 'android') return;
41
+
42
+ const nativeLog = (
43
+ message: string,
44
+ level: 'debug' | 'info' | 'warn' | 'error' = 'debug',
45
+ ) =>
46
+ getCallingxLibIfAvailable()?.log(
47
+ `[callingx - onRingNotificationReceived]: ${message}`,
48
+ level,
49
+ );
50
+
51
+ const pushConfig = StreamVideoRN.getConfig().push;
52
+ if (
53
+ !pushConfig ||
54
+ !data ||
55
+ data.sender !== 'stream.video' ||
56
+ data.type !== 'call.ring'
57
+ ) {
58
+ return;
59
+ }
60
+
61
+ const call_cid = data.call_cid as string;
62
+ const callingx = getCallingxLib();
63
+
64
+ if (pushUnsubscriptionCallbacks.has(call_cid)) {
65
+ nativeLog(
66
+ `call_cid ${call_cid} is already being watched, skipping the duplicate call.ring notification`,
67
+ );
68
+ return;
69
+ }
70
+
71
+ const asForegroundService = canListenToWS();
72
+ const backgroundTaskOwner = `push:${call_cid}`;
73
+ nativeLog(
74
+ `call.ring for callCid: ${call_cid} asForegroundService=${asForegroundService}`,
75
+ );
76
+
77
+ const finishBackgroundTask = () => {
78
+ nativeLog(`Finishing background task for callCid: ${call_cid}`);
79
+ callingx.releaseBackgroundTask(backgroundTaskOwner);
80
+ };
81
+
82
+ /**
83
+ * Gives up on this push: dismisses the call the native push path already displayed, then asks
84
+ * the call service to stop. `stopService` is only a request — it no-ops while any other call is
85
+ * registered or being registered — so this call must be ended explicitly rather than relying on
86
+ * the service teardown to wipe it.
87
+ */
88
+ const abandonPush = async () => {
89
+ if (asForegroundService) {
90
+ finishBackgroundTask();
91
+ }
92
+ // Each step is contained on its own: failing to dismiss the call must not skip the stop
93
+ // request, and neither may skip the unsubscription cleanup — a stale entry makes every later
94
+ // push for this cid look like a duplicate and get discarded.
95
+ try {
96
+ await callingx.endCallWithReason(call_cid, 'error');
97
+ } catch (error) {
98
+ nativeLog(`Failed to end call ${call_cid}: ${error}`, 'error');
99
+ }
100
+ try {
101
+ await callingx.stopService();
102
+ } catch (error) {
103
+ nativeLog(
104
+ `Failed to stop the call service for ${call_cid}: ${error}`,
105
+ 'error',
106
+ );
107
+ }
108
+ pushUnsubscriptionCallbacks.delete(call_cid);
109
+ };
110
+
111
+ if (asForegroundService) {
112
+ // initialize the callback array immediately to avoid race condition
113
+ pushUnsubscriptionCallbacks.set(call_cid, []);
114
+ // The owner is added synchronously inside acquireBackgroundTask (before its own await), so it
115
+ // is registered immediately even though we don't await the returned promise here.
116
+ nativeLog(`acquiring background task for callCid: ${call_cid}`);
117
+ callingx.acquireBackgroundTask(backgroundTaskOwner).catch((e) => {
118
+ nativeLog(
119
+ `Failed to acquire background task for callCid: ${call_cid} error: ${e}`,
120
+ 'error',
121
+ );
122
+ });
123
+ }
124
+
125
+ let client: StreamVideoClient | undefined;
126
+ try {
127
+ client = await pushConfig.createStreamVideoClient();
128
+ if (!client) {
129
+ nativeLog(`video client not found, skipping the call.ring notification`);
130
+ await abandonPush();
131
+ return;
132
+ }
133
+ } catch (error) {
134
+ //we need to release the background task and stop the service to avoid stale owner
135
+ nativeLog(`Failed to create video client: ${error}`, 'error');
136
+ await abandonPush();
137
+ return;
138
+ }
139
+
140
+ if (asForegroundService) {
141
+ // Listen to call events from WS with the keep-alive headless task, bound to the call service.
142
+ (async () => {
143
+ try {
144
+ nativeLog(`onRingingCall (fg service) for callCid: ${call_cid}`);
145
+ const callFromPush = await client.onRingingCall(call_cid);
146
+ const { mustEndCall, endCallReason } = shouldCallBeClosed(
147
+ callFromPush,
148
+ data,
149
+ );
150
+ if (mustEndCall) {
151
+ nativeLog(
152
+ `Closing fg service callCid: ${call_cid} endCallReason: ${endCallReason}`,
153
+ );
154
+ callingx.endCallWithReason(call_cid, endCallReason);
155
+ callFromPush.leave({ reject: false }).catch((error) => {
156
+ nativeLog(
157
+ `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
158
+ 'error',
159
+ );
160
+ });
161
+ finishBackgroundTask();
162
+ pushUnsubscriptionCallbacks.delete(call_cid);
163
+ return;
164
+ }
165
+
166
+ // prevent subscriptions in case of call being left and subscriptions being already cleared
167
+ const unsubscribeFunctions = pushUnsubscriptionCallbacks.get(call_cid);
168
+ if (!unsubscribeFunctions) {
169
+ nativeLog(
170
+ `ring cancelled during onRingingCall for callCid: ${call_cid}, releasing background task`,
171
+ );
172
+ finishBackgroundTask();
173
+ return;
174
+ }
175
+ nativeLog(`watching WS for ringing callCid: ${call_cid}`);
176
+ // check if service needs to be closed if accept/decline event was done on another device
177
+ const unsubscribe = callFromPush.on(
178
+ 'all',
179
+ (event: AllCallEvents[AllClientEventTypes]) => {
180
+ const _canListenToWS = canListenToWS();
181
+ if (!_canListenToWS) {
182
+ nativeLog(
183
+ `Closing fg service from event ${event.type} callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
184
+ );
185
+ unsubscribeFunctions.forEach((fn) => fn());
186
+ return;
187
+ }
188
+
189
+ const {
190
+ mustEndCall: mustEndCallFromEvent,
191
+ endCallReason: endCallReasonFromEvent,
192
+ } = shouldCallBeClosed(callFromPush, data);
193
+ if (mustEndCallFromEvent) {
194
+ nativeLog(
195
+ `Closing fg service from event ${event.type} callCid: ${call_cid} shouldCallBeClosed`,
196
+ );
197
+ callingx.endCallWithReason(call_cid, endCallReasonFromEvent);
198
+ unsubscribeFunctions.forEach((fn) => fn());
199
+ }
200
+ },
201
+ );
202
+
203
+ // check if service needs to be closed if call was left
204
+ const stateSubscription = callFromPush.state.callingState$.subscribe(
205
+ (callingState) => {
206
+ if (
207
+ callingState === CallingState.IDLE ||
208
+ callingState === CallingState.LEFT
209
+ ) {
210
+ nativeLog(
211
+ `Closing fg service from callingState callCid: ${call_cid} callingState: ${callingState}`,
212
+ );
213
+ unsubscribeFunctions.forEach((fn) => fn());
214
+ }
215
+ },
216
+ );
217
+
218
+ //stop background task when app comes to foreground
219
+ const appStateSubscription = AppState.addEventListener(
220
+ 'change',
221
+ (nextAppState) => {
222
+ const _canListenToWS = canListenToWS();
223
+ nativeLog(
224
+ `AppState changed to: ${nextAppState} for callCid: ${call_cid} canListenToWS: ${_canListenToWS}`,
225
+ );
226
+ if (!_canListenToWS) {
227
+ unsubscribeFunctions.forEach((fn) => fn());
228
+ return;
229
+ }
230
+ },
231
+ );
232
+
233
+ unsubscribeFunctions.push(unsubscribe);
234
+ unsubscribeFunctions.push(() => stateSubscription.unsubscribe());
235
+ unsubscribeFunctions.push(() => appStateSubscription.remove());
236
+ unsubscribeFunctions.push(finishBackgroundTask);
237
+ unsubscribeFunctions.push(() =>
238
+ pushUnsubscriptionCallbacks.delete(call_cid),
239
+ );
240
+ nativeLog(`WS subscriptions registered for callCid: ${call_cid}`);
241
+ } catch (error) {
242
+ nativeLog(
243
+ `Failed to start background task with callCid: ${call_cid} error: ${error}`,
244
+ 'error',
245
+ );
246
+ pushUnsubscriptionCallbacks.delete(call_cid);
247
+ finishBackgroundTask();
248
+ }
249
+ })();
250
+ }
251
+
252
+ if (asForegroundService) {
253
+ // no need to check if call has be closed as that will be handled by the fg service
254
+ return;
255
+ }
256
+
257
+ nativeLog(`onRingingCall (foreground) for callCid: ${call_cid}`);
258
+ const callFromPush = await client.onRingingCall(call_cid);
259
+
260
+ const { mustEndCall, endCallReason } = shouldCallBeClosed(callFromPush, data);
261
+ if (mustEndCall) {
262
+ nativeLog(
263
+ `Removing incoming call notification immediately with callCid: ${call_cid} as it should be closed`,
264
+ );
265
+ callingx.endCallWithReason(call_cid, endCallReason);
266
+ callFromPush.leave({ reject: false }).catch((error) => {
267
+ nativeLog(
268
+ `Failed to leave already-ended ringing call ${call_cid}: ${error}`,
269
+ 'error',
270
+ );
271
+ });
272
+ }
273
+ };
@@ -1,12 +1,13 @@
1
- import { lib, type Type } from './lib';
2
-
3
- export type { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
4
- export type FirebaseMessagingType = Type;
1
+ import { lib, type MessagingModule } from './lib';
5
2
 
6
3
  const INSTALLATION_INSTRUCTION =
7
4
  'Please see https://rnfirebase.io/messaging/usage#installation for installation instructions';
8
5
 
9
- export function getFirebaseMessagingLib(): FirebaseMessagingType {
6
+ export type FirebaseRemoteMessage = NonNullable<
7
+ Awaited<ReturnType<MessagingModule['getInitialNotification']>>
8
+ >;
9
+
10
+ function getFirebaseMessagingModule() {
10
11
  if (!lib) {
11
12
  throw Error(
12
13
  '@react-native-firebase/messaging is not installed. ' +
@@ -15,3 +16,13 @@ export function getFirebaseMessagingLib(): FirebaseMessagingType {
15
16
  }
16
17
  return lib;
17
18
  }
19
+
20
+ export function getFirebaseMessagingLib() {
21
+ const messagingModule = getFirebaseMessagingModule();
22
+ const messaging = messagingModule.getMessaging();
23
+ return () => ({
24
+ getToken: () => messagingModule.getToken(messaging),
25
+ onTokenRefresh: (listener: (token: string) => void) =>
26
+ messagingModule.onTokenRefresh(messaging, listener),
27
+ });
28
+ }
@@ -1,9 +1,9 @@
1
- export type Type = typeof import('@react-native-firebase/messaging').default;
1
+ export type MessagingModule = typeof import('@react-native-firebase/messaging');
2
2
 
3
- let lib: Type | undefined;
3
+ let lib: MessagingModule | undefined;
4
4
 
5
5
  try {
6
- lib = require('@react-native-firebase/messaging').default;
6
+ lib = require('@react-native-firebase/messaging');
7
7
  } catch {}
8
8
 
9
9
  export { lib };
@@ -0,0 +1,25 @@
1
+ import { Platform } from 'react-native';
2
+ import { videoLoggerSystem } from '@stream-io/video-client';
3
+ import { StreamVideoConfig } from '../StreamVideoRN/types';
4
+ import { getCallingxLib } from './libs';
5
+ import { onRingNotificationReceived } from './internal/android';
6
+
7
+ export function setupAndroidPushEvents(
8
+ pushConfig: NonNullable<StreamVideoConfig['push']>,
9
+ ) {
10
+ const logger = videoLoggerSystem.getLogger('setupAndroidPushEvents');
11
+ if (Platform.OS !== 'android' || !pushConfig.android?.pushProviderName) {
12
+ logger.debug(
13
+ `setupAndroidPushEvents skipped: platform=${Platform.OS} pushProviderName=${pushConfig.android?.pushProviderName}`,
14
+ );
15
+ return;
16
+ }
17
+
18
+ const callingx = getCallingxLib();
19
+ callingx.addEventListener('ringCallPushReceived', (params) => {
20
+ logger.debug(`ringCallPushReceived event call_cid: ${params?.call_cid}`);
21
+ onRingNotificationReceived(params).catch((error) => {
22
+ logger.error(`Error in onRingNotificationReceived: ${error}`);
23
+ });
24
+ });
25
+ }
@@ -14,6 +14,7 @@ import {
14
14
  type EventParams,
15
15
  } from './libs/callingx';
16
16
  import { Platform } from 'react-native';
17
+ import { onRingNotificationReceived } from './internal/android';
17
18
 
18
19
  type PushConfig = NonNullable<StreamVideoConfig['push']>;
19
20
 
@@ -62,11 +63,10 @@ export function setupCallingExpEvents(pushConfig: NonNullable<PushConfig>) {
62
63
  const events = callingx.getInitialEvents();
63
64
  events.forEach((event: EventData) => {
64
65
  const { eventName, params } = event;
66
+ logger.debug(`${eventName} delayed event`);
65
67
  if (eventName === 'answerCall') {
66
- logger.debug(`answerCall delayed event callId: ${params?.callId}`);
67
68
  onAcceptCall(pushConfig)(params as EventParams['answerCall']);
68
69
  } else if (eventName === 'endCall') {
69
- logger.debug(`endCall delayed event callId: ${params?.callId}`);
70
70
  onEndCall(pushConfig)(params as EventParams['endCall']);
71
71
  } else if (eventName === 'didActivateAudioSession') {
72
72
  onDidActivateAudioSession();
@@ -74,6 +74,10 @@ export function setupCallingExpEvents(pushConfig: NonNullable<PushConfig>) {
74
74
  onDidDeactivateAudioSession();
75
75
  } else if (eventName === 'providerReset') {
76
76
  onProviderReset(pushConfig)(params as EventParams['providerReset']);
77
+ } else if (eventName === 'ringCallPushReceived') {
78
+ onRingNotificationReceived(params).catch((error) => {
79
+ logger.error(`Error in onRingNotificationReceived: ${error}`);
80
+ });
77
81
  }
78
82
  });
79
83
  }
@@ -128,7 +132,6 @@ const onAcceptCall =
128
132
 
129
133
  const callingx = getCallingxLib();
130
134
  clearPushWSEventSubscriptions(call_cid);
131
-
132
135
  processCallFromPushInBackground(
133
136
  pushConfig,
134
137
  call_cid,
@@ -9,20 +9,21 @@ import { getCallingxLib } from './libs';
9
9
  export function setupIosVoipPushEvents(
10
10
  pushConfig: NonNullable<StreamVideoConfig['push']>,
11
11
  ) {
12
- if (Platform.OS !== 'ios' || !pushConfig.ios?.pushProviderName) {
13
- return;
14
- }
15
12
  const logger = videoLoggerSystem.getLogger('setupIosVoipPushEvents');
16
- if (!pushConfig.ios.pushProviderName) {
17
- // TODO: remove this check and find a better way once we have telecom integration for android
13
+ if (Platform.OS !== 'ios' || !pushConfig.ios?.pushProviderName) {
18
14
  logger.debug(
19
- 'ios pushProviderName is not defined, so skipping the setupIosVoipPushEvents',
15
+ `setupIosVoipPushEvents skipped: platform=${Platform.OS} pushProviderName=${pushConfig.ios?.pushProviderName}`,
20
16
  );
21
17
  return;
22
18
  }
23
19
 
24
20
  const callingx = getCallingxLib();
25
21
  callingx.addEventListener('voipNotificationReceived', (params) => {
26
- onVoipNotificationReceived(params, pushConfig);
22
+ logger.debug(
23
+ `voipNotificationReceived event call_cid: ${params?.stream?.call_cid}`,
24
+ );
25
+ onVoipNotificationReceived(params, pushConfig).catch((error) => {
26
+ logger.error(`Error in onVoipNotificationReceived: ${error}`);
27
+ });
27
28
  });
28
29
  }