expo-callkit-telecom 0.3.8 → 0.3.9
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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +7 -0
- package/README.md +2 -1
- package/android/src/main/java/expo/modules/callkittelecom/models/CallModels.kt +7 -5
- package/android/src/main/java/expo/modules/callkittelecom/services/ExpoCallKitTelecomMessagingService.kt +11 -8
- package/ios/Managers/VoIPPushManager+PKPushRegistryDelegate.swift +1 -1
- package/ios/Models/IncomingCallEvent.swift +7 -4
- package/lefthook.yml +16 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,13 @@ All notable changes to `expo-callkit-telecom` are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.9](https://github.com/mfairley/expo-callkit-telecom/compare/v0.3.8...v0.3.9) (2026-06-04)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* make incomingCall the canonical VoIP push key, accept incoming_call ([637fb83](https://github.com/mfairley/expo-callkit-telecom/commit/637fb835ba7675d17f2ed31e1842e9655c89d428)), closes [#15](https://github.com/mfairley/expo-callkit-telecom/issues/15)
|
|
14
|
+
|
|
8
15
|
## [0.3.8](https://github.com/mfairley/expo-callkit-telecom/compare/v0.3.7...v0.3.8) (2026-05-21)
|
|
9
16
|
|
|
10
17
|
|
package/README.md
CHANGED
|
@@ -97,6 +97,7 @@ With custom ringtone and dialtone:
|
|
|
97
97
|
"defaultDialtone": "dialtone.wav",
|
|
98
98
|
"incomingCallTimeout": 45,
|
|
99
99
|
"outgoingCallTimeout": 60,
|
|
100
|
+
"fulfillAnswerCallTimeout": 30,
|
|
100
101
|
"microphonePermission": "$(PRODUCT_NAME) needs the microphone to make calls."
|
|
101
102
|
}
|
|
102
103
|
]
|
|
@@ -158,7 +159,7 @@ Calls.addCallAnsweredListener(({ id }) => {
|
|
|
158
159
|
});
|
|
159
160
|
```
|
|
160
161
|
|
|
161
|
-
Both transports wrap the event under an `incomingCall` key, just at different layers — APNs in the push payload dictionary, FCM in the data block
|
|
162
|
+
Both transports wrap the event under an `incomingCall` key, just at different layers — APNs in the push payload dictionary, FCM in the data block. (The snake_case `incoming_call` is also accepted for backwards compatibility.)
|
|
162
163
|
|
|
163
164
|
### 🍎 iOS — APNs VoIP push
|
|
164
165
|
|
|
@@ -62,7 +62,7 @@ data class CallParticipant(
|
|
|
62
62
|
*
|
|
63
63
|
* Two construction paths:
|
|
64
64
|
* - [fromMap]: parses JS camelCase dictionaries (used by `reportIncomingCall`).
|
|
65
|
-
* - [fromPayload]: parses a push payload that wraps the event under the top-level `
|
|
65
|
+
* - [fromPayload]: parses a push payload that wraps the event under the top-level `incomingCall`
|
|
66
66
|
* key (used by VoIP push handling).
|
|
67
67
|
*/
|
|
68
68
|
data class IncomingCallEvent(
|
|
@@ -164,15 +164,17 @@ data class IncomingCallEvent(
|
|
|
164
164
|
/**
|
|
165
165
|
* Parses an `IncomingCallEvent` from a push payload.
|
|
166
166
|
*
|
|
167
|
-
* The payload MUST wrap the event under the top-level key `
|
|
168
|
-
*
|
|
169
|
-
*
|
|
167
|
+
* The payload MUST wrap the event under the top-level key `incomingCall`; the snake_case
|
|
168
|
+
* `incoming_call` is also accepted for backwards compatibility. There is no fallback to a
|
|
169
|
+
* flat top-level shape. Inner keys are camelCase, matching the TS contract.
|
|
170
170
|
*
|
|
171
171
|
* Returns `null` if the envelope is missing or required fields are absent.
|
|
172
172
|
*/
|
|
173
173
|
@Suppress("UNCHECKED_CAST")
|
|
174
174
|
fun fromPayload(payload: Map<String, Any?>): IncomingCallEvent? {
|
|
175
|
-
val event =
|
|
175
|
+
val event =
|
|
176
|
+
(payload["incomingCall"] ?: payload["incoming_call"]) as? Map<String, Any?>
|
|
177
|
+
?: return null
|
|
176
178
|
|
|
177
179
|
val eventId = event["eventId"] as? String ?: ""
|
|
178
180
|
val serverCallId = event["serverCallId"] as? String ?: ""
|
|
@@ -19,15 +19,18 @@ import org.json.JSONObject
|
|
|
19
19
|
* by the existing notification delegate via [super], and call payloads are routed directly to
|
|
20
20
|
* Telecom.
|
|
21
21
|
*
|
|
22
|
-
* Wire format (matches example/server/lib/fcm.ts): data["messageType"] = "
|
|
23
|
-
* data["
|
|
22
|
+
* Wire format (matches example/server/lib/fcm.ts): data["messageType"] = "incomingCall"
|
|
23
|
+
* data["incomingCall"] = JSON string of the IncomingCallEvent (camelCase). The snake_case envelope
|
|
24
|
+
* (messageType/key "incoming_call") is also accepted for backwards compatibility.
|
|
24
25
|
*/
|
|
25
26
|
class ExpoCallKitTelecomMessagingService : ExpoFirebaseMessagingService() {
|
|
26
27
|
companion object {
|
|
27
28
|
private const val TAG = "ExpoCallKitTelecom.FCM"
|
|
28
29
|
private const val KEY_MESSAGE_TYPE = "messageType"
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Canonical camelCase envelope plus the snake_case form accepted for backwards
|
|
31
|
+
// compatibility.
|
|
32
|
+
private val MESSAGE_TYPE_INCOMING_CALL = setOf("incomingCall", "incoming_call")
|
|
33
|
+
private val KEYS_INCOMING_CALL = listOf("incomingCall", "incoming_call")
|
|
31
34
|
private const val DEDUP_WINDOW_MS = 120_000L
|
|
32
35
|
|
|
33
36
|
private val dedupeLock = Any()
|
|
@@ -65,8 +68,8 @@ class ExpoCallKitTelecomMessagingService : ExpoFirebaseMessagingService() {
|
|
|
65
68
|
try {
|
|
66
69
|
CallManager.shared.initialize(applicationContext)
|
|
67
70
|
|
|
68
|
-
// Wrap under the envelope so we go through the same parser path as iOS.
|
|
69
|
-
val event = IncomingCallEvent.fromPayload(mapOf(
|
|
71
|
+
// Wrap under the canonical envelope so we go through the same parser path as iOS.
|
|
72
|
+
val event = IncomingCallEvent.fromPayload(mapOf("incomingCall" to eventMap))
|
|
70
73
|
if (event == null) {
|
|
71
74
|
Log.w(TAG, "Failed to validate incoming call event from FCM payload")
|
|
72
75
|
return
|
|
@@ -82,11 +85,11 @@ class ExpoCallKitTelecomMessagingService : ExpoFirebaseMessagingService() {
|
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
private fun parseIncomingCallEvent(data: Map<String, String>): Map<String, Any?>? {
|
|
85
|
-
if (data[KEY_MESSAGE_TYPE]
|
|
88
|
+
if (data[KEY_MESSAGE_TYPE] !in MESSAGE_TYPE_INCOMING_CALL) {
|
|
86
89
|
return null
|
|
87
90
|
}
|
|
88
91
|
|
|
89
|
-
val nestedPayload = data[
|
|
92
|
+
val nestedPayload = KEYS_INCOMING_CALL.firstNotNullOfOrNull { data[it] } ?: return null
|
|
90
93
|
return try {
|
|
91
94
|
jsonObjectToMap(JSONObject(nestedPayload))
|
|
92
95
|
} catch (error: Throwable) {
|
|
@@ -40,7 +40,7 @@ extension VoIPPushManager: PKPushRegistryDelegate {
|
|
|
40
40
|
///
|
|
41
41
|
/// This method MUST report a call to CallKit before returning, per Apple's requirements.
|
|
42
42
|
/// The payload is expected to wrap an `IncomingCallEvent` under the top-level
|
|
43
|
-
/// key `"
|
|
43
|
+
/// key `"incomingCall"`, matching `IncomingCallEventParser`.
|
|
44
44
|
///
|
|
45
45
|
/// - Parameters:
|
|
46
46
|
/// - registry: The push registry.
|
|
@@ -132,12 +132,15 @@ struct IncomingCallEventRecord: Record {
|
|
|
132
132
|
|
|
133
133
|
/// Parses an `IncomingCallEvent` from a VoIP push payload.
|
|
134
134
|
///
|
|
135
|
-
/// The payload must wrap the event under the top-level key `"
|
|
136
|
-
///
|
|
137
|
-
/// to
|
|
135
|
+
/// The payload must wrap the event under the top-level key `"incomingCall"`.
|
|
136
|
+
/// The snake_case `"incoming_call"` is also accepted for backwards compatibility.
|
|
137
|
+
/// There is no fallback to a flat top-level shape. Inner keys are camelCase to
|
|
138
|
+
/// match the TS contract.
|
|
138
139
|
enum IncomingCallEventParser {
|
|
139
140
|
static func parse(from payload: [AnyHashable: Any]) -> IncomingCallEvent? {
|
|
140
|
-
guard
|
|
141
|
+
guard
|
|
142
|
+
let event = (payload["incomingCall"] ?? payload["incoming_call"]) as? [AnyHashable: Any]
|
|
143
|
+
else {
|
|
141
144
|
return nil
|
|
142
145
|
}
|
|
143
146
|
|
package/lefthook.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Git hooks managed by lefthook (https://lefthook.dev).
|
|
2
|
+
# Run `lefthook install` once after cloning to wire up the hooks.
|
|
3
|
+
# Formatters live in the flox env — commit from a `flox activate` shell so
|
|
4
|
+
# `swift-format` and `ktfmt` are on PATH.
|
|
5
|
+
pre-commit:
|
|
6
|
+
parallel: true
|
|
7
|
+
commands:
|
|
8
|
+
swift-format:
|
|
9
|
+
glob: "ios/*.swift"
|
|
10
|
+
run: swift-format format --in-place {staged_files}
|
|
11
|
+
stage_fixed: true
|
|
12
|
+
ktfmt:
|
|
13
|
+
# Kotlin official style guide = 4-space indent (matches the rest of the module).
|
|
14
|
+
glob: "android/src/*.kt"
|
|
15
|
+
run: ktfmt --kotlinlang-style {staged_files}
|
|
16
|
+
stage_fixed: true
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-callkit-telecom",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.9",
|
|
4
4
|
"description": "CallKit + Jetpack Core-Telecom for Expo / React Native — native call UI, VoIP push, LiveKit-friendly audio. A modern react-native-callkeep alternative.",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|