From 4db87992b223fd224b00069a6982caf3858b1b89 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 29 Oct 2021 15:22:23 +1100 Subject: [PATCH] refactor for voip push notification --- Session.xcodeproj/project.pbxproj | 4 - .../Calls/Call Management/SessionCall.swift | 6 +- .../Call Management/SessionCallManager.swift | 12 ++- Session/Calls/CallVC.swift | 2 + .../Views & Modals/IncomingCallBanner.swift | 7 +- .../ConversationVC+Interaction.swift | 2 - Session/Meta/AppDelegate+VoIP.swift | 21 ----- Session/Meta/AppDelegate.m | 2 - Session/Meta/AppDelegate.swift | 2 - .../PushRegistrationManager.swift | 84 +++++++++++++++++-- 10 files changed, 93 insertions(+), 49 deletions(-) delete mode 100644 Session/Meta/AppDelegate+VoIP.swift diff --git a/Session.xcodeproj/project.pbxproj b/Session.xcodeproj/project.pbxproj index db90d6424..cd78b38cf 100644 --- a/Session.xcodeproj/project.pbxproj +++ b/Session.xcodeproj/project.pbxproj @@ -150,7 +150,6 @@ 7BA68909272A27BE00EFC32F /* SessionCall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BA68908272A27BE00EFC32F /* SessionCall.swift */; }; 7BC01A3E241F40AB00BC7C55 /* NotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */; }; 7BC01A42241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; - 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */; }; 7BC707F227290ACB002817AD /* SessionCallManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BC707F127290ACB002817AD /* SessionCallManager.swift */; }; 7BCD116C27016062006330F1 /* WebRTCSession+DataChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */; }; 7BDCFC08242186E700641C39 /* NotificationServiceExtensionContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7BDCFC07242186E700641C39 /* NotificationServiceExtensionContext.swift */; }; @@ -1137,7 +1136,6 @@ 7BC01A3B241F40AB00BC7C55 /* SessionNotificationServiceExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SessionNotificationServiceExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 7BC01A3D241F40AB00BC7C55 /* NotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationServiceExtension.swift; sourceTree = ""; }; 7BC01A3F241F40AB00BC7C55 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AppDelegate+VoIP.swift"; sourceTree = ""; }; 7BC707F127290ACB002817AD /* SessionCallManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionCallManager.swift; sourceTree = ""; }; 7BCD116B27016062006330F1 /* WebRTCSession+DataChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WebRTCSession+DataChannel.swift"; sourceTree = ""; }; 7BDCFC0424206E7300641C39 /* SessionNotificationServiceExtension.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = SessionNotificationServiceExtension.entitlements; sourceTree = ""; }; @@ -3491,7 +3489,6 @@ 76EB03C218170B33006006FC /* AppDelegate.h */, 76EB03C318170B33006006FC /* AppDelegate.m */, C3AAFFF125AE99710089E6DD /* AppDelegate.swift */, - 7BC707E927267973002817AD /* AppDelegate+VoIP.swift */, 34D99CE3217509C1000AFB39 /* AppEnvironment.swift */, B81D260326158DF5004D1FE1 /* Certificates */, B8FF8E6025C10D8B004D1F22 /* Countries */, @@ -4956,7 +4953,6 @@ 4CA46F4C219CCC630038ABDE /* CaptionView.swift in Sources */, C328253025CA55370062D0A7 /* ContextMenuWindow.swift in Sources */, 340FC8B7204DAC8D007AEB0F /* OWSConversationSettingsViewController.m in Sources */, - 7BC707EA27267973002817AD /* AppDelegate+VoIP.swift in Sources */, 7B1581E4271FC59D00848B49 /* CallModal.swift in Sources */, 34BECE2E1F7ABCE000D7438D /* GifPickerViewController.swift in Sources */, B84664F5235022F30083A1CD /* MentionUtilities.swift in Sources */, diff --git a/Session/Calls/Call Management/SessionCall.swift b/Session/Calls/Call Management/SessionCall.swift index 4d00962f9..9292a5667 100644 --- a/Session/Calls/Call Management/SessionCall.swift +++ b/Session/Calls/Call Management/SessionCall.swift @@ -98,13 +98,16 @@ public final class SessionCall: NSObject { func reportIncomingCallIfNeeded() { guard case .offer = mode else { return } AppEnvironment.shared.callManager.reportIncomingCall(self, callerName: contactName) { error in - + if let error = error { + SNLog("[Calls] Failed to report incoming call to CallKit due to error: \(error)") + } } } // MARK: Actions func startSessionCall(completion: (() -> Void)?) { guard case .offer = mode else { return } + AppEnvironment.shared.callManager.reportOutgoingCall(self) Storage.write { transaction in self.webRTCSession.sendPreOffer(to: self.sessionID, using: transaction).done { self.webRTCSession.sendOffer(to: self.sessionID, using: transaction).done { @@ -128,5 +131,6 @@ public final class SessionCall: NSObject { self.webRTCSession.endCall(with: self.sessionID, using: transaction) } hasEnded = true + AppEnvironment.shared.callManager.reportCurrentCallEnded() } } diff --git a/Session/Calls/Call Management/SessionCallManager.swift b/Session/Calls/Call Management/SessionCallManager.swift index 149451fd6..b309ce49a 100644 --- a/Session/Calls/Call Management/SessionCallManager.swift +++ b/Session/Calls/Call Management/SessionCallManager.swift @@ -45,13 +45,15 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { public func providerDidReset(_ provider: CXProvider) { AssertIsOnMainThread() - + currentCall?.endSessionCall() } - public func reportOutgoingCall(_ call: SessionCall, completion: @escaping (Error?) -> Void) { + public func reportOutgoingCall(_ call: SessionCall) { AssertIsOnMainThread() - self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) self.currentCall = call + call.hasStartedConnectingDidChange = { + self.provider.reportOutgoingCall(with: call.uuid, startedConnectingAt: call.connectingDate) + } call.hasConnectedDidChange = { self.provider.reportOutgoingCall(with: call.uuid, connectedAt: call.connectedDate) } @@ -80,6 +82,10 @@ public final class SessionCallManager: NSObject, CXProviderDelegate { } } + public func reportCurrentCallEnded() { + self.currentCall = nil + } + // MARK: Util private func disableUnsupportedFeatures(callUpdate: CXCallUpdate) { // Call Holding is failing to restart audio when "swapping" calls on the CallKit screen diff --git a/Session/Calls/CallVC.swift b/Session/Calls/CallVC.swift index f2ebb0517..cf816f940 100644 --- a/Session/Calls/CallVC.swift +++ b/Session/Calls/CallVC.swift @@ -166,6 +166,8 @@ final class CallVC : UIViewController, WebRTCSessionDelegate, VideoPreviewDelega self.conversationVC?.showInputAccessoryView() self.presentingViewController?.dismiss(animated: true, completion: nil) } + self.modalPresentationStyle = .overFullScreen + self.modalTransitionStyle = .crossDissolve } required init(coder: NSCoder) { preconditionFailure("Use init(for:) instead.") } diff --git a/Session/Calls/Views & Modals/IncomingCallBanner.swift b/Session/Calls/Views & Modals/IncomingCallBanner.swift index 9eff176c3..29f1a0221 100644 --- a/Session/Calls/Views & Modals/IncomingCallBanner.swift +++ b/Session/Calls/Views & Modals/IncomingCallBanner.swift @@ -155,9 +155,8 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { } @objc private func endCall() { - self.call.endSessionCall{ - self.dismiss() - } + self.call.endSessionCall() + self.dismiss() } public func showCallVC(answer: Bool) { @@ -165,8 +164,6 @@ final class IncomingCallBanner: UIView, UIGestureRecognizerDelegate { guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully let callVC = CallVC(for: self.call) callVC.shouldAnswer = answer - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve if let conversationVC = presentingVC as? ConversationVC { callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true diff --git a/Session/Conversations/ConversationVC+Interaction.swift b/Session/Conversations/ConversationVC+Interaction.swift index 3b12eca66..efc0f6ced 100644 --- a/Session/Conversations/ConversationVC+Interaction.swift +++ b/Session/Conversations/ConversationVC+Interaction.swift @@ -34,8 +34,6 @@ extension ConversationVC : InputViewDelegate, MessageCellDelegate, ContextMenuAc let call = SessionCall(for: contactSessionID, uuid: UUID().uuidString, mode: .offer) let callVC = CallVC(for: call) callVC.conversationVC = self - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve self.inputAccessoryView?.isHidden = true self.inputAccessoryView?.alpha = 0 present(callVC, animated: true, completion: nil) diff --git a/Session/Meta/AppDelegate+VoIP.swift b/Session/Meta/AppDelegate+VoIP.swift deleted file mode 100644 index beb7e9757..000000000 --- a/Session/Meta/AppDelegate+VoIP.swift +++ /dev/null @@ -1,21 +0,0 @@ -import PushKit -import SessionUtilitiesKit - -extension AppDelegate: PKPushRegistryDelegate { - - @objc public func registerVoIP() { - let pushRegistry = PKPushRegistry(queue: .main) - pushRegistry.delegate = self - pushRegistry.desiredPushTypes = [.voIP] - } - - public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { - let device = NSData(data: pushCredentials.token) - let deviceId = device.description.replacingOccurrences(of:"<", with:"").replacingOccurrences(of:">", with:"").replacingOccurrences(of:" ", with:"") - SNLog(deviceId) - } - - public func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { - SNLog("Incoming VoIP with payload \(payload)") - } -} diff --git a/Session/Meta/AppDelegate.m b/Session/Meta/AppDelegate.m index 9a0c79914..dfab8a3b3 100644 --- a/Session/Meta/AppDelegate.m +++ b/Session/Meta/AppDelegate.m @@ -149,8 +149,6 @@ static NSTimeInterval launchStartedAt; launchStartedAt = CACurrentMediaTime(); [LKAppModeManager configureWithDelegate:self]; - - [self registerVoIP]; // OWSLinkPreview is now in SessionMessagingKit, so to still be able to deserialize them we // need to tell NSKeyedUnarchiver about the changes. diff --git a/Session/Meta/AppDelegate.swift b/Session/Meta/AppDelegate.swift index 8d4bc29de..70c0976b5 100644 --- a/Session/Meta/AppDelegate.swift +++ b/Session/Meta/AppDelegate.swift @@ -15,8 +15,6 @@ extension AppDelegate { guard let presentingVC = CurrentAppContext().frontmostViewController() else { preconditionFailure() } // TODO: Handle more gracefully if let conversationVC = presentingVC as? ConversationVC, let contactThread = conversationVC.thread as? TSContactThread, contactThread.contactSessionID() == message.sender! { let callVC = CallVC(for: call) - callVC.modalPresentationStyle = .overFullScreen - callVC.modalTransitionStyle = .crossDissolve callVC.conversationVC = conversationVC conversationVC.inputAccessoryView?.isHidden = true conversationVC.inputAccessoryView?.alpha = 0 diff --git a/Session/Notifications/PushRegistrationManager.swift b/Session/Notifications/PushRegistrationManager.swift index fc8b2f600..a2041f069 100644 --- a/Session/Notifications/PushRegistrationManager.swift +++ b/Session/Notifications/PushRegistrationManager.swift @@ -17,7 +17,7 @@ public enum PushRegistrationError: Error { /** * Singleton used to integrate with push notification services - registration and routing received remote notifications. */ -@objc public class PushRegistrationManager: NSObject { +@objc public class PushRegistrationManager: NSObject, PKPushRegistryDelegate { // MARK: - Dependencies @@ -44,21 +44,25 @@ public enum PushRegistrationError: Error { private var vanillaTokenResolver: Resolver? private var voipRegistry: PKPushRegistry? - private var voipTokenPromise: Promise? - private var voipTokenResolver: Resolver? + private var voipTokenPromise: Promise? + private var voipTokenResolver: Resolver? // MARK: Public interface public func requestPushTokens() -> Promise<(pushToken: String, voipToken: String)> { - return firstly { - self.registerUserNotificationSettings() - }.then { () -> Promise<(pushToken: String, voipToken: String)> in + Logger.info("") + + return firstly { () -> Promise in + return self.registerUserNotificationSettings() + }.then { (_) -> Promise<(pushToken: String, voipToken: String)> in guard !Platform.isSimulator else { throw PushRegistrationError.pushNotSupported(description: "Push not supported on simulators") } - - return self.registerForVanillaPushToken().map { vanillaPushToken -> (pushToken: String, voipToken: String) in - return (pushToken: vanillaPushToken, voipToken: "") + + return self.registerForVanillaPushToken().then { vanillaPushToken -> Promise<(pushToken: String, voipToken: String)> in + self.registerForVoipPushToken().map { voipPushToken in + (pushToken: vanillaPushToken, voipToken: voipPushToken ?? "") + } } } } @@ -168,6 +172,68 @@ public enum PushRegistrationError: Error { self.vanillaTokenPromise = nil } } + + private func createVoipRegistryIfNecessary() { + AssertIsOnMainThread() + + guard voipRegistry == nil else { return } + let voipRegistry = PKPushRegistry(queue: nil) + self.voipRegistry = voipRegistry + voipRegistry.desiredPushTypes = [.voIP] + voipRegistry.delegate = self + } + + private func registerForVoipPushToken() -> Promise { + AssertIsOnMainThread() + + guard self.voipTokenPromise == nil else { + let promise = self.voipTokenPromise! + return promise.map { $0?.hexEncodedString } + } + + // No pending voip token yet. Create a new promise + let (promise, resolver) = Promise.pending() + self.voipTokenPromise = promise + self.voipTokenResolver = resolver + + // We don't create the voip registry in init, because it immediately requests the voip token, + // potentially before we're ready to handle it. + createVoipRegistryIfNecessary() + + guard let voipRegistry = self.voipRegistry else { + owsFailDebug("failed to initialize voipRegistry") + resolver.reject(PushRegistrationError.assertionError(description: "failed to initialize voipRegistry")) + return promise.map { _ in + // coerce expected type of returned promise - we don't really care about the value, + // since this promise has been rejected. In practice this shouldn't happen + String() + } + } + + // If we've already completed registering for a voip token, resolve it immediately, + // rather than waiting for the delegate method to be called. + if let voipTokenData = voipRegistry.pushToken(for: .voIP) { + Logger.info("using pre-registered voIP token") + resolver.fulfill(voipTokenData) + } + + return promise.map { (voipTokenData: Data?) -> String? in + Logger.info("successfully registered for voip push notifications") + return voipTokenData?.hexEncodedString + }.ensure { + self.voipTokenPromise = nil + } + } + + // MARK: PKPushRegistryDelegate + public func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { + Logger.info("") + owsAssertDebug(type == .voIP) + owsAssertDebug(pushCredentials.type == .voIP) + guard let voipTokenResolver = voipTokenResolver else { return } + + voipTokenResolver.fulfill(pushCredentials.token) + } } // We transmit pushToken data as hex encoded string to the server