@kapsula-chat/capacitor-push-calls 1.0.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/Package.swift +31 -0
- package/README.md +252 -0
- package/android/build.gradle +46 -0
- package/android/src/main/AndroidManifest.xml +31 -0
- package/android/src/main/java/com/capacitor/voipcalls/CallManager.kt +213 -0
- package/android/src/main/java/com/capacitor/voipcalls/CapacitorVoipCallsPlugin.kt +584 -0
- package/android/src/main/java/com/capacitor/voipcalls/PushRouterMessagingService.kt +26 -0
- package/android/src/main/java/com/capacitor/voipcalls/VoipConnection.kt +112 -0
- package/android/src/main/java/com/capacitor/voipcalls/VoipConnectionService.kt +101 -0
- package/dist/esm/definitions.d.ts +279 -0
- package/dist/esm/definitions.d.ts.map +1 -0
- package/dist/esm/definitions.js +1 -0
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +6 -0
- package/dist/esm/web.d.ts +67 -0
- package/dist/esm/web.d.ts.map +1 -0
- package/dist/esm/web.js +81 -0
- package/dist/plugin.cjs.js +96 -0
- package/dist/plugin.cjs.js.map +1 -0
- package/dist/plugin.js +99 -0
- package/dist/plugin.js.map +1 -0
- package/ios/Plugin/CallManager.swift +226 -0
- package/ios/Plugin/CapacitorVoipCallsPlugin.swift +517 -0
- package/ios/Plugin/Plugin.m +31 -0
- package/package.json +95 -0
|
@@ -0,0 +1,517 @@
|
|
|
1
|
+
import AVFoundation
|
|
2
|
+
import CallKit
|
|
3
|
+
import Capacitor
|
|
4
|
+
import Foundation
|
|
5
|
+
import PushKit
|
|
6
|
+
import UIKit
|
|
7
|
+
import UserNotifications
|
|
8
|
+
|
|
9
|
+
@objc(CapacitorVoipCallsPlugin)
|
|
10
|
+
public class CapacitorVoipCallsPlugin: CAPPlugin, CAPBridgedPlugin {
|
|
11
|
+
public let identifier = "CapacitorVoipCallsPlugin"
|
|
12
|
+
public let jsName = "CapacitorPushCalls"
|
|
13
|
+
public let pluginMethods: [CAPPluginMethod] = [
|
|
14
|
+
CAPPluginMethod(name: "register", returnType: CAPPluginReturnPromise),
|
|
15
|
+
CAPPluginMethod(name: "unregister", returnType: CAPPluginReturnPromise),
|
|
16
|
+
CAPPluginMethod(name: "checkPermissions", returnType: CAPPluginReturnPromise),
|
|
17
|
+
CAPPluginMethod(name: "requestPermissions", returnType: CAPPluginReturnPromise),
|
|
18
|
+
CAPPluginMethod(name: "getDeliveredNotifications", returnType: CAPPluginReturnPromise),
|
|
19
|
+
CAPPluginMethod(name: "removeDeliveredNotifications", returnType: CAPPluginReturnPromise),
|
|
20
|
+
CAPPluginMethod(name: "removeAllDeliveredNotifications", returnType: CAPPluginReturnPromise),
|
|
21
|
+
CAPPluginMethod(name: "getBadgeCount", returnType: CAPPluginReturnPromise),
|
|
22
|
+
CAPPluginMethod(name: "getBadgeNumber", returnType: CAPPluginReturnPromise),
|
|
23
|
+
CAPPluginMethod(name: "setBadgeCount", returnType: CAPPluginReturnPromise),
|
|
24
|
+
CAPPluginMethod(name: "setBadgeNumber", returnType: CAPPluginReturnPromise),
|
|
25
|
+
CAPPluginMethod(name: "clearBadgeCount", returnType: CAPPluginReturnPromise),
|
|
26
|
+
CAPPluginMethod(name: "createChannel", returnType: CAPPluginReturnPromise),
|
|
27
|
+
CAPPluginMethod(name: "deleteChannel", returnType: CAPPluginReturnPromise),
|
|
28
|
+
CAPPluginMethod(name: "listChannels", returnType: CAPPluginReturnPromise),
|
|
29
|
+
CAPPluginMethod(name: "registerVoipNotifications", returnType: CAPPluginReturnPromise),
|
|
30
|
+
CAPPluginMethod(name: "startCall", returnType: CAPPluginReturnPromise),
|
|
31
|
+
CAPPluginMethod(name: "endCall", returnType: CAPPluginReturnPromise),
|
|
32
|
+
CAPPluginMethod(name: "answerCall", returnType: CAPPluginReturnPromise),
|
|
33
|
+
CAPPluginMethod(name: "rejectCall", returnType: CAPPluginReturnPromise),
|
|
34
|
+
CAPPluginMethod(name: "setCallOnHold", returnType: CAPPluginReturnPromise),
|
|
35
|
+
CAPPluginMethod(name: "setAudioRoute", returnType: CAPPluginReturnPromise),
|
|
36
|
+
CAPPluginMethod(name: "setMuted", returnType: CAPPluginReturnPromise),
|
|
37
|
+
CAPPluginMethod(name: "handleIncomingCall", returnType: CAPPluginReturnPromise),
|
|
38
|
+
CAPPluginMethod(name: "updateCallStatus", returnType: CAPPluginReturnPromise),
|
|
39
|
+
CAPPluginMethod(name: "isSupported", returnType: CAPPluginReturnPromise),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
private var callManager: CallManager?
|
|
43
|
+
private var voipRegistry: PKPushRegistry?
|
|
44
|
+
|
|
45
|
+
override public func load() {
|
|
46
|
+
callManager = CallManager(plugin: self)
|
|
47
|
+
|
|
48
|
+
NotificationCenter.default.addObserver(
|
|
49
|
+
self,
|
|
50
|
+
selector: #selector(didRegisterForRemoteNotifications(_:)),
|
|
51
|
+
name: .capacitorDidRegisterForRemoteNotifications,
|
|
52
|
+
object: nil
|
|
53
|
+
)
|
|
54
|
+
NotificationCenter.default.addObserver(
|
|
55
|
+
self,
|
|
56
|
+
selector: #selector(didFailToRegisterForRemoteNotifications(_:)),
|
|
57
|
+
name: .capacitorDidFailToRegisterForRemoteNotifications,
|
|
58
|
+
object: nil
|
|
59
|
+
)
|
|
60
|
+
bridge?.notificationRouter.pushNotificationHandler = self
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
deinit {
|
|
64
|
+
NotificationCenter.default.removeObserver(self)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@objc func register(_ call: CAPPluginCall) {
|
|
68
|
+
DispatchQueue.main.async {
|
|
69
|
+
UIApplication.shared.registerForRemoteNotifications()
|
|
70
|
+
call.resolve()
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@objc func unregister(_ call: CAPPluginCall) {
|
|
75
|
+
DispatchQueue.main.async {
|
|
76
|
+
UIApplication.shared.unregisterForRemoteNotifications()
|
|
77
|
+
call.resolve()
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@objc override public func checkPermissions(_ call: CAPPluginCall) {
|
|
82
|
+
UNUserNotificationCenter.current().getNotificationSettings { settings in
|
|
83
|
+
let permission = self.permissionValue(from: settings.authorizationStatus)
|
|
84
|
+
call.resolve(["receive": permission])
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@objc override public func requestPermissions(_ call: CAPPluginCall) {
|
|
89
|
+
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) { _, error in
|
|
90
|
+
if let error {
|
|
91
|
+
call.reject("Failed to request push notification permissions", nil, error)
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
self.checkPermissions(call)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@objc func getDeliveredNotifications(_ call: CAPPluginCall) {
|
|
100
|
+
UNUserNotificationCenter.current().getDeliveredNotifications { notifications in
|
|
101
|
+
let payload = notifications.map { self.serialize(notification: $0) }
|
|
102
|
+
call.resolve(["notifications": payload])
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@objc func removeDeliveredNotifications(_ call: CAPPluginCall) {
|
|
107
|
+
guard let raw = call.options["notifications"] as? [Any] else {
|
|
108
|
+
call.reject("Missing notifications parameter")
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let identifiers: [String] = raw.compactMap {
|
|
113
|
+
if let identifier = $0 as? String {
|
|
114
|
+
return identifier
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if let dict = $0 as? [String: Any] {
|
|
118
|
+
return dict["id"] as? String
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return nil
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
UNUserNotificationCenter.current().removeDeliveredNotifications(withIdentifiers: identifiers)
|
|
125
|
+
call.resolve()
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@objc func removeAllDeliveredNotifications(_ call: CAPPluginCall) {
|
|
129
|
+
UNUserNotificationCenter.current().removeAllDeliveredNotifications()
|
|
130
|
+
call.resolve()
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@objc func getBadgeCount(_ call: CAPPluginCall) {
|
|
134
|
+
DispatchQueue.main.async {
|
|
135
|
+
call.resolve(["count": UIApplication.shared.applicationIconBadgeNumber])
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
@objc func getBadgeNumber(_ call: CAPPluginCall) {
|
|
140
|
+
getBadgeCount(call)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@objc func setBadgeCount(_ call: CAPPluginCall) {
|
|
144
|
+
guard let count = call.getInt("count"), count >= 0 else {
|
|
145
|
+
call.reject("Invalid count parameter")
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
DispatchQueue.main.async {
|
|
150
|
+
UIApplication.shared.applicationIconBadgeNumber = count
|
|
151
|
+
call.resolve()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
@objc func setBadgeNumber(_ call: CAPPluginCall) {
|
|
156
|
+
setBadgeCount(call)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@objc func clearBadgeCount(_ call: CAPPluginCall) {
|
|
160
|
+
DispatchQueue.main.async {
|
|
161
|
+
UIApplication.shared.applicationIconBadgeNumber = 0
|
|
162
|
+
call.resolve()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
@objc func createChannel(_ call: CAPPluginCall) {
|
|
167
|
+
// iOS does not support Android notification channels.
|
|
168
|
+
call.resolve()
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
@objc func deleteChannel(_ call: CAPPluginCall) {
|
|
172
|
+
// iOS does not support Android notification channels.
|
|
173
|
+
call.resolve()
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@objc func listChannels(_ call: CAPPluginCall) {
|
|
177
|
+
// iOS does not support Android notification channels.
|
|
178
|
+
call.resolve(["channels": []])
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
@objc func registerVoipNotifications(_ call: CAPPluginCall) {
|
|
182
|
+
DispatchQueue.main.async {
|
|
183
|
+
self.voipRegistry = PKPushRegistry(queue: DispatchQueue.main)
|
|
184
|
+
self.voipRegistry?.delegate = self
|
|
185
|
+
self.voipRegistry?.desiredPushTypes = [.voIP]
|
|
186
|
+
|
|
187
|
+
call.resolve()
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@objc func startCall(_ call: CAPPluginCall) {
|
|
192
|
+
guard let handle = call.getString("handle"),
|
|
193
|
+
let displayName = call.getString("displayName") else {
|
|
194
|
+
call.reject("Missing required parameters: handle and displayName")
|
|
195
|
+
return
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
let handleType = call.getString("handleType") ?? "generic"
|
|
199
|
+
let video = call.getBool("video") ?? false
|
|
200
|
+
|
|
201
|
+
let callId = UUID()
|
|
202
|
+
let handleTypeEnum: CXHandle.HandleType = {
|
|
203
|
+
switch handleType {
|
|
204
|
+
case "phone": return .phoneNumber
|
|
205
|
+
case "email": return .emailAddress
|
|
206
|
+
default: return .generic
|
|
207
|
+
}
|
|
208
|
+
}()
|
|
209
|
+
|
|
210
|
+
callManager?.startCall(
|
|
211
|
+
uuid: callId,
|
|
212
|
+
handle: handle,
|
|
213
|
+
displayName: displayName,
|
|
214
|
+
handleType: handleTypeEnum,
|
|
215
|
+
video: video
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
call.resolve(["callId": callId.uuidString])
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@objc func endCall(_ call: CAPPluginCall) {
|
|
222
|
+
guard let callIdString = call.getString("callId"),
|
|
223
|
+
let callId = UUID(uuidString: callIdString) else {
|
|
224
|
+
call.reject("Invalid callId")
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
callManager?.endCall(uuid: callId)
|
|
229
|
+
call.resolve()
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
@objc func answerCall(_ call: CAPPluginCall) {
|
|
233
|
+
guard let callIdString = call.getString("callId"),
|
|
234
|
+
let callId = UUID(uuidString: callIdString) else {
|
|
235
|
+
call.reject("Invalid callId")
|
|
236
|
+
return
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
callManager?.answerCall(uuid: callId)
|
|
240
|
+
call.resolve()
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
@objc func rejectCall(_ call: CAPPluginCall) {
|
|
244
|
+
guard let callIdString = call.getString("callId"),
|
|
245
|
+
let callId = UUID(uuidString: callIdString) else {
|
|
246
|
+
call.reject("Invalid callId")
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
callManager?.rejectCall(uuid: callId)
|
|
251
|
+
call.resolve()
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
@objc func setCallOnHold(_ call: CAPPluginCall) {
|
|
255
|
+
guard let callIdString = call.getString("callId"),
|
|
256
|
+
let callId = UUID(uuidString: callIdString),
|
|
257
|
+
let onHold = call.getBool("onHold") else {
|
|
258
|
+
call.reject("Invalid parameters")
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
callManager?.setCallOnHold(uuid: callId, onHold: onHold)
|
|
263
|
+
call.resolve()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
@objc func setAudioRoute(_ call: CAPPluginCall) {
|
|
267
|
+
guard let route = call.getString("route") else {
|
|
268
|
+
call.reject("Missing route parameter")
|
|
269
|
+
return
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
let audioSession = AVAudioSession.sharedInstance()
|
|
273
|
+
|
|
274
|
+
do {
|
|
275
|
+
switch route {
|
|
276
|
+
case "speaker":
|
|
277
|
+
try audioSession.overrideOutputAudioPort(.speaker)
|
|
278
|
+
case "earpiece":
|
|
279
|
+
try audioSession.overrideOutputAudioPort(.none)
|
|
280
|
+
case "bluetooth":
|
|
281
|
+
try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: [.allowBluetooth])
|
|
282
|
+
default:
|
|
283
|
+
call.reject("Invalid route: \(route)")
|
|
284
|
+
return
|
|
285
|
+
}
|
|
286
|
+
call.resolve()
|
|
287
|
+
} catch {
|
|
288
|
+
call.reject("Failed to set audio route: \(error.localizedDescription)")
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
@objc func setMuted(_ call: CAPPluginCall) {
|
|
293
|
+
guard call.getBool("muted") != nil else {
|
|
294
|
+
call.reject("Missing muted parameter")
|
|
295
|
+
return
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Mute should be handled in your media layer.
|
|
299
|
+
call.resolve()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
@objc func handleIncomingCall(_ call: CAPPluginCall) {
|
|
303
|
+
guard let handle = call.getString("handle"),
|
|
304
|
+
let displayName = call.getString("displayName") else {
|
|
305
|
+
call.reject("Missing required parameters: handle and displayName")
|
|
306
|
+
return
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let requestedCallId = call.getString("callId")
|
|
310
|
+
let callId = UUID(uuidString: requestedCallId ?? "") ?? UUID()
|
|
311
|
+
let callIdString = callId.uuidString
|
|
312
|
+
|
|
313
|
+
let handleTypeString = call.getString("handleType") ?? "generic"
|
|
314
|
+
let video = call.getBool("video") ?? false
|
|
315
|
+
let metadata = call.getObject("metadata")
|
|
316
|
+
|
|
317
|
+
let handleType: CXHandle.HandleType = {
|
|
318
|
+
switch handleTypeString {
|
|
319
|
+
case "phone": return .phoneNumber
|
|
320
|
+
case "email": return .emailAddress
|
|
321
|
+
default: return .generic
|
|
322
|
+
}
|
|
323
|
+
}()
|
|
324
|
+
|
|
325
|
+
callManager?.reportIncomingCall(
|
|
326
|
+
uuid: callId,
|
|
327
|
+
handle: handle,
|
|
328
|
+
displayName: displayName,
|
|
329
|
+
handleType: handleType,
|
|
330
|
+
video: video
|
|
331
|
+
) { error in
|
|
332
|
+
if let error {
|
|
333
|
+
call.reject("Failed to report incoming call: \(error.localizedDescription)")
|
|
334
|
+
return
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
var notificationData: [String: Any] = [
|
|
338
|
+
"callId": callIdString,
|
|
339
|
+
"handle": handle,
|
|
340
|
+
"displayName": displayName,
|
|
341
|
+
"handleType": handleTypeString,
|
|
342
|
+
"video": video,
|
|
343
|
+
]
|
|
344
|
+
if let metadata {
|
|
345
|
+
notificationData["metadata"] = metadata
|
|
346
|
+
}
|
|
347
|
+
self.emitEvent("incomingCall", data: notificationData)
|
|
348
|
+
call.resolve(["callId": callIdString])
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
@objc func updateCallStatus(_ call: CAPPluginCall) {
|
|
353
|
+
guard let callIdString = call.getString("callId"),
|
|
354
|
+
let callId = UUID(uuidString: callIdString),
|
|
355
|
+
let status = call.getString("status") else {
|
|
356
|
+
call.reject("Invalid parameters")
|
|
357
|
+
return
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
callManager?.updateCallStatus(uuid: callId, status: status)
|
|
361
|
+
call.resolve()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@objc func isSupported(_ call: CAPPluginCall) {
|
|
365
|
+
call.resolve(["supported": true])
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
@objc private func didRegisterForRemoteNotifications(_ notification: Notification) {
|
|
369
|
+
guard let token = notification.object as? Data else {
|
|
370
|
+
emitEvent("registrationError", data: ["error": "Missing APNS token data"])
|
|
371
|
+
return
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let tokenString = token.map { String(format: "%02x", $0) }.joined()
|
|
375
|
+
emitEvent("registration", data: ["value": tokenString])
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@objc private func didFailToRegisterForRemoteNotifications(_ notification: Notification) {
|
|
379
|
+
let error = (notification.object as? Error)?.localizedDescription ?? "Unknown registration error"
|
|
380
|
+
emitEvent("registrationError", data: ["error": error])
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
private func permissionValue(from status: UNAuthorizationStatus) -> String {
|
|
384
|
+
switch status {
|
|
385
|
+
case .authorized, .provisional, .ephemeral:
|
|
386
|
+
return "granted"
|
|
387
|
+
case .denied:
|
|
388
|
+
return "denied"
|
|
389
|
+
case .notDetermined:
|
|
390
|
+
return "prompt"
|
|
391
|
+
@unknown default:
|
|
392
|
+
return "prompt"
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
private func serialize(notification: UNNotification) -> [String: Any] {
|
|
397
|
+
let content = notification.request.content
|
|
398
|
+
let userInfo = content.userInfo.reduce(into: [String: Any]()) { result, entry in
|
|
399
|
+
result[String(describing: entry.key)] = entry.value
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
var payload: [String: Any] = [
|
|
403
|
+
"id": notification.request.identifier,
|
|
404
|
+
"title": content.title,
|
|
405
|
+
"subtitle": content.subtitle,
|
|
406
|
+
"body": content.body,
|
|
407
|
+
"data": userInfo,
|
|
408
|
+
]
|
|
409
|
+
|
|
410
|
+
if let badge = content.badge as? Int {
|
|
411
|
+
payload["badge"] = badge
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return payload
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
internal func emitEvent(_ event: String, data: [String: Any]) {
|
|
418
|
+
super.notifyListeners(event, data: data)
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Backward-compatible helper used by CallManager.
|
|
422
|
+
internal func notifyListeners(event: String, data: [String: Any]) {
|
|
423
|
+
emitEvent(event, data: data)
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// MARK: - PKPushRegistryDelegate
|
|
428
|
+
extension CapacitorVoipCallsPlugin: PKPushRegistryDelegate {
|
|
429
|
+
public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
|
|
430
|
+
let token = pushCredentials.token.map { String(format: "%02x", $0) }.joined()
|
|
431
|
+
emitEvent("voipPushToken", data: ["token": token])
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) {
|
|
435
|
+
guard type == .voIP else {
|
|
436
|
+
completion()
|
|
437
|
+
return
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
let payloadDict = payload.dictionaryPayload
|
|
441
|
+
|
|
442
|
+
guard let callIdString = payloadDict["callId"] as? String,
|
|
443
|
+
let callId = UUID(uuidString: callIdString),
|
|
444
|
+
let handle = payloadDict["handle"] as? String,
|
|
445
|
+
let displayName = payloadDict["displayName"] as? String else {
|
|
446
|
+
completion()
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let handleTypeString = payloadDict["handleType"] as? String ?? "generic"
|
|
451
|
+
let video = payloadDict["video"] as? Bool ?? false
|
|
452
|
+
|
|
453
|
+
let handleType: CXHandle.HandleType = {
|
|
454
|
+
switch handleTypeString {
|
|
455
|
+
case "phone": return .phoneNumber
|
|
456
|
+
case "email": return .emailAddress
|
|
457
|
+
default: return .generic
|
|
458
|
+
}
|
|
459
|
+
}()
|
|
460
|
+
|
|
461
|
+
callManager?.reportIncomingCall(
|
|
462
|
+
uuid: callId,
|
|
463
|
+
handle: handle,
|
|
464
|
+
displayName: displayName,
|
|
465
|
+
handleType: handleType,
|
|
466
|
+
video: video
|
|
467
|
+
) { error in
|
|
468
|
+
if let error {
|
|
469
|
+
print("Error reporting incoming call: \(error.localizedDescription)")
|
|
470
|
+
}
|
|
471
|
+
completion()
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
var notificationData: [String: Any] = [
|
|
475
|
+
"callId": callIdString,
|
|
476
|
+
"handle": handle,
|
|
477
|
+
"displayName": displayName,
|
|
478
|
+
"handleType": handleTypeString,
|
|
479
|
+
"video": video,
|
|
480
|
+
]
|
|
481
|
+
|
|
482
|
+
if let metadata = payloadDict["metadata"] as? [String: Any] {
|
|
483
|
+
notificationData["metadata"] = metadata
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
emitEvent("incomingCall", data: notificationData)
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
public func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
|
|
490
|
+
print("Push token invalidated for type: \(type)")
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
extension CapacitorVoipCallsPlugin: NotificationHandlerProtocol {
|
|
495
|
+
public func willPresent(notification: UNNotification) -> UNNotificationPresentationOptions {
|
|
496
|
+
emitEvent("pushNotificationReceived", data: serialize(notification: notification))
|
|
497
|
+
|
|
498
|
+
if #available(iOS 14.0, *) {
|
|
499
|
+
return [.banner, .list, .badge, .sound]
|
|
500
|
+
} else {
|
|
501
|
+
return [.alert, .badge, .sound]
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
public func didReceive(response: UNNotificationResponse) {
|
|
506
|
+
var payload: [String: Any] = [
|
|
507
|
+
"actionId": response.actionIdentifier,
|
|
508
|
+
"notification": serialize(notification: response.notification),
|
|
509
|
+
]
|
|
510
|
+
|
|
511
|
+
if let textResponse = response as? UNTextInputNotificationResponse {
|
|
512
|
+
payload["inputValue"] = textResponse.userText
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
emitEvent("pushNotificationActionPerformed", data: payload)
|
|
516
|
+
}
|
|
517
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#import <Foundation/Foundation.h>
|
|
2
|
+
#import <Capacitor/Capacitor.h>
|
|
3
|
+
|
|
4
|
+
CAP_PLUGIN(CapacitorVoipCallsPlugin, "CapacitorPushCalls",
|
|
5
|
+
CAP_PLUGIN_METHOD(register, CAPPluginReturnPromise);
|
|
6
|
+
CAP_PLUGIN_METHOD(unregister, CAPPluginReturnPromise);
|
|
7
|
+
CAP_PLUGIN_METHOD(checkPermissions, CAPPluginReturnPromise);
|
|
8
|
+
CAP_PLUGIN_METHOD(requestPermissions, CAPPluginReturnPromise);
|
|
9
|
+
CAP_PLUGIN_METHOD(getDeliveredNotifications, CAPPluginReturnPromise);
|
|
10
|
+
CAP_PLUGIN_METHOD(removeDeliveredNotifications, CAPPluginReturnPromise);
|
|
11
|
+
CAP_PLUGIN_METHOD(removeAllDeliveredNotifications, CAPPluginReturnPromise);
|
|
12
|
+
CAP_PLUGIN_METHOD(getBadgeCount, CAPPluginReturnPromise);
|
|
13
|
+
CAP_PLUGIN_METHOD(getBadgeNumber, CAPPluginReturnPromise);
|
|
14
|
+
CAP_PLUGIN_METHOD(setBadgeCount, CAPPluginReturnPromise);
|
|
15
|
+
CAP_PLUGIN_METHOD(setBadgeNumber, CAPPluginReturnPromise);
|
|
16
|
+
CAP_PLUGIN_METHOD(clearBadgeCount, CAPPluginReturnPromise);
|
|
17
|
+
CAP_PLUGIN_METHOD(createChannel, CAPPluginReturnPromise);
|
|
18
|
+
CAP_PLUGIN_METHOD(deleteChannel, CAPPluginReturnPromise);
|
|
19
|
+
CAP_PLUGIN_METHOD(listChannels, CAPPluginReturnPromise);
|
|
20
|
+
CAP_PLUGIN_METHOD(registerVoipNotifications, CAPPluginReturnPromise);
|
|
21
|
+
CAP_PLUGIN_METHOD(startCall, CAPPluginReturnPromise);
|
|
22
|
+
CAP_PLUGIN_METHOD(endCall, CAPPluginReturnPromise);
|
|
23
|
+
CAP_PLUGIN_METHOD(answerCall, CAPPluginReturnPromise);
|
|
24
|
+
CAP_PLUGIN_METHOD(rejectCall, CAPPluginReturnPromise);
|
|
25
|
+
CAP_PLUGIN_METHOD(setCallOnHold, CAPPluginReturnPromise);
|
|
26
|
+
CAP_PLUGIN_METHOD(setAudioRoute, CAPPluginReturnPromise);
|
|
27
|
+
CAP_PLUGIN_METHOD(setMuted, CAPPluginReturnPromise);
|
|
28
|
+
CAP_PLUGIN_METHOD(handleIncomingCall, CAPPluginReturnPromise);
|
|
29
|
+
CAP_PLUGIN_METHOD(updateCallStatus, CAPPluginReturnPromise);
|
|
30
|
+
CAP_PLUGIN_METHOD(isSupported, CAPPluginReturnPromise);
|
|
31
|
+
)
|
package/package.json
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@kapsula-chat/capacitor-push-calls",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Capacitor plugin with push routing (message/call), CallKit (iOS), and ConnectionService (Android)",
|
|
5
|
+
"main": "dist/plugin.cjs.js",
|
|
6
|
+
"module": "dist/esm/index.js",
|
|
7
|
+
"types": "dist/esm/index.d.ts",
|
|
8
|
+
"unpkg": "dist/plugin.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"android/src/main/",
|
|
11
|
+
"android/build.gradle",
|
|
12
|
+
"dist/",
|
|
13
|
+
"ios/",
|
|
14
|
+
"Package.swift"
|
|
15
|
+
],
|
|
16
|
+
"author": "",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": ""
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": ""
|
|
27
|
+
},
|
|
28
|
+
"keywords": [
|
|
29
|
+
"capacitor",
|
|
30
|
+
"plugin",
|
|
31
|
+
"native",
|
|
32
|
+
"voip",
|
|
33
|
+
"calls",
|
|
34
|
+
"callkit",
|
|
35
|
+
"pushkit",
|
|
36
|
+
"connectionservice",
|
|
37
|
+
"ios",
|
|
38
|
+
"android",
|
|
39
|
+
"sip",
|
|
40
|
+
"webrtc",
|
|
41
|
+
"phone",
|
|
42
|
+
"telephony",
|
|
43
|
+
"spm"
|
|
44
|
+
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
|
|
47
|
+
"verify:ios": "xcodebuild -scheme CapacitorPushCalls -destination generic/platform=iOS",
|
|
48
|
+
"verify:android": "cd android && ./gradlew clean build test && cd ..",
|
|
49
|
+
"verify:web": "npm run build",
|
|
50
|
+
"lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",
|
|
51
|
+
"fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format",
|
|
52
|
+
"eslint": "eslint . --ext ts",
|
|
53
|
+
"prettier": "prettier \"**/*.{css,html,ts,js,java}\"",
|
|
54
|
+
"swiftlint": "node-swiftlint",
|
|
55
|
+
"docgen": "docgen --api CapacitorPushCallsPlugin --output-readme README.md --output-json dist/docs.json || true",
|
|
56
|
+
"build": "npm run clean && tsc && rollup -c rollup.config.js",
|
|
57
|
+
"build:docs": "npm run build && npm run docgen",
|
|
58
|
+
"clean": "rimraf ./dist",
|
|
59
|
+
"watch": "tsc --watch",
|
|
60
|
+
"prepublishOnly": "npm run build",
|
|
61
|
+
"test:setup": "./scripts/test-plugin.sh",
|
|
62
|
+
"test:update": "./scripts/update-test.sh"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@capacitor/android": "^8.0.0",
|
|
66
|
+
"@capacitor/core": "^8.0.0",
|
|
67
|
+
"@capacitor/docgen": "^0.2.0",
|
|
68
|
+
"@capacitor/ios": "^8.0.0",
|
|
69
|
+
"@ionic/eslint-config": "^0.3.0",
|
|
70
|
+
"@ionic/prettier-config": "^4.0.0",
|
|
71
|
+
"@ionic/swiftlint-config": "^1.1.2",
|
|
72
|
+
"eslint": "^8.0.0",
|
|
73
|
+
"prettier": "^3.0.0",
|
|
74
|
+
"prettier-plugin-java": "^2.0.0",
|
|
75
|
+
"rimraf": "^5.0.0",
|
|
76
|
+
"rollup": "^3.0.0",
|
|
77
|
+
"swiftlint": "^1.0.1",
|
|
78
|
+
"typescript": "~5.0.0"
|
|
79
|
+
},
|
|
80
|
+
"peerDependencies": {
|
|
81
|
+
"@capacitor/core": "^8.0.0"
|
|
82
|
+
},
|
|
83
|
+
"prettier": "@ionic/prettier-config",
|
|
84
|
+
"eslintConfig": {
|
|
85
|
+
"extends": "@ionic/eslint-config/recommended"
|
|
86
|
+
},
|
|
87
|
+
"capacitor": {
|
|
88
|
+
"ios": {
|
|
89
|
+
"src": "ios"
|
|
90
|
+
},
|
|
91
|
+
"android": {
|
|
92
|
+
"src": "android"
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|