summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorAndrej Mihajlov <and@mullvad.net>2020-03-26 12:45:41 +0100
committerAndrej Mihajlov <and@mullvad.net>2020-03-26 16:23:13 +0100
commitae05a614cee69892952655ca7003fdc5081d764f (patch)
tree582dad6a87823c5df8b0a4588a1bdc8453c03686
parent11fde0398a9e2a758c082dad02ea08de6844b70a (diff)
downloadmullvadvpn-ae05a614cee69892952655ca7003fdc5081d764f.tar.xz
mullvadvpn-ae05a614cee69892952655ca7003fdc5081d764f.zip
Add AppStoreReceipt reader
-rw-r--r--ios/MullvadVPN.xcodeproj/project.pbxproj4
-rw-r--r--ios/MullvadVPN/AppStoreReceipt.swift79
2 files changed, 83 insertions, 0 deletions
diff --git a/ios/MullvadVPN.xcodeproj/project.pbxproj b/ios/MullvadVPN.xcodeproj/project.pbxproj
index 449a9f06cc..205cd82a43 100644
--- a/ios/MullvadVPN.xcodeproj/project.pbxproj
+++ b/ios/MullvadVPN.xcodeproj/project.pbxproj
@@ -122,6 +122,7 @@
58D0C7A223F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58D0C7A023F1CECF00FE9BA7 /* MullvadVPNScreenshots.swift */; };
58EC4E6C23915325003F5C5B /* Bundle+MullvadVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58EC4E6B23915325003F5C5B /* Bundle+MullvadVersion.swift */; };
58F19E35228C15BA00C7710B /* SpinnerActivityIndicatorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */; };
+ 58FD5BE724192A2C00112C88 /* AppStoreReceipt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BE624192A2B00112C88 /* AppStoreReceipt.swift */; };
58FD5BE92419406000112C88 /* SKRequestPublisher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 58FD5BE82419406000112C88 /* SKRequestPublisher.swift */; };
/* End PBXBuildFile section */
@@ -259,6 +260,7 @@
58F19E34228C15BA00C7710B /* SpinnerActivityIndicatorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpinnerActivityIndicatorView.swift; sourceTree = "<group>"; };
58FBDAA422A52BDA00EB69A3 /* PacketTunnel-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "PacketTunnel-Bridging-Header.h"; sourceTree = "<group>"; };
58FBDAAA22A52DC500EB69A3 /* MullvadVPN-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MullvadVPN-Bridging-Header.h"; sourceTree = "<group>"; };
+ 58FD5BE624192A2B00112C88 /* AppStoreReceipt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStoreReceipt.swift; sourceTree = "<group>"; };
58FD5BE82419406000112C88 /* SKRequestPublisher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SKRequestPublisher.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -351,6 +353,7 @@
5868585424054096000B8131 /* AppButton.swift */,
58CE5E63224146200008646E /* AppDelegate.swift */,
58BFA5CB22A7CE1F00A6173D /* ApplicationConfiguration.swift */,
+ 58FD5BE624192A2B00112C88 /* AppStoreReceipt.swift */,
58CE5E6A224146210008646E /* Assets.xcassets */,
5845F839236C6A7200B2D93C /* AutoDisposableSink.swift */,
589AB4F6227B64450039131E /* BasicTableViewCell.swift */,
@@ -751,6 +754,7 @@
5888AD83227B11080051EB06 /* SelectLocationCell.swift in Sources */,
58CE5E66224146200008646E /* LoginViewController.swift in Sources */,
5877152E23981C5B001F8237 /* SettingsBasicCell.swift in Sources */,
+ 58FD5BE724192A2C00112C88 /* AppStoreReceipt.swift in Sources */,
5835B7CC233B76CB0096D79F /* TunnelManager.swift in Sources */,
58ADDB3C227B1BD200FAFEA7 /* JsonRpc.swift in Sources */,
581CBCEE229826FD00727D7F /* StaticTableViewDataSource.swift in Sources */,
diff --git a/ios/MullvadVPN/AppStoreReceipt.swift b/ios/MullvadVPN/AppStoreReceipt.swift
new file mode 100644
index 0000000000..ecb2e8d130
--- /dev/null
+++ b/ios/MullvadVPN/AppStoreReceipt.swift
@@ -0,0 +1,79 @@
+//
+// AppStoreReceipt.swift
+// MullvadVPN
+//
+// Created by pronebird on 11/03/2020.
+// Copyright © 2020 Mullvad VPN AB. All rights reserved.
+//
+
+import Combine
+import Foundation
+import StoreKit
+
+enum AppStoreReceipt {
+ enum Error: Swift.Error {
+ /// AppStore receipt file does not exist or file URL is not available
+ case doesNotExist
+
+ /// IO error
+ case io(Swift.Error)
+
+ /// Failure to refresh the receipt from AppStore
+ case refresh(Swift.Error)
+
+ var localizedDescription: String {
+ switch self {
+ case .doesNotExist:
+ return "AppStore receipt file does not exist on disk"
+ case .io(let ioError):
+ return "Read error: \(ioError.localizedDescription)"
+ case .refresh(let refreshError):
+ return "Receipt refresh error: \(refreshError.localizedDescription)"
+ }
+ }
+ }
+
+ /// Read AppStore receipt from disk
+ 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 {
+ return .doesNotExist
+ } else {
+ return .io(error)
+ }
+ }
+ }
+
+ /// 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) -> AnyPublisher<Data, Error> {
+ let refreshReceiptPublisher = Deferred {
+ SKReceiptRefreshRequest(receiptProperties: receiptProperties)
+ .publisher
+ .mapError { .refresh($0) }
+ .flatMap({ _ -> Result<Data, Error>.Publisher in
+ return self.readFromDisk().publisher
+ })
+ }
+
+ if forceRefresh {
+ return refreshReceiptPublisher.eraseToAnyPublisher()
+ } else {
+ return Deferred { self.readFromDisk().publisher }
+ .catch({ (readError) -> AnyPublisher<Data, Error> in
+ // Refresh the receipt from AppStore if it's not on disk
+ if case .doesNotExist = readError {
+ return refreshReceiptPublisher.eraseToAnyPublisher()
+ } else {
+ return Fail(error: readError).eraseToAnyPublisher()
+ }
+ })
+ .eraseToAnyPublisher()
+ }
+ }
+}