Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(onbaording): implement basic functions for the new onboarding #17003

Merged
merged 1 commit into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/app/boot/app_controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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()
Expand Down
4 changes: 2 additions & 2 deletions src/app/modules/main/profile_section/devices/controller.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -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.} =
Expand Down
4 changes: 2 additions & 2 deletions src/app/modules/main/profile_section/devices/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
4 changes: 2 additions & 2 deletions src/app/modules/main/profile_section/devices/view.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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)
74 changes: 72 additions & 2 deletions src/app/modules/onboarding/controller.nim
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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 = "",
caybro marked this conversation as resolved.
Show resolved Hide resolved
imagePath = "",
ImageCropRectangle(),
)

proc restoreAccountAndLogin*(self: Controller, password, mnemonic: string, recoverAccount: bool, keycardInstanceUID: string): string =
return self.accountsService.importAccountAndLogin(
mnemonic,
password,
recoverAccount,
displayName = "",
caybro marked this conversation as resolved.
Show resolved Hide resolved
imagePath = "",
ImageCropRectangle(),
keycardInstanceUID,
)
21 changes: 21 additions & 0 deletions src/app/modules/onboarding/io_interface.nim
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
117 changes: 115 additions & 2 deletions src/app/modules/onboarding/module.nim
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,59 @@ 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
Comment on lines +18 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soo, do we need PrimaryFlow, if all of the values in SecondaryFlow have prefix indicating the primary flow? Looks absolutely redundant 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not indeed. I still didn't have access to the C++ enum, so now that I'm rebased on top of it, maybe we can simplify some more

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you're right. The QML only provides the secondary flow, which is sufficient anyway:

!!! ONBOARDING FINISHED; flow: 1 ; data: {\"password\":\"1234567890\",\"keycardPin\":\"\",\"seedphrase\":\"\",\"enableBiometrics\":false}

I'll get rid of the primary flow complexity


type SecondaryFlow* {.pure} = enum
Unknown = 0,
CreateProfileWithPassword,
CreateProfileWithSeedphrase,
CreateProfileWithKeycard,
CreateProfileWithKeycardNewSeedphrase,
CreateProfileWithKeycardExistingSeedphrase,
LoginWithSeedphrase,
LoginWithSyncing,
LoginWithKeycard
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My idea was to use the C++ enum here as the only source of truth (which I created in the other PR which is not merged yet ofc), and also in QML. Perhaps later :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yeah. I didn't find an enum that was usable, so I created one, but I have nothing against getting rid of this one to share the same you have once I integrate.
Do you know how to access the enum from Nim?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know how to access the enum from Nim?

Not exactly but with my slightly limited NIM knowledge, it should be possible 😆


type
Module*[T: io_interface.DelegateInterface] = ref object of io_interface.AccessInterface
delegate: T
view: View
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.}

Expand All @@ -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 =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, I like the simplicity 👍

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.}
Loading