skip to Main Content

I feel like there is an obvious solution to this that is on the tip of my brain, but I can’t seem to figure it out. I am using the FirebaseAuth library, so I can’t edit it (or I don’t want to go down that path). The function getIDTokenForcingRefresh() uses dispatch_async. This would be a lot easier if it used the async/await functionality from Swift 5.5, but I have to rely on solutions for dispatch_async. I need to grab the output to run a Firebase function request using the token. The following obviously doesn’t work because the function will return before getIDTokenForcingRefresh() is finished. I don’t care if the main thread is blocked because the user can’t proceed in the app until this is done.

    var userToken: String {

        print("FIREBASE: Getting User Token")
        var token = ""
        Auth.auth().currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
            if let error = error {
                print("FIREBASE: There was an error getting the user token")
                return
            }
            print("FIREBASE: Got user token: (idToken ?? "NONE")")
            token = idToken ?? ""
        }
        print("FIREBASE: Token: (token)")
        return token
    }

2

Answers


  1. Chosen as BEST ANSWER

    EDIT: Thanks to vadian's comment above and HangerRash's suggestion, an async computed property is better:

    extension Auth {
        var currentUserToken: Task<String, Error> {
            Task { @MainActor in
                do {
                    let idToken = try await currentUser?.getIDTokenResult()
                    print("FIREBASE: Got user token: (idToken?.token ?? "NONE")")
                    return idToken?.token ?? ""
                } catch {
                    print("FIREBASE: There was an error getting the user token")
                    throw error
                }
            }
        }
    }
    
            var token = ""
            do {
                token = try await Auth.auth().currentUserToken.value
                //print("FIREBASE: Token: (token)")
                // Proceed with the code that depends on the token
            } catch {
                print("FIREBASE: Error retrieving user token: (error)")
                // Handle the error
            }
            
            print("FIREBASE: Token = (token)")
    

    Old, non-optimal semaphore way

    I was able to figure this out using semaphore:

        func getUserToken(completion: @escaping (String?) -> Void) {
            print("FIREBASE: Getting User Token")
            Auth.auth().currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
                if let error = error {
                    print("FIREBASE: There was an error getting the user token: (error.localizedDescription)")
                    completion(nil)
                } else {
                    print("FIREBASE: Got user token: (idToken ?? "NONE")")
                    completion(idToken)
                }
            }
        }
        
        func getUserTokenBlocking() -> String {
            var userToken: String = ""
            let semaphore = DispatchSemaphore(value: 0)
            
            GPTClient.getUserToken { token in
                userToken = token ?? ""
                semaphore.signal()
            }
            
            semaphore.wait()
            return userToken
        }
    

    Now when I want to grab the token I use:

    let token = getUserTokenBlocking()
    

    I obviously need to do some error handling, and this is only useful when you WANT to block the main thread, which usually isn't the right answer but works for me in this situation.


  2. Even with a completion handler method like getIDTokenForcingRefresh you can take advantage of async/await by wrapping the asynchronous function with completion handler in a Continuation. The error handling is for free

    func getUserToken() async throws -> String {
        print("FIREBASE: Getting User Token")
        return withCheckedThrowingContinuation { continuation in
            Auth.auth().currentUser?.getIDTokenForcingRefresh(true) { idToken, error in
                if let error {
                    print("FIREBASE: There was an error getting the user token: (error.localizedDescription)")
                    continuation.resume(throwing: error)
                } else {
                    print("FIREBASE: Got user token: (idToken ?? "NONE")")
                    continuation.resume(returning: idToken!)
                }
            }
        }
    }
    

    And use it

    Task {
        do {
            let token = try await getUserToken()
        } catch {
            print(error)
        }
    }
    

    Doesn’t Firebase support async/await meanwhile?

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search