From 534adf3e67602f8905687b0f741c4647104613ea Mon Sep 17 00:00:00 2001 From: Surik Date: Mon, 28 Sep 2020 14:49:35 +0300 Subject: [PATCH 01/13] Added retry requests logic --- Sources/Qonversion/QNAPIClient.h | 1 + Sources/Qonversion/QNAPIClient.m | 66 +++++++++++++++++++-- Sources/Qonversion/QNProductCenterManager.m | 5 ++ 3 files changed, 67 insertions(+), 5 deletions(-) diff --git a/Sources/Qonversion/QNAPIClient.h b/Sources/Qonversion/QNAPIClient.h index dbfcc8e0..777b3798 100644 --- a/Sources/Qonversion/QNAPIClient.h +++ b/Sources/Qonversion/QNAPIClient.h @@ -28,6 +28,7 @@ NS_ASSUME_NONNULL_BEGIN - (void)attributionRequest:(QNAttributionProvider)provider data:(NSDictionary *)data completion:(QNAPIClientCompletionHandler)completion; +- (void)processStoredRequests; @end diff --git a/Sources/Qonversion/QNAPIClient.m b/Sources/Qonversion/QNAPIClient.m index cb7b682e..bad2b0af 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -9,11 +9,14 @@ #import "QNUtils.h" #import "QNUserInfo.h" +static NSString * const kStoredRequestsKey = @"storedRequests"; + @interface QNAPIClient() @property (nonatomic, strong) QNDevice *device; @property (nonatomic, strong) QNRequestSerializer *requestSerializer; @property (nonatomic, strong) QNRequestBuilder *requestBuilder; +@property (nonatomic, copy) NSArray *connectionErrorCodes; @end @@ -30,6 +33,13 @@ - (instancetype)init { _debug = NO; _device = QNDevice.current; _session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration]; + _connectionErrorCodes = @[ + @(NSURLErrorNotConnectedToInternet), + @(NSURLErrorCallIsActive), + @(NSURLErrorNetworkConnectionLost), + @(NSURLErrorDataNotAllowed), + @(NSURLErrorTimedOut) + ]; } return self; @@ -79,6 +89,25 @@ - (void)attributionRequest:(QNAttributionProvider)provider return [self dataTaskWithRequest:request completion:completion]; } +- (void)processStoredRequests { + NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; + NSArray *storedRequests = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData]; + + if (![storedRequests isKindOfClass:[NSArray class]]) { + return; + } + + for (NSInteger i = 0; i < [storedRequests count]; i++) { + if ([storedRequests[i] isKindOfClass:[NSURLRequest class]]) { + NSURLRequest *request = storedRequests[i]; + + [self dataTaskWithRequest:request completion:nil]; + } + } + + [[NSUserDefaults standardUserDefaults] setValue:nil forKey:kStoredRequestsKey]; +} + // MARK: - Private - (NSDictionary *)enrichParameters:(NSDictionary *)parameters { @@ -97,13 +126,38 @@ - (NSDictionary *)enrichParameters:(NSDictionary *)parameters { - (void)dataTaskWithRequest:(NSURLRequest *)request completion:(void (^)(NSDictionary * _Nullable dict, NSError * _Nullable error))completion { + [self dataTaskWithRequest:request tryCount:0 completion:completion]; +} + +- (void)dataTaskWithRequest:(NSURLRequest *)request + tryCount:(NSInteger)tryCount + completion:(void (^)(NSDictionary * _Nullable dict, NSError * _Nullable error))completion { + __block NSInteger doneTryCount = tryCount; + + __block __weak QNAPIClient *weakSelf = self; [[self.session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { if (error) { - completion(nil, error); - return; + BOOL isConnectionError = [weakSelf.connectionErrorCodes containsObject:@(error.code)]; + if (isConnectionError) { + if (doneTryCount < 3) { + doneTryCount++; + [weakSelf dataTaskWithRequest:request tryCount:doneTryCount completion:completion]; + + return; + } else { + NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; + NSArray *unarchivedData = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData] ?: @[]; + NSMutableArray *storedRequests = [unarchivedData mutableCopy]; + [storedRequests addObject:request]; + NSData *updatedStoredRequestsData = [NSKeyedArchiver archivedDataWithRootObject:[storedRequests copy]]; + [[NSUserDefaults standardUserDefaults] setValue:updatedStoredRequestsData forKey:kStoredRequestsKey]; + + return; + } + } } - if (!data || ![data isKindOfClass:NSData.class]) { + if ((!data || ![data isKindOfClass:NSData.class]) && completion) { completion(nil, [QNErrors errorWithCode:QNAPIErrorFailedReceiveData]); return; } @@ -111,12 +165,14 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request NSError *jsonError = [[NSError alloc] init]; NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&jsonError]; - if (jsonError.code || !dict) { + if ((jsonError.code || !dict) && completion) { completion(nil, [QNErrors errorWithCode:QNAPIErrorFailedParseResponse]); return; } - completion(dict, nil); + if (completion) { + completion(dict, nil); + } }] resume]; } diff --git a/Sources/Qonversion/QNProductCenterManager.m b/Sources/Qonversion/QNProductCenterManager.m index 7c64297c..c3702473 100644 --- a/Sources/Qonversion/QNProductCenterManager.m +++ b/Sources/Qonversion/QNProductCenterManager.m @@ -272,6 +272,11 @@ - (void)launch:(void (^)(QNLaunchResult * _Nullable result, NSError * _Nullable QNLaunchResult *launchResult = [QNMapper fillLaunchResult:result.data]; completion(launchResult, nil); + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + [weakSelf.apiClient processStoredRequests]; + }); }]; } From ca490cfd7a8c5708186b85aa4b5aaf39950e7504 Mon Sep 17 00:00:00 2001 From: Surik Date: Mon, 28 Sep 2020 18:23:30 +0300 Subject: [PATCH 02/13] Added a part of logic --- Qonversion.xcodeproj/project.pbxproj | 42 ++++++++++++++++++++++----- Sources/Qonversion/QNAPIClient.m | 25 +++++++++++----- Sources/Qonversion/QNAPIConstants.h | 15 ++++++++++ Sources/Qonversion/QNAPIConstants.m | 17 +++++++++++ Sources/Qonversion/QNRequestBuilder.m | 9 +----- 5 files changed, 84 insertions(+), 24 deletions(-) create mode 100644 Sources/Qonversion/QNAPIConstants.h create mode 100644 Sources/Qonversion/QNAPIConstants.m diff --git a/Qonversion.xcodeproj/project.pbxproj b/Qonversion.xcodeproj/project.pbxproj index ec183779..593bc9eb 100644 --- a/Qonversion.xcodeproj/project.pbxproj +++ b/Qonversion.xcodeproj/project.pbxproj @@ -7,8 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 051A90145F9029F72EFF9216 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 09492756259E8BDEC06D7069 /* libPods-Sample.a */; }; - 3ACB2676C0929D7E1B7EBD35 /* libPods-QonversionTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 6D20F2CD8AEC1ED86B5F788D /* libPods-QonversionTests.a */; }; + 37191758408E7B65F15BAAC5 /* Pods_QonversionTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 333F57000FBEEF29ACE10EFF /* Pods_QonversionTests.framework */; }; 45202189244DD02F00C1A928 /* QonversionTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45202188244DD02F00C1A928 /* QonversionTests.m */; }; 45239D6F2517CEB00085D0A8 /* QonversionFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 45239D6E2517CEB00085D0A8 /* QonversionFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; 45239D712517D5CD0085D0A8 /* QNLaunchResult.h in Headers */ = {isa = PBXBuildFile; fileRef = 45239D702517D5CD0085D0A8 /* QNLaunchResult.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -92,10 +91,13 @@ 45FFA2E724BED563007EFB8F /* QNMapperObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 45FFA2E524BED563007EFB8F /* QNMapperObject.m */; }; 45FFA2EB24BEEA9A007EFB8F /* ProductCenterManagerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 45FFA2EA24BEEA9A007EFB8F /* ProductCenterManagerTests.m */; }; 45FFA2ED24BEF379007EFB8F /* full_init.json in Resources */ = {isa = PBXBuildFile; fileRef = 45FFA2EC24BEF379007EFB8F /* full_init.json */; }; + 8911823925222C2300EBCDFA /* QNAPIConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = 8911823725222C2300EBCDFA /* QNAPIConstants.h */; }; + 8911823A25222C2300EBCDFA /* QNAPIConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 8911823825222C2300EBCDFA /* QNAPIConstants.m */; }; 89861D2C2501563B00E5D36B /* Qonversion.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 459DAB69243E329F0011ECF3 /* Qonversion.framework */; }; 89861D2D2501563B00E5D36B /* Qonversion.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 459DAB69243E329F0011ECF3 /* Qonversion.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 89ADA76C250696A400EB2E54 /* ActivePermissionsTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89ADA76B250696A400EB2E54 /* ActivePermissionsTableViewCell.swift */; }; 89ADA76D25069E1000EB2E54 /* ActivePermissionsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 89ADA769250693C400EB2E54 /* ActivePermissionsViewController.swift */; }; + ED3AB4A1C4B5D3043A8AC940 /* Pods_Sample.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F042C4A2E7FB6A12D211E46F /* Pods_Sample.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -131,8 +133,8 @@ /* Begin PBXFileReference section */ 045555B21ABEFDE2E6E5AA58 /* Pods-Sample.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.release.xcconfig"; path = "Target Support Files/Pods-Sample/Pods-Sample.release.xcconfig"; sourceTree = ""; }; - 09492756259E8BDEC06D7069 /* libPods-Sample.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Sample.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 1337D4574E133D635F96F46F /* Pods-Sample.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Sample.debug.xcconfig"; path = "Target Support Files/Pods-Sample/Pods-Sample.debug.xcconfig"; sourceTree = ""; }; + 333F57000FBEEF29ACE10EFF /* Pods_QonversionTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_QonversionTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 45202188244DD02F00C1A928 /* QonversionTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QonversionTests.m; sourceTree = ""; }; 45239D6E2517CEB00085D0A8 /* QonversionFramework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QonversionFramework.h; sourceTree = ""; }; 45239D702517D5CD0085D0A8 /* QNLaunchResult.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QNLaunchResult.h; sourceTree = ""; }; @@ -226,11 +228,13 @@ 45FFA2E524BED563007EFB8F /* QNMapperObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QNMapperObject.m; sourceTree = ""; }; 45FFA2EA24BEEA9A007EFB8F /* ProductCenterManagerTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ProductCenterManagerTests.m; sourceTree = ""; }; 45FFA2EC24BEF379007EFB8F /* full_init.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = full_init.json; sourceTree = ""; }; - 6D20F2CD8AEC1ED86B5F788D /* libPods-QonversionTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-QonversionTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 839DD50DB29B0AB311CD4074 /* Pods-QonversionTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QonversionTests.debug.xcconfig"; path = "Target Support Files/Pods-QonversionTests/Pods-QonversionTests.debug.xcconfig"; sourceTree = ""; }; + 8911823725222C2300EBCDFA /* QNAPIConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = QNAPIConstants.h; sourceTree = ""; }; + 8911823825222C2300EBCDFA /* QNAPIConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = QNAPIConstants.m; sourceTree = ""; }; 89ADA769250693C400EB2E54 /* ActivePermissionsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePermissionsViewController.swift; sourceTree = ""; }; 89ADA76B250696A400EB2E54 /* ActivePermissionsTableViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePermissionsTableViewCell.swift; sourceTree = ""; }; B82EFA1145B6FD87B8EEEF6B /* Pods-QonversionTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-QonversionTests.release.xcconfig"; path = "Target Support Files/Pods-QonversionTests/Pods-QonversionTests.release.xcconfig"; sourceTree = ""; }; + F042C4A2E7FB6A12D211E46F /* Pods_Sample.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Sample.framework; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -239,7 +243,7 @@ buildActionMask = 2147483647; files = ( 89861D2C2501563B00E5D36B /* Qonversion.framework in Frameworks */, - 051A90145F9029F72EFF9216 /* libPods-Sample.a in Frameworks */, + ED3AB4A1C4B5D3043A8AC940 /* Pods_Sample.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -255,7 +259,7 @@ buildActionMask = 2147483647; files = ( 459DAB73243E329F0011ECF3 /* Qonversion.framework in Frameworks */, - 3ACB2676C0929D7E1B7EBD35 /* libPods-QonversionTests.a in Frameworks */, + 37191758408E7B65F15BAAC5 /* Pods_QonversionTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -346,6 +350,8 @@ 45747F562517F91000354B9A /* Models */, 456ECD32249B8FE000D2BC40 /* QNRequestBuilder.h */, 456ECD33249B901900D2BC40 /* QNRequestBuilder.m */, + 8911823725222C2300EBCDFA /* QNAPIConstants.h */, + 8911823825222C2300EBCDFA /* QNAPIConstants.m */, 458A8CD624BAC61E00637130 /* QNRequestSerializer.h */, 458A8CD724BAC61E00637130 /* QNRequestSerializer.m */, 459DABF3243E35BC0011ECF3 /* QNMapper.h */, @@ -557,8 +563,8 @@ BB6B3C9FD9EFBC9E1604507D /* Frameworks */ = { isa = PBXGroup; children = ( - 6D20F2CD8AEC1ED86B5F788D /* libPods-QonversionTests.a */, - 09492756259E8BDEC06D7069 /* libPods-Sample.a */, + 333F57000FBEEF29ACE10EFF /* Pods_QonversionTests.framework */, + F042C4A2E7FB6A12D211E46F /* Pods_Sample.framework */, ); name = Frameworks; sourceTree = ""; @@ -582,6 +588,7 @@ 45FFA2E624BED563007EFB8F /* QNMapperObject.h in Headers */, 459DABF7243E35BC0011ECF3 /* QNKeychain.h in Headers */, 458A8CD524BAC5F500637130 /* QNLocalStorage.h in Headers */, + 8911823925222C2300EBCDFA /* QNAPIConstants.h in Headers */, 459DABF5243E35BC0011ECF3 /* Qonversion.h in Headers */, 459DABF8243E35BC0011ECF3 /* QNUserInfo.h in Headers */, 45FFA2D324BDFCE8007EFB8F /* QNAttributionManager.h in Headers */, @@ -649,6 +656,7 @@ 459DAB6E243E329F0011ECF3 /* Sources */, 459DAB6F243E329F0011ECF3 /* Frameworks */, 459DAB70243E329F0011ECF3 /* Resources */, + DD3851E9154547E7B62E35AC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -787,6 +795,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + DD3851E9154547E7B62E35AC /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-QonversionTests/Pods-QonversionTests-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-QonversionTests/Pods-QonversionTests-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-QonversionTests/Pods-QonversionTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -812,6 +837,7 @@ 4572FE1D24BC40F900A17AC4 /* QNProductCenterManager.m in Sources */, 454BE22B247C3E72001FE771 /* QNProperties.m in Sources */, 459DABF6243E35BC0011ECF3 /* QNUserInfo.m in Sources */, + 8911823A25222C2300EBCDFA /* QNAPIConstants.m in Sources */, 459DAC04243E35BC0011ECF3 /* Qonversion.m in Sources */, 459DABFB243E35BC0011ECF3 /* QNKeeper.m in Sources */, 45F4C2EC2517E85E00AD8FF0 /* QNPermission.m in Sources */, diff --git a/Sources/Qonversion/QNAPIClient.m b/Sources/Qonversion/QNAPIClient.m index bad2b0af..86fdc3a1 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -8,6 +8,7 @@ #import "QNMapperObject.h" #import "QNUtils.h" #import "QNUserInfo.h" +#import "QNAPIConstants.h" static NSString * const kStoredRequestsKey = @"storedRequests"; @@ -17,6 +18,7 @@ @interface QNAPIClient() @property (nonatomic, strong) QNRequestSerializer *requestSerializer; @property (nonatomic, strong) QNRequestBuilder *requestBuilder; @property (nonatomic, copy) NSArray *connectionErrorCodes; +@property (nonatomic, copy) NSArray *retriableRequests; @end @@ -40,6 +42,7 @@ - (instancetype)init { @(NSURLErrorDataNotAllowed), @(NSURLErrorTimedOut) ]; + _retriableRequests = @[kInitEndpoint, kPurchaseEndpoint, kPropertiesEndpoint, kAttributionEndpoint]; } return self; @@ -145,14 +148,7 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request return; } else { - NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; - NSArray *unarchivedData = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData] ?: @[]; - NSMutableArray *storedRequests = [unarchivedData mutableCopy]; - [storedRequests addObject:request]; - NSData *updatedStoredRequestsData = [NSKeyedArchiver archivedDataWithRootObject:[storedRequests copy]]; - [[NSUserDefaults standardUserDefaults] setValue:updatedStoredRequestsData forKey:kStoredRequestsKey]; - - return; + [weakSelf storeRequestIfNeeded:request]; } } } @@ -176,4 +172,17 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request }] resume]; } +- (void)storeRequestIfNeeded:(NSURLRequest *)request { + NSURLComponents *components = [NSURLComponents componentsWithString:request.URL.absoluteString]; + if ([self.retriableRequests containsObject:components.path]) { + NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; + NSArray *unarchivedData = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData] ?: @[]; + NSMutableArray *storedRequests = [unarchivedData mutableCopy]; + [storedRequests addObject:request]; + NSData *updatedStoredRequestsData = [NSKeyedArchiver archivedDataWithRootObject:[storedRequests copy]]; + [[NSUserDefaults standardUserDefaults] setValue:updatedStoredRequestsData forKey:kStoredRequestsKey]; + return; + } +} + @end diff --git a/Sources/Qonversion/QNAPIConstants.h b/Sources/Qonversion/QNAPIConstants.h new file mode 100644 index 00000000..a625b09e --- /dev/null +++ b/Sources/Qonversion/QNAPIConstants.h @@ -0,0 +1,15 @@ +// +// QNAPIConstants.h +// Qonversion +// +// Created by Surik Sarkisyan on 28.09.2020. +// Copyright © 2020 Qonversion Inc. All rights reserved. +// + +#import + +extern NSString *const kAPIBase; +extern NSString *const kInitEndpoint; +extern NSString *const kPurchaseEndpoint; +extern NSString *const kPropertiesEndpoint; +extern NSString *const kAttributionEndpoint; diff --git a/Sources/Qonversion/QNAPIConstants.m b/Sources/Qonversion/QNAPIConstants.m new file mode 100644 index 00000000..46e5b35b --- /dev/null +++ b/Sources/Qonversion/QNAPIConstants.m @@ -0,0 +1,17 @@ +// +// QNAPIConstants.m +// Qonversion +// +// Created by Surik Sarkisyan on 28.09.2020. +// Copyright © 2020 Qonversion Inc. All rights reserved. +// + +#import "QNAPIConstants.h" + +NSString * const kAPIBase = @"https://api.qonversion.io/"; +NSString * const kInitEndpoint = @"v1/user/init"; +NSString * const kPurchaseEndpoint = @"v1/user/purchase"; +NSString * const kPropertiesEndpoint = @"v1/properties"; + + +NSString * const kAttributionEndpoint = @"attribution"; diff --git a/Sources/Qonversion/QNRequestBuilder.m b/Sources/Qonversion/QNRequestBuilder.m index 980d2944..bee8f52d 100644 --- a/Sources/Qonversion/QNRequestBuilder.m +++ b/Sources/Qonversion/QNRequestBuilder.m @@ -1,12 +1,5 @@ #import "QNRequestBuilder.h" - -static NSString * const kAPIBase = @"https://api.qonversion.io/"; -static NSString * const kInitEndpoint = @"v1/user/init"; -static NSString * const kPurchaseEndpoint = @"v1/user/purchase"; -static NSString * const kPropertiesEndpoint = @"v1/properties"; - - -static NSString * const kAttributionEndpoint = @"attribution"; +#import "QNAPIConstants.h" @implementation QNRequestBuilder From c93fb31bec993def61df646e5b32e3964b27fdf9 Mon Sep 17 00:00:00 2001 From: Surik Date: Tue, 29 Sep 2020 17:00:03 +0300 Subject: [PATCH 03/13] Updated products loading logic --- Sample/ViewController.swift | 2 +- Sources/Qonversion/QNLaunchResult.h | 2 +- Sources/Qonversion/QNProductCenterManager.m | 32 +++++++++++++++------ 3 files changed, 25 insertions(+), 11 deletions(-) diff --git a/Sample/ViewController.swift b/Sample/ViewController.swift index cdd96398..eaa4b1ea 100644 --- a/Sample/ViewController.swift +++ b/Sample/ViewController.swift @@ -55,7 +55,7 @@ class ViewController: UIViewController { func checkProducts() { activityIndicator.startAnimating() - Qonversion.products { [weak self] (result) in + Qonversion.products { [weak self] (result, error) in guard let self = self else { return } self.activityIndicator.stopAnimating() diff --git a/Sources/Qonversion/QNLaunchResult.h b/Sources/Qonversion/QNLaunchResult.h index 43a608e8..80d66100 100644 --- a/Sources/Qonversion/QNLaunchResult.h +++ b/Sources/Qonversion/QNLaunchResult.h @@ -63,6 +63,6 @@ typedef void (^QNPermissionCompletionHandler)(NSDictionary *result, NSError *_Nullable error, BOOL cancelled) NS_SWIFT_NAME(Qonversion.PurchaseCompletionHandler); typedef void (^QNRestoreCompletionHandler)(NSDictionary *result, NSError *_Nullable error) NS_SWIFT_NAME(Qonversion.RestoreCompletionHandler); -typedef void (^QNProductsCompletionHandler)(NSDictionary *) NS_SWIFT_NAME(Qonversion.ProductsCompletionHandler); +typedef void (^QNProductsCompletionHandler)(NSDictionary *result, NSError *_Nullable error) NS_SWIFT_NAME(Qonversion.ProductsCompletionHandler); NS_ASSUME_NONNULL_END diff --git a/Sources/Qonversion/QNProductCenterManager.m b/Sources/Qonversion/QNProductCenterManager.m index 3112cad8..562dc1aa 100644 --- a/Sources/Qonversion/QNProductCenterManager.m +++ b/Sources/Qonversion/QNProductCenterManager.m @@ -75,13 +75,19 @@ - (void)launchWithCompletion:(QNLaunchCompletionHandler)completion { weakSelf.launchError = error; [weakSelf executePermissionBlocks]; - [weakSelf loadProducts]; + + if (!_productsLoaded) { + [weakSelf loadProducts]; + } if (result.uid) { QNKeeper.userID = result.uid; [[QNAPIClient shared] setUserID:result.uid]; } - run_block_on_main(completion, result, error) + + if (completion) { + run_block_on_main(completion, result, error) + } }]; } @@ -143,6 +149,10 @@ - (void)executePermissionBlocks { } - (void)executeProductsBlocks { + [self executeProductsBlocksWithError:nil]; +} + +- (void)executeProductsBlocksWithError:(NSError * _Nullable)error { @synchronized (self) { NSMutableArray *_blocks = [self->_productsBlocks copy]; if (_blocks.count == 0) { @@ -163,8 +173,9 @@ - (void)executeProductsBlocks { } } + NSError *resultError = error ?: _launchError; for (QNProductsCompletionHandler _block in _blocks) { - run_block_on_main(_block, resultProducts); + run_block_on_main(_block, resultProducts, resultError); } } @@ -191,14 +202,17 @@ - (void)products:(QNProductsCompletionHandler)completion { @synchronized (self) { [self.productsBlocks addObject:completion]; - if (_productsLoaded) { + if (_productsLoaded && !_launchError) { [self executeProductsBlocks]; + return; } - - if (_launchingFinished && !_productsLoaded) { + + if (_launchError) { __block __weak QNProductCenterManager *weakSelf = self; - [self launch:^(QNLaunchResult * _Nullable result, NSError * _Nullable error) { - [weakSelf executeProductsBlocks]; + [self launchWithCompletion:^(QNLaunchResult * _Nonnull result, NSError * _Nullable error) { + if (error || weakSelf.productsLoaded) { + [weakSelf executeProductsBlocks]; + } }]; } } @@ -354,7 +368,7 @@ - (void)handleRestoreCompletedTransactionsFailed:(NSError *)error { - (void)handleProductsRequestFailed:(NSError *)error { NSError *er = [QNErrors errorFromTransactionError:error]; QONVERSION_LOG(@"Products request failed with message: %@", er.description); - [self executeProductsBlocks]; + [self executeProductsBlocksWithError:error]; } - (void)handleFailedTransaction:(SKPaymentTransaction *)transaction forProduct:(SKProduct *)product { From 86d5dbd5ad8e189b45913baf4a7ea9cd7f7110da Mon Sep 17 00:00:00 2001 From: Surik Date: Tue, 29 Sep 2020 17:37:33 +0300 Subject: [PATCH 04/13] Updated version to 3.0.0 --- Qonversion.xcodeproj/project.pbxproj | 6 +++--- Sources/Qonversion/QNConstants.m | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Qonversion.xcodeproj/project.pbxproj b/Qonversion.xcodeproj/project.pbxproj index 14ce84a4..d7588755 100644 --- a/Qonversion.xcodeproj/project.pbxproj +++ b/Qonversion.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ @@ -1067,7 +1067,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 3.0.0; MODULEMAP_FILE = Framework/Qonversion.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.Qonversion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1096,7 +1096,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 3.0.0; MODULEMAP_FILE = Framework/Qonversion.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.Qonversion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; diff --git a/Sources/Qonversion/QNConstants.m b/Sources/Qonversion/QNConstants.m index 91d01056..57064cfb 100644 --- a/Sources/Qonversion/QNConstants.m +++ b/Sources/Qonversion/QNConstants.m @@ -3,7 +3,7 @@ NSString *const keyQPlatform = @"iOS"; NSString *const keyQOSName = @"ios"; -NSString *const keyQVersion = @"2.2.2"; +NSString *const keyQVersion = @"3.0.0"; NSString *const keyQUnknownLibrary = @"unknown"; NSString *const keyQUnknownVersion = @"unknown"; NSString *const keyQInternalUserID = @"keyQInternalUserID"; From ade11f3efb6a30c3fc8b1d06ff87c512f435ef5b Mon Sep 17 00:00:00 2001 From: Surik Date: Tue, 29 Sep 2020 18:19:42 +0300 Subject: [PATCH 05/13] Updated check retriable requests logic --- Sources/Qonversion/QNAPIClient.m | 40 +++++++++++++++++--------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/Sources/Qonversion/QNAPIClient.m b/Sources/Qonversion/QNAPIClient.m index 86fdc3a1..ebbaf4be 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -68,7 +68,7 @@ - (void)launchRequest:(void (^)(NSDictionary * _Nullable dict, NSError * _Nullab - (void)purchaseRequestWith:(SKProduct *)product transaction:(SKPaymentTransaction *)transaction - completion:(QNAPIClientCompletionHandler)completion { + completion:(QNAPIClientCompletionHandler)completion { NSDictionary *body = [self.requestSerializer purchaseData:product transaction:transaction]; NSDictionary *resultData = [self enrichParameters:body]; NSURLRequest *request = [self.requestBuilder makePurchaseRequestWith:resultData]; @@ -84,7 +84,7 @@ - (void)properties:(NSDictionary *)properties completion:(QNAPIClientCompletionH } - (void)attributionRequest:(QNAttributionProvider)provider - data:(NSDictionary *)data + data:(NSDictionary *)data completion:(QNAPIClientCompletionHandler)completion { NSDictionary *body = [self.requestSerializer attributionDataWithDict:data fromProvider:provider]; NSDictionary *resultData = [self enrichParameters:body]; @@ -93,22 +93,23 @@ - (void)attributionRequest:(QNAttributionProvider)provider } - (void)processStoredRequests { - NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; - NSArray *storedRequests = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData]; - - if (![storedRequests isKindOfClass:[NSArray class]]) { - return; - } - - for (NSInteger i = 0; i < [storedRequests count]; i++) { - if ([storedRequests[i] isKindOfClass:[NSURLRequest class]]) { - NSURLRequest *request = storedRequests[i]; - - [self dataTaskWithRequest:request completion:nil]; - } - } - + NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; + NSArray *storedRequests = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData]; + + if (![storedRequests isKindOfClass:[NSArray class]]) { [[NSUserDefaults standardUserDefaults] setValue:nil forKey:kStoredRequestsKey]; + return; + } + + for (NSInteger i = 0; i < [storedRequests count]; i++) { + if ([storedRequests[i] isKindOfClass:[NSURLRequest class]]) { + NSURLRequest *request = storedRequests[i]; + + [self dataTaskWithRequest:request completion:nil]; + } + } + + [[NSUserDefaults standardUserDefaults] setValue:nil forKey:kStoredRequestsKey]; } // MARK: - Private @@ -152,7 +153,7 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request } } } - + if ((!data || ![data isKindOfClass:NSData.class]) && completion) { completion(nil, [QNErrors errorWithCode:QNAPIErrorFailedReceiveData]); return; @@ -174,7 +175,8 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request - (void)storeRequestIfNeeded:(NSURLRequest *)request { NSURLComponents *components = [NSURLComponents componentsWithString:request.URL.absoluteString]; - if ([self.retriableRequests containsObject:components.path]) { + NSString *requestString = [components.path stringByReplacingOccurrencesOfString:@"/" withString:@"" options:NSCaseInsensitiveSearch range:(NSRange){0, 1}]; + if ([self.retriableRequests containsObject:requestString]) { NSData *storedRequestsData = [[NSUserDefaults standardUserDefaults] valueForKey:kStoredRequestsKey]; NSArray *unarchivedData = [NSKeyedUnarchiver unarchiveObjectWithData:storedRequestsData] ?: @[]; NSMutableArray *storedRequests = [unarchivedData mutableCopy]; From 6c1e72314246d8576126da5128f1420386b42cdb Mon Sep 17 00:00:00 2001 From: Surik Date: Tue, 29 Sep 2020 18:22:07 +0300 Subject: [PATCH 06/13] Moved stored requests key const from QNAPIClient to QNAPIConstants --- Sources/Qonversion/QNAPIClient.m | 2 -- Sources/Qonversion/QNAPIConstants.h | 1 + Sources/Qonversion/QNAPIConstants.m | 2 ++ 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Sources/Qonversion/QNAPIClient.m b/Sources/Qonversion/QNAPIClient.m index ebbaf4be..0fd68871 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -10,8 +10,6 @@ #import "QNUserInfo.h" #import "QNAPIConstants.h" -static NSString * const kStoredRequestsKey = @"storedRequests"; - @interface QNAPIClient() @property (nonatomic, strong) QNDevice *device; diff --git a/Sources/Qonversion/QNAPIConstants.h b/Sources/Qonversion/QNAPIConstants.h index a625b09e..0ffcfefe 100644 --- a/Sources/Qonversion/QNAPIConstants.h +++ b/Sources/Qonversion/QNAPIConstants.h @@ -13,3 +13,4 @@ extern NSString *const kInitEndpoint; extern NSString *const kPurchaseEndpoint; extern NSString *const kPropertiesEndpoint; extern NSString *const kAttributionEndpoint; +extern NSString *const kStoredRequestsKey; diff --git a/Sources/Qonversion/QNAPIConstants.m b/Sources/Qonversion/QNAPIConstants.m index 46e5b35b..f96f9dce 100644 --- a/Sources/Qonversion/QNAPIConstants.m +++ b/Sources/Qonversion/QNAPIConstants.m @@ -15,3 +15,5 @@ NSString * const kAttributionEndpoint = @"attribution"; + +NSString * const kStoredRequestsKey = @"storedRequests"; From 707cd9cd558d12c0e06099094f373658d24245fb Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 30 Sep 2020 11:52:47 +0300 Subject: [PATCH 07/13] Returned error completion --- Sources/Qonversion/QNAPIClient.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Qonversion/QNAPIClient.m b/Sources/Qonversion/QNAPIClient.m index 0fd68871..641f85d4 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -150,6 +150,8 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request [weakSelf storeRequestIfNeeded:request]; } } + + completion(nil, error); } if ((!data || ![data isKindOfClass:NSData.class]) && completion) { @@ -181,7 +183,6 @@ - (void)storeRequestIfNeeded:(NSURLRequest *)request { [storedRequests addObject:request]; NSData *updatedStoredRequestsData = [NSKeyedArchiver archivedDataWithRootObject:[storedRequests copy]]; [[NSUserDefaults standardUserDefaults] setValue:updatedStoredRequestsData forKey:kStoredRequestsKey]; - return; } } From a590a87350800e2568eef68015e4fdf02d705d86 Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 30 Sep 2020 11:59:28 +0300 Subject: [PATCH 08/13] Added missed return after error completion --- Sources/Qonversion/QNAPIClient.m | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Qonversion/QNAPIClient.m b/Sources/Qonversion/QNAPIClient.m index 641f85d4..33034bfe 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -152,6 +152,7 @@ - (void)dataTaskWithRequest:(NSURLRequest *)request } completion(nil, error); + return; } if ((!data || ![data isKindOfClass:NSData.class]) && completion) { From 9a1161b43de3cd3fad8a0605bab7f313edc97a6b Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 30 Sep 2020 12:00:13 +0300 Subject: [PATCH 09/13] Removed multiple mockSession nil for api client tests --- QonversionTests/APIClientTests.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/QonversionTests/APIClientTests.m b/QonversionTests/APIClientTests.m index 03d0dca6..17c8155c 100644 --- a/QonversionTests/APIClientTests.m +++ b/QonversionTests/APIClientTests.m @@ -36,7 +36,7 @@ - (void)setUp { - (void)tearDown { [super tearDown]; - _mockSession = nil; + _mockSession = nil; _client = nil; } From 4abc3234965a5245e53ae60827aead9278cb0e51 Mon Sep 17 00:00:00 2001 From: Surik Date: Wed, 30 Sep 2020 12:01:33 +0300 Subject: [PATCH 10/13] Added missed mock request = nil for api client tests --- QonversionTests/APIClientTests.m | 1 + 1 file changed, 1 insertion(+) diff --git a/QonversionTests/APIClientTests.m b/QonversionTests/APIClientTests.m index 17c8155c..0b65ace2 100644 --- a/QonversionTests/APIClientTests.m +++ b/QonversionTests/APIClientTests.m @@ -37,6 +37,7 @@ - (void)setUp { - (void)tearDown { [super tearDown]; + _request = nil; _mockSession = nil; _client = nil; } From c1669273daa30c642719d23b0f8d78651db6ee44 Mon Sep 17 00:00:00 2001 From: Sam Mejl Date: Wed, 30 Sep 2020 13:22:54 +0300 Subject: [PATCH 11/13] Apply suggestions from code review --- Qonversion.xcodeproj/project.pbxproj | 4 ++-- Sources/Qonversion/QNConstants.m | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Qonversion.xcodeproj/project.pbxproj b/Qonversion.xcodeproj/project.pbxproj index d7588755..b27239a3 100644 --- a/Qonversion.xcodeproj/project.pbxproj +++ b/Qonversion.xcodeproj/project.pbxproj @@ -1067,7 +1067,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 2.3.0; MODULEMAP_FILE = Framework/Qonversion.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.Qonversion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1096,7 +1096,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 3.0.0; + MARKETING_VERSION = 2.3.0; MODULEMAP_FILE = Framework/Qonversion.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.Qonversion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; diff --git a/Sources/Qonversion/QNConstants.m b/Sources/Qonversion/QNConstants.m index 57064cfb..797c018f 100644 --- a/Sources/Qonversion/QNConstants.m +++ b/Sources/Qonversion/QNConstants.m @@ -3,7 +3,7 @@ NSString *const keyQPlatform = @"iOS"; NSString *const keyQOSName = @"ios"; -NSString *const keyQVersion = @"3.0.0"; +NSString *const keyQVersion = @"2.3.0"; NSString *const keyQUnknownLibrary = @"unknown"; NSString *const keyQUnknownVersion = @"unknown"; NSString *const keyQInternalUserID = @"keyQInternalUserID"; From 6cd94cec949cb160935bbf727d618af08b47ac5e Mon Sep 17 00:00:00 2001 From: Sam Mejlumyan Date: Wed, 30 Sep 2020 13:59:15 +0300 Subject: [PATCH 12/13] Bump version --- Qonversion.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Qonversion.podspec b/Qonversion.podspec index 0dd100d6..3f33bdb0 100644 --- a/Qonversion.podspec +++ b/Qonversion.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'Qonversion' - s.version = '2.2.2' + s.version = '2.3.0' s.summary = 'qonversion.io' s.description = <<-DESC Deep Analytics for iOS Subscriptions From b111a22d12245280404dc2a2c1379e6af320f833 Mon Sep 17 00:00:00 2001 From: Sam Mejlumyan Date: Wed, 30 Sep 2020 14:11:54 +0300 Subject: [PATCH 13/13] no message --- Sources/Qonversion/QNUserPropertiesManager.m | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Sources/Qonversion/QNUserPropertiesManager.m b/Sources/Qonversion/QNUserPropertiesManager.m index d3106750..c71c58a0 100644 --- a/Sources/Qonversion/QNUserPropertiesManager.m +++ b/Sources/Qonversion/QNUserPropertiesManager.m @@ -190,6 +190,8 @@ - (void)collectIntegrationsDataInBackground { if (![QNUtils isEmptyString:afUserID]) { [self setUserProperty:@"_q_appsflyer_user_id" value:afUserID]; } + + [self sendPropertiesInBackground]; } @end