diff --git a/.swiftformat b/.swiftformat index ce62d1c..f727bd2 100644 --- a/.swiftformat +++ b/.swiftformat @@ -11,6 +11,7 @@ --disable blankLinesAroundMark --disable consecutiveSpaces --disable hoistPatternLet +--disable hoistTry --disable indent --disable trailingCommas diff --git a/Sources/MasKit/AppStore/ISStoreAccount.swift b/Sources/MasKit/AppStore/ISStoreAccount.swift index 29be002..ae62f10 100644 --- a/Sources/MasKit/AppStore/ISStoreAccount.swift +++ b/Sources/MasKit/AppStore/ISStoreAccount.swift @@ -7,85 +7,81 @@ // import CommerceKit +import PromiseKit import StoreFoundation extension ISStoreAccount: StoreAccount { - static var primaryAccount: ISStoreAccount? { + static var primaryAccount: Promise { if #available(macOS 10.13, *) { - let group = DispatchGroup() - group.enter() - - var account: ISStoreAccount? - ISServiceProxy.genericShared().accountService.primaryAccount { storeAccount in - account = storeAccount - group.leave() - } - - _ = group.wait(timeout: .now() + 30) - - return account + return race( + Promise { seal in + ISServiceProxy.genericShared().accountService.primaryAccount { storeAccount in + seal.fulfill(storeAccount) + } + }, + after(seconds: 30).then { + Promise(error: MASError.notSignedIn) + } + ) } else { - return CKAccountStore.shared().primaryAccount + return .value(CKAccountStore.shared().primaryAccount) } } - static func signIn(username: String, password: String, systemDialog: Bool = false) throws -> ISStoreAccount { + static func signIn(username: String, password: String, systemDialog: Bool) -> Promise { if #available(macOS 10.13, *) { // Signing in is no longer possible as of High Sierra. // https://github.com/mas-cli/mas/issues/164 - throw MASError.notSupported + return Promise(error: MASError.notSupported) } else { - if let account = primaryAccount, account.isSignedIn { - throw MASError.alreadySignedIn(asAccountId: account.identifier) - } + return + primaryAccount + .then { account -> Promise in + if account.isSignedIn { + return Promise(error: MASError.alreadySignedIn(asAccountId: account.identifier)) + } - let password = - password.isEmpty && !systemDialog - ? String(validatingUTF8: getpass("Password: "))! - : password + let password = + password.isEmpty && !systemDialog + ? String(validatingUTF8: getpass("Password: "))! + : password - guard !password.isEmpty || systemDialog else { - throw MASError.noPasswordProvided - } + guard !password.isEmpty || systemDialog else { + return Promise(error: MASError.noPasswordProvided) + } - let accountService = ISServiceProxy.genericShared().accountService - accountService.setStoreClient(ISStoreClient(storeClientType: 0)) + let context = ISAuthenticationContext(accountID: 0) + context.appleIDOverride = username - let context = ISAuthenticationContext(accountID: 0) - context.appleIDOverride = username - if !systemDialog { - context.demoMode = true - context.demoAccountName = username - context.demoAccountPassword = password - context.demoAutologinMode = true - } + let signInPromise = + Promise { seal in + let accountService = ISServiceProxy.genericShared().accountService + accountService.setStoreClient(ISStoreClient(storeClientType: 0)) + accountService.signIn(with: context) { success, storeAccount, error in + if success, let storeAccount { + seal.fulfill(storeAccount) + } else { + seal.reject(MASError.signInFailed(error: error as NSError?)) + } + } + } - let group = DispatchGroup() - group.enter() + if systemDialog { + return signInPromise + } else { + context.demoMode = true + context.demoAccountName = username + context.demoAccountPassword = password + context.demoAutologinMode = true - var storeAccount: ISStoreAccount? - var maserror: MASError? - // Only works on macOS Sierra and below - accountService.signIn(with: context) { success, account, error in - if success, let account { - storeAccount = account - } else { - maserror = .signInFailed(error: error as NSError?) + return race( + signInPromise, + after(seconds: 30).then { + Promise(error: MASError.signInFailed(error: nil)) + } + ) + } } - group.leave() - } - - if systemDialog { - group.wait() - } else { - _ = group.wait(timeout: .now() + 30) - } - - if let storeAccount { - return storeAccount - } - - throw maserror ?? MASError.signInFailed(error: nil) } } } diff --git a/Sources/MasKit/AppStore/SSPurchase.swift b/Sources/MasKit/AppStore/SSPurchase.swift index 5fd0eab..2bab990 100644 --- a/Sources/MasKit/AppStore/SSPurchase.swift +++ b/Sources/MasKit/AppStore/SSPurchase.swift @@ -45,7 +45,7 @@ extension SSPurchase { // Monterey obscures the user's App Store account information, but allows // redownloads without passing the account to SSPurchase. // https://github.com/mas-cli/mas/issues/417 - if let storeAccount = ISStoreAccount.primaryAccount { + if let storeAccount = try? ISStoreAccount.primaryAccount.wait() { accountIdentifier = storeAccount.dsID appleID = storeAccount.identifier } diff --git a/Sources/MasKit/Commands/Account.swift b/Sources/MasKit/Commands/Account.swift index 9701054..333e2d2 100644 --- a/Sources/MasKit/Commands/Account.swift +++ b/Sources/MasKit/Commands/Account.swift @@ -24,11 +24,11 @@ public struct AccountCommand: CommandProtocol { return .failure(.notSupported) } - if let account = ISStoreAccount.primaryAccount { - print(account.identifier) - } else { - return .failure(.notSignedIn) + do { + print(try ISStoreAccount.primaryAccount.wait().identifier) + return .success(()) + } catch { + return .failure(error as? MASError ?? .failed(error: error as NSError)) } - return .success(()) } } diff --git a/Sources/MasKit/Commands/SignIn.swift b/Sources/MasKit/Commands/SignIn.swift index 1ac241f..e04769e 100644 --- a/Sources/MasKit/Commands/SignIn.swift +++ b/Sources/MasKit/Commands/SignIn.swift @@ -25,6 +25,7 @@ public struct SignInCommand: CommandProtocol { password: options.password, systemDialog: options.dialog ) + .wait() return .success(()) } catch { return .failure(error as? MASError ?? .signInFailed(error: error as NSError)) diff --git a/Sources/MasKit/Errors/MASError.swift b/Sources/MasKit/Errors/MASError.swift index 57e40af..d5c4683 100644 --- a/Sources/MasKit/Errors/MASError.swift +++ b/Sources/MasKit/Errors/MASError.swift @@ -11,6 +11,8 @@ import Foundation public enum MASError: Error, Equatable { case notSupported + case failed(error: NSError?) + case notSignedIn case noPasswordProvided case signInFailed(error: NSError?) @@ -46,6 +48,12 @@ extension MASError: CustomStringConvertible { For more information see: \ https://github.com/mas-cli/mas#%EF%B8%8F-known-issues """ + case .failed(let error): + if let error { + return "Failed: \(error.localizedDescription)" + } else { + return "Failed" + } case .signInFailed(let error): if let error { return "Sign in failed: \(error.localizedDescription)"