diff --git a/Sources/SnapAuth/Errors.swift b/Sources/SnapAuth/Errors.swift index b546232..fb3d254 100644 --- a/Sources/SnapAuth/Errors.swift +++ b/Sources/SnapAuth/Errors.swift @@ -2,6 +2,9 @@ public enum SnapAuthError: Error { /// The network request was disrupted. This is generally safe to retry. case networkInterruption + /// A new request is starting, so the one in flight was canceled + case newRequestStarting + // MARK: Internal errors, which could represent SnapAuth bugs /// The SDK received a response from SnapAuth, but it arrived in an diff --git a/Sources/SnapAuth/SnapAuth+ASACD.swift b/Sources/SnapAuth/SnapAuth+ASACD.swift index 5d00ce5..6e7aa49 100644 --- a/Sources/SnapAuth/SnapAuth+ASACD.swift +++ b/Sources/SnapAuth/SnapAuth+ASACD.swift @@ -8,6 +8,7 @@ extension SnapAuth: ASAuthorizationControllerDelegate { controller: ASAuthorizationController, didCompleteWithError error: Error ) { + logger.debug("ASACD error") guard let asError = error as? ASAuthorizationError else { logger.error("authorizationController didCompleteWithError error was not an ASAuthorizationError") sendError(.unknown) @@ -52,18 +53,14 @@ extension SnapAuth: ASAuthorizationControllerDelegate { /// Sends the error to the appropriate delegate method and resets the internal state back to idle private func sendError(_ error: SnapAuthError) { - switch state { - case .authenticating: - authContinuation?.resume(returning: .failure(error)) - case .registering: - registerContinuation?.resume(returning: .failure(error)) - case .idle: - logger.error("Tried to send error in idle state") - case .autoFill: - // No-op for now. TODO: decide what errors to send - break - } - state = .idle + // One or the other should eb set, but not both + assert( + (continuation != nil && autoFillDelegate == nil) + || (continuation == nil && autoFillDelegate != nil) + ) + autoFillDelegate = nil + continuation?.resume(returning: .failure(error)) + continuation = nil } private func handleRegistration( @@ -116,7 +113,9 @@ extension SnapAuth: ASAuthorizationControllerDelegate { token: processAuth.token, expiresAt: processAuth.expiresAt) - registerContinuation?.resume(returning: .success(rewrapped)) + assert(continuation != nil) + continuation?.resume(returning: .success(rewrapped)) + continuation = nil } } @@ -162,15 +161,16 @@ extension SnapAuth: ASAuthorizationControllerDelegate { token: authResponse.token, expiresAt: authResponse.expiresAt) - if state == .authenticating { - // if AF, send to delegate, otherwise do this - authContinuation?.resume(returning: .success(rewrapped)) - } else if state == .autoFill { - assert(autoFillDelegate != nil, "AutoFill w/ no delegate") - autoFillDelegate?.snapAuth(didAutoFillWithResult: .success(rewrapped)) - } else { - assert(false, "Not authenticating or AF in assertion delegate") + // Short-term BC hack + if autoFillDelegate != nil { + autoFillDelegate!.snapAuth(didAutoFillWithResult: .success(rewrapped)) + autoFillDelegate = nil + return } + + assert(continuation != nil) + continuation?.resume(returning: .success(rewrapped)) + continuation = nil } } diff --git a/Sources/SnapAuth/SnapAuth+AutoFill.swift b/Sources/SnapAuth/SnapAuth+AutoFill.swift index d9288f1..055d8b2 100644 --- a/Sources/SnapAuth/SnapAuth+AutoFill.swift +++ b/Sources/SnapAuth/SnapAuth+AutoFill.swift @@ -35,7 +35,6 @@ extension SnapAuth { presentationContextProvider: ASAuthorizationControllerPresentationContextProviding ) { reset() - state = .autoFill autoFillDelegate = delegate Task { let response = await api.makeRequest( diff --git a/Sources/SnapAuth/SnapAuth.swift b/Sources/SnapAuth/SnapAuth.swift index f578372..1814eaa 100644 --- a/Sources/SnapAuth/SnapAuth.swift +++ b/Sources/SnapAuth/SnapAuth.swift @@ -23,9 +23,7 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg internal var authController: ASAuthorizationController? - internal var registerContinuation: CheckedContinuation? - internal var authContinuation: CheckedContinuation? - + internal var continuation: CheckedContinuation? /// - Parameters: /// - publishableKey: Your SnapAuth publishable key. This can be obtained @@ -60,15 +58,14 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg /// Reinitializes internal state before starting a request. internal func reset() -> Void { self.authenticatingUser = nil - cancelPendingRequest() - state = .idle - } - - private func cancelPendingRequest() { + continuation?.resume(returning: .failure(.newRequestStarting)) + continuation = nil logger.debug("Canceling pending requests") + // Do this after the continuation is cleared out, so it doesn't run twice and break if authController != nil { #if !os(tvOS) if #available(iOS 16.0, macOS 13.0, visionOS 1.0, *) { + logger.debug("Canceling existing auth controller") authController!.cancel() } #endif @@ -120,7 +117,6 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg ) async -> SnapAuthResult { reset() self.anchor = anchor - state = .registering let body = SACreateRegisterOptionsRequest(user: nil) let response = await api.makeRequest( @@ -146,7 +142,8 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg logger.debug("SR perform") return await withCheckedContinuation { continuation in - registerContinuation = continuation + assert(self.continuation == nil) + self.continuation = continuation controller.performRequests() } @@ -193,7 +190,6 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg reset() self.anchor = anchor self.authenticatingUser = user - state = .authenticating let body = ["user": user] @@ -219,7 +215,8 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg controller.delegate = self controller.presentationContextProvider = self return await withCheckedContinuation { continuation in - authContinuation = continuation + assert(self.continuation == nil) + self.continuation = continuation logger.debug("perform requests") controller.performRequests() } @@ -227,19 +224,6 @@ public class SnapAuth: NSObject { // NSObject for ASAuthorizationControllerDeleg // Sometimes the controller just WILL NOT CALL EITHER DELEGATE METHOD, so... yeah. // Maybe start a timer and auto-fail if neither delegate method runs in time? } - - internal var state: State = .idle -} - -/// SDK state -/// -/// This helps with sending appropriate failure messages back to delegates, -/// since all AS delegate failure paths go to a single place. -enum State { - case idle - case registering - case authenticating - case autoFill } public enum AuthenticatingUser {