Skip to content

Commit d598de0

Browse files
authored
Fix problems with BrowserSignin on tvOS (#259)
* Fix problems with BrowserSignin on tvOS * Throw an error instead of nil when a browser provider cannot be found
1 parent 4288f77 commit d598de0

8 files changed

Lines changed: 37 additions & 28 deletions

File tree

Sources/BrowserSignin/BrowserSignin.swift

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public enum BrowserSigninError: Error {
2727
case cannotStartBrowserSession
2828
case cannotComposeAuthenticationURL
2929
case authenticationProvider(error: any Error)
30-
case noAuthenticatorProviderResonse
30+
case noAuthenticatorProviderResponse
3131
case serverError(_ error: OAuth2ServerError)
3232
case invalidRedirectScheme(_ scheme: String?)
3333
case userCancelledLogin(_ reason: String? = nil)
@@ -55,7 +55,6 @@ public enum BrowserSigninError: Error {
5555
///
5656
/// > Note: The use of HTTPS addresses within the redirect callback URI is limited by the availability of support within ASWebAuthenticationSession, which currently requires a minimum of iOS 17.4, macOS 14.4, watchOS 10.4, tvOS 17.4, or visionOS 1.1.
5757
@MainActor
58-
@available(iOS 13.0, macOS 10.15, tvOS 16.0, watchOS 7.0, visionOS 1.0, macCatalyst 13.0, *)
5958
public final class BrowserSignin {
6059
#if os(macOS)
6160
public typealias WindowAnchor = NSWindow
@@ -141,14 +140,10 @@ public final class BrowserSignin {
141140
}
142141

143142
async let authorizeUrl = signInFlow.start(with: context)
144-
guard let provider = try await Self.providerFactory.createWebAuthenticationProvider(
143+
let provider = try await Self.providerFactory.createWebAuthenticationProvider(
145144
for: self,
146145
from: window,
147146
options: options)
148-
else {
149-
throw BrowserSigninError.noCompatibleAuthenticationProviders
150-
}
151-
152147
self.provider = provider
153148

154149
let url = try await provider.open(authorizeUrl: authorizeUrl,
@@ -208,13 +203,10 @@ public final class BrowserSignin {
208203
}
209204

210205
async let authorizeUrl = signOutFlow.start(with: context)
211-
guard let provider = try await Self.providerFactory.createWebAuthenticationProvider(
206+
let provider = try await Self.providerFactory.createWebAuthenticationProvider(
212207
for: self,
213208
from: window,
214209
options: options)
215-
else {
216-
throw BrowserSigninError.noCompatibleAuthenticationProviders
217-
}
218210

219211
self.provider = provider
220212
defer { self.provider = nil }
@@ -337,18 +329,20 @@ public final class BrowserSignin {
337329
var provider: (any BrowserSignin.Provider)?
338330
}
339331

340-
@available(iOS 13.0, macOS 10.15, tvOS 16.0, watchOS 7.0, visionOS 1.0, macCatalyst 13.0, *)
341332
extension BrowserSignin: BrowserSignin.ProviderFactory {
342333
public nonisolated static func createWebAuthenticationProvider(
343334
for webAuth: BrowserSignin,
344335
from window: BrowserSignin.WindowAnchor?,
345-
options: Option) throws -> (any BrowserSignin.Provider)?
336+
options: Option) throws -> any BrowserSignin.Provider
346337
{
347-
try AuthenticationServicesProvider(from: window, usesEphemeralSession: options.contains(.ephemeralSession))
338+
if #available(iOS 13.0, macOS 10.15, tvOS 16.0, watchOS 7.0, visionOS 1.0, macCatalyst 13.0, *) {
339+
return try AuthenticationServicesProvider(from: window, usesEphemeralSession: options.contains(.ephemeralSession))
340+
}
341+
342+
throw BrowserSigninError.noCompatibleAuthenticationProviders
348343
}
349344
}
350345

351-
@available(iOS 13.0, macOS 10.15, tvOS 16.0, watchOS 7.0, visionOS 1.0, macCatalyst 13.0, *)
352346
extension BrowserSignin {
353347
/// Asynchronously initiates authentication from the given window.
354348
/// - Parameters:

Sources/BrowserSignin/Internal/BrowserSigninError+Extensions.swift

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@
1313
import Foundation
1414
import AuthenticationServices
1515

16-
@available(iOS 13.0, macOS 10.15, tvOS 16.0, watchOS 7.0, visionOS 1.0, macCatalyst 13.0, *)
1716
extension BrowserSigninError: LocalizedError {
1817
init(_ error: any Error) {
1918
let nsError = error as NSError
20-
if nsError.domain == ASWebAuthenticationSessionErrorDomain,
19+
20+
if #available(iOS 13.0, macOS 10.15, tvOS 16.0, watchOS 7.0, visionOS 1.0, macCatalyst 13.0, *),
21+
nsError.domain == ASWebAuthenticationSessionErrorDomain,
2122
nsError.code == ASWebAuthenticationSessionError.canceledLogin.rawValue
2223
{
2324
self = .userCancelledLogin(nsError.localizedFailureReason)
@@ -105,17 +106,23 @@ extension BrowserSigninError: LocalizedError {
105106
comment: ""),
106107
errorString)
107108

108-
case .noAuthenticatorProviderResonse:
109+
case .noAuthenticatorProviderResponse:
109110
return NSLocalizedString("no_authenticator_provider_response",
110111
tableName: "BrowserSignin",
111112
bundle: .browserSignin,
112113
comment: "No authenticator provider response")
113114
case .genericError(message: let message):
114115
return message
115116
case .noSignOutFlowProvided:
116-
return "FOO"
117+
return NSLocalizedString("no_signout_flow_provided",
118+
tableName: "BrowserSignin",
119+
bundle: .browserSignin,
120+
comment: "No signout flow provided")
117121
case .cannotStartBrowserSession:
118-
return "FOO"
122+
return NSLocalizedString("cannot_start_browser_session",
123+
tableName: "BrowserSignin",
124+
bundle: .browserSignin,
125+
comment: "Cannot start browser session")
119126
}
120127
}
121128
}
@@ -129,7 +136,7 @@ extension BrowserSigninError: Equatable {
129136
case (.cannotComposeAuthenticationURL, .cannotComposeAuthenticationURL): return true
130137
case (.userCancelledLogin(let lhs), .userCancelledLogin(let rhs)):
131138
return lhs == rhs
132-
case (.noAuthenticatorProviderResonse, .noAuthenticatorProviderResonse): return true
139+
case (.noAuthenticatorProviderResponse, .noAuthenticatorProviderResponse): return true
133140
case (.missingIdToken, .missingIdToken): return true
134141
case (.authenticationProvider(error: let lhsValue), .authenticationProvider(error: let rhsValue)):
135142
return lhsValue as NSError == rhsValue as NSError

Sources/BrowserSignin/Providers/AuthenticationServicesProvider.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ final class AuthenticationServicesProvider: NSObject, BrowserSignin.Provider {
147147
}
148148

149149
guard let url = url else {
150-
return .failure(BrowserSigninError.noAuthenticatorProviderResonse)
150+
return .failure(BrowserSigninError.noAuthenticatorProviderResponse)
151151
}
152152

153153
return .success(url)

Sources/BrowserSignin/Providers/BrowserSigninProvider.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,10 @@ extension BrowserSignin {
4040
/// - window: The window anchor the sign-in is initiated from.
4141
/// - options: The options used to control the sign in provider's behavior.
4242
/// - Returns: ``BrowserSignin/Provider`` that is capable of signing in, or `nil` if browser sign in is unsupported on this platform.
43+
/// - Throws: ``BrowserSigninError/noCompatibleAuthenticationProviders`` if no provider could be found.
4344
nonisolated static func createWebAuthenticationProvider(
4445
for browserSignin: BrowserSignin,
4546
from window: BrowserSignin.WindowAnchor?,
46-
options: BrowserSignin.Option) async throws -> (any Provider)?
47+
options: BrowserSignin.Option) async throws -> any Provider
4748
}
4849
}

Sources/BrowserSignin/Resources/en.lproj/BrowserSignin.strings

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@
1010
"no_scheme_defined" = "No URL scheme defined";
1111
"missing_id_token_description" = "Missing ID Token";
1212
"no_authenticator_provider_response" = "Authentication session returned neither a URL or an error";
13+
"no_signout_flow_provided" = "No sign out flow available";
14+
"cannot_start_browser_session" = "Cannot open a browser session";

Tests/BrowserSigninTests/AuthenticationServicesProviderTests.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import CommonSupport
2424
@testable import BrowserSignin
2525
import AuthenticationServices
2626

27+
@available(iOS 12.0, macCatalyst 13.0, macOS 10.15, tvOS 16.0, visionOS 1.0, watchOS 6.2, *)
2728
class MockAuthenticationServicesProviderSession: NSObject, @unchecked Sendable, AuthenticationServicesProviderSession {
2829
let url: URL
2930
let callbackURLScheme: String?
@@ -53,9 +54,12 @@ class MockAuthenticationServicesProviderSession: NSObject, @unchecked Sendable,
5354
self.completionHandler = completionHandler
5455
}
5556

57+
#if os(iOS) || os(macOS) || os(visionOS) || targetEnvironment(macCatalyst)
5658
var presentationContextProvider: (any ASWebAuthenticationPresentationContextProviding)?
59+
#endif
60+
5761
var prefersEphemeralWebBrowserSession = false
58-
62+
5963
var canStart: Bool = true
6064

6165
func start() -> Bool {
@@ -83,7 +87,7 @@ class MockAuthenticationServicesProviderSession: NSObject, @unchecked Sendable,
8387
}
8488
}
8589

86-
@available(iOS 13.0, *)
90+
@available(iOS 12.0, macCatalyst 13.0, macOS 10.15, tvOS 16.0, visionOS 1.0, watchOS 6.2, *)
8791
class AuthenticationServicesProviderTests: XCTestCase {
8892
var provider: AuthenticationServicesProvider!
8993
let authorizeUrl = URL(string: "https://example.okta.com/oauth2/v1/authorize?client_id=clientId&redirect_uri=com.example:/callback&response_type=code&scope=openid%20profile&state=ABC123")
@@ -153,7 +157,7 @@ class AuthenticationServicesProviderTests: XCTestCase {
153157
MockAuthenticationServicesProviderSession.result.wrappedValue = nil
154158
let response = await XCTAssertThrowsErrorAsync(try await provider.open(authorizeUrl: authorizeUrl, redirectUri: redirectUri))
155159

156-
XCTAssertEqual(response as? BrowserSigninError, .noAuthenticatorProviderResonse)
160+
XCTAssertEqual(response as? BrowserSigninError, .noAuthenticatorProviderResponse)
157161
XCTAssertNil(provider.authenticationSession)
158162
}
159163
}

Tests/BrowserSigninTests/BrowserSigninFlowTests.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class BrowserSigninFlowTests: XCTestCase {
117117
XCTAssertEqual(redirectUri.absoluteString, "com.example:/logout")
118118
}
119119

120+
@available(iOS 12.0, macCatalyst 13.0, macOS 10.15, tvOS 16.0, visionOS 1.0, watchOS 6.2, *)
120121
func testCancel() async throws {
121122
let loginFlow = try AuthorizationCodeFlow(client: client,
122123
additionalParameters: ["testName": name])

Tests/BrowserSigninTests/BrowserSigninMocks.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ struct BrowserSigninProviderFactoryMock: BrowserSignin.ProviderFactory {
4343

4444
static func createWebAuthenticationProvider(for browserSignin: BrowserSignin,
4545
from window: BrowserSignin.WindowAnchor?,
46-
options: BrowserSignin.Option) async throws -> (any BrowserSignin.Provider)?
46+
options: BrowserSignin.Option) async throws -> any BrowserSignin.Provider
4747
{
4848
let testName = browserSignin.signInFlow.additionalParameters?["testName"] as? String
4949

@@ -97,7 +97,7 @@ class BrowserSigninProviderMock: @unchecked Sendable, BrowserSignin.Provider {
9797
}
9898
throw error
9999
case nil:
100-
throw BrowserSigninError.noAuthenticatorProviderResonse
100+
throw BrowserSigninError.noAuthenticatorProviderResponse
101101
}
102102
}
103103

0 commit comments

Comments
 (0)