expo-callkit-telecom 0.3.9 → 0.4.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.
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +9 -0
- package/android/src/main/java/expo/modules/callkittelecom/events/CallEventEmitter.kt +84 -7
- package/android/src/main/java/expo/modules/callkittelecom/managers/CallManager.kt +29 -3
- package/build/Calls.types.d.ts +17 -2
- package/build/Calls.types.d.ts.map +1 -1
- package/build/Calls.types.js.map +1 -1
- package/ios/Managers/CallManager+CXProviderDelegate.swift +7 -2
- package/ios/Managers/CallManager.swift +7 -2
- package/ios/Models/CallEvents.swift +7 -1
- package/package.json +2 -1
- package/plugin/build/constants.d.ts +5 -0
- package/plugin/build/constants.js +6 -1
- package/plugin/build/withExpoCallKitTelecom.d.ts +13 -0
- package/plugin/build/withExpoCallKitTelecomAndroid.js +30 -0
- package/plugin/src/constants.ts +7 -0
- package/plugin/src/withExpoCallKitTelecom.ts +13 -0
- package/plugin/src/withExpoCallKitTelecomAndroid.ts +43 -0
- package/src/Calls.types.ts +18 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,15 @@ 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.4.0](https://github.com/mfairley/expo-callkit-telecom/compare/v0.3.9...v0.4.0) (2026-06-16)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **android:** broadcast call-ended when no JS observer can receive it ([a98b6ee](https://github.com/mfairley/expo-callkit-telecom/commit/a98b6ee20cd4ddd06cb0d520e0604328a33312a3))
|
|
14
|
+
* **android:** embed session in call events and broadcast undelivered events ([24dd9e0](https://github.com/mfairley/expo-callkit-telecom/commit/24dd9e00f16b746d2aa71591ec98b4d607bf0726))
|
|
15
|
+
* **example:** demonstrate the call-ended broadcast receiver ([a032691](https://github.com/mfairley/expo-callkit-telecom/commit/a032691b5d804f7e6d3b9ba18448bd3637a4353c))
|
|
16
|
+
|
|
8
17
|
## [0.3.9](https://github.com/mfairley/expo-callkit-telecom/compare/v0.3.8...v0.3.9) (2026-06-04)
|
|
9
18
|
|
|
10
19
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
package expo.modules.callkittelecom.events
|
|
2
2
|
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.content.Intent
|
|
3
5
|
import expo.modules.callkittelecom.utils.CallKitTelecomLog
|
|
4
6
|
import java.time.Instant
|
|
5
7
|
import java.time.format.DateTimeFormatter
|
|
8
|
+
import org.json.JSONObject
|
|
6
9
|
|
|
7
10
|
private data class QueuedEvent(val body: Map<String, Any?>, val timestamp: Instant)
|
|
8
11
|
|
|
@@ -13,12 +16,21 @@ private data class QueuedEvent(val body: Map<String, Any?>, val timestamp: Insta
|
|
|
13
16
|
* - Tracks which individual events are being observed by JS
|
|
14
17
|
* - Queues events that arrive before observers mount
|
|
15
18
|
* - Flushes queued events with `{ meta: { flushed: true } }`
|
|
19
|
+
* - Broadcasts events that can't reach JS at all (dropped, not queued) when a broadcast context is
|
|
20
|
+
* set, so a JS-less process can still react (see [setBroadcastContext])
|
|
16
21
|
*
|
|
17
22
|
* All mutable state is guarded by [lock] for thread safety.
|
|
18
23
|
*/
|
|
19
24
|
object CallEventEmitter {
|
|
20
25
|
private const val TAG = "ExpoCallKitTelecom.Emitter"
|
|
21
26
|
|
|
27
|
+
/**
|
|
28
|
+
* Package-internal broadcast action for call events that couldn't reach a live JS observer.
|
|
29
|
+
* Apps receive it with a manifest receiver, registered via the `androidEventReceiver`
|
|
30
|
+
* config-plugin prop.
|
|
31
|
+
*/
|
|
32
|
+
const val ACTION_CALL_EVENT = "expo.modules.callkittelecom.ACTION_CALL_EVENT"
|
|
33
|
+
|
|
22
34
|
private val lock = Any()
|
|
23
35
|
private val observingEvents = mutableSetOf<String>()
|
|
24
36
|
private val eventQueues = mutableMapOf<String, MutableList<QueuedEvent>>()
|
|
@@ -28,11 +40,30 @@ object CallEventEmitter {
|
|
|
28
40
|
|
|
29
41
|
@Volatile private var sender: ((String, Map<String, Any?>) -> Unit)? = null
|
|
30
42
|
|
|
43
|
+
@Volatile private var broadcastContext: Context? = null
|
|
44
|
+
|
|
31
45
|
/** Sets or clears the active event sender provided by the Expo module. */
|
|
32
46
|
fun setSender(eventSender: ((String, Map<String, Any?>) -> Unit)?) {
|
|
33
47
|
sender = eventSender
|
|
34
48
|
}
|
|
35
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Enables (or disables, with `null`) the broadcast path by providing an application context.
|
|
52
|
+
*
|
|
53
|
+
* When set, any event that [send] *drops* — i.e. one that can't reach a live JS observer and
|
|
54
|
+
* isn't queued for cold-start replay (queue limit 0) — is emitted as a package-internal
|
|
55
|
+
* [ACTION_CALL_EVENT] broadcast, so a process started by a push or Telecom with no React
|
|
56
|
+
* context can still react (e.g. notify a backend of a killed-app decline). Queued events are
|
|
57
|
+
* not broadcast: they flush to JS once observed, so the queue already covers them.
|
|
58
|
+
*
|
|
59
|
+
* Broadcasting sits next to the queue — the emitter's existing handling for events JS can't
|
|
60
|
+
* receive yet — rather than behind an injected hook, since the actual consumer (the app's
|
|
61
|
+
* manifest receiver) is already decoupled by the OS and there is nothing to inject.
|
|
62
|
+
*/
|
|
63
|
+
fun setBroadcastContext(context: Context?) {
|
|
64
|
+
broadcastContext = context?.applicationContext
|
|
65
|
+
}
|
|
66
|
+
|
|
36
67
|
/**
|
|
37
68
|
* Configures queue size for a specific event.
|
|
38
69
|
*
|
|
@@ -47,8 +78,15 @@ object CallEventEmitter {
|
|
|
47
78
|
*
|
|
48
79
|
* All delivered events are augmented with a `meta` object containing timestamp and flush
|
|
49
80
|
* status.
|
|
81
|
+
*
|
|
82
|
+
* @return true when the event was delivered to a live JS observer, false when it was queued (or
|
|
83
|
+
* dropped by the event's queue limit). When the event was *dropped* (it will never reach JS)
|
|
84
|
+
* and a broadcast context is set (see [setBroadcastContext]), it is also broadcast so native
|
|
85
|
+
* code can deliver out-of-band — e.g. the app process was started by a push or Telecom with
|
|
86
|
+
* no React context. Events that were queued are not broadcast: they flush to JS once
|
|
87
|
+
* observed, so broadcasting them too would double-deliver.
|
|
50
88
|
*/
|
|
51
|
-
fun send(eventName: String, body: Map<String, Any?>) {
|
|
89
|
+
fun send(eventName: String, body: Map<String, Any?>): Boolean {
|
|
52
90
|
val timestamp = Instant.now()
|
|
53
91
|
val senderRef = sender
|
|
54
92
|
val isObserving = synchronized(lock) { observingEvents.contains(eventName) }
|
|
@@ -56,9 +94,43 @@ object CallEventEmitter {
|
|
|
56
94
|
if (senderRef != null && isObserving) {
|
|
57
95
|
CallKitTelecomLog.d(TAG) { "Sending event to JS - name: $eventName" }
|
|
58
96
|
senderRef(eventName, buildEventBody(body, flushed = false, timestamp = timestamp))
|
|
59
|
-
return
|
|
97
|
+
return true
|
|
98
|
+
}
|
|
99
|
+
val queued = queueEvent(eventName, body, timestamp)
|
|
100
|
+
if (!queued) {
|
|
101
|
+
broadcastContext?.let { context ->
|
|
102
|
+
broadcastUndelivered(
|
|
103
|
+
context,
|
|
104
|
+
eventName,
|
|
105
|
+
buildEventBody(body, flushed = false, timestamp = timestamp),
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Broadcasts an undelivered event so a JS-less process can still react.
|
|
114
|
+
*
|
|
115
|
+
* Fires a package-internal ([Intent.setPackage]) [ACTION_CALL_EVENT] broadcast carrying the
|
|
116
|
+
* event name and the JS-shaped body (the same one JS would have received) as a JSON `payload`.
|
|
117
|
+
* Receivers discriminate on the `eventName` extra. Failures are swallowed (warn-logged): a
|
|
118
|
+
* dropped broadcast must never break event emission.
|
|
119
|
+
*/
|
|
120
|
+
private fun broadcastUndelivered(context: Context, eventName: String, body: Map<String, Any?>) {
|
|
121
|
+
try {
|
|
122
|
+
val intent =
|
|
123
|
+
Intent(ACTION_CALL_EVENT)
|
|
124
|
+
.setPackage(context.packageName)
|
|
125
|
+
.putExtra("eventName", eventName)
|
|
126
|
+
.putExtra("payload", JSONObject(body).toString())
|
|
127
|
+
context.sendBroadcast(intent)
|
|
128
|
+
CallKitTelecomLog.d(TAG) {
|
|
129
|
+
"Broadcast undelivered event (no live JS observer) - name: $eventName"
|
|
130
|
+
}
|
|
131
|
+
} catch (e: Exception) {
|
|
132
|
+
CallKitTelecomLog.w(TAG) { "Event broadcast failed - name: $eventName: ${e.message}" }
|
|
60
133
|
}
|
|
61
|
-
queueEvent(eventName, body, timestamp)
|
|
62
134
|
}
|
|
63
135
|
|
|
64
136
|
/** Marks an event as observed and flushes any pending queue for that event. */
|
|
@@ -95,13 +167,18 @@ object CallEventEmitter {
|
|
|
95
167
|
return result
|
|
96
168
|
}
|
|
97
169
|
|
|
98
|
-
/**
|
|
99
|
-
|
|
170
|
+
/**
|
|
171
|
+
* Queues an event and enforces per-event queue limits (drop oldest first).
|
|
172
|
+
*
|
|
173
|
+
* @return true when the event was queued (and will flush to JS once observed), false when
|
|
174
|
+
* queueing is disabled for it (limit 0) so it was dropped and will never reach JS.
|
|
175
|
+
*/
|
|
176
|
+
private fun queueEvent(name: String, body: Map<String, Any?>, timestamp: Instant): Boolean =
|
|
100
177
|
synchronized(lock) {
|
|
101
178
|
val limit = queueLimits[name] ?: defaultQueueLimit
|
|
102
179
|
if (limit == 0) {
|
|
103
180
|
CallKitTelecomLog.d(TAG) { "Dropping event (queueing disabled) - name: $name" }
|
|
104
|
-
return
|
|
181
|
+
return@synchronized false
|
|
105
182
|
}
|
|
106
183
|
|
|
107
184
|
val queue = eventQueues.getOrPut(name) { mutableListOf() }
|
|
@@ -118,8 +195,8 @@ object CallEventEmitter {
|
|
|
118
195
|
"Queueing event (JS not listening) - name: $name, queueSize: ${queue.size}"
|
|
119
196
|
}
|
|
120
197
|
}
|
|
198
|
+
true
|
|
121
199
|
}
|
|
122
|
-
}
|
|
123
200
|
|
|
124
201
|
/** Flushes all queued events for a single event name. */
|
|
125
202
|
private fun flushQueue(eventName: String) {
|
|
@@ -114,6 +114,10 @@ class CallManager private constructor() {
|
|
|
114
114
|
CallEventEmitter.setQueueLimit(CALL_ANSWERED, 1)
|
|
115
115
|
CallEventEmitter.setQueueLimit(VOIP_PUSH_TOKEN_UPDATED, 1)
|
|
116
116
|
|
|
117
|
+
// Broadcast any event that can't reach a live JS observer (e.g. killed-app decline),
|
|
118
|
+
// so a JS-less process can still react via a manifest BroadcastReceiver.
|
|
119
|
+
CallEventEmitter.setBroadcastContext(context)
|
|
120
|
+
|
|
117
121
|
callsManager = CallsManager(context)
|
|
118
122
|
callsManager.registerAppWithTelecom(
|
|
119
123
|
CallsManager.CAPABILITY_BASELINE or CallsManager.CAPABILITY_SUPPORTS_VIDEO_CALLING
|
|
@@ -496,14 +500,18 @@ class CallManager private constructor() {
|
|
|
496
500
|
CallStore.updateStatus(id, CallSessionStatus.ENDED)
|
|
497
501
|
}
|
|
498
502
|
|
|
503
|
+
// Read the just-updated session back from the store (the source of truth) so the
|
|
504
|
+
// embedded session reflects the terminal state. Read before remove().
|
|
505
|
+
val endedSession = CallStore.session(id) ?: existingSession
|
|
506
|
+
|
|
499
507
|
if (emitEnded) {
|
|
500
|
-
CallEventEmitter.send(CallEvents.CALL_ENDED,
|
|
508
|
+
CallEventEmitter.send(CallEvents.CALL_ENDED, sessionEventBody(endedSession))
|
|
501
509
|
}
|
|
502
510
|
|
|
503
511
|
if (reportedReason != null) {
|
|
504
512
|
CallEventEmitter.send(
|
|
505
513
|
CallEvents.CALL_REPORTED_ENDED,
|
|
506
|
-
|
|
514
|
+
sessionEventBody(endedSession, "reason" to reportedReason.value),
|
|
507
515
|
)
|
|
508
516
|
}
|
|
509
517
|
|
|
@@ -519,6 +527,22 @@ class CallManager private constructor() {
|
|
|
519
527
|
}
|
|
520
528
|
}
|
|
521
529
|
|
|
530
|
+
/**
|
|
531
|
+
* Builds an event body carrying the call id plus a full session snapshot.
|
|
532
|
+
*
|
|
533
|
+
* Terminal events embed the session so JS consumers — and the [CallEventEmitter] broadcast for
|
|
534
|
+
* a JS-less process — get full context (e.g. `serverCallId`/`metadata` via `incomingCallEvent`)
|
|
535
|
+
* without a separate lookup.
|
|
536
|
+
*/
|
|
537
|
+
private fun sessionEventBody(
|
|
538
|
+
session: CallSession,
|
|
539
|
+
vararg extra: Pair<String, Any?>,
|
|
540
|
+
): Map<String, Any?> = buildMap {
|
|
541
|
+
put("id", session.id.toString())
|
|
542
|
+
put("session", session.toMap())
|
|
543
|
+
putAll(extra)
|
|
544
|
+
}
|
|
545
|
+
|
|
522
546
|
/**
|
|
523
547
|
* Safety-net cleanup called from the addCall finally block.
|
|
524
548
|
*
|
|
@@ -540,7 +564,9 @@ class CallManager private constructor() {
|
|
|
540
564
|
if (session.status != CallSessionStatus.ENDED) {
|
|
541
565
|
CallStore.updateStatus(id, CallSessionStatus.ENDED)
|
|
542
566
|
}
|
|
543
|
-
|
|
567
|
+
// Read the just-updated session back from the store (the source of truth). Before remove().
|
|
568
|
+
val endedSession = CallStore.session(id) ?: session
|
|
569
|
+
CallEventEmitter.send(CallEvents.CALL_ENDED, sessionEventBody(endedSession))
|
|
544
570
|
CallStore.remove(id)
|
|
545
571
|
|
|
546
572
|
if (CallStore.allSessions().isEmpty()) {
|
package/build/Calls.types.d.ts
CHANGED
|
@@ -244,6 +244,21 @@ export interface CaptureSession {
|
|
|
244
244
|
export interface CallActionEvent extends NativeEvent {
|
|
245
245
|
id: string;
|
|
246
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* Mixin for events that carry a full {@link CallSession} snapshot alongside the
|
|
249
|
+
* event-specific fields.
|
|
250
|
+
*
|
|
251
|
+
* Terminal events embed the session so consumers without access to the session
|
|
252
|
+
* store still get full context. On Android this is also what makes the
|
|
253
|
+
* package-internal call-event broadcast self-contained when no JS observer is
|
|
254
|
+
* alive (see "Call ended while the app is killed" in the docs) — the broadcast
|
|
255
|
+
* payload is exactly this event body.
|
|
256
|
+
*
|
|
257
|
+
* @category Call Events
|
|
258
|
+
*/
|
|
259
|
+
export interface WithSession {
|
|
260
|
+
session: CallSession;
|
|
261
|
+
}
|
|
247
262
|
/**
|
|
248
263
|
* Fired after `startOutgoingCall`, once the OS has accepted the call request.
|
|
249
264
|
*
|
|
@@ -301,7 +316,7 @@ export interface CallAnsweredEvent extends CallActionEvent {
|
|
|
301
316
|
*
|
|
302
317
|
* @category Call Events
|
|
303
318
|
*/
|
|
304
|
-
export interface CallEndedEvent extends CallActionEvent {
|
|
319
|
+
export interface CallEndedEvent extends CallActionEvent, WithSession {
|
|
305
320
|
}
|
|
306
321
|
/**
|
|
307
322
|
* Reason a call was ended, reported on {@link CallReportedEnded}.
|
|
@@ -315,7 +330,7 @@ export type CallEndedReason = "failed" | "remoteEnded" | "unanswered" | "answere
|
|
|
315
330
|
*
|
|
316
331
|
* @category Call Events
|
|
317
332
|
*/
|
|
318
|
-
export interface CallReportedEnded extends CallActionEvent {
|
|
333
|
+
export interface CallReportedEnded extends CallActionEvent, WithSession {
|
|
319
334
|
reason: CallEndedReason;
|
|
320
335
|
}
|
|
321
336
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Calls.types.d.ts","sourceRoot":"","sources":["../src/Calls.types.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"Calls.types.d.ts","sourceRoot":"","sources":["../src/Calls.types.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,KAAK,CAAC;AAMhD;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,mFAAmF;IACnF,OAAO,EAAE,OAAO,CAAC;IACjB,sDAAsD;IACtD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,eAAe,CAAC;CACvB;AAMD;;;;;;;;GAQG;AACH,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,WAAW,CAAC;IACrB,MAAM,EAAE,iBAAiB,CAAC;IAC1B,kBAAkB,EAAE,eAAe,EAAE,CAAC;IACtC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,EAAE,iBAAiB,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,MAAM,MAAM,iBAAiB,GAAG,UAAU,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAE9E;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,0DAA0D;IAC1D,EAAE,EAAE,MAAM,CAAC;IACX,oBAAoB;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;2EACuE;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qBAAqB;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,iBAAiB,GACzB,YAAY,GACZ,YAAY,GACZ,SAAS,GACT,WAAW,GACX,OAAO,CAAC;AAEZ;;;;GAIG;AACH,MAAM,WAAW,qBAAsB,SAAQ,WAAW;IACxD,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAwB,SAAQ,WAAW;IAC1D,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,uBAAwB,SAAQ,WAAW;IAC1D,EAAE,EAAE,MAAM,CAAC;CACZ;AAMD;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GACxB,SAAS,GACT,QAAQ,GACR,cAAc,GACd,YAAY,GACZ,SAAS,CAAC;AAMd;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,OAAO,CAAC;IAClB,8DAA8D;IAC9D,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oEAAoE;IACpE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,yEAAyE;IACzE,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mBAAmB,EAAE,OAAO,CAAC;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,oBAAoB,EAAE,gBAAgB,CAAC;IACvC,YAAY,EAAE,UAAU,CAAC;IACzB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,MAAM,EAAE,SAAS,EAAE,CAAC;IACpB,OAAO,EAAE,SAAS,EAAE,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,mBAAmB,CAAC;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAC3B,iBAAiB,GACjB,gBAAgB,GAChB,YAAY,GACZ,eAAe,GACf,aAAa,GACb,cAAc,GACd,SAAS,GACT,MAAM,GACN,UAAU,GACV,UAAU,GACV,SAAS,GACT,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAElB;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAED;;;;GAIG;AACH,MAAM,WAAW,0BAA2B,SAAQ,WAAW;IAC7D,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA6B,SAAQ,WAAW;IAC/D,KAAK,EAAE,oBAAoB,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAuB,SAAQ,WAAW;IACzD,YAAY,EAAE,UAAU,CAAC;IACzB,8EAA8E;IAC9E,eAAe,CAAC,EAAE,SAAS,EAAE,CAAC;CAC/B;AAMD;;;;;GAKG;AACH,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,wEAAwE;IACxE,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C;AAMD;;;;GAIG;AACH,MAAM,WAAW,eAAgB,SAAQ,WAAW;IAClD,EAAE,EAAE,MAAM,CAAC;CACZ;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,WAAW,CAAC;CACtB;AAED;;;;GAIG;AACH,MAAM,WAAW,wBAAyB,SAAQ,eAAe;CAAG;AAEpE;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB;;yEAEqE;IACrE,YAAY,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,QAAQ,EAAE,OAAO,CAAC;IAClB,iFAAiF;IACjF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sCAAsC;IACtC,MAAM,EAAE,eAAe,CAAC;IACxB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,eAAe;CAAG;AAErE;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAe,SAAQ,eAAe,EAAE,WAAW;CAAG;AAEvE;;;;GAIG;AACH,MAAM,MAAM,eAAe,GACvB,QAAQ,GACR,aAAa,GACb,YAAY,GACZ,mBAAmB,GACnB,mBAAmB,GACnB,SAAS,CAAC;AAEd;;;;;GAKG;AACH,MAAM,WAAW,iBAAkB,SAAQ,eAAe,EAAE,WAAW;IACrE,MAAM,EAAE,eAAe,CAAC;CACzB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAoB,SAAQ,eAAe;IAC1D,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAkB,SAAQ,eAAe;IACxD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,QAAQ,EAAE,OAAO,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,SAAU,SAAQ,eAAe;IAChD,MAAM,EAAE,MAAM,CAAC;CAChB;AAMD;;;;GAIG;AACH,MAAM,MAAM,oBAAoB,GAAG,aAAa,GAAG,OAAO,GAAG,SAAS,CAAC;AAEvE;;;;;GAKG;AACH,MAAM,WAAW,uBAAwB,SAAQ,WAAW;IAC1D,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,oBAAoB,CAAC;IACjC,QAAQ,EAAE,OAAO,CAAC;CACnB;AAMD;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,kCAAkC;IAClC,KAAK,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,IAAI,EAAE,aAAa,CAAC;CACrB;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,WAAW;IAC5D,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,IAAI,EAAE,aAAa,CAAC;CACrB"}
|
package/build/Calls.types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Calls.types.js","sourceRoot":"","sources":["../src/Calls.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Type of VoIP push token reported by `getVoIPPushToken`.\n *\n * - `\"APNS_VOIP\"` — Apple Push Notification service VoIP channel (iOS)\n * - `\"FCM\"` — Firebase Cloud Messaging (Android)\n *\n * @category VoIP Push\n */\nexport type PushTokenType = \"APNS_VOIP\" | \"FCM\";\n\n// ============================================================================\n// Native event infrastructure\n// ============================================================================\n\n/**\n * Metadata attached to every native event.\n *\n * @category Core\n */\nexport interface NativeEventMeta {\n /** Whether the event was flushed from queue (true) or sent in real-time (false) */\n flushed: boolean;\n /** ISO8601 timestamp of when the event was created */\n timestamp: string;\n}\n\n/**\n * Base shape extended by every native event — carries a {@link NativeEventMeta} envelope.\n *\n * @category Core\n */\nexport interface NativeEvent {\n meta: NativeEventMeta;\n}\n\n// ============================================================================\n// Call sessions\n// ============================================================================\n\n/**\n * Active call as tracked by the module.\n *\n * Represents one in-flight call. Mirrors the OS-side `CXCall` (iOS) /\n * `CallControlScope` (Android) plus app-level state (origin, participants,\n * incoming-call payload, mute/hold).\n *\n * @category Sessions\n */\nexport interface CallSession {\n id: string;\n options: CallOptions;\n origin: CallSessionOrigin;\n remoteParticipants: CallParticipant[];\n incomingCallEvent?: IncomingCallEvent;\n status: CallSessionStatus;\n connectedAt?: string;\n isMuted: boolean;\n isOnHold: boolean;\n dtmfDigits?: string;\n}\n\n/**\n * Per-call options set at session start.\n *\n * @category Sessions\n */\nexport interface CallOptions {\n hasVideo: boolean;\n}\n\n/**\n * Where a call session originated.\n *\n * - `incoming` — Reported from a VoIP push (or directly via `reportIncomingCall`).\n * - `outgoingApp` — Started by your app via `startOutgoingCall`.\n * - `outgoingSystem` — Started by the OS via a call intent (Recents, Siri).\n *\n * @category Sessions\n */\nexport type CallSessionOrigin = \"incoming\" | \"outgoingApp\" | \"outgoingSystem\";\n\n/**\n * Identity for a remote party on a call.\n *\n * @category Sessions\n */\nexport interface CallParticipant {\n /** Opaque, stable app identifier for this participant. */\n id: string;\n /** Display name. */\n displayName?: string;\n /** Avatar URL. */\n avatarUrl?: string;\n /** Phone number in E.164 (e.g. \"+14155551234\"). When present on iOS, the\n * CallKit handle is set to this number, enabling Recents and Siri. */\n phoneNumber?: string;\n /** Email address. */\n email?: string;\n}\n\n/**\n * Call session status representing the lifecycle of a call.\n *\n * Outgoing call flow: requesting → connecting → connected → ended\n * Incoming call flow: ringing → connecting → connected → ended\n *\n * - `requesting` — Outgoing only. The call request has been submitted to\n * CallKit/Telecom and is awaiting system acceptance.\n * - `ringing` — Incoming only. The call has been reported to CallKit/Telecom\n * and the user sees the native incoming call UI or notification.\n * - `connecting` — Both directions. For outgoing calls, the system accepted the\n * call and the dialtone is playing while waiting for the remote party to answer.\n * For incoming calls, the user answered and media is being established.\n * - `connected` — Both directions. Media is flowing and the call is active.\n * - `ended` — Both directions. Transient state during teardown before the\n * session is removed from the store.\n *\n * @category Sessions\n */\nexport type CallSessionStatus =\n | \"requesting\"\n | \"connecting\"\n | \"ringing\"\n | \"connected\"\n | \"ended\";\n\n/**\n * Fired when a new {@link CallSession} is created (outgoing request or incoming report).\n *\n * @category Sessions\n */\nexport interface CallSessionAddedEvent extends NativeEvent {\n session: CallSession;\n}\n\n/**\n * Fired when an existing {@link CallSession}'s state changes (status, mute, hold, etc.).\n *\n * @category Sessions\n */\nexport interface CallSessionUpdatedEvent extends NativeEvent {\n session: CallSession;\n}\n\n/**\n * Fired when a {@link CallSession} is removed after the call has ended and been cleaned up.\n *\n * @category Sessions\n */\nexport interface CallSessionRemovedEvent extends NativeEvent {\n id: string;\n}\n\n// ============================================================================\n// Permissions\n// ============================================================================\n\n/**\n * Permission status for microphone and camera, reported on {@link AudioSession}\n * and {@link CaptureSession}.\n *\n * @category Permissions\n */\nexport type PermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"restricted\"\n | \"unknown\";\n\n// ============================================================================\n// Audio session\n// ============================================================================\n\n/**\n * Snapshot of the current audio session, including activation state, route,\n * and (on iOS) the WebRTC `RTCAudioSession` coordination flags.\n *\n * @category Audio\n */\nexport interface AudioSession {\n isActive: boolean;\n /** iOS only: whether the WebRTC RTCAudioSession is active. */\n rtcSessionIsActive?: boolean;\n /** iOS only: whether the AVAudioSession is active. */\n avSessionIsActive?: boolean;\n /** iOS only: whether the RTCAudioSession audio track is enabled. */\n isAudioEnabled?: boolean;\n /** iOS only: whether manual audio mode is enabled on RTCAudioSession. */\n useManualAudio?: boolean;\n isOtherAudioPlaying: boolean;\n category: string;\n mode: string;\n /** iOS only: AVAudioSession category options. */\n categoryOptions?: string[];\n sampleRate: number;\n ioBufferDuration: number;\n inputNumberOfChannels: number;\n outputNumberOfChannels: number;\n microphonePermission: PermissionStatus;\n currentRoute: AudioRoute;\n /** Available audio output devices. Populated on Android; undefined on iOS. */\n availableRoutes?: AudioPort[];\n}\n\n/**\n * Currently-selected audio inputs and outputs.\n *\n * @category Audio\n */\nexport interface AudioRoute {\n inputs: AudioPort[];\n outputs: AudioPort[];\n}\n\n/**\n * A single audio input or output (earpiece, speaker, headphones, Bluetooth device, etc.).\n *\n * @category Audio\n */\nexport interface AudioPort {\n portType: AudioOutputPortType;\n portName: string;\n uid: string;\n}\n\n/**\n * Cross-platform audio output port type identifiers.\n * Both iOS and Android map their native audio device types to these shared values.\n *\n * @category Audio\n */\nexport type AudioOutputPortType =\n | \"builtInReceiver\" // Earpiece\n | \"builtInSpeaker\" // Speaker\n | \"headphones\" // Wired headphones\n | \"bluetoothA2DP\" // Bluetooth A2DP audio\n | \"bluetoothLE\" // Bluetooth Low Energy audio\n | \"bluetoothHFP\" // Bluetooth Hands-Free Profile\n | \"airPlay\" // AirPlay\n | \"hdmi\" // HDMI output\n | \"carAudio\" // CarPlay\n | \"usbAudio\" // USB audio\n | \"lineOut\" // Line out\n | (string & {}); // Allow other unknown port types\n\n/**\n * Brief summary of one call associated with an audio-session activation event.\n *\n * @category Audio Events\n */\nexport interface AudioSessionCallInfo {\n id: string;\n status: CallSessionStatus;\n}\n\n/**\n * Fired when the system activates the audio session for a call.\n *\n * @category Audio Events\n */\nexport interface AudioSessionActivatedEvent extends NativeEvent {\n calls: AudioSessionCallInfo[];\n}\n\n/**\n * Fired when the system deactivates the audio session after a call.\n *\n * @category Audio Events\n */\nexport interface AudioSessionDeactivatedEvent extends NativeEvent {\n calls: AudioSessionCallInfo[];\n}\n\n/**\n * Fired when the active audio route changes (e.g. AirPods connected, speaker toggled).\n *\n * @category Audio Events\n */\nexport interface AudioRouteChangedEvent extends NativeEvent {\n currentRoute: AudioRoute;\n /** Available audio output devices. Populated on Android; undefined on iOS. */\n availableRoutes?: AudioPort[];\n}\n\n// ============================================================================\n// Capture session (camera)\n// ============================================================================\n\n/**\n * Snapshot of camera-related state, including permission and (on iOS 16+)\n * multitasking-camera availability.\n *\n * @category Capture\n */\nexport interface CaptureSession {\n cameraPermission: PermissionStatus;\n /** Whether the device supports multitasking camera access (iOS 16+). */\n isMultitaskingCameraAccessSupported?: boolean;\n}\n\n// ============================================================================\n// Call action events\n// ============================================================================\n\n/**\n * Base shape for any event carrying a {@link CallSession.id}.\n *\n * @category Call Events\n */\nexport interface CallActionEvent extends NativeEvent {\n id: string;\n}\n\n/**\n * Fired after `startOutgoingCall`, once the OS has accepted the call request.\n *\n * @category Call Events\n */\nexport interface OutgoingCallStartedEvent extends CallActionEvent {}\n\n/**\n * Payload describing one incoming call.\n *\n * Delivered both inside a VoIP push (parsed natively by the module) and on\n * {@link CallSession.incomingCallEvent} for any incoming-origin session.\n *\n * @category VoIP Push\n */\nexport interface IncomingCallEvent {\n /** Unique event identifier (UUID). Used for dedup. */\n eventId: string;\n /** Your backend's id for this call. Distinct from {@link CallSession.id},\n * which is the OS-assigned native call UUID. Use this id to talk to your\n * server about the call (e.g. POST /calls/:serverCallId/answer). */\n serverCallId: string;\n /** True for video calls, false for audio. */\n hasVideo: boolean;\n /** RFC 3339 timestamp of when the call was placed. Optional; defaults to now. */\n startedAt?: string;\n /** Caller identity and addressing. */\n caller: CallParticipant;\n /**\n * App-defined extra fields, forwarded verbatim from the push payload.\n *\n * The library treats this as opaque — put whatever your app needs here\n * (chatId, tenantId, room name, etc.). Cast to your own type at the\n * read site.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Fired after `reportIncomingCall`, once the OS has accepted the incoming-call report.\n *\n * @category Call Events\n */\nexport interface IncomingCallReportedEvent extends CallActionEvent {}\n\n/**\n * Fired when the user answers an incoming call from the system UI.\n *\n * @category Call Events\n */\nexport interface CallAnsweredEvent extends CallActionEvent {\n requestId: string;\n}\n\n/**\n * Fired when the user ends a call from the system UI, or the OS ends the call for any reason.\n *\n * @category Call Events\n */\nexport interface CallEndedEvent extends CallActionEvent {}\n\n/**\n * Reason a call was ended, reported on {@link CallReportedEnded}.\n *\n * @category Call Events\n */\nexport type CallEndedReason =\n | \"failed\"\n | \"remoteEnded\"\n | \"unanswered\"\n | \"answeredElsewhere\"\n | \"declinedElsewhere\"\n | \"unknown\";\n\n/**\n * Fired when the app calls `reportCallEnded` to inform the OS the call has ended\n * externally (e.g. remote hang-up).\n *\n * @category Call Events\n */\nexport interface CallReportedEnded extends CallActionEvent {\n reason: CallEndedReason;\n}\n\n/**\n * Fired when the system requests a mute-state change (e.g. user pressed the\n * mute button in the CallKit UI). Apply the change to your media connection.\n *\n * @category Call Events\n */\nexport interface SetMutedActionEvent extends CallActionEvent {\n isMuted: boolean;\n}\n\n/**\n * Fired when video state changes on a call.\n *\n * @category Call Events\n */\nexport interface VideoChangedEvent extends CallActionEvent {\n hasVideo: boolean;\n}\n\n/**\n * Fired when the system requests a hold-state change. Apply the change to your media connection.\n *\n * @category Call Events\n */\nexport interface SetHeldActionEvent extends CallActionEvent {\n isOnHold: boolean;\n}\n\n/**\n * Fired when the system requests DTMF tones be played on the call.\n *\n * @category Call Events\n */\nexport interface DTMFEvent extends CallActionEvent {\n digits: string;\n}\n\n// ============================================================================\n// Call intents (iOS Recents, Siri \"call X\")\n// ============================================================================\n\n/**\n * Kind of handle attached to a call intent (Recents tap, Siri).\n *\n * @category Call Events\n */\nexport type CallIntentHandleType = \"phoneNumber\" | \"email\" | \"unknown\";\n\n/**\n * Fired when the OS routes a \"start call\" intent to the app — e.g. the user\n * tapped a Recents entry or said \"call Jane\" to Siri.\n *\n * @category Call Events\n */\nexport interface CallIntentReceivedEvent extends NativeEvent {\n handle: string;\n handleType: CallIntentHandleType;\n hasVideo: boolean;\n}\n\n// ============================================================================\n// VoIP push\n// ============================================================================\n\n/**\n * A VoIP push token bundled with its transport type.\n *\n * @category VoIP Push\n */\nexport interface VoIPPushToken {\n /** The VoIP push token string. */\n token: string;\n /** The type of token this platform provides. */\n type: PushTokenType;\n}\n\n/**\n * Fired when the VoIP push token is received, refreshed, or invalidated.\n *\n * @category VoIP Push\n */\nexport interface VoIPPushTokenUpdatedEvent extends NativeEvent {\n /** The VoIP push token string, or undefined if invalidated */\n token?: string;\n /** The type of VoIP push token (e.g. \"APNS_VOIP\", \"FCM\"). */\n type: PushTokenType;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"Calls.types.js","sourceRoot":"","sources":["../src/Calls.types.ts"],"names":[],"mappings":"","sourcesContent":["/**\n * Type of VoIP push token reported by `getVoIPPushToken`.\n *\n * - `\"APNS_VOIP\"` — Apple Push Notification service VoIP channel (iOS)\n * - `\"FCM\"` — Firebase Cloud Messaging (Android)\n *\n * @category VoIP Push\n */\nexport type PushTokenType = \"APNS_VOIP\" | \"FCM\";\n\n// ============================================================================\n// Native event infrastructure\n// ============================================================================\n\n/**\n * Metadata attached to every native event.\n *\n * @category Core\n */\nexport interface NativeEventMeta {\n /** Whether the event was flushed from queue (true) or sent in real-time (false) */\n flushed: boolean;\n /** ISO8601 timestamp of when the event was created */\n timestamp: string;\n}\n\n/**\n * Base shape extended by every native event — carries a {@link NativeEventMeta} envelope.\n *\n * @category Core\n */\nexport interface NativeEvent {\n meta: NativeEventMeta;\n}\n\n// ============================================================================\n// Call sessions\n// ============================================================================\n\n/**\n * Active call as tracked by the module.\n *\n * Represents one in-flight call. Mirrors the OS-side `CXCall` (iOS) /\n * `CallControlScope` (Android) plus app-level state (origin, participants,\n * incoming-call payload, mute/hold).\n *\n * @category Sessions\n */\nexport interface CallSession {\n id: string;\n options: CallOptions;\n origin: CallSessionOrigin;\n remoteParticipants: CallParticipant[];\n incomingCallEvent?: IncomingCallEvent;\n status: CallSessionStatus;\n connectedAt?: string;\n isMuted: boolean;\n isOnHold: boolean;\n dtmfDigits?: string;\n}\n\n/**\n * Per-call options set at session start.\n *\n * @category Sessions\n */\nexport interface CallOptions {\n hasVideo: boolean;\n}\n\n/**\n * Where a call session originated.\n *\n * - `incoming` — Reported from a VoIP push (or directly via `reportIncomingCall`).\n * - `outgoingApp` — Started by your app via `startOutgoingCall`.\n * - `outgoingSystem` — Started by the OS via a call intent (Recents, Siri).\n *\n * @category Sessions\n */\nexport type CallSessionOrigin = \"incoming\" | \"outgoingApp\" | \"outgoingSystem\";\n\n/**\n * Identity for a remote party on a call.\n *\n * @category Sessions\n */\nexport interface CallParticipant {\n /** Opaque, stable app identifier for this participant. */\n id: string;\n /** Display name. */\n displayName?: string;\n /** Avatar URL. */\n avatarUrl?: string;\n /** Phone number in E.164 (e.g. \"+14155551234\"). When present on iOS, the\n * CallKit handle is set to this number, enabling Recents and Siri. */\n phoneNumber?: string;\n /** Email address. */\n email?: string;\n}\n\n/**\n * Call session status representing the lifecycle of a call.\n *\n * Outgoing call flow: requesting → connecting → connected → ended\n * Incoming call flow: ringing → connecting → connected → ended\n *\n * - `requesting` — Outgoing only. The call request has been submitted to\n * CallKit/Telecom and is awaiting system acceptance.\n * - `ringing` — Incoming only. The call has been reported to CallKit/Telecom\n * and the user sees the native incoming call UI or notification.\n * - `connecting` — Both directions. For outgoing calls, the system accepted the\n * call and the dialtone is playing while waiting for the remote party to answer.\n * For incoming calls, the user answered and media is being established.\n * - `connected` — Both directions. Media is flowing and the call is active.\n * - `ended` — Both directions. Transient state during teardown before the\n * session is removed from the store.\n *\n * @category Sessions\n */\nexport type CallSessionStatus =\n | \"requesting\"\n | \"connecting\"\n | \"ringing\"\n | \"connected\"\n | \"ended\";\n\n/**\n * Fired when a new {@link CallSession} is created (outgoing request or incoming report).\n *\n * @category Sessions\n */\nexport interface CallSessionAddedEvent extends NativeEvent {\n session: CallSession;\n}\n\n/**\n * Fired when an existing {@link CallSession}'s state changes (status, mute, hold, etc.).\n *\n * @category Sessions\n */\nexport interface CallSessionUpdatedEvent extends NativeEvent {\n session: CallSession;\n}\n\n/**\n * Fired when a {@link CallSession} is removed after the call has ended and been cleaned up.\n *\n * @category Sessions\n */\nexport interface CallSessionRemovedEvent extends NativeEvent {\n id: string;\n}\n\n// ============================================================================\n// Permissions\n// ============================================================================\n\n/**\n * Permission status for microphone and camera, reported on {@link AudioSession}\n * and {@link CaptureSession}.\n *\n * @category Permissions\n */\nexport type PermissionStatus =\n | \"granted\"\n | \"denied\"\n | \"undetermined\"\n | \"restricted\"\n | \"unknown\";\n\n// ============================================================================\n// Audio session\n// ============================================================================\n\n/**\n * Snapshot of the current audio session, including activation state, route,\n * and (on iOS) the WebRTC `RTCAudioSession` coordination flags.\n *\n * @category Audio\n */\nexport interface AudioSession {\n isActive: boolean;\n /** iOS only: whether the WebRTC RTCAudioSession is active. */\n rtcSessionIsActive?: boolean;\n /** iOS only: whether the AVAudioSession is active. */\n avSessionIsActive?: boolean;\n /** iOS only: whether the RTCAudioSession audio track is enabled. */\n isAudioEnabled?: boolean;\n /** iOS only: whether manual audio mode is enabled on RTCAudioSession. */\n useManualAudio?: boolean;\n isOtherAudioPlaying: boolean;\n category: string;\n mode: string;\n /** iOS only: AVAudioSession category options. */\n categoryOptions?: string[];\n sampleRate: number;\n ioBufferDuration: number;\n inputNumberOfChannels: number;\n outputNumberOfChannels: number;\n microphonePermission: PermissionStatus;\n currentRoute: AudioRoute;\n /** Available audio output devices. Populated on Android; undefined on iOS. */\n availableRoutes?: AudioPort[];\n}\n\n/**\n * Currently-selected audio inputs and outputs.\n *\n * @category Audio\n */\nexport interface AudioRoute {\n inputs: AudioPort[];\n outputs: AudioPort[];\n}\n\n/**\n * A single audio input or output (earpiece, speaker, headphones, Bluetooth device, etc.).\n *\n * @category Audio\n */\nexport interface AudioPort {\n portType: AudioOutputPortType;\n portName: string;\n uid: string;\n}\n\n/**\n * Cross-platform audio output port type identifiers.\n * Both iOS and Android map their native audio device types to these shared values.\n *\n * @category Audio\n */\nexport type AudioOutputPortType =\n | \"builtInReceiver\" // Earpiece\n | \"builtInSpeaker\" // Speaker\n | \"headphones\" // Wired headphones\n | \"bluetoothA2DP\" // Bluetooth A2DP audio\n | \"bluetoothLE\" // Bluetooth Low Energy audio\n | \"bluetoothHFP\" // Bluetooth Hands-Free Profile\n | \"airPlay\" // AirPlay\n | \"hdmi\" // HDMI output\n | \"carAudio\" // CarPlay\n | \"usbAudio\" // USB audio\n | \"lineOut\" // Line out\n | (string & {}); // Allow other unknown port types\n\n/**\n * Brief summary of one call associated with an audio-session activation event.\n *\n * @category Audio Events\n */\nexport interface AudioSessionCallInfo {\n id: string;\n status: CallSessionStatus;\n}\n\n/**\n * Fired when the system activates the audio session for a call.\n *\n * @category Audio Events\n */\nexport interface AudioSessionActivatedEvent extends NativeEvent {\n calls: AudioSessionCallInfo[];\n}\n\n/**\n * Fired when the system deactivates the audio session after a call.\n *\n * @category Audio Events\n */\nexport interface AudioSessionDeactivatedEvent extends NativeEvent {\n calls: AudioSessionCallInfo[];\n}\n\n/**\n * Fired when the active audio route changes (e.g. AirPods connected, speaker toggled).\n *\n * @category Audio Events\n */\nexport interface AudioRouteChangedEvent extends NativeEvent {\n currentRoute: AudioRoute;\n /** Available audio output devices. Populated on Android; undefined on iOS. */\n availableRoutes?: AudioPort[];\n}\n\n// ============================================================================\n// Capture session (camera)\n// ============================================================================\n\n/**\n * Snapshot of camera-related state, including permission and (on iOS 16+)\n * multitasking-camera availability.\n *\n * @category Capture\n */\nexport interface CaptureSession {\n cameraPermission: PermissionStatus;\n /** Whether the device supports multitasking camera access (iOS 16+). */\n isMultitaskingCameraAccessSupported?: boolean;\n}\n\n// ============================================================================\n// Call action events\n// ============================================================================\n\n/**\n * Base shape for any event carrying a {@link CallSession.id}.\n *\n * @category Call Events\n */\nexport interface CallActionEvent extends NativeEvent {\n id: string;\n}\n\n/**\n * Mixin for events that carry a full {@link CallSession} snapshot alongside the\n * event-specific fields.\n *\n * Terminal events embed the session so consumers without access to the session\n * store still get full context. On Android this is also what makes the\n * package-internal call-event broadcast self-contained when no JS observer is\n * alive (see \"Call ended while the app is killed\" in the docs) — the broadcast\n * payload is exactly this event body.\n *\n * @category Call Events\n */\nexport interface WithSession {\n session: CallSession;\n}\n\n/**\n * Fired after `startOutgoingCall`, once the OS has accepted the call request.\n *\n * @category Call Events\n */\nexport interface OutgoingCallStartedEvent extends CallActionEvent {}\n\n/**\n * Payload describing one incoming call.\n *\n * Delivered both inside a VoIP push (parsed natively by the module) and on\n * {@link CallSession.incomingCallEvent} for any incoming-origin session.\n *\n * @category VoIP Push\n */\nexport interface IncomingCallEvent {\n /** Unique event identifier (UUID). Used for dedup. */\n eventId: string;\n /** Your backend's id for this call. Distinct from {@link CallSession.id},\n * which is the OS-assigned native call UUID. Use this id to talk to your\n * server about the call (e.g. POST /calls/:serverCallId/answer). */\n serverCallId: string;\n /** True for video calls, false for audio. */\n hasVideo: boolean;\n /** RFC 3339 timestamp of when the call was placed. Optional; defaults to now. */\n startedAt?: string;\n /** Caller identity and addressing. */\n caller: CallParticipant;\n /**\n * App-defined extra fields, forwarded verbatim from the push payload.\n *\n * The library treats this as opaque — put whatever your app needs here\n * (chatId, tenantId, room name, etc.). Cast to your own type at the\n * read site.\n */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * Fired after `reportIncomingCall`, once the OS has accepted the incoming-call report.\n *\n * @category Call Events\n */\nexport interface IncomingCallReportedEvent extends CallActionEvent {}\n\n/**\n * Fired when the user answers an incoming call from the system UI.\n *\n * @category Call Events\n */\nexport interface CallAnsweredEvent extends CallActionEvent {\n requestId: string;\n}\n\n/**\n * Fired when the user ends a call from the system UI, or the OS ends the call for any reason.\n *\n * @category Call Events\n */\nexport interface CallEndedEvent extends CallActionEvent, WithSession {}\n\n/**\n * Reason a call was ended, reported on {@link CallReportedEnded}.\n *\n * @category Call Events\n */\nexport type CallEndedReason =\n | \"failed\"\n | \"remoteEnded\"\n | \"unanswered\"\n | \"answeredElsewhere\"\n | \"declinedElsewhere\"\n | \"unknown\";\n\n/**\n * Fired when the app calls `reportCallEnded` to inform the OS the call has ended\n * externally (e.g. remote hang-up).\n *\n * @category Call Events\n */\nexport interface CallReportedEnded extends CallActionEvent, WithSession {\n reason: CallEndedReason;\n}\n\n/**\n * Fired when the system requests a mute-state change (e.g. user pressed the\n * mute button in the CallKit UI). Apply the change to your media connection.\n *\n * @category Call Events\n */\nexport interface SetMutedActionEvent extends CallActionEvent {\n isMuted: boolean;\n}\n\n/**\n * Fired when video state changes on a call.\n *\n * @category Call Events\n */\nexport interface VideoChangedEvent extends CallActionEvent {\n hasVideo: boolean;\n}\n\n/**\n * Fired when the system requests a hold-state change. Apply the change to your media connection.\n *\n * @category Call Events\n */\nexport interface SetHeldActionEvent extends CallActionEvent {\n isOnHold: boolean;\n}\n\n/**\n * Fired when the system requests DTMF tones be played on the call.\n *\n * @category Call Events\n */\nexport interface DTMFEvent extends CallActionEvent {\n digits: string;\n}\n\n// ============================================================================\n// Call intents (iOS Recents, Siri \"call X\")\n// ============================================================================\n\n/**\n * Kind of handle attached to a call intent (Recents tap, Siri).\n *\n * @category Call Events\n */\nexport type CallIntentHandleType = \"phoneNumber\" | \"email\" | \"unknown\";\n\n/**\n * Fired when the OS routes a \"start call\" intent to the app — e.g. the user\n * tapped a Recents entry or said \"call Jane\" to Siri.\n *\n * @category Call Events\n */\nexport interface CallIntentReceivedEvent extends NativeEvent {\n handle: string;\n handleType: CallIntentHandleType;\n hasVideo: boolean;\n}\n\n// ============================================================================\n// VoIP push\n// ============================================================================\n\n/**\n * A VoIP push token bundled with its transport type.\n *\n * @category VoIP Push\n */\nexport interface VoIPPushToken {\n /** The VoIP push token string. */\n token: string;\n /** The type of token this platform provides. */\n type: PushTokenType;\n}\n\n/**\n * Fired when the VoIP push token is received, refreshed, or invalidated.\n *\n * @category VoIP Push\n */\nexport interface VoIPPushTokenUpdatedEvent extends NativeEvent {\n /** The VoIP push token string, or undefined if invalidated */\n token?: string;\n /** The type of VoIP push token (e.g. \"APNS_VOIP\", \"FCM\"). */\n type: PushTokenType;\n}\n"]}
|
|
@@ -104,8 +104,13 @@ extension CallManager: CXProviderDelegate {
|
|
|
104
104
|
cancelCallTimeout(for: action.callUUID)
|
|
105
105
|
|
|
106
106
|
Task {
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
// Snapshot the session as ended so the embedded session reflects the terminal state.
|
|
108
|
+
if var session = await store.session(for: action.callUUID) {
|
|
109
|
+
session.status = .ended
|
|
110
|
+
await MainActor.run {
|
|
111
|
+
CallEventEmitter.shared.send(
|
|
112
|
+
CallEndedEvent(id: action.callUUID, session: session))
|
|
113
|
+
}
|
|
109
114
|
}
|
|
110
115
|
|
|
111
116
|
await store.remove(for: action.callUUID)
|
|
@@ -566,8 +566,13 @@ class CallManager: NSObject {
|
|
|
566
566
|
|
|
567
567
|
provider.reportCall(with: id, endedAt: Date(), reason: reason)
|
|
568
568
|
|
|
569
|
-
|
|
570
|
-
|
|
569
|
+
// Snapshot the session as ended so the embedded session reflects the terminal state.
|
|
570
|
+
if var session = await store.session(for: id) {
|
|
571
|
+
session.status = .ended
|
|
572
|
+
await MainActor.run {
|
|
573
|
+
CallEventEmitter.shared.send(
|
|
574
|
+
CallReportedEnded(id: id, reason: reason, session: session))
|
|
575
|
+
}
|
|
571
576
|
}
|
|
572
577
|
|
|
573
578
|
await store.remove(for: id)
|
|
@@ -159,9 +159,13 @@ struct CallEndedEvent: CallEvent {
|
|
|
159
159
|
static let name = "onCallEnded"
|
|
160
160
|
|
|
161
161
|
let id: UUID
|
|
162
|
+
let session: CallSession
|
|
162
163
|
|
|
163
164
|
var body: [String: Any] {
|
|
164
|
-
[
|
|
165
|
+
[
|
|
166
|
+
CallEventKeys.id: id.uuidString,
|
|
167
|
+
"session": session.toDictionary(),
|
|
168
|
+
]
|
|
165
169
|
}
|
|
166
170
|
}
|
|
167
171
|
|
|
@@ -170,11 +174,13 @@ struct CallReportedEnded: CallEvent {
|
|
|
170
174
|
|
|
171
175
|
let id: UUID
|
|
172
176
|
let reason: CXCallEndedReason
|
|
177
|
+
let session: CallSession
|
|
173
178
|
|
|
174
179
|
var body: [String: Any] {
|
|
175
180
|
[
|
|
176
181
|
CallEventKeys.id: id.uuidString,
|
|
177
182
|
"reason": Self.reasonString(for: reason),
|
|
183
|
+
"session": session.toDictionary(),
|
|
178
184
|
]
|
|
179
185
|
}
|
|
180
186
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "expo-callkit-telecom",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
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",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"build": "expo-module build && expo-module build plugin",
|
|
18
|
+
"build:plugin": "expo-module build plugin",
|
|
18
19
|
"clean": "expo-module clean",
|
|
19
20
|
"lint": "expo-module lint",
|
|
20
21
|
"test": "expo-module test",
|
|
@@ -1,3 +1,8 @@
|
|
|
1
1
|
export declare const DEFAULT_INCOMING_CALL_TIMEOUT = 45;
|
|
2
2
|
export declare const DEFAULT_OUTGOING_CALL_TIMEOUT = 60;
|
|
3
3
|
export declare const DEFAULT_FULFILL_ANSWER_CALL_TIMEOUT = 30;
|
|
4
|
+
/**
|
|
5
|
+
* Action for the package-internal call-event broadcast (Android). The
|
|
6
|
+
* `androidEventReceiver` plugin prop registers the manifest receiver for it.
|
|
7
|
+
*/
|
|
8
|
+
export declare const ANDROID_CALL_EVENT_ACTION = "expo.modules.callkittelecom.ACTION_CALL_EVENT";
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DEFAULT_FULFILL_ANSWER_CALL_TIMEOUT = exports.DEFAULT_OUTGOING_CALL_TIMEOUT = exports.DEFAULT_INCOMING_CALL_TIMEOUT = void 0;
|
|
3
|
+
exports.ANDROID_CALL_EVENT_ACTION = exports.DEFAULT_FULFILL_ANSWER_CALL_TIMEOUT = exports.DEFAULT_OUTGOING_CALL_TIMEOUT = exports.DEFAULT_INCOMING_CALL_TIMEOUT = void 0;
|
|
4
4
|
// Default timeout values in seconds
|
|
5
5
|
exports.DEFAULT_INCOMING_CALL_TIMEOUT = 45;
|
|
6
6
|
exports.DEFAULT_OUTGOING_CALL_TIMEOUT = 60;
|
|
7
7
|
exports.DEFAULT_FULFILL_ANSWER_CALL_TIMEOUT = 30;
|
|
8
|
+
/**
|
|
9
|
+
* Action for the package-internal call-event broadcast (Android). The
|
|
10
|
+
* `androidEventReceiver` plugin prop registers the manifest receiver for it.
|
|
11
|
+
*/
|
|
12
|
+
exports.ANDROID_CALL_EVENT_ACTION = "expo.modules.callkittelecom.ACTION_CALL_EVENT";
|
|
@@ -62,6 +62,19 @@ export type ExpoCallKitTelecomPluginProps = {
|
|
|
62
62
|
* @platform android
|
|
63
63
|
*/
|
|
64
64
|
defaultDialtone?: string;
|
|
65
|
+
/**
|
|
66
|
+
* Class name of a `BroadcastReceiver` to register for the module's call-event
|
|
67
|
+
* broadcast, fired when a call event can't reach a live JS observer (e.g. the
|
|
68
|
+
* user declines an incoming call while the app is killed). The plugin writes
|
|
69
|
+
* the manifest `<receiver>` + `<intent-filter>`. You supply the receiver class
|
|
70
|
+
* (e.g. via a local Expo module); the plugin does not generate it.
|
|
71
|
+
*
|
|
72
|
+
* Accepts a manifest-relative name (`.CallEventReceiver`) or a fully-qualified
|
|
73
|
+
* one (`com.acme.app.CallEventReceiver`). See "Call ended while the app is
|
|
74
|
+
* killed" in the docs.
|
|
75
|
+
* @platform android
|
|
76
|
+
*/
|
|
77
|
+
androidEventReceiver?: string;
|
|
65
78
|
};
|
|
66
79
|
declare const _default: ConfigPlugin<void | ExpoCallKitTelecomPluginProps>;
|
|
67
80
|
export default _default;
|
|
@@ -163,6 +163,35 @@ const withFirebaseMessagingService = (config) => {
|
|
|
163
163
|
return config;
|
|
164
164
|
});
|
|
165
165
|
};
|
|
166
|
+
/**
|
|
167
|
+
* Registers a manifest BroadcastReceiver for the module's call-event broadcast.
|
|
168
|
+
*
|
|
169
|
+
* Fired when a call event can't reach a live JS observer (e.g. a killed-app
|
|
170
|
+
* decline). The receiver entry uses {@link ANDROID_CALL_EVENT_ACTION}. The app
|
|
171
|
+
* supplies the receiver class itself (the plugin does not generate it).
|
|
172
|
+
*/
|
|
173
|
+
const withEventReceiver = (config, { androidEventReceiver }) => {
|
|
174
|
+
if (!androidEventReceiver) {
|
|
175
|
+
return config;
|
|
176
|
+
}
|
|
177
|
+
return (0, config_plugins_1.withAndroidManifest)(config, (config) => {
|
|
178
|
+
const app = config_plugins_1.AndroidConfig.Manifest.getMainApplicationOrThrow(config.modResults);
|
|
179
|
+
// Remove any existing entry first (idempotent across repeated prebuilds).
|
|
180
|
+
app.receiver = (app.receiver ?? []).filter((receiver) => receiver.$["android:name"] !== androidEventReceiver);
|
|
181
|
+
app.receiver.push({
|
|
182
|
+
$: {
|
|
183
|
+
"android:name": androidEventReceiver,
|
|
184
|
+
"android:exported": "false",
|
|
185
|
+
},
|
|
186
|
+
"intent-filter": [
|
|
187
|
+
{
|
|
188
|
+
action: [{ $: { "android:name": constants_1.ANDROID_CALL_EVENT_ACTION } }],
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
});
|
|
192
|
+
return config;
|
|
193
|
+
});
|
|
194
|
+
};
|
|
166
195
|
const withExpoCallKitTelecomAndroid = (config, props) => {
|
|
167
196
|
config = withTimeouts(config, props);
|
|
168
197
|
config = withSounds(config, props);
|
|
@@ -172,6 +201,7 @@ const withExpoCallKitTelecomAndroid = (config, props) => {
|
|
|
172
201
|
});
|
|
173
202
|
config = withDefaultDialtone(config, props);
|
|
174
203
|
config = withFirebaseMessagingService(config, props);
|
|
204
|
+
config = withEventReceiver(config, props);
|
|
175
205
|
return config;
|
|
176
206
|
};
|
|
177
207
|
exports.withExpoCallKitTelecomAndroid = withExpoCallKitTelecomAndroid;
|
package/plugin/src/constants.ts
CHANGED
|
@@ -2,3 +2,10 @@
|
|
|
2
2
|
export const DEFAULT_INCOMING_CALL_TIMEOUT = 45;
|
|
3
3
|
export const DEFAULT_OUTGOING_CALL_TIMEOUT = 60;
|
|
4
4
|
export const DEFAULT_FULFILL_ANSWER_CALL_TIMEOUT = 30;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Action for the package-internal call-event broadcast (Android). The
|
|
8
|
+
* `androidEventReceiver` plugin prop registers the manifest receiver for it.
|
|
9
|
+
*/
|
|
10
|
+
export const ANDROID_CALL_EVENT_ACTION =
|
|
11
|
+
"expo.modules.callkittelecom.ACTION_CALL_EVENT";
|
|
@@ -67,6 +67,19 @@ export type ExpoCallKitTelecomPluginProps = {
|
|
|
67
67
|
* @platform android
|
|
68
68
|
*/
|
|
69
69
|
defaultDialtone?: string;
|
|
70
|
+
/**
|
|
71
|
+
* Class name of a `BroadcastReceiver` to register for the module's call-event
|
|
72
|
+
* broadcast, fired when a call event can't reach a live JS observer (e.g. the
|
|
73
|
+
* user declines an incoming call while the app is killed). The plugin writes
|
|
74
|
+
* the manifest `<receiver>` + `<intent-filter>`. You supply the receiver class
|
|
75
|
+
* (e.g. via a local Expo module); the plugin does not generate it.
|
|
76
|
+
*
|
|
77
|
+
* Accepts a manifest-relative name (`.CallEventReceiver`) or a fully-qualified
|
|
78
|
+
* one (`com.acme.app.CallEventReceiver`). See "Call ended while the app is
|
|
79
|
+
* killed" in the docs.
|
|
80
|
+
* @platform android
|
|
81
|
+
*/
|
|
82
|
+
androidEventReceiver?: string;
|
|
70
83
|
};
|
|
71
84
|
|
|
72
85
|
const withExpoCallKitTelecom: ConfigPlugin<ExpoCallKitTelecomPluginProps | void> = (
|
|
@@ -8,6 +8,7 @@ import { copyFileSync, existsSync, mkdirSync } from "fs";
|
|
|
8
8
|
import { basename, resolve } from "path";
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
|
+
ANDROID_CALL_EVENT_ACTION,
|
|
11
12
|
DEFAULT_FULFILL_ANSWER_CALL_TIMEOUT,
|
|
12
13
|
DEFAULT_INCOMING_CALL_TIMEOUT,
|
|
13
14
|
DEFAULT_OUTGOING_CALL_TIMEOUT,
|
|
@@ -277,6 +278,47 @@ const withFirebaseMessagingService: ConfigPlugin<ExpoCallKitTelecomPluginProps>
|
|
|
277
278
|
});
|
|
278
279
|
};
|
|
279
280
|
|
|
281
|
+
/**
|
|
282
|
+
* Registers a manifest BroadcastReceiver for the module's call-event broadcast.
|
|
283
|
+
*
|
|
284
|
+
* Fired when a call event can't reach a live JS observer (e.g. a killed-app
|
|
285
|
+
* decline). The receiver entry uses {@link ANDROID_CALL_EVENT_ACTION}. The app
|
|
286
|
+
* supplies the receiver class itself (the plugin does not generate it).
|
|
287
|
+
*/
|
|
288
|
+
const withEventReceiver: ConfigPlugin<{ androidEventReceiver?: string }> = (
|
|
289
|
+
config,
|
|
290
|
+
{ androidEventReceiver },
|
|
291
|
+
) => {
|
|
292
|
+
if (!androidEventReceiver) {
|
|
293
|
+
return config;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return withAndroidManifest(config, (config) => {
|
|
297
|
+
const app = AndroidConfig.Manifest.getMainApplicationOrThrow(
|
|
298
|
+
config.modResults,
|
|
299
|
+
);
|
|
300
|
+
|
|
301
|
+
// Remove any existing entry first (idempotent across repeated prebuilds).
|
|
302
|
+
app.receiver = (app.receiver ?? []).filter(
|
|
303
|
+
(receiver) => receiver.$["android:name"] !== androidEventReceiver,
|
|
304
|
+
);
|
|
305
|
+
|
|
306
|
+
app.receiver.push({
|
|
307
|
+
$: {
|
|
308
|
+
"android:name": androidEventReceiver,
|
|
309
|
+
"android:exported": "false",
|
|
310
|
+
},
|
|
311
|
+
"intent-filter": [
|
|
312
|
+
{
|
|
313
|
+
action: [{ $: { "android:name": ANDROID_CALL_EVENT_ACTION } }],
|
|
314
|
+
},
|
|
315
|
+
],
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
return config;
|
|
319
|
+
});
|
|
320
|
+
};
|
|
321
|
+
|
|
280
322
|
export const withExpoCallKitTelecomAndroid: ConfigPlugin<ExpoCallKitTelecomPluginProps> = (
|
|
281
323
|
config,
|
|
282
324
|
props,
|
|
@@ -289,5 +331,6 @@ export const withExpoCallKitTelecomAndroid: ConfigPlugin<ExpoCallKitTelecomPlugi
|
|
|
289
331
|
});
|
|
290
332
|
config = withDefaultDialtone(config, props);
|
|
291
333
|
config = withFirebaseMessagingService(config, props);
|
|
334
|
+
config = withEventReceiver(config, props);
|
|
292
335
|
return config;
|
|
293
336
|
};
|
package/src/Calls.types.ts
CHANGED
|
@@ -312,6 +312,22 @@ export interface CallActionEvent extends NativeEvent {
|
|
|
312
312
|
id: string;
|
|
313
313
|
}
|
|
314
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Mixin for events that carry a full {@link CallSession} snapshot alongside the
|
|
317
|
+
* event-specific fields.
|
|
318
|
+
*
|
|
319
|
+
* Terminal events embed the session so consumers without access to the session
|
|
320
|
+
* store still get full context. On Android this is also what makes the
|
|
321
|
+
* package-internal call-event broadcast self-contained when no JS observer is
|
|
322
|
+
* alive (see "Call ended while the app is killed" in the docs) — the broadcast
|
|
323
|
+
* payload is exactly this event body.
|
|
324
|
+
*
|
|
325
|
+
* @category Call Events
|
|
326
|
+
*/
|
|
327
|
+
export interface WithSession {
|
|
328
|
+
session: CallSession;
|
|
329
|
+
}
|
|
330
|
+
|
|
315
331
|
/**
|
|
316
332
|
* Fired after `startOutgoingCall`, once the OS has accepted the call request.
|
|
317
333
|
*
|
|
@@ -371,7 +387,7 @@ export interface CallAnsweredEvent extends CallActionEvent {
|
|
|
371
387
|
*
|
|
372
388
|
* @category Call Events
|
|
373
389
|
*/
|
|
374
|
-
export interface CallEndedEvent extends CallActionEvent {}
|
|
390
|
+
export interface CallEndedEvent extends CallActionEvent, WithSession {}
|
|
375
391
|
|
|
376
392
|
/**
|
|
377
393
|
* Reason a call was ended, reported on {@link CallReportedEnded}.
|
|
@@ -392,7 +408,7 @@ export type CallEndedReason =
|
|
|
392
408
|
*
|
|
393
409
|
* @category Call Events
|
|
394
410
|
*/
|
|
395
|
-
export interface CallReportedEnded extends CallActionEvent {
|
|
411
|
+
export interface CallReportedEnded extends CallActionEvent, WithSession {
|
|
396
412
|
reason: CallEndedReason;
|
|
397
413
|
}
|
|
398
414
|
|