@stream-io/react-native-callingx 0.1.0-beta.7 → 0.1.1-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 (45) hide show
  1. package/android/build.gradle +7 -1
  2. package/android/src/main/AndroidManifest.xml +31 -1
  3. package/android/src/main/java/io/getstream/rn/callingx/CallEventBroadcastReceiver.kt +17 -0
  4. package/android/src/main/java/io/getstream/rn/callingx/CallRegistrationStore.kt +176 -0
  5. package/android/src/main/java/io/getstream/rn/callingx/CallService.kt +302 -80
  6. package/android/src/main/java/io/getstream/rn/callingx/CallingxModuleImpl.kt +176 -191
  7. package/android/src/main/java/io/getstream/rn/callingx/StreamMessagingService.kt +48 -0
  8. package/android/src/main/java/io/getstream/rn/callingx/model/Call.kt +1 -0
  9. package/android/src/main/java/io/getstream/rn/callingx/notifications/CallNotificationManager.kt +196 -46
  10. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationIntentFactory.kt +14 -8
  11. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverActivity.kt +12 -1
  12. package/android/src/main/java/io/getstream/rn/callingx/notifications/NotificationReceiverService.kt +7 -0
  13. package/android/src/main/java/io/getstream/rn/callingx/repo/CallRepository.kt +38 -19
  14. package/android/src/main/java/io/getstream/rn/callingx/repo/LegacyCallRepository.kt +64 -55
  15. package/android/src/main/java/io/getstream/rn/callingx/repo/TelecomCallRepository.kt +241 -195
  16. package/android/src/main/java/io/getstream/rn/callingx/utils/CallEventBus.kt +61 -0
  17. package/android/src/main/java/io/getstream/rn/callingx/utils/SettingsStore.kt +51 -0
  18. package/android/src/newarch/java/io/getstream/rn/callingx/CallingxModule.kt +12 -3
  19. package/android/src/oldarch/java/io/getstream/rn/callingx/CallingxModule.kt +13 -3
  20. package/dist/module/CallingxModule.js +13 -10
  21. package/dist/module/CallingxModule.js.map +1 -1
  22. package/dist/module/spec/NativeCallingx.js.map +1 -1
  23. package/dist/module/utils/constants.js +24 -13
  24. package/dist/module/utils/constants.js.map +1 -1
  25. package/dist/typescript/src/CallingxModule.d.ts +3 -0
  26. package/dist/typescript/src/CallingxModule.d.ts.map +1 -1
  27. package/dist/typescript/src/spec/NativeCallingx.d.ts +7 -1
  28. package/dist/typescript/src/spec/NativeCallingx.d.ts.map +1 -1
  29. package/dist/typescript/src/types.d.ts +31 -0
  30. package/dist/typescript/src/types.d.ts.map +1 -1
  31. package/dist/typescript/src/utils/constants.d.ts +1 -1
  32. package/dist/typescript/src/utils/constants.d.ts.map +1 -1
  33. package/ios/AudioSessionManager.swift +2 -2
  34. package/ios/Callingx.mm +41 -17
  35. package/ios/CallingxImpl.swift +213 -83
  36. package/ios/Settings.swift +2 -2
  37. package/ios/UUIDStorage.swift +10 -10
  38. package/ios/VoipNotificationsManager.swift +8 -8
  39. package/package.json +4 -2
  40. package/src/CallingxModule.ts +14 -10
  41. package/src/spec/NativeCallingx.ts +10 -3
  42. package/src/types.ts +34 -0
  43. package/src/utils/constants.ts +23 -9
  44. /package/android/src/main/java/io/getstream/rn/callingx/{ResourceUtils.kt → utils/ResourceUtils.kt} +0 -0
  45. /package/android/src/main/java/io/getstream/rn/callingx/{Utils.kt → utils/Utils.kt} +0 -0
@@ -65,7 +65,7 @@ android {
65
65
  "generated/java",
66
66
  "generated/jni",
67
67
  "build/generated/source/codegen/java",
68
- "build/generated/source/codegen/jni"
68
+ "build/generated/source/codegen/jni",
69
69
  ]
70
70
  if (isNewArchitectureEnabled()) {
71
71
  java.srcDirs += ["src/newarch"]
@@ -87,4 +87,10 @@ dependencies {
87
87
  implementation "com.facebook.react:react-android"
88
88
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
89
89
  implementation "androidx.core:core-telecom:1.0.1"
90
+ // Compile-time only dependency so StreamMessagingService can reference RemoteMessage.
91
+ // The consuming app (via @react-native-firebase/messaging) must provide the actual runtime version.
92
+ compileOnly "com.google.firebase:firebase-messaging-ktx:24.1.2"
93
+ // Optional: use Firebase native code when the app has react-native-firebase installed (peer deps)
94
+ implementation project(':react-native-firebase_app')
95
+ implementation project(':react-native-firebase_messaging')
90
96
  }
@@ -1,4 +1,5 @@
1
- <manifest xmlns:android="http://schemas.android.com/apk/res/android">
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android"
2
+ xmlns:tools="http://schemas.android.com/tools">
2
3
 
3
4
  <uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
4
5
  <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
@@ -25,5 +26,34 @@
25
26
  android:foregroundServiceType="phoneCall"
26
27
  android:stopWithTask="true" />
27
28
 
29
+ <service
30
+ android:name="io.getstream.rn.callingx.StreamMessagingService"
31
+ android:exported="false">
32
+ <intent-filter>
33
+ <action android:name="com.google.firebase.MESSAGING_EVENT" />
34
+ </intent-filter>
35
+ </service>
36
+
37
+ <!-- Action values must match constants in CallingxModuleImpl.kt -->
38
+ <receiver
39
+ android:name="io.getstream.rn.callingx.CallEventBroadcastReceiver"
40
+ android:exported="false">
41
+ <intent-filter>
42
+ <action android:name="io.getstream.CALL_REGISTERED" />
43
+ <action android:name="io.getstream.CALL_REGISTERED_INCOMING" />
44
+ <action android:name="io.getstream.CALL_ANSWERED" />
45
+ <action android:name="io.getstream.CALL_INACTIVE" />
46
+ <action android:name="io.getstream.CALL_ACTIVE" />
47
+ <action android:name="io.getstream.CALL_MUTED" />
48
+ <action android:name="io.getstream.CALL_ENDPOINT_CHANGED" />
49
+ <action android:name="io.getstream.CALL_END" />
50
+ <action android:name="io.getstream.CALL_REGISTRATION_FAILED" />
51
+ </intent-filter>
52
+ </receiver>
53
+
54
+ <!-- Remove default FCM service so StreamMessagingService is the single handler -->
55
+ <service
56
+ android:name="io.invertase.firebase.messaging.ReactNativeFirebaseMessagingService"
57
+ tools:node="remove" />
28
58
  </application>
29
59
  </manifest>
@@ -0,0 +1,17 @@
1
+ package io.getstream.rn.callingx
2
+
3
+ import android.content.BroadcastReceiver
4
+ import android.content.Context
5
+ import android.content.Intent
6
+ import android.os.Bundle
7
+
8
+ class CallEventBroadcastReceiver : BroadcastReceiver() {
9
+
10
+ override fun onReceive(context: Context, intent: Intent) {
11
+ val action = intent.action ?: return
12
+ val extras = intent.extras ?: Bundle()
13
+
14
+ CallEventBus.publish(CallEvent(action = action, extras = extras))
15
+ }
16
+ }
17
+
@@ -0,0 +1,176 @@
1
+ package io.getstream.rn.callingx
2
+
3
+ import android.os.Handler
4
+ import android.os.Looper
5
+ import android.util.Log
6
+ import com.facebook.react.bridge.Promise
7
+ import java.util.concurrent.ConcurrentHashMap
8
+
9
+ object CallRegistrationStore {
10
+
11
+ private const val TAG = "[Callingx] CallRegistrationStore"
12
+ private const val DISPLAY_TIMEOUT_MS = 10_000L
13
+
14
+ private val trackedCallIds: MutableSet<String> = ConcurrentHashMap.newKeySet()
15
+
16
+ /** Pending disconnect cause code (android.telecom.DisconnectCause code) per callId. */
17
+ private val pendingDisconnectCauseByCallId = ConcurrentHashMap<String, Int>()
18
+ private val pendingAnswerByCallId = ConcurrentHashMap<String, Boolean>()
19
+ private val pendingMuteByCallId = ConcurrentHashMap<String, Boolean>()
20
+
21
+ // Per-callId pending promises for displayIncomingCall awaiting CALL_REGISTERED_INCOMING_ACTION
22
+ private val pendingPromises = mutableMapOf<String, Promise>()
23
+ private val pendingTimeouts = mutableMapOf<String, Runnable>()
24
+ private val mainHandler = Handler(Looper.getMainLooper())
25
+
26
+ fun trackCallRegistration(callId: String, promise: Promise?) {
27
+ debugLog(TAG, "[store] trackCallRegistration: Tracking call registration for callId: $callId")
28
+ trackedCallIds.add(callId)
29
+
30
+ if (promise == null) return
31
+
32
+ synchronized(pendingPromises) {
33
+ // Cancel any existing timeout for this callId to avoid a stale runnable
34
+ // rejecting the new promise after it overwrites the old one.
35
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
36
+
37
+ pendingPromises[callId] = promise
38
+
39
+ val timeoutRunnable = Runnable {
40
+ synchronized(pendingPromises) {
41
+ pendingPromises.remove(callId)?.reject(
42
+ "TIMEOUT",
43
+ "Timed out waiting for call registration: $callId"
44
+ )
45
+ pendingTimeouts.remove(callId)
46
+ trackedCallIds.remove(callId)
47
+ }
48
+ }
49
+ pendingTimeouts[callId] = timeoutRunnable
50
+ mainHandler.postDelayed(timeoutRunnable, DISPLAY_TIMEOUT_MS)
51
+ }
52
+ }
53
+
54
+ fun onRegistrationSuccess(callId: String) {
55
+ synchronized(pendingPromises) {
56
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
57
+ pendingPromises.remove(callId)?.resolve(true)
58
+ }
59
+ }
60
+
61
+ fun onRegistrationFailed(callId: String) {
62
+ reportRegistrationFail(
63
+ callId,
64
+ "REGISTRATION_FAILED",
65
+ "Failed to register call with telecom: $callId",
66
+ null
67
+ )
68
+ }
69
+
70
+ fun reportRegistrationFail(
71
+ callId: String,
72
+ code: String,
73
+ message: String?,
74
+ throwable: Throwable?
75
+ ) {
76
+ trackedCallIds.remove(callId)
77
+
78
+ synchronized(pendingPromises) {
79
+ pendingTimeouts.remove(callId)?.let { mainHandler.removeCallbacks(it) }
80
+ val promise = pendingPromises.remove(callId)
81
+ if (promise != null) {
82
+ if (throwable != null) {
83
+ promise.reject(code, message, throwable)
84
+ } else if (message != null) {
85
+ promise.reject(code, message)
86
+ } else {
87
+ promise.reject(code, "Unknown error")
88
+ }
89
+ }
90
+ }
91
+ }
92
+
93
+ fun addTrackedCall(callId: String) {
94
+ debugLog(TAG, "[store] addTrackedCall: Adding tracked call: $callId")
95
+ trackedCallIds.add(callId)
96
+ }
97
+
98
+ fun removeTrackedCall(callId: String) {
99
+ debugLog(TAG, "[store] removeTrackedCall: Removing tracked call: $callId")
100
+ trackedCallIds.remove(callId)
101
+ }
102
+
103
+ fun isCallTracked(callId: String): Boolean {
104
+ val isTracked = trackedCallIds.contains(callId)
105
+ debugLog(TAG, "[store] isCallTracked: Is call $callId tracked: $isTracked")
106
+ return isTracked
107
+ }
108
+
109
+ fun hasRegisteredCall(): Boolean {
110
+ return trackedCallIds.isNotEmpty()
111
+ }
112
+
113
+ /**
114
+ * Records that a disconnect was requested for this call before it was registered.
115
+ * When the call becomes registered, the service should take this and run the disconnect action.
116
+ */
117
+ fun setPendingDisconnect(callId: String, disconnectCauseCode: Int) {
118
+ debugLog(TAG, "[store] setPendingDisconnect: callId=$callId causeCode=$disconnectCauseCode")
119
+ pendingDisconnectCauseByCallId[callId] = disconnectCauseCode
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
130
+ }
131
+
132
+ /**
133
+ * Returns and removes the pending disconnect cause code for this call, if any.
134
+ * Used when the call has just become registered so the service can run the disconnect action.
135
+ */
136
+ fun takePendingDisconnect(callId: String): Int? {
137
+ val code = pendingDisconnectCauseByCallId.remove(callId)
138
+ if (code != null) {
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
158
+ }
159
+
160
+ fun clearAll() {
161
+ synchronized(pendingPromises) {
162
+ pendingTimeouts.values.forEach { mainHandler.removeCallbacks(it) }
163
+ pendingTimeouts.clear()
164
+ pendingPromises.clear()
165
+ }
166
+ trackedCallIds.clear()
167
+ pendingDisconnectCauseByCallId.clear()
168
+ pendingAnswerByCallId.clear()
169
+ pendingMuteByCallId.clear()
170
+ }
171
+
172
+ private fun debugLog(tag: String, message: String) {
173
+ Log.d(tag, message)
174
+ }
175
+ }
176
+