فهرست منبع

[auth-swift] Styling #10819

Paul Beusterien 3 سال پیش
والد
کامیت
73df2f111b

+ 1 - 1
FirebaseAuth/Sources/Auth/FIRAuthGlobalWorkQueue.m

@@ -20,7 +20,7 @@
 NS_ASSUME_NONNULL_BEGIN
 
 dispatch_queue_t FIRAuthGlobalWorkQueue(void) {
-    return [FIRAuthGlobalWorkQueueWrapper queue];
+  return [FIRAuthGlobalWorkQueueWrapper queue];
 }
 
 NS_ASSUME_NONNULL_END

+ 11 - 4
FirebaseAuth/Sources/Swift/Auth/AuthGlobalWorkQueue.swift

@@ -1,9 +1,16 @@
+// Copyright 2023 Google LLC
 //
-//  File.swift
-//  
+// 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
 //
-//  Created by Morten Bek Ditlevsen on 13/02/2023.
+//      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 Foundation
 
@@ -12,5 +19,5 @@ internal let kAuthGlobalWorkQueue = DispatchQueue(label: "com.google.firebase.au
 // TODO: Hack to allow Obj-C to get a hold of this
 
 @objc public class FIRAuthGlobalWorkQueueWrapper: NSObject {
-    @objc public static var queue: DispatchQueue = kAuthGlobalWorkQueue
+  @objc public static var queue: DispatchQueue = kAuthGlobalWorkQueue
 }

+ 29 - 18
FirebaseAuth/Sources/Swift/Auth/AuthSerialTaskQueue.swift

@@ -1,29 +1,40 @@
+// Copyright 2023 Google LLC
 //
-//  File.swift
-//  
+// 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
 //
-//  Created by Morten Bek Ditlevsen on 13/02/2023.
+//      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 Foundation
 
 public typealias FIRAuthSerialTaskCompletionBlock = () -> Void
-public typealias FIRAuthSerialTask = (_ complete: @escaping FIRAuthSerialTaskCompletionBlock) -> Void
+public typealias FIRAuthSerialTask = (_ complete: @escaping FIRAuthSerialTaskCompletionBlock)
+  -> Void
 
 @objc(FIRAuthSerialTaskQueue) public class AuthSerialTaskQueue: NSObject {
-    private let dispatchQueue: DispatchQueue
-    
-    @objc public override init() {
-        self.dispatchQueue = DispatchQueue(label: "com.google.firebase.auth.serialTaskQueue", target: kAuthGlobalWorkQueue)
-        super.init()
-    }
-    
-    @objc public func enqueueTask(_ task: @escaping FIRAuthSerialTask) {
-        dispatchQueue.async {
-            self.dispatchQueue.suspend()
-            task {
-                self.dispatchQueue.resume()
-            }
-        }
+  private let dispatchQueue: DispatchQueue
+
+  @objc override public init() {
+    dispatchQueue = DispatchQueue(
+      label: "com.google.firebase.auth.serialTaskQueue",
+      target: kAuthGlobalWorkQueue
+    )
+    super.init()
+  }
+
+  @objc public func enqueueTask(_ task: @escaping FIRAuthSerialTask) {
+    dispatchQueue.async {
+      self.dispatchQueue.suspend()
+      task {
+        self.dispatchQueue.resume()
+      }
     }
+  }
 }

+ 34 - 33
FirebaseAuth/Sources/Swift/AuthProvider/PhoneAuthProvider.swift

@@ -68,7 +68,7 @@ import Foundation
     public func verify(phoneNumber: String,
                        UIDelegate: AuthUIDelegate?,
                        completion: ((_: String?, _: Error?) -> Void)?) {
-      guard FIRAuthWebUtils.isCallbackSchemeRegistered(forCustomURLScheme: callbackScheme) else {
+      guard AuthWebUtils.isCallbackSchemeRegistered(forCustomURLScheme: callbackScheme) else {
         fatalError(
           "Please register custom URL scheme \(callbackScheme) in the app's Info.plist file."
         )
@@ -225,7 +225,7 @@ import Foundation
     private func reCAPTCHAFlowWithUIDelegate(withUIDelegate UIDelegate: AuthUIDelegate,
                                              completion: @escaping (AuthAppCredential?, String?,
                                                                     Error?) -> Void) {
-      let eventID = FIRAuthWebUtils.randomString(withLength: 10)
+      let eventID = AuthWebUtils.randomString(withLength: 10)
       reCAPTCHAURL(withEventID: eventID) { reCAPTCHAURL, error in
         if let error = error {
           completion(nil, nil, error)
@@ -237,7 +237,7 @@ import Foundation
           )
         }
         let callbackMatcher: (URL?) -> Bool = { callbackURL in
-          FIRAuthWebUtils.isExpectedCallbackURL(
+          AuthWebUtils.isExpectedCallbackURL(
             callbackURL,
             eventID: eventID,
             authType: self.kAuthTypeVerifyApp,
@@ -267,13 +267,13 @@ import Foundation
       guard let queryItems = actualURLComponents?.queryItems else {
         return nil
       }
-      guard let deepLinkURL = FIRAuthWebUtils.queryItemValue("deep_link_id", from: queryItems)
+      guard let deepLinkURL = AuthWebUtils.queryItemValue(name: "deep_link_id", from: queryItems)
       else {
         return nil
       }
       let deepLinkComponents = URLComponents(string: deepLinkURL)
       if let queryItems = deepLinkComponents?.queryItems {
-        return FIRAuthWebUtils.queryItemValue("recaptchaToken", from: queryItems)
+        return AuthWebUtils.queryItemValue(name: "recaptchaToken", from: queryItems)
       }
       return nil
     }
@@ -353,36 +353,37 @@ import Foundation
      */
     private func reCAPTCHAURL(withEventID eventID: String,
                               completion: @escaping ((URL?, Error?) -> Void)) {
-      FIRAuthWebUtils.fetchAuthDomain(with: auth.requestConfiguration) { authDomain, error in
-        if let error = error {
-          completion(nil, error)
-          return
-        }
-        if let authDomain = authDomain {
-          let bundleID = Bundle.main.bundleIdentifier
-          let clientID = self.auth.app?.options.clientID
-          let appID = self.auth.app?.options.googleAppID
-          let apiKey = self.auth.requestConfiguration.APIKey
-          var queryItems = [URLQueryItem(name: "apiKey", value: apiKey),
-                            URLQueryItem(name: "authType", value: self.kAuthTypeVerifyApp),
-                            URLQueryItem(name: "ibi", value: bundleID ?? ""),
-                            URLQueryItem(name: "v", value: FIRAuthBackend.authUserAgent()),
-                            URLQueryItem(name: "eventID", value: eventID)]
-          if self.usingClientIDScheme {
-            queryItems.append(URLQueryItem(name: "clientID", value: clientID))
-          } else {
-            queryItems.append(URLQueryItem(name: "appId", value: appID))
-          }
-          if let languageCode = self.auth.requestConfiguration.languageCode {
-            queryItems.append(URLQueryItem(name: "hl", value: languageCode))
+      AuthWebUtils
+        .fetchAuthDomain(withRequestConfiguration: auth.requestConfiguration) { authDomain, error in
+          if let error = error {
+            completion(nil, error)
+            return
           }
-          var components = URLComponents(string: "https://\(authDomain)/__/auth/handler?")
-          components?.queryItems = queryItems
-          if let url = components?.url {
-            completion(url, nil)
+          if let authDomain = authDomain {
+            let bundleID = Bundle.main.bundleIdentifier
+            let clientID = self.auth.app?.options.clientID
+            let appID = self.auth.app?.options.googleAppID
+            let apiKey = self.auth.requestConfiguration.APIKey
+            var queryItems = [URLQueryItem(name: "apiKey", value: apiKey),
+                              URLQueryItem(name: "authType", value: self.kAuthTypeVerifyApp),
+                              URLQueryItem(name: "ibi", value: bundleID ?? ""),
+                              URLQueryItem(name: "v", value: FIRAuthBackend.authUserAgent()),
+                              URLQueryItem(name: "eventID", value: eventID)]
+            if self.usingClientIDScheme {
+              queryItems.append(URLQueryItem(name: "clientID", value: clientID))
+            } else {
+              queryItems.append(URLQueryItem(name: "appId", value: appID))
+            }
+            if let languageCode = self.auth.requestConfiguration.languageCode {
+              queryItems.append(URLQueryItem(name: "hl", value: languageCode))
+            }
+            var components = URLComponents(string: "https://\(authDomain)/__/auth/handler?")
+            components?.queryItems = queryItems
+            if let url = components?.url {
+              completion(url, nil)
+            }
           }
         }
-      }
     }
 
 //  - (void)verifyClientWithUIDelegate:(nullable id<FIRAuthUIDelegate>)UIDelegate
@@ -510,7 +511,7 @@ import Foundation
       if let clientID = auth.app?.options.clientID {
         let reverseClientIDScheme = clientID.components(separatedBy: ".").reversed()
           .joined(separator: ".")
-        if FIRAuthWebUtils.isCallbackSchemeRegistered(forCustomURLScheme: reverseClientIDScheme) {
+        if AuthWebUtils.isCallbackSchemeRegistered(forCustomURLScheme: reverseClientIDScheme) {
           callbackScheme = reverseClientIDScheme
           usingClientIDScheme = true
           return

+ 1 - 1
FirebaseAuth/Sources/Swift/Backend/AuthBackend.swift

@@ -43,7 +43,7 @@ public class AuthBackendRPCIssuerImplementation: NSObject, AuthBackendRPCIssuer
   override init() {
     fetcherService = GTMSessionFetcherService()
     fetcherService.userAgent = AuthBackend.authUserAgent()
-    fetcherService.callbackQueue = FIRAuthGlobalWorkQueue()
+    fetcherService.callbackQueue = kAuthGlobalWorkQueue
 
     // Avoid reusing the session to prevent
     // https://github.com/firebase/firebase-ios-sdk/issues/1261

+ 61 - 45
FirebaseAuth/Sources/Swift/User/AdditionalUserInfo.swift

@@ -1,55 +1,71 @@
+// Copyright 2023 Google LLC
 //
-//  File.swift
-//  
+// 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
 //
-//  Created by Morten Bek Ditlevsen on 13/02/2023.
+//      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 Foundation
 
 @objc(FIRAdditionalUserInfo) public class AdditionalUserInfo: NSObject, NSSecureCoding {
-    private static let providerIDCodingKey = "providerID"
-    private static let profileCodingKey = "profile"
-    private static let usernameCodingKey = "username"
-    private static let newUserKey = "newUser"
-
-    @objc public var providerID: String?
-    @objc public var profile: [String: Any]?
-    @objc public var username: String?
-    @objc public var isNewUser: Bool = false
-
-    @objc public static func userInfo(verifyAssertionResponse: VerifyAssertionResponse) -> AdditionalUserInfo {
-        return AdditionalUserInfo(providerID: verifyAssertionResponse.providerID,
-                                  profile: verifyAssertionResponse.profile,
-                                  username: verifyAssertionResponse.username,
-                                  isNewUser: verifyAssertionResponse.isNewUser)
-    }
-
-    @objc public init(providerID: String?, profile: [String: Any]?, username: String?, isNewUser: Bool) {
-        self.providerID = providerID
-        self.profile = profile
-        self.username = username
-        self.isNewUser = isNewUser
-    }
-
-    public static var supportsSecureCoding: Bool {
-        return true
-    }
-
-    public required init?(coder aDecoder: NSCoder) {
-        providerID = aDecoder.decodeObject(of: NSString.self, forKey: AdditionalUserInfo.providerIDCodingKey) as String?
-        profile = aDecoder.decodeObject(of: NSDictionary.self, forKey: AdditionalUserInfo.profileCodingKey) as? [String: Any]
-        username = aDecoder.decodeObject(of: NSString.self, forKey: AdditionalUserInfo.usernameCodingKey) as String?
-        isNewUser = aDecoder.decodeObject(of: NSNumber.self, forKey: AdditionalUserInfo.newUserKey)?.boolValue ?? false
-    }
-
-    public func encode(with aCoder: NSCoder) {
-        aCoder.encode(providerID, forKey: AdditionalUserInfo.providerIDCodingKey)
-        aCoder.encode(profile, forKey: AdditionalUserInfo.profileCodingKey)
-        aCoder.encode(username, forKey: AdditionalUserInfo.usernameCodingKey)
-        aCoder.encode(isNewUser, forKey: AdditionalUserInfo.newUserKey)
-    }
-}
+  private static let providerIDCodingKey = "providerID"
+  private static let profileCodingKey = "profile"
+  private static let usernameCodingKey = "username"
+  private static let newUserKey = "newUser"
+
+  @objc public var providerID: String?
+  @objc public var profile: [String: Any]?
+  @objc public var username: String?
+  @objc public var isNewUser: Bool = false
+
+  @objc public static func userInfo(verifyAssertionResponse: VerifyAssertionResponse)
+    -> AdditionalUserInfo {
+    return AdditionalUserInfo(providerID: verifyAssertionResponse.providerID,
+                              profile: verifyAssertionResponse.profile,
+                              username: verifyAssertionResponse.username,
+                              isNewUser: verifyAssertionResponse.isNewUser)
+  }
 
+  @objc public init(providerID: String?, profile: [String: Any]?, username: String?,
+                    isNewUser: Bool) {
+    self.providerID = providerID
+    self.profile = profile
+    self.username = username
+    self.isNewUser = isNewUser
+  }
 
+  public static var supportsSecureCoding: Bool {
+    return true
+  }
 
+  public required init?(coder aDecoder: NSCoder) {
+    providerID = aDecoder.decodeObject(
+      of: NSString.self,
+      forKey: AdditionalUserInfo.providerIDCodingKey
+    ) as String?
+    profile = aDecoder.decodeObject(
+      of: NSDictionary.self,
+      forKey: AdditionalUserInfo.profileCodingKey
+    ) as? [String: Any]
+    username = aDecoder.decodeObject(
+      of: NSString.self,
+      forKey: AdditionalUserInfo.usernameCodingKey
+    ) as String?
+    isNewUser = aDecoder.decodeObject(of: NSNumber.self, forKey: AdditionalUserInfo.newUserKey)?
+      .boolValue ?? false
+  }
+
+  public func encode(with aCoder: NSCoder) {
+    aCoder.encode(providerID, forKey: AdditionalUserInfo.providerIDCodingKey)
+    aCoder.encode(profile, forKey: AdditionalUserInfo.profileCodingKey)
+    aCoder.encode(username, forKey: AdditionalUserInfo.usernameCodingKey)
+    aCoder.encode(isNewUser, forKey: AdditionalUserInfo.newUserKey)
+  }
+}

+ 166 - 145
FirebaseAuth/Sources/Swift/Utilities/AuthWebUtils.swift

@@ -1,9 +1,16 @@
+// Copyright 2023 Google LLC
 //
-//  File.swift
-//  
+// 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
 //
-//  Created by Morten Bek Ditlevsen on 13/02/2023.
+//      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 Foundation
 
@@ -15,164 +22,178 @@ import Foundation
 typealias FIRFetchAuthDomainCallback = (String?, Error?) -> Void
 
 @objc(FIRAuthWebUtils) public class AuthWebUtils: NSObject {
-    static func randomString(withLength length: Int) -> String {
-        var randomString = ""
-        for _ in 0..<length {
-            let randomValue = UInt32(arc4random_uniform(26) + 65)
-            guard let randomCharacter = Unicode.Scalar(randomValue) else { continue }
-            randomString += String(Character(randomCharacter))
-        }
-        return randomString
+  static func randomString(withLength length: Int) -> String {
+    var randomString = ""
+    for _ in 0 ..< length {
+      let randomValue = UInt32(arc4random_uniform(26) + 65)
+      guard let randomCharacter = Unicode.Scalar(randomValue) else { continue }
+      randomString += String(Character(randomCharacter))
     }
-
-    @objc public static func isCallbackSchemeRegisteredForCustomURLScheme(_ scheme: String) -> Bool {
-        let expectedCustomScheme = scheme.lowercased()
-        guard let urlTypes = Bundle.main.object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]] else {
-            return false
-        }
-        for urlType in urlTypes {
-            guard let urlTypeSchemes = urlType["CFBundleURLSchemes"] as? [String] else {
-                continue
-            }
-            for urlTypeScheme in urlTypeSchemes {
-                if urlTypeScheme.lowercased() == expectedCustomScheme {
-                    return true
-                }
-            }
-        }
-        return false
+    return randomString
+  }
+
+  @objc public static func isCallbackSchemeRegistered(forCustomURLScheme scheme: String) -> Bool {
+    let expectedCustomScheme = scheme.lowercased()
+    guard let urlTypes = Bundle.main
+      .object(forInfoDictionaryKey: "CFBundleURLTypes") as? [[String: Any]] else {
+      return false
     }
-
-    static func isExpectedCallbackURL(_ url: URL?, eventID: String, authType: String, callbackScheme: String) -> Bool {
-        guard let url else {
-            return false
-        }
-        var actualURLComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
-        actualURLComponents?.query = nil
-        actualURLComponents?.fragment = nil
-
-        var expectedURLComponents = URLComponents()
-        expectedURLComponents.scheme = callbackScheme
-        expectedURLComponents.host = "firebaseauth"
-        expectedURLComponents.path = "/link"
-
-        guard let actualURL = actualURLComponents?.url, let expectedURL = expectedURLComponents.url else {
-            return false
+    for urlType in urlTypes {
+      guard let urlTypeSchemes = urlType["CFBundleURLSchemes"] as? [String] else {
+        continue
+      }
+      for urlTypeScheme in urlTypeSchemes {
+        if urlTypeScheme.lowercased() == expectedCustomScheme {
+          return true
         }
-        if expectedURL != actualURL {
-            return false
-        }
-        let URLQueryItems = dictionary(withHttpArgumentsString: url.query)
-        guard let deeplinkURLString = URLQueryItems["deep_link_id"], let deeplinkURL = URL(string: deeplinkURLString) else {
-            return false
-        }
-        let deeplinkQueryItems = dictionary(withHttpArgumentsString: deeplinkURL.query)
-        if deeplinkQueryItems["authType"] == authType && deeplinkQueryItems["eventId"] == eventID {
-            return true
-        }
-        return false
+      }
     }
+    return false
+  }
 
-    static func fetchAuthDomain(withRequestConfiguration requestConfiguration: AuthRequestConfiguration, completion: @escaping FIRFetchAuthDomainCallback) {
-
-        if let emulatorHostAndPort = requestConfiguration.emulatorHostAndPort {
-            completion(emulatorHostAndPort, nil)
-            return
-        }
-
-        let request = GetProjectConfigRequest(requestConfiguration: requestConfiguration)
-
-        FIRAuthBackend.getProjectConfig(request) { (response, error) in
-            if let error = error {
-                completion(nil, error)
-                return
-            }
-
-            var authDomain: String?
-            for domain in response?.authorizedDomains ?? [] {
-                for supportedAuthDomain in Self.supportedAuthDomains {
-                    let index = domain.count - supportedAuthDomain.count
-                    if index >= 2, domain.hasSuffix(supportedAuthDomain), domain.count >= supportedAuthDomain.count + 2 {
-                        authDomain = domain
-                        break
-                    }
-                }
-
-                if authDomain != nil {
-                    break
-                }
-            }
+  static func isExpectedCallbackURL(_ url: URL?, eventID: String, authType: String,
+                                    callbackScheme: String) -> Bool {
+    guard let url else {
+      return false
+    }
+    var actualURLComponents = URLComponents(url: url, resolvingAgainstBaseURL: false)
+    actualURLComponents?.query = nil
+    actualURLComponents?.fragment = nil
+
+    var expectedURLComponents = URLComponents()
+    expectedURLComponents.scheme = callbackScheme
+    expectedURLComponents.host = "firebaseauth"
+    expectedURLComponents.path = "/link"
+
+    guard let actualURL = actualURLComponents?.url,
+          let expectedURL = expectedURLComponents.url else {
+      return false
+    }
+    if expectedURL != actualURL {
+      return false
+    }
+    let URLQueryItems = dictionary(withHttpArgumentsString: url.query)
+    guard let deeplinkURLString = URLQueryItems["deep_link_id"],
+          let deeplinkURL = URL(string: deeplinkURLString) else {
+      return false
+    }
+    let deeplinkQueryItems = dictionary(withHttpArgumentsString: deeplinkURL.query)
+    if deeplinkQueryItems["authType"] == authType, deeplinkQueryItems["eventId"] == eventID {
+      return true
+    }
+    return false
+  }
+
+  static func fetchAuthDomain(withRequestConfiguration requestConfiguration: AuthRequestConfiguration,
+                              completion: @escaping FIRFetchAuthDomainCallback) {
+    if let emulatorHostAndPort = requestConfiguration.emulatorHostAndPort {
+      // If we are using the auth emulator, we do not want to call the GetProjectConfig endpoint. The
+      // widget is hosted on the emulator host and port, so we can return that directly.
+      completion(emulatorHostAndPort, nil)
+      return
+    }
 
-            if authDomain == nil || authDomain!.isEmpty {
-                completion(nil, AuthErrorUtils.unexpectedErrorResponse(deserializedResponse: response))
-                return
+    let request = GetProjectConfigRequest(requestConfiguration: requestConfiguration)
+
+    AuthBackend.post(withRequest: request) { response, error in
+      if let error = error {
+        completion(nil, error)
+        return
+      }
+      // Look up an authorized domain ends with one of the supportedAuthDomains.
+      // The sequence of supportedAuthDomains matters. ("firebaseapp.com", "web.app")
+      // The searching ends once the first valid suportedAuthDomain is found.
+      var authDomain: String?
+      if let response = response as? GetProjectConfigResponse {
+        for domain in response.authorizedDomains ?? [] {
+          for supportedAuthDomain in Self.supportedAuthDomains {
+            let index = domain.count - supportedAuthDomain.count
+            if index >= 2, domain.hasSuffix(supportedAuthDomain),
+               domain.count >= supportedAuthDomain.count + 2 {
+              authDomain = domain
+              break
             }
-
-            completion(authDomain, nil)
+          }
+          if authDomain != nil {
+            break
+          }
         }
+      }
 
-    }
-    
+      if authDomain == nil || authDomain!.isEmpty {
+        completion(
+          nil,
+          AuthErrorUtils.unexpectedErrorResponse(deserializedResponse: response)
+        )
+        return
+      }
 
-    static func queryItemValue(name: String, from queryList: [URLQueryItem]) -> String? {
-        for item in queryList where item.name == name {
-            return item.value
-        }
-        return nil
+      completion(authDomain, nil)
     }
+  }
 
-    static func dictionary(withHttpArgumentsString argString: String?) -> [String: String] {
-        guard let argString else {
-            return [:]
-        }
-        var ret = [String: String]()
-        let components = argString.components(separatedBy: "&")
-        for component in components.reversed() {
-          if component.isEmpty { continue }
-          let pos = component.firstIndex(of: "=")
-          var key: String
-          var val: String
-          if pos == nil {
-            key = string(byUnescapingFromURLArgument: component)
-            val = ""
-          } else {
-            let index = component.index(after: pos!)
-            key = string(byUnescapingFromURLArgument: String(component[..<pos!]))
-            val = string(byUnescapingFromURLArgument: String(component[index...]))
-          }
-          if key.isEmpty { key = "" }
-          if val.isEmpty { val = "" }
-          ret[key] = val
-        }
-        return ret
+  static func queryItemValue(name: String, from queryList: [URLQueryItem]) -> String? {
+    for item in queryList where item.name == name {
+      return item.value
     }
+    return nil
+  }
 
-    static func string(byUnescapingFromURLArgument argument: String) -> String {
-        return argument
-            .replacingOccurrences(of: "+", with: " ")
-            .removingPercentEncoding ?? ""
+  @objc public static func dictionary(withHttpArgumentsString argString: String?)
+    -> [String: String] {
+    guard let argString else {
+      return [:]
     }
-
-    @objc public static func parseURL(_ urlString: String) -> [String: String] {
-        let urlComponents = URLComponents(string: urlString)
-        guard let linkURL = urlComponents?.query else {
-            return [:]
-        }
-        let queryComponents = linkURL.components(separatedBy: "&")
-        var queryItems = [String: String]()
-        for component in queryComponents {
-            if let equalRange = component.range(of: "=") {
-                let queryItemKey = component[..<equalRange.lowerBound].removingPercentEncoding
-                let queryItemValue = component[equalRange.upperBound...].removingPercentEncoding
-                if let queryItemKey = queryItemKey, let queryItemValue = queryItemValue {
-                    queryItems[queryItemKey] = queryItemValue
-                }
-            }
+    var ret = [String: String]()
+    let components = argString.components(separatedBy: "&")
+    // Use reverse order so that the first occurrence of a key replaces
+    // those subsequent.
+    for component in components.reversed() {
+      if component.isEmpty { continue }
+      let pos = component.firstIndex(of: "=")
+      var key: String
+      var val: String
+      if pos == nil {
+        key = string(byUnescapingFromURLArgument: component)
+        val = ""
+      } else {
+        let index = component.index(after: pos!)
+        key = string(byUnescapingFromURLArgument: String(component[..<pos!]))
+        val = string(byUnescapingFromURLArgument: String(component[index...]))
+      }
+      if key.isEmpty { key = "" }
+      if val.isEmpty { val = "" }
+      ret[key] = val
+    }
+    return ret
+  }
+
+  static func string(byUnescapingFromURLArgument argument: String) -> String {
+    return argument
+      .replacingOccurrences(of: "+", with: " ")
+      .removingPercentEncoding ?? ""
+  }
+
+  @objc public static func parseURL(_ urlString: String) -> [String: String] {
+    let urlComponents = URLComponents(string: urlString)
+    guard let linkURL = urlComponents?.query else {
+      return [:]
+    }
+    let queryComponents = linkURL.components(separatedBy: "&")
+    var queryItems = [String: String]()
+    for component in queryComponents {
+      if let equalRange = component.range(of: "=") {
+        let queryItemKey = component[..<equalRange.lowerBound].removingPercentEncoding
+        let queryItemValue = component[equalRange.upperBound...].removingPercentEncoding
+        if let queryItemKey = queryItemKey, let queryItemValue = queryItemValue {
+          queryItems[queryItemKey] = queryItemValue
         }
-        return queryItems
+      }
     }
+    return queryItems
+  }
 
-    static var supportedAuthDomains: [String] {
-        return ["firebaseapp.com", "web.app"]
-    }
+  static var supportedAuthDomains: [String] {
+    return ["firebaseapp.com", "web.app"]
+  }
 }

+ 12 - 12
FirebaseAuth/Tests/Unit/FIRAdditionalUserInfoTests.m

@@ -77,24 +77,24 @@ static NSString *const kProviderID = @"PROVIDER_ID";
         @c userInfoWithVerifyAssertionResponse call.
  */
 - (void)testAdditionalUserInfoCreationWithStaticInitializer {
-    FIRVerifyAssertionResponse *response = [[FIRVerifyAssertionResponse alloc] init];
-    response.providerID = kProviderID;
-    response.profile = [[self class] profile];
-    response.username = kUserName;
-    response.isNewUser = kIsNewUser;
-    // XXX TODO: Mocking doesn't appear to work across language boundary.
-//  id mockVerifyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
-//  OCMExpect([mockVerifyAssertionResponse providerID]).andReturn(kProviderID);
-//  OCMExpect([mockVerifyAssertionResponse profile]).andReturn([[self class] profile]);
-//  OCMExpect([mockVerifyAssertionResponse username]).andReturn(kUserName);
-//  OCMExpect([mockVerifyAssertionResponse isNewUser]).andReturn(kIsNewUser);
+  FIRVerifyAssertionResponse *response = [[FIRVerifyAssertionResponse alloc] init];
+  response.providerID = kProviderID;
+  response.profile = [[self class] profile];
+  response.username = kUserName;
+  response.isNewUser = kIsNewUser;
+  // XXX TODO: Mocking doesn't appear to work across language boundary.
+  //  id mockVerifyAssertionResponse = OCMClassMock([FIRVerifyAssertionResponse class]);
+  //  OCMExpect([mockVerifyAssertionResponse providerID]).andReturn(kProviderID);
+  //  OCMExpect([mockVerifyAssertionResponse profile]).andReturn([[self class] profile]);
+  //  OCMExpect([mockVerifyAssertionResponse username]).andReturn(kUserName);
+  //  OCMExpect([mockVerifyAssertionResponse isNewUser]).andReturn(kIsNewUser);
   FIRAdditionalUserInfo *userInfo =
       [FIRAdditionalUserInfo userInfoWithVerifyAssertionResponse:response];
   XCTAssertEqualObjects(userInfo.providerID, kProviderID);
   XCTAssertEqualObjects(userInfo.profile, [[self class] profile]);
   XCTAssertEqualObjects(userInfo.username, kUserName);
   XCTAssertEqual(userInfo.isNewUser, kIsNewUser);
-//  OCMVerifyAll(mockVerifyAssertionResponse);
+  //  OCMVerifyAll(mockVerifyAssertionResponse);
 }
 
 /** @fn testAdditionalUserInfoCoding

+ 29 - 29
FirebaseAuth/Tests/Unit/FIRAuthSerialTaskQueueTests.m

@@ -39,41 +39,41 @@ static const NSTimeInterval kTimeout = 1;
     completionArg();
     [expectation fulfill];
   }];
-    NSLog(@"XXX");
+  NSLog(@"XXX");
   [self waitForExpectationsWithTimeout:kTimeout handler:nil];
 }
 
 - (void)testCompletion {
-    NSLog(@"11");
+  NSLog(@"11");
   XCTestExpectation *expectation = [self expectationWithDescription:@"executed"];
-    NSLog(@"12");
+  NSLog(@"12");
   FIRAuthSerialTaskQueue *queue = [[FIRAuthSerialTaskQueue alloc] init];
-    NSLog(@"14");
-//  __block FIRAuthSerialTaskCompletionBlock completion = nil;
-//    NSLog(@"1");
-//  [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
-//    completion = completionArg;
-//    [expectation fulfill];
-//  }];
-//    NSLog(@"2");
-//  __block XCTestExpectation *nextExpectation = nil;
-//  __block BOOL executed = NO;
-//  [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
-//    executed = YES;
-//    completionArg();
-//    [nextExpectation fulfill];
-//  }];
-//    NSLog(@"3");
-//
-//  // The second task should not be executed until the first is completed.
-//  [self waitForExpectationsWithTimeout:kTimeout handler:nil];
-//    NSLog(@"4");
-//  XCTAssertNotNil(completion);
-//  XCTAssertFalse(executed);
-//  nextExpectation = [self expectationWithDescription:@"executed next"];
-//  completion();
-//  [self waitForExpectationsWithTimeout:kTimeout handler:nil];
-//  XCTAssertTrue(executed);
+  NSLog(@"14");
+  //  __block FIRAuthSerialTaskCompletionBlock completion = nil;
+  //    NSLog(@"1");
+  //  [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+  //    completion = completionArg;
+  //    [expectation fulfill];
+  //  }];
+  //    NSLog(@"2");
+  //  __block XCTestExpectation *nextExpectation = nil;
+  //  __block BOOL executed = NO;
+  //  [queue enqueueTask:^(FIRAuthSerialTaskCompletionBlock completionArg) {
+  //    executed = YES;
+  //    completionArg();
+  //    [nextExpectation fulfill];
+  //  }];
+  //    NSLog(@"3");
+  //
+  //  // The second task should not be executed until the first is completed.
+  //  [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+  //    NSLog(@"4");
+  //  XCTAssertNotNil(completion);
+  //  XCTAssertFalse(executed);
+  //  nextExpectation = [self expectationWithDescription:@"executed next"];
+  //  completion();
+  //  [self waitForExpectationsWithTimeout:kTimeout handler:nil];
+  //  XCTAssertTrue(executed);
 }
 
 - (void)testTargetQueue {

+ 2 - 0
FirebaseAuth/Tests/Unit/FIRAuthTests.m

@@ -2340,6 +2340,7 @@ static const NSTimeInterval kWaitInterval = .5;
 #endif  // TODO_SWIFT
 #if TARGET_OS_IOS
 #pragma mark - Application Delegate tests
+#ifdef TODO_SWIFT
 - (void)testAppDidRegisterForRemoteNotifications_APNSTokenUpdated {
   NSData *apnsToken = [NSData data];
 
@@ -2354,6 +2355,7 @@ static const NSTimeInterval kWaitInterval = .5;
 
   [self.mockTokenManager verify];
 }
+#endif
 
 - (void)testAppDidFailToRegisterForRemoteNotifications_TokenManagerCancels {
   NSError *error = [NSError errorWithDomain:@"FIRAuthTests" code:-1 userInfo:nil];