Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: application state check on app extensions #303

Merged
merged 8 commits into from
Aug 4, 2020
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 0 additions & 8 deletions Purchases/Misc/RCCrossPlatformSupport.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,3 @@
#else
#define PURCHASES_INITIATED_FROM_APP_STORE_AVAILABLE 0
#endif

#if TARGET_OS_IOS || TARGET_OS_TV
#define IS_APPLICATION_BACKGROUNDED UIApplication.sharedApplication.applicationState == UIApplicationStateBackground
#elif TARGET_OS_OSX
#define IS_APPLICATION_BACKGROUNDED NO
#elif TARGET_OS_WATCH
#define IS_APPLICATION_BACKGROUNDED WKExtension.sharedExtension.applicationState == WKApplicationStateBackground
#endif
2 changes: 1 addition & 1 deletion Purchases/Misc/RCSystemInfo.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
@property(nonatomic, copy, readonly) NSString *platformFlavorVersion;


- (BOOL)isApplicationBackgrounded;
- (void)isApplicationBackgroundedWithCompletion:(void(^)(BOOL))completion; // calls completion on the main thread
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there might be a better way to document that this returns on the main thread?


+ (BOOL)isSandbox;
+ (NSString *)frameworkVersion;
Expand Down
35 changes: 34 additions & 1 deletion Purchases/Misc/RCSystemInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,10 +83,43 @@ + (void)setProxyURL:(nullable NSURL *)newProxyURL {
}
}

- (void)isApplicationBackgroundedWithCompletion:(void(^)(BOOL))completion {
dispatch_async(dispatch_get_main_queue(), ^{
BOOL isApplicationBackgrounded = self.isApplicationBackgrounded;
completion(isApplicationBackgrounded);
});
}

- (BOOL)isApplicationBackgrounded {
return IS_APPLICATION_BACKGROUNDED;
#if TARGET_OS_IOS
return self.isApplicationBackgroundedIOS;
#elif TARGET_OS_TV
return UIApplication.sharedApplication.applicationState == UIApplicationStateBackground;
#elif TARGET_OS_OSX
return NO;
#elif TARGET_OS_WATCH
return WKExtension.sharedExtension.applicationState == WKApplicationStateBackground;
Comment on lines +96 to +101
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the other targets should be safe I believe

#endif
}

#if TARGET_OS_IOS

- (BOOL)isApplicationBackgroundedIOS {
if (self.isAppExtension) {
return YES;
}
NSString *sharedApplicationPropertyName = @"sharedApplication";
aboedo marked this conversation as resolved.
Show resolved Hide resolved

UIApplication *sharedApplication = [UIApplication valueForKey:sharedApplicationPropertyName];
return sharedApplication.applicationState == UIApplicationStateBackground;
}

- (BOOL)isAppExtension {
return [NSBundle.mainBundle.bundlePath hasSuffix:@".appex"];
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was taken from MParticle's solution

}

#endif

@end


Expand Down
18 changes: 12 additions & 6 deletions Purchases/Public/RCPurchases.m
Original file line number Diff line number Diff line change
Expand Up @@ -295,12 +295,18 @@ - (instancetype)initWithAppUserID:(nullable NSString *)appUserID
};

[self.identityManager configureWithAppUserID:appUserID];
if (!self.systemInfo.isApplicationBackgrounded) {
[self updateAllCachesWithCompletionBlock:callDelegate];
} else {
[self sendCachedPurchaserInfoIfAvailable];
}


[self.systemInfo isApplicationBackgroundedWithCompletion:^(BOOL isBackgrounded) {
if (!isBackgrounded) {
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(backgroundQueue, ^{
[self updateAllCachesWithCompletionBlock:callDelegate];
});
} else {
[self sendCachedPurchaserInfoIfAvailable];
}
}];
Comment on lines +299 to +308
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is the reason for a lot of the changes in the tests - since we're calling the updateAllCaches method from a background queue, it will take longer to return for tests.


[self configureSubscriberAttributesManager];

self.storeKitWrapper.delegate = self;
Expand Down
4 changes: 2 additions & 2 deletions PurchasesTests/Mocks/MockSystemInfo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import Foundation
class MockSystemInfo: RCSystemInfo {
var stubbedIsApplicationBackgrounded: Bool?

override func isApplicationBackgrounded() -> Bool {
return stubbedIsApplicationBackgrounded ?? super.isApplicationBackgrounded()
override func isApplicationBackgrounded(completion: @escaping (Bool) -> Void) {
completion(stubbedIsApplicationBackgrounded ?? false)
}
}
11 changes: 9 additions & 2 deletions PurchasesTests/Networking/HTTPClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,17 @@ class HTTPClientTests: XCTestCase {
}

self.client.performRequest("GET", path: path, body: nil, headers: nil) { (status, data, responseError) in
successFailed = (status >= 500) && (data == nil) && (error == responseError as NSError?)
if let responseNSError = responseError as? NSError {
successFailed = (status >= 500
&& data == nil
&& error.domain == responseNSError.domain
&& error.code == responseNSError.code)
Comment on lines +209 to +213
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this had been bugging me for a while - apparently on iOS 14, error != responseError as NSError?.
So this would fail. I'm just manually checking the domain and code, which should suffice for the purposes of the test.

} else {
successFailed = false
}
}

expect(successFailed).toEventually(equal(true), timeout: 1.0)
expect(successFailed).toEventually(equal(true))
}

func testServerSide400s() {
Expand Down
15 changes: 9 additions & 6 deletions PurchasesTests/Purchasing/PurchasesTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class PurchasesTests: XCTestCase {

override func setUp() {
self.userDefaults = UserDefaults(suiteName: "TestDefaults")
requestFetcher = MockRequestFetcher()
systemInfo = MockSystemInfo(platformFlavor: nil, platformFlavorVersion: nil, finishTransactions: true)
Comment on lines +15 to +16
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should be doing this for all mocks - otherwise they just get created once and keep state in between tests.
I'm just doing it here for these two since I had issues, but I'm not 100% it's necessary. I'll check before merging.

}

override func tearDown() {
Expand Down Expand Up @@ -169,7 +171,7 @@ class PurchasesTests: XCTestCase {


let receiptFetcher = MockReceiptFetcher()
let requestFetcher = MockRequestFetcher()
var requestFetcher: MockRequestFetcher!
let backend = MockBackend()
let storeKitWrapper = MockStoreKitWrapper()
let notificationCenter = MockNotificationCenter()
Expand All @@ -179,7 +181,7 @@ class PurchasesTests: XCTestCase {
let deviceCache = MockDeviceCache()
let subscriberAttributesManager = MockSubscriberAttributesManager()
let identityManager = MockUserManager(mockAppUserID: "app_user");
let systemInfo = MockSystemInfo(platformFlavor: nil, platformFlavorVersion: nil, finishTransactions: true)
var systemInfo: MockSystemInfo!

let purchasesDelegate = MockPurchasesDelegate()

Expand Down Expand Up @@ -641,7 +643,8 @@ class PurchasesTests: XCTestCase {
}
}

func testFetchesProductInfoIfNotCachedAndAppActive() {
func testFetchesProductInfoIfNotCached() {
systemInfo.stubbedIsApplicationBackgrounded = true
setupPurchases()
let product = MockSKProduct(mockProductIdentifier: "com.product.id1")

Expand All @@ -657,7 +660,7 @@ class PurchasesTests: XCTestCase {
transaction.mockState = SKPaymentTransactionState.purchased
self.storeKitWrapper.delegate?.storeKitWrapper(self.storeKitWrapper, updatedTransaction: transaction)

expect(self.requestFetcher.requestedProducts! as NSSet).to(contain([product.productIdentifier]))
expect(self.requestFetcher.requestedProducts! as NSSet).toEventually(contain([product.productIdentifier]))

expect(self.backend.postedProductID).toNot(beNil())
expect(self.backend.postedPrice).toNot(beNil())
Expand Down Expand Up @@ -1270,7 +1273,7 @@ class PurchasesTests: XCTestCase {

setupPurchases()

expect(self.backend.getSubscriberCallCount).to(equal(1))
expect(self.backend.getSubscriberCallCount).toEventually(equal(1))

purchases!.purchaserInfo { (info, error) in
}
Expand Down Expand Up @@ -2074,7 +2077,7 @@ class PurchasesTests: XCTestCase {
expect(self.backend.getSubscriberCallCount).toEventually(equal(2))
expect(self.deviceCache.cachedPurchaserInfo.count).toEventually(equal(2))
expect(self.deviceCache.cachedPurchaserInfo[newAppUserID]).toNot(beNil())
expect(self.purchasesDelegate.purchaserInfoReceivedCount).toEventually(equal(2))
expect(self.purchasesDelegate.purchaserInfoReceivedCount).toEventually(equal(2), timeout: 3.0)
expect(self.deviceCache.setPurchaserInfoCacheTimestampToNowCount).toEventually(equal(2))
expect(self.deviceCache.setOfferingsCacheTimestampToNowCount).toEventually(equal(2))
expect(self.backend.gotOfferings).toEventually(equal(2))
Expand Down