Working on writing a bridge function for registering a new user in Gigya. Or registration flow is set to send a code verification email on registering a new user. This seems to not be firing when registering via the Swift SDK. Am I missing something in the way we are sending the request?
swift...
#objc(registerAccount:password:params:resolve:rejecter:)
func registerAccount(email: String, password: String, params: [String : Any],
_ resolve: #escaping RCTPromiseResolveBlock,
rejecter reject: #escaping RCTPromiseRejectBlock) -> Void {
self.gigya.register(email: email, password: password, params: params) { result in
switch result {
case .success(let data):
resolve(data);
case .failure(let error):
switch error.error {
case .gigyaError(let data):
resolve(data.toDictionary())
default:
reject("E_REGISTER_ACCOUNT", "Registration failed", error.error)
}
}
}
}
objc...
RCT_EXTERN_METHOD(registerAccount:(NSString *)email
password:(NSString *)password
params:(NSDictionary *)params
resolve:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject)
and lastly this gets called by JS...
export async function register() {
const email = 'test-registration#purple.com';
const password = '234567';
const req = await GIGYA_MANAGER.registerAccount(
email,
password,
{
finalizeRegistration: true,
profile: {
email: email,
firstName: 'Stella',
lastName: 'Beaglesmith'
}
}
).catch((err) => console.log(err));
return req;
}
I am able to see new users created in the sites identity access tab, but no code verification email seems to be sent.
For anyone coming across this in the future. When registering via the API, you need to send the verification manually so in Swift you will have a method that looks like this...
#objc(resendVerificationCode:regToken:resolve:rejecter:)
func resendVerificationCode(UID: String, regToken: String,
_ resolve: #escaping RCTPromiseResolveBlock,
rejecter reject: #escaping RCTPromiseRejectBlock) -> Void {
self.gigya.send(api: "accounts.resendVerificationCode", params: [
"UID": UID,
"regToken": regToken
]) { result in
switch result {
case .success(let data):
print(data.values)
case .failure(let error):
print(error)
}
}
}
Related
I am trying to register a user in Firebase, AND add that user to a "users" collection with additional fields. When registering a user, I only want these commands to execute if BOTH of them are successful. For example, I don't want to register a user in Firebase if the user fails to be added to the users collection. But I also don't want the user to be added to the users collection if the firebase createUser function fails.
func register(withEmail email: String, password: String, fullname: String, username: String) {
Auth.auth().createUser(withEmail: email, password: password) { [self] result, error in
if let error = error {
print("Failed to register with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
let data = ["email": email,
"username": username.lowercased(),
"fullname": fullname,
"uid": user.uid,
"listOfUserActions": listOfUserActions]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { _ in
self.didAuthenticateUser = true
}
}
}
The way I have this set up right now, if the user was added to FirebaseAuth but the post to "users" failed, wouldn't this just break the app if I have functionality depending on the "users" collection?
You may use a combination of the methods from the createUser and setData methods to make sure that both of those operations succeed before setting didAuthenticateUser to true.
Here's the modified code:
func register(withEmail email: String, password: String, fullname: String, username: String) {
Auth.auth().createUser(withEmail: email, password: password) { [self] result, error in
if let error = error {
print("Failed to register with error \(error.localizedDescription)")
return
}
guard let user = result?.user else { return }
let data = ["email": email,
"username": username.lowercased(),
"fullname": fullname,
"uid": user.uid,
"listOfUserActions": listOfUserActions]
Firestore.firestore().collection("users")
.document(user.uid)
.setData(data) { error in
if let error = error {
print("Failed to set data with error \(error.localizedDescription)")
return
}
self.didAuthenticateUser = true
}
}
}
I am using Amplify Auth for iOS and attempting to implement the signUp functionality:
public class UserSessionRepository {
func signUp(email: String, password: String) -> AnyPublisher<AuthSignUpResult, AuthError> {
let userAttributes = [AuthUserAttribute(.email, value: email)]
let options = AuthSignUpRequest.Options(userAttributes: userAttributes)
return Amplify.Auth.signUp(username: email, password: password, options: options).resultPublisher
}
}
However, when I attempt to subscribe to the Publisher and remap the return values, the subscribe does not seem to function correctly:
class SignUpEmailService {
#Injected private var userSessionRepository: UserSessionRepository
func signupEmailRequest(to email: String, password: String) -> AnyPublisher<SignupComplete, Error> {
return userSessionRepository.signUp(email: email, password: password)
.map { signupResult in
print("SignUpEmailService.signupEmailRequest: \(signupResult)")
return SignupComplete(result: signupResult.isSignupComplete)
}
.mapError { error in
print("SignUpEmailService.signupEmailRequest: \(error)")
return FooError.authError
}
.eraseToAnyPublisher()
}
}
Neither the map or mapError is actually called.
Thoughts on what I am missing in terms of setting up the Combine publish/subscribe mechanism?
I'm trying to validate user's email and password using two separate function calls.
Both functions return AnyPublisher publishers, and I use combineLatest to collect the returned values (each validate call returns the string it's validating) into a tuple.
Then I'm using flatMap to make a network request to sign the user up using the values returned by combineLatest, however the flatMap operator never gets called.
validator.validate(text: email, with: [.validEmail])
.combineLatest(validator.validate(text: password, with: [.notEmpty]))
.flatMap { credentials in
return self.userSessionRepository.signUp(email: credentials.0, password: credentials.1)
}
.sink(receiveCompletion: { completion in
switch completion {
case .failure(let error):
print(error)
self.indicateErrorSigningIn(error)
case .finished:
self.goToSignInNavigator.navigateToOtp()
}
}, receiveValue: { _ in })
.store(in: &subscriptions)
signUp(email:password:) returns AnyPublisher
Here's the validator function:
public func validate(text: String, with rules: [Rule]) -> AnyPublisher<String, ErrorMessage> {
rules.publisher
.compactMap { $0.check(text) }
.setFailureType(to: ErrorMessage.self)
.flatMap {
Fail<Void, ErrorMessage>(error: ErrorMessage(title: "Error", message: $0.description))
}
.map { text }
.eraseToAnyPublisher()
}
And the signUp function:
public func signUp(email: String, password: String) -> AnyPublisher<Void, ErrorMessage> {
remoteAPI.signUp(email: email, password: password)
.flatMap(dataStore.save)
.mapError { error -> ErrorMessage in
return ErrorMessage(title: "Error", message: error.description)
}
.eraseToAnyPublisher()
}
It calls these two functions:
public func signUp(email: String, password: String) -> AnyPublisher<Confirmation, RemoteError> {
guard email == "john.doe#email.com" else {
return Fail<Confirmation, RemoteError>(error: .invalidCredentials)
.eraseToAnyPublisher()
}
return Just(Confirmation(otp: "", nonce: "abcd"))
.setFailureType(to: RemoteError.self)
.eraseToAnyPublisher()
}
public func save(confirmation: Confirmation) -> AnyPublisher<Void, RemoteError> {
self.nonce = confirmation.nonce
return Empty().eraseToAnyPublisher()
}
I'm not sure what's wrong, though it's likely my not understanding Combine enough, as I've just started learning it recently.
I have figured it out.
The problem was with the validate(text:with:) function.
In the event of an error, the function behaved correctly, but when there was no error the function wasn't emitting any value, and that's why flatMap or any other operator in the pipeline wasn't being invoked.
The reason it wasn't emitting any values boils down to how the check(_:) function, which is called in compactMap, works. It returns an optional string, which is an error message. But if there's no error, there's no string, and thus no value is emitted.
As a result, the call to .map { text } doesn't get evaluated, and the credential doesn't get returned.
I've changed the code to this and now the program behaves correctly:
public func validate(text: String, with rules: [Rule]) -> AnyPublisher<String, ErrorMessage> {
rules.publisher
.setFailureType(to: ErrorMessage.self)
.tryMap { rule -> String in
if let error = rule.check(text) {
throw ErrorMessage(title: "Error", message: error)
}
return text
}
.mapError { error -> ErrorMessage in
return error as! ErrorMessage
}
.eraseToAnyPublisher()
}
Firstly, I'm aware that the implementation has changed in v6 and and I using the seal object as intended, the problem I'm having is that even when following the example to the letter it still gives me the old Cannot convert value of type '(_) -> CustomerLoginResponse' to expected argument type '(_) -> _' error.
Here is my function that returns the promise:
static func makeCustomerLoginRequest(userName: String, password: String) -> Promise<CustomerLoginResponse>
{
return Promise
{ seal in
Alamofire.request(ApiProvider.buildUrl(), method: .post, parameters: ApiObjectFactory.Requests.createCustomerLoginRequest(userName: userName, password: password).toXML(), encoding: XMLEncoding.default, headers: Constants.Header)
.responseXMLObject { (resp: DataResponse<CustomerLoginResponse>) in
if let error = resp.error
{
seal.reject(error)
}
guard let Xml = resp.result.value else {
return seal.reject(ApiError.credentialError)
}
seal.fulfill(Xml)
}
}
}
and here is the function that is consuming it:
static func Login(userName: String, password: String) {
ApiClient.makeCustomerLoginRequest(userName: userName, password: password).then { data -> CustomerLoginResponse in
}
}
You might have to provide more information if you want to chain more than one promises. In v6, you need to use .done if you don't want to continue the promises chain. If you have only one promise with this request then below is the correct implementation.
static func Login(userName: String, password: String) {
ApiClient.makeCustomerLoginRequest(userName: userName, password: password)
.done { loginResponse in
print(loginResponse)
}.catch { error in
print(error)
}
}
Remember, you have to return a promise if you are using .then until you break the chain by using .done. If you want to chain multiple promises then your syntax should look like this,
ApiClient.makeCustomerLoginRequest(userName: userName, password: password)
.then { loginResponse -> Promise<CustomerLoginResponse> in
return .value(loginResponse)
}.then { loginResponse -> Promise<Bool> in
print(loginResponse)
return .value(true)
}.then { bool -> Promise<String> in
print(bool)
return .value("hello world")
}.then { string -> Promise<Int> in
print(string)
return .value(100)
}.done { int in
print(int)
}.catch { error in
print(error)
}
Closed. This question is opinion-based. It is not currently accepting answers.
Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.
Closed 1 year ago.
Improve this question
I’m practicing Swift and have a scenario (and a method) where the result could either be successful or a failure.
It’s a security service class. I have a method where I can authenticate with an email address and password, and want to either return a User instance if the credentials are correct, or throw some form of false value.
I’m a bit confused as my understanding of Swift methods is you need to specify a return type, so I have:
class SecurityService {
static func loginWith(email: String, password: String) -> User {
// Body
}
}
I’ve seen in Go and Node.js methods that return a “double” value where the first represents any errors, and the second is the “success” response. I also know that Swift doesn’t have things like errors or exceptions (but that may have changed since as I was learning an early version of Swift).
What would be the appropriate thing to do in this scenario?
If you want to handle errors that can happen during login process than use the power of Swift error handling:
struct User {
}
enum SecurityError: Error {
case emptyEmail
case emptyPassword
}
class SecurityService {
static func loginWith(email: String, password: String) throws -> User {
if email.isEmpty {
throw SecurityError.emptyEmail
}
if password.isEmpty {
throw SecurityError.emptyPassword
}
return User()
}
}
do {
let user = try SecurityService.loginWith1(email: "", password: "")
} catch SecurityError.emptyEmail {
// email is empty
} catch SecurityError.emptyPassword {
// password is empty
} catch {
print("\(error)")
}
Or convert to optional:
guard let user = try? SecurityService.loginWith(email: "", password: "") else {
// error during login, handle and return
return
}
// successful login, do something with `user`
If you just want to get User or nil:
class SecurityService {
static func loginWith(email: String, password: String) -> User? {
if !email.isEmpty && !password.isEmpty {
return User()
} else {
return nil
}
}
}
if let user = SecurityService.loginWith(email: "", password: "") {
// do something with user
} else {
// error
}
// or
guard let user = SecurityService.loginWith(email: "", password: "") else {
// error
return
}
// do something with user
Besides the standard way to throw errors you can use also an enum with associated types as return type
struct User {}
enum LoginResult {
case success(User)
case failure(String)
}
class SecurityService {
static func loginWith(email: String, password: String) -> LoginResult {
if email.isEmpty { return .failure("Email is empty") }
if password.isEmpty { return .failure("Password is empty") }
return .success(User())
}
}
And call it:
let result = SecurityService.loginWith("Foo", password: "Bar")
switch result {
case .Success(let user) :
print(user)
// do something with the user
case .Failure(let errormessage) :
print(errormessage)
// handle the error
}
Returning a result enum with associated values, throwing exception, and using a callback with optional error and optional user, although valid make an assumption of login failure being an error. However thats not necessarily always the case.
Returning an enum with cases for success and failure containing result and error respectively is almost identical as returning an optional User?. More like writing a custom optional enum, both end up cluttering the caller. In addition, it only works if the login process is synchronous.
Returning result through a callBack, looks better as it allows for the operation to be async. But there is still error handling right in front of callers face.
Throwing is generally preferred than returning an error as long as the scope of the caller is the right place to handle the error, or at least the caller has access to an object/method that can handle this error.
Here is an alternative:
func login(with login: Login, failure: ((LoginError) -> ())?, success: (User) -> ()?) {
if successful {
success?(user)
} else {
failure?(customError)
}
}
// Rename with exactly how this handles the error if you'd have more handlers,
// Document the existence of this handler, so caller can pass it along if they wish to.
func handleLoginError(_ error: LoginError) {
// Error handling
}
Now caller can; simply decide to ignore the error or pass a handler function/closure.
login(with: Login("email", "password"), failure: nil) { user in
// Ignores the error
}
login(with: Login("email", "password"), failure: handleLoginError) { user in
// Lets the error be handled by the "default" handler.
}
PS, Its a good idea to create a data structure for related fields; Login email and password, rather individually setting the properties.
struct Login {
typealias Email = String
typealias Password = String
let email: Email
let password: Password
}
To add an answer to this question (five years later), there’s a dedicated Result type for this exact scenario. It can return the type you want on success, or type an error on failure.
It does mean re-factoring some code to instead accept a completion handler, and then enumerating over the result in that callback:
class SecurityService {
static func loginWith(email: String, password: String, completionHandler: #escaping (Result<User, SecurityError>) -> Void) {
// Body
}
}
Then in a handler:
securityService.loginWith(email: email, password: password) { result in
switch result {
case .success(let user):
// Do something with user
print("Authenticated as \(user.name)")
case .failure(let error):
// Do something with error
print(error.localizedDescription)
}
}
I think that the result of calling loginWith could be derived from a network request, here is code that I could do in the scenario you presented:
Helper classes:
struct User {
var name: String
var email: String
}
class HTTP {
static func request(URL: String, method: String, params: [String: AnyObject], callback: (error: NSError?, result: [String:AnyObject]?) -> Void) -> Void {
// network request
}
}
class SecurityService {
static func loginWith(email: String, password: String, callback: (error: NSError?, user: User?) -> Void) -> Void {
let URL = ".."
let params = [
"email": email,
"password": password
]
HTTP.request(URL, method: "POST", params: params) { (error, result) in
if let error = error {
callback(error: error, user: nil)
} else {
guard let JSON = result else {
let someDomain = "some_domain"
let someCode = 100
let someInfo = [NSLocalizedDescriptionKey: "No results were sent by the server."]
let error = NSError(domain: someDomain, code: someCode, userInfo: someInfo)
callback(error: error, user: nil)
return
}
guard let name = JSON["name"] as? String, email = JSON["email"] as? String else {
let someDomain = "some_domain"
let someCode = 100
let someInfo = [NSLocalizedDescriptionKey: "No user properties were sent by the server."]
let error = NSError(domain: someDomain, code: someCode, userInfo: someInfo)
callback(error: error, user: nil)
return
}
let user = User(name: name, email: email)
callback(error: nil, user: user)
}
}
}
}
Using the SecurityService class:
SecurityService.loginWith("someone#email.com", password: "123456") { (error, user) in
if let error = error {
print(error)
} else {
guard let user = user else {
print("no user found.")
return
}
print(user)
}
}