Skip to content
Open
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 @@ -40,6 +40,7 @@ import app_service/service/market/service as market_service
import app/modules/onboarding/module as onboarding_module
import app/modules/onboarding/post_onboarding/[keycard_replacement_task, keycard_convert_account, save_biometrics_task]
import app/modules/main/module as main_module
import app/modules/keycard_channel/module as keycard_channel_module
import app/core/notifications/notifications_manager
import app/global/global_singleton
import app/global/app_signals
Expand Down Expand Up @@ -105,6 +106,7 @@ type
# Modules
onboardingModule: onboarding_module.AccessInterface
mainModule: main_module.AccessInterface
keycardChannelModule: keycard_channel_module.AccessInterface

#################################################
# Forward declaration section
Expand Down Expand Up @@ -233,6 +235,7 @@ proc newAppController*(statusFoundation: StatusFoundation): AppController =
result.marketService = market_service.newService(statusFoundation.events, result.settingsService)

# Modules
result.keycardChannelModule = keycard_channel_module.newModule(statusFoundation.events)
result.onboardingModule = onboarding_module.newModule[AppController](
result,
statusFoundation.events,
Expand Down Expand Up @@ -299,6 +302,9 @@ proc delete*(self: AppController) =
self.onboardingModule.delete
self.onboardingModule = nil
self.mainModule.delete
if not self.keycardChannelModule.isNil:
self.keycardChannelModule.delete
self.keycardChannelModule = nil

self.appSettingsVariant.delete
self.localAppSettingsVariant.delete
Expand Down Expand Up @@ -346,6 +352,9 @@ proc initializeQmlContext(self: AppController) =
singletonInstance.engine.setRootContextProperty("globalUtils", self.globalUtilsVariant)
singletonInstance.engine.setRootContextProperty("metrics", self.metricsVariant)

# Load keycard channel module (available before login for Session API)
self.keycardChannelModule.load()

singletonInstance.engine.load(newQUrl("qrc:///main.qml"))

proc onboardingDidLoad*(self: AppController) =
Expand Down
9 changes: 9 additions & 0 deletions src/app/modules/keycard_channel/constants.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Constants for keycard channel operational states
## These values must match the strings emitted by status-keycard-qt

const KEYCARD_CHANNEL_STATE_IDLE* = "idle"
const KEYCARD_CHANNEL_STATE_WAITING_FOR_KEYCARD* = "waiting-for-keycard"
const KEYCARD_CHANNEL_STATE_READING* = "reading"
const KEYCARD_CHANNEL_STATE_ERROR* = "error"


27 changes: 27 additions & 0 deletions src/app/modules/keycard_channel/controller.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import ./io_interface
import app/core/eventemitter
import app_service/service/keycardV2/service as keycard_serviceV2

type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter

proc newController*(
delegate: io_interface.AccessInterface,
events: EventEmitter
): Controller =
result = Controller()
result.delegate = delegate
result.events = events

proc delete*(self: Controller) =
discard

proc init*(self: Controller) =
# Listen to channel state changes
self.events.on(keycard_serviceV2.SIGNAL_KEYCARD_CHANNEL_STATE_UPDATED) do(e: Args):
let args = keycard_serviceV2.KeycardChannelStateArg(e)
self.delegate.setKeycardChannelState(args.state)


23 changes: 23 additions & 0 deletions src/app/modules/keycard_channel/io_interface.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.

method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

# View Delegate Interface
# Delegate for the view must be declared here due to use of QtObject and multi
# inheritance, which is not well supported in Nim.
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

method setKeycardChannelState*(self: AccessInterface, state: string) {.base.} =
raise newException(ValueError, "No implementation available")


49 changes: 49 additions & 0 deletions src/app/modules/keycard_channel/module.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import nimqml

import io_interface, view, controller
import app/global/global_singleton
import app/core/eventemitter
import ./constants

export io_interface
export constants

type
Module* = ref object of io_interface.AccessInterface
view: View
viewVariant: QVariant
controller: Controller
moduleLoaded: bool

proc newModule*(
events: EventEmitter,
): Module =
result = Module()
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events)
result.moduleLoaded = false

singletonInstance.engine.setRootContextProperty("keycardChannelModule", result.viewVariant)

method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete

method load*(self: Module) =
self.controller.init()
self.view.load()

method isLoaded*(self: Module): bool =
return self.moduleLoaded

proc checkIfModuleDidLoad(self: Module) =
self.moduleLoaded = true

method viewDidLoad*(self: Module) =
self.checkIfModuleDidLoad()

method setKeycardChannelState*(self: Module, state: string) =
self.view.setKeycardChannelState(state)

62 changes: 62 additions & 0 deletions src/app/modules/keycard_channel/view.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import nimqml

import ./io_interface
import ./constants

QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
keycardChannelState: string # Operational channel state

proc setup(self: View)
proc delete*(self: View)
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.delegate = delegate
result.keycardChannelState = KEYCARD_CHANNEL_STATE_IDLE
result.setup()

proc load*(self: View) =
self.delegate.viewDidLoad()

proc keycardChannelStateChanged*(self: View) {.signal.}
proc setKeycardChannelState*(self: View, value: string) =
if self.keycardChannelState == value:
return
self.keycardChannelState = value
self.keycardChannelStateChanged()
proc getKeycardChannelState*(self: View): string {.slot.} =
return self.keycardChannelState
QtProperty[string] keycardChannelState:
read = getKeycardChannelState
write = setKeycardChannelState
notify = keycardChannelStateChanged

# Constants for channel states (readonly properties for QML)
proc getStateIdle*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_IDLE
QtProperty[string] stateIdle:
read = getStateIdle

proc getStateWaitingForKeycard*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_WAITING_FOR_KEYCARD
QtProperty[string] stateWaitingForKeycard:
read = getStateWaitingForKeycard

proc getStateReading*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_READING
QtProperty[string] stateReading:
read = getStateReading

proc getStateError*(self: View): string {.slot.} =
return KEYCARD_CHANNEL_STATE_ERROR
QtProperty[string] stateError:
read = getStateError

proc setup(self: View) =
self.QObject.setup

proc delete*(self: View) =
self.QObject.delete

3 changes: 3 additions & 0 deletions src/app_service/service/keycard/service.nim
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ QtObject:
return

let flowType = typeObj.getStr
if flowType == "channel-state-changed":
return #nothing related to flows here

let flowEvent = toKeycardEvent(eventObj)
self.lastReceivedKeycardData = (flowType: flowType, flowEvent: flowEvent)
self.events.emit(SIGNAL_KEYCARD_RESPONSE, KeycardLibArgs(flowType: flowType, flowEvent: flowEvent))
Expand Down
14 changes: 11 additions & 3 deletions src/app_service/service/keycardV2/service.nim
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const PUKLengthForStatusApp* = 12
const KeycardLibCallsInterval = 500 # 0.5 seconds

const SIGNAL_KEYCARD_STATE_UPDATED* = "keycardStateUpdated"
const SIGNAL_KEYCARD_CHANNEL_STATE_UPDATED* = "keycardChannelStateUpdated"
const SIGNAL_KEYCARD_SET_PIN_FAILURE* = "keycardSetPinFailure"
const SIGNAL_KEYCARD_AUTHORIZE_FINISHED* = "keycardAuthorizeFinished"
const SIGNAL_KEYCARD_LOAD_MNEMONIC_FAILURE* = "keycardLoadMnemonicFailure"
Expand Down Expand Up @@ -60,6 +61,9 @@ type
KeycardExportedKeysArg* = ref object of Args
exportedKeys*: KeycardExportedKeysDto

KeycardChannelStateArg* = ref object of Args
state*: string

include utils
include app_service/common/async_tasks
include async_tasks
Expand Down Expand Up @@ -100,7 +104,6 @@ QtObject:
if status_const.IS_MACOS and status_const.IS_INTEL:
sleep 700
self.initializeRPC()
self.asyncStart(status_const.KEYCARDPAIRINGDATAFILE)
discard

proc initializeRPC(self: Service) {.slot, featureGuard(KEYCARD_ENABLED).} =
Expand All @@ -110,10 +113,15 @@ QtObject:
try:
# Since only one service can register to signals, we pass the signal to the old service too
var jsonSignal = signal.parseJson
if jsonSignal["type"].getStr == "status-changed":
let signalType = jsonSignal["type"].getStr

if signalType == "status-changed":
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe we can add "status-changed" and "channel-state-changed" to app_service/common/wallet_constants.nim?

let keycardEvent = jsonSignal["event"].toKeycardEventDto()

self.events.emit(SIGNAL_KEYCARD_STATE_UPDATED, KeycardEventArg(keycardEvent: keycardEvent))
elif signalType == "channel-state-changed":
let state = jsonSignal["event"]["state"].getStr
debug "keycardV2 service: emitting channel state update", state=state, signal=SIGNAL_KEYCARD_CHANNEL_STATE_UPDATED
self.events.emit(SIGNAL_KEYCARD_CHANNEL_STATE_UPDATED, KeycardChannelStateArg(state: state))
except Exception as e:
error "error receiving a keycard signal", err=e.msg, data = signal

Expand Down