From a7fc4dcc39d43ccfe7d20840a81c967badc262b5 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Thu, 7 Jan 2021 23:26:34 +0100 Subject: [PATCH 1/4] Drop hosts restriction Makes "Unlimited hosts" in-app useless. --- Passepartout/App/iOS/AppDelegate.swift | 6 ------ Passepartout/App/iOS/CHANGELOG.md | 6 ++++++ .../App/iOS/Global/AppConstants+App.swift | 2 -- .../App/iOS/Global/ProductManager+App.swift | 19 ------------------- .../Organizer/OrganizerViewController.swift | 12 ------------ Passepartout/Core/Sources/Model/Product.swift | 3 --- 6 files changed, 6 insertions(+), 42 deletions(-) diff --git a/Passepartout/App/iOS/AppDelegate.swift b/Passepartout/App/iOS/AppDelegate.swift index f47c3380..e9fb4920 100644 --- a/Passepartout/App/iOS/AppDelegate.swift +++ b/Passepartout/App/iOS/AppDelegate.swift @@ -105,12 +105,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele fatalError("No window.rootViewController?") } let topmost = root.presentedViewController ?? root - if TransientStore.shared.service.hasReachedMaximumNumberOfHosts { - guard ProductManager.shared.isEligible(forFeature: .unlimitedHosts) else { - topmost.presentPurchaseScreen(forProduct: .unlimitedHosts) - return false - } - } return tryParseURL(url, passphrase: nil, target: topmost) } diff --git a/Passepartout/App/iOS/CHANGELOG.md b/Passepartout/App/iOS/CHANGELOG.md index 3144ab16..158efe50 100644 --- a/Passepartout/App/iOS/CHANGELOG.md +++ b/Passepartout/App/iOS/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Drop hosts restriction in free version ("Unlimited hosts"). + ## 1.14.0 (2021-01-07) ### Added diff --git a/Passepartout/App/iOS/Global/AppConstants+App.swift b/Passepartout/App/iOS/Global/AppConstants+App.swift index 0c02f749..449e91bc 100644 --- a/Passepartout/App/iOS/Global/AppConstants+App.swift +++ b/Passepartout/App/iOS/Global/AppConstants+App.swift @@ -38,7 +38,5 @@ extension AppConstants { static let isBetaFullVersion = true static let lastFullVersionBuild = 2016 - - static let limitedNumberOfHosts = 2 } } diff --git a/Passepartout/App/iOS/Global/ProductManager+App.swift b/Passepartout/App/iOS/Global/ProductManager+App.swift index 0571cffb..f751095b 100644 --- a/Passepartout/App/iOS/Global/ProductManager+App.swift +++ b/Passepartout/App/iOS/Global/ProductManager+App.swift @@ -71,18 +71,6 @@ extension ProductManager { } } - log.debug("Checking 'Unlimited hosts'") - if !isEligible(forFeature: .unlimitedHosts) { - let ids = service.hostIds() - if ids.count > AppConstants.InApp.limitedNumberOfHosts { - for id in ids { - service.removeProfile(ProfileKey(.host, id)) - } - log.debug("\tRefunded") - anyRefund = true - } - } - log.debug("Checking providers") for name in service.providerNames() { guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else { @@ -108,10 +96,3 @@ extension ProductManager { NotificationCenter.default.post(name: ProductManager.didReviewPurchases, object: nil) } } - -extension ConnectionService { - var hasReachedMaximumNumberOfHosts: Bool { - let numberOfHosts = hostIds().count - return numberOfHosts >= AppConstants.InApp.limitedNumberOfHosts - } -} diff --git a/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift index 8a452f22..56a0e735 100644 --- a/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift +++ b/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift @@ -222,12 +222,6 @@ class OrganizerViewController: UITableViewController, StrongTableHost { } private func addNewHost() { - if TransientStore.shared.service.hasReachedMaximumNumberOfHosts { - guard ProductManager.shared.isEligible(forFeature: .unlimitedHosts) else { - presentPurchaseScreen(forProduct: .unlimitedHosts) - return - } - } let picker = UIDocumentPickerViewController(documentTypes: AppConstants.URLs.filetypes, in: .import) picker.allowsMultipleSelection = false picker.delegate = self @@ -243,12 +237,6 @@ class OrganizerViewController: UITableViewController, StrongTableHost { } private func importNewHost() { - if TransientStore.shared.service.hasReachedMaximumNumberOfHosts { - guard ProductManager.shared.isEligible(forFeature: .unlimitedHosts) else { - presentPurchaseScreen(forProduct: .unlimitedHosts) - return - } - } perform(segue: StoryboardSegue.Organizer.showImportedHostsSegueIdentifier) } diff --git a/Passepartout/Core/Sources/Model/Product.swift b/Passepartout/Core/Sources/Model/Product.swift index 0eff5313..ea8a7795 100644 --- a/Passepartout/Core/Sources/Model/Product.swift +++ b/Passepartout/Core/Sources/Model/Product.swift @@ -71,8 +71,6 @@ public struct Product: RawRepresentable, Equatable, Hashable { // MARK: Features #if os(iOS) - public static let unlimitedHosts = Product(featureId: "unlimited_hosts") - public static let trustedNetworks = Product(featureId: "trusted_networks") public static let siriShortcuts = Product(featureId: "siri") @@ -82,7 +80,6 @@ public struct Product: RawRepresentable, Equatable, Hashable { #if os(iOS) public static let allFeatures: [Product] = [ - .unlimitedHosts, .trustedNetworks, .siriShortcuts, .fullVersion From 54c9ca671a041832c72817f8d8d7b2acd8404ba4 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Thu, 7 Jan 2021 23:28:18 +0100 Subject: [PATCH 2/4] Revert "Assume full version in beta" This reverts commit 04fc806e5a4cf185869a54fa69d3e5800bc2472c. --- Passepartout/App/iOS/Global/AppConstants+App.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Passepartout/App/iOS/Global/AppConstants+App.swift b/Passepartout/App/iOS/Global/AppConstants+App.swift index 449e91bc..8094f689 100644 --- a/Passepartout/App/iOS/Global/AppConstants+App.swift +++ b/Passepartout/App/iOS/Global/AppConstants+App.swift @@ -32,10 +32,9 @@ extension AppConstants { } struct InApp { -// static var isBetaFullVersion: Bool { -// return ProcessInfo.processInfo.environment["FULL_VERSION"] != nil -// } - static let isBetaFullVersion = true + static var isBetaFullVersion: Bool { + return ProcessInfo.processInfo.environment["FULL_VERSION"] != nil + } static let lastFullVersionBuild = 2016 } From cc8c01a13adeb7e46400154dc135d234050d1932 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Thu, 7 Jan 2021 23:29:16 +0100 Subject: [PATCH 3/4] Disable providers and features in beta --- Passepartout/Core/Sources/Model/ProductManager.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Passepartout/Core/Sources/Model/ProductManager.swift b/Passepartout/Core/Sources/Model/ProductManager.swift index de0f9187..8c622d7a 100644 --- a/Passepartout/Core/Sources/Model/ProductManager.swift +++ b/Passepartout/Core/Sources/Model/ProductManager.swift @@ -156,10 +156,16 @@ public class ProductManager: NSObject { } public func isEligible(forFeature feature: Product) -> Bool { + guard !isBeta else { + return false + } return isFullVersion() || purchasedFeatures.contains(feature) } public func isEligible(forProvider metadata: Infrastructure.Metadata) -> Bool { + guard !isBeta else { + return false + } return isFullVersion() || purchasedFeatures.contains(metadata.product) } From d1cb70a5d9dc38a71ef989230e64da7d5f136bbb Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Thu, 7 Jan 2021 23:37:32 +0100 Subject: [PATCH 4/4] Lock features with alert if beta --- Passepartout/App/iOS/AppDelegate.swift | 2 +- Passepartout/App/iOS/Global/Macros.swift | 6 ++++ .../App/iOS/Global/ProductManager+App.swift | 4 +-- .../Organizer/OrganizerViewController.swift | 9 +++-- .../WizardProviderViewController.swift | 11 ++++-- .../iOS/Scenes/ServiceViewController.swift | 35 +++++++++++++++---- .../Core/Sources/Model/ProductManager.swift | 12 ++++--- 7 files changed, 60 insertions(+), 19 deletions(-) diff --git a/Passepartout/App/iOS/AppDelegate.swift b/Passepartout/App/iOS/AppDelegate.swift index e9fb4920..e36aac87 100644 --- a/Passepartout/App/iOS/AppDelegate.swift +++ b/Passepartout/App/iOS/AppDelegate.swift @@ -162,7 +162,7 @@ extension UISplitViewController { extension AppDelegate { func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool { - guard ProductManager.shared.isEligible(forFeature: .siriShortcuts) else { + guard (try? ProductManager.shared.isEligible(forFeature: .siriShortcuts)) ?? false else { return false } guard let interaction = userActivity.interaction else { diff --git a/Passepartout/App/iOS/Global/Macros.swift b/Passepartout/App/iOS/Global/Macros.swift index a84bddf4..9b5fcc4a 100644 --- a/Passepartout/App/iOS/Global/Macros.swift +++ b/Passepartout/App/iOS/Global/Macros.swift @@ -75,6 +75,12 @@ extension UIViewController { present(nav, animated: true, completion: nil) } + + func presentBetaFeatureUnavailable(_ title: String) { + let alert = UIAlertController.asAlert(title, "The requested feature is unavailable in beta.") + alert.addCancelAction("OK") + present(alert, animated: true, completion: nil) + } } func visitURL(_ url: URL) { diff --git a/Passepartout/App/iOS/Global/ProductManager+App.swift b/Passepartout/App/iOS/Global/ProductManager+App.swift index f751095b..364f4ecd 100644 --- a/Passepartout/App/iOS/Global/ProductManager+App.swift +++ b/Passepartout/App/iOS/Global/ProductManager+App.swift @@ -46,7 +46,7 @@ extension ProductManager { // review features and potentially revert them if they were used (Siri is handled in AppDelegate) log.debug("Checking 'Trusted networks'") - if !isEligible(forFeature: .trustedNetworks) { + if !((try? isEligible(forFeature: .trustedNetworks)) ?? false) { // reset trusted networks for ALL profiles (must load first) for key in service.allProfileKeys() { @@ -76,7 +76,7 @@ extension ProductManager { guard let metadata = InfrastructureFactory.shared.metadata(forName: name) else { continue } - if !isEligible(forProvider: metadata) { + if !((try? isEligible(forProvider: metadata)) ?? false) { service.removeProfile(ProfileKey(name)) log.debug("\tRefunded provider: \(name)") anyRefund = true diff --git a/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift index 56a0e735..cb81072e 100644 --- a/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift +++ b/Passepartout/App/iOS/Scenes/Organizer/OrganizerViewController.swift @@ -241,8 +241,13 @@ class OrganizerViewController: UITableViewController, StrongTableHost { } private func addShortcuts() { - guard ProductManager.shared.isEligible(forFeature: .siriShortcuts) else { - presentPurchaseScreen(forProduct: .siriShortcuts) + do { + guard try ProductManager.shared.isEligible(forFeature: .siriShortcuts) else { + presentPurchaseScreen(forProduct: .siriShortcuts) + return + } + } catch { + presentBetaFeatureUnavailable("Siri") return } perform(segue: StoryboardSegue.Organizer.siriShortcutsSegueIdentifier) diff --git a/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift b/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift index 907d7206..d48d9b8f 100644 --- a/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift +++ b/Passepartout/App/iOS/Scenes/Organizer/WizardProviderViewController.swift @@ -70,11 +70,16 @@ class WizardProviderViewController: UITableViewController, StrongTableHost { private func tryNext(withMetadata metadata: Infrastructure.Metadata, purchaseIfNecessary: Bool) { selectedMetadata = metadata - guard ProductManager.shared.isEligible(forProvider: metadata) else { - guard purchaseIfNecessary else { + do { + guard try ProductManager.shared.isEligible(forProvider: metadata) else { + guard purchaseIfNecessary else { + return + } + presentPurchaseScreen(forProduct: metadata.product, delegate: self) return } - presentPurchaseScreen(forProduct: metadata.product, delegate: self) + } catch { + presentBetaFeatureUnavailable("Providers") return } diff --git a/Passepartout/App/iOS/Scenes/ServiceViewController.swift b/Passepartout/App/iOS/Scenes/ServiceViewController.swift index dafa9b2a..1b76562d 100644 --- a/Passepartout/App/iOS/Scenes/ServiceViewController.swift +++ b/Passepartout/App/iOS/Scenes/ServiceViewController.swift @@ -377,11 +377,19 @@ class ServiceViewController: UIViewController, StrongTableHost { } private func trustMobileNetwork(cell: ToggleTableViewCell) { - guard ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { + do { + guard try ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { + delay { + cell.setOn(false, animated: true) + } + presentPurchaseScreen(forProduct: .trustedNetworks) + return + } + } catch { delay { cell.setOn(false, animated: true) } - presentPurchaseScreen(forProduct: .trustedNetworks) + presentBetaFeatureUnavailable("Trusted networks") return } @@ -394,8 +402,13 @@ class ServiceViewController: UIViewController, StrongTableHost { } private func trustCurrentWiFi() { - guard ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { - presentPurchaseScreen(forProduct: .trustedNetworks) + do { + guard try ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { + presentPurchaseScreen(forProduct: .trustedNetworks) + return + } + } catch { + presentBetaFeatureUnavailable("Trusted networks") return } @@ -447,14 +460,22 @@ class ServiceViewController: UIViewController, StrongTableHost { } private func toggleTrustWiFi(cell: ToggleTableViewCell, at row: Int) { - guard ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { + do { + guard try ProductManager.shared.isEligible(forFeature: .trustedNetworks) else { + delay { + cell.setOn(false, animated: true) + } + presentPurchaseScreen(forProduct: .trustedNetworks) + return + } + } catch { delay { cell.setOn(false, animated: true) } - presentPurchaseScreen(forProduct: .trustedNetworks) + presentBetaFeatureUnavailable("Trusted networks") return } - + if cell.isOn { trustedNetworks.enableWifi(at: row) } else { diff --git a/Passepartout/Core/Sources/Model/ProductManager.swift b/Passepartout/Core/Sources/Model/ProductManager.swift index 8c622d7a..47cefd6a 100644 --- a/Passepartout/Core/Sources/Model/ProductManager.swift +++ b/Passepartout/Core/Sources/Model/ProductManager.swift @@ -32,6 +32,10 @@ import TunnelKit private let log = SwiftyBeaver.self +public enum ProductError: Error { + case beta +} + public class ProductManager: NSObject { public struct Configuration { public let isBetaFullVersion: Bool @@ -155,16 +159,16 @@ public class ProductManager: NSObject { return purchasedFeatures.contains(.fullVersion) } - public func isEligible(forFeature feature: Product) -> Bool { + public func isEligible(forFeature feature: Product) throws -> Bool { guard !isBeta else { - return false + throw ProductError.beta } return isFullVersion() || purchasedFeatures.contains(feature) } - public func isEligible(forProvider metadata: Infrastructure.Metadata) -> Bool { + public func isEligible(forProvider metadata: Infrastructure.Metadata) throws -> Bool { guard !isBeta else { - return false + throw ProductError.beta } return isFullVersion() || purchasedFeatures.contains(metadata.product) }