diff options
| author | Andrej Mihajlov <and@mullvad.net> | 2020-03-26 12:45:41 +0100 |
|---|---|---|
| committer | Andrej Mihajlov <and@mullvad.net> | 2020-03-26 16:23:13 +0100 |
| commit | ae05a614cee69892952655ca7003fdc5081d764f (patch) | |
| tree | 582dad6a87823c5df8b0a4588a1bdc8453c03686 | |
| parent | 11fde0398a9e2a758c082dad02ea08de6844b70a (diff) | |
| download | mullvadvpn-ae05a614cee69892952655ca7003fdc5081d764f.tar.xz mullvadvpn-ae05a614cee69892952655ca7003fdc5081d764f.zip | |
Add AppStoreReceipt reader
| -rw-r--r-- | ios/MullvadVPN.xcodeproj/project.pbxproj | 4 | ||||
| -rw-r--r-- | ios/MullvadVPN/AppStoreReceipt.swift | 79 |
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() + } + } +} |
