Error handler not called for promise - ios

I have a service, which fails when I enter bad login credentials. However, my Promise error handler does not get called.
I don't seem to grasp what's wrong with my code so that the error callback is never reached.
Service
func loadRepositories() -> Promise<[Repository]>{
return Promise { fullfill, reject in
manager.request(Method.GET, baseURL + "/api/1.0/user/repositories")
.authenticate(user: username, password: password)
.responseArray { (response: Response<[Repository], NSError>) in
switch response.result{
case .Success(let value):
fullfill(value)
case .Failure(let e):
// The breakpoint here is reached.
reject(e)
}
}
}
}
Handling
firstly{
service!.loadRepositories()
}.then { repositories -> Void in
loginVC.dismissViewControllerAnimated(true, completion: nil)
self.onLoginSuccessful()
}.always{
// Always gets called
loginVC.isSigningIn = false
}.error { error in
// I never get here even though `reject(e)` is called from the service.
loginVC.errorString = "Login went wrong"
}

By default error does not handle cancellation errors and bad credentials is exactly a cancellation error. If you put print(e.cancelled) before reject(e), you will see that it will return true. If you give a wrong URL for example, you will receive false. In order to get around this, just replace
}.error { error in
with:
}.error(policy: .AllErrors) { error in
and error will be triggered then. In case you use recover, cancellation errors will be handled by default. You can check https://github.com/mxcl/PromiseKit/blob/master/Sources/Promise.swift#L367 for more information.

Related

How to customize Amplify Auth Error Messages

I am attempting to implement amplify auth on iOS, and what I would like to be able to do is customize the error message that is displayed to a user when authentication fails, as the default error messages are not end-user friendly, but I have no idea how to do this.
For instance, my signIn method is as follows:
func signIn(username: String) {
Amplify.Auth.signIn(username: username, password: "bla") { [weak self] result in
switch result {
case .success (let result):
if case .confirmSignInWithCustomChallenge(_) = result.nextStep {
DispatchQueue.main.async {
self?.showConfirmationSignInView()
}
} else {
print("Sign in succeeded")
}
case .failure(let error):
print (error)
}
}
}
Now in the .failure case, instead of printing the error, I would ideally like to determine if the error is a userNotFound error, or something else. I can't find any info in the docs on this. Any help would be appreciated.
You can do it by checking the error.code. for example, for a user who did not confirm the email if he tries to login then error.code will have UserNotConfirmedException string value. Amplify auth returns different exception codes for different types of errors. You can see all the exceptions from this link. Although it is for flutter, the exception code is identical for any framework. I have used these exception codes in react.

The operation couldn’t be completed. (AWSMobileClient.AWSMobileClientError error 20.)

I'm currently running into this error when implementing the AWSMobileClient signUp function. I haven't really altered the code sample from the AWS page describing how to implement it, other than changing the attributes to fit my user pool attribute requirements.
First in viewDidLoad, I initialize the mobile client like so:
AWSMobileClient.sharedInstance().initialize { (userState, error) in
if let userState = userState {
print("UserState: \(userState.rawValue)")
} else if let error = error {
print("error: \(error.localizedDescription)")
}
}
Then I have the function for signing up. This is what the code looks like (I encapsulate this in a function called signUpUser):
AWSMobileClient.sharedInstance().signUp(username: userEmail,
password: userPass,
userAttributes: ["email":userEmail, "given_name":userFirstName, "family_name": userLastName, "custom:school":userSchool]) { (signUpResult, error) in
if let signUpResult = signUpResult {
switch(signUpResult.signUpConfirmationState) {
case .confirmed:
print("User is signed up and confirmed.")
case .unconfirmed:
print("User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case .unknown:
print("Unexpected case")
}
} else if let error = error {
if let error = error as? AWSMobileClientError {
switch(error) {
case .usernameExists(let message):
print(message)
default:
break
}
}
print("\(error.localizedDescription)")
}
When I run the app on my iPhone, I call this function when the "Sign Up Button" is clicked. In the debug window, I get the following error:
The operation couldn’t be completed. (AWSMobileClient.AWSMobileClientError error 20.)
That's the only info that appears in the Xcode console. Does anyone know how to go about debugging or fixing this?
EDIT: I'm not sure what the issue was that caused this error. I started a fresh project, set up a new cognito pool and backend services, and ported over the code from this project, which resulted in everything working perfectly. The error may have been from incorrectly setting up the user pool, or perhaps not allowing unauthorized access to the sign up function (not sure if I had that set to "No").
If you exhaust the rest of the switch case there, you will be able to see what exactly is the error coming back from the service.
reference: https://stackoverflow.com/a/59521025/2464632

How to describe error from Alamofire using switch case in Swift?

I want to give info to the user about the error that occurred while sending a request to the server. I use Alamofire.
The code is like below:
Alamofire.request(url, method: methodUsed, parameters: parameters).responseData { (response) in
switch response.result {
case .failure(let error) :
// I want to the describe the error in here
case .success(let value) :
let json = JSON(value)
completion(.success(json))
}
}
I have tried but I can't switch the error. I want something similar to this to be placed in the code above:
switch error {
case .NoSignal : // give alert to the user about the signal
case .ServerError : // give alert to the user about server error
}
For some case I want to inform the user to take some action on the alert but I don't know what the available cases are and I don't know the syntax that has to be used.
As per Jayesh Thanki says you can identify the server error using status code and for internet connectivity you can use NetworkReachabilityManager of Alamofire. Write following code in viewDidLoad():
var networkManager: NetworkReachabilityManager = NetworkReachabilityManager()!
networkManager.startListening()
networkManager.listener = { (status) -> Void in
if status == NetworkReachabilityManager.NetworkReachabilityStatus.notReachable {
print("No internet available")
}else{
print("Internet available")
}
You can identify error using status code. response.response.statusCode.
There is lots of HTTP status code and using them you can inform end user with alert.
Here is wikipedia link for list of status code.
For example is status code is 200 OK then its successful HTTP request
and status code is 500 Internal Server Error then its server related error.
You can also provide error description using response.result.error.localizedDescription if error is available.

Problems With Error Casting In Swift

Not sure if this a bug or an intended feature.
To create a user with an email and password in Firebase, I've been using the following code:
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
guard let error = error as? FIRAuthErrorCode else { return } // ALWAYS FAILS
...code...
}
...code...
}
The error parameter in the completion handler for the method cannot be cast as FIRAuthErrorCode; it always fails. Is that a bug, or is that the expected behaviour?
Edit: I am aware that error codes can be used to distinguish between the different types of FIRAuthErrorCode errors. It's just not readable, and it doesn't make much sense for the error parameter in the completion handler to be not of be of type FIRAuthErrorCode. The cases and error codes of FIRAuthErrorCode can be found here.
Have you tried using guard let error = error as! FIRAuthErrorCode else { return } to force the casting and check whether the return is nil or not?
After contacting Firebase support about the issue, they've said that the errors that are passed back in completion handlers are just Error objects. They weren't FIRAuthErrorCode objects. To test for the various FIRAuthErrorCode cases, one would have to do something like this:
FIRAuth.auth()?.signIn(withEmail: email, password: password) { (user, error) in
if let error = error {
guard let error = FIRAuthErrorCode(rawValue: error._code) else {
fatalError("This should never be executed")
}
switch error {
case .errorCodeInvalidEmail: ...
case .errorCodeWrongPassword: ...
default: ...
}
...code...
}
...code...
}
^ This preserves readability and makes error handling more intuitive. And, there is no need for error casting!
You should check the documentation of signIn method in firebase in order to check the all possible type of error this method can send and then check those type of errors in guard block in your code.
Try this, it's how I do my login and it seems to work great.
FIRAuth.auth()?.signIn(withEmail: EmailField.text!, password: PasswordField.text!, completion: { user, error in
if error == nil {
print("Successfully Logged IN \(user!)")
self.performSegue(withIdentifier: "Login", sender: self)
}
})
I just check that there's not an error with the signin process, then perform my segue.

Using retryWhen to update tokens based on http error code

I found this example on How to refresh oauth token using moya and rxswift which I had to alter slightly to get to compile. This code works 80% for my scenario. The problem with it is that it will run for all http errors, and not just 401 errors. What I want is to have all my other http errors passed on as errors, so that I can handle them else where and not swallow them here.
With this code, if I get a HttpStatus 500, it will run the authentication code 3 times which is obviously not what I want.
Ive tried to alter this code to handle only handle 401 errors, but it seem that no matter what I do I can't get the code to compile. It's always complaining about wrong return type, "Cannot convert return expression of type Observable<Response> to return type Observable<Response>" which makes no sense to me..
What I want: handle 401, but stop on all other errors
import RxSwift
import KeychainAccess
import Moya
public extension ObservableType where E == Response {
/// Tries to refresh auth token on 401 errors and retry the request.
/// If the refresh fails, the signal errors.
public func retryWithAuthIfNeeded() -> Observable<E> {
return self.retryWhen {
(e: Observable<ErrorType>) in
return Observable.zip(e, Observable.range(start: 1, count: 3), resultSelector: { $1 })
.flatMap { i in
return AuthProvider.sharedInstance.request(
.LoginFacebookUser(
accessToken: AuthenticationManager.defaultInstance().getLoginTokenFromKeyChain(),
useFaceBookLogin: AuthenticationManager.defaultInstance().isFacebookLogin())
)
.filterSuccessfulStatusCodes()
.mapObject(Accesstoken.self)
.catchError {
error in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error {
if response.statusCode == 401 {
// Force logout after failed attempt
log.debug("401:, force user logout")
NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
}
}
return Observable.error(error)
}.flatMapLatest({
token -> Observable<Accesstoken> in
AuthenticationManager.defaultInstance().storeServiceTokenInKeychain(token)
return Observable.just(token)
})
}
}
}
}
Compilation Error
Which line has the compilation error? It seems to me that it would be this line:
.catchError {
error in
//...
return Observable.error(error) // is this the line causing the compilation error?
}
If so, it's probably because catchError is expecting the block to return an Observable<Response> with which it can continue in case of an error, and not an Observable<ErrorType>.
In either case, it helps to annotate your code with more types so that you can pinpoint problems like this, as well as help the Swift compiler, which often can't figure out these kinds of things on its own. So something like this would have helped you:
.catchError {
error -> Observable<Response> in
//...
return Observable.error(error) // Swift should have a more accurate and helpful error message here now
}
Note that I'm only showing you what the error is and how to get Xcode to give you better error messages. What you're trying to return still isn't correct.
Only retry on 401
I'm not sure why you're expecting this code to treat 401 differently (other than posting to the notification center and logging). As it is, you're catching the error, but you're always returning an Observable with an Error event at the end (return Observable.error(error)), so it will never retry.
To get 401 to retry, you should return an Observable from the retryWhen block, which will send a Next event (signifying that you want to retry). For all other status codes, that Observable should send an Error (as you're currently doing), which will signify that you don't want to retry, and that you'd like the error propagated.
So something like this:
.retryWhen { errorObservable -> Observable<ErrorType> in
log.debug("ReAuth error: \(error)")
if case Error.StatusCode(let response) = error where response.statusCode == 401 {
log.debug("401:, force user logout")
NSNotificationCenter.defaultCenter().postNotificationName(Constants.Notifications.userNotAuthenticated, object: nil, userInfo: nil)
// If `401`, then return the `Observable<ErrorType>` which was given to us
// It will emit a `.Next<ErrorType>`
// Since it is a `.Next` event, `retryWhen` will retry.
return errorObservable
}
else {
// If not `401`, then `flatMap` the `Observable<ErrorType>` which
// is about to emit a `.Next<ErrorType>` into
// an `Observable<ErrorType>` which will instead emit a `.Error<ErrorType>`.
// Since it is an `.Error` event, `retryWhen` will *not* retry.
// Instead, it will propagate the error.
return errorObservable.flatMap { Observable.error($0) }
}
}
When you catchError, if it isn't a 401 error, then you simply need to throw the error. That will send the error down the pipe.
There's a different solution to solve this problem without using Observable. It's written on pure RxSwift and returns a classic error in case of fail.
The easy way to refresh session token of Auth0 with RxSwift and Moya
The main advantage of the solution is that it can be easily applicable for different services similar to Auth0 allowing to authenticate users in mobile apps.

Resources