From e6dca10d87fd8842a1b90b652c8687dd26038ae5 Mon Sep 17 00:00:00 2001 From: Scott Pfeil Date: Thu, 28 Jul 2022 18:22:00 -0400 Subject: [PATCH] modernization. Adding json protocol to continue to support legacy (unfortunately) --- MVMCore/MVMCore.xcodeproj/project.pbxproj | 8 + .../ActionHandling/ActionActionsHandler.swift | 23 ++- .../ActionHandling/ActionActionsModel.swift | 2 +- .../ActionHandling/ActionBackHandler.swift | 25 +-- .../ActionHandling/ActionBackModel.swift | 2 +- .../ActionHandling/ActionCallHandler.swift | 2 +- .../ActionHandling/ActionCallModel.swift | 2 +- .../ActionHandling/ActionCancelHandler.swift | 9 +- .../ActionHandling/ActionCancelModel.swift | 2 +- .../ActionHandling/ActionContactHandler.swift | 2 +- .../ActionHandling/ActionContactModel.swift | 2 +- .../ActionDelegateProtocol.swift | 8 - .../ActionHandling/ActionNoopHandler.swift | 2 +- .../ActionHandling/ActionNoopModel.swift | 2 +- .../ActionOpenPageHandler.swift | 58 +++++-- .../ActionHandling/ActionOpenPageModel.swift | 28 ++-- .../ActionHandling/ActionOpenSMSHandler.swift | 2 +- .../ActionHandling/ActionOpenSMSModel.swift | 2 +- .../ActionHandling/ActionOpenUrlHandler.swift | 44 +---- .../ActionHandling/ActionOpenUrlModel.swift | 16 +- .../ActionPreviousSubmitHandler.swift | 14 +- .../ActionPreviousSubmitModel.swift | 2 +- .../ActionHandling/ActionRestartHandler.swift | 10 +- .../ActionHandling/ActionRestartModel.swift | 2 +- .../ActionHandling/ActionSettingHandler.swift | 2 +- .../ActionHandling/ActionSettingModel.swift | 2 +- .../ActionHandling/ActionShareHandler.swift | 4 +- .../ActionHandling/ActionShareModel.swift | 2 +- .../ActionHandling/MVMCoreActionHandler.swift | 155 +++++++++++++----- MVMCore/MVMCore/Utility/MVMCoreError.swift | 41 +++++ MVMCore/MVMCore/Utility/MVMError.swift | 18 ++ 31 files changed, 329 insertions(+), 164 deletions(-) create mode 100644 MVMCore/MVMCore/Utility/MVMCoreError.swift create mode 100644 MVMCore/MVMCore/Utility/MVMError.swift diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index a928cbe..d14b68c 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -92,6 +92,8 @@ AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */; }; + AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F1289212CA00919EEB /* MVMError.swift */; }; + AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F3289212EB00919EEB /* MVMCoreError.swift */; }; AF69D4E9286E54D500BC6862 /* ActionCallHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */; }; AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */; }; AF69D4ED286E5D8C00BC6862 /* ActionCancelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */; }; @@ -253,6 +255,8 @@ AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = ""; }; AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = ""; }; AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = ""; }; + AF60A7F1289212CA00919EEB /* MVMError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMError.swift; sourceTree = ""; }; + AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = ""; }; AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCallHandler.swift; sourceTree = ""; }; AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRestartHandler.swift; sourceTree = ""; }; AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCancelHandler.swift; sourceTree = ""; }; @@ -403,6 +407,8 @@ 8876D5D41FB50AAB00EB2E3D /* Utility */ = { isa = PBXGroup; children = ( + AF60A7F1289212CA00919EEB /* MVMError.swift */, + AF60A7F3289212EB00919EEB /* MVMCoreError.swift */, 881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */, 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */, 881D26921FCC9D180079C521 /* MVMCoreOperation.h */, @@ -921,6 +927,7 @@ 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */, 94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */, + AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */, 8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */, D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */, 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */, @@ -967,6 +974,7 @@ AFBB96611FBA3A570008D868 /* MVMCoreLoadObject.m in Sources */, AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */, 946EE1B4237B619D0036751F /* Encoder.swift in Sources */, + AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */, AFBB96941FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.m in Sources */, 94C014D524211AF0005811A9 /* ActionCancelModel.swift in Sources */, AF43A5841FBB66DE008E9347 /* MVMCoreConstants.m in Sources */, diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift index 3ee233a..59095e2 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsHandler.swift @@ -8,10 +8,29 @@ import Foundation -open class ActionActionsHandler: MVMCoreActionHandlerProtocol { +open class ActionActionsHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionActionsModel else { return } + let actions = JSON.arrayForKey("actions") + if model.concurrent { + await withThrowingTaskGroup(of: Void.self) { group in + for case let (index, action as [AnyHashable: Any]) in actions.enumerated() { + group.addTask{ + try await MVMCoreActionHandler.shared()?.handleAction(with: model.actions[index], json: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + } else { + for case let (index, action as [AnyHashable: Any]) in actions.enumerated() { + try Task.checkCancellation() + try await MVMCoreActionHandler.shared()?.handleAction(with: model.actions[index], json: action, additionalData: additionalData, delegateObject: delegateObject) + } + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionActionsModel else { return } if model.concurrent { // TODO: inspect warning. diff --git a/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift index 0638f3e..33444c0 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionActionsModel.swift @@ -8,7 +8,7 @@ import Foundation -@objcMembers open class ActionActionsModel: ActionModelProtocol { +open class ActionActionsModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift index 14cefcb..30f2594 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackHandler.swift @@ -8,23 +8,24 @@ import Foundation -open class ActionBackHandler: MVMCoreActionHandlerProtocol { +open class ActionBackHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - guard let model = model as? ActionBackModel else { return } + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { if let closure = delegateObject?.actionDelegate?.handleBackAction { // Legacy code will use the old handler function and break the task chain here. - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - closure(json, additionalData) - } + closure(JSON, additionalData) } else { - await withCheckedContinuation { (continuation: CheckedContinuation) in - Task { - await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { - continuation.resume() - }) - } + try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + await withCheckedContinuation { (continuation: CheckedContinuation) in + Task(priority: .userInitiated) { + await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { + continuation.resume() + }) } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift b/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift index 3ecdfe4..fea8eea 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionBackModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionBackModel: ActionModelProtocol { +public struct ActionBackModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift index 644bbb1..7010746 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionCallHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionCallModel else { return } // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/PhoneLinks/PhoneLinks.html#//apple_ref/doc/uid/TP40007899-CH6-SW1 try await ActionOpenUrlHandler.openURL(with: "tel://\(model.callNumber)") diff --git a/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift index e5dba2b..dc6a32e 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCallModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionCallModel: ActionModelProtocol { +public struct ActionCallModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift index d8aa646..7f5169c 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelHandler.swift @@ -8,12 +8,13 @@ import Foundation -open class ActionCancelHandler: MVMCoreActionHandlerProtocol { +open class ActionCancelHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + delegateObject?.actionDelegate?.handleCancel?(JSON, additionalData: additionalData) + } + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - delegateObject?.actionDelegate?.handleCancel?(json, additionalData: additionalData) - } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift b/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift index 8a77c11..83ae013 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionCancelModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionCancelModel: ActionModelProtocol { +public struct ActionCancelModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift index 18ac4ec..7231eb2 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactHandler.swift @@ -20,7 +20,7 @@ open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNConta private func continueInTask(with closure: @escaping () async -> Void) async { let _: Bool = await withCheckedContinuation { continuation in self.continuation = continuation - Task { + Task(priority: .userInitiated) { await closure() } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift index 3f4c2f8..9b975fc 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionContactModel.swift @@ -9,7 +9,7 @@ import ContactsUI -@objcMembers public class ActionContactModel: ActionModelProtocol { +public struct ActionContactModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift index 001a2b5..eeb5734 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift @@ -12,9 +12,6 @@ public protocol ActionDelegateProtocol: MVMCoreActionDelegateProtocol { /// Allows the delegate to cancel the action. func shouldPerform(action: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool - /// Allows the delegate to create the request parameters as desired. - func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) - /// Allows the delegate to handle any custom actions that are not registered with the Action Handler. func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool } @@ -25,11 +22,6 @@ public extension ActionDelegateProtocol { return true } - func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable : Any]? = nil) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) { - let json = try MVMCoreActionHandler.convertActionToJSON(model) - return (MVMCoreRequestParameters(actionMap: json)!,additionalData) - } - func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool { return false } diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift index f2608f3..30e5780 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopHandler.swift @@ -11,5 +11,5 @@ import Foundation open class ActionNoopHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {} } diff --git a/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift b/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift index 25f3ded..941e80a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionNoopModel.swift @@ -6,7 +6,7 @@ // Copyright © 2020 myverizon. All rights reserved. // -@objcMembers public class ActionNoopModel: ActionModelProtocol { +public struct ActionNoopModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index a3f00e8..f4f2e98 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -8,30 +8,54 @@ import Foundation -open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { +public protocol ActionOpenPageDelegateProtocol { + /// Allows the delegate to create the request parameters as desired. + func getRequestParameters(for model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) throws -> (MVMCoreRequestParameters,[AnyHashable : Any]?) +} + +open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenPageModel else { return } - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - var additionalData = additionalData - + if model.background != true { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() + } + defer { + if model.background != true { + MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) + } + } + + do { // Allows the delegate a chance to create and modify request parameters. var requestParameters: MVMCoreRequestParameters - if let _ = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.getRequestParameters { - let value = try (delegateObject!.actionDelegate! as! ActionDelegateProtocol).getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) + var additionalData = additionalData + if let value = try (delegateObject?.actionDelegate as? ActionOpenPageDelegateProtocol)?.getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) { requestParameters = value.0 additionalData = value.1 } else { - requestParameters = MVMCoreRequestParameters(actionMap: json)! + requestParameters = MVMCoreRequestParameters(actionMap: JSON)! } - + if let closure = delegateObject?.actionDelegate?.handleOpenPage { // Legacy code will use the old handler function and break the task chain here. - closure(requestParameters, json, additionalData) + closure(requestParameters, JSON, additionalData) } else { try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) } + } catch { + try handle(error: error, model: model, delegateObject: delegateObject) + } + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + guard let model = model as? ActionOpenPageModel else { return } + do { + let json = try MVMCoreActionHandler.convertActionToJSON(model) + try await performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) + } catch { + try handle(error: error, model: model, delegateObject: delegateObject) } } @@ -46,6 +70,19 @@ open class ActionOpenPageHandler: MVMCoreActionHandlerProtocol { // Makes the request and waits for it. try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) } + + /// Ensures background requests do not have showing errors. + private func handle(error: Error, model: ActionOpenPageModel, delegateObject: DelegateObject?) throws { + switch error { + case MVMCoreError.errorObject(let errorObject): + errorObject.silentError = model.background == true + throw MVMCoreError.errorObject(errorObject) + default: + let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType))! + errorObject.silentError = model.background == true + throw MVMCoreError.errorObject(errorObject) + } + } } public extension ClientParameterHandler { @@ -63,7 +100,6 @@ public extension ClientParameterHandler { return try await withCheckedThrowingContinuation({ continuation in do { try getParameters(with: model, requestParameters: requestParameters) { parameters in - MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true) continuation.resume(returning: parameters) } } catch { diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index 3e8620b..9a17245 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -7,24 +7,24 @@ // -@objcMembers open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { +open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public class var identifier: String { "openPage" } - public var actionType: String = identifier - public var pageType: String - public var modules: [String]? - public var baseURL: String? - public var appContext: String? - public var requestURL: String? - public var extraParameters: JSONValueDictionary? - public var analyticsData: JSONValueDictionary? - public var presentationStyle: String? - public var tabBarIndex: Int? - public var background: Bool? - public var clientParameters: ClientParameterModel? + open class var identifier: String { "openPage" } + open var actionType: String = identifier + open var pageType: String + open var modules: [String]? + open var baseURL: String? + open var appContext: String? + open var requestURL: String? + open var extraParameters: JSONValueDictionary? + open var analyticsData: JSONValueDictionary? + open var presentationStyle: String? + open var tabBarIndex: Int? + open var background: Bool? + open var clientParameters: ClientParameterModel? //-------------------------------------------------- // MARK: - Initialzier diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift index 0adbe69..904e712 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSHandler.swift @@ -24,7 +24,7 @@ extension String { open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenSMSModel else { return } // https://developer.apple.com/library/archive/featuredarticles/iPhoneURLScheme_Reference/SMSLinks/SMSLinks.html#:~:text=Note%3A%20SMS%20text%20links%20are,number%20of%20the%20SMS%20message. let string = try "sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncodingThrowable(withAllowedCharacters: CharacterSet.urlQueryAllowed) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift index ead5d68..88f709d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenSMSModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionOpenSMSModel: ActionModelProtocol { +public struct ActionOpenSMSModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 9403bdb..5f8dbf1 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -9,7 +9,7 @@ import Foundation public extension URL { - private enum URLError: MVMError, CustomStringConvertible { + enum URLError: MVMError, CustomStringConvertible { case invalid(string: String) public var description: String { @@ -27,40 +27,8 @@ public extension URL { return url } } - -public enum MVMCoreError: MVMError { - case error(code: Int, messageToDisplay: String, location: String) - case errorObject(_ object: MVMCoreErrorObject) - - public var errorCode: Int { - switch self { - case MVMCoreError.error(let code, _, _): - return code - case MVMCoreError.errorObject(let object): - return object.code - } - } - - public var description: String { - switch self { - case MVMCoreError.error(_, let message, _): - return message - case MVMCoreError.errorObject(let object): - return object.messageToDisplay ?? "Error" - } - } -} -protocol MVMError: LocalizedError, CustomNSError {} -extension MVMError { - public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } - - public static var errorDomain: String { - return ErrorDomainNative - } -} - -open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { +open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} public enum URLError: MVMError, CustomStringConvertible { @@ -74,11 +42,13 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } } + /// Creates a url and calls open(url: URL) public static func openURL(with string: String) async throws { let url = try URL.createURL(with: string) try await ActionOpenUrlHandler.open(url: url) } + /// Opens the url using UIApplication open(url:). Throws URLError.failedToOpen if it fails. @MainActor public static func open(url: URL) async throws { try await withCheckedThrowingContinuation { continuation in UIApplication.shared.open(url, options: [:]) { successful in @@ -91,6 +61,10 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } as Void } + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenUrlModel else { return } @@ -102,7 +76,7 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol { } catch { // Log error and continue MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") - if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { + if let errorObject = MVMCoreActionHandler.shared()?.getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) { MVMCoreLoggingHandler.addError(toLog: errorObject) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift index 9133883..c9f6f8a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlModel.swift @@ -8,18 +8,18 @@ import Foundation -@objcMembers open class ActionOpenUrlModel: ActionModelProtocol { +open class ActionOpenUrlModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- - public static var identifier: String = "openURL" - public var actionType: String = ActionOpenUrlModel.identifier - public var browserUrl: URL - public var appURL: URL? - public var appURLOptions: OpenUrlOptionsModel? - public var extraParameters: JSONValueDictionary? - public var analyticsData: JSONValueDictionary? + open class var identifier: String { "openURL" } + open var actionType: String = ActionOpenUrlModel.identifier + open var browserUrl: URL + open var appURL: URL? + open var appURLOptions: OpenUrlOptionsModel? + open var extraParameters: JSONValueDictionary? + open var analyticsData: JSONValueDictionary? //-------------------------------------------------- // MARK: - Initialzier diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift index 1717446..4a56e7a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitHandler.swift @@ -9,10 +9,18 @@ import Foundation /// Makes the previous request, needs the delegate for this -open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { +open class ActionPreviousSubmitHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + private var json: [AnyHashable: Any]? + + // Conform to MVMCoreJSONActionHandlerProtocol To allow for legacy handleOpenPage delegate + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + json = JSON + try await performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let loadObject = (delegateObject?.actionDelegate as? MVMCoreViewControllerProtocol)?.loadObject, let previousRequest = loadObject?.requestParameters else { return } @@ -25,7 +33,7 @@ open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { if let _ = delegateObject?.actionDelegate?.handleOpenPage { // Legacy handling. Will lose the task. - let json = try MVMCoreActionHandler.convertActionToJSON(model) + let json = try json ?? MVMCoreActionHandler.convertActionToJSON(model) delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData) } else { try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData) diff --git a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift index 506f056..31290dc 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionPreviousSubmitModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionPreviousSubmitModel: ActionModelProtocol { +public struct ActionPreviousSubmitModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift index e8bbd7b..d2744b6 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -11,23 +11,23 @@ import Foundation open class ActionRestartHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionRestartModel else { return } - let _: Bool = try await withCheckedThrowingContinuation { continuation in + let _: Void = try await withCheckedThrowingContinuation { continuation in // Invalidates the session before restarting. MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in if let error = error { guard error.code != NSURLErrorCancelled else { - continuation.resume(returning: false) + continuation.resume() return } continuation.resume(throwing: MVMCoreError.errorObject(error)) } else { // Restarts the app (forcing any passed in page types). - MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters, clearAllVariables: true) - continuation.resume(returning: true) + MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters.toJSON(), clearAllVariables: true) + continuation.resume() } }) } diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift index da785ac..7cc2d26 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionRestartModel: ActionModelProtocol { +public struct ActionRestartModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift index bea0fd1..e1c718d 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingHandler.swift @@ -11,7 +11,7 @@ import Foundation open class ActionSettingHandler: MVMCoreActionHandlerProtocol { required public init() {} - public func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { + open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString) } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift b/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift index 96e95d3..b2fd687 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionSettingModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionSettingModel: ActionModelProtocol { +public struct ActionSettingModel: ActionModelProtocol { //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index 7573434..77dbebf 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -24,7 +24,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { try await shareWith(activityItems: shareData, model: model) } - @MainActor public func shareWith(activityItems: [Any], model: ActionShareModel) async throws { + @MainActor open func shareWith(activityItems: [Any], model: ActionShareModel, delegateObject: DelegateObject? = nil) async throws { try await withCheckedThrowingContinuation { continuation in let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view @@ -39,7 +39,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { } else if let _ = activityType { // If a specific type of activity failed, the activity controller is still presented, cannot continue yet. if let error = error, - let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: #function) { + let errorObject = MVMCoreActionHandler.shared()?.getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) { MVMCoreLoggingHandler.addError(toLog: errorObject) } } else if let error = error { diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift index ed7cbea..2203a8c 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift @@ -7,7 +7,7 @@ // -@objcMembers public class ActionShareModel: ActionModelProtocol { +public struct ActionShareModel: ActionModelProtocol { public enum SharedType: String, Codable { case text diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index d1eeb17..9ecd2fd 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -13,9 +13,16 @@ public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol { init() /// Legacy function to handle actions. func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) + func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws } +/// Protocol used to bridge legacy, non model based code. Allows us to keep the original json intact and not lose key values during decode/encode. +public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { + /// Perform the function using the original json and model. + func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws +} + extension MVMCoreActionHandlerProtocol { public func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { @@ -27,11 +34,8 @@ extension MVMCoreActionHandlerProtocol { } @objc open class MVMCoreActionHandler: NSObject { - - /// The key used to pass along the json for the legacy handlers in additionalData - public static let originalJSONKey: String = "ORIGINAL_JSON" - enum ActionError: MVMError { + enum ActionError: MVMError, CustomStringConvertible { case unknownAction(type: String) public var description: String { @@ -104,29 +108,34 @@ extension MVMCoreActionHandlerProtocol { /// Handle an action with the given model. open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() + + let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + // Allow the delegate to intercept. guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { return } + defer { + MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) + } + // Log the action logAction(with: model, additionalData: additionalData, delegateObject: delegateObject) - let uuid = UUID() do { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Begin Action \(model.actionType) \(uuid)") + MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type let handler = handlerType.init() try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData) } catch ModelRegistry.Error.handlerNotMapped { try Task.checkCancellation() // Allows custom handling if there no handler for the action. - guard try await handleUnregisteredAction(with: model, additionalData: additionalData, delegateObject: delegateObject) else { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action Unknown \(model.actionType) \(uuid)") + guard try await handleUnregisteredAction(with: model, json: model.toJSON()!, additionalData: additionalData, delegateObject: delegateObject) else { + MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData) throw ActionError.unknownAction(type: model.actionType) } } catch { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action \(error) \(model.actionType) \(uuid)") + MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData) throw error } - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Successful Action \(model.actionType) \(uuid)") } /// Performs the action as a task and returns immediately. @@ -134,7 +143,8 @@ extension MVMCoreActionHandlerProtocol { let task = Task(priority: .userInitiated) { try Task.checkCancellation() do { - try await handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + let json = try MVMCoreActionHandler.convertActionToJSON(model) + try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) } catch { try Task.checkCancellation() let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) @@ -164,25 +174,7 @@ extension MVMCoreActionHandlerProtocol { open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { // Calls legacy log action function. Task { - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) - } - } - } - - /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function - open func handleUnregisteredAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { - // Check if the delegate handles the action. - if try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.handlesUnknownAction(for: model, delegateObject: delegateObject, additionalData: additionalData) == true { - return true - } else if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { - // Check if the legacy delegate handles the action. - try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in - closure(model.actionType, json, additionalData) - } - return true - } else { - return false + delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData) } } @@ -194,43 +186,118 @@ extension MVMCoreActionHandlerProtocol { // MARK: - Legacy Holdovers - public static func getOriginalJSON(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, closure: ([AnyHashable: Any], [AnyHashable: Any]?) async throws -> Void) async throws { - var additionalData = additionalData - let json = try additionalData?[MVMCoreActionHandler.originalJSONKey] as? [AnyHashable: Any] ?? MVMCoreActionHandler.convertActionToJSON(model) - additionalData?.removeValue(forKey: MVMCoreActionHandler.originalJSONKey) - try await closure(json, additionalData) + static public func setUUID(additionalData: [AnyHashable: Any]?) -> [AnyHashable: Any]? { + var additionalData = additionalData ?? [:] + if additionalData.optionalStringForKey("Action-UUID") == nil { + additionalData["Action-UUID"] = UUID().uuidString + } + return additionalData + } + + static public func getUUID(additionalData: [AnyHashable: Any]?) -> String? { + return additionalData?.optionalStringForKey("Action-UUID") + } + + static public func log(string: String, additionalData: [AnyHashable: Any]?) { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: UUID: \(String(describing: getUUID(additionalData: additionalData))), \(string)") } /// Legacy handle action with json. @objc(handleActionWithDictionary:additionalData:delegateObject:) open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: json \(String(describing: json))") - Task(priority: .userInitiated) { - var additionalData = additionalData ?? [:] - additionalData[MVMCoreActionHandler.originalJSONKey] = json + let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + let task = Task(priority: .userInitiated) { + try Task.checkCancellation() do { guard let json = json else { throw ModelRegistry.Error.keyNotFound } let model = try MVMCoreActionHandler.createModel(with: json, delegateObject: delegateObject) - _ = asyncHandleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + try await handleAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) } catch { + let actionType = json?.optionalStringForKey(KeyActionType) switch error { case ModelRegistry.Error.decoderErrorModelNotMapped: // If the model is not mapped, give the legacy classes a chance to handle it. - let actionType = json?.optionalStringForKey(KeyActionType) if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { - additionalData.removeValue(forKey: MVMCoreActionHandler.originalJSONKey) + MVMCoreActionHandler.log(string: "Unknown handled (Model not registered): \(String(describing: actionType)) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) closure(actionType, json, additionalData) } else { fallthrough } default: - MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Error \(error)") - let errorObject = getActionErrorObject(for: error, actionType: json?.stringForkey(KeyActionType) ?? "noAction", delegateObject: delegateObject) + MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) + let errorObject = getActionErrorObject(for: error, actionType: actionType ?? "noAction", delegateObject: delegateObject) handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) } } } + Task { + let result = await task.result + do { + try result.get() + print("ActionHandler: task done") + } catch { + print("ActionHandler: \(error)") + } + } + } + + /// Bridges the legacy json using functions and the new model using functions. + open func handleAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { + try Task.checkCancellation() + let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + + MVMCoreActionHandler.log(string: "Begin Action: type: \(model.actionType) json: \(String(describing: json))", additionalData: additionalData) + defer { + MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) + } + + // Allow the delegate to intercept. + guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { + MVMCoreActionHandler.log(string: "Action should not be performed: \(model.actionType)", additionalData: additionalData) + return + } + try Task.checkCancellation() + + // Log the action + delegateObject?.actionDelegate?.logAction?(withActionInformation: json, additionalData: additionalData) + + do { + MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) + let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type + let handler = handlerType.init() + if let handler = handler as? MVMCoreJSONActionHandlerProtocol { + try await handler.performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) + } else { + try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData) + } + } catch ModelRegistry.Error.handlerNotMapped { + try Task.checkCancellation() + // Allows custom handling if there no handler for the action. + guard try await handleUnregisteredAction(with: model, json: json, additionalData: additionalData, delegateObject: delegateObject) else { + MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData) + throw ActionError.unknownAction(type: model.actionType) + } + } catch { + MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData) + throw error + } + } + + /// Subclass to handle and any actions where a handler was not registered. Checks with the delegate handlesUnknownAction function + open func handleUnregisteredAction(with model: ActionModelProtocol, json: [AnyHashable: Any], additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws -> Bool { + // Check if the delegate handles the action. + if try await (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.handlesUnknownAction(for: model, delegateObject: delegateObject, additionalData: additionalData) == true { + MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(model.actionType) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) + return true + } else if let closure = delegateObject?.actionDelegate?.handleUnknownActionType { + MVMCoreActionHandler.log(string: "Unknown handled (Handler not registered): \(model.actionType) \(String(describing: delegateObject?.actionDelegate))", additionalData: additionalData) + // Check if the legacy delegate handles the action. + closure(model.actionType, json, additionalData) + return true + } else { + return false + } } } diff --git a/MVMCore/MVMCore/Utility/MVMCoreError.swift b/MVMCore/MVMCore/Utility/MVMCoreError.swift new file mode 100644 index 0000000..61c1034 --- /dev/null +++ b/MVMCore/MVMCore/Utility/MVMCoreError.swift @@ -0,0 +1,41 @@ +// +// MVMCoreError.swift +// MVMCore +// +// Created by Scott Pfeil on 7/27/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +public enum MVMCoreError: MVMError, CustomStringConvertible { + case error(code: Int, messageToDisplay: String? = nil, messageToLog: String) + case errorObject(_ object: MVMCoreErrorObject) + + public var errorCode: Int { + switch self { + case MVMCoreError.error(let code, _, _): + return code + case MVMCoreError.errorObject(let object): + return object.code + } + } + + public var description: String { + switch self { + case MVMCoreError.error(_, _, let messageToLog): + return messageToLog + case MVMCoreError.errorObject(let object): + return object.messageToLog ?? "Error" + } + } + + public var errorDescription: String? { + switch self { + case MVMCoreError.error(_, let message, _): + return message ?? MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) + case MVMCoreError.errorObject(let object): + return object.messageToDisplay ?? MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) + } + } +} diff --git a/MVMCore/MVMCore/Utility/MVMError.swift b/MVMCore/MVMCore/Utility/MVMError.swift new file mode 100644 index 0000000..ee1662b --- /dev/null +++ b/MVMCore/MVMCore/Utility/MVMError.swift @@ -0,0 +1,18 @@ +// +// MVMError.swift +// MVMCore +// +// Created by Scott Pfeil on 7/27/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +protocol MVMError: LocalizedError, CustomNSError {} +extension MVMError { + public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) } + + public static var errorDomain: String { + return ErrorDomainNative + } +}