expo-callkit-telecom 0.3.7 → 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.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "0.3.7"
2
+ ".": "0.3.9"
3
3
  }
package/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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
+
15
+ ## [0.3.8](https://github.com/mfairley/expo-callkit-telecom/compare/v0.3.7...v0.3.8) (2026-05-21)
16
+
17
+
18
+ ### Bug Fixes
19
+
20
+ * **docs:** further reduce image sizes ([23b21c9](https://github.com/mfairley/expo-callkit-telecom/commit/23b21c9b5d2be0458c19d80952f9daac6f1a5f2d))
21
+ * **docs:** reduce image sizes and fix accessibility issues ([4c7db67](https://github.com/mfairley/expo-callkit-telecom/commit/4c7db67b29120424d00bf21a4be4437d8178e804))
22
+ * **docs:** update readme image references ([eb67ba1](https://github.com/mfairley/expo-callkit-telecom/commit/eb67ba1aabad4451d11898784abe5f5bcfbe7046))
23
+
8
24
  ## [0.3.7](https://github.com/mfairley/expo-callkit-telecom/compare/v0.3.6...v0.3.7) (2026-05-19)
9
25
 
10
26
 
package/README.md CHANGED
@@ -27,21 +27,21 @@ The module is opinionated about *system integration* and unopinionated about *me
27
27
  <td align="center"><strong>iOS</strong></td>
28
28
  <td align="center">
29
29
  <a href="https://github.com/mfairley/expo-callkit-telecom/raw/main/docs/public/outgoing-call-ios.mp4" title="Click to watch — outgoing call on iOS">
30
- <img src="docs/public/outgoing-call-ios-poster.jpg" alt="Outgoing call on iOS (click to watch)" width="200">
30
+ <img src="docs/public/outgoing-call-ios-poster.webp" alt="Outgoing call on iOS (click to watch)" width="200">
31
31
  </a>
32
32
  </td>
33
- <td align="center"><img src="docs/public/incoming-call-banner-ios.png" alt="Incoming call banner on iOS" width="200"></td>
34
- <td align="center"><img src="docs/public/incoming-call-fullscreen-ios.png" alt="Incoming call full screen on iOS" width="200"></td>
33
+ <td align="center"><img src="docs/public/incoming-call-banner-ios.webp" alt="Incoming call banner on iOS" width="200"></td>
34
+ <td align="center"><img src="docs/public/incoming-call-fullscreen-ios.webp" alt="Incoming call full screen on iOS" width="200"></td>
35
35
  </tr>
36
36
  <tr>
37
37
  <td align="center"><strong>Android</strong></td>
38
38
  <td align="center">
39
39
  <a href="https://github.com/mfairley/expo-callkit-telecom/raw/main/docs/public/outgoing-call-android.mp4" title="Click to watch — outgoing call on Android">
40
- <img src="docs/public/outgoing-call-android-poster.jpg" alt="Outgoing call on Android (click to watch)" width="200">
40
+ <img src="docs/public/outgoing-call-android-poster.webp" alt="Outgoing call on Android (click to watch)" width="200">
41
41
  </a>
42
42
  </td>
43
- <td align="center"><img src="docs/public/incoming-call-banner-android.png" alt="Incoming call banner on Android" width="200"></td>
44
- <td align="center"><img src="docs/public/incoming-call-fullscreen-android.png" alt="Incoming call full screen on Android" width="200"></td>
43
+ <td align="center"><img src="docs/public/incoming-call-banner-android.webp" alt="Incoming call banner on Android" width="200"></td>
44
+ <td align="center"><img src="docs/public/incoming-call-fullscreen-android.webp" alt="Incoming call full screen on Android" width="200"></td>
45
45
  </tr>
46
46
  </table>
47
47
 
@@ -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 `incoming_call`
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 `incoming_call`. There is no
168
- * fallback to a flat top-level shape. Inner keys are camelCase, matching the TS contract
169
- * and the example server.
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 = payload["incoming_call"] as? Map<String, Any?> ?: return null
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"] = "incoming_call"
23
- * data["incoming_call"] = JSON string of the IncomingCallEvent (camelCase)
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
- private const val MESSAGE_TYPE_INCOMING_CALL = "incoming_call"
30
- private const val KEY_INCOMING_CALL = "incoming_call"
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(KEY_INCOMING_CALL to eventMap))
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] != MESSAGE_TYPE_INCOMING_CALL) {
88
+ if (data[KEY_MESSAGE_TYPE] !in MESSAGE_TYPE_INCOMING_CALL) {
86
89
  return null
87
90
  }
88
91
 
89
- val nestedPayload = data[KEY_INCOMING_CALL] ?: return null
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 `"incoming_call"`, matching `IncomingCallEventParser`.
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 `"incoming_call"`.
136
- /// There is no fallback to a flat top-level shape. Inner keys are camelCase
137
- /// to match the TS contract and the example server's wire format.
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 let event = payload["incoming_call"] as? [AnyHashable: Any] else {
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.7",
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",