@stream-io/video-react-native-sdk 0.1.2 → 0.1.4

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 (111) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/dist/commonjs/components/Call/CallControls/HangupCallButton.js +4 -6
  3. package/dist/commonjs/components/Call/CallControls/HangupCallButton.js.map +1 -1
  4. package/dist/commonjs/hooks/push/index.js +4 -0
  5. package/dist/commonjs/hooks/push/index.js.map +1 -1
  6. package/dist/commonjs/hooks/push/useInitAndroidTokenAndRest.js +8 -2
  7. package/dist/commonjs/hooks/push/useInitAndroidTokenAndRest.js.map +1 -1
  8. package/dist/commonjs/hooks/push/useIosInitRemoteNotifications.js +34 -0
  9. package/dist/commonjs/hooks/push/useIosInitRemoteNotifications.js.map +1 -0
  10. package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js +50 -4
  11. package/dist/commonjs/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
  12. package/dist/commonjs/hooks/push/useProcessPushNonRingingCallEffect.js +50 -0
  13. package/dist/commonjs/hooks/push/useProcessPushNonRingingCallEffect.js.map +1 -0
  14. package/dist/commonjs/utils/StreamVideoRN/index.js +33 -0
  15. package/dist/commonjs/utils/StreamVideoRN/index.js.map +1 -1
  16. package/dist/commonjs/utils/internal/newNotificationCallbacks.js +18 -0
  17. package/dist/commonjs/utils/internal/newNotificationCallbacks.js.map +1 -0
  18. package/dist/commonjs/utils/internal/pushLogoutCallback.js +18 -0
  19. package/dist/commonjs/utils/internal/pushLogoutCallback.js.map +1 -0
  20. package/dist/commonjs/utils/push/android.js +182 -68
  21. package/dist/commonjs/utils/push/android.js.map +1 -1
  22. package/dist/commonjs/utils/push/ios.js +107 -1
  23. package/dist/commonjs/utils/push/ios.js.map +1 -1
  24. package/dist/commonjs/utils/push/libs.js +34 -1
  25. package/dist/commonjs/utils/push/libs.js.map +1 -1
  26. package/dist/commonjs/utils/push/rxSubjects.js +8 -1
  27. package/dist/commonjs/utils/push/rxSubjects.js.map +1 -1
  28. package/dist/commonjs/utils/push/utils.js +29 -1
  29. package/dist/commonjs/utils/push/utils.js.map +1 -1
  30. package/dist/commonjs/version.js +1 -1
  31. package/dist/module/components/Call/CallControls/HangupCallButton.js +3 -4
  32. package/dist/module/components/Call/CallControls/HangupCallButton.js.map +1 -1
  33. package/dist/module/hooks/push/index.js +4 -0
  34. package/dist/module/hooks/push/index.js.map +1 -1
  35. package/dist/module/hooks/push/useInitAndroidTokenAndRest.js +8 -2
  36. package/dist/module/hooks/push/useInitAndroidTokenAndRest.js.map +1 -1
  37. package/dist/module/hooks/push/useIosInitRemoteNotifications.js +28 -0
  38. package/dist/module/hooks/push/useIosInitRemoteNotifications.js.map +1 -0
  39. package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js +49 -4
  40. package/dist/module/hooks/push/useIosVoipPushEventsSetupEffect.js.map +1 -1
  41. package/dist/module/hooks/push/useProcessPushNonRingingCallEffect.js +44 -0
  42. package/dist/module/hooks/push/useProcessPushNonRingingCallEffect.js.map +1 -0
  43. package/dist/module/utils/StreamVideoRN/index.js +32 -0
  44. package/dist/module/utils/StreamVideoRN/index.js.map +1 -1
  45. package/dist/module/utils/internal/newNotificationCallbacks.js +10 -0
  46. package/dist/module/utils/internal/newNotificationCallbacks.js.map +1 -0
  47. package/dist/module/utils/internal/pushLogoutCallback.js +10 -0
  48. package/dist/module/utils/internal/pushLogoutCallback.js.map +1 -0
  49. package/dist/module/utils/push/android.js +184 -70
  50. package/dist/module/utils/push/android.js.map +1 -1
  51. package/dist/module/utils/push/ios.js +103 -1
  52. package/dist/module/utils/push/ios.js.map +1 -1
  53. package/dist/module/utils/push/libs.js +31 -1
  54. package/dist/module/utils/push/libs.js.map +1 -1
  55. package/dist/module/utils/push/rxSubjects.js +5 -0
  56. package/dist/module/utils/push/rxSubjects.js.map +1 -1
  57. package/dist/module/utils/push/utils.js +27 -0
  58. package/dist/module/utils/push/utils.js.map +1 -1
  59. package/dist/module/version.js +1 -1
  60. package/dist/typescript/hooks/push/index.d.ts.map +1 -1
  61. package/dist/typescript/hooks/push/useInitAndroidTokenAndRest.d.ts +1 -1
  62. package/dist/typescript/hooks/push/useInitAndroidTokenAndRest.d.ts.map +1 -1
  63. package/dist/typescript/hooks/push/useIosInitRemoteNotifications.d.ts +5 -0
  64. package/dist/typescript/hooks/push/useIosInitRemoteNotifications.d.ts.map +1 -0
  65. package/dist/typescript/hooks/push/useIosVoipPushEventsSetupEffect.d.ts.map +1 -1
  66. package/dist/typescript/hooks/push/useProcessPushNonRingingCallEffect.d.ts +7 -0
  67. package/dist/typescript/hooks/push/useProcessPushNonRingingCallEffect.d.ts.map +1 -0
  68. package/dist/typescript/utils/StreamVideoRN/index.d.ts +12 -0
  69. package/dist/typescript/utils/StreamVideoRN/index.d.ts.map +1 -1
  70. package/dist/typescript/utils/StreamVideoRN/types.d.ts +40 -6
  71. package/dist/typescript/utils/StreamVideoRN/types.d.ts.map +1 -1
  72. package/dist/typescript/utils/internal/newNotificationCallbacks.d.ts +10 -0
  73. package/dist/typescript/utils/internal/newNotificationCallbacks.d.ts.map +1 -0
  74. package/dist/typescript/utils/internal/pushLogoutCallback.d.ts +8 -0
  75. package/dist/typescript/utils/internal/pushLogoutCallback.d.ts.map +1 -0
  76. package/dist/typescript/utils/push/android.d.ts +1 -1
  77. package/dist/typescript/utils/push/android.d.ts.map +1 -1
  78. package/dist/typescript/utils/push/ios.d.ts +4 -0
  79. package/dist/typescript/utils/push/ios.d.ts.map +1 -1
  80. package/dist/typescript/utils/push/libs.d.ts +6 -0
  81. package/dist/typescript/utils/push/libs.d.ts.map +1 -1
  82. package/dist/typescript/utils/push/rxSubjects.d.ts +9 -0
  83. package/dist/typescript/utils/push/rxSubjects.d.ts.map +1 -1
  84. package/dist/typescript/utils/push/utils.d.ts +9 -1
  85. package/dist/typescript/utils/push/utils.d.ts.map +1 -1
  86. package/dist/typescript/version.d.ts +1 -1
  87. package/expo-config-plugin/dist/index.d.ts +3 -2
  88. package/expo-config-plugin/dist/index.js +9 -5
  89. package/expo-config-plugin/dist/withPushAppDelegate.d.ts +4 -0
  90. package/expo-config-plugin/dist/withPushAppDelegate.js +119 -0
  91. package/expo-config-plugin/dist/withiOSInfoPlist.d.ts +2 -1
  92. package/expo-config-plugin/dist/withiOSInfoPlist.js +6 -1
  93. package/package.json +19 -3
  94. package/src/components/Call/CallControls/HangupCallButton.tsx +4 -4
  95. package/src/hooks/push/index.ts +4 -0
  96. package/src/hooks/push/useInitAndroidTokenAndRest.ts +8 -2
  97. package/src/hooks/push/useIosInitRemoteNotifications.ts +29 -0
  98. package/src/hooks/push/useIosVoipPushEventsSetupEffect.ts +50 -3
  99. package/src/hooks/push/useProcessPushNonRingingCallEffect.ts +44 -0
  100. package/src/utils/StreamVideoRN/index.ts +36 -0
  101. package/src/utils/StreamVideoRN/types.ts +47 -6
  102. package/src/utils/internal/newNotificationCallbacks.ts +29 -0
  103. package/src/utils/internal/pushLogoutCallback.ts +17 -0
  104. package/src/utils/push/android.ts +203 -74
  105. package/src/utils/push/ios.ts +120 -1
  106. package/src/utils/push/libs.ts +48 -2
  107. package/src/utils/push/rxSubjects.ts +9 -0
  108. package/src/utils/push/utils.ts +35 -1
  109. package/src/version.ts +1 -1
  110. /package/expo-config-plugin/dist/{withAppDelegate.d.ts → withStreamVideoReactNativeSDKAppDelegate.d.ts} +0 -0
  111. /package/expo-config-plugin/dist/{withAppDelegate.js → withStreamVideoReactNativeSDKAppDelegate.js} +0 -0
@@ -2,14 +2,23 @@ import notifee, { EventType, Event } from '@notifee/react-native';
2
2
  import { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
3
3
  import { StreamVideoClient } from '@stream-io/video-client';
4
4
  import { Platform } from 'react-native';
5
- import type { StreamVideoConfig } from '../StreamVideoRN/types';
6
- import { getFirebaseMessagingLib } from './libs';
5
+ import type {
6
+ NonRingingPushEvent,
7
+ StreamVideoConfig,
8
+ } from '../StreamVideoRN/types';
9
+ import {
10
+ getFirebaseMessagingLib,
11
+ getExpoNotificationsLib,
12
+ getExpoTaskManagerLib,
13
+ } from './libs';
7
14
  import {
8
15
  pushAcceptedIncomingCallCId$,
9
16
  pushRejectedIncomingCallCId$,
10
17
  pushTappedIncomingCallCId$,
18
+ pushNonRingingCallData$,
11
19
  } from './rxSubjects';
12
20
  import { processCallFromPushInBackground } from './utils';
21
+ import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
13
22
 
14
23
  const ACCEPT_CALL_ACTION_ID = 'accept';
15
24
  const DECLINE_CALL_ACTION_ID = 'decline';
@@ -21,17 +30,59 @@ export function setupFirebaseHandlerAndroid(pushConfig: PushConfig) {
21
30
  if (Platform.OS !== 'android') {
22
31
  return;
23
32
  }
24
- const messaging = getFirebaseMessagingLib();
25
- messaging().setBackgroundMessageHandler(
26
- async (msg) => await firebaseMessagingOnMessageHandler(msg, pushConfig),
27
- );
28
- // messaging().onMessage(firebaseMessagingOnMessageHandler); // this is to listen to foreground messages, which we dont need for now
33
+ if (pushConfig.isExpo) {
34
+ const Notifications = getExpoNotificationsLib();
35
+ const TaskManager = getExpoTaskManagerLib();
36
+ const BACKGROUND_NOTIFICATION_TASK =
37
+ 'STREAM-VIDEO-SDK-INTERNAL-BACKGROUND-NOTIFICATION-TASK';
38
+
39
+ TaskManager.defineTask(BACKGROUND_NOTIFICATION_TASK, ({ data, error }) => {
40
+ if (error) {
41
+ return;
42
+ }
43
+ // @ts-ignore
44
+ const dataToProcess = data.notification?.data;
45
+ firebaseMessagingOnMessageHandler(dataToProcess, pushConfig);
46
+ });
47
+ // background handler
48
+ Notifications.registerTaskAsync(BACKGROUND_NOTIFICATION_TASK);
49
+ // foreground handler
50
+ Notifications.setNotificationHandler({
51
+ handleNotification: async (notification) => {
52
+ // @ts-ignore
53
+ const trigger = notification?.request?.trigger;
54
+ if (trigger.type === 'push') {
55
+ const data = trigger?.remoteMessage?.data;
56
+ if (data?.sender === 'stream.video') {
57
+ await firebaseMessagingOnMessageHandler(data, pushConfig);
58
+ return {
59
+ shouldShowAlert: false,
60
+ shouldPlaySound: false,
61
+ shouldSetBadge: false,
62
+ };
63
+ }
64
+ }
65
+ return {
66
+ shouldShowAlert: true,
67
+ shouldPlaySound: false,
68
+ shouldSetBadge: false,
69
+ };
70
+ },
71
+ });
72
+ } else {
73
+ const messaging = getFirebaseMessagingLib();
74
+ messaging().setBackgroundMessageHandler(
75
+ async (msg) =>
76
+ await firebaseMessagingOnMessageHandler(msg.data, pushConfig),
77
+ );
78
+ messaging().onMessage((msg) =>
79
+ firebaseMessagingOnMessageHandler(msg.data, pushConfig),
80
+ ); // this is to listen to foreground messages, which we dont need for now
81
+ }
29
82
  notifee.onBackgroundEvent(async (event) => {
30
- // NOTE: When app was opened from a quit state, we will never hit this when on accept event as app will open and the click event will go to foreground
31
83
  await onNotifeeEvent(event, pushConfig);
32
84
  });
33
85
  notifee.onForegroundEvent((event) => {
34
- // NOTE: When app was opened from a quit state, we will never hit this when on accept event as app will open and go to foreground immediately
35
86
  onNotifeeEvent(event, pushConfig);
36
87
  });
37
88
  }
@@ -40,23 +91,47 @@ export function setupFirebaseHandlerAndroid(pushConfig: PushConfig) {
40
91
  export async function initAndroidPushToken(
41
92
  client: StreamVideoClient,
42
93
  pushConfig: PushConfig,
94
+ setUnsubscribeListener: (unsubscribe: () => void) => void,
43
95
  ) {
44
- if (Platform.OS !== 'android') {
96
+ if (Platform.OS !== 'android' || !pushConfig.android.pushProviderName) {
45
97
  return;
46
98
  }
47
- const messaging = getFirebaseMessagingLib();
48
- const token = await messaging().getToken();
49
- const push_provider_name = pushConfig.android.pushProviderName;
50
- await client.addDevice(token, 'firebase', push_provider_name);
99
+ const setDeviceToken = async (token: string) => {
100
+ setPushLogoutCallback(() => {
101
+ client.removeDevice(token).catch((err) => {
102
+ console.warn('Failed to remove voip token from stream', err);
103
+ });
104
+ });
105
+ const push_provider_name = pushConfig.android.pushProviderName;
106
+ await client.addDevice(token, 'firebase', push_provider_name);
107
+ };
108
+ if (pushConfig.isExpo) {
109
+ const expoNotificationsLib = getExpoNotificationsLib();
110
+ const subscription = expoNotificationsLib.addPushTokenListener(
111
+ (devicePushToken) => {
112
+ setDeviceToken(devicePushToken.data);
113
+ },
114
+ );
115
+ setUnsubscribeListener(() => subscription.remove());
116
+ const devicePushToken =
117
+ await expoNotificationsLib.getDevicePushTokenAsync();
118
+ const token = devicePushToken.data;
119
+ await setDeviceToken(token);
120
+ } else {
121
+ const messaging = getFirebaseMessagingLib();
122
+ const unsubscribe = messaging().onTokenRefresh((refreshedToken) =>
123
+ setDeviceToken(refreshedToken),
124
+ );
125
+ setUnsubscribeListener(unsubscribe);
126
+ const token = await messaging().getToken();
127
+ await setDeviceToken(token);
128
+ }
51
129
  }
52
130
 
53
131
  const firebaseMessagingOnMessageHandler = async (
54
- message: FirebaseMessagingTypes.RemoteMessage,
132
+ data: FirebaseMessagingTypes.RemoteMessage['data'],
55
133
  pushConfig: PushConfig,
56
134
  ) => {
57
- if (Platform.OS !== 'android') {
58
- return;
59
- }
60
135
  /* Example data from firebase
61
136
  "message": {
62
137
  "data": {
@@ -72,43 +147,86 @@ const firebaseMessagingOnMessageHandler = async (
72
147
  // other stuff
73
148
  }
74
149
  */
75
- const data = message.data;
76
150
  if (!data || data.sender !== 'stream.video') {
77
151
  return;
78
152
  }
79
- await notifee.createChannel(pushConfig.android.incomingCallChannel);
80
- const { getTitle, getBody } =
81
- pushConfig.android.incomingCallNotificationTextGetters;
82
- const channelId = pushConfig.android.incomingCallChannel.id;
83
- const createdUserName = data.created_by_display_name;
84
- await notifee.displayNotification({
85
- title: getTitle(createdUserName),
86
- body: getBody(createdUserName),
87
- data,
88
- android: {
89
- channelId,
90
- pressAction: {
91
- id: 'default',
92
- launchActivity: 'default', // open the app when the notification is pressed
93
- },
94
- actions: [
95
- {
96
- title: 'Decline',
97
- pressAction: {
98
- id: DECLINE_CALL_ACTION_ID,
99
- },
153
+
154
+ if (data.type === 'call.ring') {
155
+ const incomingCallChannel = pushConfig.android.incomingCallChannel;
156
+ const incomingCallNotificationTextGetters =
157
+ pushConfig.android.incomingCallNotificationTextGetters;
158
+ if (!incomingCallChannel || !incomingCallNotificationTextGetters) {
159
+ console.debug(
160
+ "Can't show incoming call notification as either or both incomingCallChannel and was not provided",
161
+ );
162
+ return;
163
+ }
164
+ await notifee.createChannel(incomingCallChannel);
165
+ const { getTitle, getBody } = incomingCallNotificationTextGetters;
166
+ const createdUserName = data.created_by_display_name;
167
+
168
+ const channelId = incomingCallChannel.id;
169
+ await notifee.displayNotification({
170
+ title: getTitle(createdUserName),
171
+ body: getBody(createdUserName),
172
+ data,
173
+ android: {
174
+ channelId,
175
+ pressAction: {
176
+ id: 'default',
177
+ launchActivity: 'default', // open the app when the notification is pressed
100
178
  },
101
- {
102
- title: 'Accept',
103
- pressAction: {
104
- id: ACCEPT_CALL_ACTION_ID,
105
- launchActivity: 'default', // open the app when the notification is pressed
179
+ actions: [
180
+ {
181
+ title: 'Decline',
182
+ pressAction: {
183
+ id: DECLINE_CALL_ACTION_ID,
184
+ },
106
185
  },
186
+ {
187
+ title: 'Accept',
188
+ pressAction: {
189
+ id: ACCEPT_CALL_ACTION_ID,
190
+ launchActivity: 'default', // open the app when the notification is pressed
191
+ },
192
+ },
193
+ ],
194
+ timeoutAfter: 60000, // 60 seconds, after which the notification will be dismissed automatically
195
+ },
196
+ });
197
+ } else {
198
+ // the other types are call.live_started and call.notification
199
+ const callChannel = pushConfig.android.callChannel;
200
+ const callNotificationTextGetters =
201
+ pushConfig.android.callNotificationTextGetters;
202
+ if (!callChannel || !callNotificationTextGetters) {
203
+ console.debug(
204
+ "Can't show call notification as either or both callChannel and callNotificationTextGetters is not provided",
205
+ );
206
+ return;
207
+ }
208
+ await notifee.createChannel(callChannel);
209
+ const channelId = callChannel.id;
210
+ const { getTitle, getBody } = callNotificationTextGetters;
211
+ const createdUserName = data.created_by_display_name;
212
+ // we can safely cast to string because the data is from "stream.video"
213
+ const type = data.type as NonRingingPushEvent;
214
+ await notifee.displayNotification({
215
+ title: getTitle(type, createdUserName),
216
+ body: getBody(type, createdUserName),
217
+ data,
218
+ android: {
219
+ channelId,
220
+ pressAction: {
221
+ id: 'default',
222
+ launchActivity: 'default', // open the app when the notification is pressed
107
223
  },
108
- ],
109
- timeoutAfter: 60000, // 60 seconds, after which the notification will be dismissed automatically
110
- },
111
- });
224
+ timeoutAfter: 60000, // 60 seconds, after which the notification will be dismissed automatically
225
+ },
226
+ });
227
+ const cid = data.call_cid;
228
+ pushNonRingingCallData$.next({ cid, type });
229
+ }
112
230
  };
113
231
 
114
232
  const onNotifeeEvent = async (event: Event, pushConfig: PushConfig) => {
@@ -128,32 +246,43 @@ const onNotifeeEvent = async (event: Event, pushConfig: PushConfig) => {
128
246
  // we can safely cast to string because the data is from "stream.video"
129
247
  const call_cid = data.call_cid as string;
130
248
 
131
- // check if we have observers for the call cid (this means the app is in the foreground state)
132
- const hasObservers =
133
- pushAcceptedIncomingCallCId$.observed &&
134
- pushRejectedIncomingCallCId$.observed;
249
+ if (data.type === 'call.ring') {
250
+ // check if we have observers for the call cid (this means the app is in the foreground state)
251
+ const hasObservers =
252
+ pushAcceptedIncomingCallCId$.observed &&
253
+ pushRejectedIncomingCallCId$.observed;
135
254
 
136
- // Check if we need to decline the call
137
- const didPressDecline =
138
- type === EventType.ACTION_PRESS &&
139
- pressAction.id === DECLINE_CALL_ACTION_ID;
140
- const didDismiss = type === EventType.DISMISSED;
141
- const mustDecline = didPressDecline || didDismiss;
142
- // Check if we need to accept the call
143
- const mustAccept =
144
- type === EventType.ACTION_PRESS && pressAction.id === ACCEPT_CALL_ACTION_ID;
145
- if (mustAccept) {
146
- pushAcceptedIncomingCallCId$.next(call_cid);
147
- // NOTE: accept will be handled by the app with rxjs observers as the app will go to foreground always
148
- } else if (mustDecline) {
149
- pushRejectedIncomingCallCId$.next(call_cid);
150
- if (hasObservers) {
151
- // if we had observers we can return here as the observers will handle the call as the app is in the foreground state
152
- return;
255
+ // Check if we need to decline the call
256
+ const didPressDecline =
257
+ type === EventType.ACTION_PRESS &&
258
+ pressAction.id === DECLINE_CALL_ACTION_ID;
259
+ const didDismiss = type === EventType.DISMISSED;
260
+ const mustDecline = didPressDecline || didDismiss;
261
+ // Check if we need to accept the call
262
+ const mustAccept =
263
+ type === EventType.ACTION_PRESS &&
264
+ pressAction.id === ACCEPT_CALL_ACTION_ID;
265
+ if (mustAccept) {
266
+ pushAcceptedIncomingCallCId$.next(call_cid);
267
+ // NOTE: accept will be handled by the app with rxjs observers as the app will go to foreground always
268
+ } else if (mustDecline) {
269
+ pushRejectedIncomingCallCId$.next(call_cid);
270
+ if (hasObservers) {
271
+ // if we had observers we can return here as the observers will handle the call as the app is in the foreground state
272
+ return;
273
+ }
274
+ await processCallFromPushInBackground(pushConfig, call_cid, 'decline');
275
+ } else if (type === EventType.PRESS) {
276
+ pushTappedIncomingCallCId$.next(call_cid);
277
+ // pressed state will be handled by the app with rxjs observers as the app will go to foreground always
278
+ }
279
+ } else {
280
+ if (type === EventType.PRESS) {
281
+ pushTappedIncomingCallCId$.next(call_cid);
282
+ pushConfig.onTapNonRingingCallNotification?.(
283
+ call_cid,
284
+ data.type as NonRingingPushEvent,
285
+ );
153
286
  }
154
- await processCallFromPushInBackground(pushConfig, call_cid, 'decline');
155
- } else if (type === EventType.PRESS) {
156
- pushTappedIncomingCallCId$.next(call_cid);
157
- // pressed state will be handled by the app with rxjs observers as the app will go to foreground always
158
287
  }
159
288
  };
@@ -1,14 +1,45 @@
1
- import type { StreamVideoConfig } from '../StreamVideoRN/types';
1
+ import { Platform } from 'react-native';
2
+ import type {
3
+ NonRingingPushEvent,
4
+ StreamVideoConfig,
5
+ } from '../StreamVideoRN/types';
2
6
  import {
3
7
  pushAcceptedIncomingCallCId$,
4
8
  voipPushNotificationCallCId$,
5
9
  voipCallkeepCallOnForegroundMap$,
6
10
  voipCallkeepAcceptedCallOnNativeDialerMap$,
11
+ pushNonRingingCallData$,
7
12
  } from './rxSubjects';
8
13
  import { processCallFromPushInBackground } from './utils';
14
+ import { getExpoNotificationsLib, getPushNotificationIosLib } from './libs';
15
+ import { StreamVideoClient } from '@stream-io/video-client';
16
+ import { setPushLogoutCallback } from '../internal/pushLogoutCallback';
17
+ import notifee, { EventType } from '@notifee/react-native';
9
18
 
10
19
  type PushConfig = NonNullable<StreamVideoConfig['push']>;
11
20
 
21
+ type StreamPayload =
22
+ | {
23
+ call_cid: string;
24
+ type: 'call.ring' | NonRingingPushEvent;
25
+ sender: string;
26
+ }
27
+ | undefined;
28
+
29
+ function processNonRingingNotificationStreamPayload(
30
+ streamPayload: StreamPayload,
31
+ ) {
32
+ if (
33
+ streamPayload?.sender === 'stream.video' &&
34
+ streamPayload?.type !== 'call.ring'
35
+ ) {
36
+ const cid = streamPayload.call_cid;
37
+ const type = streamPayload.type;
38
+ pushNonRingingCallData$.next({ cid, type });
39
+ return { cid, type };
40
+ }
41
+ }
42
+
12
43
  export const iosCallkeepAcceptCall = (
13
44
  call_cid: string | undefined,
14
45
  callUUIDFromCallkeep: string,
@@ -55,3 +86,91 @@ const shouldProcessCallFromCallkeep = (
55
86
  }
56
87
  return true;
57
88
  };
89
+
90
+ export const setupRemoteNotificationsHandleriOS = (pushConfig: PushConfig) => {
91
+ if (Platform.OS !== 'ios') {
92
+ return;
93
+ }
94
+ notifee.onForegroundEvent(({ type, detail }) => {
95
+ if (type === EventType.PRESS) {
96
+ const streamPayload = detail.notification?.data?.stream as
97
+ | StreamPayload
98
+ | undefined;
99
+ const result = processNonRingingNotificationStreamPayload(streamPayload);
100
+ if (result) {
101
+ pushConfig.onTapNonRingingCallNotification?.(result.cid, result.type);
102
+ }
103
+ }
104
+ });
105
+ if (pushConfig.isExpo) {
106
+ const Notifications = getExpoNotificationsLib();
107
+
108
+ // foreground handler (just to show the notifications on foreground)
109
+ Notifications.setNotificationHandler({
110
+ handleNotification: async () => {
111
+ return {
112
+ shouldShowAlert: true,
113
+ shouldPlaySound: true,
114
+ shouldSetBadge: false,
115
+ };
116
+ },
117
+ });
118
+ }
119
+ };
120
+
121
+ /** Send token to stream */
122
+ export async function initIosNonVoipToken(
123
+ client: StreamVideoClient,
124
+ pushConfig: PushConfig,
125
+ setUnsubscribeListener: (unsubscribe: () => void) => void,
126
+ ) {
127
+ if (Platform.OS !== 'ios' || !pushConfig.android.pushProviderName) {
128
+ return;
129
+ }
130
+ const setDeviceToken = async (token: string) => {
131
+ setPushLogoutCallback(() => {
132
+ client.removeDevice(token).catch((err) => {
133
+ console.warn('Failed to remove voip token from stream', err);
134
+ });
135
+ });
136
+ const push_provider_name = pushConfig.ios.pushProviderName;
137
+ await client.addDevice(token, 'apn', push_provider_name);
138
+ };
139
+ if (pushConfig.isExpo) {
140
+ const expoNotificationsLib = getExpoNotificationsLib();
141
+ const subscription = expoNotificationsLib.addPushTokenListener(
142
+ (devicePushToken) => {
143
+ setDeviceToken(devicePushToken.data);
144
+ },
145
+ );
146
+ const subscriptionForReceive =
147
+ expoNotificationsLib.addNotificationReceivedListener((event) => {
148
+ // listen to foreground notifications
149
+ if (event.request.trigger.type === 'push') {
150
+ const streamPayload = event.request.trigger.payload
151
+ ?.stream as StreamPayload;
152
+ processNonRingingNotificationStreamPayload(streamPayload);
153
+ }
154
+ });
155
+ setUnsubscribeListener(() => {
156
+ subscription.remove();
157
+ subscriptionForReceive.remove();
158
+ });
159
+ } else {
160
+ const pushNotificationIosLib = getPushNotificationIosLib();
161
+ pushNotificationIosLib.addEventListener('register', (token) => {
162
+ setDeviceToken(token);
163
+ });
164
+ pushNotificationIosLib.addEventListener('notification', (notification) => {
165
+ const data = notification.getData();
166
+ const streamPayload = data?.stream as StreamPayload;
167
+ // listen to foreground notifications
168
+ processNonRingingNotificationStreamPayload(streamPayload);
169
+ notification.finish(pushNotificationIosLib.FetchResult.NoData);
170
+ });
171
+ setUnsubscribeListener(() => {
172
+ pushNotificationIosLib.removeEventListener('register');
173
+ pushNotificationIosLib.removeEventListener('notification');
174
+ });
175
+ }
176
+ }
@@ -1,14 +1,20 @@
1
1
  export type { FirebaseMessagingTypes } from '@react-native-firebase/messaging';
2
-
3
2
  export type RNCallKeepType = typeof import('react-native-callkeep').default;
4
3
  export type FirebaseMessagingType =
5
4
  typeof import('@react-native-firebase/messaging').default;
6
5
  export type VoipPushNotificationType =
7
6
  typeof import('react-native-voip-push-notification').default;
7
+ export type ExpoNotificationsLib = typeof import('expo-notifications');
8
+ export type ExpoTaskManagerLib = typeof import('expo-task-manager');
9
+ export type PushNotificationIosLib =
10
+ typeof import('@react-native-community/push-notification-ios').default;
8
11
 
9
12
  let callkeep: RNCallKeepType | undefined;
10
13
  let messaging: FirebaseMessagingType | undefined;
11
14
  let voipPushNotification: VoipPushNotificationType | undefined;
15
+ let expoNotificationsLib: ExpoNotificationsLib | undefined;
16
+ let expoTaskManagerLib: ExpoTaskManagerLib | undefined;
17
+ let pushNotificationIosLib: PushNotificationIosLib | undefined;
12
18
 
13
19
  try {
14
20
  callkeep = require('react-native-callkeep').default;
@@ -22,6 +28,46 @@ try {
22
28
  voipPushNotification = require('react-native-voip-push-notification').default;
23
29
  } catch (e) {}
24
30
 
31
+ try {
32
+ expoNotificationsLib = require('expo-notifications');
33
+ } catch (e) {}
34
+
35
+ try {
36
+ expoTaskManagerLib = require('expo-task-manager');
37
+ } catch (e) {}
38
+
39
+ try {
40
+ pushNotificationIosLib =
41
+ require('@react-native-community/push-notification-ios').default;
42
+ } catch (e) {}
43
+
44
+ export function getExpoNotificationsLib() {
45
+ if (!expoNotificationsLib) {
46
+ throw Error(
47
+ 'expo-notifications library is not installed. Please see https://docs.expo.dev/versions/latest/sdk/notifications/ for installation instructions',
48
+ );
49
+ }
50
+ return expoNotificationsLib;
51
+ }
52
+
53
+ export function getExpoTaskManagerLib() {
54
+ if (!expoTaskManagerLib) {
55
+ throw Error(
56
+ 'expo-task-manager library is not installed. Please see https://docs.expo.dev/versions/latest/sdk/task-manager/ for installation instructions',
57
+ );
58
+ }
59
+ return expoTaskManagerLib;
60
+ }
61
+
62
+ export function getPushNotificationIosLib() {
63
+ if (!pushNotificationIosLib) {
64
+ throw Error(
65
+ '@react-native-community/push-notification-ios library is not installed. Please install it using "yarn add @react-native-community/push-notification-ios" or "npm i @react-native-community/push-notification-ios --save"',
66
+ );
67
+ }
68
+ return pushNotificationIosLib;
69
+ }
70
+
25
71
  export function getCallKeepLib() {
26
72
  if (!callkeep) {
27
73
  throw Error(
@@ -43,7 +89,7 @@ export function getFirebaseMessagingLib() {
43
89
  export function getVoipPushNotificationLib() {
44
90
  if (!voipPushNotification) {
45
91
  throw Error(
46
- "react-native-voip-push-notification library is not installed. Please install it using 'yarn add react-native-voip-push-notification' or 'npm install react-native-voip-push-notification'",
92
+ "react-native-voip-push-notification library is not installed. Please install it using 'yarn add react-native-voip-push-notification' or 'npm i react-native-voip-push-notification --save'",
47
93
  );
48
94
  }
49
95
  return voipPushNotification;
@@ -1,4 +1,13 @@
1
1
  import { BehaviorSubject } from 'rxjs';
2
+ import { NonRingingPushEvent } from '../StreamVideoRN/types';
3
+
4
+ /**
5
+ * This rxjs subject is used to store the call cid of the accepted incoming call from push notification
6
+ * Note: it is should be subscribed only when a user has connected to the websocket of Stream
7
+ */
8
+ export const pushNonRingingCallData$ = new BehaviorSubject<
9
+ { cid: string; type: NonRingingPushEvent } | undefined
10
+ >(undefined);
2
11
 
3
12
  /**
4
13
  * This rxjs subject is used to store the call cid of the accepted incoming call from push notification
@@ -1,5 +1,9 @@
1
1
  import { Call, StreamVideoClient } from '@stream-io/video-client';
2
- import type { StreamVideoConfig } from '../StreamVideoRN/types';
2
+ import type {
3
+ NonRingingPushEvent,
4
+ StreamVideoConfig,
5
+ } from '../StreamVideoRN/types';
6
+ import { onNewCallNotification } from '../internal/newNotificationCallbacks';
3
7
 
4
8
  type PushConfig = NonNullable<StreamVideoConfig['push']>;
5
9
 
@@ -56,3 +60,33 @@ export const processCallFromPush = async (
56
60
  console.log('failed to process call from push notification', e, action);
57
61
  }
58
62
  };
63
+
64
+ /**
65
+ * This function is used process the call from push notifications due to non ringing calls
66
+ * It does the following steps:
67
+ * 1. Get the call from the client if present or create a new call
68
+ * 2. Fetch the latest state of the call from the server if its not already in ringing state
69
+ * 3. Call all the callbacks to inform the app about the call
70
+ */
71
+ export const processNonIncomingCallFromPush = async (
72
+ client: StreamVideoClient,
73
+ call_cid: string,
74
+ nonRingingNotificationType: NonRingingPushEvent,
75
+ ) => {
76
+ let callFromPush: Call;
77
+ try {
78
+ const _callFromPush = client.state.calls.find((c) => c.cid === call_cid);
79
+ if (_callFromPush) {
80
+ callFromPush = _callFromPush;
81
+ } else {
82
+ // if not it means that WS is not alive when receiving the push notifications and we need to fetch the call
83
+ const [callType, callId] = call_cid.split(':');
84
+ callFromPush = client.call(callType, callId);
85
+ await callFromPush.get();
86
+ }
87
+ } catch (e) {
88
+ console.log('failed to fetch call from push notification', e);
89
+ return;
90
+ }
91
+ onNewCallNotification(callFromPush, nonRingingNotificationType);
92
+ };
package/src/version.ts CHANGED
@@ -1 +1 @@
1
- export const version = '0.1.2';
1
+ export const version = '0.1.4';