Compare commits

...

76 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
Pfeil, Scott Robert
58f7abb5fc Merge branch 'bugfix/CXTDT-544924' into 'develop'
Digital PCT32 defect CXTDT-544924 - Insert missing OpenUrlOptionsModel handling.

### Summary
Insert missing OpenUrlOptionsModel handling for universalLinksOnly.

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

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

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/322
2024-04-17 15:41:36 +00:00
Hedden, Kyle Matthew
262881f52b Digital PCT32 defect CXTDT-544924 - Code review. Formatting. 2024-04-17 15:32:13 +00:00
Hedden, Kyle Matthew
c4b6122d4c Digital PCT32 defect CXTDT-544924 - Insert missing OpenUrlOptionsModel handling. 2024-04-16 18:15:23 -04:00
Hedden, Kyle Matthew
0f89fb51d2 Merge branch 'bugfix/CXTDT-509545' into 'develop'
CXTDT-509545 : old topNotification tagging fix

### Summary
Declaring the logLoadFinished function to be overriden from MFLoggingHandler to log the tagging data for BAU top notification

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

Co-authored-by: Nandhini Rajendran <nandhini.rajendran@verizon.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/321
2024-04-15 17:38:44 +00:00
Hedden, Kyle Matthew
c63a7f6d90 Merge branch 'release/11_5_0' into 'develop'
release/11_5_0 hotfix merge

Co-authored-by: Bruce, Matt R <matt.bruce@one.verizon.com>
Co-authored-by: Scott Pfeil <Scott.Pfeil3@verizonwireless.com>

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/320
2024-04-12 13:39:27 +00:00
Nandhini Rajendran
fac3e77984 CXTDT-509545 : old topNotification tagging fix 2024-04-09 21:54:56 +05:30
Bruce, Matt R
f6f272f727 Merge branch 'feature/feed_cache' into 'release/11_5_0'
Feature/feed cache

### Summary
Persistently Cached Discover

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

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

See merge request https://gitlab.verizon.com/BPHV_MIPS/mvm_core/-/merge_requests/319
2024-04-05 20:17:36 +00:00
Scott Pfeil
19c277d68e Digital PCT265 story MVAPCT-48 - exposing constants. 2024-04-04 17:33:05 -04:00
Scott Pfeil
64aba6a100 Digital PCT265 story MVAPCT-48 - Bug fixes and optimizations 2024-04-03 21:49:19 -04:00
Scott Pfeil
25b79530eb Digital PCT265 story MVAPCT-48 - Code cleanup 2024-04-03 12:20:24 -04:00
Scott Pfeil
8235aff75f Digital PCT265 story MVAPCT-48 - Added cache timing hacks 2024-04-01 18:07:52 -04:00
Hedden, Kyle Matthew
d08a8f6782 Digital PCT265 story MVAPCT-48 - Caches root directory, file protection for encryption, atomicWrite API deprecation. 2024-03-29 16:45:23 -04:00
Scott Pfeil
4fc4aa21f3 Digital PCT265 story MVAPCT-48 - More expiry logic fixes 2024-03-28 13:35:07 -04:00
Scott Pfeil
4bdd93dbe5 Digital PCT265 story MVAPCT-48 - Minor cache code cleanup 2024-03-27 13:38:13 -04:00
Scott Pfeil
3b410fb522 Digital PCT265 story MVAPCT-48 - Add logging and fix expiry bug 2024-03-27 11:30:52 -04:00
Scott Pfeil
c90f267599 Digital PCT265 story MVAPCT-48 - Remove test code 2024-03-27 11:03:27 -04:00
Scott Pfeil
f89bad1c7a Digital PCT265 story MVAPCT-48 - Loading overlay cleanup 2024-03-26 11:07:10 -04:00
Scott Pfeil
54e2ecb313 Manager updates for navigating to controller. 2024-03-25 13:29:29 -04:00
Scott Pfeil
b9097361ab Digital PCT265 story MVAPCT-48 - Caching piping improvements 2024-03-22 14:50:39 -04:00
Scott Pfeil
a16e09c569 Digital PCT265 story MVAPCT-48 - Cache piping improvements. 2024-03-22 11:02:04 -04:00
Scott Pfeil
84f4a1ac46 Digital PCT265 story MVAPCT-48 - code fixes for cache optimizations 2024-03-21 10:39:25 -04:00
Scott Pfeil
8c32dbbd7d Digital PCT265 story MVAPCT-48 - Initial demo of loading feed from cache 2024-03-20 19:09:13 -04:00
51 changed files with 980 additions and 117 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 */; };
@ -96,6 +97,7 @@
AF43A7411FC5FA6F008E9347 /* MVMCoreViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
AF43A74C1FC6109F008E9347 /* MVMCoreSessionObject.h in Headers */ = {isa = PBXBuildFile; fileRef = AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
AF43A74D1FC6109F008E9347 /* MVMCoreSessionObject.m in Sources */ = {isa = PBXBuildFile; fileRef = AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */; };
AF4955E22BAB1EB200567276 /* MVMCoreCache+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */; };
AF60A7F2289212CA00919EEB /* MVMError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F1289212CA00919EEB /* MVMError.swift */; };
AF60A7F4289212EB00919EEB /* MVMCoreError.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF60A7F3289212EB00919EEB /* MVMCoreError.swift */; };
AF686FDA2A8A876A008F666A /* NavigationOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = AF686FD92A8A876A008F666A /* NavigationOperation.swift */; };
@ -193,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>"; };
@ -252,6 +255,7 @@
AF43A7401FC5FA6F008E9347 /* MVMCoreViewProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreViewProtocol.h; sourceTree = "<group>"; };
AF43A74A1FC6109F008E9347 /* MVMCoreSessionObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MVMCoreSessionObject.h; sourceTree = "<group>"; };
AF43A74B1FC6109F008E9347 /* MVMCoreSessionObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MVMCoreSessionObject.m; sourceTree = "<group>"; };
AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MVMCoreCache+Extension.swift"; sourceTree = "<group>"; };
AF60A7F1289212CA00919EEB /* MVMError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMError.swift; sourceTree = "<group>"; };
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MVMCoreError.swift; sourceTree = "<group>"; };
AF686FD92A8A876A008F666A /* NavigationOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationOperation.swift; sourceTree = "<group>"; };
@ -289,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>"; };
@ -394,6 +397,7 @@
children = (
AF60A7F1289212CA00919EEB /* MVMError.swift */,
AF60A7F3289212EB00919EEB /* MVMCoreError.swift */,
5878F0B12BDAA63E00ADE23D /* ReadableDecodingErrors.swift */,
881D26911FCC9D180079C521 /* MVMCoreErrorObject.h */,
881D268F1FCC9D180079C521 /* MVMCoreErrorObject.m */,
881D26921FCC9D180079C521 /* MVMCoreOperation.h */,
@ -646,7 +650,6 @@
AF43A6FC1FBE2F2A008E9347 /* Reachability */,
AF43A5C01FBB76D5008E9347 /* CoreGraphics.framework */,
AF43A5BF1FBB76C3008E9347 /* UIKit.framework */,
AFBB96D21FBA44420008D868 /* VZWAuthentication.framework */,
);
name = Frameworks;
sourceTree = "<group>";
@ -656,6 +659,7 @@
children = (
AF43A7091FC4F415008E9347 /* MVMCoreCache.h */,
AF43A7081FC4F415008E9347 /* MVMCoreCache.m */,
AF4955E12BAB1EB200567276 /* MVMCoreCache+Extension.swift */,
605A9A292ABD712F00487E47 /* MVMCoreLoggingHandler.swift */,
6042E8FB2B3094680031644B /* MVMCoreLoggingHandlerHelper.h */,
D288D5F426C6EFE000A5C365 /* MVMCoreLoggingHandler+Extension.swift */,
@ -874,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 */,
@ -902,6 +907,7 @@
AFBB96351FBA34310008D868 /* MVMCoreErrorConstants.m in Sources */,
AF43A5881FBB67D6008E9347 /* MVMCoreActionUtility.m in Sources */,
AFED77A61FCCA29400BAE689 /* MVMCoreViewControllerStoryBoardMappingObject.m in Sources */,
AF4955E22BAB1EB200567276 /* MVMCoreCache+Extension.swift in Sources */,
016CF36925FA6DD400B82A1F /* ClientParameterHandler.swift in Sources */,
5846ABF42B44BB9000FA6C76 /* Collection+Safe.swift in Sources */,
AF69D4F7286EA0B800BC6862 /* ActionPreviousSubmitHandler.swift in Sources */,
@ -1107,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;
@ -1134,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

@ -83,10 +83,10 @@ open class ActionOpenPageHandler: MVMCoreJSONActionHandlerProtocol {
}
// Adds any client parameters to the request parameters.
if let parametersToFetch = model.clientParameters,
let fetchedParameters = try await ClientParameterHandler().getClientParameters(
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

@ -54,9 +54,9 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol {
/// Opens the url using UIApplication open(url:). Throws URLError.failedToOpen if it fails.
@MainActor
public static func open(url: URL) async throws {
public static func open(url: URL, options: [UIApplication.OpenExternalURLOptionsKey : Any] = [:]) async throws {
try await withCheckedThrowingContinuation { continuation in
UIApplication.shared.open(url, options: [:]) { successful in
UIApplication.shared.open(url, options: options) { successful in
if successful {
continuation.resume()
} else {
@ -76,12 +76,13 @@ open class ActionOpenUrlHandler: MVMCoreJSONActionHandlerProtocol {
// Try loading the app url first, otherwise fall back to browser url.
if let appURL = model.appURL {
do {
try await ActionOpenUrlHandler.open(url: appURL)
try await ActionOpenUrlHandler.open(url: appURL, options: model.appURLOptions?.options ?? [:])
return
} catch {
// Log error and continue
MVMCoreLoggingHandler.shared()?.handleDebugMessage("Failed to open app url: \(appURL)")
if let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) {
let isUsingUniversalLinksOnly = model.appURLOptions?.options[.universalLinksOnly] as? Bool ?? false
if !isUsingUniversalLinksOnly, let errorObject = MVMCoreErrorObject.createErrorObject(for: error, location: MVMCoreActionHandler.getErrorLocation(with: delegateObject?.actionDelegate, actionType: model.actionType)) {
MVMCoreLoggingHandler.shared()?.addError(toLog: errorObject)
}
}

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.
@ -385,6 +385,7 @@
} else {
MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithRequestParameters:requestParameters dataForPage:dataForPage delegateObject:delegateObject backgroundLoad:NO];
loadOperation.identifier = requestParameters.identifier;
[loadOperation startLoadingAnimationIfNeeded];
[self.blockingLoadQueue addOperation:loadOperation];
return loadOperation;
}
@ -399,6 +400,8 @@
- (MVMCoreLoadRequestOperation *)loadObject:(nonnull MVMCoreLoadObject *)loadObject {
MVMCoreLoadRequestOperation *loadOperation = [[MVMCoreLoadRequestOperation alloc] initWithLoadObject:loadObject backgroundLoad:NO];
loadOperation.identifier = loadObject.requestParameters.identifier;
[loadOperation startLoadingAnimationIfNeeded];
[self.blockingLoadQueue addOperation:loadOperation];
return loadOperation;
}

View File

@ -38,6 +38,42 @@ public enum PopBackError: MVMError, CustomStringConvertible {
@objc
public extension MVMCoreLoadRequestOperation {
/// Attempt to navigate to the controller with the given load object. Return the controller that we navigated to if successful.
@objc
@MainActor
func goToViewController(loadObject: MVMCoreLoadObject) async -> UIViewController? {
guard loadObject.requestParameters?.replaceViewIfOnStackElseLoadWithStyle == true,
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")
guard let controllerMappingObject = MVMCoreViewControllerMappingObject.shared()?.getViewControllerMapping(forTemplate: template, pageType: pageType) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Failed to create a new controller. template:\(String(describing: template)) page:\(pageType)")
return nil
}
var controllerType: AnyClass?
if let programmaticMapping = controllerMappingObject as? MVMCoreViewControllerProgrammaticMappingObject {
controllerType = programmaticMapping.viewControllerClass
} else if let newVC = MVMCoreViewControllerMappingObject.shared()?.createMFViewController(ofTemplate: loadObject.pageJSON?.optionalStringForKey("template"), pageType: pageType) {
// Need to create the view controller to fetch the type.
controllerType = type(of: newVC)
} else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Failed to create a new controller. template:\(String(describing: template)) page:\(pageType)")
return nil
}
guard let viewController = await NavigationHandler.shared().navigateToViewController(of: pageType, controllerType: controllerType) else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): No matching controller found in the hierarchy. Will need to create a new controller. pageType:\(pageType) controllerType:\(String(describing: controllerType)).")
return nil
}
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "\(type(of: self)): Navigated to controller. pageType:\(pageType) controllerType:\(String(describing: controllerType))")
stopLoadingAnimationIfNeeded()
return viewController
}
@objc
func popBackToPage(for loadObject: MVMCoreLoadObject) {
Task(priority: .high) {
@ -71,13 +107,9 @@ public extension MVMCoreLoadRequestOperation {
func navigate(with navigationOperation: NavigationOperation, loadObject: MVMCoreLoadObject?) async {
// stop any loading animation we may have started if we are about to display
cancellable = NavigationHandler.shared().onNavigation
.filter { $0.0 == .willNavigate }
.sink { (event, operation) in
if navigationOperation == operation,
!self.backgroundLoad,
!(loadObject?.requestParameters?.noloadingOverlay ?? false) {
MVMCoreLoadingOverlayHandler.sharedLoadingOverlay()?.stopLoading(false)
}
.filter { $0.0 == .willNavigate && navigationOperation == $0.1 }
.sink { [weak self] (event, operation) in
self?.stopLoadingAnimationIfNeeded()
}
await NavigationHandler.shared().navigate(with: navigationOperation)
cancellable = nil

View File

@ -19,7 +19,6 @@
@interface MVMCoreLoadRequestOperation : MVMCoreOperation
@property (nullable, strong, nonatomic) MVMCoreRequestParameters *requestParameters;
/// For load objects as in input parameter. Does not attach self generated load objects.
@property (nullable, strong, nonatomic) MVMCoreLoadObject *loadObject;
@property (nullable, strong, nonatomic) NSDictionary *dataForPage;
@property (nullable, strong, nonatomic) DelegateObject *delegateObject;
@ -42,6 +41,12 @@
// Initializes the operation with the load object, data for page, and mvm view controller to handle the loading with. Can be used for loading a screen without going to the cache or server.
- (nullable instancetype)initWithLoadObject:(nullable MVMCoreLoadObject *)loadObject backgroundLoad:(BOOL)backgroundLoad;
/// Begins the loading animation if needed.
- (void)startLoadingAnimationIfNeeded;
/// Ends the loading animation if needed.
- (void)stopLoadingAnimationIfNeeded;
/* Checks the cache for the data and calls the completion handler with any found data.
* @param completionHandler The block that gets called with any fetched data. */
+ (void)checkCacheForDataForRequest:(nonnull MVMCoreRequestParameters *)requestParameters completionHandler:(nonnull void (^)(NSDictionary * _Nullable pageFromCache, NSDictionary * _Nullable modulesFromCache))completionHandler;
@ -90,6 +95,8 @@
*/
+ (void)removeCaches:(nullable NSDictionary *)cacheDictionary;
+ (void)notifyListenersOfNewResponse:(nullable NSDictionary *)pages modules:(nullable NSDictionary *)modules systemParameters:(nullable NSDictionary *)systemParameters loadObject:(nonnull MVMCoreLoadObject *)loadObject;
/** Creates the view controller based on the load object passed in.
* @param loadObject The load data from the cache or server.
* @param completionHandler The completion handler to load once finished. Returns any loaded view controller and the load.*/

View File

@ -34,6 +34,8 @@
@property (nonatomic, readwrite) BOOL alertToShow;
@property (strong, nonatomic, nullable) MVMCoreErrorObject *errorForAlertToShow;
@property (nonatomic, readwrite) BOOL loadingAnimationRunning;
@end
@implementation MVMCoreLoadRequestOperation
@ -64,14 +66,13 @@
- (void)cancel {
[super cancel];
[self.sessionTask cancel];
[self stopLoadingAnimationIfNeeded];
}
- (void)start {
// Adds a loading overlay if necessary.
if (!self.backgroundLoad && !self.requestParameters.noloadingOverlay) {
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading];
}
[self startLoadingAnimationIfNeeded];
[super start];
}
@ -83,11 +84,9 @@
- (void)markAsFinished {
// stop any loading animation we may have started
if (!self.backgroundLoad && !self.requestParameters.noloadingOverlay) {
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:NO];
}
[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];
}
@ -108,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]];
@ -140,14 +139,15 @@
// 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.
MVMCoreLoadObject *loadObject = [self createLoadObjectWithPageFromCache:pageFromCache modulesFromCache:modulesFromCache];
self.loadObject = loadObject;
// Check if we need to go to server for missing data.
MVMCoreRequestParameters *requestForMissingData = [MVMCoreLoadRequestOperation createRequestForDataWithLoadObject:loadObject];
@ -162,22 +162,22 @@
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) {
#if ENABLE_HARD_CODED_RESPONSE
if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(modifyJSON:)]) {
json = [[MVMCoreObject sharedInstance].globalLoadDelegate modifyJSON:json];
}
#endif
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) {
@ -202,6 +202,20 @@
#pragma mark - Load Functions
- (void)startLoadingAnimationIfNeeded {
if (self.loadingAnimationRunning) { return; }
if (self.backgroundLoad) { return; }
if (self.requestParameters.noloadingOverlay) { return; }
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] startLoading];
self.loadingAnimationRunning = YES;
}
- (void)stopLoadingAnimationIfNeeded {
if (!self.loadingAnimationRunning) { return; }
[[MVMCoreLoadingOverlayHandler sharedLoadingOverlay] stopLoading:YES];
self.loadingAnimationRunning = NO;
}
+ (void)checkCacheForDataForRequest:(nonnull MVMCoreRequestParameters *)requestParameters completionHandler:(nonnull void (^)(NSDictionary * _Nullable pageFromCache, NSDictionary * _Nullable modulesFromCache))completionHandler {
if (requestParameters.neverLoadFromCache) {
@ -430,6 +444,10 @@
NSDictionary *systemParameters = [jsonDictionary dict:KeySystemParameters];
loadObject.systemParametersJSON = systemParameters.count > 0 ? systemParameters : nil;
if ([[MVMCoreObject sharedInstance].globalLoadDelegate respondsToSelector:@selector(willProcessLoadObject:)]) {
[[MVMCoreObject sharedInstance].globalLoadDelegate willProcessLoadObject:loadObject];
}
// module items are cached.
MVMCoreErrorObject *moduleCachingError = nil;
BOOL shouldContinue = [MVMCoreLoadRequestOperation cacheModules:modules loadObject:loadObject error:&moduleCachingError];
@ -581,9 +599,18 @@
};
if (!error.nativeDrivenErrorScreen) {
// Server driven screen, create normally
[MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler];
[MVMCoreDispatchUtility performBlockOnMainThread:^{
[loadObject.operation goToViewControllerWithLoadObject:loadObject completionHandler:^(UIViewController * _Nullable viewController) {
[MVMCoreDispatchUtility performBlockInBackground:^{
if (viewController) {
[MVMCoreLoadRequestOperation loadFinished:loadObject loadedViewController:viewController errorObject:nil];
} else {
[MVMCoreLoadRequestOperation createViewControllerWithLoadObject:loadObject completionHandler:completionHandler];
}
}];
}];
}];
} else {
// Get the proper native error screen from the delegate
[MVMCoreDispatchUtility performBlockOnMainThread:^{
@ -659,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]
@ -669,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 {
@ -795,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

@ -41,6 +41,9 @@
/// Checks to see if the operation has content to show.
- (BOOL)hasContentToShow:(nonnull MVMCoreLoadObject *)loadObject error:(nullable MVMCoreErrorObject *)error;
/// Notifies the delegate we are about to process the load object.
- (void)willProcessLoadObject:(nonnull MVMCoreLoadObject *)loadObject;
#if ENABLE_HARD_CODED_RESPONSE
- (nullable NSDictionary *)getJSONForRequestParameters:(nonnull MVMCoreRequestParameters *)requestParameters;
- (nonnull NSDictionary *)modifyJSON:(nonnull NSDictionary *)json;

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

@ -19,6 +19,9 @@
- (nullable NSArray*)getAccessibilityElements; //AccessibilityElements that are owned by Manager.
/// Attempt to navigate to the controller. Return the controller that we navigated to if successful.
- (void)navigateToViewControllerOfPageType:(nonnull NSString *)pageType controllerType:(_Nullable Class)controllerType completionHandler:(void (^ __nullable)(UIViewController * _Nullable viewController))completionHandler;
@optional
/// Notifies the manager that the controller received new data.

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

@ -0,0 +1,106 @@
//
// Cache.swift
// JSONCreator
//
// Created by Matt Bruce on 3/20/24.
// Copyright © 2024 Verizon Wireless. All rights reserved.
//
import Foundation
public enum CacheError: Error {
case serializationFailed
case deserializationFailed
case dataNotFound
case dataExpired
case saveFailed(Error)
case loadFailed(Error)
}
public class CachedData: Codable {
public var data: [String: AnyHashable]
public var expirationDate: Date
enum CodingKeys: CodingKey {
case data, expirationDate
}
public init(data: [String: AnyHashable], expirationDate: Date) {
self.data = data
self.expirationDate = expirationDate
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(expirationDate, forKey: .expirationDate)
let dataAsData = try JSONSerialization.data(withJSONObject: data, options: [])
try container.encode(dataAsData, forKey: .data)
}
required public init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
expirationDate = try values.decode(Date.self, forKey: .expirationDate)
let dataAsData = try values.decode(Data.self, forKey: .data)
guard let jsonData = try JSONSerialization.jsonObject(with: dataAsData, options: []) as? [String: AnyHashable] else {
throw CacheError.deserializationFailed
}
data = jsonData
}
}
@objc public class PersistentCacheManager: NSObject {
@objc public static let shared = PersistentCacheManager()
private let fileManager = FileManager.default
@objc public lazy var cacheDirectory = { fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!.appendingPathComponent("Atomic")}()
private override init() {}
@objc public func save(data: [String: AnyHashable], forKey key: String, path: URL, expirationDate: Date) throws {
let cachedData = CachedData(data: data, expirationDate: expirationDate)
do {
try FileManager.default.createDirectory(atPath: path.deletingLastPathComponent().relativePath, withIntermediateDirectories: true, attributes: [.protectionKey: FileProtectionType.complete])
let dataToSave = try JSONEncoder().encode(cachedData)
try dataToSave.write(to: path, options: [.atomic, .completeFileProtection])
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: SAVED TO PERSISTENT CACHE, key:\(key), path:\(path)")
} catch is EncodingError {
throw CacheError.serializationFailed
} catch {
throw CacheError.saveFailed(error)
}
}
@objc public func load(forKey key: String, path: URL) throws -> [String: AnyHashable] {
do {
let data = try Data(contentsOf: path)
let decodedCachedData = try JSONDecoder().decode(CachedData.self, from: data)
if Date() < decodedCachedData.expirationDate {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: LOADED FROM PERSISTENT CACHE, key:\(key), path:\(path)")
return decodedCachedData.data
} else {
MVMCoreLoggingHandler.logDebugMessage(withDelegate: "CACHEDFEED: EXPIRED, key:\(key), path:\(path)")
throw CacheError.dataExpired
}
} catch {
// Remove item from the cache on any failure.
try fileManager.removeItem(at: path)
switch error {
case is DecodingError:
throw CacheError.deserializationFailed
default:
throw CacheError.loadFailed(error)
}
}
}
@objc public func remove(forKey key: String) throws {
let filePath = self.filePath(forKey: key)
try fileManager.removeItem(at: filePath)
}
@objc public func removeAll() throws {
try FileManager.default.removeItem(at: cacheDirectory)
}
private func filePath(forKey key: String) -> URL {
return cacheDirectory.appendingPathComponent("\(key).json")
}
}

View File

@ -12,6 +12,9 @@
#import <AVKit/AVKit.h>
@class MVMCoreErrorObject;
extern NSString * _Nonnull const KeyCachePolicy;
extern NSString * _Nonnull const KeyCacheExpiry;
//block returned when getting image
//parameters are UIImage object for the image, NSData for gif images, UIImage object for the image, A BOOL to indicate if it is a fall back image.
typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOOL);
@ -31,10 +34,28 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
#pragma mark - Page and Module Handling
// Checks the set of pageTypes to be cached for the given pageType.
- (BOOL)shouldCacheJSONWithPageType:(nonnull NSString *)pageType;
- (BOOL)shouldCachePageJSON:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType;
// Checks the set of modules to be cached for the given module
- (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module;
- (BOOL)shouldCacheModuleJSON:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName;
/// Returns if the json is expired or not.
- (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary;
/// Returns the expiry time for the object.
- (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary;
/// Checks if the page is to be persistently cached.
- (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType;
/// Checks if the module is to be persistently cached.
- (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module;
/// Can override the path for the page to be cached. Currently Cache/Atomic/Pages/pageType
- (nullable NSURL *)getPathForPersistentCachePage:(nonnull NSString *)pageType;
/// Can override the path for the page to be cached. Currently Cache/Atomic/Modules/moduleName
- (nullable NSURL *)getPathForPersistentCacheModule:(nonnull NSString *)moduleName;
// For pages external to the mobile first framework to be added to the list to not cache
- (void)addPageTypesToNotCache:(nullable NSArray <NSString *>*)array;
@ -53,6 +74,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
// Gets a json dictionary from the cache with all the requested modules. Pass in the block that you want to run once the dictionary is received. This will be run on a background thread.
- (void)fetchJSONForModules:(nullable NSArray *)modules completionHandler:(nonnull void (^)(NSDictionary * _Nullable jsonDictionary))completionHandler;
/// Returns a page JSON from the persistent cache.
- (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType;
/// Returns a module JSON from the persistent cache.
- (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName;
#pragma mark - Advanced Fetch
// Pass in the block that you want to run once the dictionary is received and which queue to run it on. Pass in if you'd like the current thread to wait.
@ -75,6 +102,12 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
// Adds a json dictionary to the cache by modules.
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary;
/// Adds the json to the persistent cache by pageType.
- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate;
/// Adds the json to the persistent cache by module.
- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate;
#pragma mark - Advanced Insertion
// Adds a json dictionary to the cache by pageType. Pass in the block that you want to run once the dictionary is received and which queue to run it on. Pass in if you'd like the current thread to wait.
@ -111,6 +144,15 @@ typedef void(^MVMCoreGetImageBlock)(UIImage * _Nullable, NSData * _Nullable, BOO
// Removes the json for modules. Pass in the block that you want to run once the dictionary is received and which queue to run it on.
- (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock;
/// 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

@ -17,6 +17,9 @@
#import "MVMCoreErrorConstants.h"
#import "MVMCoreLoggingHandlerHelper.h"
NSString * _Nonnull const KeyCachePolicy = @"cachePolicy";
NSString * _Nonnull const KeyCacheExpiry = @"expiry";
@interface MVMCoreCache ()
// The cache for json.
@ -91,12 +94,55 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
return _modulesToNotCache;
}
- (BOOL)shouldCacheJSONWithPageType:(nonnull NSString *)pageType {
return ![self.pageTypesToNotCache containsObject:pageType];
- (BOOL)shouldCachePageJSON:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType {
if ([self.pageTypesToNotCache containsObject:pageType]) { return NO; }
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
return shouldCache == nil || shouldCache.boolValue;
}
- (BOOL)shouldCacheJSONWithModule:(nonnull NSString *)module {
return ![self.modulesToNotCache containsObject:module];
- (BOOL)shouldCacheModuleJSON:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName {
if ([self.modulesToNotCache containsObject:moduleName]) { return NO; }
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
return shouldCache == nil || shouldCache.boolValue;
}
- (BOOL)isJSONExpired:(nonnull NSDictionary *)jsonDictionary {
NSDate *expirationDate = [self getExpirationDateForJSON:jsonDictionary];
NSDate *now = [NSDate date];
if ([now compare:expirationDate] == NSOrderedDescending) {
[MVMCoreLoggingHandler logDebugMessageWithDelegate:[NSString stringWithFormat:@"CACHEDFEED: NEW DATA ALREADY EXPIRED %@ now:%@ expirationDate:%@",jsonDictionary,now,expirationDate]];
}
return [now compare:expirationDate] == NSOrderedDescending;
}
- (nonnull NSDate *)getExpirationDateForJSON:(nonnull NSDictionary *)jsonDictionary {
NSDictionary *cachePolicy = [jsonDictionary dict:KeyCachePolicy];
NSTimeInterval interval = [[cachePolicy string:KeyCacheExpiry] doubleValue] / 1000;
return [NSDate dateWithTimeIntervalSince1970:interval];
}
- (BOOL)shouldPersistentlyCacheJSON:(nonnull NSDictionary *)jsonDictionary {
NSDictionary *cachePolicy = [jsonDictionary dict:KeyCachePolicy];
if (!cachePolicy || ![cachePolicy boolForKey:@"persist"]) {
return NO;
}
return ![self isJSONExpired:jsonDictionary];
}
- (BOOL)shouldPersistentlyCachePage:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType {
return [self shouldPersistentlyCacheJSON:jsonDictionary];
}
- (BOOL)shouldPersistentlyCacheModule:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module {
return [self shouldPersistentlyCacheJSON:jsonDictionary];
}
- (nullable NSURL *)getPathForPersistentCachePage:(nonnull NSString *)pageType {
return [[[[PersistentCacheManager shared].cacheDirectory URLByAppendingPathComponent:@"Pages"] URLByAppendingPathComponent:pageType] URLByAppendingPathExtension:@"json"];
}
- (nullable NSURL *)getPathForPersistentCacheModule:(nonnull NSString *)moduleName {
return [[[[PersistentCacheManager shared].cacheDirectory URLByAppendingPathComponent:@"Modules"] URLByAppendingPathComponent:moduleName]URLByAppendingPathExtension:@"json"];
}
- (void)addPageTypesToNotCache:(nullable NSArray <NSString *>*)array {
@ -125,6 +171,24 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
[self fetchJSONForModules:modules queue:self.completionQueue waitUntilFinished:NO completionHandler:completionHandler];
}
- (NSDictionary * _Nullable)fetchPageFromPersistentCache:(nonnull NSString *)pageType {
NSError *error = nil;
return [[PersistentCacheManager shared] loadForKey:pageType path:[self getPathForPersistentCachePage:pageType] error:&error];
}
- (NSDictionary * _Nullable)fetchModuleFromPersistentCache:(nonnull NSString *)moduleName {
NSError *error = nil;
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 {
@ -140,6 +204,9 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
// First checks the cache by page type.
dictionary = [weakSelf.pageTypeCache objectForKey:pageType];
if (!dictionary) {
dictionary = [self fetchPageFromPersistentCache:pageType];
}
} else {
// If no pagetype, return whole cache
@ -176,6 +243,11 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
NSDictionary *moduleDictionary = [weakSelf.moduleCache objectForKey:module];
if (moduleDictionary) {
[modulesDictionary setObject:moduleDictionary forKey:module];
} else {
moduleDictionary = [self fetchModuleFromPersistentCache:module];
if (moduleDictionary) {
[modulesDictionary setObject:moduleDictionary forKey:module];
}
}
}
@ -206,16 +278,37 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
[self addModulesToCache:jsonDictionary queue:nil waitUntilFinished:NO completionBlock:NULL];
}
- (void)addPageToPersistentCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType expirationDate:(nonnull NSDate *)expirationDate {
NSError *error = nil;
[[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:pageType path:[self getPathForPersistentCachePage:pageType] expirationDate:expirationDate error:&error];
if (error) {
[[MVMCoreLoggingHandler sharedLoggingHandler] addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:[NSString stringWithFormat:@"%s_%@",__PRETTY_FUNCTION__,pageType]]];
}
}
- (void)addModuleToPersistentCache:(nonnull NSDictionary *)jsonDictionary moduleName:(nonnull NSString *)moduleName expirationDate:(nonnull NSDate *)expirationDate {
NSError *error = nil;
[[PersistentCacheManager shared] saveWithData:jsonDictionary forKey:moduleName path:[self getPathForPersistentCacheModule:moduleName] expirationDate:expirationDate error:&error];
if (error) {
[[MVMCoreLoggingHandler sharedLoggingHandler] addErrorToLog:[MVMCoreErrorObject createErrorObjectForNSError:error location:[NSString stringWithFormat:@"%s_%@",__PRETTY_FUNCTION__,moduleName]]];
}
}
#pragma mark - Advanced Insertion
- (void)addPageToCache:(nonnull NSDictionary *)jsonDictionary pageType:(nonnull NSString *)pageType queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
if (![self shouldCachePageJSON:jsonDictionary pageType:pageType]) {
if (completionBlock) {
completionBlock();
}
return;
}
NSBlockOperation *addOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = addOperation;
__weak typeof(self) weakSelf = self;
[addOperation addExecutionBlock:^{
if (!weakOperation.isCancelled && [[MVMCoreCache sharedCache] shouldCacheJSONWithPageType:pageType]) {
if (!weakOperation.isCancelled) {
// There must be a dictionary and page type to cache.
if (jsonDictionary && pageType) {
@ -227,10 +320,13 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
}
// Adds json to cache with page type key.
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
if (shouldCache == nil || shouldCache.boolValue) {
[weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType];
[weakSelf.pageTypeCache setObject:jsonDictionary forKey:pageType];
if (![self shouldPersistentlyCachePage:jsonDictionary pageType:pageType]) {
[self removePersistentJSONCacheForPageType:pageType pageJSON:jsonDictionary];
return;
}
[self addPageToPersistentCache:jsonDictionary pageType:pageType expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
}
}
if (completionBlock) {
@ -241,11 +337,12 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
}
- (void)addModuleToCache:(nonnull NSDictionary *)jsonDictionary module:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
[self addModulesToCache:@{module:jsonDictionary} queue:queue waitUntilFinished:waitUntilFinished completionBlock:completionBlock];
}
- (void)addModulesToCache:(nonnull NSDictionary *)jsonDictionary queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
if (![self shouldCacheModuleJSON:jsonDictionary moduleName:module]) {
if (completionBlock) {
completionBlock();
}
return;
}
NSBlockOperation *addOperation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = addOperation;
__weak typeof(self) weakSelf = self;
@ -253,23 +350,19 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
if (!weakOperation.isCancelled) {
[jsonDictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
if (!weakSelf.moduleCache) {
if ([[MVMCoreCache sharedCache] shouldCacheJSONWithModule:key]) {
if (!weakSelf.moduleCache) {
// Create the cache if necessary.
weakSelf.moduleCache = [NSMutableDictionary dictionary];
}
// Adds json to cache with page type key.
NSNumber *shouldCache = [jsonDictionary optionalNumberForKey:@"cache"];
if (shouldCache == nil || shouldCache.boolValue) {
[weakSelf.moduleCache setObject:obj forKey:key];
}
}
}];
// Create the cache if necessary.
weakSelf.moduleCache = [NSMutableDictionary dictionary];
}
[weakSelf.moduleCache setObject:jsonDictionary forKey:module];
if (![self shouldPersistentlyCacheModule:jsonDictionary module:module]) {
[self removePersistentModuleCacheForModule:module moduleJSON:jsonDictionary];
return;
}
[self addModuleToPersistentCache:jsonDictionary moduleName:module expirationDate:[self getExpirationDateForJSON:jsonDictionary]];
}
if (completionBlock) {
[(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished];
@ -278,6 +371,28 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
[self.moduleQueue addOperations:@[addOperation] waitUntilFinished:waitUntilFinished];
}
- (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;
[self.moduleQueue addOperations:@[[NSBlockOperation blockOperationWithBlock:^{
if (completionBlock) {
[(queue ?: weakSelf.completionQueue) addOperations:@[[NSBlockOperation blockOperationWithBlock:completionBlock]] waitUntilFinished:waitUntilFinished];
}
}]] waitUntilFinished:waitUntilFinished];
}
#pragma mark - Simple Deletion
- (void)removeJSONForPageType:(nonnull NSString *)pageType {
@ -292,6 +407,10 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
[self removeJSONForModules:modules queue:nil waitUntilFinished:NO completionBlock:NULL];
}
- (void)clearPersistentJSONCache {
[[PersistentCacheManager shared] removeAllAndReturnError:nil];
}
- (void)clearMFCache {
[self.pageTypeQueue cancelAllOperations];
[self.moduleQueue cancelAllOperations];
@ -329,6 +448,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
}
}];
[self.pageTypeQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished];
[[PersistentCacheManager shared] removeForKey:pageType error:nil];
}
- (void)removeJSONForModule:(nonnull NSString *)module queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
@ -352,6 +472,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
}
}];
[self.moduleQueue addOperations:@[removeOperation] waitUntilFinished:waitUntilFinished];
[[PersistentCacheManager shared] removeForKey:module error:nil];
}
- (void)removeJSONForModules:(nonnull NSArray *)modules queue:(nullable NSOperationQueue *)queue waitUntilFinished:(BOOL)waitUntilFinished completionBlock:(nullable void (^)(void))completionBlock {
@ -371,6 +492,7 @@ static NSString * const STATIC_CACHE_COMPONENT = @"StaticCache.txt";
// Removes json from cache with module key.
[weakSelf.moduleCache removeObjectForKey:obj];
}
[[PersistentCacheManager shared] removeForKey:obj error:nil];
}
}];
if (completionBlock) {

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,13 +48,58 @@ 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

@ -189,6 +189,13 @@ public class NavigationHandler {
await navigate(with: .pop(navigationController: navigationController, animated: animated), delegateObject: delegateObject)
}
}
/// Attempts to go to navigate to a viewcontroller of pageType and controllerType. Returns the view controller if successful
@MainActor
func navigateToViewController(of pageType: String, controllerType: AnyClass?) async -> UIViewController? {
// TODO: Need to manage for present view controllers.
return await MVMCoreObject.sharedInstance()?.viewControllerManager?.navigate(toViewControllerOfPageType: pageType, controllerType: controllerType)
}
}
extension UINavigationController {

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

@ -23,6 +23,9 @@ public class MVMCoreObject: NSObject {
public var globalLoadDelegate: MVMCoreGlobalLoadProtocol?
public var loadingProtocol: MVMCoreLoadingOverlayDelegateProtocol?
public var loggingDelegate: MVMCoreLoggingDelegateProtocol?
/// The main manager of the view controllers in the application.
public weak var viewControllerManager: MVMCoreViewManagerProtocol?
/// A reference to the calling application delegate that should be set. For a normal app, could be the UIApplicationDelegate. For watch, could be WKExtensionDelegate. For iMessage, could be MSMessagesAppViewController. etc, etc. Useful for the framework to call delegate specific functions.
public weak var applicationDelegate: AnyObject?

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

View File

@ -47,6 +47,9 @@
// For pages external to the mobile first framework to be added to the view controller mapping.
- (void)addToTemplateViewControllerMapping:(nullable NSDictionary <NSString *,NSObject <MVMCoreViewControllerMappingProtocol>*>*)map;
// Transition function: A mix of new and legacy.
- (nullable NSObject <MVMCoreViewControllerMappingProtocol>*)getViewControllerMappingForTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType;
// Transition function: A mix of new and legacy.
- (nullable UIViewController <MVMCoreViewControllerProtocol> *)createMFViewControllerOfTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType;

View File

@ -46,6 +46,16 @@
}
}
- (nullable NSObject <MVMCoreViewControllerMappingProtocol>*)getViewControllerMappingForTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType {
if (templateID) {
return [self getViewControllerMappingForTemplate:templateID];
} else if (pageType) {
return [self getViewControllerMappingForPageType:pageType];
} else {
return nil;
}
}
// Transition function: A mix of new and legacy.
- (nullable UIViewController <MVMCoreViewControllerProtocol> *)createMFViewControllerOfTemplate:(nullable NSString *)templateID pageType:(nullable NSString *)pageType {
if (templateID) {