forked from shadowfacts/Tusker
Use async/await for login
This commit is contained in:
parent
27e05cc72d
commit
c36a239f46
|
@ -101,6 +101,19 @@ public class Client {
|
|||
return task
|
||||
}
|
||||
|
||||
public func run<Result>(_ request: Request<Result>) async throws -> (Result, Pagination?) {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
run(request) { response in
|
||||
switch response {
|
||||
case let .failure(error):
|
||||
continuation.resume(throwing: error)
|
||||
case let .success(result, pagination):
|
||||
continuation.resume(returning: (result, pagination))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func createURLRequest<Result>(request: Request<Result>) -> URLRequest? {
|
||||
guard var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: true) else { return nil }
|
||||
components.path = request.path
|
||||
|
@ -117,24 +130,21 @@ public class Client {
|
|||
}
|
||||
|
||||
// MARK: - Authorization
|
||||
public func registerApp(name: String, redirectURI: String, scopes: [Scope], website: URL? = nil, completion: @escaping Callback<RegisteredApplication>) {
|
||||
public func registerApp(name: String, redirectURI: String, scopes: [Scope], website: URL? = nil) async throws -> RegisteredApplication {
|
||||
let request = Request<RegisteredApplication>(method: .post, path: "/api/v1/apps", body: ParametersBody([
|
||||
"client_name" => name,
|
||||
"redirect_uris" => redirectURI,
|
||||
"scopes" => scopes.scopeString,
|
||||
"website" => website?.absoluteString
|
||||
]))
|
||||
run(request) { result in
|
||||
defer { completion(result) }
|
||||
guard case let .success(application, _) = result else { return }
|
||||
|
||||
self.appID = application.id
|
||||
self.clientID = application.clientID
|
||||
self.clientSecret = application.clientSecret
|
||||
}
|
||||
let (application, _) = try await run(request)
|
||||
self.appID = application.id
|
||||
self.clientID = application.clientID
|
||||
self.clientSecret = application.clientSecret
|
||||
return application
|
||||
}
|
||||
|
||||
public func getAccessToken(authorizationCode: String, redirectURI: String, completion: @escaping Callback<LoginSettings>) {
|
||||
public func getAccessToken(authorizationCode: String, redirectURI: String) async throws -> LoginSettings {
|
||||
let request = Request<LoginSettings>(method: .post, path: "/oauth/token", body: ParametersBody([
|
||||
"client_id" => clientID,
|
||||
"client_secret" => clientSecret,
|
||||
|
@ -142,12 +152,9 @@ public class Client {
|
|||
"code" => authorizationCode,
|
||||
"redirect_uri" => redirectURI
|
||||
]))
|
||||
run(request) { result in
|
||||
defer { completion(result) }
|
||||
guard case let .success(loginSettings, _) = result else { return }
|
||||
|
||||
self.accessToken = loginSettings.accessToken
|
||||
}
|
||||
let (settings, _) = try await run(request)
|
||||
self.accessToken = settings.accessToken
|
||||
return settings
|
||||
}
|
||||
|
||||
// MARK: - Self
|
||||
|
|
|
@ -2342,6 +2342,7 @@
|
|||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Pachyderm/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -2373,6 +2374,7 @@
|
|||
DYLIB_INSTALL_NAME_BASE = "@rpath";
|
||||
INFOPLIST_FILE = Pachyderm/Info.plist;
|
||||
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -2560,7 +2562,7 @@
|
|||
CURRENT_PROJECT_VERSION = 19;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -2593,7 +2595,7 @@
|
|||
CURRENT_PROJECT_VERSION = 19;
|
||||
DEVELOPMENT_TEAM = V4WK9KR9U2;
|
||||
INFOPLIST_FILE = Tusker/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 14.1;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 15.0;
|
||||
"IPHONEOS_DEPLOYMENT_TARGET[sdk=macosx*]" = 14.3;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
"object": {
|
||||
"pins": [
|
||||
{
|
||||
"package": "PLCrashReporter",
|
||||
"package": "plcrashreporter",
|
||||
"repositoryURL": "https://github.com/microsoft/plcrashreporter",
|
||||
"state": {
|
||||
"branch": null,
|
||||
|
|
|
@ -65,28 +65,22 @@ class MastodonController: ObservableObject {
|
|||
return client.run(request, completion: completion)
|
||||
}
|
||||
|
||||
func registerApp(completion: @escaping (_ clientID: String, _ clientSecret: String) -> Void) {
|
||||
guard client.clientID == nil,
|
||||
client.clientSecret == nil else {
|
||||
|
||||
completion(client.clientID!, client.clientSecret!)
|
||||
return
|
||||
}
|
||||
|
||||
client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow]) { response in
|
||||
guard case let .success(app, _) = response else { fatalError() }
|
||||
self.client.clientID = app.clientID
|
||||
self.client.clientSecret = app.clientSecret
|
||||
completion(app.clientID, app.clientSecret)
|
||||
}
|
||||
@discardableResult
|
||||
func run<Result>(_ request: Request<Result>) async throws -> (Result, Pagination?) {
|
||||
return try await client.run(request)
|
||||
}
|
||||
|
||||
func authorize(authorizationCode: String, completion: @escaping (_ accessToken: String) -> Void) {
|
||||
client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth") { response in
|
||||
guard case let .success(settings, _) = response else { fatalError() }
|
||||
self.client.accessToken = settings.accessToken
|
||||
completion(settings.accessToken)
|
||||
}
|
||||
func registerApp() async -> (String, String) {
|
||||
guard client.clientID == nil,
|
||||
client.clientSecret == nil else {
|
||||
return (client.clientID!, client.clientSecret!)
|
||||
}
|
||||
let app = try! await client.registerApp(name: "Tusker", redirectURI: "tusker://oauth", scopes: [.read, .write, .follow])
|
||||
return (app.clientID, app.clientSecret)
|
||||
}
|
||||
|
||||
func authorize(authorizationCode: String) async -> String {
|
||||
return try! await client.getAccessToken(authorizationCode: authorizationCode, redirectURI: "tusker://oauth").accessToken
|
||||
}
|
||||
|
||||
func getOwnAccount(completion: ((Result<Account, Client.Error>) -> Void)? = nil) {
|
||||
|
@ -118,6 +112,12 @@ class MastodonController: ObservableObject {
|
|||
}
|
||||
}
|
||||
|
||||
func getOwnAccount() async throws -> Account {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
getOwnAccount(completion: continuation.resume)
|
||||
}
|
||||
}
|
||||
|
||||
func getOwnInstance(completion: ((Instance) -> Void)? = nil) {
|
||||
getOwnInstanceInternal(retryAttempt: 0, completion: completion)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import UIKit
|
||||
import AuthenticationServices
|
||||
import Pachyderm
|
||||
|
||||
protocol OnboardingViewControllerDelegate {
|
||||
func didFinishOnboarding(account: LocalData.UserAccountInfo)
|
||||
|
@ -40,54 +41,20 @@ class OnboardingViewController: UINavigationController {
|
|||
|
||||
instanceSelector.delegate = self
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate {
|
||||
func didSelectInstance(url instanceURL: URL) {
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL)
|
||||
mastodonController.registerApp { (clientID, clientSecret) in
|
||||
|
||||
var components = URLComponents(url: instanceURL, resolvingAgainstBaseURL: false)!
|
||||
components.path = "/oauth/authorize"
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "client_id", value: clientID),
|
||||
URLQueryItem(name: "response_type", value: "code"),
|
||||
URLQueryItem(name: "scope", value: "read write follow"),
|
||||
URLQueryItem(name: "redirect_uri", value: "tusker://oauth")
|
||||
]
|
||||
let authorizeURL = components.url!
|
||||
|
||||
self.authenticationSession = ASWebAuthenticationSession(url: authorizeURL, callbackURLScheme: "tusker") { url, error in
|
||||
guard error == nil,
|
||||
let url = url,
|
||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
let item = components.queryItems?.first(where: { $0.name == "code" }),
|
||||
let authCode = item.value else { return }
|
||||
|
||||
mastodonController.authorize(authorizationCode: authCode) { (accessToken) in
|
||||
// construct a temporary UserAccountInfo instance for the MastodonController to use to fetch it's own account
|
||||
let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
|
||||
mastodonController.accountInfo = tempAccountInfo
|
||||
|
||||
mastodonController.getOwnAccount { (result) in
|
||||
DispatchQueue.main.async {
|
||||
switch result {
|
||||
case let .failure(error):
|
||||
let alert = UIAlertController(title: "Unable to Verify Credentials", message: "Your account could not be fetched at this time: \(error.localizedDescription)", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
|
||||
self.present(alert, animated: true)
|
||||
|
||||
case let .success(account):
|
||||
// this needs to happen on the main thread because it publishes a new value for the ObservableObject
|
||||
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
|
||||
mastodonController.accountInfo = accountInfo
|
||||
|
||||
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private func doAuthenticationSession(url: URL) async throws -> URL {
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
self.authenticationSession = ASWebAuthenticationSession(url: url, callbackURLScheme: "tusker") { url, error in
|
||||
defer {
|
||||
DispatchQueue.main.async {
|
||||
self.authenticationSession = nil
|
||||
}
|
||||
}
|
||||
if let url = url {
|
||||
continuation.resume(returning: url)
|
||||
} else {
|
||||
continuation.resume(throwing: error!)
|
||||
}
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
// Prefer ephemeral sessions to make it easier to sign into multiple accounts on the same instance.
|
||||
|
@ -97,6 +64,53 @@ extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension OnboardingViewController: InstanceSelectorTableViewControllerDelegate {
|
||||
func didSelectInstance(url instanceURL: URL) {
|
||||
async {
|
||||
let mastodonController = MastodonController(instanceURL: instanceURL)
|
||||
let (clientID, clientSecret) = await mastodonController.registerApp()
|
||||
|
||||
var components = URLComponents(url: instanceURL, resolvingAgainstBaseURL: false)!
|
||||
components.path = "/oauth/authorize"
|
||||
components.queryItems = [
|
||||
URLQueryItem(name: "client_id", value: clientID),
|
||||
URLQueryItem(name: "response_type", value: "code"),
|
||||
URLQueryItem(name: "scope", value: "read write follow"),
|
||||
URLQueryItem(name: "redirect_uri", value: "tusker://oauth")
|
||||
]
|
||||
|
||||
guard let url = try? await self.doAuthenticationSession(url: components.url!),
|
||||
let components = URLComponents(url: url, resolvingAgainstBaseURL: true),
|
||||
let item = components.queryItems?.first(where: { $0.name == "code" }),
|
||||
let authCode = item.value else {
|
||||
return
|
||||
}
|
||||
|
||||
let accessToken = await mastodonController.authorize(authorizationCode: authCode)
|
||||
|
||||
// construct a temporary UserAccountInfo instance for the MastodonController to use to fetch its own account
|
||||
let tempAccountInfo = LocalData.UserAccountInfo(id: "temp", instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: nil, accessToken: accessToken)
|
||||
mastodonController.accountInfo = tempAccountInfo
|
||||
|
||||
let account: Account
|
||||
do {
|
||||
account = try await mastodonController.getOwnAccount()
|
||||
} catch {
|
||||
let alert = UIAlertController(title: "Unable to Verify Credentials", message: "Your account fcould not be fetcheda this time: \(error.localizedDescription)", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
|
||||
self.present(alert, animated: true)
|
||||
return
|
||||
}
|
||||
|
||||
let accountInfo = LocalData.shared.addAccount(instanceURL: instanceURL, clientID: clientID, clientSecret: clientSecret, username: account.username, accessToken: accessToken)
|
||||
mastodonController.accountInfo = accountInfo
|
||||
|
||||
self.onboardingDelegate?.didFinishOnboarding(account: accountInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension OnboardingViewController: ASWebAuthenticationPresentationContextProviding {
|
||||
|
|
Loading…
Reference in New Issue