diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift index 24726262840..58fb6c4be80 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift @@ -22,82 +22,61 @@ import Foundation #endif @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -protocol AuthBackendRPCIssuer { - /// Asynchronously send a HTTP request. - /// - Parameter request: The request to be made. - /// - Parameter body: Request body. - /// - Parameter contentType: Content type of the body. - /// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously - /// on the auth global work queue in the future. - func asyncCallToURL(with request: T, - body: Data?, - contentType: String) async -> (Data?, Error?) -} - -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackendRPCIssuerImplementation: AuthBackendRPCIssuer { - let fetcherService: GTMSessionFetcherService - - init() { - fetcherService = GTMSessionFetcherService() - fetcherService.userAgent = AuthBackend.authUserAgent() - fetcherService.callbackQueue = kAuthGlobalWorkQueue - - // Avoid reusing the session to prevent - // https://github.com/firebase/firebase-ios-sdk/issues/1261 - fetcherService.reuseSession = false - } - - func asyncCallToURL(with request: T, - body: Data?, - contentType: String) async -> (Data?, Error?) { - let requestConfiguration = request.requestConfiguration() - 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)) - } - } - } +protocol AuthBackendProtocol { + func call(with request: T) async throws -> T.Response } @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackend { +class AuthBackend: AuthBackendProtocol { static func authUserAgent() -> String { return "FirebaseAuth.iOS/\(FirebaseVersion()) \(GTMFetcherStandardUserAgentString(nil))" } - private static var realRPCBackend = AuthBackendRPCImplementation() - private static var gBackendImplementation = realRPCBackend + static func call(with request: T) async throws -> T.Response { + return try await shared.call(with: request) + } - class func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) { - gBackendImplementation.rpcIssuer = issuer + static func setTestRPCIssuer(issuer: AuthBackendRPCIssuer) { + shared.rpcIssuer = issuer } - class func resetRPCIssuer() { - gBackendImplementation.rpcIssuer = realRPCBackend.rpcIssuer + static func resetRPCIssuer() { + shared.rpcIssuer = AuthBackendRPCIssuer() } - class func implementation() -> AuthBackendImplementation { - return gBackendImplementation + private static let shared: AuthBackend = .init(rpcIssuer: AuthBackendRPCIssuer()) + + private var rpcIssuer: any AuthBackendRPCIssuerProtocol + + init(rpcIssuer: any AuthBackendRPCIssuerProtocol) { + self.rpcIssuer = rpcIssuer } - class func call(with request: T) async throws -> T.Response { - return try await implementation().call(with: request) + /// Calls the RPC using HTTP request. + /// Possible error responses: + /// * See FIRAuthInternalErrorCodeRPCRequestEncodingError + /// * See FIRAuthInternalErrorCodeJSONSerializationError + /// * See FIRAuthInternalErrorCodeNetworkError + /// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse + /// * See FIRAuthInternalErrorCodeUnexpectedResponse + /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError + /// - Parameter request: The request. + /// - Returns: The response. + func call(with request: T) async throws -> T.Response { + let response = try await callInternal(with: request) + if let auth = request.requestConfiguration().auth, + let mfaError = Self.generateMFAError(response: response, auth: auth) { + throw mfaError + } else if let error = Self.phoneCredentialInUse(response: response) { + throw error + } else { + return response + } } - class func request(withURL url: URL, - contentType: String, - requestConfiguration: AuthRequestConfiguration) async -> URLRequest { + static func request(withURL url: URL, + contentType: String, + requestConfiguration: AuthRequestConfiguration) async -> URLRequest { // Kick off tasks for the async header values. async let heartbeatsHeaderValue = requestConfiguration.heartbeatLogger?.asyncHeaderValue() async let appCheckTokenHeaderValue = requestConfiguration.appCheck? @@ -132,41 +111,11 @@ class AuthBackend { } return request } -} -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -protocol AuthBackendImplementation { - func call(with request: T) async throws -> T.Response -} - -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -private class AuthBackendRPCImplementation: AuthBackendImplementation { - var rpcIssuer: AuthBackendRPCIssuer = AuthBackendRPCIssuerImplementation() - - /// Calls the RPC using HTTP request. - /// Possible error responses: - /// * See FIRAuthInternalErrorCodeRPCRequestEncodingError - /// * See FIRAuthInternalErrorCodeJSONSerializationError - /// * See FIRAuthInternalErrorCodeNetworkError - /// * See FIRAuthInternalErrorCodeUnexpectedErrorResponse - /// * See FIRAuthInternalErrorCodeUnexpectedResponse - /// * See FIRAuthInternalErrorCodeRPCResponseDecodingError - /// - Parameter request: The request. - /// - Returns: The response. - fileprivate func call(with request: T) async throws -> T.Response { - let response = try await callInternal(with: request) - if let auth = request.requestConfiguration().auth, - let mfaError = Self.generateMFAError(response: response, auth: auth) { - throw mfaError - } else if let error = Self.phoneCredentialInUse(response: response) { - throw error - } else { - return response - } - } - - #if os(iOS) - private class func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? { + private static func generateMFAError(response: AuthRPCResponse, auth: Auth) -> Error? { + #if !os(iOS) + return nil + #else if let mfaResponse = response as? AuthMFAResponse, mfaResponse.idToken == nil, let enrollments = mfaResponse.mfaInfo { @@ -189,17 +138,15 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { } else { return nil } - } - #else - private class func generateMFAError(response: AuthRPCResponse, auth: Auth?) -> Error? { - return nil - } - #endif + #endif // !os(iOS) + } - #if os(iOS) - // Check whether or not the successful response is actually the special case phone - // auth flow that returns a temporary proof and phone number. - private class func phoneCredentialInUse(response: AuthRPCResponse) -> Error? { + // Check whether or not the successful response is actually the special case phone + // auth flow that returns a temporary proof and phone number. + private static func phoneCredentialInUse(response: AuthRPCResponse) -> Error? { + #if !os(iOS) + return nil + #else if let phoneAuthResponse = response as? VerifyPhoneNumberResponse, let phoneNumber = phoneAuthResponse.phoneNumber, phoneNumber.count > 0, @@ -214,12 +161,8 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { } else { return nil } - } - #else - private class func phoneCredentialInUse(response: AuthRPCResponse) -> Error? { - return nil - } - #endif + #endif // !os(iOS) + } /// Calls the RPC using HTTP request. /// @@ -308,7 +251,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { } dictionary = decodedDictionary - let response = T.Response() + var 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 @@ -318,7 +261,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { if error != nil { if let errorDictionary = dictionary["error"] as? [String: AnyHashable] { if let errorMessage = errorDictionary["message"] as? String { - if let clientError = AuthBackendRPCImplementation.clientError( + if let clientError = Self.clientError( withServerErrorMessage: errorMessage, errorDictionary: errorDictionary, response: response, @@ -351,7 +294,7 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { if let verifyAssertionRequest = request as? VerifyAssertionRequest { if verifyAssertionRequest.returnIDPCredential { if let errorMessage = dictionary["errorMessage"] as? String { - if let clientError = AuthBackendRPCImplementation.clientError( + if let clientError = Self.clientError( withServerErrorMessage: errorMessage, errorDictionary: dictionary, response: response, @@ -365,10 +308,10 @@ private class AuthBackendRPCImplementation: AuthBackendImplementation { return response } - private class func clientError(withServerErrorMessage serverErrorMessage: String, - errorDictionary: [String: Any], - response: AuthRPCResponse, - error: Error?) -> Error? { + private static func clientError(withServerErrorMessage serverErrorMessage: String, + errorDictionary: [String: Any], + response: AuthRPCResponse, + error: Error?) -> Error? { let split = serverErrorMessage.split(separator: ":") let shortErrorMessage = split.first?.trimmingCharacters(in: .whitespacesAndNewlines) let serverDetailErrorMessage = String(split.count > 1 ? split[1] : "") diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift b/FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift new file mode 100644 index 00000000000..01001c5d712 --- /dev/null +++ b/FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift @@ -0,0 +1,71 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import FirebaseCore +import FirebaseCoreExtension +import Foundation +#if COCOAPODS + import GTMSessionFetcher +#else + import GTMSessionFetcherCore +#endif + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +protocol AuthBackendRPCIssuerProtocol { + /// Asynchronously send a HTTP request. + /// - Parameter request: The request to be made. + /// - Parameter body: Request body. + /// - Parameter contentType: Content type of the body. + /// - Parameter completionHandler: Handles HTTP response. Invoked asynchronously + /// on the auth global work queue in the future. + func asyncCallToURL(with request: T, + body: Data?, + contentType: String) async -> (Data?, Error?) +} + +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class AuthBackendRPCIssuer: AuthBackendRPCIssuerProtocol { + let fetcherService: GTMSessionFetcherService + + init() { + fetcherService = GTMSessionFetcherService() + fetcherService.userAgent = AuthBackend.authUserAgent() + fetcherService.callbackQueue = kAuthGlobalWorkQueue + + // Avoid reusing the session to prevent + // https://github.com/firebase/firebase-ios-sdk/issues/1261 + fetcherService.reuseSession = false + } + + func asyncCallToURL(with request: T, + body: Data?, + contentType: String) async -> (Data?, Error?) { + let requestConfiguration = request.requestConfiguration() + 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)) + } + } + } +} diff --git a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift index b1284762fb2..0b7ea8a3a52 100644 --- a/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift @@ -14,7 +14,7 @@ import Foundation -protocol AuthRPCResponse { +protocol AuthRPCResponse: Sendable { /// Bare initializer for a response. init() @@ -22,7 +22,7 @@ protocol AuthRPCResponse { /// - Parameter dictionary: The dictionary decoded from HTTP JSON response. /// - Parameter error: An out field for an error which occurred constructing the request. /// - Returns: Whether the operation was successful or not. - func setFields(dictionary: [String: AnyHashable]) throws + mutating func setFields(dictionary: [String: AnyHashable]) throws /// This optional method allows response classes to create client errors given a short error /// message and a detail error message from the server. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift index de26c51ed3a..019a92f4b90 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift @@ -16,8 +16,7 @@ import Foundation /// Represents the parameters for the createAuthUri endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/createAuthUri - -class CreateAuthURIResponse: AuthRPCResponse { +struct CreateAuthURIResponse: AuthRPCResponse { /// The URI used by the IDP to authenticate the user. var authURI: String? @@ -36,10 +35,7 @@ class CreateAuthURIResponse: AuthRPCResponse { /// A list of sign-in methods available for the passed identifier. var signinMethods: [String] = [] - /// Bare initializer. - required init() {} - - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { providerID = dictionary["providerId"] as? String authURI = dictionary["authUri"] as? String registered = dictionary["registered"] as? Bool ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift index 868d36d25d2..4b6802dafb3 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift @@ -17,8 +17,6 @@ import Foundation /// Represents the response from the deleteAccount endpoint. /// /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/deleteAccount -class DeleteAccountResponse: AuthRPCResponse { - required init() {} - - func setFields(dictionary: [String: AnyHashable]) throws {} +struct DeleteAccountResponse: AuthRPCResponse { + mutating func setFields(dictionary: [String: AnyHashable]) throws {} } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift index 107c0859ac7..608c38237cc 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift @@ -15,9 +15,7 @@ import Foundation /// Represents the response from the emailLinkSignin endpoint. -class EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { - required init() {} - +struct EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { /// The ID token in the email link sign-in response. private(set) var idToken: String? @@ -42,7 +40,7 @@ class EmailLinkSignInResponse: AuthRPCResponse, AuthMFAResponse { /// Info on which multi-factor authentication providers are enabled. private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String idToken = dictionary["idToken"] as? String isNewUser = dictionary["isNewUser"] as? Bool ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift index 8b4ae17d627..e0f9c463959 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift @@ -14,143 +14,138 @@ import Foundation -/// The key for the "error" value in JSON responses from the server. -private let kErrorKey = "error" - -/// Represents the provider user info part of the response from the getAccountInfo endpoint. +/// Represents the response from the setAccountInfo endpoint. /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -class GetAccountInfoResponseProviderUserInfo { - /// The ID of the identity provider. - let providerID: String? - - /// The user's display name at the identity provider. - let displayName: String? - - /// The user's photo URL at the identity provider. - let photoURL: URL? - - /// The user's identifier at the identity provider. - let federatedID: String? - - /// The user's email at the identity provider. - let email: String? - - /// A phone number associated with the user. - let phoneNumber: String? - - /// Designated initializer. - /// - Parameter dictionary: The provider user info data from endpoint. - init(dictionary: [String: Any]) { - providerID = dictionary["providerId"] as? String - displayName = dictionary["displayName"] as? String - if let photoURL = dictionary["photoUrl"] as? String { - self.photoURL = URL(string: photoURL) - } else { - photoURL = nil +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +struct GetAccountInfoResponse: AuthRPCResponse { + /// Represents the provider user info part of the response from the getAccountInfo endpoint. + /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + struct ProviderUserInfo { + /// The ID of the identity provider. + let providerID: String? + + /// The user's display name at the identity provider. + let displayName: String? + + /// The user's photo URL at the identity provider. + let photoURL: URL? + + /// The user's identifier at the identity provider. + let federatedID: String? + + /// The user's email at the identity provider. + let email: String? + + /// A phone number associated with the user. + let phoneNumber: String? + + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. + init(dictionary: [String: Any]) { + providerID = dictionary["providerId"] as? String + displayName = dictionary["displayName"] as? String + if let photoURL = dictionary["photoUrl"] as? String { + self.photoURL = URL(string: photoURL) + } else { + photoURL = nil + } + federatedID = + dictionary["federatedId"] as? String + email = dictionary["email"] as? String + phoneNumber = dictionary["phoneNumber"] as? String } - federatedID = - dictionary["federatedId"] as? String - email = dictionary["email"] as? String - phoneNumber = dictionary["phoneNumber"] as? String } -} -/// Represents the firebase user info part of the response from the getAccountInfo endpoint. -/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -class GetAccountInfoResponseUser { - /// The ID of the user. - let localID: String? + /// Represents the firebase user info part of the response from the getAccountInfo endpoint. + /// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo + struct User { + /// The ID of the user. + let localID: String? - /// The email or the user. - let email: String? + /// The email or the user. + let email: String? - /// Whether the email has been verified. - let emailVerified: Bool + /// Whether the email has been verified. + let emailVerified: Bool - /// The display name of the user. - let displayName: String? + /// The display name of the user. + let displayName: String? - /// The user's photo URL. - let photoURL: URL? + /// The user's photo URL. + let photoURL: URL? - /// The user's creation date. - let creationDate: Date? + /// The user's creation date. + let creationDate: Date? - /// The user's last login date. - let lastLoginDate: Date? + /// The user's last login date. + let lastLoginDate: Date? - /// The user's profiles at the associated identity providers. - let providerUserInfo: [GetAccountInfoResponseProviderUserInfo]? + /// The user's profiles at the associated identity providers. + let providerUserInfo: [GetAccountInfoResponse.ProviderUserInfo]? - /// Information about user's password. - /// This is not necessarily the hash of user's actual password. - let passwordHash: String? + /// Information about user's password. + /// This is not necessarily the hash of user's actual password. + let passwordHash: String? - /// A phone number associated with the user. - let phoneNumber: String? + /// A phone number associated with the user. + let phoneNumber: String? - let mfaEnrollments: [AuthProtoMFAEnrollment]? + let mfaEnrollments: [AuthProtoMFAEnrollment]? - /// Designated initializer. - /// - Parameter dictionary: The provider user info data from endpoint. - init(dictionary: [String: Any]) { - if let providerUserInfoData = dictionary["providerUserInfo"] as? [[String: Any]] { - providerUserInfo = providerUserInfoData.map { - GetAccountInfoResponseProviderUserInfo(dictionary: $0) + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. + init(dictionary: [String: Any]) { + if let providerUserInfoData = dictionary["providerUserInfo"] as? [[String: Any]] { + providerUserInfo = providerUserInfoData + .map(GetAccountInfoResponse.ProviderUserInfo.init(dictionary:)) + } else { + providerUserInfo = nil + } + localID = dictionary["localId"] as? String + displayName = dictionary["displayName"] as? String + email = dictionary["email"] as? String + if let photoURL = dictionary["photoUrl"] as? String { + self.photoURL = URL(string: photoURL) + } else { + photoURL = nil + } + if let createdAt = dictionary["createdAt"] as? String, + let timeInterval = Double(createdAt) { + // Divide by 1000 in order to convert milliseconds to seconds. + creationDate = Date(timeIntervalSince1970: timeInterval / 1000) + } else { + creationDate = nil + } + if let lastLoginAt = dictionary["lastLoginAt"] as? String, + let timeInterval = Double(lastLoginAt) { + // Divide by 1000 in order to convert milliseconds to seconds. + lastLoginDate = Date(timeIntervalSince1970: timeInterval / 1000) + } else { + lastLoginDate = nil } - } else { - providerUserInfo = nil - } - localID = dictionary["localId"] as? String - displayName = dictionary["displayName"] as? String - email = dictionary["email"] as? String - if let photoURL = dictionary["photoUrl"] as? String { - self.photoURL = URL(string: photoURL) - } else { - photoURL = nil - } - if let createdAt = dictionary["createdAt"] as? String, - let timeInterval = Double(createdAt) { - // Divide by 1000 in order to convert milliseconds to seconds. - creationDate = Date(timeIntervalSince1970: timeInterval / 1000) - } else { - creationDate = nil - } - if let lastLoginAt = dictionary["lastLoginAt"] as? String, - let timeInterval = Double(lastLoginAt) { - // Divide by 1000 in order to convert milliseconds to seconds. - lastLoginDate = Date(timeIntervalSince1970: timeInterval / 1000) - } else { - lastLoginDate = nil - } - emailVerified = dictionary["emailVerified"] as? Bool ?? false - passwordHash = dictionary["passwordHash"] as? String - phoneNumber = dictionary["phoneNumber"] as? String - if let mfaEnrollmentData = dictionary["mfaInfo"] as? [[String: AnyHashable]] { - mfaEnrollments = mfaEnrollmentData.map { AuthProtoMFAEnrollment(dictionary: $0) + emailVerified = dictionary["emailVerified"] as? Bool ?? false + passwordHash = dictionary["passwordHash"] as? String + phoneNumber = dictionary["phoneNumber"] as? String + if let mfaEnrollmentData = dictionary["mfaInfo"] as? [[String: AnyHashable]] { + mfaEnrollments = mfaEnrollmentData.map { AuthProtoMFAEnrollment(dictionary: $0) + } + } else { + mfaEnrollments = nil } - } else { - mfaEnrollments = nil } } -} - -/// Represents the response from the setAccountInfo endpoint. -/// See https://developers.google.com/identity/toolkit/web/reference/relyingparty/getAccountInfo -@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class GetAccountInfoResponse: AuthRPCResponse { - required init() {} /// The requested users' profiles. - var users: [GetAccountInfoResponseUser]? - func setFields(dictionary: [String: AnyHashable]) throws { + var users: [Self.User]? + + mutating func setFields(dictionary: [String: AnyHashable]) throws { guard let usersData = dictionary["users"] as? [[String: AnyHashable]] else { throw AuthErrorUtils.unexpectedResponse(deserializedResponse: dictionary) } guard usersData.count == 1 else { throw AuthErrorUtils.unexpectedResponse(deserializedResponse: dictionary) } - users = [GetAccountInfoResponseUser(dictionary: usersData[0])] + users = [Self.User(dictionary: usersData[0])] } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift index 9a19848151a..91721fd659e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift @@ -16,12 +16,10 @@ import Foundation private let kOOBCodeKey = "oobCode" -class GetOOBConfirmationCodeResponse: AuthRPCResponse { - required init() {} - +struct GetOOBConfirmationCodeResponse: AuthRPCResponse { var OOBCode: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { OOBCode = dictionary[kOOBCodeKey] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift index fb5f282c18a..b9fb18771b7 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift @@ -14,14 +14,12 @@ import Foundation -class GetProjectConfigResponse: AuthRPCResponse { - required init() {} - +struct GetProjectConfigResponse: AuthRPCResponse { var projectID: String? var authorizedDomains: [String]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { projectID = dictionary["projectId"] as? String if let authorizedDomains = dictionary["authorizedDomains"] as? String, let data = authorizedDomains.data(using: .utf8) { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift index 9331670db86..b62bd84a695 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift @@ -14,13 +14,11 @@ import Foundation -class GetRecaptchaConfigResponse: AuthRPCResponse { - required init() {} - +struct GetRecaptchaConfigResponse: AuthRPCResponse { private(set) var recaptchaKey: String? private(set) var enforcementState: [[String: String]]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { recaptchaKey = dictionary["recaptchaKey"] as? String enforcementState = dictionary["recaptchaEnforcementState"] as? [[String: String]] } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift index af4bd6c7658..bdd3ce89d31 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift @@ -15,15 +15,13 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FinalizeMFAEnrollmentResponse: AuthRPCResponse { - required init() {} - +struct FinalizeMFAEnrollmentResponse: AuthRPCResponse { private(set) var idToken: String? private(set) var refreshToken: String? private(set) var phoneSessionInfo: AuthProtoFinalizeMFAPhoneResponseInfo? private(set) var totpSessionInfo: AuthProtoFinalizeMFATOTPEnrollmentResponseInfo? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift index 158be37c33d..dadbb9a15be 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift @@ -15,13 +15,11 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StartMFAEnrollmentResponse: AuthRPCResponse { - required init() {} - +struct StartMFAEnrollmentResponse: AuthRPCResponse { private(set) var phoneSessionInfo: AuthProtoStartMFAPhoneResponseInfo? private(set) var totpSessionInfo: AuthProtoStartMFATOTPEnrollmentResponseInfo? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { if let data = dictionary["phoneSessionInfo"] as? [String: AnyHashable] { phoneSessionInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data) } else if let data = dictionary["totpSessionInfo"] as? [String: AnyHashable] { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift index c93f0817b27..032dab3ff86 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift @@ -15,13 +15,11 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class FinalizeMFASignInResponse: AuthRPCResponse { - required init() {} - +struct FinalizeMFASignInResponse: AuthRPCResponse { var IDToken: String? var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { IDToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift index a2b3399d03e..cc597172010 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift @@ -15,12 +15,10 @@ import Foundation @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class StartMFASignInResponse: AuthRPCResponse { - required init() {} - +struct StartMFASignInResponse: AuthRPCResponse { var responseInfo: AuthProtoStartMFAPhoneResponseInfo? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { if let data = dictionary["phoneResponseInfo"] as? [String: AnyHashable] { responseInfo = AuthProtoStartMFAPhoneResponseInfo(dictionary: data) } else { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift index 38d0e9b9aad..4744a6965ee 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift @@ -14,13 +14,11 @@ import Foundation -class WithdrawMFAResponse: AuthRPCResponse { - required init() {} - +struct WithdrawMFAResponse: AuthRPCResponse { var idToken: String? var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift index e8356c98375..8983e54dfac 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift @@ -14,7 +14,6 @@ import Foundation -@objc protocol AuthProto: NSObjectProtocol { +protocol AuthProto { init(dictionary: [String: AnyHashable]) - @objc optional var dictionary: [String: AnyHashable] { get } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift index 494252ac56e..d13e34112f9 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift @@ -14,18 +14,22 @@ import Foundation -class AuthProtoMFAEnrollment: NSObject, AuthProto { +struct AuthProtoMFAEnrollment: AuthProto { let phoneInfo: String? // In practice, this will be an empty dictionary. The presence of which // indicates TOTP MFA enrollment rather than phone MFA enrollment. - let totpInfo: NSObject? + let totpInfo: String? let mfaEnrollmentID: String? let displayName: String? let enrolledAt: Date? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { phoneInfo = dictionary["phoneInfo"] as? String - totpInfo = dictionary["totpInfo"] as? NSObject + if let _ = dictionary["totpInfo"] as? NSObject { + totpInfo = "" + } else { + totpInfo = nil + } mfaEnrollmentID = dictionary["mfaEnrollmentId"] as? String displayName = dictionary["displayName"] as? String if let enrolledAt = dictionary["enrolledAt"] as? String { diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift index 2274e2826ea..690e998b27e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift @@ -14,10 +14,10 @@ import Foundation -class AuthProtoFinalizeMFAPhoneResponseInfo: NSObject, AuthProto { +struct AuthProtoFinalizeMFAPhoneResponseInfo: AuthProto { var phoneNumber: String? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { phoneNumber = dictionary["phoneNumber"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift index c888b2e2fff..bd1c9a06d08 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift @@ -14,10 +14,10 @@ import Foundation -class AuthProtoStartMFAPhoneResponseInfo: NSObject, AuthProto { +struct AuthProtoStartMFAPhoneResponseInfo: AuthProto { var sessionInfo: String? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { sessionInfo = dictionary["sessionInfo"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift index 825d15aef78..1dfae6f0cb1 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift @@ -14,7 +14,7 @@ import Foundation -class AuthProtoStartMFATOTPEnrollmentResponseInfo: NSObject, AuthProto { +struct AuthProtoStartMFATOTPEnrollmentResponseInfo: AuthProto { let sharedSecretKey: String let verificationCodeLength: Int let hashingAlgorithm: String? @@ -22,7 +22,7 @@ class AuthProtoStartMFATOTPEnrollmentResponseInfo: NSObject, AuthProto { let sessionInfo: String? let finalizeEnrollmentTime: Date? - required init(dictionary: [String: AnyHashable]) { + init(dictionary: [String: AnyHashable]) { guard let key = dictionary["sharedSecretKey"] as? String else { fatalError("Missing sharedSecretKey for AuthProtoStartMFATOTPEnrollmentResponseInfo") } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift index 88096a9f1a5..4c9d03d018c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift @@ -14,10 +14,9 @@ import Foundation -class AuthProtoFinalizeMFATOTPEnrollmentResponseInfo: NSObject, AuthProto { +// TODO(ncooke3): This implementation looks useless? +struct AuthProtoFinalizeMFATOTPEnrollmentResponseInfo: AuthProto { var sessionInfo: String? - required init(dictionary: [String: AnyHashable]) { - super.init() - } + init(dictionary: [String: AnyHashable]) {} } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift index fcfb619732e..22cb703e5ad 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift @@ -24,9 +24,7 @@ import Foundation /// * FIRAuthErrorCodeInvalidActionCode /// /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/resetPassword -class ResetPasswordResponse: AuthRPCResponse { - required init() {} - +struct ResetPasswordResponse: AuthRPCResponse { /// The email address corresponding to the reset password request. var email: String? @@ -36,7 +34,7 @@ class ResetPasswordResponse: AuthRPCResponse { /// The type of request as returned by the backend. var requestType: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String requestType = dictionary["requestType"] as? String verifiedEmail = dictionary["newEmail"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift index 081d1917550..7e6bf0e7378 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift @@ -14,10 +14,8 @@ import Foundation -class RevokeTokenResponse: AuthRPCResponse { - required init() {} - - func setFields(dictionary: [String: AnyHashable]) throws { +struct RevokeTokenResponse: AuthRPCResponse { + mutating func setFields(dictionary: [String: AnyHashable]) throws { // Nothing to set or throw. } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift index e7fa5ee57e5..c07d60c7a60 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift @@ -59,7 +59,7 @@ private var gAPIHost = "securetoken.googleapis.com" /// Represents the parameters for the token endpoint. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SecureTokenRequest: AuthRPCRequest { +struct SecureTokenRequest: AuthRPCRequest { typealias Response = SecureTokenResponse /// The type of grant requested. diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift index f16bd573da0..86291a60015 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift @@ -29,9 +29,7 @@ private let kAccessTokenKey = "access_token" private let kIDTokenKey = "id_token" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class SecureTokenResponse: AuthRPCResponse { - required init() {} - +struct SecureTokenResponse: AuthRPCResponse { var approximateExpirationDate: Date? var refreshToken: String? var accessToken: String? @@ -39,7 +37,7 @@ class SecureTokenResponse: AuthRPCResponse { var expectedKind: String? { nil } - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { refreshToken = dictionary[kRefreshTokenKey] as? String self.accessToken = dictionary[kAccessTokenKey] as? String idToken = dictionary[kIDTokenKey] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift index a532806c005..f9e6872cfcb 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift @@ -14,12 +14,10 @@ import Foundation -class SendVerificationCodeResponse: AuthRPCResponse { - required init() {} - +struct SendVerificationCodeResponse: AuthRPCResponse { var verificationID: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { verificationID = dictionary["sessionInfo"] as? String } } diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift index 4990e0dc0a0..c1b932b83c2 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift @@ -14,33 +14,31 @@ import Foundation -/// Represents the provider user info part of the response from the setAccountInfo endpoint. +/// Represents the response from the setAccountInfo endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo -class SetAccountInfoResponseProviderUserInfo { - /// The ID of the identity provider. - var providerID: String? +struct SetAccountInfoResponse: AuthRPCResponse { + /// Represents the provider user info part of the response from the setAccountInfo endpoint. + /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo + struct ProviderUserInfo { + /// The ID of the identity provider. + var providerID: String? - /// The user's display name at the identity provider. - var displayName: String? + /// The user's display name at the identity provider. + var displayName: String? - /// The user's photo URL at the identity provider. - var photoURL: URL? + /// The user's photo URL at the identity provider. + var photoURL: URL? - /// Designated initializer. - /// - Parameter dictionary: The provider user info data from endpoint. - init(dictionary: [String: Any]) { - providerID = dictionary["providerId"] as? String - displayName = dictionary["displayName"] as? String - if let photoURL = dictionary["photoUrl"] as? String { - self.photoURL = URL(string: photoURL) + /// Designated initializer. + /// - Parameter dictionary: The provider user info data from endpoint. + init(dictionary: [String: Any]) { + providerID = dictionary["providerId"] as? String + displayName = dictionary["displayName"] as? String + if let photoURL = dictionary["photoUrl"] as? String { + self.photoURL = URL(string: photoURL) + } } } -} - -/// Represents the response from the setAccountInfo endpoint. -/// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/setAccountInfo -class SetAccountInfoResponse: AuthRPCResponse { - required init() {} /// The email or the user. var email: String? @@ -49,7 +47,7 @@ class SetAccountInfoResponse: AuthRPCResponse { var displayName: String? /// The user's profiles at the associated identity providers. - var providerUserInfo: [SetAccountInfoResponseProviderUserInfo]? + var providerUserInfo: [Self.ProviderUserInfo]? /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set @@ -62,7 +60,7 @@ class SetAccountInfoResponse: AuthRPCResponse { /// The refresh token from Secure Token Service. var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { email = dictionary["email"] as? String displayName = dictionary["displayName"] as? String idToken = dictionary["idToken"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift index 75d9d5ce1dd..d291384ff49 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift @@ -14,9 +14,7 @@ import Foundation -class SignInWithGameCenterResponse: AuthRPCResponse { - required init() {} - +struct SignInWithGameCenterResponse: AuthRPCResponse { var idToken: String? var refreshToken: String? var localID: String? @@ -27,7 +25,7 @@ class SignInWithGameCenterResponse: AuthRPCResponse { var isNewUser: Bool = false var displayName: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift index fe6c8d9556e..7b25c6a6f8c 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift @@ -14,9 +14,7 @@ import Foundation -class SignUpNewUserResponse: AuthRPCResponse { - required init() {} - +struct SignUpNewUserResponse: AuthRPCResponse { /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set /// on the request. @@ -28,7 +26,7 @@ class SignUpNewUserResponse: AuthRPCResponse { /// The refresh token from Secure Token Service. var refreshToken: String? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String if let approximateExpirationDate = dictionary["expiresIn"] as? String { self diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift index 0f3aafe66b7..9cf822cae0e 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift @@ -16,10 +16,7 @@ import Foundation /// Represents the response from the verifyAssertion endpoint. /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyAssertion -class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { - required init() {} - - /// The unique ID identifies the IdP account. +struct VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { var federatedID: String? /// The IdP ID. For white listed IdPs it's a short domain name e.g. google.com, aol.com, @@ -109,7 +106,7 @@ class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { var isNewUser: Bool = false /// Dictionary containing the additional IdP specific information. - var profile: [String: Any]? + var profile: [String: Sendable]? /// The name of the user. var username: String? @@ -135,7 +132,7 @@ class VerifyAssertionResponse: AuthRPCResponse, AuthMFAResponse { private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { federatedID = dictionary["federatedId"] as? String providerID = dictionary["providerId"] as? String localID = dictionary["localId"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift index 74053b5f3b5..5c66bf6ba74 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift @@ -15,9 +15,7 @@ import Foundation /// Represents the response from the verifyCustomToken endpoint. -class VerifyCustomTokenResponse: AuthRPCResponse { - required init() {} - +struct VerifyCustomTokenResponse: AuthRPCResponse { /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set /// on the request. @@ -32,7 +30,7 @@ class VerifyCustomTokenResponse: AuthRPCResponse { /// Flag indicating that the user signing in is a new user and not a returning user. var isNewUser: Bool = false - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String if let dateString = dictionary["expiresIn"] as? NSString { approximateExpirationDate = Date(timeIntervalSinceNow: dateString.doubleValue) diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift index 7735450ed76..ba93ac1e0a5 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift @@ -21,9 +21,7 @@ import Foundation /// * FIRAuthInternalErrorCodeEmailNotFound /// /// See https: // developers.google.com/identity/toolkit/web/reference/relyingparty/verifyPassword -class VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { - required init() {} - +struct VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { /// The RP local ID if it's already been mapped to the IdP account identified by the federated ID. var localID: String? @@ -53,7 +51,7 @@ class VerifyPasswordResponse: AuthRPCResponse, AuthMFAResponse { private(set) var mfaInfo: [AuthProtoMFAEnrollment]? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { localID = dictionary["localId"] as? String email = dictionary["email"] as? String displayName = dictionary["displayName"] as? String diff --git a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift index 82325a25fbe..b5efb964c73 100644 --- a/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift @@ -14,9 +14,7 @@ import Foundation -class VerifyPhoneNumberResponse: AuthRPCResponse { - required init() {} - +struct VerifyPhoneNumberResponse: AuthRPCResponse { /// Either an authorization code suitable for performing an STS token exchange, or the /// access token from Secure Token Service, depending on whether `returnSecureToken` is set /// on the request. @@ -45,7 +43,7 @@ class VerifyPhoneNumberResponse: AuthRPCResponse { nil } - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { idToken = dictionary["idToken"] as? String refreshToken = dictionary["refreshToken"] as? String isNewUser = (dictionary["isNewUser"] as? Bool) ?? false diff --git a/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift b/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift index 36508691d8d..683ed33a01f 100644 --- a/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift +++ b/FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift @@ -14,8 +14,8 @@ import Foundation -class VerifyClientResponse: AuthRPCResponse { - required init() {} +struct VerifyClientResponse: AuthRPCResponse { + init() {} /// Receipt that the APNS token was successfully validated with APNS. private(set) var receipt: String? @@ -23,7 +23,7 @@ class VerifyClientResponse: AuthRPCResponse { /// The date after which delivery of the silent push notification is considered to have failed. private(set) var suggestedTimeOutDate: Date? - func setFields(dictionary: [String: AnyHashable]) throws { + mutating func setFields(dictionary: [String: AnyHashable]) throws { receipt = dictionary["receipt"] as? String let suggestedTimeout = dictionary["suggestedTimeout"] if let string = suggestedTimeout as? String, diff --git a/FirebaseAuth/Sources/Swift/User/User.swift b/FirebaseAuth/Sources/Swift/User/User.swift index 02cdeb8f979..0100fedfdc6 100644 --- a/FirebaseAuth/Sources/Swift/User/User.swift +++ b/FirebaseAuth/Sources/Swift/User/User.swift @@ -1229,7 +1229,7 @@ extension User: NSSecureCoding {} /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest` /// - Parameter callback: A block to invoke when the change is complete. Invoked asynchronously on /// the auth global work queue in the future. - func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponseUser, + func executeUserUpdateWithChanges(changeBlock: @escaping (GetAccountInfoResponse.User, SetAccountInfoRequest) -> Void, callback: @escaping (Error?) -> Void) { Task { @@ -1250,7 +1250,7 @@ extension User: NSSecureCoding {} /// Gets the users' account data from the server, updating our local values. /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an /// error has been detected. Invoked asynchronously on the auth global work queue in the future. - func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponseUser?, + func getAccountInfoRefreshingCache(callback: @escaping (GetAccountInfoResponse.User?, Error?) -> Void) { Task { do { diff --git a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift index 6f19622ddcb..a1fe938ae03 100644 --- a/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift +++ b/FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift @@ -14,18 +14,21 @@ import Foundation +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) extension UserInfoImpl: NSSecureCoding {} -@objc(FIRUserInfoImpl) class UserInfoImpl: NSObject, UserInfo { +@available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) +class UserInfoImpl: NSObject, UserInfo { /// A convenience factory method for constructing a `UserInfo` instance from data /// returned by the getAccountInfo endpoint. /// - Parameter providerUserInfo: Data returned by the getAccountInfo endpoint. /// - Returns: A new instance of `UserInfo` using data from the getAccountInfo endpoint. - class func userInfo(withGetAccountInfoResponseProviderUserInfo providerUserInfo: GetAccountInfoResponseProviderUserInfo) + class func userInfo(withGetAccountInfoResponseProviderUserInfo providerUserInfo: GetAccountInfoResponse + .ProviderUserInfo) -> UserInfoImpl { guard let providerID = providerUserInfo.providerID else { // This was a crash in ObjC implementation. Should providerID be not nullable? - fatalError("Missing providerID from GetAccountInfoResponseProviderUserInfo") + fatalError("Missing providerID from GetAccountInfoResponse.ProviderUserInfo") } return UserInfoImpl(withProviderID: providerID, userID: providerUserInfo.federatedID ?? "", diff --git a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift index 2d561246557..74aa5cf5c73 100644 --- a/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift +++ b/FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift @@ -101,7 +101,7 @@ actor UserProfileUpdate { /// atomically in regards to other calls to this method. /// - Parameter changeBlock: A block responsible for mutating a template `SetAccountInfoRequest` func executeUserUpdateWithChanges(user: User, - changeBlock: @escaping (GetAccountInfoResponseUser, + changeBlock: @escaping (GetAccountInfoResponse.User, SetAccountInfoRequest) -> Void) async throws { let userAccountInfo = try await getAccountInfoRefreshingCache(user) @@ -179,7 +179,7 @@ actor UserProfileUpdate { /// - Parameter callback: Invoked when the request to getAccountInfo has completed, or when an /// error has been detected. Invoked asynchronously on the auth global work queue in the future. func getAccountInfoRefreshingCache(_ user: User) async throws - -> GetAccountInfoResponseUser { + -> GetAccountInfoResponse.User { let token = try await user.internalGetTokenAsync() let request = GetAccountInfoRequest(accessToken: token, requestConfiguration: user.requestConfiguration) diff --git a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift similarity index 95% rename from FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift rename to FirebaseAuth/Tests/Unit/AuthBackendTests.swift index 7180313df34..a7a484a7d5d 100644 --- a/FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift +++ b/FirebaseAuth/Tests/Unit/AuthBackendTests.swift @@ -27,7 +27,7 @@ private let kFakeAPIKey = "kTestAPIKey" private let kFakeAppID = "kTestFirebaseAppID" @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *) -class AuthBackendRPCImplementationTests: RPCBaseTests { +class AuthBackendTests: RPCBaseTests { let kFakeErrorDomain = "fakeDomain" let kFakeErrorCode = -1 @@ -41,7 +41,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let request = FakeRequest(withEncodingError: encodingError) do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -71,7 +71,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { func testBodyDataSerializationError() async throws { let request = FakeRequest(withRequestBody: ["unencodable": self]) do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -99,7 +99,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: nil, error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -131,7 +131,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -168,7 +168,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: nil) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -210,7 +210,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -252,7 +252,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(withData: data, error: nil) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -287,7 +287,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -329,7 +329,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { ) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -356,7 +356,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -398,7 +398,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -436,7 +436,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { let _ = try self.rpcIssuer.respond(withJSON: [:], error: responseError) } do { - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -473,7 +473,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { try self.rpcIssuer.respond(serverErrorMessage: customErrorMessage, error: responseError) } do { - let _ = try await rpcImplementation.call(with: FakeRequest(withRequestBody: [:])) + let _ = try await AuthBackend.call(with: FakeRequest(withRequestBody: [:])) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -497,7 +497,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { } do { let request = FakeDecodingErrorRequest(withRequestBody: [:]) - let _ = try await rpcImplementation.call(with: request) + let _ = try await AuthBackend.call(with: request) XCTFail("Expected to throw") } catch { let rpcError = error as NSError @@ -528,8 +528,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // value it was given. try self.rpcIssuer.respond(withJSON: [kTestKey: kTestValue]) } - let rpcResponse = try await rpcImplementation.call(with: FakeRequest(withRequestBody: [:])) - XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedDictionary[kTestKey] as? String), kTestValue) + let rpcResponse = try await AuthBackend.call(with: FakeRequest(withRequestBody: [:])) + XCTAssertEqual(try XCTUnwrap(rpcResponse.receivedValue), kTestValue) } #if COCOAPODS || SWIFT_PACKAGE @@ -593,7 +593,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // Force return from async post try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await rpcImplementation.call(with: request) + _ = try? await AuthBackend.call(with: request) // Then let expectedHeader = HeartbeatLoggingTestUtils.nonEmptyHeartbeatsPayload.headerValue() @@ -620,7 +620,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // Just force return from async call. try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await rpcImplementation.call(with: request) + _ = try? await AuthBackend.call(with: request) let completeRequest = await rpcIssuer.completeRequest.value let headerValue = completeRequest.value(forHTTPHeaderField: "X-Firebase-AppCheck") @@ -650,7 +650,7 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { // Force return from async post try self.rpcIssuer.respond(withJSON: [:]) } - _ = try? await rpcImplementation.call(with: request) + _ = try? await AuthBackend.call(with: request) // Then let completeRequest = await rpcIssuer.completeRequest.value @@ -711,12 +711,10 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { } } - private class FakeResponse: AuthRPCResponse { - required init() {} - - var receivedDictionary: [String: AnyHashable] = [:] - func setFields(dictionary: [String: AnyHashable]) throws { - receivedDictionary = dictionary + private struct FakeResponse: AuthRPCResponse { + var receivedValue: String? + mutating func setFields(dictionary: [String: AnyHashable]) throws { + receivedValue = dictionary["TestKey"] as? String } } @@ -740,10 +738,8 @@ class AuthBackendRPCImplementationTests: RPCBaseTests { } } - private class FakeDecodingErrorResponse: FakeResponse { - required init() {} - - override func setFields(dictionary: [String: AnyHashable]) throws { + private struct FakeDecodingErrorResponse: AuthRPCResponse { + mutating func setFields(dictionary: [String: AnyHashable]) throws { throw NSError(domain: "dummy", code: -1) } } diff --git a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift index f1b4cee7a5d..e09e8d2106c 100644 --- a/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift +++ b/FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift @@ -77,8 +77,8 @@ class FakeBackendRPCIssuer: AuthBackendRPCIssuer { var secureTokenErrorString: String? var recaptchaSiteKey = "unset recaptcha siteKey" - func asyncCallToURL(with request: T, body: Data?, - contentType: String) async -> (Data?, Error?) + override func asyncCallToURL(with request: T, body: Data?, + contentType: String) async -> (Data?, Error?) where T: FirebaseAuth.AuthRPCRequest { return await withCheckedContinuation { continuation in self.asyncCallToURL(with: request, body: body, contentType: contentType) { data, error in diff --git a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift index 567b3aa4bbd..009289a1b44 100644 --- a/FirebaseAuth/Tests/Unit/RPCBaseTests.swift +++ b/FirebaseAuth/Tests/Unit/RPCBaseTests.swift @@ -68,12 +68,10 @@ class RPCBaseTests: XCTestCase { let kTestIdentifier = "Identifier" var rpcIssuer: FakeBackendRPCIssuer! - var rpcImplementation: AuthBackendImplementation! override func setUp() { rpcIssuer = FakeBackendRPCIssuer() AuthBackend.setTestRPCIssuer(issuer: rpcIssuer) - rpcImplementation = AuthBackend.implementation() } override func tearDown() { diff --git a/FirebaseCore/Extension/FIRHeartbeatLogger.h b/FirebaseCore/Extension/FIRHeartbeatLogger.h index 807aea22062..95497d2a689 100644 --- a/FirebaseCore/Extension/FIRHeartbeatLogger.h +++ b/FirebaseCore/Extension/FIRHeartbeatLogger.h @@ -33,6 +33,7 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) { FIRDailyHeartbeatCodeSome = 2, }; +NS_SWIFT_SENDABLE @protocol FIRHeartbeatLoggerProtocol /// Asynchronously logs a heartbeat.