Download this file
| 1 | import AuthenticationServices |
| 2 | import Foundation |
| 3 | #if os(iOS) |
| 4 | import UIKit |
| 5 | #elseif os(macOS) |
| 6 | import AppKit |
| 7 | #endif |
| 8 | |
| 9 | /// Keeps browser authentication attached to the app, including servers that still |
| 10 | /// finish through the proof-bound exchange instead of issuing a callback. |
| 11 | @MainActor public final class NativeBrowserSignIn: NSObject, ASWebAuthenticationPresentationContextProviding { |
| 12 | private var session: ASWebAuthenticationSession? |
| 13 | private var generation = UUID() |
| 14 | public override init() { super.init() } |
| 15 | |
| 16 | public func start(url: URL, scheme: String, completion: @escaping @MainActor (URL?) -> Void) -> Bool { |
| 17 | cancel() |
| 18 | let generation = self.generation |
| 19 | let session = ASWebAuthenticationSession(url: url, callbackURLScheme: scheme) { url, _ in |
| 20 | Task { @MainActor [weak self] in |
| 21 | guard self?.generation == generation else { return } |
| 22 | completion(url) |
| 23 | } |
| 24 | } |
| 25 | session.presentationContextProvider = self |
| 26 | session.prefersEphemeralWebBrowserSession = false |
| 27 | self.session = session |
| 28 | return session.start() |
| 29 | } |
| 30 | |
| 31 | public func cancel() { |
| 32 | generation = UUID() |
| 33 | session?.cancel() |
| 34 | session = nil |
| 35 | } |
| 36 | |
| 37 | public func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor { |
| 38 | #if os(iOS) |
| 39 | return UIApplication.shared.connectedScenes.compactMap { $0 as? UIWindowScene } |
| 40 | .filter { $0.activationState == .foregroundActive } |
| 41 | .flatMap(\.windows).first(where: \.isKeyWindow) ?? ASPresentationAnchor() |
| 42 | #else |
| 43 | return NSApplication.shared.keyWindow ?? NSApplication.shared.windows.first ?? ASPresentationAnchor() |
| 44 | #endif |
| 45 | } |
| 46 | } |
| 47 | |