modernization.

Adding json protocol to continue to support legacy (unfortunately)
This commit is contained in:
Scott Pfeil 2022-07-28 18:22:00 -04:00
parent 71b2146862
commit e6dca10d87
31 changed files with 329 additions and 164 deletions

View File

@ -92,6 +92,8 @@
AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; 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, ); }; }; 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 */; }; 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 */; }; AF69D4E9286E54D500BC6862 /* ActionCallHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */; };
AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */; }; AF69D4EB286E586200BC6862 /* ActionRestartHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */; };
AF69D4ED286E5D8C00BC6862 /* ActionCancelHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.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 = "<group>"; }; AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = "<group>"; };
AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = "<group>"; }; AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = "<group>"; };
AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = "<group>"; }; AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = "<group>"; };
AF60A7F1289212CA00919EEB /* MVMError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMError.swift; sourceTree = "<group>"; };
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = "<group>"; };
AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCallHandler.swift; sourceTree = "<group>"; }; AF69D4E8286E54D500BC6862 /* ActionCallHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCallHandler.swift; sourceTree = "<group>"; };
AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRestartHandler.swift; sourceTree = "<group>"; }; AF69D4EA286E586200BC6862 /* ActionRestartHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRestartHandler.swift; sourceTree = "<group>"; };
AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCancelHandler.swift; sourceTree = "<group>"; }; AF69D4EC286E5D8C00BC6862 /* ActionCancelHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionCancelHandler.swift; sourceTree = "<group>"; };
@ -403,6 +407,8 @@
8876D5D41FB50AAB00EB2E3D /* Utility */ = { 8876D5D41FB50AAB00EB2E3D /* Utility */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
AF60A7F1289212CA00919EEB /* MVMError.swift */,
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */,
881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */, 881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */,
881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */, 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */,
881D26921FCC9D180079C521 /* MVMCoreOperation.h */, 881D26921FCC9D180079C521 /* MVMCoreOperation.h */,
@ -921,6 +927,7 @@
30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */,
D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */, D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */,
94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */, 94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */,
AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */,
8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */, 8876D5E91FB50AB000EB2E3D /* NSArray+MFConvenience.m in Sources */,
D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */, D27073B725BB45C4001C7246 /* ActionActionsModel.swift in Sources */,
946EE1B2237B5F260036751F /* JSONValue.swift in Sources */, 946EE1B2237B5F260036751F /* JSONValue.swift in Sources */,
@ -967,6 +974,7 @@
AFBB96611FBA3A570008D868 /* MVMCoreLoadObject.m in Sources */, AFBB96611FBA3A570008D868 /* MVMCoreLoadObject.m in Sources */,
AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */, AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */,
946EE1B4237B619D0036751F /* Encoder.swift in Sources */, 946EE1B4237B619D0036751F /* Encoder.swift in Sources */,
AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */,
AFBB96941FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.m in Sources */, AFBB96941FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.m in Sources */,
94C014D524211AF0005811A9 /* ActionCancelModel.swift in Sources */, 94C014D524211AF0005811A9 /* ActionCancelModel.swift in Sources */,
AF43A5841FBB66DE008E9347 /* MVMCoreConstants.m in Sources */, AF43A5841FBB66DE008E9347 /* MVMCoreConstants.m in Sources */,

View File

@ -8,10 +8,29 @@
import Foundation import Foundation
open class ActionActionsHandler: MVMCoreActionHandlerProtocol { open class ActionActionsHandler: MVMCoreJSONActionHandlerProtocol {
required public init() {} 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 } guard let model = model as? ActionActionsModel else { return }
if model.concurrent { if model.concurrent {
// TODO: inspect warning. // TODO: inspect warning.

View File

@ -8,7 +8,7 @@
import Foundation import Foundation
@objcMembers open class ActionActionsModel: ActionModelProtocol { open class ActionActionsModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -8,23 +8,24 @@
import Foundation import Foundation
open class ActionBackHandler: MVMCoreActionHandlerProtocol { open class ActionBackHandler: MVMCoreJSONActionHandlerProtocol {
required public init() {} 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? ActionBackModel else { return }
if let closure = delegateObject?.actionDelegate?.handleBackAction { if let closure = delegateObject?.actionDelegate?.handleBackAction {
// Legacy code will use the old handler function and break the task chain here. // 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 { } else {
await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in try await performAction(model, delegateObject: delegateObject, additionalData: additionalData)
Task { }
await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: { }
continuation.resume()
}) open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
} await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in
Task(priority: .userInitiated) {
await MVMCoreNavigationHandler.shared()?.removeCurrentViewController(true, completionHandler: {
continuation.resume()
})
} }
} }
} }

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionBackModel: ActionModelProtocol { public struct ActionBackModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -11,7 +11,7 @@ import Foundation
open class ActionCallHandler: MVMCoreActionHandlerProtocol { open class ActionCallHandler: MVMCoreActionHandlerProtocol {
required public init() {} 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 } 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 // 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)") try await ActionOpenUrlHandler.openURL(with: "tel://\(model.callNumber)")

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionCallModel: ActionModelProtocol { public struct ActionCallModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -8,12 +8,13 @@
import Foundation import Foundation
open class ActionCancelHandler: MVMCoreActionHandlerProtocol { open class ActionCancelHandler: MVMCoreJSONActionHandlerProtocol {
required public init() {} 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 { 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)
}
} }
} }

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionCancelModel: ActionModelProtocol { public struct ActionCancelModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -20,7 +20,7 @@ open class ActionContactHandler: NSObject, MVMCoreActionHandlerProtocol, CNConta
private func continueInTask(with closure: @escaping () async -> Void) async { private func continueInTask(with closure: @escaping () async -> Void) async {
let _: Bool = await withCheckedContinuation { continuation in let _: Bool = await withCheckedContinuation { continuation in
self.continuation = continuation self.continuation = continuation
Task { Task(priority: .userInitiated) {
await closure() await closure()
} }
} }

View File

@ -9,7 +9,7 @@
import ContactsUI import ContactsUI
@objcMembers public class ActionContactModel: ActionModelProtocol { public struct ActionContactModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -12,9 +12,6 @@ public protocol ActionDelegateProtocol: MVMCoreActionDelegateProtocol {
/// Allows the delegate to cancel the action. /// Allows the delegate to cancel the action.
func shouldPerform(action: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) -> Bool 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. /// 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 func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool
} }
@ -25,11 +22,6 @@ public extension ActionDelegateProtocol {
return true 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 { func handlesUnknownAction(for model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> Bool {
return false return false
} }

View File

@ -11,5 +11,5 @@ import Foundation
open class ActionNoopHandler: MVMCoreActionHandlerProtocol { open class ActionNoopHandler: MVMCoreActionHandlerProtocol {
required public init() {} 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 {}
} }

View File

@ -6,7 +6,7 @@
// Copyright © 2020 myverizon. All rights reserved. // Copyright © 2020 myverizon. All rights reserved.
// //
@objcMembers public class ActionNoopModel: ActionModelProtocol { public struct ActionNoopModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -8,30 +8,54 @@
import Foundation 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() {} 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 } guard let model = model as? ActionOpenPageModel else { return }
try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in if model.background != true {
var additionalData = additionalData MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading()
}
defer {
if model.background != true {
MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true)
}
}
do {
// Allows the delegate a chance to create and modify request parameters. // Allows the delegate a chance to create and modify request parameters.
var requestParameters: MVMCoreRequestParameters var requestParameters: MVMCoreRequestParameters
if let _ = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.getRequestParameters { var additionalData = additionalData
let value = try (delegateObject!.actionDelegate! as! ActionDelegateProtocol).getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) if let value = try (delegateObject?.actionDelegate as? ActionOpenPageDelegateProtocol)?.getRequestParameters(for: model, delegateObject: delegateObject, additionalData: additionalData) {
requestParameters = value.0 requestParameters = value.0
additionalData = value.1 additionalData = value.1
} else { } else {
requestParameters = MVMCoreRequestParameters(actionMap: json)! requestParameters = MVMCoreRequestParameters(actionMap: JSON)!
} }
if let closure = delegateObject?.actionDelegate?.handleOpenPage { if let closure = delegateObject?.actionDelegate?.handleOpenPage {
// Legacy code will use the old handler function and break the task chain here. // Legacy code will use the old handler function and break the task chain here.
closure(requestParameters, json, additionalData) closure(requestParameters, JSON, additionalData)
} else { } else {
try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) 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. // Makes the request and waits for it.
try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: requestParameters, delegateObject: delegateObject, additionalData: additionalData) 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 { public extension ClientParameterHandler {
@ -63,7 +100,6 @@ public extension ClientParameterHandler {
return try await withCheckedThrowingContinuation({ continuation in return try await withCheckedThrowingContinuation({ continuation in
do { do {
try getParameters(with: model, requestParameters: requestParameters) { parameters in try getParameters(with: model, requestParameters: requestParameters) { parameters in
MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(true)
continuation.resume(returning: parameters) continuation.resume(returning: parameters)
} }
} catch { } catch {

View File

@ -7,24 +7,24 @@
// //
@objcMembers open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol { open class ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, ClientParameterActionProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public class var identifier: String { "openPage" } open class var identifier: String { "openPage" }
public var actionType: String = identifier open var actionType: String = identifier
public var pageType: String open var pageType: String
public var modules: [String]? open var modules: [String]?
public var baseURL: String? open var baseURL: String?
public var appContext: String? open var appContext: String?
public var requestURL: String? open var requestURL: String?
public var extraParameters: JSONValueDictionary? open var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary? open var analyticsData: JSONValueDictionary?
public var presentationStyle: String? open var presentationStyle: String?
public var tabBarIndex: Int? open var tabBarIndex: Int?
public var background: Bool? open var background: Bool?
public var clientParameters: ClientParameterModel? open var clientParameters: ClientParameterModel?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initialzier // MARK: - Initialzier

View File

@ -24,7 +24,7 @@ extension String {
open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol { open class ActionOpenSMSHandler: MVMCoreActionHandlerProtocol {
required public init() {} 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 } 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. // 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) let string = try "sms:\(model.phoneNumber)&body=\(model.message ?? "")".addingPercentEncodingThrowable(withAllowedCharacters: CharacterSet.urlQueryAllowed)

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionOpenSMSModel: ActionModelProtocol { public struct ActionOpenSMSModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -9,7 +9,7 @@
import Foundation import Foundation
public extension URL { public extension URL {
private enum URLError: MVMError, CustomStringConvertible { enum URLError: MVMError, CustomStringConvertible {
case invalid(string: String) case invalid(string: String)
public var description: String { public var description: String {
@ -27,40 +27,8 @@ public extension URL {
return 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 {} open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol {
extension MVMError {
public var errorDescription: String? { return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) }
public static var errorDomain: String {
return ErrorDomainNative
}
}
open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol {
required public init() {} required public init() {}
public enum URLError: MVMError, CustomStringConvertible { 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 { public static func openURL(with string: String) async throws {
let url = try URL.createURL(with: string) let url = try URL.createURL(with: string)
try await ActionOpenUrlHandler.open(url: url) 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 { @MainActor public static func open(url: URL) async throws {
try await withCheckedThrowingContinuation { continuation in try await withCheckedThrowingContinuation { continuation in
UIApplication.shared.open(url, options: [:]) { successful in UIApplication.shared.open(url, options: [:]) { successful in
@ -91,6 +61,10 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol {
} as Void } 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 { open func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws {
guard let model = model as? ActionOpenUrlModel else { return } guard let model = model as? ActionOpenUrlModel else { return }
@ -102,7 +76,7 @@ open class ActionOpenUrlHandler: MVMCoreActionHandlerProtocol {
} catch { } catch {
// Log error and continue // Log error and continue
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)") 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) MVMCoreLoggingHandler.addError(toLog: errorObject)
} }
} }

View File

@ -8,18 +8,18 @@
import Foundation import Foundation
@objcMembers open class ActionOpenUrlModel: ActionModelProtocol { open class ActionOpenUrlModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------
public static var identifier: String = "openURL" open class var identifier: String { "openURL" }
public var actionType: String = ActionOpenUrlModel.identifier open var actionType: String = ActionOpenUrlModel.identifier
public var browserUrl: URL open var browserUrl: URL
public var appURL: URL? open var appURL: URL?
public var appURLOptions: OpenUrlOptionsModel? open var appURLOptions: OpenUrlOptionsModel?
public var extraParameters: JSONValueDictionary? open var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary? open var analyticsData: JSONValueDictionary?
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Initialzier // MARK: - Initialzier

View File

@ -9,10 +9,18 @@
import Foundation import Foundation
/// Makes the previous request, needs the delegate for this /// Makes the previous request, needs the delegate for this
open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol { open class ActionPreviousSubmitHandler: MVMCoreJSONActionHandlerProtocol {
required public init() {} 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, guard let loadObject = (delegateObject?.actionDelegate as? MVMCoreViewControllerProtocol)?.loadObject,
let previousRequest = loadObject?.requestParameters else { return } let previousRequest = loadObject?.requestParameters else { return }
@ -25,7 +33,7 @@ open class ActionPreviousSubmitHandler: MVMCoreActionHandlerProtocol {
if let _ = delegateObject?.actionDelegate?.handleOpenPage { if let _ = delegateObject?.actionDelegate?.handleOpenPage {
// Legacy handling. Will lose the task. // 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) delegateObject?.actionDelegate?.handleOpenPage?(for: previousRequest, actionInformation: json, additionalData: additionalData)
} else { } else {
try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData) try await MVMCoreLoadHandler.sharedGlobal()?.performRequest(with: previousRequest, delegateObject: delegateObject, additionalData: additionalData)

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionPreviousSubmitModel: ActionModelProtocol { public struct ActionPreviousSubmitModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -11,23 +11,23 @@ import Foundation
open class ActionRestartHandler: MVMCoreActionHandlerProtocol { open class ActionRestartHandler: MVMCoreActionHandlerProtocol {
required public init() {} 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 } 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. // Invalidates the session before restarting.
MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in MVMCoreSessionTimeHandler.sharedSession()?.invalidateSession({ error in
if let error = error { if let error = error {
guard error.code != NSURLErrorCancelled else { guard error.code != NSURLErrorCancelled else {
continuation.resume(returning: false) continuation.resume()
return return
} }
continuation.resume(throwing: MVMCoreError.errorObject(error)) continuation.resume(throwing: MVMCoreError.errorObject(error))
} else { } else {
// Restarts the app (forcing any passed in page types). // Restarts the app (forcing any passed in page types).
MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters, clearAllVariables: true) MVMCoreSessionObject.sharedGlobal()?.restartSession(withPageType: model.pageType, parameters: model.extraParameters.toJSON(), clearAllVariables: true)
continuation.resume(returning: true) continuation.resume()
} }
}) })
} }

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionRestartModel: ActionModelProtocol { public struct ActionRestartModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -11,7 +11,7 @@ import Foundation
open class ActionSettingHandler: MVMCoreActionHandlerProtocol { open class ActionSettingHandler: MVMCoreActionHandlerProtocol {
required public init() {} 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) try await ActionOpenUrlHandler.openURL(with: await UIApplication.openSettingsURLString)
} }
} }

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionSettingModel: ActionModelProtocol { public struct ActionSettingModel: ActionModelProtocol {
//-------------------------------------------------- //--------------------------------------------------
// MARK: - Properties // MARK: - Properties
//-------------------------------------------------- //--------------------------------------------------

View File

@ -24,7 +24,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol {
try await shareWith(activityItems: shareData, model: model) 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 try await withCheckedThrowingContinuation { continuation in
let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: nil)
controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view controller.popoverPresentationController?.sourceView = MVMCoreNavigationHandler.shared()?.viewControllerToPresentOn?.view
@ -39,7 +39,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol {
} else if let _ = activityType { } else if let _ = activityType {
// If a specific type of activity failed, the activity controller is still presented, cannot continue yet. // If a specific type of activity failed, the activity controller is still presented, cannot continue yet.
if let error = error, 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) MVMCoreLoggingHandler.addError(toLog: errorObject)
} }
} else if let error = error { } else if let error = error {

View File

@ -7,7 +7,7 @@
// //
@objcMembers public class ActionShareModel: ActionModelProtocol { public struct ActionShareModel: ActionModelProtocol {
public enum SharedType: String, Codable { public enum SharedType: String, Codable {
case text case text

View File

@ -13,9 +13,16 @@ public protocol MVMCoreActionHandlerProtocol: ModelHandlerProtocol {
init() init()
/// Legacy function to handle actions. /// Legacy function to handle actions.
func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?)
func performAction(_ model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws 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 { extension MVMCoreActionHandlerProtocol {
public func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) { public func handleAction(_ model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) {
@ -27,11 +34,8 @@ extension MVMCoreActionHandlerProtocol {
} }
@objc open class MVMCoreActionHandler: NSObject { @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) case unknownAction(type: String)
public var description: String { public var description: String {
@ -104,29 +108,34 @@ extension MVMCoreActionHandlerProtocol {
/// Handle an action with the given model. /// Handle an action with the given model.
open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws {
try Task.checkCancellation() try Task.checkCancellation()
let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData)
// Allow the delegate to intercept. // Allow the delegate to intercept.
guard (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.shouldPerform(action: model, additionalData: additionalData, delegateObject: delegateObject) ?? true else { return } 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 // Log the action
logAction(with: model, additionalData: additionalData, delegateObject: delegateObject) logAction(with: model, additionalData: additionalData, delegateObject: delegateObject)
let uuid = UUID()
do { 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 handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type
let handler = handlerType.init() let handler = handlerType.init()
try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData) try await handler.performAction(model, delegateObject: delegateObject, additionalData: additionalData)
} catch ModelRegistry.Error.handlerNotMapped { } catch ModelRegistry.Error.handlerNotMapped {
try Task.checkCancellation() try Task.checkCancellation()
// Allows custom handling if there no handler for the action. // Allows custom handling if there no handler for the action.
guard try await handleUnregisteredAction(with: model, additionalData: additionalData, delegateObject: delegateObject) else { guard try await handleUnregisteredAction(with: model, json: model.toJSON()!, additionalData: additionalData, delegateObject: delegateObject) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action Unknown \(model.actionType) \(uuid)") MVMCoreActionHandler.log(string: "Failed Action Unknown", additionalData: additionalData)
throw ActionError.unknownAction(type: model.actionType) throw ActionError.unknownAction(type: model.actionType)
} }
} catch { } catch {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Failed Action \(error) \(model.actionType) \(uuid)") MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData)
throw error throw error
} }
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Finished Successful Action \(model.actionType) \(uuid)")
} }
/// Performs the action as a task and returns immediately. /// Performs the action as a task and returns immediately.
@ -134,7 +143,8 @@ extension MVMCoreActionHandlerProtocol {
let task = Task(priority: .userInitiated) { let task = Task(priority: .userInitiated) {
try Task.checkCancellation() try Task.checkCancellation()
do { 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 { } catch {
try Task.checkCancellation() try Task.checkCancellation()
let errorObject = getActionErrorObject(for: error, actionType: model.actionType, delegateObject: delegateObject) 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?) { open func logAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
// Calls legacy log action function. // Calls legacy log action function.
Task { Task {
try await MVMCoreActionHandler.getOriginalJSON(with: model, additionalData: additionalData) { json, additionalData in delegateObject?.actionDelegate?.logAction?(withActionInformation: model.toJSON(), additionalData: additionalData)
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
} }
} }
@ -194,43 +186,118 @@ extension MVMCoreActionHandlerProtocol {
// MARK: - Legacy Holdovers // MARK: - Legacy Holdovers
public static func getOriginalJSON(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, closure: ([AnyHashable: Any], [AnyHashable: Any]?) async throws -> Void) async throws { static public func setUUID(additionalData: [AnyHashable: Any]?) -> [AnyHashable: Any]? {
var additionalData = additionalData var additionalData = additionalData ?? [:]
let json = try additionalData?[MVMCoreActionHandler.originalJSONKey] as? [AnyHashable: Any] ?? MVMCoreActionHandler.convertActionToJSON(model) if additionalData.optionalStringForKey("Action-UUID") == nil {
additionalData?.removeValue(forKey: MVMCoreActionHandler.originalJSONKey) additionalData["Action-UUID"] = UUID().uuidString
try await closure(json, additionalData) }
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. /// Legacy handle action with json.
@objc(handleActionWithDictionary:additionalData:delegateObject:) @objc(handleActionWithDictionary:additionalData:delegateObject:)
open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: json \(String(describing: json))") let additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData)
Task(priority: .userInitiated) { let task = Task(priority: .userInitiated) {
var additionalData = additionalData ?? [:] try Task.checkCancellation()
additionalData[MVMCoreActionHandler.originalJSONKey] = json
do { do {
guard let json = json else { guard let json = json else {
throw ModelRegistry.Error.keyNotFound throw ModelRegistry.Error.keyNotFound
} }
let model = try MVMCoreActionHandler.createModel(with: json, delegateObject: delegateObject) 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 { } catch {
let actionType = json?.optionalStringForKey(KeyActionType)
switch error { switch error {
case ModelRegistry.Error.decoderErrorModelNotMapped: case ModelRegistry.Error.decoderErrorModelNotMapped:
// If the model is not mapped, give the legacy classes a chance to handle it. // 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 { 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) closure(actionType, json, additionalData)
} else { } else {
fallthrough fallthrough
} }
default: default:
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: Error \(error)") MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData)
let errorObject = getActionErrorObject(for: error, actionType: json?.stringForkey(KeyActionType) ?? "noAction", delegateObject: delegateObject) let errorObject = getActionErrorObject(for: error, actionType: actionType ?? "noAction", delegateObject: delegateObject)
handle(errorObject: errorObject, delegateObject: delegateObject, additionalData: additionalData) 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
}
} }
} }

View File

@ -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)
}
}
}

View File

@ -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
}
}