@qusaieilouti99/call-manager 0.1.199 → 0.1.201
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/ios/AudioManager.swift
CHANGED
|
@@ -247,4 +247,11 @@ class AudioManager {
|
|
|
247
247
|
return "Earpiece"
|
|
248
248
|
}
|
|
249
249
|
switch output.portType {
|
|
250
|
-
case
|
|
250
|
+
case .bluetoothHFP, .bluetoothA2DP, .bluetoothLE: return "Bluetooth"
|
|
251
|
+
case .builtInSpeaker: return "Speaker"
|
|
252
|
+
case .builtInReceiver: return "Earpiece"
|
|
253
|
+
case .headphones, .headsetMic: return "Headset"
|
|
254
|
+
default: return "Earpiece"
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
package/ios/CallEngine.swift
CHANGED
|
@@ -315,7 +315,13 @@ class CallEngine {
|
|
|
315
315
|
|
|
316
316
|
private func updateOverallIdleTimerDisabledState() {
|
|
317
317
|
let shouldDisable = manualIdleTimerDisabled || hasActiveCalls()
|
|
318
|
+
|
|
318
319
|
DispatchQueue.main.async {
|
|
320
|
+
// Only modify UI if app is in foreground/active state
|
|
321
|
+
guard UIApplication.shared.applicationState == .active else {
|
|
322
|
+
self.logger.info("Skipping idle timer update - app not active")
|
|
323
|
+
return
|
|
324
|
+
}
|
|
319
325
|
UIApplication.shared.isIdleTimerDisabled = shouldDisable
|
|
320
326
|
}
|
|
321
327
|
}
|
package/ios/CallManager.swift
CHANGED
|
@@ -3,6 +3,9 @@ import NitroModules
|
|
|
3
3
|
import OSLog
|
|
4
4
|
import UIKit
|
|
5
5
|
|
|
6
|
+
/// The public-facing bridge class that implements the `HybridCallManagerSpec` protocol.
|
|
7
|
+
/// Its responsibility is to translate calls from the JavaScript layer into commands
|
|
8
|
+
/// for the `CallEngine` singleton. It acts as a thin, safe interface to the native call logic.
|
|
6
9
|
public class CallManager: HybridCallManagerSpec {
|
|
7
10
|
private let logger = Logger(
|
|
8
11
|
subsystem: "com.qusaieilouti99.callmanager",
|
|
@@ -53,6 +56,7 @@ public class CallManager: HybridCallManagerSpec {
|
|
|
53
56
|
self.logger.debug("🎯 event \(event.stringValue), payload.len=\(payload.count)")
|
|
54
57
|
listener(event, payload)
|
|
55
58
|
}
|
|
59
|
+
// Return a closure that will be called by the JS layer to unsubscribe.
|
|
56
60
|
return {
|
|
57
61
|
self.logger.info("🎯 removeListener ▶ js → native")
|
|
58
62
|
CallEngine.shared.setEventHandler(nil)
|
|
@@ -150,10 +154,12 @@ public class CallManager: HybridCallManagerSpec {
|
|
|
150
154
|
}
|
|
151
155
|
|
|
152
156
|
public func requestOverlayPermissionAndroid() throws -> Bool {
|
|
157
|
+
// This is an Android-specific method, so we provide a no-op success case for iOS.
|
|
153
158
|
return true
|
|
154
159
|
}
|
|
155
160
|
|
|
156
161
|
public func hasOverlayPermissionAndroid() throws -> Bool {
|
|
162
|
+
// This is an Android-specific method, so we provide a no-op success case for iOS.
|
|
157
163
|
return true
|
|
158
164
|
}
|
|
159
165
|
}
|
|
@@ -2,7 +2,12 @@ import Foundation
|
|
|
2
2
|
import PushKit
|
|
3
3
|
import OSLog
|
|
4
4
|
|
|
5
|
+
/// Manages the VoIP push token lifecycle using Apple's PushKit framework.
|
|
6
|
+
/// This class is a singleton responsible for registering for VoIP pushes, receiving the token,
|
|
7
|
+
/// and handling incoming push payloads that represent new calls.
|
|
5
8
|
class VoIPTokenManager: NSObject, PKPushRegistryDelegate {
|
|
9
|
+
// MARK: - Singleton and Properties
|
|
10
|
+
|
|
6
11
|
static let shared = VoIPTokenManager()
|
|
7
12
|
private let logger = Logger(
|
|
8
13
|
subsystem: "com.qusaieilouti99.callmanager",
|
|
@@ -14,39 +19,49 @@ class VoIPTokenManager: NSObject, PKPushRegistryDelegate {
|
|
|
14
19
|
|
|
15
20
|
private override init() {
|
|
16
21
|
super.init()
|
|
17
|
-
logger.info("VoIPTokenManager
|
|
22
|
+
logger.info("VoIPTokenManager singleton created.")
|
|
18
23
|
}
|
|
19
24
|
|
|
25
|
+
// MARK: - Public Methods
|
|
26
|
+
|
|
27
|
+
/// Configures the PushKit registry. This should be called once when the app's call engine is initialized.
|
|
20
28
|
func setupPushKit() {
|
|
21
29
|
guard pushRegistry == nil else {
|
|
22
|
-
logger.info("PushKit already
|
|
30
|
+
logger.info("PushKit already set up. Ignoring.")
|
|
23
31
|
return
|
|
24
32
|
}
|
|
25
33
|
pushRegistry = PKPushRegistry(queue: .main)
|
|
26
34
|
pushRegistry?.delegate = self
|
|
27
35
|
pushRegistry?.desiredPushTypes = [.voIP]
|
|
28
|
-
logger.info("PushKit registry configured")
|
|
36
|
+
logger.info("PushKit registry configured.")
|
|
29
37
|
}
|
|
30
38
|
|
|
39
|
+
/// Registers a listener (typically from the JS bridge) to receive the VoIP token.
|
|
31
40
|
func registerTokenListener(_ listener: @escaping (String) -> Void) {
|
|
32
|
-
logger.info("
|
|
41
|
+
logger.info("Registering VoIP token listener.")
|
|
33
42
|
tokenListener = listener
|
|
43
|
+
// If the token has already been received, provide it to the new listener immediately.
|
|
34
44
|
if let t = cachedToken {
|
|
35
|
-
logger.info("
|
|
45
|
+
logger.info("Providing cached VoIP token to new listener.")
|
|
36
46
|
listener(t)
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
|
|
50
|
+
/// Unregisters the token listener.
|
|
40
51
|
func unregisterTokenListener() {
|
|
41
|
-
logger.info("
|
|
52
|
+
logger.info("Unregistering VoIP token listener.")
|
|
42
53
|
tokenListener = nil
|
|
43
54
|
}
|
|
44
55
|
|
|
56
|
+
// MARK: - PKPushRegistryDelegate
|
|
57
|
+
|
|
58
|
+
/// Called by the system when a new VoIP push token is available or has been updated.
|
|
45
59
|
func pushRegistry(
|
|
46
60
|
_ registry: PKPushRegistry,
|
|
47
61
|
didUpdate pushCredentials: PKPushCredentials,
|
|
48
62
|
for type: PKPushType
|
|
49
63
|
) {
|
|
64
|
+
// Convert the token data to a hex string for transmission.
|
|
50
65
|
let token = pushCredentials.token
|
|
51
66
|
.map { String(format: "%02.2hhx", $0) }
|
|
52
67
|
.joined()
|
|
@@ -55,24 +70,42 @@ class VoIPTokenManager: NSObject, PKPushRegistryDelegate {
|
|
|
55
70
|
tokenListener?(token)
|
|
56
71
|
}
|
|
57
72
|
|
|
73
|
+
/// Called by the system when the VoIP push token has been invalidated.
|
|
58
74
|
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
|
|
59
75
|
logger.warning("VoIP token invalidated.")
|
|
60
76
|
cachedToken = nil
|
|
61
77
|
tokenListener?("")
|
|
62
78
|
}
|
|
63
79
|
|
|
64
|
-
|
|
80
|
+
/// This is the entry point for an incoming VoIP call when the app is in the background or killed.
|
|
81
|
+
/// It is CRITICAL that the `completion` handler is called in all code paths to avoid the
|
|
82
|
+
/// system terminating the app.
|
|
65
83
|
func pushRegistry(
|
|
66
84
|
_ registry: PKPushRegistry,
|
|
67
85
|
didReceiveIncomingPushWith payload: PKPushPayload,
|
|
68
86
|
for type: PKPushType,
|
|
69
87
|
completion: @escaping () -> Void
|
|
70
88
|
) {
|
|
71
|
-
|
|
72
89
|
logger.info("🔔 Received VoIP Push")
|
|
73
90
|
|
|
91
|
+
// Initialize if not already initialized
|
|
92
|
+
CallEngine.shared.initialize()
|
|
93
|
+
|
|
94
|
+
// *** THIS IS THE WATCHDOG TIMER ***
|
|
95
|
+
// It creates a failsafe that will call the completion handler after 10 seconds
|
|
96
|
+
// if our main logic hangs or fails silently, preventing the OS from killing the app.
|
|
97
|
+
let completionTimeout = DispatchWorkItem {
|
|
98
|
+
self.logger.error("⚠️ VoIP push handling timed out. Forcing completion.")
|
|
99
|
+
completion()
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
DispatchQueue.main.asyncAfter(deadline: .now() + 10, execute: completionTimeout)
|
|
103
|
+
|
|
104
|
+
// The payload is expected to have a specific structure.
|
|
105
|
+
// Using a key like "custom_payload" or "data" is common.
|
|
74
106
|
guard let info = payload.dictionaryPayload["custom_payload"] as? [AnyHashable: Any] else {
|
|
75
107
|
logger.error("❌ Invalid payload format or missing 'custom_payload' dictionary.")
|
|
108
|
+
completionTimeout.cancel() // Defuse the timer
|
|
76
109
|
completion()
|
|
77
110
|
return
|
|
78
111
|
}
|
|
@@ -85,6 +118,7 @@ class VoIPTokenManager: NSObject, PKPushRegistryDelegate {
|
|
|
85
118
|
.error(
|
|
86
119
|
"❌ Missing required fields in VoIP payload: callId, callType, or name."
|
|
87
120
|
)
|
|
121
|
+
completionTimeout.cancel() // Defuse the timer
|
|
88
122
|
completion()
|
|
89
123
|
return
|
|
90
124
|
}
|
|
@@ -93,7 +127,9 @@ class VoIPTokenManager: NSObject, PKPushRegistryDelegate {
|
|
|
93
127
|
let metadata = info["metadata"] as? String
|
|
94
128
|
|
|
95
129
|
logger.info("📞 Reporting incoming call from VoIP push: \(callId)")
|
|
96
|
-
|
|
130
|
+
|
|
131
|
+
// Delegate the call reporting to the CallEngine.
|
|
132
|
+
// The CallEngine is self-initializing, making this call safe.
|
|
97
133
|
CallEngine.shared.reportIncomingCall(
|
|
98
134
|
callId: callId,
|
|
99
135
|
callType: callType,
|
|
@@ -102,6 +138,8 @@ class VoIPTokenManager: NSObject, PKPushRegistryDelegate {
|
|
|
102
138
|
metadata: metadata
|
|
103
139
|
) { success in
|
|
104
140
|
self.logger.info("📞 CallKit report from VoIP push completed. Success: \(success)")
|
|
141
|
+
// This is the crucial call to satisfy the PushKit watchdog.
|
|
142
|
+
completionTimeout.cancel() // Defuse the timer
|
|
105
143
|
completion()
|
|
106
144
|
}
|
|
107
145
|
}
|