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, ); }; };
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 = "<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>"; };
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>"; };
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>"; };
@ -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 */,

View File

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

View File

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

View File

@ -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<Void, Never>) 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<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
//--------------------------------------------------

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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