Swift - AWS Authentication fetchSession - make global function - ios

I am using AWS authentication on my app. And I want to create a global function for fetching the user token so I can access it anywhere in the app whenever I need to fetch the new id token, the token is changing every 5 mins so I need to call it from time to time. I do it like this
func fetchSession() {
Amplify.Auth.fetchAuthSession { result in
do {
let session = try result.get()
// Get cognito user pool token
if let cognitoTokenProvider = session as? AuthCognitoTokensProvider {
let tokens = try cognitoTokenProvider.getCognitoTokens().get()
return tokens.idToken
}
} catch {
print("Fetch auth session failed with error - \(error)")
}
}
}
but I am getting this error
Unexpected non-void return value in void function
I also try to make it like this
func fetchSession() {
let token = Amplify.Auth.fetchAuthSession { result -> AnyObject in
do {
let session = try result.get()
// Get cognito user pool token
if let cognitoTokenProvider = session as? AuthCognitoTokensProvider {
let tokens = try cognitoTokenProvider.getCognitoTokens().get()
return tokens.idToken
}
} catch {
print("Fetch auth session failed with error - \(error)")
}
}
}
and this is the error I got
Cannot convert value of type '(AmplifyOperation<AuthFetchSessionRequest, AuthSession, AuthError>.OperationResult) -> AnyObject' (aka '(Result<AuthSession, AuthError>) -> AnyObject') to expected argument type '((AmplifyOperation<AuthFetchSessionRequest, AuthSession, AuthError>.OperationResult) -> Void)?' (aka 'Optional<(Result<AuthSession, AuthError>) -> ()>')

Looks like you need to specify what fetchSession will return. Right now you are saying it will return Void because you have neglected the return type.
Here is an example of specifying the returned type.
func fetchSession() -> String? {
// ...
do {
// ...
return tokens.idToken // I assume idToken is a String
} catch {
print("Fetch auth session failed with error - \(error)")
return nil
}
}
Do not forgot to return nil in the catch or rethrow the error.
Swift docs on functions: https://docs.swift.org/swift-book/LanguageGuide/Functions.html

Related

Handle error in flatmap when using chaining of publishers

I am trying use Combine in my Swift application and have problem in my following code:
//Get it from local storage(realm)
voucherCodeStorageProvider.fetchVoucherCode(voucherId).flatMap { (code) -> AnyPublisher<String?, Error> in
if let code = code {
return Just(code).setFailureType(to: Error.self).eraseToAnyPublisher()
}
//If not found in storage, Get it from api
return self.voucherCodeProvider.fetchVoucherCode(voucherId: voucherId).handleEvents( receiveOutput: { code in
guard let code = code else { return }
_ = self.voucherCodeStorageProvider.saveVoucherCode(code, voucherId)
}).mapError{ $0 as Error }.eraseToAnyPublisher()
}.eraseToAnyPublisher()
Above fetchVoucherCode is currently publishing an error, now I want to catch that error and do task that I perform after nil check in my code. But I am not able to catch error here. How can I catch an error in flatmap and can perform some operation like I have above?
I did it using catch before flatmap. Below is my working code:
voucherCodeStorageProvider.fetchVoucherCode(voucherId).catch { _ in
return self.voucherCodeProvider.fetchVoucherCode(voucherId: voucherId).handleEvents( receiveOutput: { code in
guard let code = code else { return }
_ = self.voucherCodeStorageProvider.saveVoucherCode(code, voucherId)
}).mapError{ $0 as Error }.eraseToAnyPublisher()
}.flatMap { (code) -> AnyPublisher<String?, Error> in
return Just(code).setFailureType(to: Error.self).eraseToAnyPublisher()
}.eraseToAnyPublisher()

AWS Cognito Import throw userNotFound

I'm trying to migrate a batch of users into Cognito via a CSV (i.e. one-time import). This part works successfully and I can see the users in the User Pool. As expected, I see the user with a status of "Enabled / RESET_REQUIRED". So far so good, right?
When I try to sign-in to the user in my mobile app, it throws an error of "userNotFound" instead of the "passwordResetRequired" which would trigger the proper flow.
Has anyone seen this? Or know how to solve this problem?
Just for grins, here is my code:
private func signInWithAWS(email: String, password: String) -> Observable<AWSSignInResult> {
Observable<AWSSignInResult>.create { observer in
AWSMobileClient.default().signIn(
username: email.lowercased(),
password: password,
completionHandler: { signInResult, error in
if let signInResult = signInResult {
switch signInResult.signInState {
case .signedIn:
observer.onNext(.success)
observer.onCompleted()
default:
observer.onCompleted()
}
} else if let error = error as? AWSMobileClientError {
if case AWSMobileClientError.userNotConfirmed = error {
observer.onNext(.unconfirmed)
observer.onCompleted()
return
} else if case AWSMobileClientError.passwordResetRequired = error {
observer.onNext(.resetPasswordRequired)
observer.onCompleted()
}
let message = getCognitoErrorMessage(error: error)
observer.onError(AuthServiceError.signIn(message))
}
}
)
return Disposables.create()
}
}
It fails the "userNotConfirmed" and "passwordResetRequired" checks into the generic handler because the actual error is "userNotFound".

Proper way to throw error from my closure?

I'm actually trying to some code logic with swift for training purpose at the moment, I was wondering what is the proper way to throw my error from my init ?
So the flow is Controller ask for account creation when initializing Model is asking my sql manager to create the account and this method return the result from a closure.
But something feels wrong, should I just use a return from the sql manager who contained both my Int? and Error? ?
init(_ username: String, _ password: String) throws {
self.id = 0
self.username = username
self.password = password
var toThrow: Error? = nil
// Register in database
userManager.create(self) { (id: Int?, err: Error?) in
Thread.sleep(forTimeInterval: 10)
if let error = err {
// Register in database goes wrong
debugPrint("Handle error from user creation...")
toThrow = error
} else {
// There is no id and no error ?
guard let _ = id else { return }
self.id = id!
}
}
if let error = toThrow {
throw error
}
}
If you are on Swift 5 you could look into using Result and define your closure like
(id: Int) -> Result<Int, Error>
and change your code to
userManager.create(self) { (id: Int?) -> Result<Int, Error> in
Thread.sleep(forTimeInterval: 10)
if let error = err {
// Register in database goes wrong
debugPrint("Handle error from user creation...")
return .failure(error)
} else {
// There is no id and no error ?
guard let _ = id else { return }
return .success(id)
}
}
If you have your own error enum for the Db class like
enum DbError {
case create
case update
//...
}
Then you can use that type in the closure declaration
(id: Int?) -> Result<Int, DbError>
and return a specific error for this action
return .failure(.create)
Note that I haven't compiled this so consider it an example
Here is the solution if people want to see:
#IBAction func didPressRegister() {
guard let username = usernameField.text else { return }
guard let password = passwordField.text else { return }
let user = UserModel(username, password)
userManager.create(user) { result in
switch(result) {
case .failure(let error):
// TODO: UIAlert
debugPrint(error)
case .success(let int):
// TODO: Generate user token and redirect main
debugPrint(int)
}
}
}
// TODO
public func create(_ user: UserModel, _ complete: #escaping (Result<Int, Error>) -> ()) {
debugPrint("Requested to create the user... \(user)")
complete(.failure(toThrow.ACCOUNT_ERROR))
}

Swift: Cannot invoke 'filter' with an argument list of type '((Any) throws -> Bool)'

I have a fetch request that comes back with some data, that I would like to filter, like so:
// Set up fetch request
let finalVariants : [DBVariant]
do {
let variants = try context.fetch(request)
finalVariants = variants.filter() { $0.variant == true }
} catch let error as NSError {
// Handle error
finalVariants = []
}
Writing this code, Xcode gives me the error in the title:
Cannot invoke 'filter' with an argument list of type '((Any) throws -> Bool)'
The code in the filter block does not throw, moving the filter block into a separate var with the signature (Any)->Bool did not help.
The answer turned out to be that I wasn't unwrapping and assigning type to the optional result of the fetch request, so the working code looks like:
// Set up fetch request
let finalVariants : [DBVariant]
do {
let variantsResult = try context.fetch(request)
if let variants = variantsResult as? [DBVariant] {
finalVariants = variants.filter() { $0.variant == true }
} else {
finalVariants = []
}
} catch let error as NSError {
// Handle error
finalVariants = []
}

Variables assigned in else clause not being passed via return in Swift

I am having an issue using Xcode 6.2 and Swift where I have a tuple that I am returning from a function. I have a subfunction that is running inside the function that is authenticating a user into our datastore and then returning the authentication tokens. If the user does not authenticate, i.e., has an error, then I am returning that correctly to the calling function. If the user passes authentication then I am only passing nil back to the calling controller even though I am making the same variable assignments. Here is the code that we are using:
func login(email:String, password:String) -> (uid: String?, provider: String?, error: NSError?) {
var errorStatement: NSError?
var provider: String?
var testResult: String?
var authData: FAuthData
ref.authUser(email, password: password) {
error, authData in
if error != nil {
// an error occured while attempting login
println("error is: \(error)")
errorStatement = error
testResult = "failed"
provider = "Error"
} else {
// user is logged in, check authData for data
testResult = "passed"
provider = authData.provider
userUID = authData.uid
println("User uid = \(userUID) and provider = \(provider)")
}
}
return (testResult, provider, errorStatement)
}
I am getting perfectly what I would expect when it has an error, but nothing when it is fine. I know this is going to be some simple fix but I cannot seem to find it. Thanks for any help as I am new to this language.
Here is the corrected code after I implemented the completion as suggested by #rdelmar
func login(email:String?, password:String?, completion: (result: String?, errorDesc: String?) -> Void) {
var errorStatement: String?
var testResult: String?
ref.authUser(email, password: password) {
error, authData in
if error != nil {
// an error occured while attempting login
println("error is: \(error)")
println(error.domain)
errorStatement = "errpor"//error.localizedDescription
testResult = "failed"
completion(result: testResult, errorDesc: errorStatement) //send the calling function the attached information
} else {
// user is l ogged in, check authData for data
testResult = "passed"
userUID = authData.uid
completion(result: testResult, errorDesc: errorStatement)//send the calling function the attached information
}
}
}

Resources