diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index 989971a..2f79085 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -11,9 +11,19 @@ import Foundation open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { required public init() {} + func requestParamaters(for model: ActionOpenPageModel) -> MVMCoreRequestParameters { + let requestParameters = model.requestParameters.copy() as! MVMCoreRequestParameters + if let pageType = requestParameters.pageType { + // Re-evaluate required & optional modules as action models might have been generated prior to recent additions to the mapping. + requestParameters.modules = MVMCoreViewControllerMappingObject.shared()?.modulesRequired(forPageType: pageType) as? [String] + requestParameters.optionalModules = MVMCoreViewControllerMappingObject.shared()?.modulesOptional(forPageType: pageType) as? [String] + } + return requestParameters + } + open func performAction(with JSON: [AnyHashable : Any], model: ActionModelProtocol, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws { guard let model = model as? ActionOpenPageModel else { return } - let requestParameters: MVMCoreRequestParameters = model.requestParameters.copy() as! MVMCoreRequestParameters + let requestParameters = requestParamaters(for: model) do { if let closure = delegateObject?.actionDelegate?.handleOpenPage { // Legacy code will use the old handler function and break the task chain here. diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift index e82baa2..3634c11 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareHandler.swift @@ -14,15 +14,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { open func execute(with model: ActionModelProtocol, delegateObject: DelegateObject? = nil, additionalData: [AnyHashable: Any]? = nil) async throws { guard let model = model as? ActionShareModel else { return } - var shareData: [Any] - switch model.sharedType { - case .text: - shareData = [model.sharedText] - case .url: - let url = try URL.createURL(with: model.sharedText) - shareData = [url] - } - try await shareWith(activityItems: shareData, model: model) + try await shareWith(activityItems: model.items.map { $0.value }, model: model) } @MainActor @@ -33,17 +25,7 @@ open class ActionShareHandler: MVMCoreActionHandlerProtocol { controller.completionWithItemsHandler = {(activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) in if completed { // Activity was completed, considered finished. - if activityType == .copyToPasteboard { - // Allow copy - MVMCoreSessionObject.sharedGlobal()?.copyString(toClipboard: model.sharedText) - } continuation.resume() - } 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: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) { - MVMCoreLoggingHandler.addError(toLog: errorObject) - } } else if let error = error { continuation.resume(throwing: error) } else { diff --git a/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift index 2203a8c..ddeb7d8 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionShareModel.swift @@ -6,14 +6,51 @@ // Copyright © 2020 myverizon. All rights reserved. // - -public struct ActionShareModel: ActionModelProtocol { +public struct ActionShareItemModel: Codable { public enum SharedType: String, Codable { case text case url } + public var type: SharedType + public var value: Any + + private enum CodingKeys: String, CodingKey { + case type + case value + } + + public init(type: SharedType, value: Any) { + self.type = type + self.value = value + } + + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + type = try typeContainer.decode(SharedType.self, forKey: .type) + switch type { + case .text: + value = try typeContainer.decode(String.self, forKey: .value) + case .url: + value = try typeContainer.decode(URL.self, forKey: .value) + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + switch type { + case .text: + try container.encode(value as! String, forKey: .value) + case .url: + try container.encode(value as! URL, forKey: .value) + } + } +} + +public struct ActionShareModel: ActionModelProtocol { + //-------------------------------------------------- // MARK: - Properties //-------------------------------------------------- @@ -21,8 +58,7 @@ public struct ActionShareModel: ActionModelProtocol { public static var identifier: String = "share" public var actionType: String = ActionShareModel.identifier - public var sharedType: SharedType - public var sharedText: String + public var items: [ActionShareItemModel] public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -30,10 +66,54 @@ public struct ActionShareModel: ActionModelProtocol { // MARK: - Initializer //-------------------------------------------------- - public init(sharedText: String, sharedType: SharedType, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { - self.sharedType = sharedType - self.sharedText = sharedText + public init(items: [ActionShareItemModel], _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + self.items = items self.extraParameters = extraParameters self.analyticsData = analyticsData } + + //-------------------------------------------------- + // MARK: - Codable + //-------------------------------------------------- + + private enum CodingKeys: String, CodingKey { + case actionType + case items + case sharedType + case sharedText + } + + private enum DeprecatedCodingKeys: String, CodingKey { + case sharedType + case sharedText + } + + public init(from decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: CodingKeys.self) + if let items = try typeContainer.decodeIfPresent([ActionShareItemModel].self, forKey: .items) { + self.init(items: items) + } else { + // Legacy + try self.init(deprecatedFrom: decoder) + } + } + + private init(deprecatedFrom decoder: Decoder) throws { + let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self) + let type = try typeContainer.decode(ActionShareItemModel.SharedType.self, forKey: .sharedType) + var value: Any + switch type { + case .url: + value = try typeContainer.decode(URL.self, forKey: .sharedText) + default: + value = try typeContainer.decode(String.self, forKey: .sharedText) + } + items = [ActionShareItemModel(type: type, value: value)] + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(actionType, forKey: .actionType) + try container.encode(items, forKey: .items) + } } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index d6e352b..f0cc338 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -299,6 +299,9 @@ if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getJSONForRequestParameters:)]) { NSDictionary *json = [[MVMCoreObject sharedInstance].globalLoadDelegate getJSONForRequestParameters:requestParameters]; if (json) { +#if HARD_CODED_RESPONSE_DELAY > 0 + [NSThread sleepForTimeInterval:HARD_CODED_RESPONSE_DELAY]; +#endif completionHandler(json); return; } diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift index bd1db37..6f8c7e0 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift @@ -104,13 +104,17 @@ timingOutIn: timeout) { (receivedParameter) in // Queue the results for merge. parametersWorkQueue.async { - if (returnedList[index] != nil) { - MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!) - } else { - MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterFetchComplete(name: parameterType, uuid: requestUUID[index], actionId: actionId)) - returnedList[index] = receivedParameter - group.leave() // Leaving is only done after setup (barriered). + guard !complete else { + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "Client \(parameterType) responded after task completion.") + return } + guard returnedList[index] == nil else { + MVMCoreLoggingHandler.addError(toLog: MVMCoreErrorObject(title: nil, message: "Client parameter \(parameterType) has already executed. The completion handler should only be called once!", code: ErrorCode.default.rawValue, domain: ErrorDomainNative, location: String(describing: ClientParameterHandler.self))!) + return + } + MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterFetchComplete(name: parameterType, uuid: requestUUID[index], actionId: actionId)) + returnedList[index] = receivedParameter + group.leave() // Leaving is only done after setup (barriered). } } } diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift index 7f52778..e55fd15 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterProtocol.swift @@ -15,7 +15,7 @@ public protocol ClientParameterProtocol: ModelHandlerProtocol { var clientParameterModel: ClientParameterModelProtocol { get set } - func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: Double, completionHandler:@escaping ([String: AnyHashable]?) -> ()) + func fetchClientParameters(requestParameters: [String: Any], timingOutIn timeout: TimeInterval, completionHandler:@escaping ([String: AnyHashable]?) -> ()) /// Default parameter for timeout scenarios. It will use the protocol extension method bydefault. Can override to send custom values. func valueOnTimeout() -> [String: AnyHashable] @@ -28,10 +28,8 @@ public extension ClientParameterProtocol { } /// The handler should call this method to pass the parameter back to the caller. - func returnParameters(_ isFlatMap: Bool, _ parameter: [String: AnyHashable]?, completionHandler: @escaping ([String: AnyHashable]?) -> ()) { - guard let parameter = parameter else { - return completionHandler(nil) - } + /// If using isFlatMap, you must provide at least 1 element in parameters or it will result in triggering a timeout. + func returnParameters(_ isFlatMap: Bool, _ parameter: [String: AnyHashable], completionHandler: @escaping ([String: AnyHashable]?) -> ()) { if isFlatMap { completionHandler(parameter) } else { diff --git a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift index 10299bb..bb6e590 100644 --- a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift +++ b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift @@ -27,7 +27,7 @@ public extension MVMCoreActionUtility { - Parameter timemout: the time the operation has to finish before throwing a timeout error. - Parameter operation: the operation to perform. */ - static func perform(with timeout: Int = 1, operation: @escaping @Sendable () async throws -> T) async throws -> T { + static func perform(withTimeout timeout: TimeInterval, operation: @escaping @Sendable () async throws -> T) async throws -> T { return try await withCheckedThrowingContinuation { continuation in Task { await withThrowingTaskGroup(of: T.self) { group in @@ -40,7 +40,7 @@ public extension MVMCoreActionUtility { // Task for time out. group.addTask { try Task.checkCancellation() - try await Task.sleep(nanoseconds: UInt64(timeout) * NSEC_PER_SEC) + try await Task.sleep(nanoseconds: UInt64(timeout * TimeInterval(NSEC_PER_SEC))) throw MVMCoreActionUtilityError.timeOut }