mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-09 06:43:25 +01:00
Improves error message when registering app + app extension after App ID limit is reached
This commit is contained in:
29
AltStore/Extensions/ALTApplication+AppExtensions.swift
Normal file
29
AltStore/Extensions/ALTApplication+AppExtensions.swift
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// ALTApplication+AppExtensions.swift
|
||||||
|
// AltStore
|
||||||
|
//
|
||||||
|
// Created by Riley Testut on 2/10/20.
|
||||||
|
// Copyright © 2020 Riley Testut. All rights reserved.
|
||||||
|
//
|
||||||
|
|
||||||
|
import AltSign
|
||||||
|
|
||||||
|
extension ALTApplication
|
||||||
|
{
|
||||||
|
var appExtensions: Set<ALTApplication> {
|
||||||
|
guard let bundle = Bundle(url: self.fileURL) else { return [] }
|
||||||
|
|
||||||
|
var appExtensions: Set<ALTApplication> = []
|
||||||
|
|
||||||
|
if let directory = bundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
||||||
|
{
|
||||||
|
for case let fileURL as URL in enumerator where fileURL.pathExtension.lowercased() == "appex"
|
||||||
|
{
|
||||||
|
guard let appExtension = ALTApplication(fileURL: fileURL) else { continue }
|
||||||
|
appExtensions.insert(appExtension)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return appExtensions
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,6 +25,11 @@ extension ALTTeamType
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Team
|
||||||
|
{
|
||||||
|
static let maximumFreeAppIDs = 10
|
||||||
|
}
|
||||||
|
|
||||||
@objc(Team)
|
@objc(Team)
|
||||||
class Team: NSManagedObject, Fetchable
|
class Team: NSManagedObject, Fetchable
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ enum OperationError: LocalizedError
|
|||||||
case invalidParameters
|
case invalidParameters
|
||||||
|
|
||||||
case iOSVersionNotSupported(ALTApplication)
|
case iOSVersionNotSupported(ALTApplication)
|
||||||
case maximumAppIDLimitReached(Date)
|
case maximumAppIDLimitReached(application: ALTApplication, requiredAppIDs: Int, availableAppIDs: Int, nextExpirationDate: Date)
|
||||||
|
|
||||||
case noSources
|
case noSources
|
||||||
|
|
||||||
@@ -58,26 +58,38 @@ enum OperationError: LocalizedError
|
|||||||
var recoverySuggestion: String? {
|
var recoverySuggestion: String? {
|
||||||
switch self
|
switch self
|
||||||
{
|
{
|
||||||
case .maximumAppIDLimitReached(let date):
|
case .maximumAppIDLimitReached(let application, let requiredAppIDs, let availableAppIDs, let date):
|
||||||
let remainingTime: String
|
let baseMessage = NSLocalizedString("Delete sideloaded apps to free up App ID slots.", comment: "")
|
||||||
|
let message: String
|
||||||
|
|
||||||
let numberOfDays = date.numberOfCalendarDays(since: Date())
|
if requiredAppIDs > 1
|
||||||
switch numberOfDays {
|
{
|
||||||
case 0:
|
let availableText: String
|
||||||
let components = Calendar.current.dateComponents([.hour], from: Date(), to: date)
|
|
||||||
let numberOfHours = components.hour!
|
|
||||||
|
|
||||||
switch numberOfHours
|
switch availableAppIDs
|
||||||
{
|
{
|
||||||
case 1: remainingTime = NSLocalizedString("1 hour", comment: "")
|
case 0: availableText = NSLocalizedString("none are available", comment: "")
|
||||||
default: remainingTime = String(format: NSLocalizedString("%@ hours", comment: ""), NSNumber(value: numberOfHours))
|
case 1: availableText = NSLocalizedString("only 1 is available", comment: "")
|
||||||
|
default: availableText = String(format: NSLocalizedString("only %@ are available", comment: ""), NSNumber(value: availableAppIDs))
|
||||||
}
|
}
|
||||||
|
|
||||||
case 1: remainingTime = NSLocalizedString("1 day", comment: "")
|
let prefixMessage = String(format: NSLocalizedString("%@ requires %@ App IDs, but %@.", comment: ""), application.name, NSNumber(value: requiredAppIDs), availableText)
|
||||||
default: remainingTime = String(format: NSLocalizedString("%@ days", comment: ""), NSNumber(value: numberOfDays))
|
message = prefixMessage + " " + baseMessage
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
let dateComponents = Calendar.current.dateComponents([.day, .hour, .minute], from: Date(), to: date)
|
||||||
|
|
||||||
|
let dateComponentsFormatter = DateComponentsFormatter()
|
||||||
|
dateComponentsFormatter.maximumUnitCount = 1
|
||||||
|
dateComponentsFormatter.unitsStyle = .full
|
||||||
|
|
||||||
|
let remainingTime = dateComponentsFormatter.string(from: dateComponents)!
|
||||||
|
|
||||||
|
let remainingTimeMessage = String(format: NSLocalizedString("You can register another App ID in %@.", comment: ""), remainingTime)
|
||||||
|
message = baseMessage + " " + remainingTimeMessage
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = String(format: NSLocalizedString("Delete sideloaded apps to free up App ID slots. You can register another App ID in %@.", comment: ""), remainingTime)
|
|
||||||
return message
|
return message
|
||||||
|
|
||||||
default: return nil
|
default: return nil
|
||||||
|
|||||||
@@ -102,53 +102,43 @@ private extension ResignAppOperation
|
|||||||
{
|
{
|
||||||
func prepareProvisioningProfiles(_ fileURL: URL, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
|
func prepareProvisioningProfiles(_ fileURL: URL, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<[String: ALTProvisioningProfile], Error>) -> Void)
|
||||||
{
|
{
|
||||||
guard let bundle = Bundle(url: fileURL), let app = ALTApplication(fileURL: fileURL) else { return completionHandler(.failure(OperationError.invalidApp)) }
|
guard let app = ALTApplication(fileURL: fileURL) else { return completionHandler(.failure(OperationError.invalidApp)) }
|
||||||
|
|
||||||
let dispatchGroup = DispatchGroup()
|
|
||||||
|
|
||||||
var profiles = [String: ALTProvisioningProfile]()
|
|
||||||
var error: Error?
|
|
||||||
|
|
||||||
dispatchGroup.enter()
|
|
||||||
|
|
||||||
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
self.prepareProvisioningProfile(for: app, parentApp: nil, team: team, session: session) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let e): error = e
|
case .failure(let error): completionHandler(.failure(error))
|
||||||
case .success(let profile):
|
case .success(let profile):
|
||||||
profiles[app.bundleIdentifier] = profile
|
var profiles = [app.bundleIdentifier: profile]
|
||||||
}
|
var error: Error?
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
|
||||||
|
|
||||||
if let directory = bundle.builtInPlugInsURL, let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants])
|
|
||||||
{
|
|
||||||
for case let fileURL as URL in enumerator where fileURL.pathExtension.lowercased() == "appex"
|
|
||||||
{
|
|
||||||
guard let appExtension = ALTApplication(fileURL: fileURL) else { continue }
|
|
||||||
|
|
||||||
dispatchGroup.enter()
|
let dispatchGroup = DispatchGroup()
|
||||||
|
|
||||||
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
for appExtension in app.appExtensions
|
||||||
switch result
|
{
|
||||||
{
|
dispatchGroup.enter()
|
||||||
case .failure(let e): error = e
|
|
||||||
case .success(let profile):
|
self.prepareProvisioningProfile(for: appExtension, parentApp: app, team: team, session: session) { (result) in
|
||||||
profiles[appExtension.bundleIdentifier] = profile
|
switch result
|
||||||
|
{
|
||||||
|
case .failure(let e): error = e
|
||||||
|
case .success(let profile): profiles[appExtension.bundleIdentifier] = profile
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.leave()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchGroup.notify(queue: .global()) {
|
||||||
|
if let error = error
|
||||||
|
{
|
||||||
|
completionHandler(.failure(error))
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
completionHandler(.success(profiles))
|
||||||
}
|
}
|
||||||
dispatchGroup.leave()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatchGroup.notify(queue: .global()) {
|
|
||||||
if let error = error
|
|
||||||
{
|
|
||||||
completionHandler(.failure(error))
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
completionHandler(.success(profiles))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -181,8 +171,19 @@ private extension ResignAppOperation
|
|||||||
preferredBundleID = app.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
|
preferredBundleID = app.bundleIdentifier.replacingOccurrences(of: parentBundleID, with: updatedParentBundleID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let preferredName: String
|
||||||
|
|
||||||
|
if let parentApp = parentApp
|
||||||
|
{
|
||||||
|
preferredName = "\(parentApp.name) - \(app.name)"
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
preferredName = app.name
|
||||||
|
}
|
||||||
|
|
||||||
// Register
|
// Register
|
||||||
self.register(app, bundleIdentifier: preferredBundleID, team: team, session: session) { (result) in
|
self.registerAppID(for: app, name: preferredName, bundleIdentifier: preferredBundleID, team: team, session: session) { (result) in
|
||||||
switch result
|
switch result
|
||||||
{
|
{
|
||||||
case .failure(let error): completionHandler(.failure(error))
|
case .failure(let error): completionHandler(.failure(error))
|
||||||
@@ -215,10 +216,8 @@ private extension ResignAppOperation
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func register(_ app: ALTApplication, bundleIdentifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
func registerAppID(for application: ALTApplication, name: String, bundleIdentifier: String, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result<ALTAppID, Error>) -> Void)
|
||||||
{
|
{
|
||||||
let appName = app.name
|
|
||||||
|
|
||||||
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
|
ALTAppleAPI.shared.fetchAppIDs(for: team, session: session) { (appIDs, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
@@ -230,7 +229,27 @@ private extension ResignAppOperation
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ALTAppleAPI.shared.addAppID(withName: appName, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
let requiredAppIDs = 1 + application.appExtensions.count
|
||||||
|
let availableAppIDs = max(0, Team.maximumFreeAppIDs - appIDs.count)
|
||||||
|
|
||||||
|
let sortedExpirationDates = appIDs.compactMap { $0.expirationDate }.sorted(by: { $0 < $1 })
|
||||||
|
|
||||||
|
if team.type == .free
|
||||||
|
{
|
||||||
|
if requiredAppIDs > availableAppIDs
|
||||||
|
{
|
||||||
|
if let expirationDate = sortedExpirationDates.first
|
||||||
|
{
|
||||||
|
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw ALTAppleAPIError(.maximumAppIDLimitReached)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ALTAppleAPI.shared.addAppID(withName: name, bundleIdentifier: bundleIdentifier, team: team, session: session) { (appID, error) in
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
do
|
do
|
||||||
@@ -240,11 +259,9 @@ private extension ResignAppOperation
|
|||||||
}
|
}
|
||||||
catch ALTAppleAPIError.maximumAppIDLimitReached
|
catch ALTAppleAPIError.maximumAppIDLimitReached
|
||||||
{
|
{
|
||||||
let sortedExpirationDates = appIDs.compactMap { $0.expirationDate }.sorted(by: { $0 < $1 })
|
|
||||||
|
|
||||||
if let expirationDate = sortedExpirationDates.first
|
if let expirationDate = sortedExpirationDates.first
|
||||||
{
|
{
|
||||||
throw OperationError.maximumAppIDLimitReached(expirationDate)
|
throw OperationError.maximumAppIDLimitReached(application: application, requiredAppIDs: requiredAppIDs, availableAppIDs: availableAppIDs, nextExpirationDate: expirationDate)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user