@stream-io/react-native-callingx 0.1.1-beta.0 → 0.1.1
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.
- package/CHANGELOG.md +15 -0
- package/README.md +45 -357
- package/android/src/main/java/io/getstream/rn/callingx/CallRegistrationStore.kt +34 -65
- package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +45 -43
- package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +8 -235
- package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +9 -25
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +18 -6
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +41 -18
- package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +24 -0
- package/dist/module/CallingxModule.js +7 -14
- package/dist/module/CallingxModule.js.map +1 -1
- package/dist/module/spec/NativeCallingx.js.map +1 -1
- package/dist/module/utils/constants.js +0 -1
- package/dist/module/utils/constants.js.map +1 -1
- package/dist/typescript/src/CallingxModule.d.ts +1 -2
- package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
- package/dist/typescript/src/spec/NativeCallingx.d.ts +0 -3
- package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
- package/dist/typescript/src/types.d.ts +2 -5
- package/dist/typescript/src/types.d.ts.map +1 -1
- package/dist/typescript/src/utils/constants.d.ts +1 -2
- package/dist/typescript/src/utils/constants.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/CallingxModule.ts +6 -11
- package/src/spec/NativeCallingx.ts +0 -3
- package/src/types.ts +2 -4
- package/src/utils/constants.ts +0 -3
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver).
|
|
4
|
+
|
|
5
|
+
## [0.1.1](https://github.com/GetStream/stream-video-js/compare/@stream-io/react-native-callingx-0.1.0...@stream-io/react-native-callingx-0.1.1) (2026-04-09)
|
|
6
|
+
|
|
7
|
+
### Bug Fixes
|
|
8
|
+
|
|
9
|
+
- callingx docs update ([#2195](https://github.com/GetStream/stream-video-js/issues/2195)) ([7a6b632](https://github.com/GetStream/stream-video-js/commit/7a6b632270ec1187236a0e4e5c5396a98a20fd16))
|
|
10
|
+
|
|
11
|
+
## 0.1.0 (2026-04-09)
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
- callkit/telecom integration ([#2028](https://github.com/GetStream/stream-video-js/issues/2028)) ([d579acd](https://github.com/GetStream/stream-video-js/commit/d579acd1975fb4945e40452b27e372694c737628))
|
package/README.md
CHANGED
|
@@ -1,395 +1,83 @@
|
|
|
1
|
-
# react-native-callingx
|
|
1
|
+
# @stream-io/react-native-callingx
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React Native native-calling bridge for:
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- iOS CallKit
|
|
6
|
+
- Android Telecom/ConnectionService
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- 📲 **Outgoing call registration** — Register outgoing calls with the system
|
|
9
|
-
- 🎛️ **Call controls** — Mute, hold, end calls with native system integration
|
|
10
|
-
- 🔔 **Custom notifications** — Configurable Android notification channels
|
|
11
|
-
- ⚡ **Turbo Module** — Built with the New Architecture for optimal performance
|
|
12
|
-
- 📱 **Background support** — Handle calls when the app is backgrounded or killed
|
|
13
|
-
|
|
14
|
-
## Requirements
|
|
15
|
-
|
|
16
|
-
- React Native 0.73+ (New Architecture / Turbo Modules)
|
|
17
|
-
- iOS 13.0+
|
|
18
|
-
- Android API 26+ (Android 8.0 Oreo)
|
|
19
|
-
|
|
20
|
-
## Installation
|
|
8
|
+
## Install
|
|
21
9
|
|
|
22
10
|
```sh
|
|
23
|
-
npm install @stream-io/react-native-callingx
|
|
24
|
-
# or
|
|
25
11
|
yarn add @stream-io/react-native-callingx
|
|
26
12
|
```
|
|
27
13
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
1. Add the required background modes to your `Info.plist`:
|
|
31
|
-
|
|
32
|
-
```xml
|
|
33
|
-
<key>UIBackgroundModes</key>
|
|
34
|
-
<array>
|
|
35
|
-
<string>voip</string>
|
|
36
|
-
<string>audio</string>
|
|
37
|
-
</array>
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
2. Run pod install:
|
|
14
|
+
Then run iOS pods:
|
|
41
15
|
|
|
42
16
|
```sh
|
|
43
17
|
cd ios && pod install
|
|
44
18
|
```
|
|
45
19
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
```objc
|
|
49
|
-
#import <Callingx/CallingxPublic.h>
|
|
50
|
-
|
|
51
|
-
- (void)pushRegistry:(PKPushRegistry *)registry
|
|
52
|
-
didReceiveIncomingPushWithPayload:(PKPushPayload *)payload
|
|
53
|
-
forType:(PKPushType)type
|
|
54
|
-
withCompletionHandler:(void (^)(void))completion {
|
|
55
|
-
|
|
56
|
-
// Extract call information from payload
|
|
57
|
-
NSString *callId = payload.dictionaryPayload[@"call_id"];
|
|
58
|
-
NSString *callerName = payload.dictionaryPayload[@"caller_name"];
|
|
59
|
-
NSString *handle = payload.dictionaryPayload[@"handle"];
|
|
60
|
-
BOOL hasVideo = [payload.dictionaryPayload[@"has_video"] boolValue];
|
|
61
|
-
|
|
62
|
-
[Callingx reportNewIncomingCall:callId
|
|
63
|
-
handle:handle
|
|
64
|
-
handleType:@"generic"
|
|
65
|
-
hasVideo:hasVideo
|
|
66
|
-
localizedCallerName:callerName
|
|
67
|
-
supportsHolding:YES
|
|
68
|
-
supportsDTMF:NO
|
|
69
|
-
supportsGrouping:NO
|
|
70
|
-
supportsUngrouping:NO
|
|
71
|
-
payload:payload.dictionaryPayload
|
|
72
|
-
withCompletionHandler:completion];
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### Android Setup
|
|
20
|
+
## Quick start
|
|
77
21
|
|
|
78
|
-
|
|
22
|
+
```ts
|
|
23
|
+
import { CallingxModule } from '@stream-io/react-native-callingx';
|
|
79
24
|
|
|
80
|
-
### Setup
|
|
81
|
-
|
|
82
|
-
Initialize the module with platform-specific configuration:
|
|
83
|
-
|
|
84
|
-
```typescript
|
|
85
|
-
import { CallingxModule } from 'react-native-callingx';
|
|
86
|
-
|
|
87
|
-
// Setup must be called before any other method
|
|
88
25
|
CallingxModule.setup({
|
|
89
26
|
ios: {
|
|
90
|
-
appName: 'My App',
|
|
91
27
|
supportsVideo: true,
|
|
92
|
-
|
|
93
|
-
maximumCallGroups: 1,
|
|
94
|
-
handleType: 'generic', // 'generic' | 'number' | 'phone' | 'email'
|
|
28
|
+
callsHistory: false,
|
|
95
29
|
},
|
|
96
30
|
android: {
|
|
97
31
|
incomingChannel: {
|
|
98
|
-
id: '
|
|
99
|
-
name: 'Incoming
|
|
100
|
-
sound: 'ringtone', // optional custom sound
|
|
101
|
-
vibration: true,
|
|
32
|
+
id: 'incoming_calls_channel',
|
|
33
|
+
name: 'Incoming calls',
|
|
102
34
|
},
|
|
103
|
-
outgoingChannel: {
|
|
104
|
-
id: 'ongoing_calls',
|
|
105
|
-
name: 'Ongoing Calls',
|
|
106
|
-
},
|
|
107
|
-
// Optional: transform display text
|
|
108
|
-
titleTransformer: (name) => `Call from ${name}`,
|
|
109
|
-
subtitleTransformer: (phoneNumber) => phoneNumber,
|
|
110
35
|
},
|
|
111
36
|
});
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
### Request Permissions
|
|
115
37
|
|
|
116
|
-
Before displaying calls, request the required permissions:
|
|
117
|
-
|
|
118
|
-
```typescript
|
|
119
|
-
const permissions = await CallingxModule.requestPermissions();
|
|
120
|
-
console.log('Audio permission:', permissions.recordAudio);
|
|
121
|
-
console.log('Notification permission:', permissions.postNotifications);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
### Display Incoming Call
|
|
125
|
-
|
|
126
|
-
Show the native incoming call UI:
|
|
127
|
-
|
|
128
|
-
```typescript
|
|
129
38
|
await CallingxModule.displayIncomingCall(
|
|
130
|
-
'
|
|
131
|
-
'+
|
|
132
|
-
'John Doe', // caller name
|
|
133
|
-
true, // has video
|
|
134
|
-
);
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
### Start Outgoing Call
|
|
138
|
-
|
|
139
|
-
Register an outgoing call with the system:
|
|
140
|
-
|
|
141
|
-
```typescript
|
|
142
|
-
await CallingxModule.startCall(
|
|
143
|
-
'unique-call-id',
|
|
144
|
-
'+1234567890',
|
|
39
|
+
'call-id',
|
|
40
|
+
'+123456789',
|
|
145
41
|
'John Doe',
|
|
146
|
-
|
|
42
|
+
true,
|
|
147
43
|
);
|
|
148
44
|
```
|
|
149
45
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
Answer an incoming call programmatically:
|
|
153
|
-
|
|
154
|
-
```typescript
|
|
155
|
-
await CallingxModule.answerIncomingCall('unique-call-id');
|
|
156
|
-
```
|
|
157
|
-
|
|
158
|
-
### Activate Call
|
|
159
|
-
|
|
160
|
-
Mark a call as active (connected):
|
|
161
|
-
|
|
162
|
-
```typescript
|
|
163
|
-
await CallingxModule.setCurrentCallActive('unique-call-id');
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### End Call
|
|
167
|
-
|
|
168
|
-
End a call with a specific reason:
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import type { EndCallReason } from 'react-native-callingx';
|
|
172
|
-
|
|
173
|
-
// Available reasons:
|
|
174
|
-
// 'local' | 'remote' | 'rejected' | 'busy' | 'answeredElsewhere' |
|
|
175
|
-
// 'missed' | 'error' | 'canceled' | 'restricted' | 'unknown'
|
|
176
|
-
await CallingxModule.endCallWithReason('unique-call-id', 'remote');
|
|
177
|
-
```
|
|
178
|
-
|
|
179
|
-
### Mute/Unmute
|
|
180
|
-
|
|
181
|
-
Toggle call mute state:
|
|
182
|
-
|
|
183
|
-
```typescript
|
|
184
|
-
await CallingxModule.setMutedCall('unique-call-id', true); // mute
|
|
185
|
-
await CallingxModule.setMutedCall('unique-call-id', false); // unmute
|
|
186
|
-
```
|
|
187
|
-
|
|
188
|
-
### Hold/Unhold
|
|
189
|
-
|
|
190
|
-
Toggle call hold state:
|
|
191
|
-
|
|
192
|
-
```typescript
|
|
193
|
-
await CallingxModule.setOnHoldCall('unique-call-id', true); // hold
|
|
194
|
-
await CallingxModule.setOnHoldCall('unique-call-id', false); // unhold
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
### Update Display
|
|
198
|
-
|
|
199
|
-
Update the caller information during a call:
|
|
200
|
-
|
|
201
|
-
```typescript
|
|
202
|
-
await CallingxModule.updateDisplay(
|
|
203
|
-
'unique-call-id',
|
|
204
|
-
'+1234567890',
|
|
205
|
-
'Updated Name',
|
|
206
|
-
);
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
### Event Listeners
|
|
210
|
-
|
|
211
|
-
Subscribe to call events:
|
|
212
|
-
|
|
213
|
-
```typescript
|
|
214
|
-
import { CallingxModule } from 'react-native-callingx';
|
|
215
|
-
import type { EventName } from 'react-native-callingx';
|
|
216
|
-
|
|
217
|
-
// Answer event - user answered from system UI
|
|
218
|
-
const answerSubscription = CallingxModule.addEventListener(
|
|
219
|
-
'answerCall',
|
|
220
|
-
(params) => {
|
|
221
|
-
console.log('Call answered:', params.callId);
|
|
222
|
-
},
|
|
223
|
-
);
|
|
224
|
-
|
|
225
|
-
// End event - call ended
|
|
226
|
-
const endSubscription = CallingxModule.addEventListener('endCall', (params) => {
|
|
227
|
-
console.log('Call ended:', params.callId, 'Cause:', params.cause);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// Hold toggle event
|
|
231
|
-
const holdSubscription = CallingxModule.addEventListener(
|
|
232
|
-
'didToggleHoldCallAction',
|
|
233
|
-
(params) => {
|
|
234
|
-
console.log('Hold toggled:', params.callId, 'On hold:', params.hold);
|
|
235
|
-
},
|
|
236
|
-
);
|
|
237
|
-
|
|
238
|
-
// Mute toggle event
|
|
239
|
-
const muteSubscription = CallingxModule.addEventListener(
|
|
240
|
-
'didPerformSetMutedCallAction',
|
|
241
|
-
(params) => {
|
|
242
|
-
console.log('Mute toggled:', params.callId, 'Muted:', params.muted);
|
|
243
|
-
},
|
|
244
|
-
);
|
|
245
|
-
|
|
246
|
-
// Start call action (outgoing call initiated from system)
|
|
247
|
-
const startSubscription = CallingxModule.addEventListener(
|
|
248
|
-
'didReceiveStartCallAction',
|
|
249
|
-
(params) => {
|
|
250
|
-
console.log('Start call action:', params.callId);
|
|
251
|
-
},
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// Clean up when done
|
|
255
|
-
answerSubscription.remove();
|
|
256
|
-
endSubscription.remove();
|
|
257
|
-
// ... remove other subscriptions
|
|
258
|
-
```
|
|
259
|
-
|
|
260
|
-
### Handle Initial Events
|
|
261
|
-
|
|
262
|
-
When the app is launched from a killed state by a call action, retrieve queued events:
|
|
263
|
-
|
|
264
|
-
```typescript
|
|
265
|
-
// Get events that occurred before the module was initialized
|
|
266
|
-
const initialEvents = CallingxModule.getInitialEvents();
|
|
267
|
-
initialEvents.forEach((event) => {
|
|
268
|
-
console.log('Initial event:', event.eventName, event.params);
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
// Clear initial events after processing
|
|
272
|
-
await CallingxModule.clearInitialEvents();
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
### Background Tasks (Android)
|
|
276
|
-
|
|
277
|
-
Run background tasks for call-related operations:
|
|
278
|
-
|
|
279
|
-
```typescript
|
|
280
|
-
// Start a managed background task
|
|
281
|
-
await CallingxModule.startBackgroundTask(async (taskData, stopTask) => {
|
|
282
|
-
try {
|
|
283
|
-
// Perform background work (e.g., connect to call server)
|
|
284
|
-
await connectToCallServer();
|
|
285
|
-
} finally {
|
|
286
|
-
stopTask(); // Always call when done
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
// Or stop manually
|
|
291
|
-
await CallingxModule.stopBackgroundTask();
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
## API Reference
|
|
295
|
-
|
|
296
|
-
### CallingxModule
|
|
297
|
-
|
|
298
|
-
| Method | Description |
|
|
299
|
-
| ---------------------------------------------------------------- | ---------------------------------------------------- |
|
|
300
|
-
| `setup(options)` | Initialize the module with platform-specific options |
|
|
301
|
-
| `requestPermissions()` | Request required permissions (audio, notifications) |
|
|
302
|
-
| `checkPermissions()` | Check current permission status |
|
|
303
|
-
| `displayIncomingCall(callId, phoneNumber, callerName, hasVideo)` | Display incoming call UI |
|
|
304
|
-
| `answerIncomingCall(callId)` | Answer an incoming call |
|
|
305
|
-
| `startCall(callId, phoneNumber, callerName, hasVideo)` | Register an outgoing call |
|
|
306
|
-
| `setCurrentCallActive(callId)` | Mark call as active/connected |
|
|
307
|
-
| `updateDisplay(callId, phoneNumber, callerName)` | Update caller display info |
|
|
308
|
-
| `endCallWithReason(callId, reason)` | End call with specified reason |
|
|
309
|
-
| `setMutedCall(callId, isMuted)` | Toggle call mute state |
|
|
310
|
-
| `setOnHoldCall(callId, isOnHold)` | Toggle call hold state |
|
|
311
|
-
| `addEventListener(eventName, callback)` | Subscribe to call events |
|
|
312
|
-
| `getInitialEvents()` | Get queued events from app launch |
|
|
313
|
-
| `clearInitialEvents()` | Clear queued initial events |
|
|
314
|
-
| `startBackgroundTask(taskProvider)` | Start Android background task |
|
|
315
|
-
| `stopBackgroundTask()` | Stop Android background task |
|
|
316
|
-
| `log(message, level)` | Log message to native console |
|
|
317
|
-
|
|
318
|
-
### Events
|
|
319
|
-
|
|
320
|
-
| Event | Parameters | Description |
|
|
321
|
-
| ------------------------------ | ------------------- | --------------------------------- |
|
|
322
|
-
| `answerCall` | `{ callId }` | User answered call from system UI |
|
|
323
|
-
| `endCall` | `{ callId, cause }` | Call ended |
|
|
324
|
-
| `didToggleHoldCallAction` | `{ callId, hold }` | Hold state changed |
|
|
325
|
-
| `didPerformSetMutedCallAction` | `{ callId, muted }` | Mute state changed |
|
|
326
|
-
| `didReceiveStartCallAction` | `{ callId }` | Outgoing call action received |
|
|
327
|
-
|
|
328
|
-
### Types
|
|
329
|
-
|
|
330
|
-
```typescript
|
|
331
|
-
type EndCallReason =
|
|
332
|
-
| 'local' // Call ended by the local user (e.g., hanging up)
|
|
333
|
-
| 'remote' // Call ended by the remote party, or outgoing not answered
|
|
334
|
-
| 'rejected' // Call was rejected/declined
|
|
335
|
-
| 'busy' // Remote party was busy
|
|
336
|
-
| 'answeredElsewhere' // Answered on another device
|
|
337
|
-
| 'missed' // No response to an incoming call
|
|
338
|
-
| 'error' // Call failed due to an error (e.g., network issue)
|
|
339
|
-
| 'canceled' // Call canceled before the remote party could answer
|
|
340
|
-
| 'restricted' // Call restricted (e.g., airplane mode)
|
|
341
|
-
| 'unknown'; // Unknown or unspecified disconnect reason
|
|
342
|
-
|
|
343
|
-
type CallingExpiOSOptions = {
|
|
344
|
-
appName: string;
|
|
345
|
-
supportsVideo?: boolean;
|
|
346
|
-
maximumCallsPerCallGroup?: number;
|
|
347
|
-
maximumCallGroups?: number;
|
|
348
|
-
handleType?: 'generic' | 'number' | 'phone' | 'email';
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
type CallingExpAndroidOptions = {
|
|
352
|
-
incomingChannel?: {
|
|
353
|
-
id: string;
|
|
354
|
-
name: string;
|
|
355
|
-
sound?: string;
|
|
356
|
-
vibration?: boolean;
|
|
357
|
-
};
|
|
358
|
-
outgoingChannel?: {
|
|
359
|
-
id: string;
|
|
360
|
-
name: string;
|
|
361
|
-
sound?: string;
|
|
362
|
-
vibration?: boolean;
|
|
363
|
-
};
|
|
364
|
-
};
|
|
365
|
-
|
|
366
|
-
type PermissionsResult = {
|
|
367
|
-
recordAudio: boolean;
|
|
368
|
-
postNotifications: boolean;
|
|
369
|
-
};
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
## Troubleshooting
|
|
373
|
-
|
|
374
|
-
### iOS
|
|
375
|
-
|
|
376
|
-
- **Incoming call not showing**: Ensure `voip` background mode is enabled and VoIP push certificate is configured
|
|
377
|
-
- **CallKit errors**: Check that `appName` is set in setup options
|
|
378
|
-
- **Audio issues**: The module automatically configures the audio session, but ensure no conflicts with other audio libraries
|
|
46
|
+
## Main APIs
|
|
379
47
|
|
|
380
|
-
|
|
48
|
+
- `setup(options)` - required before any call action.
|
|
49
|
+
- `displayIncomingCall(callId, phoneNumber, callerName, hasVideo)`.
|
|
50
|
+
- `startCall(callId, phoneNumber, callerName, hasVideo)`.
|
|
51
|
+
- `setCurrentCallActive(callId)`.
|
|
52
|
+
- `updateDisplay(callId, phoneNumber, callerName, incoming)`.
|
|
53
|
+
- `endCallWithReason(callId, reason)`.
|
|
54
|
+
- `setMutedCall(callId, isMuted)`.
|
|
55
|
+
- `setOnHoldCall(callId, isOnHold)`.
|
|
56
|
+
- `addEventListener(eventName, callback)`.
|
|
57
|
+
- `getInitialEvents()` and `getInitialVoipEvents()`.
|
|
58
|
+
- `registerBackgroundTask(taskProvider)` / `startBackgroundTask()` / `stopBackgroundTask()` (Android).
|
|
381
59
|
|
|
382
|
-
|
|
383
|
-
- **Call not answered on tap**: Ensure `handleCallingIntent` is called in both `onCreate` and `onNewIntent` in your MainActivity
|
|
60
|
+
## Event names
|
|
384
61
|
|
|
385
|
-
|
|
62
|
+
Call events:
|
|
386
63
|
|
|
387
|
-
|
|
64
|
+
- `answerCall`
|
|
65
|
+
- `endCall`
|
|
66
|
+
- `didDisplayIncomingCall`
|
|
67
|
+
- `didToggleHoldCallAction`
|
|
68
|
+
- `didPerformSetMutedCallAction`
|
|
69
|
+
- `didChangeAudioRoute`
|
|
70
|
+
- `didReceiveStartCallAction`
|
|
71
|
+
- `didActivateAudioSession`
|
|
72
|
+
- `didDeactivateAudioSession`
|
|
388
73
|
|
|
389
|
-
|
|
74
|
+
VoIP events:
|
|
390
75
|
|
|
391
|
-
|
|
76
|
+
- `voipNotificationsRegistered`
|
|
77
|
+
- `voipNotificationReceived`
|
|
392
78
|
|
|
393
|
-
|
|
79
|
+
## Notes
|
|
394
80
|
|
|
395
|
-
|
|
81
|
+
- Import from `@stream-io/react-native-callingx`.
|
|
82
|
+
- iOS-only helpers: `registerVoipToken`, `fulfillAnswerCallAction`, `fulfillEndCallAction`.
|
|
83
|
+
- Android helpers: `canPostNotifications`, `isOngoingCallsEnabled`.
|
|
@@ -2,9 +2,11 @@ package io.getstream.rn.callingx
|
|
|
2
2
|
|
|
3
3
|
import android.os.Handler
|
|
4
4
|
import android.os.Looper
|
|
5
|
-
import android.util.Log
|
|
6
5
|
import com.facebook.react.bridge.Promise
|
|
6
|
+
import io.getstream.rn.callingx.model.CallAction
|
|
7
|
+
import java.util.Collections
|
|
7
8
|
import java.util.concurrent.ConcurrentHashMap
|
|
9
|
+
import kotlin.collections.emptyList
|
|
8
10
|
|
|
9
11
|
object CallRegistrationStore {
|
|
10
12
|
|
|
@@ -13,10 +15,8 @@ object CallRegistrationStore {
|
|
|
13
15
|
|
|
14
16
|
private val trackedCallIds: MutableSet<String> = ConcurrentHashMap.newKeySet()
|
|
15
17
|
|
|
16
|
-
/** Pending
|
|
17
|
-
private val
|
|
18
|
-
private val pendingAnswerByCallId = ConcurrentHashMap<String, Boolean>()
|
|
19
|
-
private val pendingMuteByCallId = ConcurrentHashMap<String, Boolean>()
|
|
18
|
+
/** Pending actions per callId, queued until the call is registered in Telecom. */
|
|
19
|
+
private val pendingActionsByCallId = ConcurrentHashMap<String, MutableList<CallAction>>()
|
|
20
20
|
|
|
21
21
|
// Per-callId pending promises for displayIncomingCall awaiting CALL_REGISTERED_INCOMING_ACTION
|
|
22
22
|
private val pendingPromises = mutableMapOf<String, Promise>()
|
|
@@ -24,7 +24,10 @@ object CallRegistrationStore {
|
|
|
24
24
|
private val mainHandler = Handler(Looper.getMainLooper())
|
|
25
25
|
|
|
26
26
|
fun trackCallRegistration(callId: String, promise: Promise?) {
|
|
27
|
-
debugLog(
|
|
27
|
+
debugLog(
|
|
28
|
+
TAG,
|
|
29
|
+
"[store] trackCallRegistration: Tracking call registration for callId: $callId"
|
|
30
|
+
)
|
|
28
31
|
trackedCallIds.add(callId)
|
|
29
32
|
|
|
30
33
|
if (promise == null) return
|
|
@@ -38,10 +41,9 @@ object CallRegistrationStore {
|
|
|
38
41
|
|
|
39
42
|
val timeoutRunnable = Runnable {
|
|
40
43
|
synchronized(pendingPromises) {
|
|
41
|
-
pendingPromises
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
)
|
|
44
|
+
pendingPromises
|
|
45
|
+
.remove(callId)
|
|
46
|
+
?.reject("TIMEOUT", "Timed out waiting for call registration: $callId")
|
|
45
47
|
pendingTimeouts.remove(callId)
|
|
46
48
|
trackedCallIds.remove(callId)
|
|
47
49
|
}
|
|
@@ -60,24 +62,25 @@ object CallRegistrationStore {
|
|
|
60
62
|
|
|
61
63
|
fun onRegistrationFailed(callId: String) {
|
|
62
64
|
reportRegistrationFail(
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
callId,
|
|
66
|
+
"REGISTRATION_FAILED",
|
|
67
|
+
"Failed to register call with telecom: $callId",
|
|
68
|
+
null
|
|
67
69
|
)
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
fun reportRegistrationFail(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
73
|
+
callId: String,
|
|
74
|
+
code: String,
|
|
75
|
+
message: String?,
|
|
76
|
+
throwable: Throwable?
|
|
75
77
|
) {
|
|
76
78
|
trackedCallIds.remove(callId)
|
|
77
79
|
|
|
78
80
|
synchronized(pendingPromises) {
|
|
79
81
|
pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
|
|
80
82
|
val promise = pendingPromises.remove(callId)
|
|
83
|
+
pendingActionsByCallId.remove(callId)
|
|
81
84
|
if (promise != null) {
|
|
82
85
|
if (throwable != null) {
|
|
83
86
|
promise.reject(code, message, throwable)
|
|
@@ -111,50 +114,23 @@ object CallRegistrationStore {
|
|
|
111
114
|
}
|
|
112
115
|
|
|
113
116
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
117
|
+
* Queues an action for a call that is not yet registered.
|
|
118
|
+
* Pending actions are drained and executed once registration completes.
|
|
116
119
|
*/
|
|
117
|
-
fun
|
|
118
|
-
debugLog(TAG, "[store]
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
fun setPendingAnswer(callId: String, isAudioCall: Boolean) {
|
|
123
|
-
debugLog(TAG, "[store] setPendingAnswer: callId=$callId")
|
|
124
|
-
pendingAnswerByCallId[callId] = isAudioCall
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
fun setPendingMute(callId: String, isMute: Boolean) {
|
|
128
|
-
debugLog(TAG, "[store] setPendingMute: callId=$callId isMute=$isMute")
|
|
129
|
-
pendingMuteByCallId[callId] = isMute
|
|
120
|
+
fun addPendingAction(callId: String, action: CallAction) {
|
|
121
|
+
debugLog(TAG, "[store] addPendingAction: callId=$callId action=${action::class.simpleName}")
|
|
122
|
+
pendingActionsByCallId
|
|
123
|
+
.computeIfAbsent(callId) { Collections.synchronizedList(mutableListOf()) }
|
|
124
|
+
.add(action)
|
|
130
125
|
}
|
|
131
126
|
|
|
132
127
|
/**
|
|
133
|
-
* Returns and removes
|
|
134
|
-
* Used
|
|
128
|
+
* Returns and removes all queued actions for this call.
|
|
129
|
+
* Used once a call is registered so the service can replay pending actions.
|
|
135
130
|
*/
|
|
136
|
-
fun
|
|
137
|
-
val
|
|
138
|
-
|
|
139
|
-
debugLog(TAG, "[store] takePendingDisconnect: callId=$callId causeCode=$code")
|
|
140
|
-
}
|
|
141
|
-
return code
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
fun takePendingAnswer(callId: String): Boolean? {
|
|
145
|
-
val isAudioCall = pendingAnswerByCallId.remove(callId)
|
|
146
|
-
if (isAudioCall != null) {
|
|
147
|
-
debugLog(TAG, "[store] takePendingAnswer: callId=$callId isAudioCall=$isAudioCall")
|
|
148
|
-
}
|
|
149
|
-
return isAudioCall
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
fun takePendingMute(callId: String): Boolean? {
|
|
153
|
-
val isMute = pendingMuteByCallId.remove(callId)
|
|
154
|
-
if (isMute != null) {
|
|
155
|
-
debugLog(TAG, "[store] takePendingMute: callId=$callId isMute=$isMute")
|
|
156
|
-
}
|
|
157
|
-
return isMute
|
|
131
|
+
fun takePendingActions(callId: String): List<CallAction> {
|
|
132
|
+
val list = pendingActionsByCallId.remove(callId) ?: return emptyList()
|
|
133
|
+
synchronized(list) { return list.toList() }
|
|
158
134
|
}
|
|
159
135
|
|
|
160
136
|
fun clearAll() {
|
|
@@ -164,13 +140,6 @@ object CallRegistrationStore {
|
|
|
164
140
|
pendingPromises.clear()
|
|
165
141
|
}
|
|
166
142
|
trackedCallIds.clear()
|
|
167
|
-
|
|
168
|
-
pendingAnswerByCallId.clear()
|
|
169
|
-
pendingMuteByCallId.clear()
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
private fun debugLog(tag: String, message: String) {
|
|
173
|
-
Log.d(tag, message)
|
|
143
|
+
pendingActionsByCallId.clear()
|
|
174
144
|
}
|
|
175
145
|
}
|
|
176
|
-
|