@stream-io/react-native-callingx 0.1.0-beta.4 → 0.1.0-beta.6

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 (42) hide show
  1. package/README.md +0 -1
  2. package/android/build.gradle +9 -0
  3. package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +69 -45
  4. package/android/src/main/java/io/getstream/rn/callingx/CallingxEventEmitterAdapter.kt +7 -0
  5. package/android/src/main/java/io/getstream/rn/callingx/{CallingxModule.kt → CallingxModuleImpl.kt} +194 -103
  6. package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +9 -9
  7. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +8 -8
  8. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +7 -7
  9. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +7 -7
  10. package/android/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +3 -0
  11. package/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt +148 -0
  12. package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxModule.kt +177 -0
  13. package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxPackage.kt +16 -0
  14. package/dist/module/CallingxModule.js +2 -2
  15. package/dist/module/CallingxModule.js.map +1 -1
  16. package/dist/module/EventManager.js +11 -4
  17. package/dist/module/EventManager.js.map +1 -1
  18. package/dist/module/spec/NativeCallingx.js +4 -2
  19. package/dist/module/spec/NativeCallingx.js.map +1 -1
  20. package/dist/module/utils/utils.js +3 -0
  21. package/dist/module/utils/utils.js.map +1 -1
  22. package/dist/typescript/src/CallingxModule.d.ts +1 -1
  23. package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
  24. package/dist/typescript/src/EventManager.d.ts.map +1 -1
  25. package/dist/typescript/src/spec/NativeCallingx.d.ts +3 -3
  26. package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
  27. package/dist/typescript/src/types.d.ts +2 -2
  28. package/dist/typescript/src/types.d.ts.map +1 -1
  29. package/dist/typescript/src/utils/utils.d.ts +1 -0
  30. package/dist/typescript/src/utils/utils.d.ts.map +1 -1
  31. package/ios/Callingx.mm +326 -22
  32. package/ios/CallingxCall.swift +105 -0
  33. package/ios/CallingxImpl.swift +136 -70
  34. package/ios/CallingxPublic.h +2 -5
  35. package/ios/UUIDStorage.swift +71 -26
  36. package/package.json +3 -3
  37. package/src/CallingxModule.ts +2 -2
  38. package/src/EventManager.ts +18 -5
  39. package/src/spec/NativeCallingx.ts +12 -3
  40. package/src/types.ts +2 -2
  41. package/src/utils/utils.ts +3 -0
  42. /package/android/src/{main → newarch}/java/io/getstream/rn/callingx/CallingxPackage.kt +0 -0
@@ -8,7 +8,9 @@ import android.content.IntentFilter
8
8
  import android.content.ServiceConnection
9
9
  import android.os.Build
10
10
  import android.os.Bundle
11
+ import android.os.Handler
11
12
  import android.os.IBinder
13
+ import android.os.Looper
12
14
  import android.telecom.DisconnectCause
13
15
  import android.util.Log
14
16
  import androidx.core.content.ContextCompat
@@ -21,14 +23,16 @@ import com.facebook.react.bridge.ReadableMap
21
23
  import com.facebook.react.bridge.WritableArray
22
24
  import com.facebook.react.bridge.WritableMap
23
25
  import com.facebook.react.bridge.WritableNativeArray
24
- import com.facebook.react.module.annotations.ReactModule
25
26
  import com.facebook.react.modules.core.DeviceEventManagerModule
26
27
  import io.getstream.rn.callingx.model.CallAction
27
28
  import io.getstream.rn.callingx.notifications.NotificationChannelsManager
28
29
  import io.getstream.rn.callingx.notifications.NotificationsConfig
30
+ import java.util.concurrent.ConcurrentHashMap
29
31
 
30
- @ReactModule(name = CallingxModule.NAME)
31
- class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec(reactContext) {
32
+ class CallingxModuleImpl(
33
+ private val reactApplicationContext: ReactApplicationContext,
34
+ private val eventEmitter: CallingxEventEmitterAdapter
35
+ ) {
32
36
 
33
37
  companion object {
34
38
  const val TAG = "[Callingx] CallingxModule"
@@ -50,6 +54,7 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
50
54
  const val CALL_MUTED_ACTION = "call_muted"
51
55
  const val CALL_ENDPOINT_CHANGED_ACTION = "call_endpoint_changed"
52
56
  const val CALL_END_ACTION = "call_end"
57
+ const val CALL_REGISTRATION_FAILED_ACTION = "call_registration_failed"
53
58
  // Background task name
54
59
  const val HEADLESS_TASK_NAME = "HandleCallBackgroundState"
55
60
  const val SERVICE_READY_ACTION = "service_ready"
@@ -69,6 +74,16 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
69
74
  private var canSendEvents = false
70
75
  private var isHeadlessTaskRegistered = false
71
76
 
77
+ // Synchronous call tracking set, updated before async service start to mirror iOS semantics.
78
+ // This ensures isCallTracked() returns true immediately after displayIncomingCall/startCall.
79
+ private val trackedCallIds = ConcurrentHashMap.newKeySet<String>()
80
+
81
+ // Per-callId pending promises for displayIncomingCall awaiting CALL_REGISTERED_INCOMING_ACTION
82
+ private val pendingDisplayPromises = mutableMapOf<String, Promise>()
83
+ private val pendingTimeouts = mutableMapOf<String, Runnable>()
84
+ private val mainHandler = Handler(Looper.getMainLooper())
85
+ private val displayTimeoutMs = 10_000L // 10 second safety timeout
86
+
72
87
  private val notificationChannelsManager = NotificationChannelsManager(reactApplicationContext)
73
88
  private val callEventBroadcastReceiver = CallEventBroadcastReceiver()
74
89
  private val appStateListener =
@@ -86,21 +101,18 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
86
101
 
87
102
  init {
88
103
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
89
- reactContext.registerReceiver(
104
+ reactApplicationContext.registerReceiver(
90
105
  callEventBroadcastReceiver,
91
106
  getReceiverFilter(),
92
107
  Context.RECEIVER_NOT_EXPORTED
93
108
  )
94
109
  } else {
95
110
  @Suppress("UnspecifiedRegisterReceiverFlag")
96
- reactContext.registerReceiver(callEventBroadcastReceiver, getReceiverFilter())
111
+ reactApplicationContext.registerReceiver(callEventBroadcastReceiver, getReceiverFilter())
97
112
  }
98
113
  }
99
114
 
100
- override fun getName(): String = NAME
101
-
102
- override fun initialize() {
103
- super.initialize()
115
+ fun initialize() {
104
116
  reactApplicationContext.addLifecycleEventListener(appStateListener)
105
117
 
106
118
  tryToBindIfNeeded()
@@ -108,10 +120,17 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
108
120
  debugLog(TAG, "[module] initialize: Initializing module")
109
121
  }
110
122
 
111
- override fun invalidate() {
112
- super.invalidate()
123
+ fun invalidate() {
113
124
  debugLog(TAG, "[module] invalidate: Invalidating module")
114
125
 
126
+ // Clean up pending display promises to prevent leaks
127
+ synchronized(pendingDisplayPromises) {
128
+ pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
129
+ pendingTimeouts.clear()
130
+ pendingDisplayPromises.clear()
131
+ }
132
+
133
+ trackedCallIds.clear()
115
134
  unbindServiceSafely()
116
135
 
117
136
  reactApplicationContext.removeLifecycleEventListener(appStateListener)
@@ -119,11 +138,7 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
119
138
  isModuleInitialized = false
120
139
  }
121
140
 
122
- override fun setupiOS(options: ReadableMap) {
123
- // leave empty
124
- }
125
-
126
- override fun setupAndroid(options: ReadableMap) {
141
+ fun setupAndroid(options: ReadableMap) {
127
142
  debugLog(TAG, "[module] setupAndroid: Setting up Android: $options")
128
143
  val notificationsConfig =
129
144
  NotificationsConfig.saveNotificationsConfig(reactApplicationContext, options)
@@ -133,24 +148,11 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
133
148
  isModuleInitialized = true
134
149
  }
135
150
 
136
- override fun canPostNotifications(): Boolean {
151
+ fun canPostNotifications(): Boolean {
137
152
  return notificationChannelsManager.getNotificationStatus().canPost
138
153
  }
139
154
 
140
- override fun setShouldRejectCallWhenBusy(shouldReject: Boolean) {
141
- // leave empty
142
- }
143
-
144
- override fun getInitialVoipEvents(): WritableArray {
145
- // leave empty
146
- return Arguments.createArray()
147
- }
148
-
149
- override fun registerVoipToken() {
150
- // leave empty
151
- }
152
-
153
- override fun getInitialEvents(): WritableArray {
155
+ fun getInitialEvents(): WritableArray {
154
156
  // NOTE: writabel native array can be consumed only once, think of getting rid from clear
155
157
  // event and clear eat immidiate after getting initial events
156
158
  val events = delayedEvents
@@ -160,12 +162,12 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
160
162
  return events
161
163
  }
162
164
 
163
- override fun setCurrentCallActive(callId: String, promise: Promise) {
165
+ fun setCurrentCallActive(callId: String, promise: Promise) {
164
166
  debugLog(TAG, "[module] activateCall: Activating call: $callId")
165
167
  executeServiceAction(callId, CallAction.Activate, promise)
166
168
  }
167
169
 
168
- override fun displayIncomingCall(
170
+ fun displayIncomingCall(
169
171
  callId: String,
170
172
  phoneNumber: String,
171
173
  callerName: String,
@@ -182,19 +184,52 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
182
184
  return
183
185
  }
184
186
 
185
- startCallService(
186
- CallService.ACTION_INCOMING_CALL,
187
- callId,
188
- callerName,
189
- phoneNumber,
190
- hasVideo,
191
- displayOptions
192
- )
187
+ trackedCallIds.add(callId)
188
+
189
+ // Store the promise keyed by callId; it will be resolved when CALL_REGISTERED_INCOMING_ACTION
190
+ // broadcast is received, or rejected on timeout / registration failure.
191
+ synchronized(pendingDisplayPromises) {
192
+ // Cancel any existing timeout for this callId to avoid a stale
193
+ // runnable rejecting the new promise after it overwrites the old one.
194
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
195
+
196
+ pendingDisplayPromises[callId] = promise
197
+
198
+ // Per-call timeout runnable
199
+ val timeoutRunnable = Runnable {
200
+ synchronized(pendingDisplayPromises) {
201
+ pendingDisplayPromises.remove(callId)?.reject(
202
+ "TIMEOUT",
203
+ "Timed out waiting for call registration: $callId"
204
+ )
205
+ pendingTimeouts.remove(callId)
206
+ }
207
+ }
208
+ pendingTimeouts[callId] = timeoutRunnable
209
+ mainHandler.postDelayed(timeoutRunnable, displayTimeoutMs)
210
+ }
193
211
 
194
- promise.resolve(true)
212
+ try {
213
+ startCallService(
214
+ CallService.ACTION_INCOMING_CALL,
215
+ callId,
216
+ callerName,
217
+ phoneNumber,
218
+ hasVideo,
219
+ displayOptions
220
+ )
221
+ } catch (e: Exception) {
222
+ Log.e(TAG, "[module] displayIncomingCall: Failed to start foreground service: ${e.message}", e)
223
+ trackedCallIds.remove(callId)
224
+ synchronized(pendingDisplayPromises) {
225
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
226
+ pendingDisplayPromises.remove(callId)
227
+ }
228
+ promise.reject("START_FOREGROUND_SERVICE_ERROR", e.message, e)
229
+ }
195
230
  }
196
231
 
197
- override fun answerIncomingCall(callId: String, promise: Promise) {
232
+ fun answerIncomingCall(callId: String, promise: Promise) {
198
233
  debugLog(TAG, "[module] answerIncomingCall: Answering call: $callId")
199
234
  // TODO: get the call type from the call attributes
200
235
  val isAudioCall = true // TODO: get the call type from the call attributes
@@ -204,7 +239,7 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
204
239
  executeServiceAction(callId, CallAction.Answer(isAudioCall), promise)
205
240
  }
206
241
 
207
- override fun startCall(
242
+ fun startCall(
208
243
  callId: String,
209
244
  phoneNumber: String,
210
245
  callerName: String,
@@ -221,19 +256,26 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
221
256
  return
222
257
  }
223
258
 
224
- startCallService(
225
- CallService.ACTION_OUTGOING_CALL,
226
- callId,
227
- callerName,
228
- phoneNumber,
229
- hasVideo,
230
- displayOptions
231
- )
259
+ trackedCallIds.add(callId)
232
260
 
233
- promise.resolve(true)
261
+ try {
262
+ startCallService(
263
+ CallService.ACTION_OUTGOING_CALL,
264
+ callId,
265
+ callerName,
266
+ phoneNumber,
267
+ hasVideo,
268
+ displayOptions
269
+ )
270
+ promise.resolve(true)
271
+ } catch (e: Exception) {
272
+ Log.e(TAG, "[module] startCall: Failed to start foreground service: ${e.message}", e)
273
+ trackedCallIds.remove(callId)
274
+ promise.reject("START_FOREGROUND_SERVICE_ERROR", e.message, e)
275
+ }
234
276
  }
235
277
 
236
- override fun updateDisplay(
278
+ fun updateDisplay(
237
279
  callId: String,
238
280
  phoneNumber: String,
239
281
  callerName: String,
@@ -247,84 +289,101 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
247
289
  }
248
290
 
249
291
  // for now only display options will be updated, rest of the parameters will be ignored
250
- startCallService(
251
- CallService.ACTION_UPDATE_CALL,
252
- callId,
253
- callerName,
254
- phoneNumber,
255
- true,
256
- displayOptions,
257
- )
258
- promise.resolve(true)
292
+ try {
293
+ startCallService(
294
+ CallService.ACTION_UPDATE_CALL,
295
+ callId,
296
+ callerName,
297
+ phoneNumber,
298
+ true,
299
+ displayOptions,
300
+ )
301
+ promise.resolve(true)
302
+ } catch (e: Exception) {
303
+ Log.e(TAG, "[module] updateDisplay: Failed to start foreground service: ${e.message}", e)
304
+ promise.reject("START_FOREGROUND_SERVICE_ERROR", e.message, e)
305
+ }
259
306
  }
260
307
 
261
- override fun endCallWithReason(callId: String, reason: Double, promise: Promise) {
308
+ fun endCallWithReason(callId: String, reason: Double, promise: Promise) {
262
309
  debugLog(TAG, "[module] endCallWithReason: Ending call: $callId, $reason")
310
+ trackedCallIds.remove(callId)
263
311
  val action = CallAction.Disconnect(DisconnectCause(reason.toInt()))
264
312
  executeServiceAction(callId, action, promise)
265
313
  }
266
314
 
267
- override fun endCall(callId: String, promise: Promise) {
315
+ fun endCall(callId: String, promise: Promise) {
268
316
  debugLog(TAG, "[module] endCall: Ending call: $callId")
317
+ trackedCallIds.remove(callId)
269
318
  val action = CallAction.Disconnect(DisconnectCause(DisconnectCause.LOCAL))
270
319
  executeServiceAction(callId, action, promise)
271
320
  }
272
321
 
273
- override fun isCallRegistered(callId: String): Boolean {
274
- val isCallRegistered = callService?.isCallRegistered(callId) ?: false
275
- debugLog(TAG, "[module] isCallRegistered: Is call registered: $isCallRegistered")
276
- return isCallRegistered
322
+ fun isCallTracked(callId: String): Boolean {
323
+ val isTracked = trackedCallIds.contains(callId)
324
+ debugLog(TAG, "[module] isCallTracked: Is call tracked: $isTracked")
325
+ return isTracked
277
326
  }
278
327
 
279
- override fun hasRegisteredCall(): Boolean {
328
+ fun hasRegisteredCall(): Boolean {
280
329
  val hasRegisteredCall = callService?.hasRegisteredCall() ?: false
281
330
  debugLog(TAG, "[module] hasRegisteredCall: Has registered call: $hasRegisteredCall")
282
331
  return hasRegisteredCall
283
332
  }
284
333
 
285
- override fun setMutedCall(callId: String, isMuted: Boolean, promise: Promise) {
334
+ fun setMutedCall(callId: String, isMuted: Boolean, promise: Promise) {
286
335
  debugLog(TAG, "[module] setMutedCall: Setting muted call: $callId, $isMuted")
287
336
  val action = CallAction.ToggleMute(isMuted)
288
337
  executeServiceAction(callId, action, promise)
289
338
  }
290
339
 
291
- override fun setOnHoldCall(callId: String, isOnHold: Boolean, promise: Promise) {
340
+ fun setOnHoldCall(callId: String, isOnHold: Boolean, promise: Promise) {
292
341
  debugLog(TAG, "[module] setOnHoldCall: Setting on hold call: $callId, $isOnHold")
293
342
  val action = if (isOnHold) CallAction.Hold else CallAction.Activate
294
343
  executeServiceAction(callId, action, promise)
295
344
  }
296
345
 
297
- override fun startBackgroundTask(taskName: String, timeout: Double, promise: Promise) {
298
- Intent(reactApplicationContext, CallService::class.java)
299
- .apply {
300
- this.action = CallService.ACTION_START_BACKGROUND_TASK
301
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
302
- putExtra(CallService.EXTRA_TASK_DATA, Bundle())
303
- putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
304
- }
305
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
346
+ fun startBackgroundTask(taskName: String, timeout: Double, promise: Promise) {
347
+ try {
348
+ Intent(reactApplicationContext, CallService::class.java)
349
+ .apply {
350
+ this.action = CallService.ACTION_START_BACKGROUND_TASK
351
+ putExtra(CallService.EXTRA_TASK_NAME, taskName)
352
+ putExtra(CallService.EXTRA_TASK_DATA, Bundle())
353
+ putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
354
+ }
355
+ .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
306
356
 
307
- promise.resolve(true)
357
+ promise.resolve(true)
358
+ } catch (e: Exception) {
359
+ Log.e(TAG, "[module] startBackgroundTask: Failed to start foreground service: ${e.message}", e)
360
+ promise.reject("START_FOREGROUND_SERVICE_ERROR", e.message, e)
361
+ }
308
362
  }
309
363
 
310
- override fun stopBackgroundTask(taskName: String, promise: Promise) {
311
- Intent(reactApplicationContext, CallService::class.java)
312
- .apply {
313
- this.action = CallService.ACTION_STOP_BACKGROUND_TASK
314
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
315
- }
316
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
364
+ fun stopBackgroundTask(taskName: String, promise: Promise) {
365
+ try {
366
+ Intent(reactApplicationContext, CallService::class.java)
367
+ .apply {
368
+ this.action = CallService.ACTION_STOP_BACKGROUND_TASK
369
+ putExtra(CallService.EXTRA_TASK_NAME, taskName)
370
+ }
371
+ .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
317
372
 
318
- isHeadlessTaskRegistered = false
319
- promise.resolve(true)
373
+ isHeadlessTaskRegistered = false
374
+ promise.resolve(true)
375
+ } catch (e: Exception) {
376
+ Log.e(TAG, "[module] stopBackgroundTask: Failed to start foreground service: ${e.message}", e)
377
+ promise.reject("START_FOREGROUND_SERVICE_ERROR", e.message, e)
378
+ }
320
379
  }
321
380
 
322
- override fun registerBackgroundTaskAvailable() {
381
+ fun registerBackgroundTaskAvailable() {
323
382
  debugLog(TAG, "[module] registerBackgroundTaskAvailable: Headless task registered")
324
383
  isHeadlessTaskRegistered = true
325
384
  }
326
385
 
327
- override fun isServiceStarted(promise: Promise) {
386
+ fun isServiceStarted(promise: Promise) {
328
387
  val isStarted =
329
388
  bindingState == BindingState.BOUND ||
330
389
  bindingState == BindingState.BINDING ||
@@ -333,7 +392,7 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
333
392
  promise.resolve(isStarted)
334
393
  }
335
394
 
336
- override fun log(message: String, level: String) {
395
+ fun log(message: String, level: String) {
337
396
  when (level) {
338
397
  "debug" -> debugLog(TAG, "[module] log: $message")
339
398
  "info" -> Log.i(TAG, "[module] log: $message")
@@ -371,14 +430,18 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
371
430
  return
372
431
  }
373
432
 
374
- Intent(reactApplicationContext, CallService::class.java)
375
- .apply {
376
- this.action = CallService.ACTION_START_BACKGROUND_TASK
377
- putExtra(CallService.EXTRA_TASK_NAME, taskName)
378
- putExtra(CallService.EXTRA_TASK_DATA, Bundle())
379
- putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
380
- }
381
- .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
433
+ try {
434
+ Intent(reactApplicationContext, CallService::class.java)
435
+ .apply {
436
+ this.action = CallService.ACTION_START_BACKGROUND_TASK
437
+ putExtra(CallService.EXTRA_TASK_NAME, taskName)
438
+ putExtra(CallService.EXTRA_TASK_DATA, Bundle())
439
+ putExtra(CallService.EXTRA_TASK_TIMEOUT, timeout.toLong())
440
+ }
441
+ .also { ContextCompat.startForegroundService(reactApplicationContext, it) }
442
+ } catch (e: Exception) {
443
+ Log.e(TAG, "[module] startBackgroundTaskAutomatically: Failed to start foreground service: ${e.message}", e)
444
+ }
382
445
  }
383
446
 
384
447
  private fun executeServiceAction(callId: String, action: CallAction, promise: Promise) {
@@ -432,7 +495,7 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
432
495
  putString("eventName", eventName)
433
496
  putMap("params", paramsMap)
434
497
  }
435
- emitOnNewEvent(value)
498
+ eventEmitter.emitNewEvent(value)
436
499
  } else {
437
500
  debugLog(TAG, "[module] sendJSEvent: Queueing event: $eventName, $params")
438
501
  Arguments.createMap()
@@ -454,6 +517,7 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
454
517
  addAction(CALL_MUTED_ACTION)
455
518
  addAction(CALL_ENDPOINT_CHANGED_ACTION)
456
519
  addAction(CALL_END_ACTION)
520
+ addAction(CALL_REGISTRATION_FAILED_ACTION)
457
521
  addAction(SERVICE_READY_ACTION)
458
522
  }
459
523
 
@@ -563,8 +627,30 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
563
627
  sendJSEvent("didReceiveStartCallAction", params)
564
628
  }
565
629
  CALL_REGISTERED_INCOMING_ACTION -> {
630
+ // Resolve the pending displayIncomingCall promise for this callId
631
+ if (callId != null) {
632
+ synchronized(pendingDisplayPromises) {
633
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
634
+ pendingDisplayPromises.remove(callId)?.resolve(true)
635
+ }
636
+ }
566
637
  sendJSEvent("didDisplayIncomingCall", params)
567
638
  }
639
+ CALL_REGISTRATION_FAILED_ACTION -> {
640
+ if (callId != null) {
641
+ trackedCallIds.remove(callId)
642
+ }
643
+ // Reject the pending displayIncomingCall promise for this callId
644
+ if (callId != null) {
645
+ synchronized(pendingDisplayPromises) {
646
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
647
+ pendingDisplayPromises.remove(callId)?.reject(
648
+ "REGISTRATION_FAILED",
649
+ "Failed to register call with telecom: $callId"
650
+ )
651
+ }
652
+ }
653
+ }
568
654
  CALL_ANSWERED_ACTION -> {
569
655
  if (intent.hasExtra(EXTRA_SOURCE)) {
570
656
  params.putString("source", intent.getStringExtra(EXTRA_SOURCE))
@@ -577,6 +663,11 @@ class CallingxModule(reactContext: ReactApplicationContext) : NativeCallingxSpec
577
663
  params.putString("source", source)
578
664
  }
579
665
  if (source == "app") {
666
+ if (callId != null) {
667
+ //we stop tracking the call only when it was handled by the app
668
+ //in case source is "sys" we still need to know that call is tracked, otherwise we'll unable to end call from js side
669
+ trackedCallIds.remove(callId)
670
+ }
580
671
  // means the call was disconnected, we're ready to unbind the service
581
672
  unbindServiceSafely()
582
673
  }
@@ -13,7 +13,7 @@ import androidx.core.app.NotificationManagerCompat
13
13
  import androidx.core.app.Person
14
14
  import androidx.core.graphics.drawable.IconCompat
15
15
  import io.getstream.rn.callingx.CallService
16
- import io.getstream.rn.callingx.CallingxModule
16
+ import io.getstream.rn.callingx.CallingxModuleImpl
17
17
  import io.getstream.rn.callingx.R
18
18
  import io.getstream.rn.callingx.ResourceUtils
19
19
  import io.getstream.rn.callingx.debugLog
@@ -78,7 +78,7 @@ class CallNotificationManager(
78
78
  val contentIntent =
79
79
  NotificationIntentFactory.getLaunchActivityIntent(
80
80
  context,
81
- CallingxModule.CALL_ANSWERED_ACTION,
81
+ CallingxModuleImpl.CALL_ANSWERED_ACTION,
82
82
  call.id
83
83
  )
84
84
  val callStyle = createCallStyle(call)
@@ -190,21 +190,21 @@ class CallNotificationManager(
190
190
  caller,
191
191
  NotificationIntentFactory.getPendingBroadcastIntent(
192
192
  context,
193
- CallingxModule.CALL_END_ACTION,
193
+ CallingxModuleImpl.CALL_END_ACTION,
194
194
  call.id
195
195
  ) {
196
196
  putExtra(
197
- CallingxModule.EXTRA_DISCONNECT_CAUSE,
197
+ CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE,
198
198
  getDisconnectCauseString(DisconnectCause(DisconnectCause.REJECTED))
199
199
  )
200
200
  putExtra(
201
- CallingxModule.EXTRA_SOURCE,
201
+ CallingxModuleImpl.EXTRA_SOURCE,
202
202
  CallRepository.EventSource.SYS.name.lowercase()
203
203
  )
204
204
  },
205
205
  NotificationIntentFactory.getPendingNotificationIntent(
206
206
  context,
207
- CallingxModule.CALL_ANSWERED_ACTION,
207
+ CallingxModuleImpl.CALL_ANSWERED_ACTION,
208
208
  call.id,
209
209
  CallRepository.EventSource.SYS.name.lowercase()
210
210
  )
@@ -215,15 +215,15 @@ class CallNotificationManager(
215
215
  caller,
216
216
  NotificationIntentFactory.getPendingBroadcastIntent(
217
217
  context,
218
- CallingxModule.CALL_END_ACTION,
218
+ CallingxModuleImpl.CALL_END_ACTION,
219
219
  call.id
220
220
  ) {
221
221
  putExtra(
222
- CallingxModule.EXTRA_DISCONNECT_CAUSE,
222
+ CallingxModuleImpl.EXTRA_DISCONNECT_CAUSE,
223
223
  getDisconnectCauseString(DisconnectCause(DisconnectCause.LOCAL))
224
224
  )
225
225
  putExtra(
226
- CallingxModule.EXTRA_SOURCE,
226
+ CallingxModuleImpl.EXTRA_SOURCE,
227
227
  CallRepository.EventSource.SYS.name.lowercase()
228
228
  )
229
229
  },
@@ -4,7 +4,7 @@ import android.app.PendingIntent
4
4
  import android.content.Context
5
5
  import android.content.Intent
6
6
  import android.os.Build
7
- import io.getstream.rn.callingx.CallingxModule
7
+ import io.getstream.rn.callingx.CallingxModuleImpl
8
8
 
9
9
  object NotificationIntentFactory {
10
10
  // Stable request codes for PendingIntents
@@ -29,8 +29,8 @@ object NotificationIntentFactory {
29
29
  val intent =
30
30
  Intent(context, NotificationReceiverService::class.java).apply {
31
31
  this.action = action
32
- putExtra(CallingxModule.EXTRA_CALL_ID, callId)
33
- putExtra(CallingxModule.EXTRA_SOURCE, source)
32
+ putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
33
+ putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
34
34
  }
35
35
 
36
36
  return PendingIntent.getService(
@@ -45,8 +45,8 @@ object NotificationIntentFactory {
45
45
  val receiverIntent =
46
46
  Intent(context, NotificationReceiverActivity::class.java).apply {
47
47
  this.action = action
48
- putExtra(CallingxModule.EXTRA_CALL_ID, callId)
49
- putExtra(CallingxModule.EXTRA_SOURCE, source)
48
+ putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
49
+ putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
50
50
  }
51
51
 
52
52
  val launchActivity = context.packageManager.getLaunchIntentForPackage(context.packageName)
@@ -68,8 +68,8 @@ object NotificationIntentFactory {
68
68
  val callIntent =
69
69
  Intent(launchIntent).apply {
70
70
  this.action = action
71
- putExtra(CallingxModule.EXTRA_CALL_ID, callId)
72
- source?.let { putExtra(CallingxModule.EXTRA_SOURCE, it) }
71
+ putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
72
+ source?.let { putExtra(CallingxModuleImpl.EXTRA_SOURCE, it) }
73
73
  addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
74
74
  }
75
75
 
@@ -90,7 +90,7 @@ object NotificationIntentFactory {
90
90
  val intent =
91
91
  Intent(action).apply {
92
92
  setPackage(context.packageName)
93
- putExtra(CallingxModule.EXTRA_CALL_ID, callId)
93
+ putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
94
94
  addExtras(this)
95
95
  }
96
96
 
@@ -3,7 +3,7 @@ package io.getstream.rn.callingx.notifications
3
3
  import android.app.Activity
4
4
  import android.content.Intent
5
5
  import android.os.Bundle
6
- import io.getstream.rn.callingx.CallingxModule
6
+ import io.getstream.rn.callingx.CallingxModuleImpl
7
7
 
8
8
  // For Android 12+
9
9
  class NotificationReceiverActivity : Activity() {
@@ -27,14 +27,14 @@ class NotificationReceiverActivity : Activity() {
27
27
  }
28
28
 
29
29
  //we need it only for answered call event, as for cold start case we need to send broadcast event and to launch the app
30
- if (intent.action == CallingxModule.CALL_ANSWERED_ACTION) {
31
- val callId = intent.getStringExtra(CallingxModule.EXTRA_CALL_ID)
32
- val source = intent.getStringExtra(CallingxModule.EXTRA_SOURCE)
33
- Intent(CallingxModule.CALL_ANSWERED_ACTION)
30
+ if (intent.action == CallingxModuleImpl.CALL_ANSWERED_ACTION) {
31
+ val callId = intent.getStringExtra(CallingxModuleImpl.EXTRA_CALL_ID)
32
+ val source = intent.getStringExtra(CallingxModuleImpl.EXTRA_SOURCE)
33
+ Intent(CallingxModuleImpl.CALL_ANSWERED_ACTION)
34
34
  .apply {
35
35
  setPackage(packageName)
36
- putExtra(CallingxModule.EXTRA_CALL_ID, callId)
37
- putExtra(CallingxModule.EXTRA_SOURCE, source)
36
+ putExtra(CallingxModuleImpl.EXTRA_CALL_ID, callId)
37
+ putExtra(CallingxModuleImpl.EXTRA_SOURCE, source)
38
38
  }
39
39
  .also { sendBroadcast(it) }
40
40
  }