Not getting Firebase token id when calling getIdToken in Swift 5 iOS - swift

I have been trying to make this work for 3 days now, and I can't see where I am going. When trying to get firebase tokenId using the - (void)getIDTokenWithCompletion: (nullable void (^)(NSString *_Nullable __strong, NSError *_Nullable __strong))completion; function provided by firebase I am getting nothing in return.
I have created a separate class to get the Id using a completion handler. Below is the code I am using
import Foundation
import FirebaseAuth
class FirebaseToken {
static var shared = FirebaseToken.init()
func getIdToken(token completion: #escaping(String?,Error?) -> Void){
Auth.auth().currentUser?.getIDToken(completion:{ idToken, error in
guard let error = error else {return }
print(error)
completion(nil, error)
guard let token = idToken else {return}
completion(token, nil)
print(token)
})
}
}
This is the class I am using to call the func getIdToken function to get the Id, which is inside the func makeAPICall<T:Codable>(urlPath: String, apiMethod: HTTPMethod, expectedReturnType: T.Type,user completionHandler: #escaping ([T]?,Error?) function.
import Foundation
import Alamofire
import Firebase
class ApiService {
static var shared = ApiService.init()
let session: Session = {
let manager = ServerTrustManager(allHostsMustBeEvaluated: false,evaluators: ["localhost": DisabledTrustEvaluator()])
let configuration = URLSessionConfiguration.af.default
return Session(configuration: configuration, serverTrustManager: manager)
}()
//MARK:- GET
func makeAPICall<T:Codable>(urlPath: String, apiMethod: HTTPMethod, expectedReturnType: T.Type,user completionHandler: #escaping ([T]?,Error?) -> Void) {
var urlComponent = URLComponents()
urlComponent.scheme = "https"
urlComponent.host = "localhost"
urlComponent.port = 5001
urlComponent.path = "/api/" + urlPath
print(urlComponent.url!)
guard let url = urlComponent.url else {
return
}
var headers: HTTPHeaders?
FirebaseToken.shared.getIdToken(token: {tokenId, error in
guard let errors = error else {return}
print(errors)
guard let tokens = tokenId else {return}
headers = [
.authorization(bearerToken: tokens),
.accept("application/json")
]
})
guard let headerAuth = headers else {
print("not getting firebase token")
return
}
print(headerAuth)
session.request(url, method: apiMethod).validate().responseDecodable(of: [T].self) {(response) in
switch response.result{
case .success:
guard let users = response.value else {
return
}
//print(header)
completionHandler(users, nil)
case .failure(let error):
completionHandler(nil, error)
}
}
}
}
the variable var headers: HTTPHeaders?, which inside the function
guard let headerAuth = headers else {
print("not getting firebase token")
return
}
should be printing out the token yet, for some reason the token isn't being added. Can someone let me know where I am going wrong as I have been stuck for 3 days and I am still very new to firebase?
The printing result should be within print(headerAuth), however; I keep on getting the result within print("not getting firebase token")

The getIdToken method is an asynchronous call. Any code that needs the resulting token, needs to be inside the completion handler.
So something like:
FirebaseToken.shared.getIdToken(token: {tokenId, error in
guard let tokens = tokenId else {return}
headers = [
.authorization(bearerToken: tokens),
.accept("application/json")
]
guard let headerAuth = headers else {
print("not getting firebase token")
return
}
session.request(url, method: apiMethod).validate().responseDecodable(of: [T].self) {(response) in
switch response.result{
case .success:
guard let users = response.value else {
return
}
completionHandler(users, nil)
case .failure(let error):
completionHandler(nil, error)
}
}
})
I also removed this line:
guard let errors = error else {return}
As I'm pretty sure this returns out of the block when there is no error.

Related

Firebase getIDToken and how to use it in an API call

I have an API call that grabs json, but requires token authentication. Token auth works great, but when I try and pass the token along to the API function, it's coming back nil. I believe it's because Auth.auth().currentUser!.getIDToken(...) hasn't actually completed yet. Relevant code below... How do I modify this to
class SessionData : ObservableObject {
...
func token() -> String? {
var result: String? = nil
Auth.auth().currentUser!.getIDToken(completion: { (res, err) in
if err != nil {
print("*** TOKEN() ERROR: \(err!)")
} else {
print("*** TOKEN() SUCCESS: \(err!)")
result = res!
}
})
return result
}
...
}
class FetchPosts: ObservableObject {
#Published var posts = [Post]()
func load(api: Bool, session: SessionData) {
if api {
let url = URL(string: MyAPI.getAddress(token: session.token()!))!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let postsData = data {
// 3.
let decodedData = try JSONDecoder().decode(Response.self, from: postsData)
DispatchQueue.main.async {
self.posts = decodedData.result
if decodedData.error != nil {
print("ERROR: \(decodedData.error!)")
session.json_error(error: decodedData.error!)
}
}
} else {
print("No data. Connection error.")
DispatchQueue.main.async {
session.json_error(error: "Could not connect to server, please try again!")
}
}
} catch {
print("* Error: \(error)")
}
}.resume()
} else {
let url = Bundle.main.url(forResource: "test", withExtension: "json")!
let data = try! Data(contentsOf: url)
let decoder = JSONDecoder()
if let products = try? decoder.decode([Post].self, from: data) {
self.posts = products
}
}
}
}
And this is how the .load function is called:
UserViewer(fetch: posts)
.transition(AnyTransition.slide)
.animation(.default)
.onAppear {
withAnimation{
posts.load(api: true, session: session)
}
}
.environmentObject(session)
Because getIDToken executes and returns asynchronously, you can't return directly from it. Instead, you'll need to use a callback function.
Here's a modification of your function:
func token(_ completion: #escaping (String?) -> ()) {
guard let user = Auth.auth().currentUser else {
//handle error
return
}
user.getIDToken(completion: { (res, err) in
if err != nil {
print("*** TOKEN() ERROR: \(err!)")
//handle error
} else {
print("*** TOKEN() SUCCESS: \(err!)")
completion(res)
}
})
}
Then, you can use it later on:
.onAppear {
session.token { token in
guard let token = token else {
//handle nil
return
}
withAnimation{
posts.load(api: true, session: session, token: token)
}
}
}
Modify your load to take a token parameter:
func load(api: Bool, session: SessionData, token: String) {
if api {
guard let url = URL(string: MyAPI.getAddress(token: token)) else {
//handle bad URL
return
}
Also, as you can see I'm doing in my code samples, I would try to get out of the habit of using ! to force unwrap optionals. If the optional is nil and you use !, your program will crash. Instead, familiarize yourself with guard let and if let and learn to handle optionals in a way that won't lead to a crash -- it's one of the great benefits of Swift.

Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)

Below line of code is producing the error,
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
When print jsonData This code returns perfect result of array but getting this error,
Could not cast value of type 'Swift.String' (0x10fef45c0) to 'Swift.Error' (0x10ff2bd10). (lldb)
As the error says I understand its a cast exception, but I'm not able to modify the code to make it work. I'm kinda new to Swift, so any help would be appreciated. Below is my
import Foundation
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<Any, Error>, Error) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error), Error.self as! Error)
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response")
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data")
return
}
do {
// Parse the data
let decoder = JSONDecoder()
let jsonData = try decoder.decode(APIService.self, from: data)
// print(jsonData)
// Back to the main thread
DispatchQueue.main.async {
completion(.success(jsonData), Error as! Error)
}
} catch let error {
completion(.failure(error),error)
}
}
dataTask?.resume()
}
}
Modify the completion block parameters, you already are returning the error inside the Result's .failure(Error) block so no need to repeat it again as another parameter in the completion parameter. Here's how you fix this:
Declaration:
class APIService {
private var dataTask: URLSessionDataTask?
func getPopularPosts(completion: #escaping (Result<CategoriesNewsData, Error>) -> Void) {
let popularURL = "URL Here"
guard let url = URL(string: popularURL) else {return}
// Create URL Session - work on the background
dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
// Handle Error
if let error = error {
completion(.failure(error))
print("DataTask error: \(error.localizedDescription)")
return
}
guard let response = response as? HTTPURLResponse else {
// Handle Empty Response
print("Empty Response") // Throw a custom error here too.
return
}
print("Response status code: \(response.statusCode)")
guard let data = data else {
// Handle Empty Data
print("Empty Data") // Throw a custom error here too.
return
}
do {
let decoder = JSONDecoder()
let jsonData = try decoder.decode(CategoriesNewsData.self, from: data)
DispatchQueue.main.async {
completion(.success(jsonData))
}
} catch let error {
completion(.failure(error))
}
}
dataTask?.resume()
}
}
Calling:
service.getPopularPosts { result in
switch result {
case .success(let categoriesNewsData):
print(categoriesNewsData)
case .failure(let error):
print(error)
}
}

Swift scoping outside of a function

I have a singleton URLSession that is parsing the response data into a dictionary. I want to use a single value from that dictionary in a subsequent piece of code, but cannot figure out how to pass the value out from the scope it's currently in.
Here is the code as it stands now:
let task = URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
debugPrint ("error: \(error!)")
return
}
guard let content = data else {
debugPrint("No data")
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
debugPrint("Not containing JSON")
return
}
if let idToken = json["id_token"] as? String {
let privateToken = idToken;
debugPrint("Gotten json response dictionary is \(idToken)")
}
}
task.resume()
return privateToken
Currently there is an IDE error on return privateToken saying that I am using an unresolved identifier: privateToken.
How can I take the string idToken and return it as a privateToken for use elsewhere?
Could you use a completion handler like:
func getPrivateToken(completion: #escaping(String) -> (), failure: #escaping (Error) -> ()) {
URLSession.shared.dataTask(with: request) { data, response, error in
guard error == nil else {
debugPrint ("error: \(error!)")
failure(error)
return
}
guard let content = data else {
debugPrint("No data")
failure(NSError(domain: "Your error message here.", code: 401, userInfo: nil))
return
}
guard let json = (try? JSONSerialization.jsonObject(with: content, options: JSONSerialization.ReadingOptions.mutableContainers)) as? [String: Any] else {
debugPrint("Not containing JSON")
failure(NSError(domain: "Your error message here.", code: 401, userInfo: nil))
return
}
if let idToken = json["id_token"] as? String {
completion(idToken)
debugPrint("Gotten json response dictionary is \(idToken)")
}
}.resume()
}
And use it like so:
func exampleFunction() {
self.getPrivateToken(completion: { (token) in
// Do what ever you need with the token here.
print("ID token is: \(token)")
}) { (error) in
// Present error here
}
}

Swift function produces a "SIGILL" on it's return statement

I am writing a piece of code in Swift to hit a public API endpoint to pull back data in JSON and use it in the application. I am using URLSession to do the request and am using an async/await similar paradigm to extract data out of the URLSession callback and place it in a local variable. Then, the function returns the optional dictionary returned by JSONSerialization to the caller.
This code executes perfectly fine outside of a function and run as part of the main program, but as soon as it is moved to a function, the return statement produces a "SIGILL" exit.
I breakpointed to the return statement and found that it is exactly what is throwing this error. Since this is an optional dictionary, I tried just returning an unwrapped version of the dictionary and found the same results. I also tried just returning a blank dictionary and I still get a SIGILL
Functioning:
let url = URL(string: <endpointURL>)!
var tenant: [String: Any]? = nil;
let sem = DispatchSemaphore(value: 1)
sem.wait()
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if let json = json {
print(json)
tenant = json
sem.signal()
} else {
print("ERR: Null JSON")
}
} catch let error as NSError {
print("ERR: " + error.localizedDescription)
}
} else if let error = error {
print("ERR: " + error.localizedDescription);
} else {
print("ERR: Unknown")
}
}
print("resuming")
task.resume()
print("waiting: ")
sem.wait()
print("done waiting")
print(tenant!["tenant_name"]!)
Fails:
let _ = HttpHelper.getTenantFor(tenantId: <someUUID>)
class HttpHelper {
static func getTenantFor(tenantId: String) -> [String:Any]? {
let url = URL(string: <endpointURL>)!
var tenant: [String: Any]? = nil;
let sem = DispatchSemaphore(value: 1)
sem.wait()
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]
if let json = json {
print(json)
tenant = json
sem.signal()
} else {
print("ERR: Null JSON")
}
} catch let error as NSError {
print("ERR: " + error.localizedDescription)
}
} else if let error = error {
print("ERR: " + error.localizedDescription);
} else {
print("ERR: Unknown")
}
}
print("resuming")
task.resume()
print("waiting: ")
sem.wait()
print("done waiting")
return [String:Any]()
}
}
On the functioning code, the app outputs the proper value for the "tenant_name" key in the JSON object and in the failed code I get the following:
Process finished with exit code 132 (interrupted by signal 4: SIGILL)

How to create proper completion handler for server login in swift?

I have an api manager class in my swift application and it has a server login with username and password.
I want to know how to create a completion handler for it that when the server responses with 200 status code, the function handles that response and for example performs a segue in the viewcontroller.
I did not find any tutorials for this. Thanks for your help!
EDIT 1:
What i need is: The completion handler is immediately run when the function is called. I want the completion handler run after server responds.
And this is my login function:
public class func Login(username: String, password: String, complitionHandler: #escaping (Int) -> Void) {
let urlS = "http://server.com/" + "login.php"
let url = URL(string: urlS)
var request = URLRequest(url: url!)
request.httpMethod = "POST"
let body = "username=\(username.lowercased())&password=\(password)"
request.httpBody = body.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else {
print(error!)
print("error")
logedIn = 2
return
}
do{
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
if let parseJson = json {
let code = parseJson["status"] as! String
if code == "200" {
print("loged inn")
logedIn = 1
}else if code == "400" {
print("uuuser/pass error")
logedIn = 0
}
}
}catch{
print("json error")
logedIn = 2
}
}
task.resume()
DispatchQueue.main.async {
complitionHandler(logedIn)
}
}
And how i call the function in my ViewController:
Manager.Login(username: "1", password: "1") { (i) in
switch i {
case 0:
print("user/pass error")
case 1:
print("loged in")
self.performSegue(withIdentifier: "toMain", sender: self)
case 2:
print("json error")
default:
()
}
}
You have all of the pieces in place. You just need to move your call to the completion handler to the correct place:
}catch{
print("json error")
logedIn = 2
}
DispatchQueue.main.async {
complitionHandler(logedIn)
}
}
task.resume()
Also note that method names should start with lowercase letters so your Login function should be named login.
Now you can use this login method like:
login(username: someUsername, password: somePassword) { (result) in
if result == 1 {
// success - do your segue
} else if result == 0 {
// bad username/password
} else {
// some error
}
}