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 diff --git a/Qonversion.xcodeproj/project.pbxproj b/Qonversion.xcodeproj/project.pbxproj index 14ce84a4..03593bc3 100644 --- a/Qonversion.xcodeproj/project.pbxproj +++ b/Qonversion.xcodeproj/project.pbxproj @@ -3,11 +3,11 @@ archiveVersion = 1; classes = { }; - objectVersion = 50; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ - 051A90145F9029F72EFF9216 /* libPods-Sample.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 09492756259E8BDEC06D7069 /* libPods-Sample.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, ); }; }; @@ -91,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 */ @@ -130,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 = ""; }; @@ -225,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 */ @@ -238,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; }; @@ -254,6 +259,7 @@ buildActionMask = 2147483647; files = ( 459DAB73243E329F0011ECF3 /* Qonversion.framework in Frameworks */, + 37191758408E7B65F15BAAC5 /* Pods_QonversionTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -344,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 */, @@ -555,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 = ""; @@ -580,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 */, @@ -647,6 +656,7 @@ 459DAB6E243E329F0011ECF3 /* Sources */, 459DAB6F243E329F0011ECF3 /* Frameworks */, 459DAB70243E329F0011ECF3 /* Resources */, + DD3851E9154547E7B62E35AC /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -785,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 */ @@ -810,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 */, @@ -1067,7 +1095,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 2.3.0; MODULEMAP_FILE = Framework/Qonversion.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.Qonversion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; @@ -1096,7 +1124,7 @@ "@executable_path/Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 2.2.2; + MARKETING_VERSION = 2.3.0; MODULEMAP_FILE = Framework/Qonversion.modulemap; PRODUCT_BUNDLE_IDENTIFIER = com.qonversion.Qonversion; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; diff --git a/QonversionTests/APIClientTests.m b/QonversionTests/APIClientTests.m index 03d0dca6..0b65ace2 100644 --- a/QonversionTests/APIClientTests.m +++ b/QonversionTests/APIClientTests.m @@ -36,7 +36,8 @@ - (void)setUp { - (void)tearDown { [super tearDown]; - _mockSession = nil; + + _request = nil; _mockSession = nil; _client = nil; } 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/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..33034bfe 100644 --- a/Sources/Qonversion/QNAPIClient.m +++ b/Sources/Qonversion/QNAPIClient.m @@ -8,12 +8,15 @@ #import "QNMapperObject.h" #import "QNUtils.h" #import "QNUserInfo.h" +#import "QNAPIConstants.h" @interface QNAPIClient() @property (nonatomic, strong) QNDevice *device; @property (nonatomic, strong) QNRequestSerializer *requestSerializer; @property (nonatomic, strong) QNRequestBuilder *requestBuilder; +@property (nonatomic, copy) NSArray *connectionErrorCodes; +@property (nonatomic, copy) NSArray *retriableRequests; @end @@ -30,6 +33,14 @@ - (instancetype)init { _debug = NO; _device = QNDevice.current; _session = [NSURLSession sessionWithConfiguration:NSURLSessionConfiguration.defaultSessionConfiguration]; + _connectionErrorCodes = @[ + @(NSURLErrorNotConnectedToInternet), + @(NSURLErrorCallIsActive), + @(NSURLErrorNetworkConnectionLost), + @(NSURLErrorDataNotAllowed), + @(NSURLErrorTimedOut) + ]; + _retriableRequests = @[kInitEndpoint, kPurchaseEndpoint, kPropertiesEndpoint, kAttributionEndpoint]; } return self; @@ -55,7 +66,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]; @@ -71,7 +82,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]; @@ -79,6 +90,26 @@ - (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]]) { + [[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 - (NSDictionary *)enrichParameters:(NSDictionary *)parameters { @@ -97,13 +128,34 @@ - (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) { + BOOL isConnectionError = [weakSelf.connectionErrorCodes containsObject:@(error.code)]; + if (isConnectionError) { + if (doneTryCount < 3) { + doneTryCount++; + [weakSelf dataTaskWithRequest:request tryCount:doneTryCount completion:completion]; + + return; + } else { + [weakSelf storeRequestIfNeeded:request]; + } + } + completion(nil, error); return; } - - if (!data || ![data isKindOfClass:NSData.class]) { + + if ((!data || ![data isKindOfClass:NSData.class]) && completion) { completion(nil, [QNErrors errorWithCode:QNAPIErrorFailedReceiveData]); return; } @@ -111,13 +163,28 @@ - (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]; } +- (void)storeRequestIfNeeded:(NSURLRequest *)request { + NSURLComponents *components = [NSURLComponents componentsWithString:request.URL.absoluteString]; + 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]; + [storedRequests addObject:request]; + NSData *updatedStoredRequestsData = [NSKeyedArchiver archivedDataWithRootObject:[storedRequests copy]]; + [[NSUserDefaults standardUserDefaults] setValue:updatedStoredRequestsData forKey:kStoredRequestsKey]; + } +} + @end diff --git a/Sources/Qonversion/QNAPIConstants.h b/Sources/Qonversion/QNAPIConstants.h new file mode 100644 index 00000000..0ffcfefe --- /dev/null +++ b/Sources/Qonversion/QNAPIConstants.h @@ -0,0 +1,16 @@ +// +// 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; +extern NSString *const kStoredRequestsKey; diff --git a/Sources/Qonversion/QNAPIConstants.m b/Sources/Qonversion/QNAPIConstants.m new file mode 100644 index 00000000..f96f9dce --- /dev/null +++ b/Sources/Qonversion/QNAPIConstants.m @@ -0,0 +1,19 @@ +// +// 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"; + +NSString * const kStoredRequestsKey = @"storedRequests"; diff --git a/Sources/Qonversion/QNConstants.m b/Sources/Qonversion/QNConstants.m index 91d01056..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 = @"2.2.2"; +NSString *const keyQVersion = @"2.3.0"; NSString *const keyQUnknownLibrary = @"unknown"; NSString *const keyQUnknownVersion = @"unknown"; NSString *const keyQInternalUserID = @"keyQInternalUserID"; 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..1f4d2bf1 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]; + } }]; } } @@ -272,6 +286,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]; + }); }]; } @@ -354,7 +373,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 { 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 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