Compare commits

...

54 Commits

Author SHA1 Message Date
Pfeil, Scott Robert
3e1581e28e Merge branch 'bugfix/prevent_feed_cache_clearing' into 'develop'
Bugfix/prevent feed cache clearing

### Summary
Prevent premature feeds cache clearing.

### JIRA Ticket
https://onejira.verizon.com/browse/MVAPCT-322

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/351
2024-10-04 18:06:12 +00:00
Hedden, Kyle Matthew
8f2483eb1b Digital PCT265 defect MVAPCT-322: Mesasge to log for modules missing their ResponseInfo. 2024-10-04 12:20:59 -04:00
Hedden, Kyle Matthew
12d17dbca8 Digital PCT265 defect MVAPCT-322: Convert couple more hard coded restarts to be soft. 2024-10-04 12:20:35 -04:00
Pfeil, Scott Robert
c360c75512 Merge branch 'feature/ONEAPP-11359' into 'develop'
Digital ACT192 story ONEAPP-11359: Lift the minimum supported iOS version number to 15.

### Summary
Lift the minimum supported iOS version number to 15.

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-11359

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/349
2024-10-03 21:23:07 +00:00
Pfeil, Scott Robert
d57a86e140 Merge branch 'bugfix/prevent_feed_cache_clearing' into 'develop'
Digital PCT265 defect: Prevent standard session restarts from clearing the persistent cache.

### Summary
Discovered during Monday's outage, the feed cache is actually getting prematurely cleared on standard session timeouts. Moving logic to be based on the logout action itself.

### JIRA
https://onejira.verizon.com/browse/MVAPCT-322

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/350
2024-10-03 19:23:07 +00:00
Hedden, Kyle Matthew
214ccfd8f6 Digital PCT265 defect MVAPCT-322: Spelling fix. 2024-10-03 15:15:11 -04:00
Pfeil, Scott Robert
31aa1d6df7 Merge branch 'feature/CXTDT-552152' into 'develop'
CXTDT-552152

### Summary
Potential Fix for Crash in New Relic

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-552152

Co-authored-by: Danish Phiroz <danish.phiroz@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/348
2024-10-03 13:29:14 +00:00
Hedden, Kyle Matthew
817d7900f4 Digital PCT265 defect: Prevent standard session restarts from clearing the persistent cache. 2024-10-02 17:45:58 -04:00
Danish Phiroz
4b35a8bb2d PR Review comment fixed 2024-10-02 13:27:33 -04:00
Danish Phiroz
a29fa60e5e PR Review comments fixed 2024-10-02 12:47:26 -04:00
Hedden, Kyle Matthew
3103f8dd89 Digital ACT192 story ONEAPP-11359: Lift the minimum supported iOS version number to 15. 2024-10-01 20:23:42 -04:00
Danish Phiroz
2b36711bca Potential Fix for Crash 2024-10-01 16:08:55 -04:00
Hedden, Kyle Matthew
a570f3c0e6 Merge branch 'feature/action_id_initial_parameters' into 'develop'
Digital ACT191 story ONEAPP-10840 - Passing action id through to initialParameters getter

### Summary
Passing actionId through to initial parameters getter

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-10840

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/347
2024-09-20 18:42:35 +00:00
Scott Pfeil
038fb8cdf7 Digital ACT191 story ONEAPP-10840 - Optimizing, passing in existing actionId variable 2024-09-20 09:54:47 -04:00
Scott Pfeil
dd2b0f9de5 Digital ACT191 story ONEAPP-10840 - Passing action id through to initialParameters getter 2024-09-19 16:27:08 -04:00
Hedden, Kyle Matthew
d61cb6cfb9 Merge branch 'feature/MVAPCT-273' into 'develop'
MVAPCT-273

### Summary
Action decoding failed Error Logs 

### MVAPCT-273
https://onejira.verizon.com/browse/MVAPCT-273

Co-authored-by: Danish Phiroz <danish.phiroz@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/346
2024-09-19 12:30:48 +00:00
Phiroz, Danish
cf3d7810d4 MVAPCT-273 2024-09-19 12:30:48 +00:00
Hedden, Kyle Matthew
3c8af09752 Merge branch 'feature/atomic-vds-new-forms-atoms' into 'develop'
ModelComparisonProtocol addition

### Summary
Appending to existing Protocol for == and !=

Co-authored-by: Matt Bruce <matt.bruce@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/343
2024-08-22 20:38:56 +00:00
Matt Bruce
b219f3f75d added == and != to existing
Signed-off-by: Matt Bruce <matt.bruce@verizon.com>
2024-08-22 15:35:07 -05:00
Pfeil, Scott Robert
fba2e80b8a Merge branch 'release/20_0_0' into 'develop'
release/20_0_0 hotfix merge

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>
Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>
Co-authored-by: Xi Zhang <xi.zhang@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/334
2024-06-26 14:34:53 +00:00
Pfeil, Scott Robert
460aa06678 Merge branch 'bugfix/CXTDT-574791-2' into 'release/20_0_0'
Bugfix/cxtdt 574791 2

### Summary
Fix navigation crash on controller replace.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-574791

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/338
2024-06-21 14:44:42 +00:00
Hedden, Kyle Matthew
c18ce5e811 Digital PCT265 defect CXTDT-574791: Prevent crash when modifying the controller array slice. 2024-06-21 10:27:12 -04:00
Pfeil, Scott Robert
4bf30550ef Merge branch 'feature/offlineFeedbackClearAppCache' into 'release/20_0_0'
Add support for removing persistent cache properly.

Add support for removing persistent cache properly including the application level cache.

JIRA:
MVAPCT-155

Co-authored-by: Xi Zhang <xi.zhang@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/335
2024-06-18 21:15:55 +00:00
Hedden, Kyle Matthew
317815d44a Merge branch 'bugfix/CXTDT-573619' into 'release/20_0_0'
Digital PCT265 defect CXTDT-573619 - Add a flag to create a new controller instead of go to

### Summary
Add a flag to create a new controller instead of go to

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-573619

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/336
2024-06-18 20:08:32 +00:00
Scott Pfeil
3054f7e62d Digital PCT265 defect CXTDT-573619 - Add a flag to create a new controller instead of go to 2024-06-18 10:33:01 -04:00
Xi Zhang
25e05c6c53 update API description. 2024-06-17 20:55:20 -04:00
Xi Zhang
a1c6332131 Add support for removing persistent cache properly. 2024-06-17 20:39:39 -04:00
Bruce, Matt R
9744fc5333 Merge branch 'bugfix/referenced_removed_frameworks' into 'develop'
removing dead references

removing dead references

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/332
2024-06-06 12:48:57 +00:00
Scott Pfeil
8a92c9cdd3 removing dead references 2024-06-05 12:29:41 -04:00
Hedden, Kyle Matthew
a00eff7cf1 Merge branch 'feature/ONEAPP-7249' into 'develop'
Digital PCT265 story ONEAPP-7249 - Enhancement for allowing background polling.

### Summary
1. Logging change for less verbose logging statements and standardization. Used to help tracking pollingBehavior state.
2. Optimizations to reduce scrolling stagger on refresh.
3. Reduce layout row tearing.

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7249

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/328
2024-05-29 23:51:04 +00:00
Hedden, Kyle Matthew
0104570bc9 Digital PCT265 story PCT-135: Code reivew. Restore debug crash. 2024-05-28 17:43:41 -04:00
Hedden, Kyle Matthew
134ce14e5a Digital PCT265 story PCT-135: Code review. Missing pageType check in ActionRestartModel. 2024-05-28 16:25:32 -04:00
Hedden, Kyle Matthew
220b8530da Digital PCT265 story PCT-135: 'curent' comment typo. 2024-05-28 16:23:59 -04:00
Hedden, Kyle Matthew
05df4ad6ef Merge remote-tracking branch 'origin/develop' into feature/ONEAPP-7249 2024-05-21 20:03:58 -04:00
Bruce, Matt R
8f88e94ed8 Merge branch 'feature/register_model_default_parameter' into 'develop'
Update for convenience in registration of models that replace

### Summary
Update for convenience in registration of models that replace

### JIRA Ticket
https://onejira.verizon.com/browse/ONEAPP-7459

Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/330
2024-05-20 20:46:37 +00:00
Scott Pfeil
d2572c4543 Digital ACT191 story ONEAPP-7459 - Update for convenience in registration of models that replace 2024-05-20 16:44:10 -04:00
Hedden, Kyle Matthew
71689b957b Digital PCT265 story PCT-135: Switch to shallow equals with deep compare on parent in order to pinpoint midmatched models. Unit testing setup. 2024-05-17 21:24:18 -04:00
Hedden, Kyle Matthew
009bb01e1b Digital PCT265 story PCT-135: Code review comments, cleanups and isEquals expansion. 2024-05-13 21:22:57 -04:00
Pfeil, Scott Robert
4bded0a433 Merge branch 'bugfix/CXTDT-552909' into 'develop'
Digital PCT265 defect CXTDT-552909 - Client crash prevention on invalid module JSON.

### Summary
Client resilience check for invalid module data.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-552909

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/329
2024-05-13 13:06:13 +00:00
Hedden, Kyle Matthew
20d4d323e0 Digital PCT265 story ONEAPP-7249 - isVisuallyEquivalent build out to work with stabilizing carousel refreshes. 2024-05-08 20:34:21 -04:00
Hedden, Kyle Matthew
62a5799312 Digital PCT265 story ONEAPP-7249 - Prevent UI updates when there are no model changes. 2024-05-07 23:27:31 -04:00
Hedden, Kyle Matthew
e8552b0aa2 Digital PCT265 story ONEAPP-7249 - More logging cleanups and categorization. 2024-05-06 12:53:28 -04:00
Hedden, Kyle Matthew
dde3e565a3 Digital PCT265 defect CXTDT-552909 - Client crash prevention on invalid module JSON. 2024-05-02 19:03:21 -04:00
Hedden, Kyle Matthew
62b7955406 Digital PCT265 defect CXTDT-552909 - Client crash prevention on invalid module JSON. 2024-05-02 17:17:57 -04:00
Hedden, Kyle Matthew
a5763d4516 Digital PCT265 story ONEAPP-7249 - Pipe logs to stdout when the message is too long. 2024-05-02 17:03:03 -04:00
Hedden, Kyle Matthew
9ff641060e Digital PCT265 story ONEAPP-7249 - Registry signature update. 2024-04-30 20:36:00 -04:00
Hedden, Kyle Matthew
85747b146e Digital PCT265 story ONEAPP-7249 - Pipe logs to system logger rather than stdout. Logging updates to categorize logs. 2024-04-30 20:35:30 -04:00
Hedden, Kyle Matthew
9b718ce0d4 Merge remote-tracking branch 'origin/develop' into feature/ONEAPP-7249 2024-04-30 13:47:25 -04:00
Hedden, Kyle Matthew
39a8451314 Digital PCT265 story ONEAPP-7249 - CoreLogging protocol for easier logging. 2024-04-30 13:42:33 -04:00
Hedden, Kyle Matthew
c38030bdb8 Merge branch 'bugfix/CXTDT-531317' into 'develop'
Digital PCT265 defect CXTDT-531317 - Adjust logging to capture webview errors.

### Summary
Error logging updates.

### JIRA Ticket
https://onejira.verizon.com/browse/CXTDT-531317

Co-authored-by: Pfeil, Scott Robert <scott.pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/327
2024-04-26 14:32:47 +00:00
Hedden, Kyle Matthew
09ff3b6457 Digital PCT265 defect CXTDT-531317 - Updated error messaging 2024-04-25 16:01:55 -04:00
Hedden, Kyle Matthew
a428d5f7ce Digital PCT265 defect CXTDT-531317 - Adjust logging to capture webview errors. 2024-04-25 15:50:41 -04:00
Pfeil, Scott Robert
fe7e98f615 Merge branch 'bugfix/PRODDEF-28200' into 'release/11_6_0'
Digital PCT265 defect PRODDEF-28200 - Prevent navigation to the same controller fixing hang ups.

### Summary
Prevent navigation to the same controller fixing hang ups.

### JIRA Ticket
https://onejira.verizon.com/browse/PRODDEF-28200

Co-authored-by: Hedden, Kyle Matthew <kyle.hedden@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/324
2024-04-23 23:06:18 +00:00
Hedden, Kyle Matthew
f7a348a8b0 Digital PCT265 defect PRODDEF-28200 - Prevent navigation to the same controller fixing hang ups. 2024-04-23 19:02:05 -04:00
42 changed files with 576 additions and 63 deletions

View File

@ -42,6 +42,7 @@
2723337B28BD534D004EAEE0 /* MVMCoreEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337A28BD534D004EAEE0 /* MVMCoreEvent.swift */; };
2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2723337C28BD53C2004EAEE0 /* Date+Extension.swift */; };
5846ABF42B44BB9000FA6C76 /* Collection+Safe.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846ABF32B44BB9000FA6C76 /* Collection+Safe.swift */; };
5878F0B22BDAA63E00ADE23D /* ReadableDecodingErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */; };
6042E8FC2B317B190031644B /* MVMCoreLoggingHandlerHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */; settings = {ATTRIBUTES = (Public, ); }; };
605A9A2A2ABD712F00487E47 /* MVMCoreLoggingHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */; };
6079EDCE2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6079EDCD2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift */; };
@ -194,6 +195,7 @@
581FABEE2A71D0E6003A8508 /* mvmcore_dev.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcore_dev.xcconfig; sourceTree = "<group>"; };
5836B8E22A4338DF002553D9 /* mvmcore.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = mvmcore.xcconfig; sourceTree = "<group>"; };
5846ABF32B44BB9000FA6C76 /* Collection+Safe.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection+Safe.swift"; sourceTree = "<group>"; };
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadableDecodingErrors.swift; sourceTree = "<group>"; };
6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreLoggingHandlerHelper.h; sourceTree = "<group>"; };
605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreLoggingHandler.swift; sourceTree = "<group>"; };
6079EDCD2AD97AA5004B7A85 /* MVMCoreLoggingDelegateProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreLoggingDelegateProtocol.swift; sourceTree = "<group>"; };
@ -291,7 +293,6 @@
AFBB96AE1FBA3B590008D868 /* MVMCoreGetterUtility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreGetterUtility.h; sourceTree = "<group>"; };
AFBB96AF1FBA3B590008D868 /* MVMCoreGetterUtility.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MVMCoreGetterUtility.m; sourceTree = "<group>"; };
AFBB96B51FBA3CEC0008D868 /* MVMCoreActionDelegateProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MVMCoreActionDelegateProtocol.h; sourceTree = "<group>"; };
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = VZWAuthentication.framework; path = ../../SharedFrameworks/VZWAuthentication.framework; sourceTree = "<group>"; };
AFBB96E91FBA4A260008D868 /* MFHardCodedServerResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MFHardCodedServerResponse.h; sourceTree = "<group>"; };
AFBB96EA1FBA4A260008D868 /* MFHardCodedServerResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MFHardCodedServerResponse.m; sourceTree = "<group>"; };
AFEA17A6209B6A1C00BC6740 /* MVMCoreBlockOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreBlockOperation.h; sourceTree = "<group>"; };
@ -396,6 +397,7 @@
children = (
AF60A7F1289212CA00919EEB /* MVMError.swift */,
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */,
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */,
881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */,
881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */,
881D26921FCC9D180079C521 /* MVMCoreOperation.h */,
@ -648,7 +650,6 @@
AF43A6FC1FBE2F2A008E9347 /* Reachability */,
AF43A5C01FBB76D5008E9347 /* CoreGraphics.framework */,
AF43A5BF1FBB76C3008E9347 /* UIKit.framework */,
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -877,6 +878,7 @@
AFA4931E29E5C988001A9663 /* MVMCoreActionUtility+Extension.swift in Sources */,
1DAD0FFE26AAB40000216E83 /* ActionRunJavaScriptModel.swift in Sources */,
946EE1AB237B5C940036751F /* Decoder.swift in Sources */,
5878F0B22BDAA63E00ADE23D /* ReadableDecodingErrors.swift in Sources */,
2723337D28BD53C2004EAEE0 /* Date+Extension.swift in Sources */,
AF70699A287DD02400077CF6 /* ActionContactHandler.swift in Sources */,
AF69D4F3286E9DCE00BC6862 /* ActionActionsHandler.swift in Sources */,
@ -1111,7 +1113,7 @@
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
INFOPLIST_FILE = MVMCore/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
MARKETING_VERSION = 3.1;
@ -1138,7 +1140,7 @@
FRAMEWORK_SEARCH_PATHS = "$(PROJECT_DIR)/../../SharedFrameworks";
INFOPLIST_FILE = MVMCore/Info.plist;
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
LIBRARY_SEARCH_PATHS = "$(inherited)";
MARKETING_VERSION = 3.1;

View File

@ -64,4 +64,19 @@ public struct ActionActionsModel: ActionModelProtocol {
try container.encodeIfPresent(extraParameters, forKey: .extraParameters)
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return actions.isEqual(to: model.actions)
&& concurrent == model.concurrent
&& extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
}
}
extension ActionActionsModel: CustomDebugStringConvertible {
public var debugDescription: String {
return "\(Self.self) [\(actions)]"
}
}

View File

@ -25,4 +25,12 @@ public struct ActionBackModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
// Default
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.actionType == actionType
&& model.extraParameters == extraParameters
&& model.analyticsData == analyticsData
}
}

View File

@ -28,4 +28,11 @@ public struct ActionCallModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
&& callNumber == model.callNumber
}
}

View File

@ -25,4 +25,12 @@ public struct ActionCancelModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
// Default
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.actionType == actionType
&& model.extraParameters == extraParameters
&& model.analyticsData == analyticsData
}
}

View File

@ -42,4 +42,14 @@ public struct ActionContactModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
&& phoneNumber == model.phoneNumber
&& firstName == model.firstName
&& lastName == model.lastName
&& approach == model.approach
}
}

View File

@ -7,6 +7,7 @@
//
public struct ActionNoopModel: ActionModelProtocol {
//--------------------------------------------------
// MARK: - Properties
//--------------------------------------------------
@ -24,4 +25,12 @@ public struct ActionNoopModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
// Default
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.actionType == actionType
&& model.extraParameters == extraParameters
&& model.analyticsData == analyticsData
}
}

View File

@ -86,7 +86,7 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol {
let fetchedParameters = await ClientParameterHandler().getClientParameters(
with: parametersToFetch,
requestParameters: requestParameters.parameters as? [String : Any] ?? [:],
actionId: MVMCoreActionHandler.getUUID(additionalData: additionalData) ?? "unknown",
actionId: actionUUID,
showLoadingOverlay: !requestParameters.backgroundRequest) {
requestParameters.add(fetchedParameters)
}

View File

@ -115,4 +115,31 @@ public struct ActionOpenPageModel: ActionModelProtocol, ActionOpenPageProtocol,
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
try container.encodeIfPresent(fallbackResponse, forKey: .fallbackResponse)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return pageType == model.pageType
&& baseURL == model.baseURL
&& appContext == model.appContext
&& requestURL == model.requestURL
&& modules == model.modules
&& presentationStyle == model.presentationStyle
&& tabBarIndex == model.tabBarIndex
&& background == model.background
&& clientParameters == model.clientParameters
&& customTimeoutTime == model.customTimeoutTime
&& extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
&& fallbackResponse == model.fallbackResponse
}
}
extension ActionOpenPageModel: CustomDebugStringConvertible {
public var debugDescription: String {
if let requestURL {
return "\(Self.self) for \(pageType) @ \(requestURL)"
} else {
return "\(Self.self) for \(pageType)"
}
}
}

View File

@ -30,4 +30,13 @@ public struct ActionOpenSMSModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
// Default
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.extraParameters == extraParameters
&& model.analyticsData == analyticsData
&& model.phoneNumber == phoneNumber
&& model.message == message
}
}

View File

@ -60,4 +60,13 @@ open class ActionOpenUrlModel: ActionModelProtocol {
try container.encodeIfPresent(extraParameters, forKey: .extraParameters)
try container.encodeIfPresent(analyticsData, forKey: .analyticsData)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
&& browserUrl == model.browserUrl
&& appURL == model.appURL
&& appURLOptions == model.appURLOptions
}
}

View File

@ -25,4 +25,12 @@ public struct ActionPreviousSubmitModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
// Default
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.actionType == actionType
&& model.extraParameters == extraParameters
&& model.analyticsData == analyticsData
}
}

View File

@ -25,11 +25,16 @@ open class ActionRestartHandler: MVMCoreActionHandlerProtocol {
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
}
if let sessionObject = MVMCoreSessionObject.sharedGlobal() {
if model.hardReset {
sessionObject.clearPersistentCache()
}
// Restarts the app (forcing any passed in page types).
sessionObject.restartSession(withPageType: model.pageType, request: model.requestURL, parameters: model.extraParameters?.toJSON(), clearAllVariables: true)
}
continuation.resume()
})
}
}

View File

@ -16,6 +16,7 @@ public struct ActionRestartModel: ActionModelProtocol {
public static var identifier: String = "restart"
public var actionType: String = ActionRestartModel.identifier
public var requestURL: URL?
@DecodableDefault.True public var hardReset: Bool
public var extraParameters: JSONValueDictionary?
public var analyticsData: JSONValueDictionary?
@ -26,10 +27,20 @@ public struct ActionRestartModel: ActionModelProtocol {
// MARK: - Initializer
//--------------------------------------------------
public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
public init(_ pageType: String? = nil, _ requestUrl: URL? = nil, hardReset: Bool? = nil, _ extraParameters: JSONValueDictionary? = nil, _ analyticsData: JSONValueDictionary? = nil) {
self.pageType = pageType
self.requestURL = requestUrl
self.extraParameters = extraParameters
self.analyticsData = analyticsData
self.hardReset = hardReset ?? true
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
&& requestURL == model.requestURL
&& pageType == model.pageType
&& hardReset == model.hardReset
}
}

View File

@ -25,4 +25,10 @@ public struct ActionSettingModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
}
}

View File

@ -6,7 +6,7 @@
// Copyright © 2020 myverizon. All rights reserved.
//
public struct ActionShareItemModel: Codable {
public struct ActionShareItemModel: Codable, Equatable {
public enum SharedType: String, Codable {
case text
@ -14,14 +14,14 @@ public struct ActionShareItemModel: Codable {
}
public var type: SharedType
public var value: Any
public var value: AnyHashable // Common Equatable type between String and URL.
private enum CodingKeys: String, CodingKey {
case type
case value
}
public init(type: SharedType, value: Any) {
public init(type: SharedType, value: AnyHashable) {
self.type = type
self.value = value
}
@ -47,6 +47,11 @@ public struct ActionShareItemModel: Codable {
try container.encode(value as! URL, forKey: .value)
}
}
public static func == (lhs: ActionShareItemModel, rhs: ActionShareItemModel) -> Bool {
return lhs.type == rhs.type
&& lhs.value == rhs.value
}
}
public struct ActionShareModel: ActionModelProtocol {
@ -101,7 +106,7 @@ public struct ActionShareModel: ActionModelProtocol {
private init(deprecatedFrom decoder: Decoder) throws {
let typeContainer = try decoder.container(keyedBy: DeprecatedCodingKeys.self)
let type = try typeContainer.decode(ActionShareItemModel.SharedType.self, forKey: .sharedType)
var value: Any
var value: AnyHashable
switch type {
case .url:
value = try typeContainer.decode(URL.self, forKey: .sharedText)
@ -116,4 +121,11 @@ public struct ActionShareModel: ActionModelProtocol {
try container.encode(actionType, forKey: .actionType)
try container.encode(items, forKey: .items)
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
&& items == model.items
}
}

View File

@ -85,9 +85,9 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
open func handleAction(with model: ActionModelProtocol, additionalData: [AnyHashable: Any]?, delegateObject: DelegateObject?) async throws {
try Task.checkCancellation()
var (additionalData, uuid) = MVMCoreActionHandler.setUUID(additionalData: additionalData)
MVMCoreActionHandler.log(string: "Begin Action: \(model.actionType)", additionalData: additionalData)
MVMCoreActionHandler.log(string: "Begin Action: \(model)", additionalData: additionalData)
defer {
MVMCoreActionHandler.log(string: "End Action: \(model.actionType)", additionalData: additionalData)
MVMCoreActionHandler.log(string: "End Action: \(model)", additionalData: additionalData)
}
let json = try additionalData.removeValue(forKey: jsonKey) as? [AnyHashable : Any] ?? MVMCoreActionHandler.convertActionToJSON(model)
@ -148,12 +148,18 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
}
static public func log(string: String, additionalData: [AnyHashable: Any]?) {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "ActionHandler: UUID: \(String(describing: getUUID(additionalData: additionalData))), \(string)")
MVMCoreLoggingHandler.shared()?.handleDebugMessage("ActionHandler: UUID: \(getUUID(additionalData: additionalData) ?? "untracked"), \(string)", category: String(describing: Self.self))
}
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")) {
if let fromBridge = additionalData?["fromBridge"] as? Bool, fromBridge, let browserUrl = additionalData?["browserUrl"] as? String {
errorObject.requestUrl = browserUrl
}
if let humanReadableMessage = (error as? HumanReadableDecodingErrorProtocol)?.readableDescription {
errorObject.messageToLog = "Failed to decode the \(actionType ?? "") action model. " + humanReadableMessage
}
defaultHandleActionError(errorObject, additionalData: additionalData)
}
}
@ -176,7 +182,7 @@ public protocol MVMCoreJSONActionHandlerProtocol: MVMCoreActionHandlerProtocol {
} catch {
let actionType = json?.optionalStringForKey(KeyActionType)
switch error {
case ModelRegistry.Error.decoderError, is DecodingError:
case is ModelRegistry.Error, is DecodingError:
MVMCoreLoggingHandler.shared()?.logCoreEvent(.actionFailedToDecode(pageType: pageType(from: delegateObject), error: error))
logActionError(error, actionType, additionalData, delegateObject)
case ModelRegistry.Error.decoderErrorModelNotMapped:

View File

@ -9,7 +9,8 @@
import Foundation
/// A model for UIApplication.OpenExternalURLOptionsKey
open class OpenUrlOptionsModel: Codable {
open class OpenUrlOptionsModel: Codable, Equatable {
public var options: [UIApplication.OpenExternalURLOptionsKey: Any]
//--------------------------------------------------
@ -42,4 +43,16 @@ open class OpenUrlOptionsModel: Codable {
try container.encode(universalLinksValue, forKey: .universalLinksOnly)
}
}
public static func == (lhs: OpenUrlOptionsModel, rhs: OpenUrlOptionsModel) -> Bool {
return lhs.options.allSatisfy { pair in
switch(pair.key) {
case .universalLinksOnly:
return rhs.options[pair.key] as? Bool == pair.value as? Bool
default:
return true
}
}
}
}

View File

@ -52,7 +52,7 @@
- (void)getJsonData:(nonnull MVMCoreRequestParameters *)requestParameters forUrl:(nonnull NSURL *)url completion:(nonnull void (^)(NSData * _Nullable data, MVMCoreErrorObject *_Nullable error))completion;
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections completion:(nonnull void (^)(NSDictionary *_Nullable parameters))completion NS_SWIFT_NAME(initialParameters(excludingSections:completion:));
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections actionId:(nonnull NSString *)actionId completion:(nonnull void (^)(NSDictionary *_Nullable parameters))completion NS_SWIFT_NAME(initialParameters(excludingSections:actionId:completion:));
// Creates a request object with the given parameters.
- (void)transformToRequestWithParameters:(nonnull MVMCoreRequestParameters *)requestParameters completion:(nonnull void (^)(NSURLRequest * _Nullable request, MVMCoreErrorObject *_Nullable error))completion;

View File

@ -190,7 +190,7 @@
}
}
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *_Nullable)excludeSections completion:(nonnull void (^)(NSDictionary *_Nullable parameters))closure {}
- (void)getInitialParametersExcludingSections:(NSSet<NSString *> *)excludeSections actionId:(NSString *)actionId completion:(void (^)(NSDictionary * _Nullable))completion {}
- (void)getJsonDictionary:(nonnull MVMCoreRequestParameters *)requestParameters completion:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completion {
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
@ -206,7 +206,7 @@
}
// Sets up the Initial parameters.
[self getInitialParametersExcludingSections:requestParameters.excludedInitialParameters completion:^(NSDictionary * _Nullable initialParameters) {
[self getInitialParametersExcludingSections:requestParameters.excludedInitialParameters actionId:requestParameters.identifier ?: [NSUUID UUID].UUIDString completion:^(NSDictionary * _Nullable initialParameters) {
if (initialParameters) {
[parameters setObject:initialParameters forKey:@"InitialParams"];
}
@ -232,7 +232,7 @@
return;
}
NSString *jsonString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
MVMCoreLog(@"Request Parameters for URL %@:\n%@", [url absoluteString], jsonString);
MVMCoreNetworkLog(@"Request Parameters for URL %@:\n%@", [url absoluteString], jsonString);
#endif
// Standard condensed to send to the server.
@ -309,15 +309,15 @@
return nil;
}
MVMCoreLog(@"********************************* Cookie Sent *********************************");
MVMCoreNetworkLog(@"********************************* Cookie Sent *********************************");
[[[NSHTTPCookieStorage sharedHTTPCookieStorage] cookiesForURL:request.URL] enumerateObjectsUsingBlock:^(NSHTTPCookie * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
MVMCoreLog(@"Cookie Name: %@, Cookie Value: %@, Domain: %@", obj.name, obj.value, obj.domain);
MVMCoreNetworkLog(@"Cookie Name: %@, Cookie Value: %@, Domain: %@", obj.name, obj.value, obj.domain);
}];
NSTimeInterval startTime = [NSDate timeIntervalSinceReferenceDate];
NSURLSessionTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
MVMCoreLog(@"Request Time %f", [NSDate timeIntervalSinceReferenceDate] - startTime);
MVMCoreNetworkLog(@"Request Time %f", [NSDate timeIntervalSinceReferenceDate] - startTime);
NSDate *startTimeDate = [NSDate dateWithTimeIntervalSinceReferenceDate:startTime];
@ -330,7 +330,7 @@
[trackInfo setObject:error.localizedDescription forKey:@"error"];
}
MVMCoreLog(@"Set-Cookie %@ Value: %@", requestParameters.pageType, [(NSHTTPURLResponse *)response allHeaderFields][@"Set-Cookie"]);
MVMCoreNetworkLog(@"Set-Cookie %@ Value: %@", requestParameters.pageType, [(NSHTTPURLResponse *)response allHeaderFields][@"Set-Cookie"]);
id jsonObject = nil;
MVMCoreErrorObject *errorObject = nil;
@ -354,7 +354,7 @@
// Log the response pretty.
NSData *prettyData = [NSJSONSerialization dataWithJSONObject:jsonObject options:NSJSONWritingPrettyPrinted error:&error];
NSString *responseString = [[NSString alloc] initWithData:prettyData encoding:NSUTF8StringEncoding];
MVMCoreLog(@"Response for Request Page Type %@:\n%@",requestParameters.pageType, responseString);
MVMCoreNetworkLog(@"Response for Request Page Type %@:\n%@",requestParameters.pageType, responseString);
}
} else {
// Empty response.

View File

@ -44,8 +44,9 @@ public extension MVMCoreLoadRequestOperation {
@MainActor
func goToViewController(loadObject: MVMCoreLoadObject) async -> UIViewController? {
guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true,
let pageType = loadObject.pageType else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): A new controller should be made and any existing shouldn't be replaced. pageType:\(String(describing: loadObject.pageType))")
loadObject.requestParameters?.replaceViewControllerIfOnStackGoToOnly == true,
let pageType = loadObject.pageType else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): A new controller should be made. pageType:\(String(describing: loadObject.pageType))")
return nil
}
let template = loadObject.pageJSON?.optionalStringForKey("template")

View File

@ -86,7 +86,7 @@
// stop any loading animation we may have started
[self stopLoadingAnimationIfNeeded];
MVMCoreLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad));
MVMCoreNetworkLog(@"Load Operation finished for page type %@, background load %@", self.requestParameters.pageType, @(self.backgroundLoad));
[super markAsFinished];
}
@ -107,7 +107,7 @@
}
- (void)main {
MVMCoreLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate);
MVMCoreNetworkLog(@"Load Operation begun for page type %@, background load %@, delegate %@", self.requestParameters.pageType, @(self.backgroundLoad),self.delegateObject.loadDelegate);
[self.requestParameters resolveURL:[MVMCoreSessionObject sharedGlobal]];
@ -139,10 +139,10 @@
// Log if loaded from cache.
if (pageFromCache) {
MVMCoreLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]);
MVMCoreNetworkLog(@"loaded from cache page %@",[MVMCoreActionUtility formatDictionaryAsJSONString:pageFromCache]);
}
if (modulesFromCache) {
MVMCoreLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]);
MVMCoreNetworkLog(@"loaded from cache modules %@",[MVMCoreActionUtility formatDictionaryAsJSONString:modulesFromCache]);
}
// Create a load object from any data we fetched.
@ -686,7 +686,16 @@
if (obj && [obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *responseInfo = [obj dict:KeyResponseInfo];
if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) {
//Response Info is missing but errorObject should be created with generic message + code + domain
if (responseInfo == nil) {
errorObject = [[MVMCoreLoadHandler sharedGlobal]
errorForLoadObject:loadObject
withTitle:nil
message:[MVMCoreGetterUtility hardcodedStringWithKey:HardcodedErrorCritical]
messageToLog:[NSString stringWithFormat:@"Module %@ is missing a %@ object.", key, KeyResponseInfo]
code:ErrorCodeJSONNotDictionary
domain:ErrorDomainServer];
} else if (![ValueTypeSuccess isEqualToString:[responseInfo string:KeyType]]) {
errorObject = [[MVMCoreLoadHandler sharedGlobal] attachLoadInformation:loadObject toError:
[[MVMCoreErrorObject alloc]
initWithTitle:[responseInfo stringForKey:KeyErrorHeading]
@ -696,7 +705,6 @@
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 {
@ -822,7 +830,7 @@
return;
}
MVMCoreLog(@"Error: %@ %@ %@ %@ %@",[error stringErrorCode], error.domain, error.location,error.messageToDisplay, error.messageToLog);
MVMCoreNetworkLog(@"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) {

View File

@ -79,8 +79,13 @@ typedef NS_ENUM(NSInteger, MFLoadStyle) {
// Determines how it is loaded.
@property (nonatomic) MFLoadStyle loadStyle;
/// Determines if we should search the stack for a controller with the same pageType or just load a new controller with style. Default true
@property (nonatomic) BOOL replaceViewIfOnStackElseLoadWithStyle;
/// Determines if, when replaceViewIfOnStackElseLoadWithStyle is true, we should create a new controller or only pop back to the existing controller. Default true
@property (nonatomic) BOOL replaceViewControllerIfOnStackGoToOnly;
// A flag for if a tab was pressed to cause this load. This will ensure that we do not load a new tab page.
@property (nonatomic) BOOL tabWasPressed;

View File

@ -29,6 +29,7 @@
// Default load style.
self.loadStyle = MFLoadStyleDefault;
self.replaceViewIfOnStackElseLoadWithStyle = YES;
self.replaceViewControllerIfOnStackGoToOnly = YES;
self.identifier = [[NSUUID UUID] UUIDString];
}
return self;
@ -189,6 +190,7 @@
copyObject.contextRoot = [self.contextRoot copy];
copyObject.loadStyle = self.loadStyle;
copyObject.replaceViewIfOnStackElseLoadWithStyle = self.replaceViewIfOnStackElseLoadWithStyle;
copyObject.replaceViewControllerIfOnStackGoToOnly = self.replaceViewControllerIfOnStackGoToOnly;
copyObject.noViewControllerToLoad = self.noViewControllerToLoad;
copyObject.dontDisplayViewController = self.dontDisplayViewController;
copyObject.noloadingOverlay = self.noloadingOverlay;

View File

@ -8,13 +8,16 @@
public protocol MVMCoreLoggingDelegateProtocol {
// Can be used to log different actions performed by the core.
/// Can be used to log different actions performed by the core.
func handleDebugMessage(_ message: String?)
/// Can be used to log a message under a particular cagetory.
func handleDebugMessage(_ message: String, category: String?)
// Can be used to choose how to log error objects.
/// Can be used to choose how to log error objects.
func addError(toLog errorObject: MVMCoreErrorObject)
// Log that the load has finished.
/// Log that the load has finished.
func logLoadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: MVMCoreViewControllerProtocol?, error: MVMCoreErrorObject?)
}

View File

@ -7,7 +7,7 @@
//
public protocol ActionModelProtocol: ModelProtocol {
public protocol ActionModelProtocol: ModelProtocol, CustomDebugStringConvertible {
var actionType: String { get }
var extraParameters: JSONValueDictionary? { get set }
@ -33,4 +33,15 @@ public extension ActionModelProtocol {
static var categoryName: String {
return "\(ActionModelProtocol.self)"
}
var debugDescription: String {
return actionType
}
func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return model.actionType == actionType
&& model.extraParameters == extraParameters
&& model.analyticsData == analyticsData
}
}

View File

@ -29,4 +29,11 @@ public class ActionRunJavaScriptModel: ActionModelProtocol {
self.extraParameters = extraParameters
self.analyticsData = analyticsData
}
public func isEqual(to model: any ModelComparisonProtocol) -> Bool {
guard let model = model as? Self else { return false }
return jsCallback == model.jsCallback
&& extraParameters == model.extraParameters
&& analyticsData == model.analyticsData
}
}

View File

@ -8,7 +8,8 @@
import Foundation
public class ClientParameterModel: Codable {
public class ClientParameterModel: Equatable, Codable {
var timeout: Double?
var list: [ClientParameterModelProtocol]
@ -33,4 +34,9 @@ public class ClientParameterModel: Codable {
try container.encodeIfPresent(timeout, forKey: .timeout)
try container.encodeModels(list, forKey: .list)
}
public static func == (lhs: ClientParameterModel, rhs: ClientParameterModel) -> Bool {
return lhs.list.isEqual(to: rhs.list)
&& lhs.timeout == rhs.timeout
}
}

View File

@ -30,4 +30,9 @@ public extension ClientParameterModelProtocol {
static var categoryName: String {
return "\(ClientParameterModelProtocol.self)"
}
func isEqual(to model: any ModelProtocol) -> Bool {
guard let model = model as? Self else { return false }
return type == model.type
}
}

View File

@ -8,7 +8,7 @@
import Foundation
public protocol ModelProtocol: Codable {
public protocol ModelProtocol: ModelComparisonProtocol, Codable {
/// The key name of the molecule
static var identifier: String { get }
@ -50,3 +50,102 @@ extension ModelProtocol {
try unkeyedContainer.encode(self)
}
}
public protocol ModelComparisonProtocol {
/// Shallow checks if the current model is equal to another model. Defaults to false unless implemented otherwise.
func isEqual(to model: ModelComparisonProtocol) -> Bool
}
extension ModelComparisonProtocol {
public func isEqual(to model: ModelComparisonProtocol) -> Bool {
return false
}
}
public extension Optional {
/// Checks if the current model is equal to another model.
func isEqual(to model: ModelComparisonProtocol?) -> Bool {
guard let self = self as? ModelComparisonProtocol else {
return model == nil
}
guard let model = model else {
return false
}
return self.isEqual(to: model)
}
}
public extension Collection {
/// Checks if all the models in the given collection match another given collection.
func isEqual(to models: [ModelComparisonProtocol]) -> Bool {
guard count == models.count, let self = self as? [ModelComparisonProtocol] else { return false }
return models.indices.allSatisfy { index in
self[index].isEqual(to: models[index])
}
}
}
public extension Optional where Wrapped: Collection {
/// Checks if the current model is equal to another model.
func isEqual(to models: [ModelComparisonProtocol]?) -> Bool {
guard let self = self as? [ModelComparisonProtocol] else {
return models == nil
}
guard let models = models else {
return false
}
return self.isEqual(to: models)
}
}
public extension Optional {
/// Checks if
func matchExistence(with anotherOptional: Optional) -> Bool {
switch(self) {
case .none:
return anotherOptional == nil
case .some(_):
return anotherOptional != nil
}
}
}
func == (lhs: (any ModelComparisonProtocol)?, rhs: (any ModelComparisonProtocol)?) -> Bool {
switch (lhs, rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs.isEqual(to: rhs)
case (.none, .none):
return true
default:
return false
}
}
func != (lhs: (any ModelComparisonProtocol)?, rhs: (any ModelComparisonProtocol)?) -> Bool {
return !(lhs == rhs)
}
func == (lhs: [any ModelComparisonProtocol]?, rhs: [any ModelComparisonProtocol]?) -> Bool {
switch (lhs, rhs) {
case (.some(let lhs), .some(let rhs)):
return lhs == rhs
case (.none, .none):
return true
default:
return false
}
}
func != (lhs: [any ModelComparisonProtocol]?, rhs: [any ModelComparisonProtocol]?) -> Bool {
return !(lhs == rhs)
}
func == (lhs: [any ModelComparisonProtocol], rhs: [any ModelComparisonProtocol]) -> Bool {
return lhs.elementsEqual(rhs, by: { (lhsElement, rhsElement) -> Bool in
return lhsElement == rhsElement
})
}
func != (lhs: [any ModelComparisonProtocol], rhs: [any ModelComparisonProtocol]) -> Bool {
return !(lhs == rhs)
}

View File

@ -50,9 +50,9 @@ public struct ModelRegistry {
/// A convenience wrapping function where error handling is managed within the class.
/// - Parameter type: Takes an object of ModelProtocol.self which is used to register
public static func register<M: ModelProtocol>(_ type: M.Type) {
public static func register<M: ModelProtocol>(_ type: M.Type, allowsReplace: Bool = false) {
do {
try throwable_register(type: type)
try throwable_register(type: type, allowsReplace: allowsReplace)
} catch {
handleError(error)
}
@ -211,7 +211,7 @@ public struct ModelRegistry {
public static func getCodingKey<T>(for type: T.Type) throws -> AnyCodingKey {
guard let category = getCategory(for: type) else {
throw ModelRegistry.Error.decoderOther(message: "decodeModelsIfPresent only works for objects implementing the ModelProtocol protocol")
throw ModelRegistry.Error.decoderOther(message: "Category hasnt been registered for the CodingKey for this type: \(String(describing: type.self))")
}
return AnyCodingKey(category.codingKey)

View File

@ -147,6 +147,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
/// Clears the persistent JSON cache
- (void)clearPersistentJSONCache;
// Removes a json dictionary from the cache by pageType.
- (void)removePersistentJSONCacheForPageType:(nonnull NSString *)pageType pageJSON:(nonnull NSDictionary *)jsonDictionary;
// Removes a json dictionary from the cache by moduleType.
- (void)removePersistentModuleCacheForModule:(nonnull NSString *)moduleType moduleJSON:(nonnull NSDictionary *)jsonDictionary;
#pragma mark Image Functions
/// Register a bundle as one to search for images in.

View File

@ -181,6 +181,14 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
return [[PersistentCacheManager shared] loadForKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] error:&error];
}
- (void)removePersistentJSONCacheForPageType:(nonnull NSString *)pageType pageJSON:(nonnull NSDictionary *)jsonDictionary {
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
}
- (void)removePersistentModuleCacheForModule:(nonnull NSString *)moduleType moduleJSON:(nonnull NSDictionary *)jsonDictionary {
[[PersistentCacheManager shared] removeForKey:moduleType error:nil];
}
#pragma mark - Advanced Fetch
- (void)fetchJSONForPageType:(nullable NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler {
@ -315,7 +323,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
[weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType];
if (![self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) {
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
[self removePersistentJSONCacheForPageType:pageType pageJSON:jsonDictionary];
return;
}
[self addPageToPersistentCache:jsonDictionary pageType:pageType expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
@ -351,7 +359,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
[weakSelf.moduleCache setObject:jsonDictionary forKey:module];
if (![self shouldPersistentlyCacheModule:jsonDictionary module:module]) {
[[PersistentCacheManager shared] removeForKey:module error:nil];
[self removePersistentModuleCacheForModule:module moduleJSON:jsonDictionary];
return;
}
[self addModuleToPersistentCache:jsonDictionary moduleName:module expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
@ -365,6 +373,16 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
[jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if (![obj isKindOfClass:[NSDictionary class]]) {
MVMCoreErrorObject *error = [[MVMCoreErrorObject alloc]
initWithTitle:nil
messageToLog:[NSString stringWithFormat:@"Invalid module format. Cannot cache %@ as it's not an object", key]
code:ErrorCodeJSONNotDictionary
domain:ErrorDomainSystem
location:NSStringFromClass([self class])];
[MVMCoreLoggingHandler.sharedLoggingHandler addErrorToLog:error];
return;
}
[self addModuleToCache:obj module:key];
}];
__weak typeof(self) weakSelf = self;

View File

@ -7,9 +7,31 @@
//
import Foundation
import os
@objc public extension MVMCoreLoggingHandler {
@objc func print(with message: String) {
Swift.print(message)
public protocol CoreLogging {
static var loggingCategory: String? { get }
var loggingPrefix: String { get }
}
public extension CoreLogging {
static var loggingCategory: String? { return nil }
var loggingPrefix: String {
return ""
}
static func debugLog(_ string: String) {
#if LOGGING
MVMCoreLoggingHandler.shared()?.handleDebugMessage(string, category: loggingCategory)
#endif
}
func debugLog(_ string: String) {
#if LOGGING
MVMCoreLoggingHandler.shared()?.handleDebugMessage("\(loggingPrefix)\(string)", category: Self.loggingCategory)
#endif
}
}

View File

@ -7,9 +7,28 @@
//
import Foundation
import os
@objc open class MVMCoreLoggingHandler: NSObject, MVMCoreLoggingDelegateProtocol {
public static let standardCategory = "General"
private let logger = Logger(subsystem: "MVMCoreLogging", category: standardCategory)
private var loggerCache = [String: Logger]()
open func getLogger(category: String?) -> Logger {
if let category {
if let logger = loggerCache[category] {
return logger
} else {
let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: category)
loggerCache[category] = logger
return logger
}
}
return logger
}
@objc(sharedLoggingHandler)
public static func shared() -> Self? {
return MVMCoreActionUtility.initializerClassCheck(MVMCoreObject.sharedInstance()?.loggingDelegate as? NSObject, classToVerify: self) as? Self
@ -29,14 +48,57 @@ import Foundation
// MARK: - logging delegate
@objc open func handleDebugMessage(_ message: String?) {
#if LOGGING
guard let message = message else { return }
self.print(with: message)
guard let message = message else { return }
guard message.count < 1024 else {
logger.debug("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
print(message) // Print the whole on stdout.
return
}
logger.debug("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
// TODO: How do we split the messaging by Library and Subsystem?
#endif
}
@objc open func handleDebugMessage(_ message: String, category: String?) {
#if LOGGING
guard message.count < 1024 else {
getLogger(category: category).debug("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
print(message) // Print the whole on stdout.
return
}
getLogger(category: category).debug("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
#endif
}
open func handleWarningMessage(_ message: String, category: String?) {
#if LOGGING
guard message.count < 1024 else {
getLogger(category: category).warning("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
print(message) // Print the whole on stdout.
return
}
getLogger(category: category).warning("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
#endif
}
open func handleErrorMessage(_ message: String, category: String?) {
#if LOGGING
guard message.count < 1024 else {
getLogger(category: category).error("\(message.prefix(300), privacy: .public)... <stdio>") // Send initial log to console.
print(message) // Print the whole on stdout.
return
}
getLogger(category: category).error("\(message, privacy: .public)") // Assume that becaues this is a LOGGING build we want these messages to be unmasked.
#endif
}
@objc(addErrorToLog:)
open func addError(toLog errorObject: MVMCoreErrorObject) {
// Subclass to handle.
if errorObject.silentError {
handleWarningMessage(errorObject.messageToLog ?? errorObject.messageToDisplay ?? "Some error occurred.", category: "Handled Exception")
} else {
handleErrorMessage(errorObject.messageToLog ?? errorObject.messageToDisplay ?? "Some error occurred.", category: "Handled Exception")
}
}
open func logLoadFinished(_ loadObject: MVMCoreLoadObject?, loadedViewController: MVMCoreViewControllerProtocol?, error: MVMCoreErrorObject?) {}

View File

@ -8,7 +8,11 @@
#ifndef MVMCoreLoggingHandlerHelper_h
#define MVMCoreLoggingHandlerHelper_h
#define MVMCoreLog(fmt, ...) \
[MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__]];
#endif
#define MVMCoreNetworkLog(fmt, ...) \
[MVMCoreLoggingHandler.sharedLoggingHandler handleDebugMessage:[NSString stringWithFormat:(@"%s [Line %d] " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__] category: @"Network"];
#endif

View File

@ -68,9 +68,9 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate
let index = navigationController.getIndexOfViewController(with: pageType) else {
return false
}
var viewControllers = navigationController.viewControllers[...index]
var viewControllers = Array(navigationController.viewControllers[...index])
viewControllers[index] = viewController
set(viewControllers: Array(viewControllers), navigationController: navigationController, animated: animated)
set(viewControllers: viewControllers, navigationController: navigationController, animated: animated)
return true
}
@ -110,7 +110,8 @@ open class NavigationOperation: MVMCoreOperation, UINavigationControllerDelegate
*/
@MainActor
open func set(viewControllers: [UIViewController], navigationController: UINavigationController, animated: Bool) {
guard viewControllers.count > 0 else {
guard viewControllers.count > 0,
navigationController.viewControllers != viewControllers else { // If the controller stack is the same, iOS will not call the delegate method of the change, causing the operation to hang.
markAsFinished()
return
}

View File

@ -38,6 +38,9 @@
/// Clears the session singleton. Creates a new session NSURLSession also.
- (void)clearSessionObject;
/// Clears any persistent cache related to the current session.
- (void)clearPersistentCache;
/// Copys string to clipboard and assigns self.clipboardString for validation
/// Should only be used when expected string is a secure string
-(void)copyStringToClipboard :(nullable NSString *)clipboardString;

View File

@ -53,4 +53,6 @@
self.session = [self createNSURLSession];
}
- (void)clearPersistentCache {}
@end

View File

@ -89,21 +89,21 @@ public extension NavigationHandler {
switch requestParameters.loadStyle {
case .replaceCurrent:
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
return NavigationOperation(with: .replace(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
case .onTopOfRoot:
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController,
let root = navigationController.viewControllers.first else { return nil }
let viewControllers = [root, viewController]
return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
return NavigationOperation(with: .set(viewControllers: viewControllers, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
case .becomeRoot:
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
return NavigationOperation(with: .set(viewControllers: [viewController], navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
case .present:
guard let viewControllerToPresentOn = getViewControllerToPresentOn() else { return nil }
return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
return NavigationOperation(with: .present(viewController: viewController, onController: viewControllerToPresentOn, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
default:
guard let navigationController = requestParameters.navigationController ?? NavigationHandler.shared().navigationController else { return nil }
return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: true, delegate: delegateObject?.presentationDelegate)
return NavigationOperation(with: .push(viewController: viewController, navigationController: navigationController, animated: shouldAnimate), tryToReplace: requestParameters.replaceViewIfOnStackElseLoadWithStyle, delegate: delegateObject?.presentationDelegate)
}
}

View File

@ -23,8 +23,10 @@
// Initialization code
self.title = title;
self.messageToDisplay = message;
self.messageToLog = message;
self.code = code;
self.domain = domain;
self.systemDomain = nil;
self.location = location;
self.date = [NSDate date];
self.silentError = YES;
@ -32,6 +34,11 @@
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
self.applicationState = [UIApplication sharedApplication].applicationState;
}];
self.sessionId = nil;
self.requestId = nil;
self.requestUrl = nil;
self.serverResponseInfo = nil;
self.crashLog = nil;
}
return self;
}
@ -41,9 +48,11 @@
if (self = [super init]) {
// Initialization code
self.title = title;
self.messageToDisplay = nil;
self.messageToLog = messageToLog;
self.code = code;
self.domain = domain;
self.systemDomain = nil;
self.location = location;
self.date = [NSDate date];
self.silentError = YES;
@ -51,6 +60,11 @@
[MVMCoreDispatchUtility performSyncBlockOnMainThread:^{
self.applicationState = [UIApplication sharedApplication].applicationState;
}];
self.sessionId = nil;
self.requestId = nil;
self.requestUrl = nil;
self.serverResponseInfo = nil;
self.crashLog = nil;
}
return self;
}

View File

@ -0,0 +1,69 @@
//
// ReadableDecodingErrors.swift
// MVMCore
//
// Created by Kyle Hedden on 10/5/23.
// Copyright © 2023 myverizon. All rights reserved.
//
import Foundation
public protocol HumanReadableDecodingErrorProtocol {
var readableDescription: String { get }
}
extension JSONError: HumanReadableDecodingErrorProtocol {
public var readableDescription: String {
switch (self) {
case .other(let other):
if let other = other as? HumanReadableDecodingErrorProtocol {
return other.readableDescription
}
return description
default:
return description
}
}
}
extension ModelRegistry.Error: HumanReadableDecodingErrorProtocol {
public var readableDescription: String {
switch (self) {
case .decoderErrorModelNotMapped(let identifier, let codingKey, let codingPath) where identifier != nil && codingKey != nil && codingPath != nil:
return "Model identifier \"\(identifier!)\" is not mapped for \"\(codingKey!.stringValue)\" @ \(codingPath!.map { return $0.stringValue })"
case .decoderErrorObjectNotPresent(let codingKey, let codingPath):
return "Required model \"\(codingKey.stringValue)\" was not found @ \(codingPath.map { return $0.stringValue })"
case .decoderOther(let message):
return "An issue occurred while decoding: \(message)"
case .other(let message):
return "Registry error: \(message)"
default:
return "Registry error: \((self as NSError).localizedFailureReason ?? self.localizedDescription)"
}
}
}
extension DecodingError: HumanReadableDecodingErrorProtocol {
public var readableDescription: String {
switch (self) {
case .keyNotFound(let codingKey, let context):
return "Required key \(codingKey.stringValue) was not found @ \(context.codingPath.map { return $0.stringValue })"
case .valueNotFound(_, let context):
return "Value not found @ \(context.codingPath.map { return $0.stringValue })"
case .typeMismatch(_, let context):
return "Value type mismatch @ \(context.codingPath.map { return $0.stringValue })"
case .dataCorrupted(let context):
return "Data corrupted @ \(context.codingPath.map { return $0.stringValue })"
@unknown default:
return (self as NSError).localizedFailureReason ?? self.localizedDescription
}
}
}