Commit 0de52659 authored by Silbino Goncalves Matado's avatar Silbino Goncalves Matado Committed by Silbino Goncalves Matado

Lookup and register name: add services and adapters

Add NameService and NameRegistrationAdapter to :

- Verify if the username is valid and available to create a new user
- Register a new username into the blockchain.

Add RegistrationState observation from the daemon to verify if the
account is properly created.

Change-Id: I5a66dde2576391b5ec2dc242fb544dc4fe680d9e
parent fd9f3ab1
This diff is collapsed.
......@@ -31,7 +31,7 @@ enum AccountModelError: Error {
/**
A class representing an account.
*/
class AccountModel {
class AccountModel : Equatable {
// MARK: Public members
let id: String
......@@ -49,25 +49,18 @@ class AccountModel {
}
init(withAccountId accountId: String,
details: Dictionary<String, String>,
volatileDetails: Dictionary<String, String>,
credentials: Array<Dictionary<String, String>>,
details: AccountConfigModel,
volatileDetails: AccountConfigModel,
credentials: [AccountCredentialsModel],
devices: Dictionary<String, String>) throws {
self.id = accountId
self.details = AccountConfigModel(withDetails: details)
self.volatileDetails = AccountConfigModel(withDetails: details)
for credential in credentials {
do {
let cred = try AccountCredentialsModel(withRawaData: credential)
credentialDetails.append(cred)
} catch CredentialsError.NotEnoughData {
print("Not enough data to build a credential object.")
throw CredentialsError.NotEnoughData
} catch {
print("Unexpected error.")
throw AccountModelError.UnexpectedError
}
}
self.details = details
self.volatileDetails = volatileDetails
self.devices = devices
}
public static func ==(lhs: AccountModel, rhs: AccountModel) -> Bool {
return lhs.id == rhs.id
}
}
......@@ -54,83 +54,50 @@ class CreateRingAccountViewModel {
var password = Variable<String>("")
var repeatPassword = Variable<String>("")
var usernameValid :Observable<Bool> {
return username.asObservable().map({ username in
return !username.isEmpty
})
}
var passwordValid :Observable<Bool> {
return Observable<Bool>.combineLatest(self.username.asObservable(),
self.password.asObservable(),
self.repeatPassword.asObservable())
{ (username, password, repeatPassword) in
return password.characters.count >= 6
}
}
var passwordsEqual :Observable<Bool> {
return Observable<Bool>.combineLatest(self.password.asObservable(),
self.repeatPassword.asObservable())
{ password, repeatPassword in
return password == repeatPassword
}
}
var passwordValid :Observable<Bool>!
var passwordsEqual :Observable<Bool>!
var canCreateAccount :Observable<Bool>!
var registerUsername = Variable<Bool>(true)
var canCreateAccount :Observable<Bool> {
return Observable<Bool>.combineLatest(self.registerUsername.asObservable(),
self.usernameValid,
self.passwordValid,
self.passwordsEqual)
{ registerUsername, usernameValid, passwordValid, passwordsEquals in
if registerUsername {
return usernameValid && passwordValid && passwordsEquals
} else {
return passwordValid && passwordsEquals
}
}
}
var hasNewPassword :Observable<Bool>!
var hidePasswordError :Observable<Bool>!
var hideRepeatPasswordError :Observable<Bool>!
var registerUsername = Variable<Bool>(true)
/**
The nameService instance injected in initializer.
*/
fileprivate var nameService: NameService
//Observes if the field is not empty
var hasNewPassword :Observable<Bool> {
return self.password.asObservable().map({ password in
return password.characters.count == 0
})
}
//MARK: - Rx Variables and Observers
//Observes if the password is valid and is not empty to show the error message
var hidePasswordError :Observable<Bool> {
return Observable<Bool>.combineLatest(self.passwordValid, hasNewPassword)
{ isPasswordValid, hasNewPassword in
return isPasswordValid || hasNewPassword
}
}
/**
Message presented to the user in function of the status of the current username lookup request
*/
var usernameValidationMessage :Observable<String>!
//Observes if the password is valid and is not empty to show the error message
var hideRepeatPasswordError :Observable<Bool> {
return Observable<Bool>.combineLatest(self.passwordValid, self.passwordsEqual) { isPasswordValid,
isPasswordsEquals in
return !isPasswordValid || isPasswordsEquals
}
}
//MARK: -
/**
Default constructor
*/
init(withAccountService accountService: AccountsService) {
init(withAccountService accountService: AccountsService, nameService: NameService) {
self.account = nil
self.accountService = accountService
self.nameService = nameService
self.initObservables()
self.initObservers()
}
/**
Constructor with AccountModel.
*/
init(withAccountService accountService: AccountsService,
accountModel account: AccountModel?) {
accountModel account: AccountModel?, nameService: NameService) {
self.account = account
self.accountService = accountService
self.nameService = nameService
self.initObservables()
self.initObservers()
}
/**
......@@ -163,9 +130,25 @@ class CreateRingAccountViewModel {
if event.eventType == ServiceEventType.AccountAdded {
print("Account added.")
}
if event.eventType == ServiceEventType.AccountsChanged {
onSuccessCallback?()
}
if event.eventType == ServiceEventType.RegistrationStateChanged {
if event.getEventInput(ServiceEventInput.RegistrationState) == Unregistered {
//Register username
if (self?.registerUsername.value)! {
self?.nameService
.registerName(withAccount: (self?.accountService.currentAccount?.id)!,
password: (self?.password.value)!,
name: (self?.username.value)!)
}
}
}
}, onError: { error in
onErrorCallback?(error)
})
......@@ -173,8 +156,9 @@ class CreateRingAccountViewModel {
//~ Launch the action.
do {
try self?.accountService.addRingAccount(withUsername: nil,
password: "coucou")
//Add account
try self?.accountService.addRingAccount(withUsername: self?.username.value,
password: (self?.password.value)!)
}
catch {
onErrorCallback?(error)
......@@ -186,4 +170,76 @@ class CreateRingAccountViewModel {
.addDisposableTo(disposeBag)
}
/**
Init obsevables needed to validate the user inputs for account creation
*/
func initObservables() {
self.passwordValid = password.asObservable().map { password in
return password.characters.count >= 6
}.shareReplay(1).observeOn(MainScheduler.instance)
self.passwordsEqual = Observable<Bool>.combineLatest(self.password.asObservable(),
self.repeatPassword.asObservable()) { password,repeatPassword in
return password == repeatPassword
}.shareReplay(1).observeOn(MainScheduler.instance)
self.canCreateAccount = Observable<Bool>.combineLatest(self.registerUsername.asObservable(),
self.nameService.usernameValidationStatus,
self.passwordValid,
self.passwordsEqual)
{ registerUsername, usernameValidationStatus, passwordValid, passwordsEquals in
if registerUsername {
return usernameValidationStatus == .valid && passwordValid && passwordsEquals
} else {
return passwordValid && passwordsEquals
}
}.shareReplay(1).observeOn(MainScheduler.instance)
self.usernameValidationMessage = self.nameService.usernameValidationStatus
.asObservable().map ({ status in
switch status {
case .lookingUp:
return NSLocalizedString("LookingForUsernameAvailability",
tableName: LocalizedStringTableNames.walkthrough,
comment: "")
case .invalid:
return NSLocalizedString("InvalidUsername",
tableName: LocalizedStringTableNames.walkthrough,
comment: "")
case .alreadyTaken:
return NSLocalizedString("UsernameAlreadyTaken",
tableName: LocalizedStringTableNames.walkthrough,
comment: "")
default:
return ""
}
}).shareReplay(1).observeOn(MainScheduler.instance)
hasNewPassword = self.password.asObservable().map({ password in
return password.characters.count > 0
})
hidePasswordError = Observable<Bool>.combineLatest(self.passwordValid, hasNewPassword) { isPasswordValid, hasNewPassword in
return isPasswordValid || !hasNewPassword
}
let hasRepeatPassword = self.repeatPassword.asObservable().map({ repeatPassword in
return repeatPassword.characters.count > 0
})
hideRepeatPasswordError = Observable<Bool>.combineLatest(self.passwordValid,self.passwordsEqual, hasRepeatPassword) { isPasswordValid, isPasswordsEquals, hasRepeatPassword in
return !isPasswordValid || isPasswordsEquals || !hasRepeatPassword
}
}
/**
Init observers needed to validate the user inputs for account creation
*/
func initObservers() {
self.username.asObservable().subscribe(onNext: { [unowned self] username in
self.nameService.lookupName(withAccount: "", nameserver: "", name: username)
}).addDisposableTo(disposeBag)
}
}
......@@ -28,6 +28,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
static let daemonService = DaemonService(dRingAdaptor: DRingAdapter())
static let accountService = AccountsService(withAccountAdapter: AccountAdapter())
static let nameService = NameService(withNameRegistrationAdapter: NameRegistrationAdapter())
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
SystemAdapter().registerConfigurationHandler()
......@@ -123,8 +124,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: - Ring Daemon
fileprivate func startDaemon() {
do {
try AppDelegate.daemonService.startDaemon()
AppDelegate.accountService.loadAccounts()
} catch StartDaemonError.InitializationFailure {
print("Daemon failed to initialize.")
} catch StartDaemonError.StartFailure {
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G1212" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="qdG-Sd-QaE">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="11762" systemVersion="15G31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="qdG-Sd-QaE">
<device id="retina4_0" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
......
......@@ -36,3 +36,5 @@
"PasswordCharactersNumberError" = "6 characters minimum";
"PasswordNotMatchingError" = "Passwords do not match";
"LookingForUsernameAvailability" = "Looking for username availability...";
"InvalidUsername" = "Invalid username";
"UsernameAlreadyTaken" = "Username already taken";
......@@ -25,6 +25,7 @@
#import "Utils.h"
#import "dring/configurationmanager_interface.h"
#import "RegistrationResponse.h"
@implementation AccountAdapter
......@@ -51,6 +52,18 @@ static id <AccountAdapterDelegate> _delegate;
[AccountAdapter.delegate accountsChanged];
}
}));
confHandlers.insert(exportable_callback<ConfigurationSignal::RegistrationStateChanged>([&](const std::string& account_id, const std::string& state, int detailsCode, const std::string& detailsStr) {
if (AccountAdapter.delegate) {
RegistrationResponse* response = [RegistrationResponse new];
response.accountId = [NSString stringWithUTF8String:account_id.c_str()];
response.state = [NSString stringWithUTF8String:state.c_str()];
response.detailsCode = (RegistrationResponseDetailsCode)detailsCode;
response.details = [NSString stringWithUTF8String:detailsStr.c_str()];
[AccountAdapter.delegate registrationStateChangedWith:response];
}
}));
registerConfHandlers(confHandlers);
}
#pragma mark -
......
......@@ -25,3 +25,7 @@
#import "AccountAdapter.h"
#import "SystemAdapter.h"
#import "DRingAdapter.h"
#import "NameRegistrationAdapter.h"
#import "LookupNameResponse.h"
#import "RegistrationResponse.h"
#import "NameRegistrationResponse.h"
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Foundation/Foundation.h>
#import "NameRegistrationAdapter.h"
//Represents the status of the lookup response from to the daemon
typedef NS_ENUM(NSInteger, LookupNameState) {
LookupNameStateFound = 0,
LookupNameStateInvalidName,
LookupNameStateNotFound,
LookupNameStateError
};
@interface LookupNameResponse : NSObject
@property (nonatomic, retain) NSString* accountId;
@property (nonatomic) LookupNameState state;
@property (nonatomic, retain) NSString* address;
@property (nonatomic, retain) NSString* name;
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "LookupNameResponse.h"
@implementation LookupNameResponse
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Foundation/Foundation.h>
@protocol NameRegistrationAdapterDelegate;
@interface NameRegistrationAdapter : NSObject
@property (class, nonatomic, weak) id <NameRegistrationAdapterDelegate> delegate;
- (void)lookupNameWithAccount:(NSString*)account nameserver:(NSString*)nameserver
name:(NSString*)name;
- (void)registerNameWithAccount:(NSString*)account password:(NSString*)password
name:(NSString*)name;
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "Ring-Swift.h"
#import "NameRegistrationAdapter.h"
#import "Utils.h"
#import "dring/configurationmanager_interface.h"
#import "LookupNameResponse.h"
#import "NameRegistrationResponse.h"
@implementation NameRegistrationAdapter
using namespace DRing;
/// Static delegate that will receive the propagated daemon events
static id <NameRegistrationAdapterDelegate> _delegate;
- (id)init {
if (self = [super init]) {
[self registerConfigurationHandler];
}
return self;
}
#pragma mark -
#pragma mark Callbacks registration
- (void)registerConfigurationHandler {
std::map<std::string, std::shared_ptr<CallbackWrapperBase>> confHandlers;
confHandlers.insert(exportable_callback<ConfigurationSignal::RegisteredNameFound>([&](const std::string&account_id,
int state,
const std::string address,
const std::string& name) {
if (NameRegistrationAdapter.delegate) {
LookupNameResponse* response = [LookupNameResponse new];
response.accountId = [NSString stringWithUTF8String:account_id.c_str()];
response.state = (LookupNameState)state;
response.address = [NSString stringWithUTF8String:address.c_str()];
response.name = [NSString stringWithUTF8String:name.c_str()];
[NameRegistrationAdapter.delegate registeredNameFoundWith:response];
}
}));
confHandlers.insert(exportable_callback<ConfigurationSignal::NameRegistrationEnded>([&](const std::string&account_id,
int state,
const std::string& name) {
if (NameRegistrationAdapter.delegate) {
NameRegistrationResponse* response = [NameRegistrationResponse new];
response.accountId = [NSString stringWithUTF8String:account_id.c_str()];
response.state = (NameRegistrationState)state;
response.name = [NSString stringWithUTF8String:name.c_str()];
[NameRegistrationAdapter.delegate nameRegistrationEndedWith:response];
}
}));
registerConfHandlers(confHandlers);
}
#pragma mark -
- (void)lookupNameWithAccount:(NSString*)account nameserver:(NSString*)nameserver name:(NSString*)name {
lookupName(std::string([account UTF8String]),std::string([nameserver UTF8String]),std::string([name UTF8String]));
}
- (void)registerNameWithAccount:(NSString*)account password:(NSString*)password name:(NSString*)name {
registerName(std::string([account UTF8String]), std::string([password UTF8String]), std::string([name UTF8String]));
}
#pragma mark NameRegistrationAdapterDelegate
+ (id <NameRegistrationAdapterDelegate>)delegate {
return _delegate;
}
+ (void) setDelegate:(id<NameRegistrationAdapterDelegate>)delegate {
_delegate = delegate;
}
#pragma mark -
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
@objc protocol NameRegistrationAdapterDelegate {
func registeredNameFound(with response: LookupNameResponse)
func nameRegistrationEnded(with response: NameRegistrationResponse)
}
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import <Foundation/Foundation.h>
//Represents the status of the registration response from to the daemon
typedef NS_ENUM(NSInteger, NameRegistrationState) {
NameRegistrationStateSuccess = 0,
NameRegistrationStateInvalidName,
NameRegistrationStateAlreadyTaken,
NameRegistrationStateError
};
@interface NameRegistrationResponse : NSObject
@property (nonatomic, retain) NSString* accountId;
@property (nonatomic) NameRegistrationState state;
@property (nonatomic, retain) NSString* address;
@property (nonatomic, retain) NSString* name;
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#import "NameRegistrationResponse.h"
@implementation NameRegistrationResponse
@end
/*
* Copyright (C) 2017 Savoir-faire Linux Inc.
*
* Author: Silbino Gonçalves Matado <silbino.gmatado@savoirfairelinux.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
import UIKit
import RxSwift
/**
Represents the status of a username validation request when the user is typing his username
*/
enum UsernameValidationStatus {
case empty
case lookingUp
case invalid
case alreadyTaken
case valid
}
class NameService: NameRegistrationAdapterDelegate {
/**
Used to make lookup name request to the daemon
*/
fileprivate let nameRegistrationAdapter :NameRegistrationAdapter
fileprivate var delayedLookupNameCall: DispatchWorkItem?
fileprivate let lookupNameCallDelay = 0.5
/**
Status of the current username lookup request
*/
var usernameValidationStatus = PublishSubject<UsernameValidationStatus>()
init(withNameRegistrationAdapter nameRegistrationAdapter: NameRegistrationAdapter) {
self.nameRegistrationAdapter = nameRegistrationAdapter
NameRegistrationAdapter.delegate = self
}
/**
Make a username lookup request to the daemon
*/
func lookupName(withAccount account: String, nameserver: String, name: String) {
//Cancel previous lookups...
delayedLookupNameCall?.cancel()
if name.isEmpty {
usernameValidationStatus.onNext(.empty)
} else {
usernameValidationStatus.onNext(.lookingUp)
//Fire a delayed lookup...
delayedLookupNameCall = DispatchWorkItem {
self.nameRegistrationAdapter.lookupName(withAccount: account, nameserver: nameserver, name: name)
}
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + lookupNameCallDelay, execute: delayedLookupNameCall!)
}
}
/**
Register the username into the the blockchain
*/
func registerName(withAccount account: String, password: String, name: String) {
self.nameRegistrationAdapter.registerName(withAccount: account, password: password, name: name)
}
//MARK: NameService delegate
internal func registeredNameFound(with response: LookupNameResponse) {
if response.state == .notFound {
usernameValidationStatus.onNext(.valid)