Bläddra i källkod

[Auth] Convert *Response classes to structs (#14012)

Nick Cooke 1 år sedan
förälder
incheckning
cdaa05ff88
41 ändrade filer med 363 tillägg och 401 borttagningar
  1. 60 117
      FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift
  2. 71 0
      FirebaseAuth/Sources/Swift/Backend/AuthBackendRPCIssuer.swift
  3. 2 2
      FirebaseAuth/Sources/Swift/Backend/AuthRPCResponse.swift
  4. 2 6
      FirebaseAuth/Sources/Swift/Backend/RPC/CreateAuthURIResponse.swift
  5. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/DeleteAccountResponse.swift
  6. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/EmailLinkSignInResponse.swift
  7. 105 110
      FirebaseAuth/Sources/Swift/Backend/RPC/GetAccountInfoResponse.swift
  8. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/GetOOBConfirmationCodeResponse.swift
  9. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/GetProjectConfigResponse.swift
  10. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/GetRecaptchaConfigResponse.swift
  11. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/FinalizeMFAEnrollmentResponse.swift
  12. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Enroll/StartMFAEnrollmentResponse.swift
  13. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/FinalizeMFASignInResponse.swift
  14. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/SignIn/StartMFASignInResponse.swift
  15. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/MultiFactor/Unenroll/WithdrawMFAResponse.swift
  16. 1 2
      FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProto.swift
  17. 8 4
      FirebaseAuth/Sources/Swift/Backend/RPC/Proto/AuthProtoMFAEnrollment.swift
  18. 2 2
      FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoFinalizeMFAPhoneResponseInfo.swift
  19. 2 2
      FirebaseAuth/Sources/Swift/Backend/RPC/Proto/Phone/AuthProtoStartMFAPhoneResponseInfo.swift
  20. 2 2
      FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoFinalizeMFATOTPEnrollmentResponseInfo.swift
  21. 3 4
      FirebaseAuth/Sources/Swift/Backend/RPC/Proto/TOTP/AuthProtoStartMFATOTPEnrollmentResponseInfo.swift
  22. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/ResetPasswordResponse.swift
  23. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/RevokeTokenResponse.swift
  24. 1 1
      FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenRequest.swift
  25. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/SecureTokenResponse.swift
  26. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/SendVerificationTokenResponse.swift
  27. 21 23
      FirebaseAuth/Sources/Swift/Backend/RPC/SetAccountInfoResponse.swift
  28. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/SignInWithGameCenterResponse.swift
  29. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/SignUpNewUserResponse.swift
  30. 3 6
      FirebaseAuth/Sources/Swift/Backend/RPC/VerifyAssertionResponse.swift
  31. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/VerifyCustomTokenResponse.swift
  32. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPasswordResponse.swift
  33. 2 4
      FirebaseAuth/Sources/Swift/Backend/RPC/VerifyPhoneNumberResponse.swift
  34. 3 3
      FirebaseAuth/Sources/Swift/Backend/VerifyClientResponse.swift
  35. 2 2
      FirebaseAuth/Sources/Swift/User/User.swift
  36. 6 3
      FirebaseAuth/Sources/Swift/User/UserInfoImpl.swift
  37. 2 2
      FirebaseAuth/Sources/Swift/User/UserProfileUpdate.swift
  38. 26 30
      FirebaseAuth/Tests/Unit/AuthBackendTests.swift
  39. 2 2
      FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift
  40. 0 2
      FirebaseAuth/Tests/Unit/RPCBaseTests.swift
  41. 1 0
      FirebaseCore/Extension/FIRHeartbeatLogger.h

+ 60 - 117
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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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] : "")

+ 71 - 0
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<T: AuthRPCRequest>(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<T: AuthRPCRequest>(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))
+      }
+    }
+  }
+}

+ 2 - 2
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.

+ 2 - 6
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

+ 2 - 4
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 {}
 }

+ 2 - 4
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

+ 105 - 110
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])]
   }
 }

+ 2 - 4
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
   }
 }

+ 2 - 4
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) {

+ 2 - 4
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]]
   }

+ 2 - 4
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
 

+ 2 - 4
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] {

+ 2 - 4
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
   }

+ 2 - 4
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 {

+ 2 - 4
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
   }

+ 1 - 2
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 }
 }

+ 8 - 4
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 {

+ 2 - 2
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
   }
 }

+ 2 - 2
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
   }
 }

+ 2 - 2
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")
     }

+ 3 - 4
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]) {}
 }

+ 2 - 4
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

+ 2 - 4
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.
   }
 }

+ 1 - 1
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.

+ 2 - 4
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

+ 2 - 4
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
   }
 }

+ 21 - 23
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

+ 2 - 4
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

+ 2 - 4
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

+ 3 - 6
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

+ 2 - 4
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)

+ 2 - 4
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

+ 2 - 4
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

+ 3 - 3
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,

+ 2 - 2
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 {

+ 6 - 3
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 ?? "",

+ 2 - 2
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)

+ 26 - 30
FirebaseAuth/Tests/Unit/AuthBackendRPCImplementationTests.swift → 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)
     }
   }

+ 2 - 2
FirebaseAuth/Tests/Unit/Fakes/FakeBackendRPCIssuer.swift

@@ -77,8 +77,8 @@ class FakeBackendRPCIssuer: AuthBackendRPCIssuer {
   var secureTokenErrorString: String?
   var recaptchaSiteKey = "unset recaptcha siteKey"
 
-  func asyncCallToURL<T>(with request: T, body: Data?,
-                         contentType: String) async -> (Data?, Error?)
+  override func asyncCallToURL<T>(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

+ 0 - 2
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() {

+ 1 - 0
FirebaseCore/Extension/FIRHeartbeatLogger.h

@@ -33,6 +33,7 @@ typedef NS_ENUM(NSInteger, FIRDailyHeartbeatCode) {
   FIRDailyHeartbeatCodeSome = 2,
 };
 
+NS_SWIFT_SENDABLE
 @protocol FIRHeartbeatLoggerProtocol <NSObject>
 
 /// Asynchronously logs a heartbeat.