summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2021-09-15 11:04:25 +0200
committerAndrej Mihajlov <and@mullvad.net>2021-09-15 11:04:25 +0200
commit9375bbe666b7e02491b39f31291b45a38a335af3 (patch)
tree5536ea27a32e9c79a061d2c20f0afd16b0fe4125
parentd336fcb0f55336373e076745fc3394232d7ca44a (diff)
parent128e7601092b5aeada2463e32d3a8fdde4c8af41 (diff)
downloadmullvadvpn-9375bbe666b7e02491b39f31291b45a38a335af3.tar.xz
mullvadvpn-9375bbe666b7e02491b39f31291b45a38a335af3.zip
Merge branch 'appstore-receipt-fixes'
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AppStoreReceipt.swift101
-rw-r--r--ios/MullvadVPN/Operations/ReceiptRefreshOperation.swift46
3 files changed, 87 insertions, 64 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 15263a1696..12f9d22b1e 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -54,6 +54,7 @@
5845F843236CBDAB00B2D93C /* PacketTunnelIpc.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */; };
5846226726E0DF960035F7C2 /* Promise+OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226626E0DF960035F7C2 /* Promise+OperationQueue.swift */; };
5846226826E0DF960035F7C2 /* Promise+OperationQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226626E0DF960035F7C2 /* Promise+OperationQueue.swift */; };
+ 5846226A26E0E6FA0035F7C2 /* ReceiptRefreshOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5846226926E0E6FA0035F7C2 /* ReceiptRefreshOperation.swift */; };
584789B8264D4A2A000E45FB /* old_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B4264D4A2A000E45FB /* old_le_root_cert.cer */; };
584789B9264D4A2A000E45FB /* old_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B4264D4A2A000E45FB /* old_le_root_cert.cer */; };
584789BE264D4A2A000E45FB /* new_le_root_cert.cer in Resources */ = {isa = PBXBuildFile; fileRef = 584789B7264D4A2A000E45FB /* new_le_root_cert.cer */; };
@@ -339,6 +340,7 @@
584592602639B4A200EF967F /* ConsentContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConsentContentView.swift; sourceTree = "<group>"; };
5845F841236CBACD00B2D93C /* PacketTunnelIpc.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelIpc.swift; sourceTree = "<group>"; };
5846226626E0DF960035F7C2 /* Promise+OperationQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Promise+OperationQueue.swift"; sourceTree = "<group>"; };
+ 5846226926E0E6FA0035F7C2 /* ReceiptRefreshOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReceiptRefreshOperation.swift; sourceTree = "<group>"; };
584789B4264D4A2A000E45FB /* old_le_root_cert.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = old_le_root_cert.cer; sourceTree = "<group>"; };
584789B7264D4A2A000E45FB /* new_le_root_cert.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = new_le_root_cert.cer; sourceTree = "<group>"; };
584789DF26529D72000E45FB /* SSLPinningURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SSLPinningURLSessionDelegate.swift; sourceTree = "<group>"; };
@@ -530,6 +532,7 @@
580EE22324B3243100F9D8A1 /* AsyncBlockOperation.swift */,
58E973DD24850EB600096F90 /* AsyncOperation.swift */,
580EE20524B3222200F9D8A1 /* ExclusivityController.swift */,
+ 5846226926E0E6FA0035F7C2 /* ReceiptRefreshOperation.swift */,
);
path = Operations;
sourceTree = "<group>";
@@ -1135,6 +1138,7 @@
58FEEB58260B662E00A621A8 /* AutomaticKeyboardResponder.swift in Sources */,
58FAEDEF245069C700CB0F5B /* KeychainAttributes.swift in Sources */,
58CB0EE024B86751001EF0D8 /* MullvadRest.swift in Sources */,
+ 5846226A26E0E6FA0035F7C2 /* ReceiptRefreshOperation.swift in Sources */,
58E1337526D2BEC400CC316B /* Promise+Optional.swift in Sources */,
58293FB125124117005D0BB5 /* CustomTextField.swift in Sources */,
582AE3102440A6CA00E6733A /* AccountTokenInput.swift in Sources */,
diff --git a/ios/MullvadVPN/AppStoreReceipt.swift b/ios/MullvadVPN/AppStoreReceipt.swift
index f6e11ef49d..d39d58cf25 100644
--- a/ios/MullvadVPN/AppStoreReceipt.swift
+++ b/ios/MullvadVPN/AppStoreReceipt.swift
@@ -33,17 +33,40 @@ enum AppStoreReceipt {
}
/// An operation queue used to run receipt refresh requests
- private static let operationQueue = OperationQueue()
+ private static let operationQueue: OperationQueue = {
+ let queue = OperationQueue()
+ queue.name = "AppStoreReceiptQueue"
+ queue.maxConcurrentOperationCount = 1
+ return queue
+ }()
+
+ /// Read AppStore receipt from disk or refresh it from AppStore if it's missing.
+ /// This call may trigger a sign in with AppStore prompt to appear.
+ static func fetch(forceRefresh: Bool = false, receiptProperties: [String: Any]? = nil) -> Result<Data, Error>.Promise {
+ if forceRefresh {
+ return refreshReceipt(receiptProperties: receiptProperties)
+ } else {
+ return self.readFromDisk()
+ .asPromise()
+ .flatMapErrorThen { error in
+ if case .doesNotExist = error {
+ return refreshReceipt(receiptProperties: receiptProperties)
+ } else {
+ return .failure(error)
+ }
+ }
+ }
+ }
/// Read AppStore receipt from disk
- static func readFromDisk() -> Result<Data, Error> {
+ private static func readFromDisk() -> Result<Data, Error> {
guard let appStoreReceiptURL = Bundle.main.appStoreReceiptURL else {
return .failure(.doesNotExist)
}
return Result { try Data(contentsOf: appStoreReceiptURL) }
.mapError { (error) -> Error in
- if let ioError = error as? CocoaError, ioError.code == .fileNoSuchFile {
+ if let cocoaError = error as? CocoaError, cocoaError.code == .fileReadNoSuchFile || cocoaError.code == .fileNoSuchFile {
return .doesNotExist
} else {
return .io(error)
@@ -51,70 +74,20 @@ enum AppStoreReceipt {
}
}
- /// Read AppStore receipt from disk or refresh it from the AppStore if it's missing
- /// This call may trigger a sign in with AppStore prompt to appear
- static func fetch(forceRefresh: Bool = false, receiptProperties: [String: Any]? = nil,
- completionHandler: @escaping (Result<Data, Error>) -> Void)
- {
- if forceRefresh {
- refreshReceipt(receiptProperties: receiptProperties,
- completionHandler: completionHandler)
- } else {
- switch self.readFromDisk() {
- case .success(let data):
- completionHandler(.success(data))
-
- case .failure(let error):
- // Refresh the receipt from AppStore if it's not on disk
- if case .doesNotExist = error {
- refreshReceipt(receiptProperties: receiptProperties,
- completionHandler: completionHandler)
- } else {
- completionHandler(.failure(error))
- }
+ /// Refresh receipt from AppStore
+ private static func refreshReceipt(receiptProperties: [String: Any]?) -> Result<Data, Error>.Promise {
+ return Result<(), Swift.Error>.Promise { resolver in
+ let operation = ReceiptRefreshOperation(receiptProperties: receiptProperties) { result in
+ resolver.resolve(value: result)
}
+ self.operationQueue.addOperation(operation)
}
- }
-
- private static func refreshReceipt(receiptProperties: [String: Any]?, completionHandler: @escaping (Result<Data, Error>) -> Void) {
- let refreshOperation = ReceiptRefreshOperation(receiptProperties: receiptProperties)
- refreshOperation.addDidFinishBlockObserver { (operation, result) in
- let result = result
- .mapError { Error.refresh($0) }
- .flatMap { Self.readFromDisk() }
- completionHandler(result)
+ .mapError { error in
+ return .refresh(error)
+ }
+ .flatMap {
+ return Self.readFromDisk()
}
-
- operationQueue.addOperation(refreshOperation)
}
}
-
-private class ReceiptRefreshOperation: AsyncOperation, OutputOperation, SKRequestDelegate {
- typealias Output = Result<(), Error>
-
- private let request: SKReceiptRefreshRequest
-
- init(receiptProperties: [String: Any]?) {
- request = SKReceiptRefreshRequest(receiptProperties: receiptProperties)
- }
-
- override func main() {
- request.delegate = self
- request.start()
- }
-
- override func operationDidCancel() {
- request.cancel()
- }
-
- // - MARK: SKRequestDelegate
-
- func requestDidFinish(_ request: SKRequest) {
- finish(with: .success(()))
- }
-
- func request(_ request: SKRequest, didFailWithError error: Error) {
- finish(with: .failure(error))
- }
-}
diff --git a/ios/MullvadVPN/Operations/ReceiptRefreshOperation.swift b/ios/MullvadVPN/Operations/ReceiptRefreshOperation.swift
new file mode 100644
index 0000000000..c386fc1cb1
--- /dev/null
+++ b/ios/MullvadVPN/Operations/ReceiptRefreshOperation.swift
@@ -0,0 +1,46 @@
+//
+// ReceiptRefreshOperation.swift
+// ReceiptRefreshOperation
+//
+// Created by pronebird on 02/09/2021.
+// Copyright © 2021 Mullvad VPN AB. All rights reserved.
+//
+
+import Foundation
+import StoreKit
+
+class ReceiptRefreshOperation: AsyncOperation, SKRequestDelegate {
+ private let request: SKReceiptRefreshRequest
+ private let completionHandler: (Result<(), Error>) -> Void
+
+ init(receiptProperties: [String: Any]?, completionHandler: @escaping (Result<(), Error>) -> Void) {
+ request = SKReceiptRefreshRequest(receiptProperties: receiptProperties)
+ self.completionHandler = completionHandler
+
+ super.init()
+
+ request.delegate = self
+ }
+
+ override func main() {
+ request.start()
+ }
+
+ override func cancel() {
+ super.cancel()
+
+ request.cancel()
+ }
+
+ // - MARK: SKRequestDelegate
+
+ func requestDidFinish(_ request: SKRequest) {
+ completionHandler(.success(()))
+ finish()
+ }
+
+ func request(_ request: SKRequest, didFailWithError error: Error) {
+ completionHandler(.failure(error))
+ finish()
+ }
+}