firebaseswiftuifirebase-authentication

PhoneAuthProvider.provider().verifyPhoneNumber hangs on about: blank page after ios firebase-ios-sdk SMS authentication recaptcha flow


I'm trying to isolate Firebase's phone authentication from their working-, with a real device and in a simulator, quickstart-ios as of firebase-ios-sdk v11.2. "Swizzling is disabled" in the simulator (2024), with which I want to achieve the recaptcha flow, first, as it's a fallback to APN's anyway.

ContentView.swift

enum FirebaseError: Error {
    case Error
    case VerificatrionEmpty
}
struct ContentView: View {
    
    @State var verificationCode = ""
    @State var newUsername = ""
    @State var phoneNumber = ""
    @State var countryCodeNumber = "+1"
    @State var country = ""
    @State var smsTextCode = ""
    @State var loggedin = false
    @State var verificationId = ""
    @State var verifiable = false
    //@State var session: User = User(coder: NSCoder())!
    
    @State public var show: String = "home"

    init() {
        // Use Firebase library to configure APIs
        //FirebaseApp.configure()
        
    }

    func signOut(){
        if Auth.auth().currentUser != nil {
            do {
                try Auth.auth().signOut()
            }
            catch {
              print (error)
            }
        }
    
        loggedin = false
    }
    var body: some View {
        if Auth.auth().currentUser == nil && !loggedin {
            Form{
                Section(footer: Text("Get an SMS code. Standard messaging rates apply.")) {
                    TextField("Country code", text: $countryCodeNumber)
                        .font(Font.system(size: 15))
                        .fontWeight(.semibold)
                        .frame(width: nil, height: nil, alignment: .leading)
                        .onChange(of: countryCodeNumber) {
                          verifiable = false
                        }
                    TextField("Enter your phone number", text: $phoneNumber)
                        .font(Font.system(size: 15))
                        .fontWeight(.semibold)
                        .frame(width: nil, height: nil, alignment: .leading)
                        .onChange(of: phoneNumber) {
                          verifiable = false
                        }
                }
                if verifiable {
                    Section{
                        TextField("SMS code", text: $verificationCode)
                            .font(Font.system(size: 15))
                            .fontWeight(.semibold)
                            .frame(width: nil, height: nil, alignment: .leading)
                    }
                }
                Button("Submit", action: {
                    
                    if !verifiable {
                        /*let _ = Auth.auth().addStateDidChangeListener({ (auth, user) in
                            if let _ = user {
                                loggedin = true
                                //session = user
                            } else {
                                loggedin = false
                                //session = User(coder: NSCoder())!
                            }
                        })*/
                        
                        //testing = true
                        //Auth.auth().settings?.isAppVerificationDisabledForTesting = true
                        if phoneNumber == "" {
                            print("No phone number")
                            return
                        }
                        if countryCodeNumber == "" {
                            return
                        }
                        let _ = PhoneAuthProvider.provider(auth: Auth.auth())
                        PhoneAuthProvider.provider().verifyPhoneNumber(countryCodeNumber + phoneNumber, uiDelegate: nil) { verificationID, error in
                            if error != nil {
                                print(error!.localizedDescription)
                                return
                            }
                            guard let verificationID = verificationID else {
                                print(FirebaseError.VerificatrionEmpty)
                                return
                            }
                            verificationId = verificationID
                            verifiable = true
                        }
                        //verifiable = true
                    } else {
                        let credential = PhoneAuthProvider.provider().credential(
                          withVerificationID: verificationId,
                          verificationCode: smsTextCode
                        )
                        Auth.auth().signIn(with: credential) { authResult, error in
                            if error != nil {
                                print(FirebaseError.Error)
                                return
                            }
                            guard let authResult = authResult else {
                                print(FirebaseError.VerificatrionEmpty)
                                return
                            }
                            _ = authResult

                            loggedin = true
                        }
                        //loggedin = true
                    }
                    
                    
                })
            }
        }
        
        if Auth.auth().currentUser != nil || loggedin {
            if show == "leaderboard"{
            }
            if show == "home" {
            }
            Text(show != "home" ? "back" : "logout")
                .font(Font.system(size: 15))
                .fontWeight(.semibold)
                .frame(width: nil, height: nil, alignment: .leading)
                .onTapGesture {
                    
                    if show == "home"{
                        //signOut()
                        loggedin = false
                        verifiable = false
                    } else {
                        show = "home"
                    }
                }
        }
    }
}


#Preview {
    ContentView()
}

App.swift (canHandle and openURLContexts func's, included)

import SwiftUI
import FirebaseCore
import FirebaseAuth
import FirebaseMessaging

@main
struct PassportApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        FirebaseApp.configure()
        print("application is starting up. ApplicationDelegate didFinishLaunchingWithOptions.")
        return true
    }
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("Yay! Got a device token 🥳 \(deviceToken)")

        Auth.auth().setAPNSToken(deviceToken, type: .sandbox)//.unknown
        //Messaging.messaging().setAPNSToken(deviceToken, type: .sandbox)
    }
    func application(_ application: UIApplication,
                     didReceiveRemoteNotification notification: [AnyHashable : Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        
        if Auth.auth().canHandleNotification(notification) {
            print("Can handle notifications")
            completionHandler(.noData)
            return
        }
    }
    func application(_ application: UIApplication, open url: URL,
                     options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {

      if Auth.auth().canHandle(url) {
          print("Can handle url")
          return true
      }
      return false
    }
    func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
        // Called when a new scene session is being created.
        // Use this method to select a configuration to create the new scene with.
        return UISceneConfiguration(
          name: "Default Configuration",
          sessionRole: connectingSceneSession.role
        )
          //let sceneConfig: UISceneConfiguration = UISceneConfiguration(name: nil, sessionRole: connectingSceneSession.role)
          //sceneConfig.delegateClass = SceneDelegate.self
          //return sceneConfig
      }
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  //var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
             options connectionOptions: UIScene.ConnectionOptions) {
      //guard let windowScene = (scene as? UIWindowScene) else { return }
    }

    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
      if let incomingURL = userActivity.webpageURL {
          let _ = incomingURL.absoluteString
      }
    }
    //this should fix it
    // Implementing this delegate method is needed when swizzling is disabled.
    // Without it, reCAPTCHA's login view controller will not dismiss.
    func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
        for urlContext in URLContexts {
          let url = urlContext.url
          let _ = Auth.auth().canHandle(url)
        }
    // URL not auth related; it should be handled separately.
    }
}

P.s. My GoogleService-Info.plist was downloaded after enabling firebase authentication in the console and my xcode Target>Info>URL Type URL Scheme includes my REVERSED_CLIENT_ID, but I'm sure I would have encountered an error instead of this issue had I not. If my URL Scheme is rather the Encoded App ID, it does not matter.


Solution

  • It works on the simulator (command + r) and a real device as is, but not #Preview. This might be as intended. I thought preview and simulator were the same at the time of asking this question.