diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index bf618fd..dac0562 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -31,9 +31,9 @@ deploy_snapshot: - bash_shell environment: name: oneartifactory - url: https://oneartifactoryprod.verizon.com/artifactory + url: https://oneartifactoryci.verizon.com/artifactory variables: - ARTIFACTORY_URL: https://oneartifactoryprod.verizon.com/artifactory + ARTIFACTORY_URL: https://oneartifactoryci.verizon.com/artifactory #promote_snapshot: # stage: go live @@ -49,9 +49,9 @@ deploy_snapshot: # - bash_shell # environment: # name: oneartifactory -# url: https://oneartifactoryprod.verizon.com/artifactory +# url: https://oneartifactoryci.verizon.com/artifactory # variables: -# ARTIFACTORY_URL: https://oneartifactoryprod.verizon.com/artifactory +# ARTIFACTORY_URL: https://oneartifactoryci.verizon.com/artifactory # #create_version_tag: # stage: tag diff --git a/MVMCore/MVMCore.xcodeproj/project.pbxproj b/MVMCore/MVMCore.xcodeproj/project.pbxproj index d8718e9..5513981 100644 --- a/MVMCore/MVMCore.xcodeproj/project.pbxproj +++ b/MVMCore/MVMCore.xcodeproj/project.pbxproj @@ -39,8 +39,9 @@ 0AEBB84625FA75C000EA80EE /* ActionOpenSMSModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AEBB84525FA75C000EA80EE /* ActionOpenSMSModel.swift */; }; 0AFF597A23FC6E60005C24E8 /* ActionShareModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */; }; 1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1DAD0FFD26AAB3FF00216E83 /* ActionRunJavaScriptModel.swift */; }; - 30349BF11FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 30349BEF1FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 30349BF01FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m */; }; + 2723337B28BD534D004EAEE0 /* MVMCoreEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337A28BD534D004EAEE0 /* MVMCoreEvent.swift */; }; + 2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337C28BD53C2004EAEE0 /* Date+Extension.swift */; }; + 60CBD0542A02397A00056CB0 /* MVMCoreSessionTimeHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CBD0532A02397A00056CB0 /* MVMCoreSessionTimeHandler.swift */; }; 881D26931FCC9D180079C521 /* MVMCoreErrorObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */; }; 881D26941FCC9D180079C521 /* MVMCoreOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 881D26901FCC9D180079C521 /* MVMCoreOperation.m */; }; 881D26951FCC9D180079C521 /* MVMCoreErrorObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -107,6 +108,7 @@ AF7069A02880F0EB00077CF6 /* ActionOpenPageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */; }; AF787413286DEF8B00670588 /* ActionBackHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF787412286DEF8B00670588 /* ActionBackHandler.swift */; }; AF8D13392774EA1D008AF4A9 /* ActionOpenUrlHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */; }; + AFA4931E29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AFA4931D29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift */; }; AF925DEE28BD35A2008E8677 /* MVMCoreNavigationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF925DED28BD35A2008E8677 /* MVMCoreNavigationHandler.swift */; }; AF925DF028BE78E2008E8677 /* MVMCoreNavigationPresentOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF925DEF28BE78E2008E8677 /* MVMCoreNavigationPresentOperation.swift */; }; AF925DF428C0FA38008E8677 /* MVMCoreNavigationPushOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF925DF328C0FA38008E8677 /* MVMCoreNavigationPushOperation.swift */; }; @@ -136,8 +138,6 @@ AFBB96951FBA3A9A0008D868 /* MVMCorePresentationDelegateProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96771FBA3A9A0008D868 /* MVMCorePresentationDelegateProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; }; AFBB96961FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96781FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.h */; settings = {ATTRIBUTES = (Public, ); }; }; AFBB96971FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB96791FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.m */; }; - AFBB96981FBA3A9A0008D868 /* MVMCoreAlertController.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB967B1FBA3A9A0008D868 /* MVMCoreAlertController.h */; settings = {ATTRIBUTES = (Public, ); }; }; - AFBB96991FBA3A9A0008D868 /* MVMCoreAlertController.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB967C1FBA3A9A0008D868 /* MVMCoreAlertController.m */; }; AFBB96B01FBA3B590008D868 /* MVMCoreDispatchUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96AC1FBA3B590008D868 /* MVMCoreDispatchUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; AFBB96B11FBA3B590008D868 /* MVMCoreDispatchUtility.m in Sources */ = {isa = PBXBuildFile; fileRef = AFBB96AD1FBA3B590008D868 /* MVMCoreDispatchUtility.m */; }; AFBB96B21FBA3B590008D868 /* MVMCoreGetterUtility.h in Headers */ = {isa = PBXBuildFile; fileRef = AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -201,8 +201,9 @@ 0AEBB84525FA75C000EA80EE /* ActionOpenSMSModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenSMSModel.swift; sourceTree = ""; }; 0AFF597923FC6E60005C24E8 /* ActionShareModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionShareModel.swift; sourceTree = ""; }; 1DAD0FFD26AAB3FF00216E83 /* ActionRunJavaScriptModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionRunJavaScriptModel.swift; sourceTree = ""; }; - 30349BEF1FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionTimeHandler.h; sourceTree = ""; }; - 30349BF01FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionTimeHandler.m; sourceTree = ""; }; + 2723337A28BD534D004EAEE0 /* MVMCoreEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreEvent.swift; sourceTree = ""; }; + 2723337C28BD53C2004EAEE0 /* Date+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extension.swift"; sourceTree = ""; }; + 60CBD0532A02397A00056CB0 /* MVMCoreSessionTimeHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreSessionTimeHandler.swift; sourceTree = ""; }; 881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreErrorObject.m; sourceTree = ""; }; 881D26901FCC9D180079C521 /* MVMCoreOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreOperation.m; sourceTree = ""; }; 881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreErrorObject.h; sourceTree = ""; }; @@ -274,6 +275,7 @@ AF70699F2880F0EB00077CF6 /* ActionOpenPageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenPageHandler.swift; sourceTree = ""; }; AF787412286DEF8B00670588 /* ActionBackHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBackHandler.swift; sourceTree = ""; }; AF8D13382774EA1D008AF4A9 /* ActionOpenUrlHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionOpenUrlHandler.swift; sourceTree = ""; }; + AFA4931D29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreActionUtility+Extension.swift"; sourceTree = ""; }; AF925DED28BD35A2008E8677 /* MVMCoreNavigationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreNavigationHandler.swift; sourceTree = ""; }; AF925DEF28BE78E2008E8677 /* MVMCoreNavigationPresentOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreNavigationPresentOperation.swift; sourceTree = ""; }; AF925DF328C0FA38008E8677 /* MVMCoreNavigationPushOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreNavigationPushOperation.swift; sourceTree = ""; }; @@ -303,8 +305,6 @@ AFBB96771FBA3A9A0008D868 /* MVMCorePresentationDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCorePresentationDelegateProtocol.h; sourceTree = ""; }; AFBB96781FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCorePresentViewControllerOperation.h; sourceTree = ""; }; AFBB96791FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCorePresentViewControllerOperation.m; sourceTree = ""; }; - AFBB967B1FBA3A9A0008D868 /* MVMCoreAlertController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreAlertController.h; sourceTree = ""; }; - AFBB967C1FBA3A9A0008D868 /* MVMCoreAlertController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreAlertController.m; sourceTree = ""; }; AFBB96AC1FBA3B590008D868 /* MVMCoreDispatchUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreDispatchUtility.h; sourceTree = ""; }; AFBB96AD1FBA3B590008D868 /* MVMCoreDispatchUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreDispatchUtility.m; sourceTree = ""; }; AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGetterUtility.h; sourceTree = ""; }; @@ -397,7 +397,6 @@ 8876D5D41FB50AAB00EB2E3D /* Utility */, AF43A7191FC5BE9E008E9347 /* MainProtocols */, AFBB96B41FBA3CEC0008D868 /* ActionHandling */, - AFBB967A1FBA3A9A0008D868 /* AlertHandling */, AFBB966B1FBA3A9A0008D868 /* PresentationHandling */, AFBB96361FBA39E70008D868 /* LoadHandling */, AFBB96131FBA26650008D868 /* ViewControllerMapping */, @@ -445,6 +444,7 @@ 8876D5E51FB50AB000EB2E3D /* UIFont+MFSpacing.m */, 8876D5E61FB50AB000EB2E3D /* UILabel+MFCustom.h */, 8876D5E71FB50AB000EB2E3D /* UILabel+MFCustom.m */, + 2723337C28BD53C2004EAEE0 /* Date+Extension.swift */, ); path = Categories; sourceTree = ""; @@ -524,8 +524,7 @@ AF43A70C1FC4F42B008E9347 /* Session */ = { isa = PBXGroup; children = ( - 30349BEF1FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h */, - 30349BF01FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m */, + 60CBD0532A02397A00056CB0 /* MVMCoreSessionTimeHandler.swift */, AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */, AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */, ); @@ -621,15 +620,6 @@ path = PresentationHandling; sourceTree = ""; }; - AFBB967A1FBA3A9A0008D868 /* AlertHandling */ = { - isa = PBXGroup; - children = ( - AFBB967B1FBA3A9A0008D868 /* MVMCoreAlertController.h */, - AFBB967C1FBA3A9A0008D868 /* MVMCoreAlertController.m */, - ); - path = AlertHandling; - sourceTree = ""; - }; AFBB96AB1FBA3B590008D868 /* Helpers */ = { isa = PBXGroup; children = ( @@ -640,6 +630,7 @@ D282AAB52240085300C46919 /* MVMCoreGetterUtility+Extension.swift */, AF43A5851FBB67D6008E9347 /* MVMCoreActionUtility.h */, AF43A5861FBB67D6008E9347 /* MVMCoreActionUtility.m */, + AFA4931D29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift */, ); path = Helpers; sourceTree = ""; @@ -702,6 +693,7 @@ AFEEE81C1FCDF3CA00B5EDD0 /* MVMCoreLoggingHandler.h */, AFEEE81D1FCDF3CA00B5EDD0 /* MVMCoreLoggingHandler.m */, D288D5F426C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift */, + 2723337A28BD534D004EAEE0 /* MVMCoreEvent.swift */, ); path = OtherHandlers; sourceTree = ""; @@ -757,7 +749,6 @@ AFBB96EC1FBA4A260008D868 /* MFHardCodedServerResponse.h in Headers */, AFBB96B21FBA3B590008D868 /* MVMCoreGetterUtility.h in Headers */, 8876D5F21FB50AB000EB2E3D /* UIFont+MFSpacing.h in Headers */, - 30349BF11FCCA78A00546A1E /* MVMCoreSessionTimeHandler.h in Headers */, AFBB96931FBA3A9A0008D868 /* MVMCorePresentAnimationOperation.h in Headers */, AFBB96961FBA3A9A0008D868 /* MVMCorePresentViewControllerOperation.h in Headers */, AFBB96911FBA3A9A0008D868 /* MVMCoreNavigationOperation.h in Headers */, @@ -765,7 +756,6 @@ 8876D5EC1FB50AB000EB2E3D /* NSDictionary+MFConvenience.h in Headers */, AFFCFA651FCCC0D700FD0730 /* MVMCoreLoadingOverlayDelegateProtocol.h in Headers */, AF1201832108C9B400E2F592 /* MVMCoreViewManagerViewControllerProtocol.h in Headers */, - AFBB96981FBA3A9A0008D868 /* MVMCoreAlertController.h in Headers */, 881D26961FCC9D180079C521 /* MVMCoreOperation.h in Headers */, 8876D5EA1FB50AB000EB2E3D /* NSDecimalNumber+MFConvenience.h in Headers */, AF43A7201FC5D2BA008E9347 /* MVMCoreViewManagerProtocol.h in Headers */, @@ -915,7 +905,6 @@ BB780ADF250F8C890030BD2F /* ActionNoopModel.swift in Sources */, D2E1FAD92260C3E400AEFD8C /* DelegateObject.swift in Sources */, 01F2A03623A80A7300D954D8 /* ActionModelProtocol.swift in Sources */, - AFBB96991FBA3A9A0008D868 /* MVMCoreAlertController.m in Sources */, 881D26941FCC9D180079C521 /* MVMCoreOperation.m in Sources */, AFED77A41FCCA29400BAE689 /* MVMCoreViewControllerMappingObject.m in Sources */, 01C851CF23CF7B260021F976 /* JSONMap.swift in Sources */, @@ -927,8 +916,10 @@ AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */, D282AAB62240085300C46919 /* MVMCoreGetterUtility+Extension.swift in Sources */, AFBB96901FBA3A9A0008D868 /* MVMCoreNavigationObject.m in Sources */, + AFA4931E29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift in Sources */, 1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */, 946EE1AB237B5C940036751F /* Decoder.swift in Sources */, + 2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */, AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */, AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */, 946EE1BC237B691A0036751F /* ActionOpenPageModel.swift in Sources */, @@ -938,7 +929,6 @@ AF925DF828C10B00008E8677 /* MVMCoreNavigationPopOperation.swift in Sources */, D288D5F526C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift in Sources */, 946EE1B0237B5EF70036751F /* JSONHelper.swift in Sources */, - 30349BF21FCCA78A00546A1E /* MVMCoreSessionTimeHandler.m in Sources */, D2DEDCB923C6400600C44CC4 /* UnitInterval.swift in Sources */, 94C014D3242119E6005811A9 /* ActionPreviousSubmitModel.swift in Sources */, AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */, @@ -968,6 +958,7 @@ 01F2A05223A8325100D954D8 /* ModelMapping.swift in Sources */, 8876D5F51FB50AB000EB2E3D /* UILabel+MFCustom.m in Sources */, AFBB96B31FBA3B590008D868 /* MVMCoreGetterUtility.m in Sources */, + 2723337B28BD534D004EAEE0 /* MVMCoreEvent.swift in Sources */, AF43A7071FC4D7A2008E9347 /* MVMCoreObject.m in Sources */, 94C014D924212360005811A9 /* ActionSettingModel.swift in Sources */, D2DEDCB723C63F3B00C44CC4 /* Clamping.swift in Sources */, @@ -997,6 +988,7 @@ AF69D4F1286E9D8000BC6862 /* ActionNoopHandler.swift in Sources */, 016FF6F2259A4FCC00F5E4AA /* ClientParameterModel.swift in Sources */, D2DEDCBB23C65BC300C44CC4 /* Percent.swift in Sources */, + 60CBD0542A02397A00056CB0 /* MVMCoreSessionTimeHandler.swift in Sources */, AFBB966A1FBA3A570008D868 /* MVMCoreLoadRequestOperation.m in Sources */, D268D82C26700292008BD413 /* MVMCoreViewManagerViewControllerProtocolHelper.m in Sources */, 0AEBB84625FA75C000EA80EE /* ActionOpenSMSModel.swift in Sources */, @@ -1027,7 +1019,6 @@ 8876D5CF1FB50A9E00EB2E3D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -1091,7 +1082,6 @@ 8876D5D01FB50A9E00EB2E3D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ALWAYS_SEARCH_USER_PATHS = NO; BUILD_LIBRARY_FOR_DISTRIBUTION = YES; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; @@ -1151,7 +1141,6 @@ 8876D5D21FB50A9E00EB2E3D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - BITCODE_GENERATION_MODE = bitcode; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; @@ -1179,7 +1168,6 @@ 8876D5D31FB50A9E00EB2E3D /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - BITCODE_GENERATION_MODE = bitcode; CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Automatic; diff --git a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift index 6788d08..c927e9a 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionDelegateProtocol.swift @@ -19,3 +19,9 @@ public extension ActionDelegateProtocol { try await MVMCoreActionHandler.shared()?.handleAction(with: model, additionalData: additionalData, delegateObject: delegateObject) } } + +public extension MVMCoreActionDelegateProtocol { + func action(with model: ActionModelProtocol, additionalData: [AnyHashable : Any]?, delegateObject: DelegateObject?) async throws { + try await (self as? ActionDelegateProtocol)?.performAction(with: model, additionalData: additionalData, delegateObject: delegateObject) + } +} diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift index 7bdc502..ea40b42 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageHandler.swift @@ -13,11 +13,12 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { 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 do { if let closure = delegateObject?.actionDelegate?.handleOpenPage { // Legacy code will use the old handler function and break the task chain here. - closure(model.requestParameters, JSON, additionalData) - } else if let operation = try await performRequestAddingClientParameters(with: model.requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) { + closure(requestParameters, JSON, additionalData) + } else if let operation = try await performRequestAddingClientParameters(with: requestParameters, model: model, delegateObject: delegateObject, additionalData: additionalData) { await withCheckedContinuation { continuation in operation.completionBlock = { continuation.resume() @@ -42,13 +43,24 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { /// Adds client parameters and makes calls performRequest() open func performRequestAddingClientParameters(with requestParameters: MVMCoreRequestParameters, model: ActionOpenPageModel, delegateObject: DelegateObject?, additionalData: [AnyHashable : Any]?) async throws -> MVMCoreLoadRequestOperation? { + let actionUUID = MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? UUID().uuidString + requestParameters.identifier = actionUUID + if !requestParameters.backgroundRequest, let pageType = requestParameters.pageType { + MVMCoreLoggingHandler.shared()?.logCoreEvent(.pageStarted(pageType: pageType, requestUUID: actionUUID)) + } // Adds any client parameters to the request parameters. if let parametersToFetch = model.clientParameters, - let fetchedParameters = try await ClientParameterHandler().getClientParameters(with: parametersToFetch, requestParameters: requestParameters.parameters as? [String : Any] ?? [:], showLoadingOverlay: !requestParameters.backgroundRequest) { + let fetchedParameters = try await ClientParameterHandler().getClientParameters( + with: parametersToFetch, + requestParameters: requestParameters.parameters as? [String : Any] ?? [:], + actionId: MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? "unknown", + showLoadingOverlay: !requestParameters.backgroundRequest) { requestParameters.add(fetchedParameters) } try Task.checkCancellation() - return MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) + let coreLoadRequestOperation = MVMCoreLoadHandler.sharedGlobal()?.loadRequest(requestParameters, dataForPage: additionalData, delegateObject: delegateObject) + coreLoadRequestOperation?.identifier = actionUUID + return coreLoadRequestOperation } /// Ensures background requests do not have showing errors. @@ -68,7 +80,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol { public extension ClientParameterHandler { /// Iterates through the clientParameters list. Gets values from the individual handlers and attaches the parameters to extraParameters. - func getClientParameters(with model: ClientParameterModel, requestParameters: [String: Any], showLoadingOverlay: Bool) async throws -> [String: Any]? { + func getClientParameters(with model: ClientParameterModel, requestParameters: [String: Any], actionId: String, showLoadingOverlay: Bool) async throws -> [String: Any]? { if showLoadingOverlay { MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.startLoading() } @@ -79,7 +91,7 @@ public extension ClientParameterHandler { } return try await withCheckedThrowingContinuation({ continuation in do { - try getParameters(with: model, requestParameters: requestParameters) { parameters in + try getParameters(with: model, requestParameters: requestParameters, actionId: actionId) { parameters in continuation.resume(returning: parameters) } } catch { diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift index 611b7b7..34308ef 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenPageModel.swift @@ -98,6 +98,7 @@ public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol, public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(pageType, forKey: .pageType) + try container.encode(actionType, forKey: .actionType) try container.encodeIfPresent(baseURL, forKey: .baseURL) try container.encodeIfPresent(appContext, forKey: .appContext) try container.encodeIfPresent(requestURL, forKey: .requestURL) diff --git a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift index 5ed9d2d..085b9a4 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionOpenUrlHandler.swift @@ -40,6 +40,10 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol { return "Failed to open url: \(url.absoluteString)" } } + + public var errorCode: Int { + return ErrorCode.linkawayFailed.rawValue + } } /// Creates a url and calls open(url: URL) diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift index 20e7a90..a108649 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartHandler.swift @@ -15,21 +15,23 @@ open class ActionRestartHandler: MVMCoreActionHandlerProtocol { guard let model = model as? ActionRestartModel else { return } 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 { + Task { @MainActor in + MVMCoreSessionTimeHandler.shared().invalidateSession({ error in + if let error = error { + guard error.code != NSURLErrorCancelled else { + 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, request: model.requestURL, parameters: model.extraParameters?.toJSON(), clearAllVariables: true) 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.toJSON(), clearAllVariables: true) - continuation.resume() - } - }) - } + }) + } + } } } diff --git a/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift index 7cc2d26..e64a6d3 100644 --- a/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift +++ b/MVMCore/MVMCore/ActionHandling/ActionRestartModel.swift @@ -6,6 +6,7 @@ // Copyright © 2020 myverizon. All rights reserved. // +import Foundation public struct ActionRestartModel: ActionModelProtocol { //-------------------------------------------------- @@ -14,6 +15,7 @@ public struct ActionRestartModel: ActionModelProtocol { public static var identifier: String = "restart" public var actionType: String = ActionRestartModel.identifier + public var requestURL: URL? public var extraParameters: JSONValueDictionary? public var analyticsData: JSONValueDictionary? @@ -24,8 +26,9 @@ public struct ActionRestartModel: ActionModelProtocol { // MARK: - Initializer //-------------------------------------------------- - public init(_ pageType: String? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { + public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) { self.pageType = pageType + self.requestURL = requestUrl self.extraParameters = extraParameters self.analyticsData = analyticsData } diff --git a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift index 398a590..c206300 100644 --- a/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift +++ b/MVMCore/MVMCore/ActionHandling/MVMCoreActionHandler.swift @@ -84,7 +84,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { /// Handle an action with the given model. open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws { try Task.checkCancellation() - var additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData) + var (additionalData, uuid) = MVMCoreActionHandler.setUUID(additionalData: additionalData) MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData) defer { MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData) @@ -97,20 +97,24 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { do { let handlerType = try ModelRegistry.getHandler(model) as! MVMCoreActionHandlerProtocol.Type let handler = handlerType.init() + MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionInvoked(name: model.actionType, pageType: pageType(from: delegateObject), uuid: uuid)) if let handler = handler as? MVMCoreJSONActionHandlerProtocol { // Needed until we can remove legacy delegate functions. try await handler.performAction(with: json, model: model, delegateObject: delegateObject, additionalData: additionalData) } else { try await handler.execute(with: model, delegateObject: delegateObject, additionalData: additionalData) } + MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionComplete(name: model.actionType, pageType: pageType(from: delegateObject), uuid: uuid)) } 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) + MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionNotFound(name: model.actionType , pageType: pageType(from: delegateObject))) throw ActionError.unknownAction(type: model.actionType) } } catch { + MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionFailed(name: model.actionType, pageType: pageType(from: delegateObject), uuid: uuid, error: error)) MVMCoreActionHandler.log(string: "Failed Action \(error)", additionalData: additionalData) throw error } @@ -125,19 +129,21 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { } /// Logs the error. - @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]?) { + @objc open func defaultHandleActionError(_ error: MVMCoreErrorObject, additionalData: [AnyHashable: Any]? = nil, delegateObject: DelegateObject? = nil) { guard error.logError else { return } MVMCoreLoggingHandler.addError(toLog: error) + error.logError = false // Further attempts to log the same error will be skipped. (Legacy action flow.) } // MARK: - Legacy Holdovers - static public func setUUID(additionalData: [AnyHashable: Any]?, force: Bool = false) -> [AnyHashable: Any] { - if !force && getUUID(additionalData: additionalData) != nil { return additionalData! } - return additionalData.dictionaryAdding(key: "Action-UUID", value: UUID().uuidString) + static public func setUUID(additionalData: [AnyHashable: Any]?, force: Bool = false) -> ([AnyHashable: Any], String) { + if !force, let uuid = getUUID(additionalData: additionalData) { return (additionalData!, uuid) } + let newUUID = UUID().uuidString + return (additionalData.dictionaryAdding(key: "Action-UUID", value: newUUID), newUUID) } - static public func getUUID(additionalData: [AnyHashable: Any]?) -> String? { + @objc static public func getUUID(additionalData: [AnyHashable: Any]?) -> String? { return additionalData?.optionalStringForKey("Action-UUID") } @@ -145,6 +151,17 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: UUID: \(String(describing: getUUID(additionalData: additionalData))), \(string)") } + fileprivate func logActionError(_ error: Error, _ actionType: String?, _ additionalData: [AnyHashable: Any]?, _ delegateObject: DelegateObject?) { + MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) + if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType ?? "noAction")) { + defaultHandleActionError(errorObject, additionalData: additionalData) + } + } + + fileprivate func pageType(from delegateObject: DelegateObject?) -> String { + return (delegateObject?.loadDelegate as? MVMCoreViewControllerProtocol)?.pageType ?? "unknown" + } + /// Legacy handle action with json. @objc(handleActionWithDictionary:additionalData:delegateObject:) open func handleAction(with json: [AnyHashable: Any]?, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) { @@ -159,16 +176,17 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { } catch { let actionType = json?.optionalStringForKey(KeyActionType) switch error { + case ModelRegistry.Error.decoderError, is DecodingError: + MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionFailedToDecode(pageType: pageType(from: delegateObject), error: error)) + logActionError(error, actionType, additionalData, delegateObject) case ModelRegistry.Error.decoderErrorModelNotMapped: // If the model is not mapped, give the legacy classes a chance to handle it. if try await handleUnregisteredAction(with: nil, json: json!, additionalData: additionalData, delegateObject: delegateObject) == false { - fallthrough + MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionNotFound(name: actionType ?? "noAction", pageType: pageType(from: delegateObject))) + logActionError(error, actionType, additionalData, delegateObject) } default: - MVMCoreActionHandler.log(string: "Failed Action: Error \(error)", additionalData: additionalData) - if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: actionType ?? "noAction")) { - defaultHandleActionError(errorObject, additionalData: additionalData) - } + logActionError(error, actionType, additionalData, delegateObject) } } } @@ -177,8 +195,8 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol { /// 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() - var additionalData = additionalData.dictionaryAdding(key: jsonKey, value: json) - additionalData = MVMCoreActionHandler.setUUID(additionalData: additionalData, force: true) + let additionalDataWithJSON = additionalData.dictionaryAdding(key: jsonKey, value: json) + let (additionalData, _) = MVMCoreActionHandler.setUUID(additionalData: additionalDataWithJSON, force: true) MVMCoreActionHandler.log(string: "JSON \(json)", additionalData: additionalData) if let closure = (delegateObject?.actionDelegate as? ActionDelegateProtocol)?.performAction { // Allow newer delegates to handle calls from legacy functions diff --git a/MVMCore/MVMCore/AlertHandling/MVMCoreAlertController.h b/MVMCore/MVMCore/AlertHandling/MVMCoreAlertController.h deleted file mode 100644 index e38bedf..0000000 --- a/MVMCore/MVMCore/AlertHandling/MVMCoreAlertController.h +++ /dev/null @@ -1,18 +0,0 @@ -// -// MVMCoreAlertController.h -// alerts -// -// Created by Scott Pfeil on 10/22/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// -// Used by our alert handler. Not for subclassing. Simply keeps track of if it's visible. Tries to parallel the UIAlertView to make it easier for the MVMCoreAlertHandler. - -#import -@class MVMCoreAlertObject; - -@interface MVMCoreAlertController : UIAlertController - -@property (nonatomic, readonly, getter=isVisible) BOOL visible; -@property (nullable, nonatomic, strong) MVMCoreAlertObject *alertObject; - -@end diff --git a/MVMCore/MVMCore/AlertHandling/MVMCoreAlertController.m b/MVMCore/MVMCore/AlertHandling/MVMCoreAlertController.m deleted file mode 100644 index 673c591..0000000 --- a/MVMCore/MVMCore/AlertHandling/MVMCoreAlertController.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// MVMAlertController.m -// alerts -// -// Created by Scott Pfeil on 10/22/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreAlertController.h" -#import "MVMCoreLoggingHandler.h" - -@interface MVMCoreAlertController () - -@property (nonatomic, readwrite, getter=isVisible) BOOL visible; - -@end - -@implementation MVMCoreAlertController - -- (void)viewDidAppear:(BOOL)animated { - [super viewDidAppear:animated]; - - [self willChangeValueForKey:@"isVisible"]; - self.visible = YES; - [self didChangeValueForKey:@"isVisible"]; - [MVMCoreLoggingHandler logAlertForAlertController:self]; -} - -- (void)viewDidDisappear:(BOOL)animated { - [super viewDidDisappear:animated]; - [self willChangeValueForKey:@"isVisible"]; - self.visible = NO; - [self didChangeValueForKey:@"isVisible"]; -} - -- (NSString *)description { - return [NSString stringWithFormat:@"%@|title=%@|message=%@", [super description],self.title,self.message]; -} - -@end diff --git a/MVMCore/MVMCore/Categories/Date+Extension.swift b/MVMCore/MVMCore/Categories/Date+Extension.swift new file mode 100644 index 0000000..3f7df53 --- /dev/null +++ b/MVMCore/MVMCore/Categories/Date+Extension.swift @@ -0,0 +1,16 @@ +// +// Date+Extension.swift +// MVMCore +// +// Created by Kyle on 8/29/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +public extension Date { + + static func unixMillisecondsNow() -> Int64 { + return Int64(Self().timeIntervalSince1970 * 1000) + } +} diff --git a/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.h b/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.h index de916ed..ef1115d 100644 --- a/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.h +++ b/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.h @@ -40,5 +40,6 @@ typedef NS_ENUM(NSInteger, ErrorCode) { ErrorCodeSSL,//23 ErrorCodeNoViewControllerToPresentOn,//24 ErrorCodeNoErrorPageSent, //25 - ErrorCodeFontNotFound //26 + ErrorCodeFontNotFound, //26 + ErrorCodePoorPerformance //27 }; diff --git a/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.m b/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.m index d626352..20be6b4 100644 --- a/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.m +++ b/MVMCore/MVMCore/Constants/MVMCoreErrorConstants.m @@ -9,6 +9,10 @@ #import "MVMCoreErrorConstants.h" // Error Domains + +/// Communication breakdowns. NSString * const ErrorDomainSystem = @"ErrorDomainSystem"; +/// Any sort of native error not due to server directives. NSString * const ErrorDomainNative = @"ErrorDomainNative"; +/// Specific errors defined by the server. NSString * const ErrorDomainServer = @"ErrorDomainServer"; diff --git a/MVMCore/MVMCore/LoadHandling/LoadingOverlay/MVMCoreLoadingOverlayHandler.m b/MVMCore/MVMCore/LoadHandling/LoadingOverlay/MVMCoreLoadingOverlayHandler.m index d8724af..f3c953e 100644 --- a/MVMCore/MVMCore/LoadHandling/LoadingOverlay/MVMCoreLoadingOverlayHandler.m +++ b/MVMCore/MVMCore/LoadHandling/LoadingOverlay/MVMCoreLoadingOverlayHandler.m @@ -139,7 +139,7 @@ if (animate) { self.animatingOut = YES; - + self.animatingIn = NO; [UIView animateWithDuration:0.3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ self.loadingViewController.view.alpha = 0; } completion:^(BOOL finished) { diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadDelegateProtocol.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadDelegateProtocol.h index 39ddc8b..db6f5b9 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadDelegateProtocol.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadDelegateProtocol.h @@ -11,7 +11,6 @@ #import @class MVMCoreRequestParameters; @class MVMCoreErrorObject; -@class MVMCoreAlertObject; @class MVMCoreLoadObject; @protocol MVMCoreLoadDelegateProtocol diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.h index 84de194..775bd55 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.h @@ -33,6 +33,16 @@ // Returns the error location for the given requesting object and page type and modules. Important for proper logging. - (nonnull NSString *)errorLocationForRequest:(nonnull id)requestingObject pageType:(nonnull NSString *)pageType modules:(nonnull NSString *)modules; +// Returns an error given a load object and error details. Attaches session data related to the load. Important for proper logging. +- (nonnull MVMCoreErrorObject *)errorForLoadObject:(nonnull MVMCoreLoadObject *)loadObject withTitle:(nullable NSString *)title message:(nullable NSString *)message code:(NSInteger)code domain:(nonnull NSString *)domain; +- (nonnull MVMCoreErrorObject *)errorForLoadObject:(nonnull MVMCoreLoadObject *)loadObject withTitle:(nullable NSString *)title message:(nullable NSString *)message messageToLog:(nullable NSString *)messageToLog code:(NSInteger)code domain:(nonnull NSString *)domain; + +// Returns an error given a load object and NSError. Attaches session data related to the load. Important for proper logging. +- (nonnull MVMCoreErrorObject *)errorForLoadObject:(nonnull MVMCoreLoadObject *)loadObject causedBy:(nonnull NSError *)error; + +// Decorates an error object given a load object. +- (nonnull MVMCoreErrorObject *)attachLoadInformation:(nonnull MVMCoreLoadObject *)loadObject toError:(nonnull MVMCoreErrorObject *)error; + #pragma mark - Request Functions. - (void)setHeadersForRequest:(nonnull NSMutableURLRequest *)request requestParameters:(nonnull MVMCoreRequestParameters *)requestParameters; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m index 842d032..64d67ce 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadHandler.m @@ -95,9 +95,34 @@ } } +- (nonnull MVMCoreErrorObject *)errorForLoadObject:(MVMCoreLoadObject *)loadObject withTitle:(NSString *)title message:(NSString *)message code:(NSInteger)code domain:(NSString *)domain { + return [self attachLoadInformation:loadObject toError:[[MVMCoreErrorObject alloc] initWithTitle:title messageToLog:message code:code domain:domain location:[self errorLocationForRequest:loadObject]]]; +} + +- (nonnull MVMCoreErrorObject *)errorForLoadObject:(MVMCoreLoadObject *)loadObject withTitle:(NSString *)title message:(NSString *)message messageToLog:(NSString *)messageToLog code:(NSInteger)code domain:(NSString *)domain { + return [self attachLoadInformation:loadObject toError:[[MVMCoreErrorObject alloc] initWithTitle:title message:message messageToLog:messageToLog code:code domain:domain location:[self errorLocationForRequest:loadObject]]]; +} + +- (nonnull MVMCoreErrorObject *)errorForLoadObject:(MVMCoreLoadObject *)loadObject causedBy:(NSError *)error { + return [self attachLoadInformation:loadObject toError:[MVMCoreErrorObject createErrorObjectForNSError:error location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]]; +} + +- (nonnull MVMCoreErrorObject *)attachLoadInformation:(MVMCoreLoadObject *)loadObject toError:(MVMCoreErrorObject *)error { + // Native and system errors have an error screen. + if (![error.domain isEqualToString:ErrorDomainServer] && [[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getNativeScreenForRequestError:requestObject:)]) { + error.errorScreenError = YES; + error.nativeDrivenErrorScreen = YES; + error.silentError = NO; + error.messageToDisplay = error.messageToDisplay ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; + error.requestId = loadObject.identifier; //To track for errors due to invalid JSON or any errors thrown at viewcontroller level. + } + return error; +} + - (nonnull NSString *)errorLocationForRequest:(nonnull MVMCoreLoadObject *)loadObject { - - return [self errorLocationForRequest:loadObject.delegateObject.loadDelegate pageType:loadObject.requestParameters.pageType modules:[NSString stringWithFormat:@"%@",loadObject.requestParameters.modules]]; + return [self errorLocationForRequest:loadObject.delegateObject.loadDelegate + pageType:loadObject.requestParameters.pageType + modules:[NSString stringWithFormat:@"%@", loadObject.requestParameters.modules]]; } - (nonnull NSString *)errorLocationForRequest:(nonnull id)requestingObject pageType:(nonnull NSString *)pageType modules:(nonnull NSString *)modules { @@ -110,31 +135,7 @@ - (nullable NSURLRequest *)requestWithParameters:(nonnull MVMCoreRequestParameters *)requestParameters error:(MVMCoreErrorObject *_Nonnull *_Nonnull)error { - NSURL *url = requestParameters.URL; - if (!url) { - if (requestParameters.alternateBaseURL) { - url = requestParameters.alternateBaseURL; - } else { - url = [MVMCoreSessionObject sharedGlobal].baseURL ?: [NSURL URLWithString:URLProdPostpayBase]; - } - - // Appends the context root. - if (requestParameters.contextRoot) { - url = [url URLByAppendingPathComponent:requestParameters.contextRoot]; - } else if ([MVMCoreSessionObject sharedGlobal].contextRoot) { - url = [url URLByAppendingPathComponent:[MVMCoreSessionObject sharedGlobal].contextRoot]; - } - - // Appends the page type - if (requestParameters.pageType) { - url = [url URLByAppendingPathComponent:requestParameters.pageType]; - } - - // This has changed since the initial agreement. Seems server always needs page type now. - /* else if (requestParameters.modules) { - url = [url URLByAppendingPathComponent:KeyModuleMap]; - }*/ - } + NSURL *url = [requestParameters resolveURL:[MVMCoreSessionObject sharedGlobal]]; // Adds modules needed to the request parameters. if (requestParameters.modules.count > 0) { @@ -349,7 +350,7 @@ if (!jsonObject) { // Error serializing json. - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] messageToLog:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] code:ErrorCodeParsingJSON domain:ErrorDomainNative location:locationForError]; + errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] messageToLog:[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] code:ErrorCodeParsingJSON domain:ErrorDomainSystem location:locationForError]; } else { // Uncomment to get the raw UTF-8 string response from server before it is parsed. Useful for identifying issues such as duplicate definitions which are removed after parsing. (Also, be careful of parsing tools such as jsoneditoronline.org which will autoresolve some of these issues as well.) @@ -362,7 +363,7 @@ } } else { // Empty response. - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeEmptyResponse domain:ErrorDomainNative location:locationForError]; + errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeEmptyResponse domain:ErrorDomainSystem location:locationForError]; } } else if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(createErrorObjectForRequestNSError:forRequest:location:)]) { @@ -388,6 +389,7 @@ return [self loadBackgroundRequest:requestParameters dataForPage:dataForPage delegateObject:delegateObject]; } else { MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO]; + loadOperation.identifier = requestParameters.identifier; [self.blockingLoadQueue addOperation:loadOperation]; return loadOperation; } @@ -395,6 +397,7 @@ - (MVMCoreLoadRequestOperation *)loadBackgroundRequest:(nonnull MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject { MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:YES]; + loadOperation.identifier = requestParameters.identifier; [self.backgroundLoadQueue addOperation:loadOperation]; return loadOperation; } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h index 92063b8..72875fa 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.h @@ -46,7 +46,6 @@ @property (nullable, strong, nonatomic) NSDictionary *dataForPage; // The load delegate -@property (nullable, weak, nonatomic) NSObject *delegate __deprecated; @property (nullable, strong, nonatomic) DelegateObject *delegateObject; // The operation that is loading. @@ -59,6 +58,9 @@ // The full response json @property (nullable, strong, nonatomic) NSDictionary *responseJSON; +//Unique Identifier for event tracking +@property (nullable, strong, nonatomic) NSString *identifier; + - (nullable instancetype)initWithPageJSON:(nullable NSDictionary *)pageJSON modulesJSON:(nullable NSDictionary *)modulesJSON requestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject; - (nullable instancetype)initWithRequestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegateObject:(nullable DelegateObject *)delegateObject; @@ -70,12 +72,4 @@ // Returns whether the load will extend the app session timer based on the response provided by the server. - (BOOL)extendsAppSession; -#pragma mark - Deprecated - -- (nullable instancetype)initWithPageJSON:(nullable NSDictionary *)pageJSON modulesJSON:(nullable NSDictionary *)modulesJSON requestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegate:(nullable NSObject*)delegate __deprecated; - -- (nullable instancetype)initWithRequestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegate:(nullable NSObject*)delegate __deprecated; - -- (nullable instancetype)initWithDelegate:(nullable NSObject*)delegate __deprecated; - @end diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m index 9c56d99..3be1428 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadObject.m @@ -55,6 +55,9 @@ if (errorObject.messageToDisplay) { [responseInfo setObject:errorObject.messageToDisplay forKey:KeyUserMessage]; } + if (errorObject.requestId) { + self.identifier = errorObject.requestId; + } self.responseInfoMap = responseInfo; } return self; @@ -68,44 +71,4 @@ return NO; } -#pragma mark - Deprecated - -- (void)setDelegateObject:(DelegateObject *)delegateObject { - _delegateObject = delegateObject; - _delegate = delegateObject.loadDelegate; -} - -- (void)setDelegate:(NSObject *)delegate { - _delegate = delegate; - if ([delegate respondsToSelector:@selector(delegateObject)]) { - _delegateObject = [delegate performSelector:@selector(delegateObject)]; - } else { - _delegateObject = [DelegateObject createWithDelegateForAll:delegate]; - } -} - -- (nullable instancetype)initWithPageJSON:(nullable NSDictionary *)pageJSON modulesJSON:(nullable NSDictionary *)modulesJSON requestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegate:(nullable NSObject*)delegate { - - if (self = [self initWithRequestParameters:requestParameters dataForPage:dataForPage delegate:delegate]) { - self.pageJSON = pageJSON; - self.modulesJSON = modulesJSON; - } - return self; -} - -- (nullable instancetype)initWithRequestParameters:(nullable MVMCoreRequestParameters *)requestParameters dataForPage:(nullable NSDictionary *)dataForPage delegate:(nullable NSObject*)delegate { - if (self = [self initWithDelegate:delegate]) { - self.requestParameters = requestParameters; - self.dataForPage = dataForPage; - } - return self; -} - -- (nullable instancetype)initWithDelegate:(nullable NSObject*)delegate { - if (self = [super init]) { - self.delegate = delegate; - } - return self; -} - @end diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h index ab87548..cbf775e 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.h @@ -22,8 +22,11 @@ @property (nullable, strong, nonatomic) MVMCoreLoadObject *loadObject; @property (nullable, strong, nonatomic) NSDictionary *dataForPage; @property (nullable, strong, nonatomic) DelegateObject *delegateObject; +@property (nullable, nonatomic, readonly) NSURLSessionTask *sessionTask; +@property (nullable, nonatomic, readonly) NSString *finalLoadSource; @property (nonatomic) BOOL backgroundLoad; @property (nonatomic, getter=areDependenciesAdded) BOOL dependenciesAdded; +@property (nonnull, nonatomic, strong) NSString *identifier; /// Legacy flag for if this operation will have an alert to show when finished. @property (nonatomic, readonly) BOOL alertToShow; diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m index d48d53f..dd5b822 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreLoadRequestOperation.m @@ -10,7 +10,6 @@ #import "MVMCoreLoadHandler.h" #import "MVMCoreLoadingOverlayHandler.h" #import "MVMCoreCache.h" -#import "MVMCoreSessionTimeHandler.h" #import "MVMCoreLoggingHandler.h" #import "MVMCoreSessionObject.h" #import "MVMCoreViewControllerMappingObject.h" @@ -55,6 +54,7 @@ - (nullable instancetype)initWithLoadObject:(nullable MVMCoreLoadObject *)loadObject backgroundLoad:(BOOL)backgroundLoad { if (self = [self initWithRequestParameters:loadObject.requestParameters dataForPage:loadObject.dataForPage delegateObject:loadObject.delegateObject backgroundLoad:backgroundLoad]) { + loadObject.identifier = self.identifier; self.loadObject = loadObject; } return self; @@ -77,6 +77,10 @@ [super start]; } +- (NSString *)finalLoadSource { + return _sessionTask.currentRequest.URL.absoluteString; +} + - (void)markAsFinished { // stop any loading animation we may have started @@ -107,6 +111,8 @@ - (void)main { MVMCoreLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate); + [self.requestParameters resolveURL:[MVMCoreSessionObject sharedGlobal]]; + // Always check for cancellation before launching the task. if ([self checkAndHandleForCancellation]) { return; @@ -116,6 +122,7 @@ // No load requested, finish. MVMCoreLoadObject *loadObject = [[MVMCoreLoadObject alloc] initWithRequestParameters:nil dataForPage:self.dataForPage delegateObject:self.delegateObject]; + loadObject.identifier = self.identifier; loadObject.operation = self; [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:nil]; } else if (self.loadObject) { @@ -147,13 +154,25 @@ MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject]; if (!requestForMissingData) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:@"0" requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + // We have all the needed data, continue with the load. [MVMCoreLoadRequestOperation handleLoadObject:loadObject error:nil]; } else { + if(!self.backgroundLoad && loadObject.requestParameters.pageType) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadStartedFor:loadObject.requestParameters.pageType requestUUID:loadObject.identifier requestURL:loadObject.requestParameters.URL.absoluteString]; + } + // Send a new request to the server. [MVMCoreLoadRequestOperation sendRequest:requestForMissingData loadObject:loadObject completionHandler:^(NSDictionary * _Nullable json) { - + + NSString *serverProcessTime = [(NSDictionary *)json objectChainOfKeysOrIndexes:@[@"ResponseInfo", @"timeStamp"]] ?: @"0"; + + if(!self.backgroundLoad && loadObject.requestParameters.pageType && serverProcessTime) { + [[MVMCoreLoggingHandler sharedLoggingHandler] logPageLoadCompleteFor:loadObject.requestParameters.pageType serverProcessingTime:serverProcessTime requestURL:loadObject.requestParameters.URL.absoluteString requestUUID:loadObject.identifier isFromCache:loadObject.pageDataFromCache]; + } + // Process the data retrieved from the server. [MVMCoreLoadRequestOperation processJSONFromServer:json loadObject:loadObject completionHandler:^(MVMCoreLoadObject * _Nonnull loadObject, MVMCoreErrorObject * _Nullable error) { @@ -222,7 +241,7 @@ if (pageFromCache) { loadObject.pageType = self.requestParameters.pageType; } - + loadObject.identifier = self.identifier; // Store if we loaded from the cache or not. loadObject.pageDataFromCache = (pageFromCache != nil); loadObject.moduleDataFromCache = (modulesFromCache != nil); @@ -287,12 +306,12 @@ } else { // Error json not correct format. - MVMCoreErrorObject *errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeJSONNotDictionary domain:ErrorDomainNative location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + MVMCoreErrorObject *errorObject = [[MVMCoreLoadHandler sharedGlobal] errorForLoadObject:loadObject withTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeJSONNotDictionary domain:ErrorDomainSystem]; [MVMCoreLoadRequestOperation loadAbortedWithError:errorObject loadObject:loadObject]; } } else { - // Error with the request/response + [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:error]; [MVMCoreLoadRequestOperation loadAbortedWithError:error loadObject:loadObject]; } }]; @@ -372,7 +391,9 @@ // Update the session timer on a good response. if (loadObject.extendsAppSession) { - [[MVMCoreSessionTimeHandler sharedSessionHandler] startSessionTimer]; + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + [[MVMCoreSessionTimeHandler sharedSessionHandler] startSessionTimer]; + }]; } // Adds the modules received from server to any modules we grabbed from the cache. @@ -494,6 +515,7 @@ if (error.errorScreenError && !error.nativeDrivenErrorScreen && loadObject.pageType.length == 0) { error.logError = YES; error.errorScreenError = NO; + error.silentError = NO; error.location = [NSString stringWithFormat:@"%li-%@",(long)ErrorCodeNoErrorPageSent,error.location]; [MVMCoreLoadRequestOperation handleError:error loadObject:loadObject showAlertForErrorIfApplicable:YES]; [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; @@ -513,9 +535,16 @@ if (viewController) { // Display or finish - if (error.errorScreenError || !loadObject.requestParameters.dontDisplayViewController) { + if (error.errorScreenError) { + error.silentError = NO; + if (error.nativeDrivenErrorScreen) { + [MVMCoreLoggingHandler addErrorToLog:error]; + } + [MVMCoreLoadRequestOperation displayViewController:viewController loadObject:loadObject error:error]; + } else if (!loadObject.requestParameters.dontDisplayViewController) { [MVMCoreLoadRequestOperation displayViewController:viewController loadObject:loadObject error:error]; } else { + [MVMCoreLoggingHandler addErrorToLog:error]; [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:viewController errorObject:error]; } } else if (!loadObject.requestParameters.shouldNotGoToServerOnCacheFail && (loadObject.pageDataFromCache || loadObject.moduleDataFromCache)) { @@ -527,7 +556,7 @@ loadObject.operation.errorForAlertToShow = nil; [loadObject.operation main]; } else { - + [MVMCoreLoggingHandler addErrorToLog:error]; // Otherwise the request is finished. [MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:nil errorObject:error]; } @@ -567,7 +596,8 @@ } else if ([ValueTypeErrorScreen isEqualToString:type]) { // Error Screen, abort the load and handle the screen if necessary - MVMCoreErrorObject *error = [MVMCoreErrorObject createErrorObjectForErrorInfo:loadObject.responseInfoMap location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + MVMCoreErrorObject *error = [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:[MVMCoreErrorObject createErrorObjectForErrorInfo:loadObject.responseInfoMap location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]]; + [MVMCoreLoadRequestOperation loadAbortedWithError:error loadObject:loadObject]; } else { @@ -578,7 +608,7 @@ } else { // Check for controller specific errors. BOOL shouldContinue; - MVMCoreErrorObject *error = [MVMCoreErrorObject createErrorObjectForErrorInfo:loadObject.responseInfoMap location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + MVMCoreErrorObject *error = [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:[MVMCoreErrorObject createErrorObjectForErrorInfo:loadObject.responseInfoMap location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]]; if ([loadObject.delegateObject.loadDelegate respondsToSelector:@selector(checkForDelegateSpecificErrors:loadObject:completionHandler:)]) { shouldContinue = [loadObject.delegateObject.loadDelegate checkForDelegateSpecificErrors:error loadObject:loadObject completionHandler:completionHandler]; } else { @@ -610,16 +640,22 @@ } if (obj && [obj isKindOfClass:[NSDictionary class]]) { - NSDictionary *responseInfo = [obj dict:KeyResponseInfo]; if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) { - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[responseInfo stringForKey:KeyErrorHeading] message:[responseInfo stringForKey:KeyUserMessage] messageToLog:[responseInfo stringForKey:KeyMessage] code:[[responseInfo string:KeyCode] integerValue] domain:ErrorDomainServer location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + errorObject = [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError: + [[MVMCoreErrorObject alloc] + initWithTitle:[responseInfo stringForKey:KeyErrorHeading] + message:[responseInfo stringForKey:KeyUserMessage] + messageToLog:[responseInfo stringForKey:KeyMessage] + code:[[responseInfo string:KeyCode] integerValue] + domain:ErrorDomainServer + location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]]; } // Caches each dictionary from the array. [[MVMCoreCache sharedCache] addModuleToCache:obj module:key queue:nil waitUntilFinished:YES completionBlock:NULL]; } else { - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeJSONNotDictionary domain:ErrorDomainNative location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + errorObject = [[MVMCoreLoadHandler sharedGlobal] errorForLoadObject:loadObject withTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeJSONNotDictionary domain:ErrorDomainSystem]; } if (errorObject) { @@ -662,10 +698,10 @@ if (pageType) { [[MVMCoreCache sharedCache] addPageToCache:obj pageType:pageType queue:nil waitUntilFinished:YES completionBlock:NULL]; } else { - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeNoPageType domain:ErrorDomainNative location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + errorObject = [[MVMCoreLoadHandler sharedGlobal] errorForLoadObject:loadObject withTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeNoPageType domain:ErrorDomainNative]; } } else { - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeJSONNotDictionary domain:ErrorDomainNative location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + errorObject = [[MVMCoreLoadHandler sharedGlobal] errorForLoadObject:loadObject withTitle:nil message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeJSONNotDictionary domain:ErrorDomainNative]; } // Logs the error. @@ -709,7 +745,7 @@ } } else { // Couldn't initialize view controller, serious error. - error = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeInitViewController domain:ErrorDomainNative location:[[MVMCoreLoadHandler sharedGlobal] errorLocationForRequest:loadObject]]; + error = [[MVMCoreLoadHandler sharedGlobal] errorForLoadObject:loadObject withTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical] code:ErrorCodeInitViewController domain:ErrorDomainNative]; } [MVMCoreLoadRequestOperation handleShouldContinue:shouldContinue error:error loadObject:loadObject errorBlock:NULL continueBlock:^{ @@ -738,17 +774,19 @@ return; } - // Logs the error. - if (error.logError) { - [MVMCoreLoggingHandler addErrorToLog:error]; - } - MVMCoreLog(@"Error: %@ %@ %@ %@ %@",[error stringErrorCode], error.domain, error.location,error.messageToDisplay, error.messageToLog); if (showAlertForErrorIfApplicable && (!loadObject.operation.backgroundLoad || loadObject.requestParameters.allowAlertsIfBackgroundRequest) && !loadObject.requestParameters.handleErrorsSilently && !error.silentError && !error.errorScreenError) { // Show alert for error. [MVMCoreLoadRequestOperation createAndShowAlertForLoadObject:loadObject error:error delegateObject:loadObject.operation.delegateObject]; + } else { + error.silentError = !error.errorScreenError && !error.nativeDrivenErrorScreen; + } + + // Logs the error. If its a native driven error screen postpone until the native screen is define with its messaging. + if (error.logError && !error.nativeDrivenErrorScreen) { + [MVMCoreLoggingHandler addErrorToLog:error]; } } diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h b/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h index 2e52a05..28d7f2d 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.h @@ -9,6 +9,7 @@ #import #import +#import // The loading style. // MFLoadStyleDefault: This means it has not been explicitely set by the developer. Standard push. @@ -100,6 +101,9 @@ typedef NS_ENUM(NSInteger, MFLoadStyle) { /// A flag for if it should be a background request or not. @property (assign, nonatomic) BOOL backgroundRequest; +//Unique Identifier for event tracking +@property (nullable, strong, nonatomic) NSString *identifier; + // Creates an object with the given page type and extra parameters. Adds the extra parameters to the standard request parameters. Will add any modules needed by the page type by default. - (nullable instancetype)initWithPageType:(nonnull NSString *)pageType extraParameters:(nullable NSDictionary *)extraParameters; @@ -124,4 +128,7 @@ typedef NS_ENUM(NSInteger, MFLoadStyle) { /// Returns optional and required modules - (nullable NSArray *)allModules; +/// Resolves the URL given the session object and the current request parameters. +- (nonnull NSURL *)resolveURL:(nonnull MVMCoreSessionObject *)sessionObject; + @end diff --git a/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.m b/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.m index 63f4f4f..899ef5f 100644 --- a/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.m +++ b/MVMCore/MVMCore/LoadHandling/MVMCoreRequestParameters.m @@ -10,9 +10,12 @@ #import "NSDictionary+MFConvenience.h" #import "MVMCoreJSONConstants.h" #import "MVMCoreViewControllerMappingObject.h" +#import "MVMCoreConstants.h" @interface MVMCoreRequestParameters () +@property (nonatomic, strong, nullable) NSURL *determinedURL; + - (nullable instancetype)initWithExtraParameters:(nullable NSDictionary *)extraParameters; @end @@ -86,6 +89,56 @@ return self; } +- (void)setAlternateBaseURL:(NSURL *)alternateBaseURL { + _alternateBaseURL = alternateBaseURL; + _URL = self.determinedURL; // Reset resolution when changed. +} + +- (void)setPageType:(NSString *)pageType { + _pageType = pageType; + _URL = self.determinedURL; // Reset resolution when changed. +} + +- (void)setContextRoot:(NSString *)contextRoot { + _contextRoot = contextRoot; + _URL = self.determinedURL; // Reset resolution when changed. +} + +- (void)setURL:(NSURL *)URL { + self.determinedURL = URL; // If set directly, this becomes the ultimate URL. + _URL = URL; +} + +- (NSURL *)resolveURL:(MVMCoreSessionObject *)sessionObject { + + if (self.URL) { + // Previously resovled. + return self.URL; + } + + NSURL *url; + if (self.alternateBaseURL) { + url = self.alternateBaseURL; + } else { + url = sessionObject.baseURL ?: [NSURL URLWithString:URLProdPostpayBase]; + } + + // Appends the context root. + if (self.contextRoot) { + url = [url URLByAppendingPathComponent:self.contextRoot]; + } else if (sessionObject.contextRoot) { + url = [url URLByAppendingPathComponent:[MVMCoreSessionObject sharedGlobal].contextRoot]; + } + + // Appends the page type + if (self.pageType) { + url = [url URLByAppendingPathComponent:self.pageType]; + } + + _URL = url; + return url; +} + - (void)addRequestParameters:(nonnull NSDictionary *)parameters { if ([parameters count] > 0) { @@ -152,6 +205,8 @@ copyObject.customTimeoutTime = self.customTimeoutTime; copyObject.backgroundRequest = self.backgroundRequest; copyObject.URL = self.URL; + copyObject.determinedURL = self.determinedURL; + copyObject.actionMap = self.actionMap; return copyObject; } diff --git a/MVMCore/MVMCore/MVMCore.h b/MVMCore/MVMCore/MVMCore.h index 8e1a01f..fd51f0a 100644 --- a/MVMCore/MVMCore/MVMCore.h +++ b/MVMCore/MVMCore/MVMCore.h @@ -63,7 +63,6 @@ FOUNDATION_EXPORT const unsigned char MVMCoreVersionString[]; #import #import #import -#import // Action Handling #import @@ -82,5 +81,4 @@ FOUNDATION_EXPORT const unsigned char MVMCoreVersionString[]; // Singletons #import -#import #import diff --git a/MVMCore/MVMCore/MainProtocols/MVMCoreLoggingDelegateProtocol.h b/MVMCore/MVMCore/MainProtocols/MVMCoreLoggingDelegateProtocol.h index b8ae254..fc0e353 100644 --- a/MVMCore/MVMCore/MainProtocols/MVMCoreLoggingDelegateProtocol.h +++ b/MVMCore/MVMCore/MainProtocols/MVMCoreLoggingDelegateProtocol.h @@ -7,7 +7,6 @@ // #import -@class MVMCoreAlertController; @protocol MVMCoreLoggingDelegateProtocol @@ -25,7 +24,4 @@ // Log that the load has finished. - (void)logLoadFinished:(nullable MVMCoreLoadObject *)loadObject loadedViewController:(nullable UIViewController *)loadedViewController error:(nullable MVMCoreErrorObject *)error; -// Log alert -- (void)logAlertForAlertController:(nullable MVMCoreAlertController *)alertController; - @end diff --git a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift index 8ead4e1..f0d78c2 100644 --- a/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift +++ b/MVMCore/MVMCore/Models/ActionType/Client Parameters/ClientParameterHandler.swift @@ -7,10 +7,8 @@ // @objcMembers open class ClientParameterHandler: NSObject { - - var parameterHandlerList: [ClientParameterProtocol] = [] - let parametersWorkQueue = DispatchQueue(label: "com.mva.clientparameter") - let group = DispatchGroup() + + static let DefaultTimeout = 30.0 open func createParametersHandler(_ clientParameterModel: ClientParameterModelProtocol) -> ClientParameterProtocol? { do { @@ -43,38 +41,41 @@ /// ] ///} /// completionHandler can return flat dictinary or a map. It depends on the paramters handler - open func getParameters(with clientParameters: [String: Any], requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { + open func getParameters(with clientParameters: [String: Any], requestParameters: [String: Any], actionId: String, completionHandler:@escaping ([String: Any]?) -> ()) throws { guard let clientParameterModel = try getClientParameterModel(clientParameters) else { completionHandler(nil) return } - try getParameters(with: clientParameterModel, requestParameters: requestParameters, completionHandler: completionHandler) + try getParameters(with: clientParameterModel, requestParameters: requestParameters, actionId: actionId, completionHandler: completionHandler) } - open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], completionHandler:@escaping ([String: Any]?) -> ()) throws { - - let timeout = model.timeout ?? 30.0 - + open func getParameters(with model: ClientParameterModel, requestParameters: [String: Any], actionId: String, completionHandler:@escaping ([String: Any]?) -> ()) throws { + + let parametersWorkQueue = DispatchQueue(label: "com.mva.clientparameter") + let group = DispatchGroup() + let timeout = model.timeout ?? Self.DefaultTimeout + + let parameterHandlerList = model.list.compactMap { createParametersHandler($0) } + let requestUUID = (0.. [String: AnyHashable] in + guard let parameter = element else { + MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterTimeout( + name: model.list[index].type, + uuid: requestUUID[index], + actionId: actionId)) + return parameterHandlerList[index].valueOnTimeout() + } + return parameter + }.reduce(into: [String: AnyHashable]()) { partialResult, next in + partialResult.merge(next) { first, last in first } } - return parametersList } // Setup completion handlers. Barriered to ensure one happens after the other. @@ -92,28 +93,30 @@ } // Setup timeout. - self.parametersWorkQueue.asyncAfter(deadline: .now() + .seconds(Int(timeout)), execute: timeoutWorkItem) + parametersWorkQueue.asyncAfter(deadline: .now() + .seconds(Int(timeout)), execute: timeoutWorkItem) // Setup the parameter execution. - for (index, parameterHandler) in self.parameterHandlerList.enumerated() { + for (index, parameterHandler) in parameterHandlerList.enumerated() { let parameterType = parameterHandler.clientParameterModel.type - self.group.enter() + MVMCoreLoggingHandler.shared()?.logCoreEvent(.clientParameterStartFetch(name: parameterType, uuid: requestUUID[index], actionId: actionId)) + group.enter() parameterHandler.fetchClientParameters(requestParameters: requestParameters, timingOutIn: timeout) { (receivedParameter) in // Queue the results for merge. - self.parametersWorkQueue.async { + 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 - self.group.leave() // Leaving is only done after setup (barriered). + group.leave() // Leaving is only done after setup (barriered). } } } } // Callback when all parameters have been merged. - self.group.notify(queue: self.parametersWorkQueue, work: completionWorkItem); + group.notify(queue: parametersWorkQueue, work: completionWorkItem); } } } diff --git a/MVMCore/MVMCore/Models/JSON/JSONHelper.swift b/MVMCore/MVMCore/Models/JSON/JSONHelper.swift index a0c20d0..857310b 100644 --- a/MVMCore/MVMCore/Models/JSON/JSONHelper.swift +++ b/MVMCore/MVMCore/Models/JSON/JSONHelper.swift @@ -18,6 +18,31 @@ public enum JSONError: Error { case error(message: String) } +extension JSONError: LocalizedError, CustomStringConvertible { + + public var description: String { + switch self { + case .pathNotFound: + return "JSON path not found" + case .data(path: let path): + return "JSON data in \(path) is corrupt" + case .other(error: let error): + if let decodingError = error as? DecodingError { + // the only way to get the decoding error description and details... + return (decodingError as NSError).description + } + return error.localizedDescription + case .error(message: let message): + return message + } + } + + // Shown to customers. + public var errorDescription: String? { + return MVMCoreGetterUtility.hardcodedString(withKey: HardcodedErrorUnableToProcess) + } +} + extension JSONDictionary { public func toJSONString(options: JSONSerialization.WritingOptions = []) throws -> String { diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m index c402552..4c121bf 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreCache.m @@ -592,6 +592,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt"; //system stores cache based datatask and check cache based on response, //need to manually store cache for response [sharedCache storeCachedResponse:cachedReponse forRequest:request]; + } else if (error) { + [MVMCoreLoggingHandler addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:@"MVMCoreCache->downloadImage"]]; + } else { + [MVMCoreLoggingHandler addErrorToLog:[[MVMCoreErrorObject alloc] initWithTitle:nil messageToLog:[NSString stringWithFormat:@"Failed to download image %@", request.URL.absoluteString] code:0 domain:ErrorDomainSystem location:@"MVMCoreCache->downloadImage"]]; } if (isGif) { [self checkImage:nil imageData:data fallbackImage:fallbackImageName completionHandler:completionHandler]; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift b/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift new file mode 100644 index 0000000..8ccae44 --- /dev/null +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreEvent.swift @@ -0,0 +1,184 @@ +// +// MVMCoreEvent.swift +// MVMCore +// +// https://oneconfluence.verizon.com/display/MFD/NewRelic+Client+Event+Logging +// +// Created by Kyle on 8/29/22. +// Copyright © 2022 myverizon. All rights reserved. +// + +import Foundation + +/// A list of possible events from the app. +public enum MVMCoreEvent { + + // ---------------------------- + // MARK: Action Events + // ---------------------------- + + /// Failed to decode the action payload. + case actionFailedToDecode( + pageType: String, + error: Error? + ) + + /// Could not find the action specified.. + case actionNotFound( + name: String, + pageType: String + ) + + /// The webview bridge action handler was invoked and is in progress. + case actionInvoked( + name: String, + pageType: String, + uuid: String + ) + + /// The action failed.. + case actionFailed( + name: String, + pageType: String, + uuid: String, + error: Error? + ) + + /// The action is completed. + case actionComplete( + name: String, + pageType: String, + uuid: String + ) + + // ---------------------------- + // MARK: ClientParameter Events + // ---------------------------- + + /// Could not find the client parameter specified. + case clientParameterNotFound( + name: String, + actionId: String + ) + + /// The client perameter handler was invoked and is in progress. + case clientParameterStartFetch( + name: String, + uuid: String, + actionId: String + ) + + /// The client perameter handler timed out and is returning a default value. + case clientParameterTimeout( + name: String, + uuid: String, + actionId: String + ) + + /// The client paramter fetch completed. + case clientParameterFetchComplete( + name: String, + uuid: String, + actionId: String + ) + + case pageStarted( + pageType: String, + requestUUID: String + ) + + case pageLoadStarted( + pageType: String, + requestUUID: String, + requestURL: String + ) + + case pageLoadComplete( + pageType: String, + requestUUID: String, + serverProcessingTime: String, + requestURL: String, + isFromCache: Bool + ) + + case pageProcessingComplete( + pageType: String, + requestUUID: String, + webUrl: String? + ) + + case pageRenderComplete( + pageType: String, + requestUUID: String, + templateName: String?, + controllerName: String, + error: String? + ) + + public enum EventType: String { + case action + case clientParameter + case pageLoad + + public var notification: Notification.Name { + return Notification.Name(rawValue: rawValue) + } + } + + public var type: EventType { + switch self { + case .actionFailedToDecode: return .action + case .actionNotFound: return .action + case .actionInvoked: return .action + case .actionFailed: return .action + case .actionComplete: return .action + case .clientParameterNotFound: return .clientParameter + case .clientParameterStartFetch: return .clientParameter + case .clientParameterTimeout: return .clientParameter + case .clientParameterFetchComplete: return .clientParameter + case .pageStarted, .pageLoadStarted, .pageLoadComplete, .pageProcessingComplete, .pageRenderComplete: return .pageLoad + } + } + + public var name: String { + let name = String(describing: self) + if let attribIndex = name.firstIndex(of: "(") { + return String(name[.. #import #import -@class MVMCoreAlertController; #define MVMCoreLog(fmt, ...) \ [MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__]]; @@ -24,7 +23,7 @@ + (void)logDebugMessageWithDelegate:(nullable NSString *)message; + (void)logWithDelegateWithObject:(nullable id)object withName:(nullable NSString *)name withExtraInfo:(nullable NSDictionary *)extra; + (void)logWithDelegateLoadFinished:(nullable MVMCoreLoadObject *)loadObject loadedViewController:(nullable UIViewController *)loadedViewController error:(nullable MVMCoreErrorObject *)error; -+ (void)logAlertForAlertController:(nullable MVMCoreAlertController *)alertController; +- (void)recordEvent:(nonnull NSString *)name attributes:(nullable NSDictionary *)attributes; #pragma mark MVMCoreLoggingDelegateProtocol - (void)handleDebugMessage:(nullable NSString *)message; diff --git a/MVMCore/MVMCore/OtherHandlers/MVMCoreLoggingHandler.m b/MVMCore/MVMCore/OtherHandlers/MVMCoreLoggingHandler.m index 766ee3d..4fb541a 100644 --- a/MVMCore/MVMCore/OtherHandlers/MVMCoreLoggingHandler.m +++ b/MVMCore/MVMCore/OtherHandlers/MVMCoreLoggingHandler.m @@ -45,11 +45,7 @@ } } -+ (void)logAlertForAlertController:(nullable MVMCoreAlertController *)alertController { - if ([[MVMCoreObject sharedInstance].loggingDelegate respondsToSelector:@selector(logAlertForAlertController:)]) { - [[MVMCoreObject sharedInstance].loggingDelegate logAlertForAlertController:alertController]; - } -} +- (void)recordEvent:(nonnull NSString *)name attributes:(nullable NSDictionary *)attributes {} #pragma mark - logging delegate diff --git a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h index 86c056d..8548c81 100644 --- a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h +++ b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.h @@ -136,9 +136,6 @@ // Use this to dismiss all presented view controllers stacked on the bottom one. - (void)dismissToBottom:(BOOL)animated; -// Removes all queued up items. -- (void)cancelPresentation; - #pragma mark - Presentation Extra // Use this to present. diff --git a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m index 15aea59..a721f50 100644 --- a/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m +++ b/MVMCore/MVMCore/PresentationHandling/MVMCoreNavigationHandler.m @@ -46,9 +46,6 @@ self.navigationQueue = [[NSOperationQueue alloc] init]; self.navigationQueue.maxConcurrentOperationCount = 1; - self.presentationQueue = [[NSOperationQueue alloc] init]; - self.presentationQueue.maxConcurrentOperationCount = 1; - self.delegates = (NSHashTable *)[NSHashTable weakObjectsHashTable]; } return self; @@ -308,10 +305,6 @@ [self dismissToBottom:animated delegate:nil completionHandler:NULL]; } -- (void)cancelPresentation { - [self.presentationQueue cancelAllOperations]; -} - #pragma mark - Presentation Extra - (void)presentViewController:(nonnull UIViewController *)viewController animated:(BOOL)animated delegate:(nullable NSObject*)delegate completionHandler:(nullable void (^)(void))completionBlock { @@ -325,7 +318,7 @@ MVMCorePresentViewControllerOperation *operation = [[MVMCorePresentViewControllerOperation alloc] initWithPresentingViewController:controllerToPresentOn presentedViewController:viewController animated:animated]; operation.delegate = delegate; operation.completionBlock = completionBlock; - [self.presentationQueue addOperation:operation]; + [self.navigationQueue addOperation:operation]; } }]; } diff --git a/MVMCore/MVMCore/PresentationHandling/MVMCorePresentViewControllerOperation.m b/MVMCore/MVMCore/PresentationHandling/MVMCorePresentViewControllerOperation.m index fc65e88..0e757ab 100644 --- a/MVMCore/MVMCore/PresentationHandling/MVMCorePresentViewControllerOperation.m +++ b/MVMCore/MVMCore/PresentationHandling/MVMCorePresentViewControllerOperation.m @@ -5,119 +5,70 @@ // Created by Scott Pfeil on 9/28/15. // Copyright © 2015 Verizon Wireless. All rights reserved. // -// -//#import "MVMCorePresentViewControllerOperation.h" -//#import "MVMCoreAlertController.h" -//#import "MVMCorePresentAnimationOperation.h" -//#import "MVMCoreDispatchUtility.h" -//#import "MVMCoreConstants.h" -// -//@interface MVMCorePresentViewControllerOperation () -// -//@property (nullable, strong, nonatomic) UIViewController *presentingViewController; -//@property (nullable, strong, nonatomic) UIViewController *presentedViewController; -//@property (nonatomic) BOOL animate; -// -//@property (nonatomic) BOOL beingObserved; -//@property (nullable, weak, nonatomic) MVMCoreAlertController *alertController; -// -//@end -// -//@implementation MVMCorePresentViewControllerOperation -// -//// The context for kvo -//static void * XXContext = &XXContext; -// -//- (void)dealloc { -// [self stopObservingAlertView]; -//} -// -//- (nullable instancetype)initWithPresentingViewController:(nullable UIViewController *)presentingViewController presentedViewController:(nullable UIViewController *)presentedViewController animated:(BOOL)animated { -// -// if (self = [super init]) { -// self.presentedViewController = presentedViewController; -// self.presentingViewController = presentingViewController; -// self.animate = animated; -// } -// return self; -//} -// -//- (void)main { -// [MVMCoreDispatchUtility performBlockOnMainThread:^{ -// // Always check for cancellation before launching the task. -// if ([self checkAndHandleForCancellation]) { -// return; -// } -// -// [self present]; -// }]; -//} -// -//- (void)present { -// -// if ([self checkAndHandleForCancellation]) { -// return; -// } -// -// if (self.presentingViewController && self.presentedViewController) { -// -// // Gets the top most presented. -// UIViewController *controllerToPresentOn = self.presentingViewController; -// while (controllerToPresentOn.presentedViewController) { -// controllerToPresentOn = controllerToPresentOn.presentedViewController; -// } -// -// // If an alert is showing, don't present until the alerts are dismissed. -// if ([controllerToPresentOn isKindOfClass:[MVMCoreAlertController class]]) { -// [self observeForAlertDismissal:(MVMCoreAlertController *)controllerToPresentOn]; -// } else { -// MVMCorePresentAnimationOperation *animationOperation = [[MVMCorePresentAnimationOperation alloc] initWithPresentingViewController:controllerToPresentOn presentedViewController:self.presentedViewController animated:self.animate]; -// animationOperation.delegate = self.delegate; -// [animationOperation setCompletionBlock:^{ -// [self markAsFinished]; -// // Notify that page has changed -// dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ -// [[NSNotificationCenter defaultCenter] postNotificationName:MVMCoreNotificationViewControllerChanged object:nil]; -// }); -// }]; -// [[NSOperationQueue mainQueue] addOperation:animationOperation]; -// } -// } else { -// [self markAsFinished]; -// } -//} -// -//- (void)cancel { -// [super cancel]; -// [self stopObservingAlertView]; -//} -// -//#pragma mark - Observer Functions -// -//- (void)observeForAlertDismissal:(MVMCoreAlertController *)alertController { -// if (!self.beingObserved) { -// self.beingObserved = YES; -// self.alertController = alertController; -// [alertController addObserver:self forKeyPath:@"visible" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:XXContext]; -// } -//} -// -//- (void)stopObservingAlertView { -// if (self.beingObserved) { -// [self.alertController removeObserver:self forKeyPath:@"visible" context:XXContext]; -// self.alertController = nil; -// self.beingObserved = NO; -// } -//} -// -//- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { -// -// if (context == XXContext && [keyPath isEqualToString:@"visible"]) { -// if (![object isVisible]) { -// [self stopObservingAlertView]; -// [self present]; -// } -// } -//} -// -//@end + +#import "MVMCorePresentViewControllerOperation.h" +#import "MVMCorePresentAnimationOperation.h" +#import "MVMCoreDispatchUtility.h" +#import "MVMCoreConstants.h" + +@interface MVMCorePresentViewControllerOperation () + +@property (nullable, strong, nonatomic) UIViewController *presentingViewController; +@property (nullable, strong, nonatomic) UIViewController *presentedViewController; +@property (nonatomic) BOOL animate; + +@end + +@implementation MVMCorePresentViewControllerOperation + +- (nullable instancetype)initWithPresentingViewController:(nullable UIViewController *)presentingViewController presentedViewController:(nullable UIViewController *)presentedViewController animated:(BOOL)animated { + + if (self = [super init]) { + self.presentedViewController = presentedViewController; + self.presentingViewController = presentingViewController; + self.animate = animated; + } + return self; +} + +- (void)main { + [MVMCoreDispatchUtility performBlockOnMainThread:^{ + // Always check for cancellation before launching the task. + if ([self checkAndHandleForCancellation]) { + return; + } + + [self present]; + }]; +} + +- (void)present { + + if ([self checkAndHandleForCancellation]) { + return; + } + + if (self.presentingViewController && self.presentedViewController) { + + // Gets the top most presented. + UIViewController *controllerToPresentOn = self.presentingViewController; + while (controllerToPresentOn.presentedViewController) { + controllerToPresentOn = controllerToPresentOn.presentedViewController; + } + + MVMCorePresentAnimationOperation *animationOperation = [[MVMCorePresentAnimationOperation alloc] initWithPresentingViewController:controllerToPresentOn presentedViewController:self.presentedViewController animated:self.animate]; + animationOperation.delegate = self.delegate; + [animationOperation setCompletionBlock:^{ + [self markAsFinished]; + // Notify that page has changed + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [[NSNotificationCenter defaultCenter] postNotificationName:MVMCoreNotificationViewControllerChanged object:nil]; + }); + }]; + [[NSOperationQueue mainQueue] addOperation:animationOperation]; + } else { + [self markAsFinished]; + } +} + +@end diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionObject.h b/MVMCore/MVMCore/Session/MVMCoreSessionObject.h index 8c86697..2f676b0 100644 --- a/MVMCore/MVMCore/Session/MVMCoreSessionObject.h +++ b/MVMCore/MVMCore/Session/MVMCoreSessionObject.h @@ -36,7 +36,7 @@ - (nullable NSDictionary *)getInitialParametersExcludingSections:(nullable NSSet *)excludeSections; /// Restarts the application session state. Can clear variables and pass a page type if needed. -- (void)restartSessionWithPageType:(nullable NSString *)pageType parameters:(nullable NSDictionary *)parameters clearAllVariables:(BOOL)clearAllVariables; +- (void)restartSessionWithPageType:(nullable NSString *)pageType requestUrl:(nullable NSURL *)requestUrl parameters:(nullable NSDictionary *)parameters clearAllVariables:(BOOL)clearAllVariables; /// Redirect, leaving the current app experience. - (void)redirectWithInfo:(nullable NSDictionary *)dictionary; diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionObject.m b/MVMCore/MVMCore/Session/MVMCoreSessionObject.m index bcdf390..e5bed5e 100644 --- a/MVMCore/MVMCore/Session/MVMCoreSessionObject.m +++ b/MVMCore/MVMCore/Session/MVMCoreSessionObject.m @@ -36,7 +36,7 @@ return nil; } -- (void)restartSessionWithPageType:(nullable NSString *)pageType parameters:(nullable NSDictionary *)parameters clearAllVariables:(BOOL)clearAllVariables { +- (void)restartSessionWithPageType:(nullable NSString *)pageType requestUrl:(nullable NSURL *)requestUrl parameters:(nullable NSDictionary *)parameters clearAllVariables:(BOOL)clearAllVariables { // Clears the singletons of any session related data. if (clearAllVariables) { [self clearSessionObject]; diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h deleted file mode 100644 index d31ae62..0000000 --- a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// MVMCoreSessionTimeHandler.h -// myverizon -// -// Created by Scott Pfeil on 2/25/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// -// Handles the session timer. - -#import - -@class MVMCoreErrorObject; - -@interface MVMCoreSessionTimeHandler : NSObject - -// The time that we started the last session timer. -@property (assign, nonatomic, readonly) NSTimeInterval timeTimerStarted; - -// Keeps track of if the session is currently being timed. Used for entering from the background. -@property (assign, nonatomic, readonly) BOOL sessionBeingTimed; - -// Keeps track of if the session has already timed out. -@property (assign, nonatomic, readonly) BOOL sessionTimedOut; - -#pragma mark - functions to override - -// Can override to provide a time until the warning shows in seconds. Set to 0 if there should be no warning. Default is 0 -- (NSTimeInterval)timeUntilWarning; - -// Can override to provide a time until the timeout happens in seconds. If there is a warning, then this value is used after the warning happens. Set to 0 if there should be no timeout. Default is 0. -- (NSTimeInterval)timeUntilTimeout; - -// Starts the timeout timer. Override to handle what happens on timeout warning. Should call super if want the timeout timer going. -- (void)sessionTimeoutWarning NS_REQUIRES_SUPER; - -// Called when the session has timed out. Override to handle what happens on timeout. Should call super. Can be called to force timeout... should never need to call unless simulating timout. -- (void)sessionTimeout:(BOOL)whileInBackground NS_REQUIRES_SUPER; - -// Keeps the session alive. A boolean for if we should show the alert if there is an error. Does nothing by default. Can override to do something. -- (void)sendKeepAliveToServer:(BOOL)notifyUserIfError; - -// Invalidates the server session and then calls the completion handler. Error may or may not populate. By default this only calls the completion handler, override to invalidate your server session as you see fit then call completion. -- (void)invalidateSession:(void (^ __nullable)(MVMCoreErrorObject * _Nullable error))completion; - -#pragma mark - Session timer functions - -// Returns the shared instance of this singleton -+ (nullable instancetype)sharedSessionHandler; - -// Starts the session timer. Should be called after every response from the server. Happens on the main thread. -- (void)startSessionTimer; - -// Should only be used in rare occassions, like on the original wifi screen. -- (void)stopSessionTimer; - -// Returns whether the app is in session. -- (BOOL)isAppInSession; - -// Resets everything. -- (void)resetState; - -@end diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m deleted file mode 100644 index f0bf1b5..0000000 --- a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.m +++ /dev/null @@ -1,199 +0,0 @@ -// -// MVMCoreSessionTimeHandler.m -// myverizon -// -// Created by Scott Pfeil on 2/25/14. -// Copyright (c) 2014 Verizon Wireless. All rights reserved. -// - -#import "MVMCoreSessionTimeHandler.h" -#import "MVMCoreLoggingHandler.h" -#import "MVMCoreSessionObject.h" -#import "MVMCoreLoadHandler.h" -#import "MVMCoreLoadingOverlayHandler.h" -#import "MVMCoreGetterUtility.h" -#import "MVMCoreErrorConstants.h" -#import "MVMCoreErrorObject.h" -#import "MVMCoreRequestParameters.h" -#import "NSDictionary+MFConvenience.h" -#import "MVMCoreObject.h" -#import "MVMCoreActionUtility.h" - -@interface MVMCoreSessionTimeHandler () - -@property (strong, nonatomic) NSTimer *sessionWarningTimer; -@property (strong, nonatomic) NSTimer *sessionTimer; - -// The time that we started the last session timer. -@property (assign, nonatomic, readwrite) NSTimeInterval timeTimerStarted; - -// Keeps track of if the session is currently being timed. Used for entering from the background. -@property (assign, nonatomic, readwrite) BOOL sessionBeingTimed; - -// Keeps track of if the session has already timed out. -@property (assign, nonatomic, readwrite) BOOL sessionTimedOut; - -@property (assign, nonatomic) NSTimeInterval secondsUntilWarning; -@property (assign, nonatomic) NSTimeInterval secondsUntilTimeout; - -// Should be called when the app enters the background. -- (void)appEnteredBackground; - -// Should be called when the app enters the foreground. -- (void)appEnteredForeground; - -@end - -@implementation MVMCoreSessionTimeHandler - -+ (nullable instancetype)sharedSessionHandler { - return [MVMCoreActionUtility initializerClassCheck:[MVMCoreObject sharedInstance].sessionHandler classToVerify:self]; -} - -- (instancetype)init { - if (self = [super init]) { - // Adds notifications for if the app entered the background/foreground. - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnteredBackground) name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(appEnteredForeground) name:UIApplicationWillEnterForegroundNotification object:nil]; - } - return self; -} - -- (void)dealloc { - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; - [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; -} - -#pragma mark - functions to override - -- (NSTimeInterval)timeUntilWarning { - return 0; -} - -- (NSTimeInterval)timeUntilTimeout { - return 0; -} - -- (void)sessionTimeoutWarning { -} - -- (void)sessionTimeout:(BOOL)whileInBackground { - self.sessionTimedOut = YES; - [self stopSessionTimer]; -} - -- (void)sendKeepAliveToServer:(BOOL)notifyUserIfError { -} - -#pragma mark - Session timer functions - -- (void)startSessionTimer { - dispatch_async(dispatch_get_main_queue(), ^(void) { - - if (!self.sessionTimedOut) { - - [self.sessionWarningTimer invalidate]; - [self.sessionTimer invalidate]; - - self.secondsUntilWarning = [self timeUntilWarning]; - self.secondsUntilTimeout = [self timeUntilTimeout]; - if (!fequal(0, self.secondsUntilTimeout)) { - self.sessionBeingTimed = YES; - self.timeTimerStarted = [NSDate timeIntervalSinceReferenceDate]; - } - - // Only start physical timer if active, otherwise will begin once entering foreground. - if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive && !fequal(0, self.secondsUntilTimeout)) { - if (!fequal(0, self.secondsUntilWarning)) { - self.sessionWarningTimer = [NSTimer scheduledTimerWithTimeInterval:self.secondsUntilWarning target:self selector:@selector(sessionTimeoutWarning) userInfo:nil repeats:NO]; - } - self.sessionTimer = [NSTimer scheduledTimerWithTimeInterval:self.secondsUntilTimeout target:self selector:@selector(sessionTimeout:) userInfo:nil repeats:NO]; - } - } - }); -} - -- (void)stopSessionTimer { - - // nil timer, session no longer timed. - dispatch_async(dispatch_get_main_queue(), ^(void) { - self.sessionBeingTimed = NO; - [self.sessionWarningTimer invalidate]; - self.sessionWarningTimer = nil; - [self.sessionTimer invalidate]; - self.sessionTimer = nil; - }); -} - -- (void)appEnteredBackground { - - // Session is still being timed. Invalidates here, will start up again on enter foreground if need be. - dispatch_async(dispatch_get_main_queue(), ^(void) { - [self.sessionWarningTimer invalidate]; - self.sessionWarningTimer = nil; - [self.sessionTimer invalidate]; - self.sessionTimer = nil; - }); -} - -- (void)appEnteredForeground { - - if (self.sessionBeingTimed || self.sessionTimedOut) { - - NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; - if ((!fequal(0, self.secondsUntilWarning) && now > self.timeTimerStarted + self.secondsUntilWarning) || (!fequal(0, self.secondsUntilTimeout) && now > self.timeTimerStarted + self.secondsUntilTimeout)) { - - // Timeout if we are passed the warning. - [self sessionTimeout:YES]; - } else { - if (!fequal(0, self.secondsUntilWarning)) { - - // Restart the warning timer! - NSTimeInterval timeLeftTillWarning = self.timeTimerStarted + self.secondsUntilWarning - now; - [self.sessionWarningTimer invalidate]; - self.sessionWarningTimer = [NSTimer scheduledTimerWithTimeInterval:timeLeftTillWarning target:self selector:@selector(sessionTimeoutWarning) userInfo:nil repeats:NO]; - } - if (!fequal(0, self.secondsUntilTimeout)) { - - // Restart the timeout timer! - NSTimeInterval timeLeftTillTimeout = self.timeTimerStarted + self.secondsUntilTimeout - now; - [self.sessionTimer invalidate]; - self.sessionTimer = [NSTimer scheduledTimerWithTimeInterval:timeLeftTillTimeout target:self selector:@selector(sessionTimeout:) userInfo:nil repeats:NO]; - } - } - } -} - -- (void)invalidateSession:(void (^ __nullable)(MVMCoreErrorObject * _Nullable error))completion { - completion(nil); -} - -// Checks to make sure session is still valid and that the timer is running. -- (void)revalidateSessionTimestamp { - if (self.sessionBeingTimed && !self.sessionTimedOut) { - - NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate]; - if ((!fequal(0, self.secondsUntilWarning) && now > self.timeTimerStarted + self.secondsUntilWarning) || (!fequal(0, self.secondsUntilTimeout) && now > self.timeTimerStarted + self.secondsUntilTimeout)) { - self.sessionTimedOut = YES; - [self.sessionWarningTimer invalidate]; - self.sessionWarningTimer = nil; - [self.sessionTimer invalidate]; - self.sessionTimer = nil; - } else if (![self.sessionTimer isValid]) { - // Restart the session timer! - [self startSessionTimer]; - } - } -} - -- (BOOL)isAppInSession { - [self revalidateSessionTimestamp]; - return self.sessionBeingTimed && !self.sessionTimedOut; -} - -- (void)resetState { - [self stopSessionTimer]; - self.sessionTimedOut = NO; -} - -@end diff --git a/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.swift b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.swift new file mode 100644 index 0000000..7f8d0ef --- /dev/null +++ b/MVMCore/MVMCore/Session/MVMCoreSessionTimeHandler.swift @@ -0,0 +1,204 @@ +// +// MVMCoreSessionTimeHandler.swift +// MVMCore +// +// Created by Nandhini Rajendran on 03/05/23. +// Copyright © 2023 myverizon. All rights reserved. +// +// Managed on the main thread because of the run loop requirements of the timers. + +@MainActor +@objc open class MVMCoreSessionTimeHandler: NSObject { + /// Keeps track of if the session is currently being timed. Used for entering from the background. + public var sessionBeingTimed: Bool = false + /// Keeps track of if the session has already timed out. + public var sessionTimedOut: Bool = false + private var sessionWarningTimer: Timer? + private var sessionTimer: Timer? + /// The time that we started the last session timer. + private var timeTimerStarted: TimeInterval? + private var secondsUntilWarning: TimeInterval = 0 + private var secondsUntilTimeout: TimeInterval = 0 + + @objc(sharedSessionHandler) + public static func shared() -> Self { + return MVMCoreActionUtility.fatalClassCheck(object: MVMCoreObject.sharedInstance()?.sessionHandler) + } + + nonisolated public override init() { + super.init() + + Task { @MainActor in + // Adds notifications for if the app entered the background/foreground. + NotificationCenter.default.addObserver(self, selector: #selector(appEnteredBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(appEnteredForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + } + } + + deinit { + Task { @MainActor in + NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) + NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) + } + } + + // MARK: - functions to override + + /// Can override to provide a time until the warning shows in seconds. Set to 0 if there should be no warning. Default is 0. + open func timeUntilWarning() -> TimeInterval { + return 0 + } + + /// Can override to provide a time until the timeout happens in seconds. If there is a warning, then this value is used after the warning happens. Set to 0 if there should be no timeout. Default is 0. + open func timeUntilTimeout() -> TimeInterval { + return 0 + } + + /// Starts the timeout timer. Override to handle what happens on timeout warning. Should call super if want the timeout timer going. + @objc open func sessionTimeoutWarning() { } + + /// Called when the session has timed out. Override to handle what happens on timeout. Should call super. Can be called to force timeout... should never need to call unless simulating timout. + @objc open func sessionTimeout(_ whileInBackground: Bool) { + sessionTimedOut = true + stopSessionTimer() + } + + /// Keeps the session alive. A boolean for if we should show the alert if there is an error. Does nothing by default. Can override to do something. + @objc(sendKeepAliveToServer:) + open func sendKeepAlive(toServer notifyUserIfError: Bool) { } + + // MARK: - State + func markStartTime() { + timeTimerStarted = Date().timeIntervalSinceReferenceDate + } + + func resetStartTime() { + timeTimerStarted = nil + } + + /// The number of seconds ago that we started the last session timer. A value of nil will be returned for sessions not started. + public func timeSinceStart() -> TimeInterval? { + guard let timeTimerStarted = timeTimerStarted else { return nil } + return Date().timeIntervalSinceReferenceDate - timeTimerStarted + } + + func isWarningEnabled() -> Bool { + return isTimeoutEnabled() && !MVMCoreGetterUtility.fequal(a: 0, b: Float(secondsUntilWarning)) + } + + func isTimeoutEnabled() -> Bool { + return !MVMCoreGetterUtility.fequal(a: 0, b: Float(secondsUntilTimeout)) + } + + /// The number of seconds remaining until a warning will occur. A value of nil will be returned for sessions not started or warning tracking is not enabled. + func remainingTimeUntilWarning() -> TimeInterval? { + guard isWarningEnabled(), + let timeTimerStarted = timeSinceStart() else { return nil } + return secondsUntilWarning - timeTimerStarted + } + + /// The number of seconds remaining until a timeout will occur. A value of nil will be returned for sessions not started or timeout tracking is not enabled. + public func remainingTimeUntiTimeout() -> TimeInterval? { + guard isTimeoutEnabled(), + let timeTimerStarted = timeSinceStart() else { return nil } + return secondsUntilTimeout - timeTimerStarted + } + + // MARK: - Session timer functions + + /// Starts the session timer. Should be called after every response from the server. Happens on the main thread. + @objc public func startSessionTimer() { + guard !sessionTimedOut else { return } + sessionWarningTimer?.invalidate() + sessionTimer?.invalidate() + + secondsUntilWarning = timeUntilWarning() + secondsUntilTimeout = timeUntilTimeout() + + guard isTimeoutEnabled() else { return } + sessionBeingTimed = true + markStartTime() + resumeSessionTimer() + } + + /// Resume the session timers if they are stopped. + func resumeSessionTimer() { + // Only start physical timer if active, otherwise will begin once entering foreground. FaceId prompt is considered UIApplicationStateInactive. + guard UIApplication.shared.applicationState != UIApplication.State.background, + let remainingTimeUntiTimeout = remainingTimeUntiTimeout(), + !(sessionTimer?.isValid ?? false) else { return } + // Session timeout, implicitly whileInBackround as no. Note based on scheduledTimerWithTimeInterval, if the remaining time < 0, this will fire near immediately. + sessionTimer = Timer.scheduledTimer(timeInterval: remainingTimeUntiTimeout, target: self, selector: #selector(sessionTimeout(_:)), userInfo: nil, repeats: false) + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "Session timeout will fire at: \(String(describing: sessionTimer?.fireDate)))") + + // Only setup a warning timer if there is a timeout timer. Note based on scheduledTimerWithTimeInterval, if the remaining time < 0, this will fire near immediately. + guard let remainingTimeUntilWarning = remainingTimeUntilWarning(), + !(self.sessionWarningTimer?.isValid ?? false) else { return } + sessionWarningTimer = Timer.scheduledTimer(timeInterval: remainingTimeUntilWarning, target: self, selector: #selector(sessionTimeoutWarning), userInfo: nil, repeats: false) + MVMCoreLoggingHandler.logDebugMessage(withDelegate: "Session warning will fire at: \(String(describing: sessionWarningTimer?.fireDate)))") + } + + /// Completely stop the session timer. Should only be used in rare occassions, like on the original wifi screen. + @objc public func stopSessionTimer() { + // nil timer, session no longer timed. + sessionBeingTimed = false + sessionWarningTimer?.invalidate() + sessionWarningTimer = nil + sessionTimer?.invalidate() + sessionTimer = nil + } + + /// Should be called when the app enters the background. + @objc nonisolated func appEnteredBackground() { + // Session is still being timed. Invalidates here, will start up again on enter foreground if need be. + Task { @MainActor in + self.sessionWarningTimer?.invalidate() + self.sessionWarningTimer = nil + self.sessionTimer?.invalidate() + self.sessionTimer = nil + } + } + + /// Should be called when the app enters the foreground. + @objc nonisolated func appEnteredForeground() { + // Special return logic to cancel when returning in warning track. This is not in the revalidateSessionTimestamp in order to prevent API checks to isAppInSession from timing out the session from under the user warning period. + Task { @MainActor in + if let remainingTimeUntilWarning = self.remainingTimeUntilWarning(), + remainingTimeUntilWarning < 0 { + self.sessionTimeout(true) + } + + // Revalidate first to check if we should be in the timeout state. + self.revalidateSessionTimestamp() + self.sendKeepAlive(toServer: false) + } + } + + @objc open func invalidateSession(_ completion: ((MVMCoreErrorObject?) -> Void)? = nil) { + completion?(nil) + } + + /// Checks to make sure session is still valid and that the timer is running. + func revalidateSessionTimestamp() { + guard sessionBeingTimed || sessionTimedOut else { return } + if let remainingTimeUntiTimeout = remainingTimeUntiTimeout(), + remainingTimeUntiTimeout < 0 { + // Force a timeout if we are passed the timeout interval. + sessionTimeout(true) + } else { + resumeSessionTimer() + } + } + + /// Returns whether the app is in session. + @objc open func isAppInSession() -> Bool { + revalidateSessionTimestamp() // Possible race condition hazard. Session timeouts will happen as if they are in background. If isAppInSession is called right before foreground trigger, it could potentially suppress the dialog. + return sessionBeingTimed && !sessionTimedOut + } + + /// Resets everything. + public func resetState() { + stopSessionTimer() + sessionTimedOut = false + } +} diff --git a/MVMCore/MVMCore/Singletons/MVMCoreObject.h b/MVMCore/MVMCore/Singletons/MVMCoreObject.h index be532a7..dd7f22f 100644 --- a/MVMCore/MVMCore/Singletons/MVMCoreObject.h +++ b/MVMCore/MVMCore/Singletons/MVMCoreObject.h @@ -10,13 +10,13 @@ #import #import #import -#import #import #import #import #import #import @class MVMCoreActionHandler; +@class MVMCoreSessionTimeHandler; @interface MVMCoreObject : NSObject diff --git a/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift new file mode 100644 index 0000000..f6b45ce --- /dev/null +++ b/MVMCore/MVMCore/Utility/Helpers/MVMCoreActionUtility+Extension.swift @@ -0,0 +1,19 @@ +// +// MVMCoreActionUtility+Extension.swift +// MVMCore +// +// Created by Scott Pfeil on 4/11/23. +// Copyright © 2023 myverizon. All rights reserved. +// + +public extension MVMCoreActionUtility { + static func fatalClassCheck(object: AnyObject?) -> T { + guard let object = object else { + fatalError("Object \(T.self) is nil.") + } + guard let instance = object as? T else { + fatalError("\(object) is not an instance of \(T.self)") + } + return instance + } +} diff --git a/MVMCore/MVMCore/Utility/MVMCoreErrorObject.h b/MVMCore/MVMCore/Utility/MVMCoreErrorObject.h index 86fdda0..465dc44 100644 --- a/MVMCore/MVMCore/Utility/MVMCoreErrorObject.h +++ b/MVMCore/MVMCore/Utility/MVMCoreErrorObject.h @@ -22,6 +22,11 @@ @property (nullable, strong, nonatomic) NSString *systemDomain; @property (nonatomic) NSInteger code; @property (nonatomic) UIApplicationState applicationState; +@property (nullable, strong, nonatomic) NSString *sessionId; +@property (nullable, strong, nonatomic) NSString *requestId; +@property (nullable, strong, nonatomic) NSString *requestUrl; +@property (nonatomic, assign) NSInteger httpStatusCode; +@property (nullable, strong, nonatomic) NSDictionary *serverResponseInfo; // For the crash log. @property (nullable, strong, nonatomic) NSDictionary *crashLog; diff --git a/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m b/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m index 09f76b4..9b316dd 100644 --- a/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m +++ b/MVMCore/MVMCore/Utility/MVMCoreErrorObject.m @@ -28,22 +28,11 @@ self.domain = domain; self.location = location; self.date = [NSDate date]; + self.silentError = YES; + self.logError = YES; [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{ self.applicationState = [UIApplication sharedApplication].applicationState; }]; - - // We don't log server errors. - if (![domain isEqualToString:ErrorDomainServer]) { - self.logError = YES; - - // Native and system errors have an error screen. - if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getNativeScreenForRequestError:requestObject:)]) { - self.errorScreenError = YES; - self.nativeDrivenErrorScreen = YES; - } - } else { - self.logError = NO; - } } return self; } @@ -58,21 +47,11 @@ self.domain = domain; self.location = location; self.date = [NSDate date]; + self.silentError = YES; + self.logError = YES; [MVMCoreDispatchUtility performSyncBlockOnMainThread:^{ self.applicationState = [UIApplication sharedApplication].applicationState; }]; - // We don't log server errors. - if (![domain isEqualToString:ErrorDomainServer]) { - self.logError = YES; - - // Native and system errors have an error screen. - if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getNativeScreenForRequestError:requestObject:)]) { - self.errorScreenError = YES; - self.nativeDrivenErrorScreen = YES; - } - } else { - self.logError = NO; - } } return self; } @@ -91,12 +70,12 @@ NSInteger errorCode = [[errorInfo string:KeyCode] integerValue]; NSString *type = [errorInfo string:KeyType]; if (![ValueTypeSuccess isEqualToString:type]) { - NSString *title = [errorInfo string:KeyErrorHeading] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle]; NSString *message = [errorInfo string:KeyUserMessage] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess]; - MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:title message:message messageToLog:[errorInfo string:KeyMessage] code:errorCode domain:ErrorDomainServer location:location]; + MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc] initWithTitle:title message:message messageToLog:[errorInfo string:KeyMessage] ?: @"ResponseInfo type is not Success." code:errorCode domain:ErrorDomainServer location:location]; + error.silentError = NO; + error.serverResponseInfo = errorInfo; if ([ValueTypeErrorScreen isEqualToString:type]) { - // If this is a server error screen, there should be no additional alerts... It will be handled by the load handler. error.errorScreenError = YES; } @@ -111,14 +90,17 @@ if (errorObject) { return errorObject; } - errorObject = [[MVMCoreErrorObject alloc] initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] message:[error localizedDescription] messageToLog:[error description] code:[error code] domain:ErrorDomainSystem location:location]; - if ([error.domain isEqualToString:NSURLErrorDomain]) { + errorObject = [[MVMCoreErrorObject alloc] + initWithTitle:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorTitle] + message:[error localizedDescription] ?: [MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorUnableToProcess] + messageToLog:[error description] + code:[error code] + domain:ErrorDomainSystem + location:location]; + if ([error.domain isEqualToString:NSURLErrorDomain] || [error.domain isEqualToString:@"WebKitErrorDomain"]) { errorObject.systemDomain = error.domain; - if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(getNativeScreenForRequestError:requestObject:)]) { - errorObject.errorScreenError = YES; - errorObject.nativeDrivenErrorScreen = YES; - } } + errorObject.requestUrl = error.userInfo[NSURLErrorFailingURLStringErrorKey]; return errorObject; } diff --git a/Scripts/upload_core_frameworks.sh b/Scripts/upload_core_frameworks.sh index 9067da5..f7b74be 100755 --- a/Scripts/upload_core_frameworks.sh +++ b/Scripts/upload_core_frameworks.sh @@ -17,7 +17,7 @@ fi # Create new aggregate builds if [ -z $ARTIFACTORY_URL ]; then - ARTIFACTORY_URL="https://oneartifactoryprod.verizon.com/artifactory" + ARTIFACTORY_URL="https://oneartifactoryci.verizon.com/artifactory" fi BUILD_DIR=$(echo "$BUILD_SETTINGS" | grep -w -o 'BUILD_DIR = .*' | cut -d\ -f3-)