Merge branch 'handle-malicious-refunds'
This commit is contained in:
commit
1ed4d32d5d
|
@ -27,6 +27,9 @@ import UIKit
|
|||
import TunnelKit
|
||||
import PassepartoutCore
|
||||
import Convenience
|
||||
import SwiftyBeaver
|
||||
|
||||
private let log = SwiftyBeaver.self
|
||||
|
||||
@UIApplicationMain
|
||||
class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDelegate {
|
||||
|
@ -79,6 +82,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, UISplitViewControllerDele
|
|||
|
||||
func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
|
||||
ProductManager.shared.reviewPurchases()
|
||||
}
|
||||
|
||||
func applicationWillTerminate(_ application: UIApplication) {
|
||||
|
@ -161,6 +165,9 @@ extension UISplitViewController {
|
|||
|
||||
extension AppDelegate {
|
||||
func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
|
||||
guard ProductManager.shared.isEligible(forFeature: .siriShortcuts) else {
|
||||
return false
|
||||
}
|
||||
guard let interaction = userActivity.interaction else {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ private let log = SwiftyBeaver.self
|
|||
|
||||
class ProductManager: NSObject {
|
||||
static let didReloadReceipt = Notification.Name("ProductManagerDidReloadReceipt")
|
||||
|
||||
static let didReviewPurchases = Notification.Name("ProductManagerDidReviewPurchases")
|
||||
|
||||
private static let lastFullVersionBuild = 2016 // 1.8.1
|
||||
|
||||
|
@ -96,7 +98,7 @@ class ProductManager: NSObject {
|
|||
|
||||
// MARK: In-app eligibility
|
||||
|
||||
private func reloadReceipt() {
|
||||
private func reloadReceipt(andNotify: Bool = true) {
|
||||
guard let url = Bundle.main.appStoreReceiptURL else {
|
||||
log.warning("No App Store receipt found!")
|
||||
return
|
||||
|
@ -122,21 +124,27 @@ class ProductManager: NSObject {
|
|||
if let iapReceipts = receipt.inAppPurchaseReceipts {
|
||||
log.debug("In-app receipts:")
|
||||
iapReceipts.forEach {
|
||||
guard let pid = $0.productIdentifier, let date = $0.originalPurchaseDate else {
|
||||
guard let pid = $0.productIdentifier, let purchaseDate = $0.originalPurchaseDate else {
|
||||
return
|
||||
}
|
||||
log.debug("\t\(pid) [\(date)]")
|
||||
log.debug("\t\(pid) [purchased on: \(purchaseDate)]")
|
||||
}
|
||||
for r in iapReceipts {
|
||||
guard let pid = r.productIdentifier, let product = Product(rawValue: pid) else {
|
||||
continue
|
||||
}
|
||||
if let cancellationDate = r.cancellationDate {
|
||||
log.debug("\t\(pid) [cancelled on: \(cancellationDate)]")
|
||||
continue
|
||||
}
|
||||
purchasedFeatures.insert(product)
|
||||
}
|
||||
}
|
||||
log.info("Purchased features: \(purchasedFeatures)")
|
||||
|
||||
NotificationCenter.default.post(name: ProductManager.didReloadReceipt, object: nil)
|
||||
if andNotify {
|
||||
NotificationCenter.default.post(name: ProductManager.didReloadReceipt, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
func isFullVersion() -> Bool {
|
||||
|
@ -165,6 +173,60 @@ class ProductManager: NSObject {
|
|||
func isEligibleForFeedback() -> Bool {
|
||||
return AppConstants.Flags.isBeta || !purchasedFeatures.isEmpty
|
||||
}
|
||||
|
||||
// MARK: Review
|
||||
|
||||
func reviewPurchases() {
|
||||
let service = TransientStore.shared.service
|
||||
reloadReceipt(andNotify: false)
|
||||
var shouldReinstall = false
|
||||
|
||||
// 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 service.preferences.trustsMobileNetwork || !service.preferences.trustedWifis.isEmpty {
|
||||
service.preferences.trustsMobileNetwork = false
|
||||
service.preferences.trustedWifis.removeAll()
|
||||
log.debug("\tRefunded")
|
||||
shouldReinstall = true
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Checking 'Unlimited hosts'")
|
||||
if !isEligible(forFeature: .unlimitedHosts) {
|
||||
let ids = service.ids(forContext: .host)
|
||||
if ids.count > AppConstants.InApp.limitedNumberOfHosts {
|
||||
for id in ids {
|
||||
service.removeProfile(ProfileKey(.host, id))
|
||||
}
|
||||
log.debug("\tRefunded")
|
||||
shouldReinstall = true
|
||||
}
|
||||
}
|
||||
|
||||
log.debug("Checking providers")
|
||||
for name in service.currentProviderNames() {
|
||||
if !isEligible(forProvider: name) {
|
||||
service.removeProfile(ProfileKey(name))
|
||||
log.debug("\tRefunded provider: \(name)")
|
||||
shouldReinstall = true
|
||||
}
|
||||
}
|
||||
|
||||
// no refunds
|
||||
guard shouldReinstall else {
|
||||
return
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
// save reverts and remove fraud VPN profile
|
||||
TransientStore.shared.serialize(withProfiles: true)
|
||||
VPN.shared.uninstall(completionHandler: nil)
|
||||
|
||||
NotificationCenter.default.post(name: ProductManager.didReviewPurchases, object: nil)
|
||||
}
|
||||
}
|
||||
|
||||
extension ConnectionService {
|
||||
|
|
|
@ -120,6 +120,7 @@ class ServiceViewController: UIViewController, StrongTableHost {
|
|||
}
|
||||
nc.addObserver(self, selector: #selector(serviceDidUpdateDataCount(_:)), name: ConnectionService.didUpdateDataCount, object: nil)
|
||||
nc.addObserver(self, selector: #selector(productManagerDidReloadReceipt), name: ProductManager.didReloadReceipt, object: nil)
|
||||
nc.addObserver(self, selector: #selector(productManagerDidReviewPurchases), name: ProductManager.didReviewPurchases, object: nil)
|
||||
|
||||
// run this no matter what
|
||||
// XXX: convenient here vs AppDelegate for updating table
|
||||
|
@ -141,6 +142,7 @@ class ServiceViewController: UIViewController, StrongTableHost {
|
|||
super.viewDidAppear(animated)
|
||||
|
||||
clearSelection()
|
||||
hideProfileIfDeleted()
|
||||
}
|
||||
|
||||
override func didReceiveMemoryWarning() {
|
||||
|
@ -674,6 +676,10 @@ class ServiceViewController: UIViewController, StrongTableHost {
|
|||
reloadModel()
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
@objc private func productManagerDidReviewPurchases() {
|
||||
hideProfileIfDeleted()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 7476e0751d1a64efca0180a1e31f0e24f04fce84
|
||||
Subproject commit dad47f3581ab25d966d204de93634d82b9fe808b
|
Loading…
Reference in New Issue