expo-callkit-telecom 0.1.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 (93) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +197 -0
  3. package/android/build.gradle +32 -0
  4. package/android/src/main/AndroidManifest.xml +33 -0
  5. package/android/src/main/java/expo/modules/callkittelecom/ExpoCallKitTelecomModule.kt +384 -0
  6. package/android/src/main/java/expo/modules/callkittelecom/IncomingCallActivity.kt +275 -0
  7. package/android/src/main/java/expo/modules/callkittelecom/events/CallEventEmitter.kt +151 -0
  8. package/android/src/main/java/expo/modules/callkittelecom/events/CallEvents.kt +59 -0
  9. package/android/src/main/java/expo/modules/callkittelecom/managers/CallAudioManager.kt +361 -0
  10. package/android/src/main/java/expo/modules/callkittelecom/managers/CallManager.kt +891 -0
  11. package/android/src/main/java/expo/modules/callkittelecom/managers/CallNotificationManager.kt +445 -0
  12. package/android/src/main/java/expo/modules/callkittelecom/managers/CaptureSessionManager.kt +27 -0
  13. package/android/src/main/java/expo/modules/callkittelecom/managers/DialtonePlayer.kt +171 -0
  14. package/android/src/main/java/expo/modules/callkittelecom/managers/FulfillRequestManager.kt +150 -0
  15. package/android/src/main/java/expo/modules/callkittelecom/managers/VoIPPushManager.kt +54 -0
  16. package/android/src/main/java/expo/modules/callkittelecom/models/CallModels.kt +269 -0
  17. package/android/src/main/java/expo/modules/callkittelecom/services/CallNotificationReceiver.kt +54 -0
  18. package/android/src/main/java/expo/modules/callkittelecom/services/ExpoCallKitTelecomMessagingService.kt +161 -0
  19. package/android/src/main/java/expo/modules/callkittelecom/store/CallStore.kt +181 -0
  20. package/android/src/main/java/expo/modules/callkittelecom/utils/CallKitTelecomLog.kt +52 -0
  21. package/android/src/main/java/expo/modules/callkittelecom/utils/PermissionUtils.kt +28 -0
  22. package/android/src/main/res/drawable/expo_callkit_telecom_bg_answer.xml +9 -0
  23. package/android/src/main/res/drawable/expo_callkit_telecom_bg_avatar.xml +5 -0
  24. package/android/src/main/res/drawable/expo_callkit_telecom_bg_decline.xml +9 -0
  25. package/android/src/main/res/drawable/expo_callkit_telecom_ic_answer.xml +9 -0
  26. package/android/src/main/res/drawable/expo_callkit_telecom_ic_decline.xml +9 -0
  27. package/android/src/main/res/drawable/expo_callkit_telecom_ic_videocam.xml +9 -0
  28. package/android/src/main/res/layout/activity_incoming_call.xml +169 -0
  29. package/app.json +8 -0
  30. package/app.plugin.js +1 -0
  31. package/build/Calls.d.ts +577 -0
  32. package/build/Calls.d.ts.map +1 -0
  33. package/build/Calls.js +715 -0
  34. package/build/Calls.js.map +1 -0
  35. package/build/Calls.types.d.ts +203 -0
  36. package/build/Calls.types.d.ts.map +1 -0
  37. package/build/Calls.types.js +2 -0
  38. package/build/Calls.types.js.map +1 -0
  39. package/build/ExpoCallKitTelecomModule.d.ts +3 -0
  40. package/build/ExpoCallKitTelecomModule.d.ts.map +1 -0
  41. package/build/ExpoCallKitTelecomModule.js +4 -0
  42. package/build/ExpoCallKitTelecomModule.js.map +1 -0
  43. package/build/hooks/index.d.ts +2 -0
  44. package/build/hooks/index.d.ts.map +1 -0
  45. package/build/hooks/index.js +2 -0
  46. package/build/hooks/index.js.map +1 -0
  47. package/build/hooks/useVoIPPushToken.d.ts +14 -0
  48. package/build/hooks/useVoIPPushToken.d.ts.map +1 -0
  49. package/build/hooks/useVoIPPushToken.js +26 -0
  50. package/build/hooks/useVoIPPushToken.js.map +1 -0
  51. package/build/index.d.ts +4 -0
  52. package/build/index.d.ts.map +1 -0
  53. package/build/index.js +4 -0
  54. package/build/index.js.map +1 -0
  55. package/expo-module.config.json +10 -0
  56. package/ios/AppDelegateSubscriber.swift +93 -0
  57. package/ios/ExpoCallKitTelecom.podspec +31 -0
  58. package/ios/ExpoCallKitTelecomLogger.swift +55 -0
  59. package/ios/ExpoCallKitTelecomModule.swift +503 -0
  60. package/ios/Managers/AudioManager.swift +363 -0
  61. package/ios/Managers/CallEventEmitter.swift +199 -0
  62. package/ios/Managers/CallManager+CXProviderDelegate.swift +195 -0
  63. package/ios/Managers/CallManager.swift +714 -0
  64. package/ios/Managers/CaptureSessionManager.swift +54 -0
  65. package/ios/Managers/DialtonePlayer.swift +126 -0
  66. package/ios/Managers/FulfillRequestManager.swift +154 -0
  67. package/ios/Managers/VoIPPushManager+PKPushRegistryDelegate.swift +123 -0
  68. package/ios/Managers/VoIPPushManager.swift +58 -0
  69. package/ios/Models/CallEvents.swift +263 -0
  70. package/ios/Models/CallOptions.swift +15 -0
  71. package/ios/Models/CallParticipant.swift +37 -0
  72. package/ios/Models/CallSession.swift +80 -0
  73. package/ios/Models/IncomingCallEvent.swift +196 -0
  74. package/ios/Stores/CallStore.swift +149 -0
  75. package/package.json +56 -0
  76. package/plugin/build/constants.d.ts +3 -0
  77. package/plugin/build/constants.js +7 -0
  78. package/plugin/build/withExpoCallKitTelecom.d.ts +67 -0
  79. package/plugin/build/withExpoCallKitTelecom.js +16 -0
  80. package/plugin/build/withExpoCallKitTelecomAndroid.d.ts +3 -0
  81. package/plugin/build/withExpoCallKitTelecomAndroid.js +177 -0
  82. package/plugin/build/withExpoCallKitTelecomIos.d.ts +3 -0
  83. package/plugin/build/withExpoCallKitTelecomIos.js +195 -0
  84. package/plugin/src/constants.ts +4 -0
  85. package/plugin/src/withExpoCallKitTelecom.ts +83 -0
  86. package/plugin/src/withExpoCallKitTelecomAndroid.ts +293 -0
  87. package/plugin/src/withExpoCallKitTelecomIos.ts +276 -0
  88. package/src/Calls.ts +848 -0
  89. package/src/Calls.types.ts +275 -0
  90. package/src/ExpoCallKitTelecomModule.ts +4 -0
  91. package/src/hooks/index.ts +1 -0
  92. package/src/hooks/useVoIPPushToken.ts +34 -0
  93. package/src/index.ts +3 -0
@@ -0,0 +1,503 @@
1
+ import CallKit
2
+ import ExpoModulesCore
3
+
4
+ public class ExpoCallKitTelecomModule: Module {
5
+ public func definition() -> ModuleDefinition {
6
+ Name("ExpoCallKitTelecom")
7
+
8
+ // MARK: - Events
9
+
10
+ Events(
11
+ CallSessionAddedEvent.name,
12
+ CallSessionUpdatedEvent.name,
13
+ CallSessionRemovedEvent.name,
14
+ AudioSessionActivatedEvent.name,
15
+ AudioSessionDeactivatedEvent.name,
16
+ AudioRouteChangedEvent.name,
17
+ IncomingCallReportedEvent.name,
18
+ OutgoingCallStartedEvent.name,
19
+ CallAnsweredEvent.name,
20
+ CallEndedEvent.name,
21
+ CallReportedEnded.name,
22
+ SetMutedActionEvent.name,
23
+ VideoChangedEvent.name,
24
+ SetHeldActionEvent.name,
25
+ DTMFEvent.name,
26
+ CallIntentReceivedEvent.name,
27
+ VoIPPushTokenUpdatedEvent.name
28
+ )
29
+
30
+ // MARK: - Lifecycle
31
+
32
+ OnCreate { [weak self] in
33
+ Task { @MainActor in
34
+ CallEventEmitter.shared.module = self
35
+ }
36
+ }
37
+
38
+ // Register per-event observers
39
+ OnStartObserving(CallSessionAddedEvent.name) {
40
+ Task { @MainActor in
41
+ CallEventEmitter.shared.startObserving(
42
+ eventName: CallSessionAddedEvent.name
43
+ )
44
+ }
45
+ }
46
+ OnStopObserving(CallSessionAddedEvent.name) {
47
+ Task { @MainActor in
48
+ CallEventEmitter.shared.stopObserving(
49
+ eventName: CallSessionAddedEvent.name
50
+ )
51
+ }
52
+ }
53
+
54
+ OnStartObserving(CallSessionUpdatedEvent.name) {
55
+ Task { @MainActor in
56
+ CallEventEmitter.shared.startObserving(
57
+ eventName: CallSessionUpdatedEvent.name
58
+ )
59
+ }
60
+ }
61
+ OnStopObserving(CallSessionUpdatedEvent.name) {
62
+ Task { @MainActor in
63
+ CallEventEmitter.shared.stopObserving(
64
+ eventName: CallSessionUpdatedEvent.name
65
+ )
66
+ }
67
+ }
68
+
69
+ OnStartObserving(CallSessionRemovedEvent.name) {
70
+ Task { @MainActor in
71
+ CallEventEmitter.shared.startObserving(
72
+ eventName: CallSessionRemovedEvent.name
73
+ )
74
+ }
75
+ }
76
+ OnStopObserving(CallSessionRemovedEvent.name) {
77
+ Task { @MainActor in
78
+ CallEventEmitter.shared.stopObserving(
79
+ eventName: CallSessionRemovedEvent.name
80
+ )
81
+ }
82
+ }
83
+
84
+ OnStartObserving(AudioSessionActivatedEvent.name) {
85
+ Task { @MainActor in
86
+ CallEventEmitter.shared.startObserving(
87
+ eventName: AudioSessionActivatedEvent.name
88
+ )
89
+ }
90
+ }
91
+ OnStopObserving(AudioSessionActivatedEvent.name) {
92
+ Task { @MainActor in
93
+ CallEventEmitter.shared.stopObserving(
94
+ eventName: AudioSessionActivatedEvent.name
95
+ )
96
+ }
97
+ }
98
+
99
+ OnStartObserving(AudioSessionDeactivatedEvent.name) {
100
+ Task { @MainActor in
101
+ CallEventEmitter.shared.startObserving(
102
+ eventName: AudioSessionDeactivatedEvent.name
103
+ )
104
+ }
105
+ }
106
+ OnStopObserving(AudioSessionDeactivatedEvent.name) {
107
+ Task { @MainActor in
108
+ CallEventEmitter.shared.stopObserving(
109
+ eventName: AudioSessionDeactivatedEvent.name
110
+ )
111
+ }
112
+ }
113
+
114
+ OnStartObserving(AudioRouteChangedEvent.name) {
115
+ Task { @MainActor in
116
+ CallEventEmitter.shared.startObserving(
117
+ eventName: AudioRouteChangedEvent.name
118
+ )
119
+ }
120
+ }
121
+ OnStopObserving(AudioRouteChangedEvent.name) {
122
+ Task { @MainActor in
123
+ CallEventEmitter.shared.stopObserving(
124
+ eventName: AudioRouteChangedEvent.name
125
+ )
126
+ }
127
+ }
128
+
129
+ OnStartObserving(IncomingCallReportedEvent.name) {
130
+ Task { @MainActor in
131
+ CallEventEmitter.shared.startObserving(
132
+ eventName: IncomingCallReportedEvent.name
133
+ )
134
+ }
135
+ }
136
+ OnStopObserving(IncomingCallReportedEvent.name) {
137
+ Task { @MainActor in
138
+ CallEventEmitter.shared.stopObserving(
139
+ eventName: IncomingCallReportedEvent.name
140
+ )
141
+ }
142
+ }
143
+
144
+ OnStartObserving(OutgoingCallStartedEvent.name) {
145
+ Task { @MainActor in
146
+ CallEventEmitter.shared.startObserving(
147
+ eventName: OutgoingCallStartedEvent.name
148
+ )
149
+ }
150
+ }
151
+ OnStopObserving(OutgoingCallStartedEvent.name) {
152
+ Task { @MainActor in
153
+ CallEventEmitter.shared.stopObserving(
154
+ eventName: OutgoingCallStartedEvent.name
155
+ )
156
+ }
157
+ }
158
+
159
+ OnStartObserving(CallAnsweredEvent.name) {
160
+ Task { @MainActor in
161
+ CallEventEmitter.shared.startObserving(
162
+ eventName: CallAnsweredEvent.name
163
+ )
164
+ }
165
+ }
166
+ OnStopObserving(CallAnsweredEvent.name) {
167
+ Task { @MainActor in
168
+ CallEventEmitter.shared.stopObserving(
169
+ eventName: CallAnsweredEvent.name
170
+ )
171
+ }
172
+ }
173
+
174
+ OnStartObserving(CallEndedEvent.name) {
175
+ Task { @MainActor in
176
+ CallEventEmitter.shared.startObserving(
177
+ eventName: CallEndedEvent.name
178
+ )
179
+ }
180
+ }
181
+ OnStopObserving(CallEndedEvent.name) {
182
+ Task { @MainActor in
183
+ CallEventEmitter.shared.stopObserving(
184
+ eventName: CallEndedEvent.name
185
+ )
186
+ }
187
+ }
188
+
189
+ OnStartObserving(CallReportedEnded.name) {
190
+ Task { @MainActor in
191
+ CallEventEmitter.shared.startObserving(
192
+ eventName: CallReportedEnded.name
193
+ )
194
+ }
195
+ }
196
+ OnStopObserving(CallReportedEnded.name) {
197
+ Task { @MainActor in
198
+ CallEventEmitter.shared.stopObserving(
199
+ eventName: CallReportedEnded.name
200
+ )
201
+ }
202
+ }
203
+
204
+ OnStartObserving(SetMutedActionEvent.name) {
205
+ Task { @MainActor in
206
+ CallEventEmitter.shared.startObserving(
207
+ eventName: SetMutedActionEvent.name
208
+ )
209
+ }
210
+ }
211
+ OnStopObserving(SetMutedActionEvent.name) {
212
+ Task { @MainActor in
213
+ CallEventEmitter.shared.stopObserving(
214
+ eventName: SetMutedActionEvent.name
215
+ )
216
+ }
217
+ }
218
+
219
+ OnStartObserving(VideoChangedEvent.name) {
220
+ Task { @MainActor in
221
+ CallEventEmitter.shared.startObserving(
222
+ eventName: VideoChangedEvent.name
223
+ )
224
+ }
225
+ }
226
+ OnStopObserving(VideoChangedEvent.name) {
227
+ Task { @MainActor in
228
+ CallEventEmitter.shared.stopObserving(
229
+ eventName: VideoChangedEvent.name
230
+ )
231
+ }
232
+ }
233
+
234
+ OnStartObserving(SetHeldActionEvent.name) {
235
+ Task { @MainActor in
236
+ CallEventEmitter.shared.startObserving(
237
+ eventName: SetHeldActionEvent.name
238
+ )
239
+ }
240
+ }
241
+ OnStopObserving(SetHeldActionEvent.name) {
242
+ Task { @MainActor in
243
+ CallEventEmitter.shared.stopObserving(
244
+ eventName: SetHeldActionEvent.name
245
+ )
246
+ }
247
+ }
248
+
249
+ OnStartObserving(DTMFEvent.name) {
250
+ Task { @MainActor in
251
+ CallEventEmitter.shared.startObserving(
252
+ eventName: DTMFEvent.name
253
+ )
254
+ }
255
+ }
256
+ OnStopObserving(DTMFEvent.name) {
257
+ Task { @MainActor in
258
+ CallEventEmitter.shared.stopObserving(eventName: DTMFEvent.name)
259
+ }
260
+ }
261
+
262
+ OnStartObserving(CallIntentReceivedEvent.name) {
263
+ Task { @MainActor in
264
+ CallEventEmitter.shared.startObserving(
265
+ eventName: CallIntentReceivedEvent.name
266
+ )
267
+ }
268
+ }
269
+ OnStopObserving(CallIntentReceivedEvent.name) {
270
+ Task { @MainActor in
271
+ CallEventEmitter.shared.stopObserving(
272
+ eventName: CallIntentReceivedEvent.name
273
+ )
274
+ }
275
+ }
276
+
277
+ OnStartObserving(VoIPPushTokenUpdatedEvent.name) {
278
+ Task { @MainActor in
279
+ CallEventEmitter.shared.startObserving(
280
+ eventName: VoIPPushTokenUpdatedEvent.name
281
+ )
282
+ }
283
+ }
284
+ OnStopObserving(VoIPPushTokenUpdatedEvent.name) {
285
+ Task { @MainActor in
286
+ CallEventEmitter.shared.stopObserving(
287
+ eventName: VoIPPushTokenUpdatedEvent.name
288
+ )
289
+ }
290
+ }
291
+
292
+ // MARK: - Call Session
293
+
294
+ AsyncFunction("getActiveCallSession") { () -> [String: Any]? in
295
+ guard let session = await CallManager.shared.store.firstSession
296
+ else {
297
+ return nil
298
+ }
299
+ return session.toDictionary()
300
+ }
301
+
302
+ // MARK: - Audio Session
303
+
304
+ Function("getAudioSessionState") { () -> [String: Any] in
305
+ return AudioManager.shared.getAudioSessionState()
306
+ }
307
+
308
+ Function("setRTCAudioSessionConfiguration") { (hasVideo: Bool) in
309
+ AudioManager.shared.setRTCAudioSessionConfiguration(hasVideo: hasVideo)
310
+ }
311
+
312
+ Function("prepareAudioSessionForCall") { (hasVideo: Bool) in
313
+ AudioManager.shared.prepareAudioSessionForCall(hasVideo: hasVideo)
314
+ }
315
+
316
+ Function("restoreAudioSession") {
317
+ AudioManager.shared.restoreAudioSession()
318
+ }
319
+
320
+ Function("setAudioSessionPortOverride") { (enabled: Bool) in
321
+ AudioManager.shared.setAudioSessionPortOverride(enabled)
322
+ }
323
+
324
+ // MARK: - Capture Session
325
+
326
+ Function("getCaptureSessionState") { () -> [String: Any] in
327
+ return CaptureSessionManager.shared.getCaptureSessionState()
328
+ }
329
+
330
+ // MARK: - Start Outgoing Call
331
+
332
+ AsyncFunction("startOutgoingCall") {
333
+ (recipient: CallParticipantRecord, options: CallOptionsRecord)
334
+ -> String in
335
+ let id = try await CallManager.shared.startOutgoingCall(
336
+ recipient: recipient.toModel(),
337
+ options: options.toModel()
338
+ )
339
+ return id.uuidString
340
+ }
341
+
342
+ // MARK: - Report Incoming Call
343
+
344
+ AsyncFunction("reportIncomingCall") {
345
+ (event: IncomingCallEventRecord) in
346
+ let incomingCallEvent = try event.toModel()
347
+ try await CallManager.shared.reportIncomingCall(
348
+ event: incomingCallEvent
349
+ )
350
+ }
351
+
352
+ // MARK: - Answer Call
353
+
354
+ AsyncFunction("answerCall") { (id: String) in
355
+ guard let uuid = UUID(uuidString: id) else {
356
+ throw Exception(
357
+ name: "InvalidUUID",
358
+ description: "Invalid call ID: \(id)"
359
+ )
360
+ }
361
+
362
+ try await CallManager.shared.answerCall(for: uuid)
363
+ }
364
+
365
+ AsyncFunction("fulfillIncomingCallAnswered") { (requestId: String) in
366
+ guard let requestUUID = UUID(uuidString: requestId) else {
367
+ throw Exception(
368
+ name: "InvalidUUID",
369
+ description: "Invalid request ID: \(requestId)"
370
+ )
371
+ }
372
+
373
+ await CallManager.shared.fulfillIncomingCall(requestId: requestUUID)
374
+ }
375
+
376
+ AsyncFunction("failIncomingCallConnected") { (requestId: String) in
377
+ guard let requestUUID = UUID(uuidString: requestId) else {
378
+ throw Exception(
379
+ name: "InvalidUUID",
380
+ description: "Invalid request ID: \(requestId)"
381
+ )
382
+ }
383
+
384
+ await CallManager.shared.failIncomingCallConnected(requestId: requestUUID)
385
+ }
386
+
387
+ AsyncFunction("reportOutgoingCallConnected") { (id: String) in
388
+ guard let uuid = UUID(uuidString: id) else {
389
+ throw Exception(
390
+ name: "InvalidUUID",
391
+ description: "Invalid call ID: \(id)"
392
+ )
393
+ }
394
+
395
+ await CallManager.shared.reportOutgoingCallConnected(for: uuid)
396
+ }
397
+
398
+ // MARK: - End Call
399
+
400
+ AsyncFunction("endCall") { (id: String) in
401
+ guard let uuid = UUID(uuidString: id) else {
402
+ throw Exception(
403
+ name: "InvalidUUID",
404
+ description: "Invalid call ID: \(id)"
405
+ )
406
+ }
407
+
408
+ try await CallManager.shared.endCall(for: uuid)
409
+ }
410
+
411
+ AsyncFunction("reportCallEnded") { (id: String, reason: String) in
412
+ guard let uuid = UUID(uuidString: id) else {
413
+ throw Exception(
414
+ name: "InvalidUUID",
415
+ description: "Invalid call ID: \(id)"
416
+ )
417
+ }
418
+
419
+ let endedReason: CXCallEndedReason
420
+ switch reason {
421
+ case "failed": endedReason = .failed
422
+ case "remoteEnded": endedReason = .remoteEnded
423
+ case "unanswered": endedReason = .unanswered
424
+ case "answeredElsewhere": endedReason = .answeredElsewhere
425
+ case "declinedElsewhere": endedReason = .declinedElsewhere
426
+ default: endedReason = .failed
427
+ }
428
+
429
+ await CallManager.shared.reportCallEnded(
430
+ for: uuid,
431
+ reason: endedReason
432
+ )
433
+ }
434
+
435
+ // MARK: - Mute Support
436
+
437
+ AsyncFunction("setMuted") { (id: String, muted: Bool) in
438
+ guard let uuid = UUID(uuidString: id) else {
439
+ throw Exception(
440
+ name: "InvalidUUID",
441
+ description: "Invalid call ID: \(id)"
442
+ )
443
+ }
444
+
445
+ try await CallManager.shared.setMuted(for: uuid, muted: muted)
446
+ }
447
+
448
+ // MARK: - Video Support
449
+
450
+ AsyncFunction("reportVideo") { (id: String, enabled: Bool) in
451
+ guard let uuid = UUID(uuidString: id) else {
452
+ throw Exception(
453
+ name: "InvalidUUID",
454
+ description: "Invalid call ID: \(id)"
455
+ )
456
+ }
457
+
458
+ await CallManager.shared.reportVideo(for: uuid, enabled: enabled)
459
+ }
460
+
461
+ // MARK: - Hold Support
462
+
463
+ AsyncFunction("setHeld") { (id: String, onHold: Bool) in
464
+ guard let uuid = UUID(uuidString: id) else {
465
+ throw Exception(
466
+ name: "InvalidUUID",
467
+ description: "Invalid call ID: \(id)"
468
+ )
469
+ }
470
+
471
+ try await CallManager.shared.setHeld(for: uuid, onHold: onHold)
472
+ }
473
+
474
+ // MARK: - DTMF Support
475
+
476
+ AsyncFunction("playDTMF") { (id: String, digits: String) in
477
+ guard let uuid = UUID(uuidString: id) else {
478
+ throw Exception(
479
+ name: "InvalidUUID",
480
+ description: "Invalid call ID: \(id)"
481
+ )
482
+ }
483
+
484
+ try await CallManager.shared.playDTMF(for: uuid, digits: digits)
485
+ }
486
+
487
+ // MARK: - VoIP Push
488
+
489
+ Function("registerVoIPPush") {
490
+ Task { @MainActor in
491
+ VoIPPushManager.shared.register()
492
+ }
493
+ }
494
+
495
+ Function("getVoIPPushToken") { () -> [String: Any?] in
496
+ return [
497
+ "token": VoIPPushManager.shared.token as Any?,
498
+ "type": "APNS_VOIP",
499
+ ]
500
+ }
501
+
502
+ }
503
+ }