Look up TestFlight flag asynchronously (#352)
Xcode has been quite obnoxious recently with this issue. Start the app with the most restrictive type (.undefined), relax restrictions after looking up sandbox and app receipt.
This commit is contained in:
parent
33d9e05907
commit
a38e3fed7a
|
@ -50,10 +50,10 @@
|
|||
0E34AC7827F840890042F2AB /* OrganizerView+Scene.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */; };
|
||||
0E34AC8227F892C40042F2AB /* OnDemandView+SSID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */; };
|
||||
0E35C09A280E95BB0071FA35 /* ProviderProfileAvailability.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */; };
|
||||
0E3A3C102AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */; };
|
||||
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */; };
|
||||
0E3A3C142AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */; };
|
||||
0E3A3C162AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */; };
|
||||
0E3A3C102AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */; };
|
||||
0E3A593C2A50975700B3FE40 /* ErrorHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */; };
|
||||
0E3B7FCD27E47B3700C66F13 /* AddHostView+Name.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */; };
|
||||
0E3B7FD627E5173A00C66F13 /* ProfileView+VPN.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */; };
|
||||
|
@ -176,7 +176,7 @@
|
|||
0ED2B36027D3C99100FD8EA9 /* PassepartoutWireGuardTunnel.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
0ED2B36727D3C9A300FD8EA9 /* WireGuardAppExtension in Frameworks */ = {isa = PBXBuildFile; productRef = 0ED2B36627D3C9A300FD8EA9 /* WireGuardAppExtension */; };
|
||||
0ED30DCC27EA197D0057D8A3 /* RevealingSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */; };
|
||||
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DCE27EA1EF80057D8A3 /* PaywallView+Beta.swift */; };
|
||||
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Restricted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DCE27EA1EF80057D8A3 /* PaywallView+Restricted.swift */; };
|
||||
0ED30DD227EA1F650057D8A3 /* PaywallView+Purchase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */; };
|
||||
0ED30DDB27EA351C0057D8A3 /* Constants+Tunnel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0ED30DDA27EA351C0057D8A3 /* Constants+Tunnel.swift */; };
|
||||
0ED30DDD27EA35230057D8A3 /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0EB17EA127D2263700D473B5 /* Constants.swift */; };
|
||||
|
@ -344,10 +344,10 @@
|
|||
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OrganizerView+Scene.swift"; sourceTree = "<group>"; };
|
||||
0E34AC8127F892C40042F2AB /* OnDemandView+SSID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OnDemandView+SSID.swift"; sourceTree = "<group>"; };
|
||||
0E35C099280E95BB0071FA35 /* ProviderProfileAvailability.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderProfileAvailability.swift; sourceTree = "<group>"; };
|
||||
0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueStore+CloudKit.swift"; sourceTree = "<group>"; };
|
||||
0E3A3C112AAB7C470003A5F6 /* UpgradeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManager.swift; sourceTree = "<group>"; };
|
||||
0E3A3C122AAB7C480003A5F6 /* DefaultUpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultUpgradeManagerStrategy.swift; sourceTree = "<group>"; };
|
||||
0E3A3C152AAB8AB80003A5F6 /* UpgradeManagerStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UpgradeManagerStrategy.swift; sourceTree = "<group>"; };
|
||||
0E3A3C0F2AA9AA530003A5F6 /* KeyValueStore+CloudKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeyValueStore+CloudKit.swift"; sourceTree = "<group>"; };
|
||||
0E3A593B2A50975700B3FE40 /* ErrorHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandler.swift; sourceTree = "<group>"; };
|
||||
0E3B7FCC27E47B3700C66F13 /* AddHostView+Name.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "AddHostView+Name.swift"; sourceTree = "<group>"; };
|
||||
0E3B7FD527E5173A00C66F13 /* ProfileView+VPN.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProfileView+VPN.swift"; sourceTree = "<group>"; };
|
||||
|
@ -497,7 +497,7 @@
|
|||
0ED2B34A27D3C77800FD8EA9 /* PassepartoutWireGuardTunnel.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = PassepartoutWireGuardTunnel.appex; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
0ED2B35A27D3C94F00FD8EA9 /* PacketTunnelProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PacketTunnelProvider.swift; sourceTree = "<group>"; };
|
||||
0ED30DCB27EA197C0057D8A3 /* RevealingSecureField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RevealingSecureField.swift; sourceTree = "<group>"; };
|
||||
0ED30DCE27EA1EF80057D8A3 /* PaywallView+Beta.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaywallView+Beta.swift"; sourceTree = "<group>"; };
|
||||
0ED30DCE27EA1EF80057D8A3 /* PaywallView+Restricted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaywallView+Restricted.swift"; sourceTree = "<group>"; };
|
||||
0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PaywallView+Purchase.swift"; sourceTree = "<group>"; };
|
||||
0ED30DDA27EA351C0057D8A3 /* Constants+Tunnel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Constants+Tunnel.swift"; sourceTree = "<group>"; };
|
||||
0ED31C3920CF39510027975F /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; };
|
||||
|
@ -676,8 +676,8 @@
|
|||
0EF8C5A728213C510053CE89 /* OrganizerView+Profiles.swift */,
|
||||
0E34AC7727F840890042F2AB /* OrganizerView+Scene.swift */,
|
||||
0EF0FAF527DD0211007EB181 /* PaywallView.swift */,
|
||||
0ED30DCE27EA1EF80057D8A3 /* PaywallView+Beta.swift */,
|
||||
0ED30DD127EA1F650057D8A3 /* PaywallView+Purchase.swift */,
|
||||
0ED30DCE27EA1EF80057D8A3 /* PaywallView+Restricted.swift */,
|
||||
0E44689527B051C300A14CE4 /* ProfileView.swift */,
|
||||
0E92D7C527F103300033CB7B /* ProfileView+Configuration.swift */,
|
||||
0E92D7C827F1042A0033CB7B /* ProfileView+Extra.swift */,
|
||||
|
@ -1481,7 +1481,7 @@
|
|||
0E3A3C132AAB7C480003A5F6 /* UpgradeManager.swift in Sources */,
|
||||
0E96D3052872010A005EFBCF /* DefaultLightVPNManager.swift in Sources */,
|
||||
0EBE880F281B18DE0090D9E6 /* OrganizerView+ProfileRow.swift in Sources */,
|
||||
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Beta.swift in Sources */,
|
||||
0ED30DCF27EA1EF80057D8A3 /* PaywallView+Restricted.swift in Sources */,
|
||||
0ECF71EE27B6A99300CDB528 /* AccountView.swift in Sources */,
|
||||
0E71ACF727C107CA00F85C4B /* DebugLogView.swift in Sources */,
|
||||
0EF0FAF927DD212C007EB181 /* IntentActivity.swift in Sources */,
|
||||
|
|
|
@ -39,10 +39,6 @@ extension Constants {
|
|||
static let appStoreId: String = bundleConfig("appstore_id")
|
||||
|
||||
static let appGroupId: String = bundleConfig("group_id")
|
||||
|
||||
static let isBeta: Bool = {
|
||||
Bundle.main.isTestFlight
|
||||
}()
|
||||
}
|
||||
|
||||
enum CloudKit {
|
||||
|
@ -56,7 +52,7 @@ extension Constants {
|
|||
}
|
||||
|
||||
enum InApp {
|
||||
static var appType: ProductManager.AppType {
|
||||
static var overriddenAppType: ProductManager.AppType? {
|
||||
if let envString = ProcessInfo.processInfo.environment["APP_TYPE"],
|
||||
let envValue = Int(envString),
|
||||
let testAppType = ProductManager.AppType(rawValue: envValue) {
|
||||
|
@ -68,7 +64,7 @@ extension Constants {
|
|||
|
||||
return testAppType
|
||||
}
|
||||
return App.isBeta ? .beta : .freemium
|
||||
return nil
|
||||
}
|
||||
|
||||
#if targetEnvironment(macCatalyst)
|
||||
|
|
|
@ -43,7 +43,7 @@ final class AppContext {
|
|||
self.coreContext = coreContext
|
||||
|
||||
productManager = ProductManager(
|
||||
appType: Constants.InApp.appType,
|
||||
overriddenAppType: Constants.InApp.overriddenAppType,
|
||||
buildProducts: Constants.InApp.buildProducts
|
||||
)
|
||||
|
||||
|
|
|
@ -29,21 +29,40 @@ import Kvitto
|
|||
import PassepartoutLibrary
|
||||
import StoreKit
|
||||
|
||||
@MainActor
|
||||
final class ProductManager: NSObject, ObservableObject {
|
||||
enum AppType: Int {
|
||||
case undefined = -1
|
||||
|
||||
case freemium = 0
|
||||
|
||||
case beta = 1
|
||||
|
||||
case fullVersion = 2
|
||||
|
||||
var isRestricted: Bool {
|
||||
switch self {
|
||||
case .undefined, .beta:
|
||||
return true
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let appType: AppType
|
||||
private let overriddenAppType: AppType?
|
||||
|
||||
private let sandboxChecker: SandboxChecker
|
||||
|
||||
private var subscriptions: Set<AnyCancellable>
|
||||
|
||||
let buildProducts: BuildProducts
|
||||
|
||||
let didRefundProducts = PassthroughSubject<Void, Never>()
|
||||
|
||||
@Published private(set) var appType: AppType
|
||||
|
||||
@Published private(set) var isRefreshingProducts = false
|
||||
|
||||
@Published private(set) var products: [SKProduct]
|
||||
|
@ -73,10 +92,14 @@ final class ProductManager: NSObject, ObservableObject {
|
|||
|
||||
private var refreshRequest: SKReceiptRefreshRequest?
|
||||
|
||||
init(appType: AppType, buildProducts: BuildProducts) {
|
||||
self.appType = appType
|
||||
init(overriddenAppType: AppType?, buildProducts: BuildProducts) {
|
||||
self.overriddenAppType = overriddenAppType
|
||||
self.buildProducts = buildProducts
|
||||
|
||||
appType = .undefined
|
||||
sandboxChecker = SandboxChecker(bundle: .main)
|
||||
subscriptions = []
|
||||
|
||||
products = []
|
||||
inApp = InApp()
|
||||
purchasedAppBuild = nil
|
||||
|
@ -88,8 +111,20 @@ final class ProductManager: NSObject, ObservableObject {
|
|||
|
||||
reloadReceipt()
|
||||
SKPaymentQueue.default().add(self)
|
||||
|
||||
refreshProducts()
|
||||
|
||||
sandboxChecker.$isSandbox
|
||||
.dropFirst() // ignore initial value
|
||||
.sink { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.appType = overriddenAppType ?? ($0 ? .beta : .freemium)
|
||||
pp_log.info("App type: \(self.appType)")
|
||||
self.reloadReceipt()
|
||||
}.store(in: &subscriptions)
|
||||
|
||||
sandboxChecker.check()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
@ -294,7 +329,7 @@ private extension ProductManager {
|
|||
let receipt = Receipt(contentsOfURL: url)
|
||||
|
||||
// in TestFlight, attempt fallback to existing release receipt
|
||||
if Bundle.main.isTestFlight {
|
||||
if appType == .beta {
|
||||
guard let receipt else {
|
||||
let releaseUrl = url.deletingLastPathComponent().appendingPathComponent("receipt")
|
||||
guard releaseUrl != url else {
|
||||
|
|
|
@ -47,6 +47,8 @@ struct PassepartoutApp: App {
|
|||
}
|
||||
|
||||
private extension View {
|
||||
|
||||
@MainActor
|
||||
func onIntentActivity(_ activity: IntentActivity<VPNManager>) -> some View {
|
||||
onContinueUserActivity(activity.name) { userActivity in
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// PaywallView+Beta.swift
|
||||
// PaywallView+Restricted.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 3/22/22.
|
||||
|
@ -26,11 +26,12 @@
|
|||
import SwiftUI
|
||||
|
||||
extension PaywallView {
|
||||
struct BetaView: View {
|
||||
struct RestrictedView: View {
|
||||
var body: some View {
|
||||
Text("The requested feature in unavailable in beta.")
|
||||
.navigationTitle("Beta")
|
||||
Text("The requested feature in unavailable in this build.")
|
||||
.multilineTextAlignment(.center)
|
||||
.padding()
|
||||
.navigationTitle("Restricted")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,8 +51,8 @@ struct PaywallView: View {
|
|||
|
||||
var body: some View {
|
||||
Group {
|
||||
if productManager.appType == .beta {
|
||||
BetaView()
|
||||
if productManager.appType.isRestricted {
|
||||
RestrictedView()
|
||||
} else {
|
||||
PurchaseView(
|
||||
isPresented: $isPresented,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// Utils+TestFlight.swift
|
||||
// SandboxChecker.swift
|
||||
// Passepartout
|
||||
//
|
||||
// Created by Davide De Rosa on 5/18/22.
|
||||
|
@ -28,19 +28,35 @@ import Foundation
|
|||
// https://stackoverflow.com/a/32238344/784615
|
||||
// https://gist.github.com/lukaskubanek/cbfcab29c0c93e0e9e0a16ab09586996
|
||||
|
||||
extension Bundle {
|
||||
public var isTestFlight: Bool {
|
||||
#if targetEnvironment(simulator)
|
||||
true
|
||||
@MainActor
|
||||
public final class SandboxChecker: ObservableObject {
|
||||
private let bundle: Bundle
|
||||
|
||||
@Published public private(set) var isSandbox = false
|
||||
|
||||
public init(bundle: Bundle) {
|
||||
self.bundle = bundle
|
||||
}
|
||||
|
||||
public func check() {
|
||||
Task {
|
||||
isSandbox = await isSandboxBuild()
|
||||
pp_log.info("Sandbox build: \(isSandbox)")
|
||||
}
|
||||
}
|
||||
|
||||
private func isSandboxBuild() async -> Bool {
|
||||
#if os(iOS)
|
||||
bundle.appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
|
||||
#elseif targetEnvironment(macCatalyst) || os(macOS)
|
||||
var status = noErr
|
||||
|
||||
var code: SecStaticCode?
|
||||
status = SecStaticCodeCreateWithPath(bundleURL as CFURL, [], &code)
|
||||
status = SecStaticCodeCreateWithPath(bundle.bundleURL as CFURL, [], &code)
|
||||
guard status == noErr else {
|
||||
return false
|
||||
}
|
||||
guard let code = code else {
|
||||
guard let code else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -53,7 +69,7 @@ extension Bundle {
|
|||
guard status == noErr else {
|
||||
return false
|
||||
}
|
||||
guard let requirement = requirement else {
|
||||
guard let requirement else {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -63,8 +79,6 @@ extension Bundle {
|
|||
requirement
|
||||
)
|
||||
return status == errSecSuccess
|
||||
#elseif os(iOS)
|
||||
appStoreReceiptURL?.lastPathComponent == "sandboxReceipt"
|
||||
#else
|
||||
false
|
||||
#endif
|
Loading…
Reference in New Issue