573 lines
21 KiB
Clojure
573 lines
21 KiB
Clojure
(ns legacy.status-im.keycard.common
|
|
(:require
|
|
[clojure.string :as string]
|
|
[legacy.status-im.bottom-sheet.events :as bottom-sheet]
|
|
[legacy.status-im.keycard.nfc :as nfc]
|
|
[legacy.status-im.popover.core :as popover]
|
|
[legacy.status-im.ui.screens.keycard.keycard-interaction :as keycard-sheet]
|
|
[legacy.status-im.utils.deprecated-types :as types]
|
|
[legacy.status-im.utils.keychain.core :as keychain]
|
|
[re-frame.core :as re-frame]
|
|
[react-native.platform :as platform]
|
|
[status-im.navigation.events :as navigation]
|
|
[taoensso.timbre :as log]
|
|
[utils.address :as address]
|
|
[utils.datetime :as datetime]
|
|
[utils.i18n :as i18n]
|
|
[utils.re-frame :as rf]))
|
|
|
|
(def default-pin "000000")
|
|
|
|
(def pin-mismatch-error #"Unexpected error SW, 0x63C(\d+)|wrongPIN\(retryCounter: (\d+)\)")
|
|
|
|
(defn pin-retries
|
|
[error]
|
|
(when-let [matched-error (re-matches pin-mismatch-error error)]
|
|
(js/parseInt (second (filter some? matched-error)))))
|
|
|
|
(rf/defn dispatch-event
|
|
[_ event]
|
|
{:dispatch-n [[event]]})
|
|
|
|
(defn vector->string
|
|
"Converts numbers stored in vector into string,
|
|
e.g. [1 2 3 4 5 6] -> \"123456\""
|
|
[v]
|
|
(apply str v))
|
|
|
|
(defn get-card-state
|
|
[{:keys [has-master-key?
|
|
applet-installed?
|
|
initialized?
|
|
free-pairing-slots
|
|
paired?]}]
|
|
(cond
|
|
|
|
(not applet-installed?)
|
|
:blank
|
|
|
|
(not initialized?)
|
|
:pre-init
|
|
|
|
(not has-master-key?)
|
|
:init
|
|
|
|
has-master-key?
|
|
:profile/profile
|
|
|
|
(and (not paired?)
|
|
(zero? free-pairing-slots))
|
|
:no-pairing-slots))
|
|
|
|
(defn tag-lost?
|
|
[error]
|
|
(or
|
|
(= error "Tag was lost.")
|
|
(= error "NFCError:100")
|
|
(re-matches #".*NFCError:100.*" error)))
|
|
|
|
(defn find-multiaccount-by-keycard-instance-uid
|
|
[db keycard-instance-uid]
|
|
(when keycard-instance-uid
|
|
(->> (:profile/profiles-overview db)
|
|
vals
|
|
(filter #(= keycard-instance-uid (:keycard-instance-uid %)))
|
|
first)))
|
|
|
|
(defn find-multiaccount-by-key-uid
|
|
[db key-uid]
|
|
(when key-uid
|
|
(->> (:profile/profiles-overview db)
|
|
vals
|
|
(filter #(= (address/normalized-hex key-uid) (:key-uid %)))
|
|
first)))
|
|
|
|
(defn get-pairing
|
|
([db]
|
|
(get-pairing db (get-in db [:keycard :application-info :key-uid])))
|
|
([db key-uid]
|
|
(or
|
|
(get-in db [:profile/profile :keycard-pairing])
|
|
(get-in db [:keycard :secrets :pairing])
|
|
(when key-uid
|
|
(:keycard-pairing
|
|
(find-multiaccount-by-key-uid db key-uid))))))
|
|
|
|
(re-frame/reg-fx
|
|
:keycard/set-nfc-supported
|
|
(fn [supported?]
|
|
(nfc/set-nfc-supported? supported?)))
|
|
|
|
(rf/defn listen-to-hardware-back-button
|
|
[{:keys [db]}]
|
|
(when-not (get-in db [:keycard :back-button-listener])
|
|
{:keycard/listen-to-hardware-back-button nil}))
|
|
|
|
(rf/defn remove-listener-to-hardware-back-button
|
|
[{:keys [db]}]
|
|
(when-let [listener (get-in db [:keycard :back-button-listener])]
|
|
{:keycard/remove-listener-to-hardware-back-button listener}))
|
|
|
|
(rf/defn set-on-card-connected
|
|
[{:keys [db]} on-connect]
|
|
(log/debug "[keycard] set-on-card-connected" on-connect)
|
|
{:db (-> db
|
|
(assoc-in [:keycard :on-card-connected] on-connect)
|
|
(assoc-in [:keycard :last-on-card-connected] nil))})
|
|
|
|
(rf/defn stash-on-card-connected
|
|
[{:keys [db]}]
|
|
(let [on-connect (get-in db [:keycard :on-card-connected])]
|
|
(log/debug "[keycard] stash-on-card-connected" on-connect)
|
|
{:db (-> db
|
|
(assoc-in [:keycard :last-on-card-connected] on-connect)
|
|
(assoc-in [:keycard :on-card-connected] nil))}))
|
|
|
|
(rf/defn restore-on-card-connected
|
|
[{:keys [db]}]
|
|
(let [on-connect (or
|
|
(get-in db [:keycard :on-card-connected])
|
|
(get-in db [:keycard :last-on-card-connected]))]
|
|
(log/debug "[keycard] restore-on-card-connected" on-connect)
|
|
{:db (-> db
|
|
(assoc-in [:keycard :on-card-connected] on-connect)
|
|
(assoc-in [:keycard :last-on-card-connect] nil))}))
|
|
|
|
(rf/defn clear-on-card-connected
|
|
[{:keys [db]}]
|
|
(log/debug "[keycard] clear-on-card-connected")
|
|
{:db (-> db
|
|
(assoc-in [:keycard :on-card-connected] nil)
|
|
(assoc-in [:keycard :last-on-card-connected] nil))})
|
|
|
|
(rf/defn set-on-card-read
|
|
[{:keys [db]} on-connect]
|
|
(log/debug "[keycard] set-on-card-read" on-connect)
|
|
{:db (-> db
|
|
(assoc-in [:keycard :on-card-read] on-connect)
|
|
(assoc-in [:keycard :last-on-card-read] nil))})
|
|
|
|
(rf/defn stash-on-card-read
|
|
[{:keys [db]}]
|
|
(let [on-connect (get-in db [:keycard :on-card-read])]
|
|
(log/debug "[keycard] stash-on-card-read" on-connect)
|
|
{:db (-> db
|
|
(assoc-in [:keycard :last-on-card-read] on-connect)
|
|
(assoc-in [:keycard :on-card-read] nil))}))
|
|
|
|
(rf/defn restore-on-card-read
|
|
[{:keys [db]}]
|
|
(let [on-connect (or
|
|
(get-in db [:keycard :on-card-read])
|
|
(get-in db [:keycard :last-on-card-read]))]
|
|
(log/debug "[keycard] restore-on-card-read" on-connect)
|
|
{:db (-> db
|
|
(assoc-in [:keycard :on-card-read] on-connect)
|
|
(assoc-in [:keycard :last-on-card-connect] nil))}))
|
|
|
|
(rf/defn clear-on-card-read
|
|
[{:keys [db]}]
|
|
(log/debug "[keycard] clear-on-card-read")
|
|
{:db (-> db
|
|
(assoc-in [:keycard :on-card-read] nil)
|
|
(assoc-in [:keycard :last-on-card-read] nil))})
|
|
|
|
(defn keycard-sheet-content
|
|
[on-cancel connected? params]
|
|
(fn []
|
|
[keycard-sheet/connect-keycard
|
|
{:on-cancel #(re-frame/dispatch on-cancel)
|
|
:connected? connected?
|
|
:params params
|
|
:on-connect ::on-card-connected
|
|
:on-disconnect ::on-card-disconnected}]))
|
|
|
|
(rf/defn show-connection-sheet-component
|
|
[{:keys [db] :as cofx}
|
|
{:keys [on-card-connected on-card-read handler]
|
|
{:keys [on-cancel]
|
|
:or {on-cancel [::cancel-sheet-confirm]}}
|
|
:sheet-options}]
|
|
(assert (keyword? on-card-connected))
|
|
(assert (fn? handler))
|
|
(let [connected? (get-in db [:keycard :card-connected?])]
|
|
(log/debug "[keycard] show-sheet-with-connection-check"
|
|
"card-connected?"
|
|
connected?)
|
|
(rf/merge
|
|
cofx
|
|
(bottom-sheet/show-bottom-sheet-old
|
|
{:view {:transparent platform/ios?
|
|
:show-handle? false
|
|
:backdrop-dismiss? false
|
|
:disable-drag? true
|
|
:back-button-cancel false
|
|
:content (keycard-sheet-content on-cancel connected? nil)}})
|
|
(when on-card-read
|
|
(set-on-card-read on-card-read))
|
|
(set-on-card-connected on-card-connected)
|
|
(when connected?
|
|
(stash-on-card-connected))
|
|
(when connected?
|
|
handler))))
|
|
|
|
(rf/defn show-connection-sheet
|
|
[{:keys [db] :as cofx} args]
|
|
(let [nfc-running? (get-in db [:keycard :nfc-running?])]
|
|
(log/debug "show connection; already running?" nfc-running?)
|
|
(if nfc-running?
|
|
(show-connection-sheet-component cofx args)
|
|
{:keycard/start-nfc-and-show-connection-sheet args})))
|
|
|
|
(rf/defn on-nfc-ready-for-sheet
|
|
{:events [:keycard.callback/show-connection-sheet]}
|
|
[cofx args]
|
|
(log/debug "on-nfc-ready-for-sheet")
|
|
(show-connection-sheet-component cofx args))
|
|
|
|
(rf/defn hide-connection-sheet-component
|
|
[{:keys [db] :as cofx}]
|
|
(rf/merge cofx
|
|
{:db (assoc-in db [:keycard :card-read-in-progress?] false)}
|
|
(restore-on-card-connected)
|
|
(restore-on-card-read)
|
|
(bottom-sheet/hide-bottom-sheet-old)))
|
|
|
|
(rf/defn hide-connection-sheet
|
|
[cofx]
|
|
(log/debug "hide-connection-sheet")
|
|
{:keycard/stop-nfc-and-hide-connection-sheet nil})
|
|
|
|
(rf/defn on-nfc-ready-to-close-sheet
|
|
{:events [:keycard.callback/hide-connection-sheet]}
|
|
[cofx]
|
|
(log/debug "on-nfc-ready-to-close-sheet")
|
|
(hide-connection-sheet-component cofx))
|
|
|
|
(rf/defn clear-pin
|
|
[{:keys [db] :as cofx}]
|
|
(rf/merge
|
|
cofx
|
|
{:db (assoc-in db
|
|
[:keycard :pin]
|
|
{:status nil
|
|
:login (get-in db [:keycard :pin :original])
|
|
:export-key []
|
|
:sign []
|
|
:puk []
|
|
:current []
|
|
:original []
|
|
:confirmation []
|
|
:error-label nil
|
|
:on-verified (get-in db [:keycard :pin :on-verified])
|
|
:on-verified-failure (get-in db [:keycard :pin :on-verified])})}))
|
|
|
|
(rf/defn cancel-sheet-confirm
|
|
{:events [::cancel-sheet-confirm
|
|
:keycard/back-button-pressed]}
|
|
[{:keys [db] :as cofx}]
|
|
(when-not (get-in db [:keycard :card-connected?])
|
|
(rf/merge cofx
|
|
(hide-connection-sheet)
|
|
(clear-pin))))
|
|
|
|
(rf/defn cancel-sheet
|
|
{:events [::cancel-sheet]}
|
|
[_]
|
|
{:ui/show-confirmation {:title (i18n/label :t/keycard-cancel-setup-title)
|
|
:content (i18n/label :t/keycard-cancel-setup-text)
|
|
:confirm-button-text (i18n/label :t/yes)
|
|
:cancel-button-text (i18n/label :t/no)
|
|
:on-accept #(re-frame/dispatch [::cancel-sheet-confirm])
|
|
:on-cancel #()}})
|
|
|
|
(rf/defn on-add-listener-to-hardware-back-button
|
|
"Adds listener to hardware back button on Android.
|
|
During keycard setup we show user a warning that setup will be cancelled
|
|
when back button pressed. This prevents user from going back during setup
|
|
flow as some of the actions changing keycard step could not be repeated."
|
|
{:events [:keycard/add-listener-to-hardware-back-button]}
|
|
[{:keys [db]} listener]
|
|
{:db (assoc-in db [:keycard :back-button-listener] listener)})
|
|
|
|
(rf/defn show-wrong-keycard-alert
|
|
[_]
|
|
(log/debug "show-wrong-keycard-alert")
|
|
{:effects.utils/show-popup {:title (i18n/label :t/wrong-card)
|
|
:content (i18n/label :t/wrong-card-text)}})
|
|
|
|
(rf/defn unauthorized-operation
|
|
[cofx]
|
|
(rf/merge cofx
|
|
{:effects.utils/show-popup {:title ""
|
|
:content (i18n/label :t/keycard-unauthorized-operation)}}
|
|
(clear-on-card-connected)
|
|
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings])))
|
|
|
|
(rf/defn navigate-to-enter-pin-screen
|
|
{:events [:keycard/navigate-to-enter-pin-screen]}
|
|
[{:keys [db] :as cofx}]
|
|
(rf/merge cofx
|
|
{:db (assoc-in db [:keycard :pin :current] [])}
|
|
(navigation/navigate-to :enter-pin-settings nil)))
|
|
|
|
(defn- tag-lost-exception?
|
|
[code error]
|
|
(or
|
|
(= code "android.nfc.TagLostException")
|
|
(= error "Tag was lost.")
|
|
(= error "NFCError:100")))
|
|
|
|
(rf/defn process-error
|
|
[{:keys [db]} code error]
|
|
(when-not (tag-lost-exception? code error)
|
|
{:db (assoc-in db [:keycard :setup-step] :error)}))
|
|
|
|
(rf/defn get-keys-from-keycard
|
|
[{:keys [db]}]
|
|
(let [key-uid (get-in db [:profile/login :key-uid])
|
|
pin (string/join (get-in db [:keycard :pin :login]))]
|
|
(log/debug "[keycard] get-keys-from-keycard"
|
|
", not empty pin:"
|
|
(boolean (seq pin)))
|
|
(when (seq pin)
|
|
{:db (assoc-in db [:keycard :pin :status] :verifying)
|
|
:keycard/get-keys {:pin pin}})))
|
|
|
|
(rf/defn on-get-keys-success
|
|
{:events [:keycard.callback/on-get-keys-success]}
|
|
[{:keys [db] :as cofx} data]
|
|
(let [{:keys [key-uid encryption-public-key whisper-private-key]
|
|
:as account-data}
|
|
(js->clj data :keywordize-keys true)
|
|
{:keys [name]} (get-in db [:profile/profiles-overview key-uid])
|
|
key-uid (get-in db [:keycard :application-info :key-uid])
|
|
multiaccount-data (types/clj->json {:name name
|
|
:key-uid key-uid})
|
|
save-keys? (get-in db [:profile/login :save-password?])]
|
|
(rf/merge cofx
|
|
{:db
|
|
(-> db
|
|
(assoc-in [:keycard :pin :status] nil)
|
|
(assoc-in [:keycard :pin :login] [])
|
|
(update-in [:keycard :application-info]
|
|
assoc
|
|
:puk-retry-counter 5
|
|
:pin-retry-counter 3)
|
|
(assoc-in [:keycard :profile/profile]
|
|
(update account-data :whisper-public-key address/normalized-hex))
|
|
(assoc-in [:keycard :flow] nil)
|
|
(update :profile/login assoc
|
|
:password encryption-public-key
|
|
:key-uid key-uid
|
|
:name name))
|
|
|
|
:keycard/login-with-keycard {:multiaccount-data multiaccount-data
|
|
:password encryption-public-key
|
|
:chat-key whisper-private-key
|
|
:key-uid key-uid}}
|
|
(when save-keys?
|
|
(keychain/save-keycard-keys key-uid encryption-public-key whisper-private-key))
|
|
(clear-on-card-connected)
|
|
(clear-on-card-read)
|
|
(hide-connection-sheet))))
|
|
|
|
(rf/defn blocked-or-frozen-keycard-popup
|
|
[{:keys [db] :as cofx} card-status]
|
|
(rf/merge
|
|
cofx
|
|
{:db (assoc-in db [:keycard :pin :status] card-status)}
|
|
(hide-connection-sheet)
|
|
; do not try to display the popover if it is already open or
|
|
; we are in the login interface (which has a different handling)
|
|
(when-not (or (:profile/login db) (:popover/popover db))
|
|
(popover/show-popover {:view card-status}))))
|
|
|
|
(rf/defn blocked-keycard-popup
|
|
[cofx]
|
|
(blocked-or-frozen-keycard-popup cofx :blocked-card))
|
|
|
|
(rf/defn frozen-keycard-popup
|
|
[cofx]
|
|
(blocked-or-frozen-keycard-popup cofx :frozen-card))
|
|
|
|
(rf/defn on-get-keys-error
|
|
{:events [:keycard.callback/on-get-keys-error]}
|
|
[{:keys [db] :as cofx} error]
|
|
(log/debug "[keycard] get keys error: " error)
|
|
(let [tag-was-lost? (tag-lost? (:error error))
|
|
key-uid (get-in db [:keycard :application-info :key-uid])
|
|
flow (get-in db [:keycard :flow])
|
|
pin-retries-count (pin-retries (:error error))]
|
|
(if tag-was-lost?
|
|
{:db (assoc-in db [:keycard :pin :status] nil)}
|
|
(if-not (nil? pin-retries-count)
|
|
(rf/merge
|
|
cofx
|
|
{:db (-> db
|
|
(assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count)
|
|
(update-in [:keycard :pin]
|
|
assoc
|
|
:status :error
|
|
:login []
|
|
:import-multiaccount []
|
|
:error-label :t/pin-mismatch))}
|
|
(hide-connection-sheet)
|
|
(when (zero? pin-retries-count) (frozen-keycard-popup)))
|
|
(show-wrong-keycard-alert)))))
|
|
|
|
(rf/defn factory-reset
|
|
{:events [:keycard/factory-reset]}
|
|
[{:keys [db]} on-card-read]
|
|
(log/debug "[keycard] factory-reset")
|
|
{:db (update db :keycard dissoc :factory-reset-card?)
|
|
:keycard/factory-reset {:on-success on-card-read}})
|
|
|
|
;; Get application info
|
|
|
|
(rf/defn update-pairings
|
|
[{:keys [db]} instance-uid pairing]
|
|
(let [paired-on (datetime/timestamp)
|
|
pairings (-> (get-in db [:keycard :pairings])
|
|
(assoc instance-uid {:pairing pairing :paired-on paired-on}))]
|
|
{:keycard/persist-pairings pairings
|
|
:db (assoc-in db [:keycard :pairings] pairings)}))
|
|
|
|
(rf/defn get-application-info
|
|
{:events [:keycard/get-application-info]}
|
|
[{:keys [db]} on-card-read]
|
|
(log/debug "[keycard] get-application-info")
|
|
{:keycard/get-application-info {:on-success on-card-read}})
|
|
|
|
(rf/defn on-get-application-info-success
|
|
{:events [:keycard.callback/on-get-application-info-success]}
|
|
[{:keys [db] :as cofx} info on-success]
|
|
(let [{:keys [pin-retry-counter puk-retry-counter instance-uid new-pairing]} info
|
|
view-id (:view-id db)
|
|
{:keys [on-card-read]} (:keycard db)
|
|
on-success' (or on-success
|
|
on-card-read)
|
|
enter-step (get-in db
|
|
[:keycard :pin
|
|
:enter-step])]
|
|
(log/debug "[keycard] on-get-application-info-success"
|
|
"on-success" on-success'
|
|
"pin-retry-counter" pin-retry-counter
|
|
"puk-retry-counter" puk-retry-counter)
|
|
(rf/merge
|
|
cofx
|
|
{:db (-> db
|
|
(assoc-in [:keycard :pin :enter-step] enter-step)
|
|
(assoc-in [:keycard :application-info] info)
|
|
(assoc-in [:keycard :application-info :applet-installed?] true)
|
|
(assoc-in [:keycard :application-info-error] nil))}
|
|
(stash-on-card-read)
|
|
(when new-pairing
|
|
(update-pairings instance-uid new-pairing))
|
|
(when (and (zero? pin-retry-counter)
|
|
(pos? puk-retry-counter)
|
|
(not= enter-step :puk))
|
|
(frozen-keycard-popup))
|
|
(fn [{:keys [db] :as cofx}]
|
|
(if (zero? puk-retry-counter)
|
|
(blocked-keycard-popup cofx)
|
|
(when on-success'
|
|
(dispatch-event cofx on-success')))))))
|
|
|
|
(rf/defn on-get-application-info-error
|
|
{:events [:keycard.callback/on-get-application-info-error]}
|
|
[{:keys [db] :as cofx} error]
|
|
(let [on-card-read (get-in db [:keycard :on-card-read])
|
|
on-card-connected (get-in db [:keycard :on-card-connected])
|
|
last-on-card-connected (get-in db [:keycard :last-on-card-connected])
|
|
login? (= on-card-read :keycard/login-with-keycard)
|
|
tag-was-lost? (tag-lost? (:error error))]
|
|
(log/debug "[keycard] application info error"
|
|
error
|
|
on-card-connected
|
|
last-on-card-connected)
|
|
(when-not tag-was-lost?
|
|
(if login?
|
|
(rf/merge cofx
|
|
(clear-on-card-read)
|
|
(navigation/navigate-to :not-keycard nil))
|
|
(rf/merge cofx
|
|
{:db (assoc-in db [:keycard :application-info-error] error)}
|
|
|
|
(when (contains?
|
|
#{last-on-card-connected on-card-connected}
|
|
:keycard/prepare-to-sign)
|
|
(show-wrong-keycard-alert))
|
|
|
|
(when on-card-read
|
|
(dispatch-event on-card-read)))))))
|
|
|
|
(rf/defn on-card-connected
|
|
{:events [::on-card-connected]}
|
|
[{:keys [db] :as cofx} _]
|
|
(let [instance-uid (get-in db [:keycard :application-info :instance-uid])
|
|
key-uid (get-in db [:keycard :application-info :key-uid])
|
|
should-read-instance-uid? (nil? instance-uid)
|
|
on-card-connected (get-in db [:keycard :on-card-connected])
|
|
on-card-read (cond
|
|
should-read-instance-uid? :keycard/get-application-info
|
|
:else (get-in db [:keycard :on-card-read]))]
|
|
(log/debug "[keycard] on-card-connected" on-card-connected
|
|
"on-card-read" on-card-read)
|
|
(when on-card-connected
|
|
(rf/merge cofx
|
|
{:db (-> db
|
|
(assoc-in [:keycard :card-read-in-progress?] (boolean on-card-read)))}
|
|
(when on-card-connected
|
|
(dispatch-event on-card-connected))
|
|
(stash-on-card-connected)
|
|
(when (and on-card-read
|
|
(nil? on-card-connected))
|
|
(get-application-info on-card-read))))))
|
|
|
|
(rf/defn on-card-disconnected
|
|
{:events [::on-card-disconnected]}
|
|
[{:keys [db] :as cofx} _]
|
|
(log/debug "[keycard] card disconnected")
|
|
(rf/merge cofx
|
|
{:db (-> db
|
|
(assoc-in [:keycard :card-read-in-progress?] false))}
|
|
(restore-on-card-connected)
|
|
(restore-on-card-read)))
|
|
|
|
(defn keycard-multiaccount?
|
|
[db]
|
|
(boolean (get-in db [:profile/profile :keycard-pairing])))
|
|
|
|
(rf/defn verify-pin
|
|
{:events [:keycard/verify-pin]}
|
|
[{:keys [db] :as cofx} {:keys [pin-step on-card-connected on-failure on-success]}]
|
|
(let [on-success (or on-success
|
|
(get-in db [:keycard :pin :on-verified]))
|
|
on-failure (or on-failure
|
|
(get-in db [:keycard :pin :on-verified-failure]))
|
|
pin-step (or pin-step
|
|
(get-in db [:keycard :pin :step]))]
|
|
(rf/merge
|
|
cofx
|
|
{:db (update-in db
|
|
[:keycard :pin]
|
|
assoc
|
|
:step pin-step
|
|
:on-verified on-success
|
|
:on-verified-failure on-failure)}
|
|
(show-connection-sheet
|
|
{:on-card-connected (or on-card-connected :keycard/verify-pin)
|
|
:handler
|
|
(fn [{:keys [db] :as cofx}]
|
|
(let [pin (vector->string (get-in db [:keycard :pin pin-step]))]
|
|
(rf/merge
|
|
cofx
|
|
{:db (assoc-in db [:keycard :pin :status] :verifying)
|
|
:keycard/verify-pin {:pin pin}})))}))))
|
|
|
|
(rf/defn navigete-to-keycard-settings
|
|
{:events [::navigate-to-keycard-settings]}
|
|
[cofx]
|
|
(navigation/set-stack-root :profile-stack [:my-profile :keycard-settings]))
|