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:
Davide De Rosa 2023-09-10 00:52:39 +02:00 committed by GitHub
parent 33d9e05907
commit a38e3fed7a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 82 additions and 34 deletions

View File

@ -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 */,

View File

@ -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)

View File

@ -43,7 +43,7 @@ final class AppContext {
self.coreContext = coreContext
productManager = ProductManager(
appType: Constants.InApp.appType,
overriddenAppType: Constants.InApp.overriddenAppType,
buildProducts: Constants.InApp.buildProducts
)

View File

@ -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 {

View File

@ -47,6 +47,8 @@ struct PassepartoutApp: App {
}
private extension View {
@MainActor
func onIntentActivity(_ activity: IntentActivity<VPNManager>) -> some View {
onContinueUserActivity(activity.name) { userActivity in

View File

@ -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")
}
}
}

View File

@ -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,

View File

@ -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