feat(onbaording): implement basic function for the new onboarding

Fixes #16832

Implements all the needed basic Nim functions for the new onboarding.

They do no do anything just yet. They shall be integrated in another commit.
This commit is contained in:
Jonathan Rainville 2024-12-19 14:33:10 -05:00
parent 3dd5fa9443
commit a54d864120
15 changed files with 386 additions and 17 deletions

View File

@ -3,6 +3,7 @@ import NimQml, sequtils, sugar, chronicles, uuids
import app_service/service/general/service as general_service
import app_service/service/keychain/service as keychain_service
import app_service/service/keycard/service as keycard_service
import app_service/service/keycardV2/service as keycard_serviceV2
import app_service/service/accounts/service as accounts_service
import app_service/service/contacts/service as contacts_service
import app_service/service/language/service as language_service
@ -70,6 +71,7 @@ type
# Services
generalService: general_service.Service
keycardService*: keycard_service.Service
keycardServiceV2*: keycard_serviceV2.Service
keychainService: keychain_service.Service
accountsService: accounts_service.Service
contactsService: contacts_service.Service
@ -169,6 +171,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
# Services
result.generalService = general_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.keycardService = keycard_service.newService(statusFoundation.events, statusFoundation.threadpool)
result.keycardServiceV2 = keycard_serviceV2.newService(statusFoundation.events, statusFoundation.threadpool)
result.nodeConfigurationService = node_configuration_service.newService(statusFoundation.fleetConfiguration,
result.settingsService, statusFoundation.events)
result.keychainService = keychain_service.newService(statusFoundation.events)
@ -255,6 +258,10 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.onboardingModule = onboarding_module.newModule[AppController](
result,
statusFoundation.events,
result.generalService,
result.accountsService,
result.devicesService,
result.keycardServiceV2,
)
result.mainModule = main_module.newModule[AppController](
result,
@ -353,6 +360,7 @@ proc delete*(self: AppController) =
self.ensService.delete
self.tokensService.delete
self.keycardService.delete
self.keycardServiceV2.delete
self.networkConnectionService.delete
self.metricsService.delete
@ -421,6 +429,7 @@ proc mainDidLoad*(self: AppController) =
proc start*(self: AppController) =
self.keycardService.init()
self.keycardServiceV2.init()
self.keychainService.init()
self.generalService.init()
self.accountsService.init()

View File

@ -106,5 +106,5 @@ proc validateConnectionString*(self: Controller, connectionString: string): stri
proc getConnectionStringForBootstrappingAnotherDevice*(self: Controller, password, chatKey: string): string =
return self.devicesService.getConnectionStringForBootstrappingAnotherDevice(password, chatKey)
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string): string =
return self.devicesService.inputConnectionStringForBootstrapping(connectionString)
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string) =
self.devicesService.inputConnectionStringForBootstrapping(connectionString)

View File

@ -59,7 +59,7 @@ method onLoggedInUserAuthenticated*(self: AccessInterface, pin: string, password
proc validateConnectionString*(self: AccessInterface, connectionString: string): string =
raise newException(ValueError, "No implementation available")
method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string): string {.base.} =
method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string) {.base.} =
raise newException(ValueError, "No implementation available")
method onLocalPairingStatusUpdate*(self: AccessInterface, status: LocalPairingStatus) {.base.} =

View File

@ -120,8 +120,8 @@ method onLoggedInUserAuthenticated*(self: Module, pin: string, password: string,
proc validateConnectionString*(self: Module, connectionString: string): string =
return self.controller.validateConnectionString(connectionString)
method inputConnectionStringForBootstrapping*(self: Module, connectionString: string): string =
return self.controller.inputConnectionStringForBootstrapping(connectionString)
method inputConnectionStringForBootstrapping*(self: Module, connectionString: string) =
self.controller.inputConnectionStringForBootstrapping(connectionString)
method onLocalPairingStatusUpdate*(self: Module, status: LocalPairingStatus) =
self.view.onLocalPairingStatusUpdate(status)

View File

@ -139,5 +139,5 @@ QtObject:
proc validateConnectionString*(self: View, connectionString: string): string {.slot.} =
return self.delegate.validateConnectionString(connectionString)
proc inputConnectionStringForBootstrapping*(self: View, connectionString: string): string {.slot.} =
return self.delegate.inputConnectionStringForBootstrapping(connectionString)
proc inputConnectionStringForBootstrapping*(self: View, connectionString: string) {.slot.} =
self.delegate.inputConnectionStringForBootstrapping(connectionString)

View File

@ -1,7 +1,12 @@
import chronicles
import chronicles, strutils
import io_interface
import app/core/eventemitter
import app_service/service/general/service as general_service
import app_service/service/accounts/service as accounts_service
import app_service/service/accounts/dto/image_crop_rectangle
import app_service/service/devices/service as devices_service
import app_service/service/keycardV2/service as keycard_serviceV2
logScope:
topics = "onboarding-controller"
@ -10,15 +15,80 @@ type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
generalService: general_service.Service
accountsService: accounts_service.Service
devicesService: devices_service.Service
keycardServiceV2: keycard_serviceV2.Service
proc newController*(delegate: io_interface.AccessInterface, events: EventEmitter):
proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter,
generalService: general_service.Service,
accountsService: accounts_service.Service,
devicesService: devices_service.Service,
keycardServiceV2: keycard_serviceV2.Service,
):
Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.generalService = generalService
result.accountsService = accountsService
result.devicesService = devicesService
result.keycardServiceV2 = keycardServiceV2
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
discard
proc setPin*(self: Controller, pin: string): bool =
self.keycardServiceV2.setPin(pin)
discard
proc getPasswordStrengthScore*(self: Controller, password, userName: string): int =
return self.generalService.getPasswordStrengthScore(password, userName)
proc validMnemonic*(self: Controller, mnemonic: string): bool =
let (_, err) = self.accountsService.validateMnemonic(mnemonic)
if err.len == 0:
return true
return false
proc buildSeedPhrasesFromIndexes(self: Controller, seedPhraseIndexes: seq[int]): string =
if seedPhraseIndexes.len == 0:
error "keycard error: cannot generate mnemonic"
return
let sp = self.keycardServiceV2.buildSeedPhrasesFromIndexes(seedPhraseIndexes)
return sp.join(" ")
proc getMnemonic*(self: Controller): string =
let indexes = self.keycardServiceV2.getMnemonicIndexes()
return self.buildSeedPhrasesFromIndexes(indexes)
proc validateLocalPairingConnectionString*(self: Controller, connectionString: string): bool =
let err = self.devicesService.validateConnectionString(connectionString)
return err.len == 0
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string) =
self.devicesService.inputConnectionStringForBootstrapping(connectionString)
proc createAccountAndLogin*(self: Controller, password: string): string =
return self.accountsService.createAccountAndLogin(
password,
displayName = "",
imagePath = "",
ImageCropRectangle(),
)
proc restoreAccountAndLogin*(self: Controller, password, mnemonic: string, recoverAccount: bool, keycardInstanceUID: string): string =
return self.accountsService.importAccountAndLogin(
mnemonic,
password,
recoverAccount,
displayName = "",
imagePath = "",
ImageCropRectangle(),
keycardInstanceUID,
)

View File

@ -10,6 +10,27 @@ method onAppLoaded*(self: AccessInterface) {.base.} =
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method setPin*(self: AccessInterface, pin: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getPasswordStrengthScore*(self: AccessInterface, password, userName: string): int {.base.} =
raise newException(ValueError, "No implementation available")
method validMnemonic*(self: AccessInterface, mnemonic: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getMnemonic*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method validateLocalPairingConnectionString*(self: AccessInterface, connectionString: string): bool {.base.} =
raise newException(ValueError, "No implementation available")
method inputConnectionStringForBootstrapping*(self: AccessInterface, connectionString: string) {.base.} =
raise newException(ValueError, "No implementation available")
method finishOnboardingFlow*(self: AccessInterface, primaryFlowInt, secondaryFlowInt: int, dataJson: string): string {.base.} =
raise newException(ValueError, "No implementation available")
# This way (using concepts) is used only for the modules managed by AppController
type
DelegateInterface* = concept c

View File

@ -5,12 +5,32 @@ import view, controller
import app/global/global_singleton
import app/core/eventemitter
import app_service/service/general/service as general_service
import app_service/service/accounts/service as accounts_service
import app_service/service/devices/service as devices_service
import app_service/service/keycardV2/service as keycard_serviceV2
export io_interface
logScope:
topics = "onboarding-module"
type PrimaryFlow* {.pure} = enum
Unknown = 0,
CreateProfile,
Login
type SecondaryFlow* {.pure} = enum
Unknown = 0,
CreateProfileWithPassword,
CreateProfileWithSeedphrase,
CreateProfileWithKeycard,
CreateProfileWithKeycardNewSeedphrase,
CreateProfileWithKeycardExistingSeedphrase,
LoginWithSeedphrase,
LoginWithSyncing,
LoginWithKeycard
type
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
delegate: T
@ -18,12 +38,26 @@ type
viewVariant: QVariant
controller: Controller
proc newModule*[T](delegate: T, events: EventEmitter): Module[T] =
proc newModule*[T](
delegate: T,
events: EventEmitter,
generalService: general_service.Service,
accountsService: accounts_service.Service,
devicesService: devices_service.Service,
keycardServiceV2: keycard_serviceV2.Service,
): Module[T] =
result = Module[T]()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events)
result.controller = controller.newController(
result,
events,
generalService,
accountsService,
devicesService,
keycardServiceV2,
)
{.push warning[Deprecated]: off.}
@ -46,4 +80,83 @@ method load*[T](self: Module[T]) =
self.controller.init()
self.delegate.onboardingDidLoad()
method setPin*[T](self: Module[T], pin: string): bool =
self.controller.setPin(pin)
method getPasswordStrengthScore*[T](self: Module[T], password, userName: string): int =
self.controller.getPasswordStrengthScore(password, userName)
method validMnemonic*[T](self: Module[T], mnemonic: string): bool =
self.controller.validMnemonic(mnemonic)
method getMnemonic*[T](self: Module[T]): string =
self.controller.getMnemonic()
method validateLocalPairingConnectionString*[T](self: Module[T], connectionString: string): bool =
self.controller.validateLocalPairingConnectionString(connectionString)
method inputConnectionStringForBootstrapping*[T](self: Module[T], connectionString: string) =
self.controller.inputConnectionStringForBootstrapping(connectionString)
method finishOnboardingFlow*[T](self: Module[T], primaryFlowInt, secondaryFlowInt: int, dataJson: string): string =
try:
let primaryFlow = PrimaryFlow(primaryFlowInt)
let secondaryFlow = SecondaryFlow(secondaryFlowInt)
let data = parseJson(dataJson)
let password = data["password"].str
let seedPhrase = data["seedPhrase"].str
var err = ""
# CREATE PROFILE PRIMARY FLOW
if primaryFlow == PrimaryFlow.CreateProfile:
case secondaryFlow:
of SecondaryFlow.CreateProfileWithPassword:
err = self.controller.createAccountAndLogin(password)
of SecondaryFlow.CreateProfileWithSeedphrase:
err = self.controller.restoreAccountAndLogin(
password,
seedPhrase,
recoverAccount = false,
keycardInstanceUID = "",
)
of SecondaryFlow.CreateProfileWithKeycard:
# TODO implement keycard function
discard
of SecondaryFlow.CreateProfileWithKeycardNewSeedphrase:
# TODO implement keycard function
discard
of SecondaryFlow.CreateProfileWithKeycardExistingSeedphrase:
# TODO implement keycard function
discard
else:
raise newException(ValueError, "Unknown secondary flow for CreateProfile: " & $secondaryFlow)
# LOGIN PRIMARY FLOW
elif primaryFlow == PrimaryFlow.Login:
case secondaryFlow:
of SecondaryFlow.LoginWithSeedphrase:
err = self.controller.restoreAccountAndLogin(
password,
seedPhrase,
recoverAccount = true,
keycardInstanceUID = "",
)
of SecondaryFlow.LoginWithSyncing:
self.controller.inputConnectionStringForBootstrapping(data["connectionString"].str)
of SecondaryFlow.LoginWithKeycard:
# TODO implement keycard function
discard
else:
raise newException(ValueError, "Unknown secondary flow for Login: " & $secondaryFlow)
if err != "":
raise newException(ValueError, err)
else:
raise newException(ValueError, "Unknown primary flow: " & $primaryFlow)
except Exception as e:
error "Error finishing Onboarding Flow", msg = e.msg
return e.msg
{.pop.}

View File

@ -1,10 +1,14 @@
import NimQml, json
import NimQml
import io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
syncState: int
keycardState: int
keycardRemainingPinAttempts: int
addKeyPairState: int
proc delete*(self: View) =
self.QObject.delete
@ -13,3 +17,72 @@ QtObject:
new(result, delete)
result.QObject.setup
result.delegate = delegate
proc syncStateChanged*(self: View) {.signal.}
proc getSyncState(self: View): int {.slot.} =
return self.syncState
QtProperty[int] syncState:
read = getSyncState
notify = syncStateChanged
proc setSyncState(self: View, syncState: int) =
self.syncState = syncState
self.syncStateChanged()
proc keycardStateChanged*(self: View) {.signal.}
proc getKeycardState(self: View): int {.slot.} =
return self.keycardState
QtProperty[int] keycardState:
read = getKeycardState
notify = keycardStateChanged
proc setKeycardState(self: View, keycardState: int) =
self.keycardState = keycardState
self.keycardStateChanged()
proc keycardRemainingPinAttemptsChanged*(self: View) {.signal.}
proc getKeycardRemainingPinAttempts(self: View): int {.slot.} =
return self.keycardRemainingPinAttempts
QtProperty[int] keycardRemainingPinAttempts:
read = getKeycardRemainingPinAttempts
notify = keycardRemainingPinAttemptsChanged
proc setKeycardRemainingPinAttempts(self: View, keycardRemainingPinAttempts: int) =
self.keycardRemainingPinAttempts = keycardRemainingPinAttempts
self.keycardRemainingPinAttemptsChanged()
proc addKeyPairStateChanged*(self: View) {.signal.}
proc getAddKeyPairState(self: View): int {.slot.} =
return self.addKeyPairState
QtProperty[int] addKeyPairState:
read = getAddKeyPairState
notify = addKeyPairStateChanged
proc setAddKeyPairState(self: View, addKeyPairState: int) =
self.addKeyPairState = addKeyPairState
self.addKeyPairStateChanged()
proc setPin(self: View, pin: string): bool {.slot.} =
return self.delegate.setPin(pin)
# TODO find what does this do
# proc startKeypairTransfer(self: View) {.slot.} =
# self.delegate.startKeypairTransfer()
proc getPasswordStrengthScore(self: View, password: string, userName: string): int {.slot.} =
return self.delegate.getPasswordStrengthScore(password, userName)
proc validMnemonic(self: View, mnemonic: string): bool {.slot.} =
return self.delegate.validMnemonic(mnemonic)
proc getMnemonic(self: View): string {.slot.} =
return self.delegate.getMnemonic()
proc validateLocalPairingConnectionString(self: View, connectionString: string): bool {.slot.} =
return self.delegate.validateLocalPairingConnectionString(connectionString)
proc inputConnectionStringForBootstrapping(self: View, connectionString: string) {.slot.} =
self.delegate.inputConnectionStringForBootstrapping(connectionString)
# TODO find what does this do
# proc mnemonicWasShown(self: View): string {.slot.} =
# return self.delegate.getMnemonic()
proc finishOnboardingFlow(self: View, primaryFlowInt: int, secondaryFlowInt: int, dataJson: string): string {.slot.} =
self.delegate.finishOnboardingFlow(primaryFlowInt, secondaryFlowInt, dataJson)

View File

@ -609,8 +609,8 @@ proc setConnectionString*(self: Controller, connectionString: string) =
proc validateLocalPairingConnectionString*(self: Controller, connectionString: string): string =
return self.devicesService.validateConnectionString(connectionString)
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string): string =
return self.devicesService.inputConnectionStringForBootstrapping(connectionString)
proc inputConnectionStringForBootstrapping*(self: Controller, connectionString: string) =
self.devicesService.inputConnectionStringForBootstrapping(connectionString)
proc setLoggedInAccount*(self: Controller, account: AccountDto) =
self.accountsService.setLoggedInAccount(account)

View File

@ -10,7 +10,7 @@ proc delete*(self: SyncDeviceWithSyncCodeState) =
method executePrimaryCommand*(self: SyncDeviceWithSyncCodeState, controller: Controller) =
let connectionString = controller.getConnectionString()
discard controller.inputConnectionStringForBootstrapping(connectionString)
controller.inputConnectionStringForBootstrapping(connectionString)
method getNextPrimaryState*(self: SyncDeviceWithSyncCodeState, controller: Controller): State =
return createState(StateType.SyncDeviceResult, self.flowType, self)

View File

@ -233,7 +233,7 @@ QtObject:
self.localPairingStatus = newLocalPairingStatus(PairingType.AppSync, LocalPairingMode.Sender)
return status_go.getConnectionStringForBootstrappingAnotherDevice($configJSON)
proc inputConnectionStringForBootstrapping*(self: Service, connectionString: string): string =
proc inputConnectionStringForBootstrapping*(self: Service, connectionString: string) =
let configJSON = %* {
"receiverConfig": %* {
"createAccount": %*accounts_service.defaultCreateAccountRequest(),

View File

@ -0,0 +1,16 @@
type
AsyncSetPinTaskArg = ref object of QObjectTaskArg
pin: string
proc asyncSetPinTask(argEncoded: string) {.gcsafe, nimcall.} =
let arg = decode[AsyncSetPinTaskArg](argEncoded)
try:
# TODO Call function from keycard_go
echo "Set pin ", arg.pin
arg.finish(%*{
"error": ""
})
except Exception as e:
arg.finish(%* {
"error": e.msg,
})

View File

@ -0,0 +1,67 @@
import NimQml, json, chronicles, strutils
# import keycard_go
import app/global/global_singleton
import app/core/eventemitter
import app/core/tasks/[qt, threadpool]
include ../../common/mnemonics
include async_tasks
logScope:
topics = "keycardV2-service"
QtObject:
type Service* = ref object of QObject
events: EventEmitter
threadpool: ThreadPool
proc delete*(self: Service) =
self.QObject.delete
proc newService*(events: EventEmitter, threadpool: ThreadPool): Service =
new(result, delete)
result.QObject.setup
result.events = events
result.threadpool = threadpool
proc init*(self: Service) =
discard
proc receiveKeycardSignal(self: Service, signal: string) {.slot.} =
var jsonSignal: JsonNode
try:
jsonSignal = signal.parseJson
except:
error "Invalid signal received", data = signal
return
debug "keycard_signal", response=signal
proc buildSeedPhrasesFromIndexes*(self: Service, seedPhraseIndexes: seq[int]): seq[string] =
var seedPhrase: seq[string]
for ind in seedPhraseIndexes:
seedPhrase.add(englishWords[ind])
return seedPhrase
proc getMnemonicIndexes*(self: Service): seq[int] =
# TODO call lib to get mnemonic indexes
echo "Get mnemonic indexes"
return @[]
proc setPin*(self: Service, pin: string) =
let arg = AsyncSetPinTaskArg(
tptr: asyncSetPinTask,
vptr: cast[ByteAddress](self.vptr),
slot: "onAsyncSetPinResponse",
pin: pin,
)
self.threadpool.start(arg)
proc onAsyncSetPinResponse*(self: Service, response: string) {.slot.} =
try:
let rpcResponseObj = response.parseJson
echo "Set the pin ", response
if (rpcResponseObj{"error"}.kind != JNull and rpcResponseObj{"error"}.getStr != ""):
raise newException(CatchableError, rpcResponseObj{"error"}.getStr)
except Exception as e:
error "error set pin: ", msg = e.msg

View File

@ -46,6 +46,6 @@ QtObject {
}
function inputConnectionStringForBootstrapping(connectionString) {
return root.devicesModule.inputConnectionStringForBootstrapping(connectionString)
root.devicesModule.inputConnectionStringForBootstrapping(connectionString)
}
}