Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions FirebaseAuth/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 11.2.0
- [Fixed] Fixed crashes that could occur in Swift continuation blocks running in the Xcode 16
betas. (#13480)

# 11.1.0
- [fixed] Fixed `Swift.error` conformance for `AuthErrorCode`. (#13430)
- [added] Added custom provider support to `AuthProviderID`. Note that this change will be breaking
Expand Down
5 changes: 1 addition & 4 deletions FirebaseAuth/Docs/threading.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ has its target queue set to this auth global work queue. This way we don't
have to think about which variables may be contested. We only need to make
sure all public APIs that may have thread-safety issues make the dispatch.
The auth global work queue is defined in
[FIRAuthGlobalWorkQueue.h](../Source/Private/FIRAuthGlobalWorkQueue.h)
and any serial task queue created by
[FIRAuthSerialTaskQueue.h](../Source/Private/FIRAuthSerialTaskQueue.h)
already has its target set properly.
[FIRAuthGlobalWorkQueue.h](../Source/Private/FIRAuthGlobalWorkQueue.h).

In following sub-sections, we divided methods into three categories, according
to the two criteria below:
Expand Down
39 changes: 0 additions & 39 deletions FirebaseAuth/Sources/Swift/Auth/AuthSerialTaskQueue.swift

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ import Foundation
}

/// Starts the flow to verify the client via silent push notification.
/// - Parameter retryOnInvalidAppCredential: Whether of not the flow should be retried if an
/// - Parameter retryOnInvalidAppCredential: Whether or not the flow should be retried if an
/// AuthErrorCodeInvalidAppCredential error is returned from the backend.
/// - Parameter phoneNumber: The phone number to be verified.
/// - Parameter callback: The callback to be invoked on the global work queue when the flow is
Expand Down
262 changes: 114 additions & 148 deletions FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ protocol AuthBackendRPCIssuer: NSObjectProtocol {
/// on the auth global work queue in the future.
func asyncCallToURL<T: AuthRPCRequest>(with request: T,
body: Data?,
contentType: String,
completionHandler: @escaping ((Data?, Error?) -> Void))
contentType: String) async -> (Data?, Error?)
}

@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
Expand All @@ -51,20 +50,22 @@ class AuthBackendRPCIssuerImplementation: NSObject, AuthBackendRPCIssuer {

func asyncCallToURL<T: AuthRPCRequest>(with request: T,
body: Data?,
contentType: String,
completionHandler: @escaping ((Data?, Error?)
-> Void)) {
contentType: String) async -> (Data?, Error?) {
let requestConfiguration = request.requestConfiguration()
AuthBackend.request(withURL: request.requestURL(),
contentType: contentType,
requestConfiguration: requestConfiguration) { request in
let fetcher = self.fetcherService.fetcher(with: request)
if let _ = requestConfiguration.emulatorHostAndPort {
fetcher.allowLocalhostRequest = true
fetcher.allowedInsecureSchemes = ["http"]
let request = await AuthBackend.request(withURL: request.requestURL(),
contentType: contentType,
requestConfiguration: requestConfiguration)
let fetcher = fetcherService.fetcher(with: request)
if let _ = requestConfiguration.emulatorHostAndPort {
fetcher.allowLocalhostRequest = true
fetcher.allowedInsecureSchemes = ["http"]
}
fetcher.bodyData = body

return await withUnsafeContinuation { continuation in
fetcher.beginFetch { data, error in
continuation.resume(returning: (data, error))
}
fetcher.bodyData = body
fetcher.beginFetch(completionHandler: completionHandler)
}
}
}
Expand Down Expand Up @@ -98,8 +99,7 @@ class AuthBackend: NSObject {

class func request(withURL url: URL,
contentType: String,
requestConfiguration: AuthRequestConfiguration,
completion: @escaping (URLRequest) -> Void) {
requestConfiguration: AuthRequestConfiguration) async -> URLRequest {
var request = URLRequest(url: url)
request.setValue(contentType, forHTTPHeaderField: "Content-Type")
let additionalFrameworkMarker = requestConfiguration
Expand All @@ -121,18 +121,15 @@ class AuthBackend: NSObject {
request.setValue(languageCode, forHTTPHeaderField: "X-Firebase-Locale")
}
if let appCheck = requestConfiguration.appCheck {
appCheck.getToken(forcingRefresh: false) { tokenResult in
if let error = tokenResult.error {
AuthLog.logWarning(code: "I-AUT000018",
message: "Error getting App Check token; using placeholder " +
"token instead. Error: \(error)")
}
request.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck")
completion(request)
let tokenResult = await appCheck.getToken(forcingRefresh: false)
if let error = tokenResult.error {
AuthLog.logWarning(code: "I-AUT000018",
message: "Error getting App Check token; using placeholder " +
"token instead. Error: \(error)")
}
} else {
completion(request)
request.setValue(tokenResult.token, forHTTPHeaderField: "X-Firebase-AppCheck")
}
return request
}
}

Expand Down Expand Up @@ -270,135 +267,104 @@ private class AuthBackendRPCImplementation: NSObject, AuthBackendImplementation
throw AuthErrorUtils.JSONSerializationErrorForUnencodableType()
}
}
return try await withCheckedThrowingContinuation { continuation in
rpcIssuer
.asyncCallToURL(with: request, body: bodyData, contentType: "application/json") {
data, error in
// If there is an error with no body data at all, then this must be a
// network error.
guard let data = data else {
if let error = error {
continuation.resume(throwing: AuthErrorUtils.networkError(underlyingError: error))
return
} else {
// TODO: this was ignored before
fatalError("Auth Internal error: RPC call didn't return data or an error.")
}
}
// Try to decode the HTTP response data which may contain either a
// successful response or error message.
var dictionary: [String: AnyHashable]
do {
let rawDecode = try JSONSerialization.jsonObject(with: data,
options: JSONSerialization
.ReadingOptions
.mutableLeaves)
guard let decodedDictionary = rawDecode as? [String: AnyHashable] else {
if error != nil {
continuation.resume(
throwing: AuthErrorUtils.unexpectedErrorResponse(deserializedResponse: rawDecode,
underlyingError: error)
)
return
} else {
continuation.resume(
throwing: AuthErrorUtils.unexpectedResponse(deserializedResponse: rawDecode)
)
return
}
}
dictionary = decodedDictionary
} catch let jsonError {
if error != nil {
// We have an error, but we couldn't decode the body, so we have no
// additional information other than the raw response and the
// original NSError (the jsonError is inferred by the error code
// (AuthErrorCodeUnexpectedHTTPResponse, and is irrelevant.)
continuation.resume(
throwing: AuthErrorUtils.unexpectedErrorResponse(
data: data,
underlyingError: error
)
)
return
} else {
// This is supposed to be a "successful" response, but we couldn't
// deserialize the body.
continuation.resume(
throwing: AuthErrorUtils.unexpectedResponse(data: data, underlyingError: jsonError)
)
return
}
}
let (data, error) = await rpcIssuer
.asyncCallToURL(with: request, body: bodyData, contentType: "application/json")
// If there is an error with no body data at all, then this must be a
// network error.
guard let data = data else {
if let error = error {
throw AuthErrorUtils.networkError(underlyingError: error)
} else {
// TODO: this was ignored before
fatalError("Auth Internal error: RPC call didn't return data or an error.")
}
}
// Try to decode the HTTP response data which may contain either a
// successful response or error message.
var dictionary: [String: AnyHashable]
var rawDecode: Any
do {
rawDecode = try JSONSerialization.jsonObject(
with: data, options: JSONSerialization.ReadingOptions.mutableLeaves
)
} catch let jsonError {
if error != nil {
// We have an error, but we couldn't decode the body, so we have no
// additional information other than the raw response and the
// original NSError (the jsonError is inferred by the error code
// (AuthErrorCodeUnexpectedHTTPResponse, and is irrelevant.)
throw AuthErrorUtils.unexpectedErrorResponse(data: data, underlyingError: error)
} else {
// This is supposed to be a "successful" response, but we couldn't
// deserialize the body.
throw AuthErrorUtils.unexpectedResponse(data: data, underlyingError: jsonError)
}
}
guard let decodedDictionary = rawDecode as? [String: AnyHashable] else {
if error != nil {
throw AuthErrorUtils.unexpectedErrorResponse(deserializedResponse: rawDecode,
underlyingError: error)
} else {
throw AuthErrorUtils.unexpectedResponse(deserializedResponse: rawDecode)
}
}
dictionary = decodedDictionary

let response = T.Response()
let response = T.Response()

// At this point we either have an error with successfully decoded
// details in the body, or we have a response which must pass further
// validation before we know it's truly successful. We deal with the
// case where we have an error with successfully decoded error details
// first:
if error != nil {
if let errorDictionary = dictionary["error"] as? [String: AnyHashable] {
if let errorMessage = errorDictionary["message"] as? String {
if let clientError = AuthBackendRPCImplementation.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: errorDictionary,
response: response,
error: error
) {
continuation.resume(throwing: clientError)
return
}
}
// Not a message we know, return the message directly.
continuation.resume(
throwing: AuthErrorUtils.unexpectedErrorResponse(
deserializedResponse: errorDictionary,
underlyingError: error
)
)
return
}
// No error message at all, return the decoded response.
continuation.resume(
throwing: AuthErrorUtils
.unexpectedErrorResponse(deserializedResponse: dictionary, underlyingError: error)
)
return
// At this point we either have an error with successfully decoded
// details in the body, or we have a response which must pass further
// validation before we know it's truly successful. We deal with the
// case where we have an error with successfully decoded error details
// first:
if error != nil {
if let errorDictionary = dictionary["error"] as? [String: AnyHashable] {
if let errorMessage = errorDictionary["message"] as? String {
if let clientError = AuthBackendRPCImplementation.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: errorDictionary,
response: response,
error: error
) {
throw clientError
}
}
// Not a message we know, return the message directly.
throw AuthErrorUtils.unexpectedErrorResponse(
deserializedResponse: errorDictionary,
underlyingError: error
)
}
// No error message at all, return the decoded response.
throw AuthErrorUtils
.unexpectedErrorResponse(deserializedResponse: dictionary, underlyingError: error)
}

// Finally, we try to populate the response object with the JSON values.
do {
try response.setFields(dictionary: dictionary)
} catch {
continuation.resume(
throwing: AuthErrorUtils
.RPCResponseDecodingError(deserializedResponse: dictionary, underlyingError: error)
)
return
}
// In case returnIDPCredential of a verifyAssertion request is set to
// @YES, the server may return a 200 with a response that may contain a
// server error.
if let verifyAssertionRequest = request as? VerifyAssertionRequest {
if verifyAssertionRequest.returnIDPCredential {
if let errorMessage = dictionary["errorMessage"] as? String {
if let clientError = AuthBackendRPCImplementation.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: dictionary,
response: response,
error: error
) {
continuation.resume(throwing: clientError)
return
}
}
}
// Finally, we try to populate the response object with the JSON values.
do {
try response.setFields(dictionary: dictionary)
} catch {
throw AuthErrorUtils
.RPCResponseDecodingError(deserializedResponse: dictionary, underlyingError: error)
}
// In case returnIDPCredential of a verifyAssertion request is set to
// @YES, the server may return a 200 with a response that may contain a
// server error.
if let verifyAssertionRequest = request as? VerifyAssertionRequest {
if verifyAssertionRequest.returnIDPCredential {
if let errorMessage = dictionary["errorMessage"] as? String {
if let clientError = AuthBackendRPCImplementation.clientError(
withServerErrorMessage: errorMessage,
errorDictionary: dictionary,
response: response,
error: error
) {
throw clientError
}
continuation.resume(returning: response)
}
}
}
return response
}

private class func clientError(withServerErrorMessage serverErrorMessage: String,
Expand Down
Loading