From fcb06ee6bc1b3cc718839032751fbb2c7ee522f0 Mon Sep 17 00:00:00 2001 From: andrey Date: Tue, 18 Jun 2024 12:25:35 +0200 Subject: [PATCH] [#20434] Allow users from v1 to login with keycard --- android/gradle.properties | 2 +- resources/images/ui2/nfc-prompt@2x.png | Bin 0 -> 6107 bytes resources/images/ui2/nfc-prompt@3x.png | Bin 0 -> 9053 bytes resources/images/ui2/nfc-success@2x.png | Bin 0 -> 6264 bytes resources/images/ui2/nfc-success@3x.png | Bin 0 -> 9378 bytes src/keycard/keycard.cljs | 313 +++++++++++++-- src/keycard/real_keycard.cljs | 365 ------------------ src/mocks/js_dependencies.cljs | 7 +- src/native_module/core.cljs | 40 +- src/quo/components/pin_input/pin/view.cljs | 45 +++ src/quo/components/pin_input/view.cljs | 27 ++ src/quo/core.cljs | 4 + src/status_im/common/bottom_sheet/view.cljs | 8 +- src/status_im/common/resources.cljs | 4 +- src/status_im/contexts/keycard/effects.cljs | 105 +++++ src/status_im/contexts/keycard/events.cljs | 62 +++ .../contexts/keycard/login/events.cljs | 74 ++++ .../contexts/keycard/pin/events.cljs | 21 + src/status_im/contexts/keycard/pin/view.cljs | 25 ++ .../contexts/keycard/sheet/events.cljs | 36 ++ .../contexts/keycard/sheet/view.cljs | 24 ++ src/status_im/contexts/keycard/utils.cljs | 45 +++ src/status_im/contexts/preview/quo/main.cljs | 3 + .../preview/quo/pin_input/pin_input.cljs | 29 ++ .../contexts/profile/profiles/view.cljs | 36 +- src/status_im/db.cljs | 7 +- src/status_im/events.cljs | 5 + src/status_im/subs/keycard.cljs | 25 ++ src/status_im/subs/root.cljs | 1 + 29 files changed, 857 insertions(+), 456 deletions(-) create mode 100644 resources/images/ui2/nfc-prompt@2x.png create mode 100644 resources/images/ui2/nfc-prompt@3x.png create mode 100644 resources/images/ui2/nfc-success@2x.png create mode 100644 resources/images/ui2/nfc-success@3x.png delete mode 100644 src/keycard/real_keycard.cljs create mode 100644 src/quo/components/pin_input/pin/view.cljs create mode 100644 src/quo/components/pin_input/view.cljs create mode 100644 src/status_im/contexts/keycard/effects.cljs create mode 100644 src/status_im/contexts/keycard/events.cljs create mode 100644 src/status_im/contexts/keycard/login/events.cljs create mode 100644 src/status_im/contexts/keycard/pin/events.cljs create mode 100644 src/status_im/contexts/keycard/pin/view.cljs create mode 100644 src/status_im/contexts/keycard/sheet/events.cljs create mode 100644 src/status_im/contexts/keycard/sheet/view.cljs create mode 100644 src/status_im/contexts/keycard/utils.cljs create mode 100644 src/status_im/contexts/preview/quo/pin_input/pin_input.cljs create mode 100644 src/status_im/subs/keycard.cljs diff --git a/android/gradle.properties b/android/gradle.properties index 5fd5209294..5474900dad 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -45,7 +45,7 @@ ANDROID_ABI_INCLUDE=armeabi-v7a;arm64-v8a;x86;x86_64 org.gradle.jvmargs=-Xmx8704M -XX:+UseParallelGC -versionCode=9999 +versionCode=2024052414 commitHash=unknown # Use this property to enable support to the new architecture. diff --git a/resources/images/ui2/nfc-prompt@2x.png b/resources/images/ui2/nfc-prompt@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c2b224cc7fb6775cb64fb0ff1204fdf175ab5005 GIT binary patch literal 6107 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 X|E>N^Tw>jZ0dAX^-@spYdiH++N!Q4> literal 0 HcmV?d00001 diff --git a/resources/images/ui2/nfc-success@2x.png b/resources/images/ui2/nfc-success@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8a24eae441e7c08e0c925d1353933b915b658212 GIT binary patch literal 6264 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%0DCKC9qKIwnzZAzD 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 literal 0 HcmV?d00001 diff --git a/src/keycard/keycard.cljs b/src/keycard/keycard.cljs index 037e721e63..7ea3692fe5 100644 --- a/src/keycard/keycard.cljs +++ b/src/keycard/keycard.cljs @@ -1,38 +1,277 @@ -(ns keycard.keycard) +(ns keycard.keycard + (:require + ["react-native" :as rn] + ["react-native-status-keycard" :default status-keycard] + [react-native.platform :as platform] + [taoensso.timbre :as log] + [utils.address :as address])) -(defprotocol Keycard - (start-nfc [this args]) - (stop-nfc [this args]) - (set-nfc-message [this args]) - (check-nfc-support [this args]) - (check-nfc-enabled [this args]) - (open-nfc-settings [this]) - (register-card-events [this args]) - (set-pairings [this args]) - (on-card-disconnected [this callback]) - (on-card-connected [this callback]) - (remove-event-listener [this event]) - (remove-event-listeners [this]) - (get-application-info [this args]) - (factory-reset [this args]) - (install-applet [this args]) - (install-cash-applet [this args]) - (init-card [this args]) - (install-applet-and-init-card [this args]) - (pair [this args]) - (generate-and-load-key [this args]) - (unblock-pin [this args]) - (verify-pin [this args]) - (change-pin [this args]) - (change-puk [this args]) - (change-pairing [this args]) - (unpair [this args]) - (delete [this args]) - (remove-key [this args]) - (remove-key-with-unpair [this args]) - (export-key [this args]) - (unpair-and-delete [this args]) - (import-keys [this args]) - (get-keys [this args]) - (sign [this args]) - (sign-typed-data [this args])) +(defonce event-emitter + (if platform/ios? + (new (.-NativeEventEmitter rn) status-keycard) + (.-DeviceEventEmitter rn))) + +(defn start-nfc + [{:keys [on-success on-failure prompt-message]}] + (log/debug "start-nfc") + (.. status-keycard + (startNFC (str prompt-message)) + (then on-success) + (catch on-failure))) + +(defn stop-nfc + [{:keys [on-success on-failure error-message]}] + (log/debug "stop-nfc") + (.. status-keycard + (stopNFC (str error-message)) + (then on-success) + (catch on-failure))) + +(defn set-nfc-message + [{:keys [on-success on-failure status-message]}] + (log/debug "set-nfc-message") + (.. status-keycard + (setNFCMessage (str status-message)) + (then on-success) + (catch on-failure))) + +(defn check-nfc-support + [{:keys [on-success]}] + (.. status-keycard + nfcIsSupported + (then on-success))) + +(defn check-nfc-enabled + [{:keys [on-success]}] + (.. status-keycard + nfcIsEnabled + (then on-success))) + +(defn open-nfc-settings + [] + (.openNfcSettings status-keycard)) + +(defn remove-event-listeners + [] + (doseq [event ["keyCardOnConnected" "keyCardOnDisconnected" "keyCardOnNFCUserCancelled" + "keyCardOnNFCTimeout"]] + (.removeAllListeners ^js event-emitter event))) + +(defn remove-event-listener + [^js event] + (when event + (.remove event))) + +(defn on-card-connected + [callback] + (.addListener ^js event-emitter "keyCardOnConnected" callback)) + +(defn on-card-disconnected + [callback] + (.addListener ^js event-emitter "keyCardOnDisconnected" callback)) + +(defn on-nfc-user-cancelled + [callback] + (.addListener ^js event-emitter "keyCardOnNFCUserCancelled" callback)) + +(defn on-nfc-timeout + [callback] + (.addListener ^js event-emitter "keyCardOnNFCTimeout" callback)) + +(defn on-nfc-enabled + [callback] + (.addListener ^js event-emitter "keyCardOnNFCEnabled" callback)) + +(defn on-nfc-disabled + [callback] + (.addListener ^js event-emitter "keyCardOnNFCDisabled" callback)) + +(defn set-pairings + [pairings] + (.. status-keycard (setPairings (clj->js (or pairings {}))))) + +(defn get-application-info + [{:keys [on-success on-failure]}] + (.. status-keycard + (getApplicationInfo) + (then (fn [response] + (let [info (-> response + (js->clj :keywordize-keys true) + (update :key-uid address/normalized-hex))] + (on-success info)))) + (catch on-failure))) + +(defn factory-reset + [{:keys [on-success on-failure]}] + (.. status-keycard + (factoryReset) + (then (fn [response] + (let [info (-> response + (js->clj :keywordize-keys true) + (update :key-uid address/normalized-hex))] + (on-success info)))) + (catch on-failure))) + +(defn install-applet + [{:keys [on-success on-failure]}] + (.. status-keycard + installApplet + (then on-success) + (catch on-failure))) + +(defn install-cash-applet + [{:keys [on-success on-failure]}] + (.. status-keycard + installCashApplet + (then on-success) + (catch on-failure))) + +(defn init-card + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (init pin) + (then on-success) + (catch on-failure))) + +(defn install-applet-and-init-card + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (installAppletAndInitCard pin) + (then on-success) + (catch on-failure))) + +(defn pair + [{:keys [password on-success on-failure]}] + (when password + (.. status-keycard + (pair password) + (then on-success) + (catch on-failure)))) + +(defn generate-and-load-key + [{:keys [mnemonic pin on-success on-failure]}] + (.. status-keycard + (generateAndLoadKey mnemonic pin) + (then on-success) + (catch on-failure))) + +(defn unblock-pin + [{:keys [puk new-pin on-success on-failure]}] + (when (and new-pin puk) + (.. status-keycard + (unblockPin puk new-pin) + (then on-success) + (catch on-failure)))) + +(defn verify-pin + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (verifyPin pin) + (then on-success) + (catch on-failure)))) + +(defn change-pin + [{:keys [current-pin new-pin on-success on-failure]}] + (when (and current-pin new-pin) + (.. status-keycard + (changePin current-pin new-pin) + (then on-success) + (catch on-failure)))) + +(defn change-puk + [{:keys [pin puk on-success on-failure]}] + (when (and pin puk) + (.. status-keycard + (changePUK pin puk) + (then on-success) + (catch on-failure)))) + +(defn change-pairing + [{:keys [pin pairing on-success on-failure]}] + (when (and pin pairing) + (.. status-keycard + (changePairingPassword pin pairing) + (then on-success) + (catch on-failure)))) + +(defn unpair + [{:keys [pin on-success on-failure]}] + (when pin + (.. status-keycard + (unpair pin) + (then on-success) + (catch on-failure)))) + +(defn delete + [{:keys [on-success on-failure]}] + (.. status-keycard + (delete) + (then on-success) + (catch on-failure))) + +(defn remove-key + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (removeKey pin) + (then on-success) + (catch on-failure))) + +(defn remove-key-with-unpair + [{:keys [pin on-success on-failure]}] + (.. status-keycard + (removeKeyWithUnpair pin) + (then on-success) + (catch on-failure))) + +(defn export-key + [{:keys [pin path on-success on-failure]}] + (.. status-keycard + (exportKeyWithPath pin path) + (then on-success) + (catch on-failure))) + +(defn unpair-and-delete + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (unpairAndDelete pin) + (then on-success) + (catch on-failure)))) + +(defn import-keys + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (importKeys pin) + (then on-success) + (catch on-failure)))) + +(defn get-keys + [{:keys [pin on-success on-failure]}] + (when (not-empty pin) + (.. status-keycard + (getKeys pin) + (then on-success) + (catch on-failure)))) + +(defn sign + [{pin :pin path :path card-hash :hash on-success :on-success on-failure :on-failure}] + (when (and pin card-hash) + (if path + (.. status-keycard + (signWithPath pin path card-hash) + (then on-success) + (catch on-failure)) + (.. status-keycard + (sign pin card-hash) + (then on-success) + (catch on-failure))))) + +(defn sign-typed-data + [{card-hash :hash on-success :on-success on-failure :on-failure}] + (when card-hash + (.. status-keycard + (signPinless card-hash) + (then on-success) + (catch on-failure)))) diff --git a/src/keycard/real_keycard.cljs b/src/keycard/real_keycard.cljs deleted file mode 100644 index 7250479e69..0000000000 --- a/src/keycard/real_keycard.cljs +++ /dev/null @@ -1,365 +0,0 @@ -(ns keycard.real-keycard - (:require - ["react-native" :as rn] - ["react-native-status-keycard" :default status-keycard] - [keycard.keycard :as keycard] - [react-native.platform :as platform] - [taoensso.timbre :as log] - [utils.address :as address])) - -(defonce event-emitter - (if platform/ios? - (new (.-NativeEventEmitter rn) status-keycard) - (.-DeviceEventEmitter rn))) - -(defonce active-listeners (atom [])) - -(defn start-nfc - [{:keys [on-success on-failure prompt-message]}] - (log/debug "start-nfc") - (.. status-keycard - (startNFC (str prompt-message)) - (then on-success) - (catch on-failure))) - -(defn stop-nfc - [{:keys [on-success on-failure error-message]}] - (log/debug "stop-nfc") - (.. status-keycard - (stopNFC (str error-message)) - (then on-success) - (catch on-failure))) - -(defn set-nfc-message - [{:keys [on-success on-failure status-message]}] - (log/debug "set-nfc-message") - (.. status-keycard - (setNFCMessage (str status-message)) - (then on-success) - (catch on-failure))) - -(defn check-nfc-support - [{:keys [on-success]}] - (.. status-keycard - nfcIsSupported - (then on-success))) - -(defn check-nfc-enabled - [{:keys [on-success]}] - (.. status-keycard - nfcIsEnabled - (then on-success))) - -(defn open-nfc-settings - [] - (.openNfcSettings status-keycard)) - -(defn remove-event-listeners - [] - (doseq [event ["keyCardOnConnected" "keyCardOnDisconnected" "keyCardOnNFCUserCancelled" - "keyCardOnNFCTimeout"]] - (.removeAllListeners ^js event-emitter event))) - -(defn remove-event-listener - [^js event] - (.remove event)) - -(defn on-card-connected - [callback] - (.addListener ^js event-emitter "keyCardOnConnected" callback)) - -(defn on-card-disconnected - [callback] - (.addListener ^js event-emitter "keyCardOnDisconnected" callback)) - -(defn on-nfc-user-cancelled - [callback] - (.addListener ^js event-emitter "keyCardOnNFCUserCancelled" callback)) - -(defn on-nfc-timeout - [callback] - (.addListener ^js event-emitter "keyCardOnNFCTimeout" callback)) - -(defn on-nfc-enabled - [callback] - (.addListener ^js event-emitter "keyCardOnNFCEnabled" callback)) - -(defn on-nfc-disabled - [callback] - (.addListener ^js event-emitter "keyCardOnNFCDisabled" callback)) - -(defn set-pairings - [{:keys [pairings]}] - (.. status-keycard (setPairings (clj->js (or pairings {}))))) - -(defn register-card-events - [args] - (doseq [listener @active-listeners] - (remove-event-listener listener)) - (reset! active-listeners - [(on-card-connected (:on-card-connected args)) - (on-card-disconnected (:on-card-disconnected args)) - (on-nfc-user-cancelled (:on-nfc-user-cancelled args)) - (on-nfc-timeout (:on-nfc-timeout args)) - (on-nfc-enabled (:on-nfc-enabled args)) - (on-nfc-disabled (:on-nfc-disabled args))])) - -(defn get-application-info - [{:keys [on-success on-failure]}] - - (.. status-keycard - (getApplicationInfo) - (then (fn [response] - (let [info (-> response - (js->clj :keywordize-keys true) - (update :key-uid address/normalized-hex))] - (on-success info)))) - (catch on-failure))) - -(defn factory-reset - [{:keys [on-success on-failure]}] - (.. status-keycard - (factoryReset) - (then (fn [response] - (let [info (-> response - (js->clj :keywordize-keys true) - (update :key-uid address/normalized-hex))] - (on-success info)))) - (catch on-failure))) - -(defn install-applet - [{:keys [on-success on-failure]}] - (.. status-keycard - installApplet - (then on-success) - (catch on-failure))) - -(defn install-cash-applet - [{:keys [on-success on-failure]}] - (.. status-keycard - installCashApplet - (then on-success) - (catch on-failure))) - -(defn init-card - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (init pin) - (then on-success) - (catch on-failure))) - -(defn install-applet-and-init-card - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (installAppletAndInitCard pin) - (then on-success) - (catch on-failure))) - -(defn pair - [{:keys [password on-success on-failure]}] - (when password - (.. status-keycard - (pair password) - (then on-success) - (catch on-failure)))) - -(defn generate-and-load-key - [{:keys [mnemonic pin on-success on-failure]}] - (.. status-keycard - (generateAndLoadKey mnemonic pin) - (then on-success) - (catch on-failure))) - -(defn unblock-pin - [{:keys [puk new-pin on-success on-failure]}] - (when (and new-pin puk) - (.. status-keycard - (unblockPin puk new-pin) - (then on-success) - (catch on-failure)))) - -(defn verify-pin - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (verifyPin pin) - (then on-success) - (catch on-failure)))) - -(defn change-pin - [{:keys [current-pin new-pin on-success on-failure]}] - (when (and current-pin new-pin) - (.. status-keycard - (changePin current-pin new-pin) - (then on-success) - (catch on-failure)))) - -(defn change-puk - [{:keys [pin puk on-success on-failure]}] - (when (and pin puk) - (.. status-keycard - (changePUK pin puk) - (then on-success) - (catch on-failure)))) - -(defn change-pairing - [{:keys [pin pairing on-success on-failure]}] - (when (and pin pairing) - (.. status-keycard - (changePairingPassword pin pairing) - (then on-success) - (catch on-failure)))) - -(defn unpair - [{:keys [pin on-success on-failure]}] - (when pin - (.. status-keycard - (unpair pin) - (then on-success) - (catch on-failure)))) - -(defn delete - [{:keys [on-success on-failure]}] - (.. status-keycard - (delete) - (then on-success) - (catch on-failure))) - -(defn remove-key - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (removeKey pin) - (then on-success) - (catch on-failure))) - -(defn remove-key-with-unpair - [{:keys [pin on-success on-failure]}] - (.. status-keycard - (removeKeyWithUnpair pin) - (then on-success) - (catch on-failure))) - -(defn export-key - [{:keys [pin path on-success on-failure]}] - (.. status-keycard - (exportKeyWithPath pin path) - (then on-success) - (catch on-failure))) - -(defn unpair-and-delete - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (unpairAndDelete pin) - (then on-success) - (catch on-failure)))) - -(defn import-keys - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (importKeys pin) - (then on-success) - (catch on-failure)))) - -(defn get-keys - [{:keys [pin on-success on-failure]}] - (when (not-empty pin) - (.. status-keycard - (getKeys pin) - (then on-success) - (catch on-failure)))) - -(defn sign - [{pin :pin path :path card-hash :hash on-success :on-success on-failure :on-failure}] - (when (and pin card-hash) - (if path - (.. status-keycard - (signWithPath pin path card-hash) - (then on-success) - (catch on-failure)) - (.. status-keycard - (sign pin card-hash) - (then on-success) - (catch on-failure))))) - -(defn sign-typed-data - [{card-hash :hash on-success :on-success on-failure :on-failure}] - (when card-hash - (.. status-keycard - (signPinless card-hash) - (then on-success) - (catch on-failure)))) - -(defrecord RealKeycard [] - keycard/Keycard - (keycard/start-nfc [_this args] - (start-nfc args)) - (keycard/stop-nfc [_this args] - (stop-nfc args)) - (keycard/set-nfc-message [_this args] - (set-nfc-message args)) - (keycard/check-nfc-support [_this args] - (check-nfc-support args)) - (keycard/check-nfc-enabled [_this args] - (check-nfc-enabled args)) - (keycard/open-nfc-settings [_this] - (open-nfc-settings)) - (keycard/register-card-events [_this args] - (register-card-events args)) - (keycard/on-card-connected [_this callback] - (on-card-connected callback)) - (keycard/on-card-disconnected [_this callback] - (on-card-disconnected callback)) - (keycard/remove-event-listener [_this event] - (remove-event-listener event)) - (keycard/remove-event-listeners [_this] - (remove-event-listeners)) - (keycard/set-pairings [_this args] - (set-pairings args)) - (keycard/get-application-info [_this args] - (get-application-info args)) - (keycard/factory-reset [_this args] - (factory-reset args)) - (keycard/install-applet [_this args] - (install-applet args)) - (keycard/install-cash-applet [_this args] - (install-cash-applet args)) - (keycard/init-card [_this args] - (init-card args)) - (keycard/install-applet-and-init-card [_this args] - (install-applet-and-init-card args)) - (keycard/pair [_this args] - (pair args)) - (keycard/generate-and-load-key [_this args] - (generate-and-load-key args)) - (keycard/unblock-pin [_this args] - (unblock-pin args)) - (keycard/verify-pin [_this args] - (verify-pin args)) - (keycard/change-pin [_this args] - (change-pin args)) - (keycard/change-puk [_this args] - (change-puk args)) - (keycard/change-pairing [_this args] - (change-pairing args)) - (keycard/unpair [_this args] - (unpair args)) - (keycard/delete [_this args] - (delete args)) - (keycard/remove-key [_this args] - (remove-key args)) - (keycard/remove-key-with-unpair [_this args] - (remove-key-with-unpair args)) - (keycard/export-key [_this args] - (export-key args)) - (keycard/unpair-and-delete [_this args] - (unpair-and-delete args)) - (keycard/import-keys [_this args] - (import-keys args)) - (keycard/get-keys [_this args] - (get-keys args)) - (keycard/sign [_this args] - (sign args)) - (keycard/sign-typed-data [_this args] - (sign-typed-data args))) diff --git a/src/mocks/js_dependencies.cljs b/src/mocks/js_dependencies.cljs index fb0a2ddf9a..a1b8aee2c0 100644 --- a/src/mocks/js_dependencies.cljs +++ b/src/mocks/js_dependencies.cljs @@ -110,8 +110,11 @@ (def status-keycard #js {:default #js - {:nfcIsSupported (fn [] #js {:then identity}) - :nfcIsEnabled (fn [] #js {:then identity})}}) + {:nfcIsSupported (fn [] #js {:then identity}) + :nfcIsEnabled (fn [] #js {:then identity}) + :getApplicationInfo (fn [] #js {:then identity}) + :getKeys (fn [] #js {:then identity}) + :setPairings (fn [] #js {:then identity})}}) (def snoopy #js {:default #js {}}) (def snoopy-filter #js {:default #js {}}) diff --git a/src/native_module/core.cljs b/src/native_module/core.cljs index 9195bf0f94..1bb471cdf6 100644 --- a/src/native_module/core.cljs +++ b/src/native_module/core.cljs @@ -85,31 +85,6 @@ config #(callback (types/json->clj %)))) -(defn save-multiaccount-and-login-with-keycard - "NOTE: chat-key is a whisper private key sent from keycard" - [key-uid multiaccount-data password settings config accounts-data chat-key] - (log/debug "[native-module] save-account-and-login-with-keycard") - (init-keystore - key-uid - #(.saveAccountAndLoginWithKeycard - ^js (account-manager) - multiaccount-data - password - settings - config - accounts-data - chat-key))) - -(defn login-with-config - "NOTE: beware, the password has to be sha3 hashed" - [key-uid account-data hashed-password config] - (log/debug "[native-module] loginWithConfig") - (clear-web-data) - (let [config (if config (types/clj->json config) "")] - (init-keystore - key-uid - #(.loginWithConfig ^js (account-manager) account-data hashed-password config)))) - (defn login-account "NOTE: beware, the password has to be sha3 hashed" [{:keys [keyUid] :as request}] @@ -251,6 +226,21 @@ (log/debug "[native-module] verify-database-password") (.verifyDatabasePassword ^js (account-manager) key-uid hashed-password callback)) +(defn save-multiaccount-and-login-with-keycard + "NOTE: chat-key is a whisper private key sent from keycard" + [key-uid multiaccount-data password settings config accounts-data chat-key] + (log/debug "[native-module] save-account-and-login-with-keycard") + (init-keystore + key-uid + #(.saveAccountAndLoginWithKeycard + ^js (account-manager) + multiaccount-data + password + settings + config + accounts-data + chat-key))) + (defn login-with-keycard [{:keys [key-uid multiaccount-data password chat-key node-config]}] (log/debug "[native-module] login-with-keycard") diff --git a/src/quo/components/pin_input/pin/view.cljs b/src/quo/components/pin_input/pin/view.cljs new file mode 100644 index 0000000000..e4dcab6ec9 --- /dev/null +++ b/src/quo/components/pin_input/pin/view.cljs @@ -0,0 +1,45 @@ +(ns quo.components.pin-input.pin.view + (:require [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn])) + +(defn view + [{:keys [theme state blur?] + :or {state :default}}] + (let [app-theme (quo.theme/use-theme) + theme (or theme app-theme)] + [rn/view {:style {:width 36 :height 36 :align-items :center :justify-content :center}} + (case state + :active + [rn/view + {:style {:width 16 + :height 16 + :border-radius 8 + :background-color (if blur? + colors/white-opa-20 + (colors/theme-colors colors/neutral-40 colors/neutral-50 theme))}}] + :filled + [rn/view + {:style {:width 16 + :height 16 + :border-radius 8 + :background-color (if blur? + colors/white + (colors/theme-colors colors/neutral-100 colors/white theme))}}] + :error + [rn/view + {:style {:width 16 + :height 16 + :border-radius 8 + :background-color (if blur? + colors/danger-60 + (colors/theme-colors colors/danger-50 colors/danger-60 theme))}}] + :default + [rn/view + {:style + {:width 12 + :height 12 + :border-radius 6 + :background-color (if blur? + colors/white-opa-20 + (colors/theme-colors colors/neutral-40 colors/neutral-50 theme))}}])])) diff --git a/src/quo/components/pin_input/view.cljs b/src/quo/components/pin_input/view.cljs new file mode 100644 index 0000000000..c83f8a80eb --- /dev/null +++ b/src/quo/components/pin_input/view.cljs @@ -0,0 +1,27 @@ +(ns quo.components.pin-input.view + (:require [quo.components.markdown.text :as text] + [quo.components.pin-input.pin.view :as pin] + [quo.foundations.colors :as colors] + quo.theme + [react-native.core :as rn])) + +(defn view + [{:keys [number-of-pins number-of-filled-pins error? info] + :or {number-of-pins 6 number-of-filled-pins 0}}] + (let [theme (quo.theme/use-theme)] + [rn/view {:style {:align-items :center}} + [rn/view {:style {:flex-direction :row}} + (for [i (range 1 (inc number-of-pins))] + ^{:key (str "pin" i)} + [pin/view + {:state (cond + error? :error + (<= i number-of-filled-pins) :filled + (= i (inc number-of-filled-pins)) :active + :else :default)}])] + (when info + [text/text + {:style {:color (if error? + (colors/theme-colors colors/danger-50 colors/danger-60 theme) + (colors/theme-colors colors/neutral-50 colors/neutral-40 theme))} + :size :paragraph-2} info])])) diff --git a/src/quo/core.cljs b/src/quo/core.cljs index 7f0abc4704..47c42f567b 100644 --- a/src/quo/core.cljs +++ b/src/quo/core.cljs @@ -120,6 +120,7 @@ quo.components.overlay.view quo.components.password.password-tips.view quo.components.password.tips.view + quo.components.pin-input.view quo.components.profile.collectible-list-item.view quo.components.profile.collectible.view quo.components.profile.expanded-collectible.view @@ -320,6 +321,9 @@ (def keyboard-key quo.components.numbered-keyboard.keyboard-key.view/view) (def numbered-keyboard quo.components.numbered-keyboard.numbered-keyboard.view/view) +;;;; PIN input +(def pin-input quo.components.pin-input.view/view) + ;;;; Links (def internal-link-card quo.components.links.internal-link-card.view/view) (def link-preview quo.components.links.link-preview.view/view) diff --git a/src/status_im/common/bottom_sheet/view.cljs b/src/status_im/common/bottom_sheet/view.cljs index a941d9bad0..c4d1b1abee 100644 --- a/src/status_im/common/bottom_sheet/view.cljs +++ b/src/status_im/common/bottom_sheet/view.cljs @@ -63,8 +63,10 @@ (defn view [{:keys [hide? insets]} {:keys [content selected-item padding-bottom-override border-radius on-close shell? - gradient-cover? customization-color hide-handle? blur-radius] - :or {border-radius 12}}] + gradient-cover? customization-color hide-handle? blur-radius + hide-on-background-press?] + :or {border-radius 12 + hide-on-background-press? true}}] (let [theme (quo.theme/use-theme) {window-height :height} (rn/get-window) [sheet-height set-sheet-height] (rn/use-state 0) @@ -119,7 +121,7 @@ :on-layout handle-layout-height} ;; backdrop [rn/pressable - {:on-press #(rf/dispatch [:hide-bottom-sheet]) + {:on-press #(when hide-on-background-press? (rf/dispatch [:hide-bottom-sheet])) :style {:flex 1}} [reanimated/view {:style (reanimated/apply-animations-to-style diff --git a/src/status_im/common/resources.cljs b/src/status_im/common/resources.cljs index 6d6013f8e6..14eacd856d 100644 --- a/src/status_im/common/resources.cljs +++ b/src/status_im/common/resources.cljs @@ -25,7 +25,9 @@ :invite-friends (js/require "../resources/images/ui2/invite-friends.png") :transaction-progress (js/require "../resources/images/ui2/transaction-progress.png") :welcome-illustration (js/require "../resources/images/ui2/welcome_illustration.png") - :notifications (js/require "../resources/images/ui2/notifications.png")}) + :notifications (js/require "../resources/images/ui2/notifications.png") + :nfc-prompt (js/require "../resources/images/ui2/nfc-prompt.png") + :nfc-success (js/require "../resources/images/ui2/nfc-success.png")}) (def ui-themed {:angry-man diff --git a/src/status_im/contexts/keycard/effects.cljs b/src/status_im/contexts/keycard/effects.cljs new file mode 100644 index 0000000000..12486ccb0b --- /dev/null +++ b/src/status_im/contexts/keycard/effects.cljs @@ -0,0 +1,105 @@ +(ns status-im.contexts.keycard.effects + (:require [keycard.keycard :as keycard] + [native-module.core :as native-module] + [react-native.async-storage :as async-storage] + [react-native.platform :as platform] + [taoensso.timbre :as log] + [utils.re-frame :as rf])) + +(defonce active-listeners (atom [])) + +(defn register-card-events + [] + (doseq [listener @active-listeners] + (keycard/remove-event-listener listener)) + (reset! active-listeners + [(keycard/on-card-connected #(rf/dispatch [:keycard.callback/on-card-connected])) + (keycard/on-card-disconnected #(rf/dispatch [:keycard.callback/on-card-disconnected])) + (when platform/ios? + (keycard/on-nfc-user-cancelled #(rf/dispatch [:keycard.ios.callback/on-nfc-user-cancelled]))) + (when platform/ios? + (keycard/on-nfc-timeout #(rf/dispatch [:keycard.ios.callback/on-nfc-timeout]))) + (keycard/on-nfc-enabled #(rf/dispatch [:keycard.callback/check-nfc-enabled-success true])) + (keycard/on-nfc-disabled #(rf/dispatch [:keycard.callback/check-nfc-enabled-success false]))])) +(rf/reg-fx :effects.keycard/register-card-events register-card-events) + +(defn check-nfc-enabled + [] + (log/debug "[keycard] check-nfc-enabled") + (keycard/check-nfc-enabled + {:on-success + (fn [response] + (log/debug "[keycard response] check-nfc-enabled") + (rf/dispatch [:keycard.callback/check-nfc-enabled-success response]))})) +(rf/reg-fx :effects.keycard/check-nfc-enabled check-nfc-enabled) + +(rf/reg-fx + :effects.keycard.ios/start-nfc + (fn [{:keys [on-success on-failure]}] + (log/debug "fx start-nfc") + (keycard/start-nfc {:on-success (or on-success #()) :on-failure (or on-failure #())}))) + +(rf/reg-fx + :effects.keycard.ios/stop-nfc + (fn [{:keys [on-success on-failure]}] + (log/debug "fx stop-nfc") + (keycard/stop-nfc {:on-success (or on-success #()) :on-failure (or on-failure #())}))) + +(defn- error-object->map + [^js object] + {:code (.-code object) + :error (.-message object)}) + +(defn get-application-info + [{:keys [on-success on-failure] :as args}] + (log/debug "[keycard] get-application-info") + (keycard/get-application-info + (merge + args + {:on-success + (fn [response] + (log/debug "[keycard response succ] get-application-info") + (when on-success + (on-success response))) + :on-failure + (fn [response] + (log/debug "[keycard response fail] get-application-info") + (when on-failure + (on-failure (error-object->map response))))}))) +(rf/reg-fx :effects.keycard/get-application-info get-application-info) + +(defn get-keys + [{:keys [on-success on-failure] :as args}] + (log/debug "[keycard] get-keys") + (keycard/get-keys + (assoc + args + :on-success + (fn [response] + (log/debug "[keycard response succ] get-keys") + (when on-success + (on-success response))) + :on-failure + (fn [response] + (log/warn "[keycard response fail] get-keys" + (error-object->map response)) + (when on-failure + (on-failure (error-object->map response))))))) +(rf/reg-fx :effects.keycard/get-keys get-keys) + +(defn login + [args] + (native-module/login-with-keycard args)) +(rf/reg-fx :effects.keycard/login-with-keycard login) + +(defn retrieve-pairings + [] + (async-storage/get-item + "status-keycard-pairings" + #(rf/dispatch [:keycard.callback/on-retrieve-pairings-success %]))) +(rf/reg-fx :effects.keycard/retrieve-pairings retrieve-pairings) + +(defn set-pairing-to-keycard + [pairings] + (keycard/set-pairings pairings)) +(rf/reg-fx :effects.keycard/set-pairing-to-keycard set-pairing-to-keycard) diff --git a/src/status_im/contexts/keycard/events.cljs b/src/status_im/contexts/keycard/events.cljs new file mode 100644 index 0000000000..e58b7c9138 --- /dev/null +++ b/src/status_im/contexts/keycard/events.cljs @@ -0,0 +1,62 @@ +(ns status-im.contexts.keycard.events + (:require [re-frame.core :as rf] + status-im.contexts.keycard.login.events + status-im.contexts.keycard.pin.events + status-im.contexts.keycard.sheet.events + [taoensso.timbre :as log])) + +(rf/reg-event-fx :keycard.callback/check-nfc-enabled-success + (fn [{:keys [db]} [nfc-enabled?]] + {:db (assoc-in db [:keycard :nfc-enabled?] nfc-enabled?)})) + +(rf/reg-event-fx :keycard.ios.callback/on-nfc-user-cancelled + (fn [{:keys [db]}] + (log/debug "[keycard] nfc user cancelled") + {:db (-> db + (assoc-in [:keycard :pin :status] nil)) + :fx [(when-let [on-nfc-cancelled-event-vector (get-in db [:keycard :on-nfc-cancelled-event-vector])] + [:dispatch on-nfc-cancelled-event-vector])]})) + +(rf/reg-event-fx :keycard.callback/on-card-connected + (fn [{:keys [db]} _] + (log/debug "[keycard] card globally connected") + {:db (assoc-in db [:keycard :card-connected?] true) + :fx [(when-let [event (get-in db [:keycard :on-card-connected-event-vector])] + [:dispatch event])]})) + +(rf/reg-event-fx :keycard.callback/on-card-disconnected + (fn [{:keys [db]} _] + (log/debug "[keycard] card disconnected") + {:db (assoc-in db [:keycard :card-connected?] false) + :fx [(when-let [event (get-in db [:keycard :on-card-disconnected-event-vector])] + [:dispatch event])]})) + +(rf/reg-event-fx :keycard.ios/start-nfc + (fn [_] + {:effects.keycard.ios/start-nfc nil})) + +(rf/reg-event-fx :keycard.ios.callback/on-nfc-timeout + (fn [{:keys [db]} _] + (log/debug "[keycard] nfc timeout") + {:db (-> db + (assoc-in [:keycard :nfc-running?] false) + (assoc-in [:keycard :card-connected?] false)) + :dispatch-later [{:ms 500 :dispatch [:keycard.ios/start-nfc]}]})) + +(rf/reg-event-fx :keycard/get-application-info + (fn [_ [{:keys [on-success on-failure]}]] + (log/debug "[keycard] get-application-info") + {:effects.keycard/get-application-info {:on-success on-success + :on-failure on-failure}})) + +(rf/reg-event-fx :keycard.callback/on-retrieve-pairings-success + (fn [{:keys [db]} [pairings]] + {:db (assoc-in db [:keycard :pairings] pairings) + :effects.keycard/set-pairing-to-keycard pairings})) + +(rf/reg-event-fx :keycard.ios.callback/start-nfc-success + (fn [{:keys [db]} [{:keys [on-cancel-event-vector]}]] + (log/debug "[keycard] nfc started success") + {:db (-> db + (assoc-in [:keycard :nfc-running?] true) + (assoc-in [:keycard :on-nfc-cancelled-event-vector] on-cancel-event-vector))})) diff --git a/src/status_im/contexts/keycard/login/events.cljs b/src/status_im/contexts/keycard/login/events.cljs new file mode 100644 index 0000000000..99e8a2ccd7 --- /dev/null +++ b/src/status_im/contexts/keycard/login/events.cljs @@ -0,0 +1,74 @@ +(ns status-im.contexts.keycard.login.events + (:require [re-frame.core :as rf] + [status-im.contexts.keycard.utils :as keycard.utils] + [taoensso.timbre :as log] + [utils.transforms :as transforms])) + +(rf/reg-event-fx :keycard.login/on-get-keys-error + (fn [{:keys [db]} [error]] + (log/debug "[keycard] get keys error: " error) + (let [tag-was-lost? (keycard.utils/tag-lost? (:error error)) + pin-retries-count (keycard.utils/pin-retries (:error error))] + (if tag-was-lost? + {:db (assoc-in db [:keycard :pin :status] nil)} + (if (nil? pin-retries-count) + {:effects.utils/show-popup {:title "wrong-keycard"}} + {:db (-> db + (assoc-in [:keycard :application-info :pin-retry-counter] pin-retries-count) + (update-in [:keycard :pin] assoc :status :error)) + :fx [[:dispatch [:keycard/hide-connection-sheet]] + (when (zero? pin-retries-count) + [:effects.utils/show-popup {:title "frozen-keycard"}])]}))))) + +(rf/reg-event-fx :keycard.login/on-get-keys-success + (fn [{:keys [db]} [data]] + (let [{:keys [key-uid encryption-public-key + whisper-private-key]} (transforms/js->clj data) + key-uid (str "0x" key-uid) + profile (get-in db [:profile/profiles-overview key-uid]) + profile-json (transforms/clj->json {:name (:name profile) + :key-uid key-uid})] + {:db + (-> db + (dissoc :keycard) + (update :profile/login assoc + :password encryption-public-key + :key-uid key-uid + :name (:name profile))) + :fx [[:dispatch [:keycard/hide-connection-sheet]] + [:effects.keycard/login-with-keycard + {:multiaccount-data profile-json + :password encryption-public-key + :chat-key whisper-private-key + :key-uid key-uid}]]}))) + +(rf/reg-event-fx :keycard.login/on-get-application-info-success + (fn [{:keys [db]} [application-info]] + (let [profile (get-in db [:profile/profiles-overview (get-in db [:profile/login :key-uid])]) + pin (get-in db [:keycard :pin :text]) + error (keycard.utils/validate-application-info profile application-info)] + (if error + {:effects.utils/show-popup {:title (str error)}} + {:db (-> db + (assoc-in [:keycard :application-info] application-info) + (assoc-in [:keycard :pin :status] :verifying)) + :effects.keycard/get-keys {:pin pin + :on-success #(rf/dispatch [:keycard.login/on-get-keys-success %]) + :on-failure #(rf/dispatch [:keycard.login/on-get-keys-error %])}})))) + +(rf/reg-event-fx :keycard.login/cancel-reading-card + (fn [{:keys [db]}] + {:db (assoc-in db [:keycard :on-card-connected-event-vector] nil)})) + +(rf/reg-event-fx :keycard/read-card-and-login + (fn [{:keys [db]}] + (let [connected? (get-in db [:keycard :card-connected?]) + event-vector [:keycard/get-application-info + {:on-success #(rf/dispatch [:keycard.login/on-get-application-info-success %])}]] + (log/debug "[keycard] proceed-to-login") + {:db (assoc-in db [:keycard :on-card-connected-event-vector] event-vector) + :fx [[:dispatch + [:keycard/show-connection-sheet + {:on-cancel-event-vector [:keycard.login/cancel-reading-card]}]] + (when connected? + [:dispatch event-vector])]}))) diff --git a/src/status_im/contexts/keycard/pin/events.cljs b/src/status_im/contexts/keycard/pin/events.cljs new file mode 100644 index 0000000000..eeba5ae4cf --- /dev/null +++ b/src/status_im/contexts/keycard/pin/events.cljs @@ -0,0 +1,21 @@ +(ns status-im.contexts.keycard.pin.events + (:require [utils.re-frame :as rf])) + +(rf/reg-event-fx :keycard.pin/delete-pressed + (fn [{:keys [db]}] + (let [pin (get-in db [:keycard :pin :text])] + (when (and pin (pos? (count pin))) + {:db (-> db + (assoc-in [:keycard :pin :text] (.slice pin 0 -1)) + (assoc-in [:keycard :pin :status] nil))})))) + +(rf/reg-event-fx :keycard.pin/number-pressed + (fn [{:keys [db]} [number max-numbers on-complete-event]] + (let [pin (get-in db [:keycard :pin :text]) + new-pin (str pin number)] + (when (<= (count new-pin) max-numbers) + {:db (-> db + (assoc-in [:keycard :pin :text] new-pin) + (assoc-in [:keycard :pin :status] nil)) + :fx [(when (= (dec max-numbers) (count pin)) + [:dispatch [on-complete-event]])]})))) diff --git a/src/status_im/contexts/keycard/pin/view.cljs b/src/status_im/contexts/keycard/pin/view.cljs new file mode 100644 index 0000000000..f983d9f4bd --- /dev/null +++ b/src/status_im/contexts/keycard/pin/view.cljs @@ -0,0 +1,25 @@ +(ns status-im.contexts.keycard.pin.view + (:require [quo.core :as quo] + [react-native.core :as rn] + [utils.i18n :as i18n] + [utils.re-frame :as rf])) + +(defn login + [] + (let [{:keys [text status]} (rf/sub [:keycard/pin]) + pin-retry-counter (rf/sub [:keycard/pin-retry-counter]) + error? (= status :error)] + [:<> + [rn/view {:flex 1 :justify-content :center :align-items :center} + [quo/pin-input + {:blur? false + :number-of-pins 6 + :number-of-filled-pins (count text) + :error? error? + :info (when error? + (i18n/label :t/pin-retries-left {:number pin-retry-counter}))}]] + [quo/numbered-keyboard + {:delete-key? true + :on-delete #(rf/dispatch [:keycard.pin/delete-pressed]) + :on-press #(rf/dispatch [:keycard.pin/number-pressed % 6 + :keycard/read-card-and-login])}]])) diff --git a/src/status_im/contexts/keycard/sheet/events.cljs b/src/status_im/contexts/keycard/sheet/events.cljs new file mode 100644 index 0000000000..f89ca462b8 --- /dev/null +++ b/src/status_im/contexts/keycard/sheet/events.cljs @@ -0,0 +1,36 @@ +(ns status-im.contexts.keycard.sheet.events + (:require [re-frame.core :as rf] + [react-native.platform :as platform] + [status-im.contexts.keycard.sheet.view :as keycard.sheet] + [taoensso.timbre :as log])) + +(rf/reg-event-fx :keycard/show-connection-sheet-component + (fn [_ [{:keys [on-cancel-event-vector]}]] + {:dismiss-keyboard true + :dispatch [:show-bottom-sheet + {:hide-on-background-press? false + :on-close #(rf/dispatch on-cancel-event-vector) + :content (fn [] + [keycard.sheet/connect-keycard])}]})) + +(rf/reg-event-fx :keycard/show-connection-sheet + (fn [{:keys [db]} [args]] + (let [nfc-running? (or platform/android? (get-in db [:keycard :nfc-running?]))] + (log/debug "show connection; already running?" nfc-running?) + (if nfc-running? + {:dispatch [:keycard/show-connection-sheet-component args]} + {:effects.keycard.ios/start-nfc + {:on-success + (fn [] + (log/debug "nfc started successfully. next: show-connection-sheet") + (rf/dispatch [:keycard.ios.callback/start-nfc-success args]) + (rf/dispatch [:keycard/show-connection-sheet-component args])) + :on-failure + (fn [] + (log/debug "nfc failed star starting. not calling show-connection-sheet"))}})))) + +(rf/reg-event-fx :keycard/hide-connection-sheet + (fn [_] + (if platform/android? + {:dispatch [:hide-bottom-sheet]} + {:effects.keycard.ios/stop-nfc nil}))) diff --git a/src/status_im/contexts/keycard/sheet/view.cljs b/src/status_im/contexts/keycard/sheet/view.cljs new file mode 100644 index 0000000000..79b1a3043d --- /dev/null +++ b/src/status_im/contexts/keycard/sheet/view.cljs @@ -0,0 +1,24 @@ +(ns status-im.contexts.keycard.sheet.view + (:require [react-native.core :as rn] + [status-im.common.resources :as resources] + [utils.re-frame :as rf])) + +(defn connect-keycard + [] + (let [connected? (rf/sub [:keycard/connected?])] + [rn/view {:style {:align-items :center :padding-horizontal 36}} + [rn/text {:style {:font-size 26 :color "#9F9FA5" :margin-bottom 36 :margin-top 30}} + "Ready to Scan"] + [rn/image + {:source (resources/get-image :nfc-prompt)}] + [rn/text {:style {:font-size 16 :color :white :margin-vertical 36}} + (if connected? + "Connected. Don’t move your card." + "Hold your phone near a Status Keycard")] + [rn/pressable + {:on-press #(rf/dispatch [:hide-bottom-sheet]) + :style {:flex-direction :row :flex 1}} + [rn/view + {:style {:background-color "#8E8E93" :flex 1 :align-items :center :padding 18 :border-radius 10}} + [rn/text {:style {:color :white :font-size 16}} + "Cancel"]]]])) diff --git a/src/status_im/contexts/keycard/utils.cljs b/src/status_im/contexts/keycard/utils.cljs new file mode 100644 index 0000000000..968c2e8807 --- /dev/null +++ b/src/status_im/contexts/keycard/utils.cljs @@ -0,0 +1,45 @@ +(ns status-im.contexts.keycard.utils + (:require [taoensso.timbre :as log])) + +(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))))) + +(defn tag-lost? + [error] + (or + (= error "Tag was lost.") + (= error "NFCError:100") + (re-matches #".*NFCError:100.*" error))) + +(defn validate-application-info + [profile {:keys [key-uid paired? pin-retry-counter puk-retry-counter] :as application-info}] + (let [profile-mismatch? (or (nil? profile) (not= (:key-uid profile) key-uid))] + (log/debug "[keycard] login-with-keycard" + "empty application info" (empty? application-info) + "no key-uid" (empty? key-uid) + "profile-mismatch?" profile-mismatch? + "no pairing" paired?) + (cond + (empty? application-info) + :not-keycard + + (empty? (:key-uid application-info)) + :keycard-blank + + profile-mismatch? + :keycard-wrong + + (not paired?) + :keycard-unpaired + + (and (zero? pin-retry-counter) + (or (nil? puk-retry-counter) + (pos? puk-retry-counter))) + nil + + :else + nil))) diff --git a/src/status_im/contexts/preview/quo/main.cljs b/src/status_im/contexts/preview/quo/main.cljs index b8db3dc7e5..1875c47d8e 100644 --- a/src/status_im/contexts/preview/quo/main.cljs +++ b/src/status_im/contexts/preview/quo/main.cljs @@ -144,6 +144,7 @@ small-option-card] [status-im.contexts.preview.quo.password.password-tips :as password-tips] [status-im.contexts.preview.quo.password.tips :as tips] + [status-im.contexts.preview.quo.pin-input.pin-input :as pin-input] [status-im.contexts.preview.quo.profile.collectible :as collectible] [status-im.contexts.preview.quo.profile.collectible-list-item :as collectible-list-item] [status-im.contexts.preview.quo.profile.expanded-collectible :as expanded-collectible] @@ -369,6 +370,8 @@ :component keyboard-key/view} {:name :numbered-keyboard :component numbered-keyboard/view}] + :pin-input [{:name :pin-input + :component pin-input/view}] :links [{:name :internal-link-card :options {:insets {:top true}} :component internal-link-card/view} diff --git a/src/status_im/contexts/preview/quo/pin_input/pin_input.cljs b/src/status_im/contexts/preview/quo/pin_input/pin_input.cljs new file mode 100644 index 0000000000..a5128afc6b --- /dev/null +++ b/src/status_im/contexts/preview/quo/pin_input/pin_input.cljs @@ -0,0 +1,29 @@ +(ns status-im.contexts.preview.quo.pin-input.pin-input + (:require [quo.core :as quo] + [react-native.core :as rn] + [reagent.core :as reagent] + [status-im.contexts.preview.quo.preview :as preview])) + + +(def descriptor + [{:key :blur? :type :boolean} + {:type :number + :key :number-of-pins} + {:type :number + :key :number-of-filled-pins} + {:type :boolean + :key :error?} + {:type :text + :key :info}]) + +(defn view + [] + (let [state (reagent/atom {:blur? false + :number-of-pins 6 + :number-of-filled-pins 0})] + (fn [] + [preview/preview-container + {:state state + :descriptor descriptor} + [rn/view {:style {:padding-vertical 40 :align-items :center :justify-content :center}} + [quo/pin-input @state]]]))) diff --git a/src/status_im/contexts/profile/profiles/view.cljs b/src/status_im/contexts/profile/profiles/view.cljs index 73371204d1..9cf3917590 100644 --- a/src/status_im/contexts/profile/profiles/view.cljs +++ b/src/status_im/contexts/profile/profiles/view.cljs @@ -9,6 +9,7 @@ [status-im.common.standard-authentication.core :as standard-authentication] [status-im.config :as config] [status-im.constants :as constants] + [status-im.contexts.keycard.pin.view :as keycard.pin] [status-im.contexts.onboarding.common.background.view :as background] [status-im.contexts.profile.profiles.style :as style] [taoensso.timbre :as log] @@ -130,7 +131,7 @@ [:profile/profile-selected key-uid]) (rf/dispatch [:profile.login/login-with-biometric-if-available key-uid]) - (when-not keycard-pairing (set-hide-profiles)))}])) + (set-hide-profiles))}])) (defn- profiles-section [{:keys [hide-profiles]}] @@ -179,8 +180,8 @@ [:profile.login/biometric-success]) :on-fail #(rf/dispatch [:profile.login/biometric-auth-fail - %])}])) - ] + %])}]))] + [standard-authentication/password-input {:shell? true :blur? true @@ -189,7 +190,7 @@ (defn login-section [{:keys [show-profiles]}] (let [processing (rf/sub [:profile/login-processing]) - {:keys [key-uid name + {:keys [key-uid name keycard-pairing customization-color]} (rf/sub [:profile/login-profile]) sign-in-enabled? (rf/sub [:sign-in-enabled?]) profile-picture (rf/sub [:profile/login-profiles-picture key-uid]) @@ -218,7 +219,7 @@ :disabled? processing :accessibility-label :show-profiles} :i/multi-profile]] - [rn/scroll-view + [(if keycard-pairing rn/view rn/scroll-view) {:keyboard-should-persist-taps :always :style {:flex 1}} [quo/profile-card @@ -226,17 +227,20 @@ :customization-color (or customization-color :primary) :profile-picture profile-picture :card-style style/login-profile-card}] - [password-input]] - [quo/button - {:size 40 - :type :primary - :customization-color (or customization-color :primary) - :accessibility-label :login-button - :icon-left :i/unlocked - :disabled? (or (not sign-in-enabled?) processing) - :on-press login-multiaccount - :container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}} - (i18n/label :t/log-in)]])) + (if keycard-pairing + [keycard.pin/login] + [password-input])] + (when-not keycard-pairing + [quo/button + {:size 40 + :type :primary + :customization-color (or customization-color :primary) + :accessibility-label :login-button + :icon-left :i/unlocked + :disabled? (or (not sign-in-enabled?) processing) + :on-press login-multiaccount + :container-style {:margin-bottom (+ (safe-area/get-bottom) 12)}} + (i18n/label :t/log-in)])])) (defn view [] diff --git a/src/status_im/db.cljs b/src/status_im/db.cljs index 3c9b06d44b..14ade26945 100644 --- a/src/status_im/db.cljs +++ b/src/status_im/db.cljs @@ -41,10 +41,5 @@ :visibility-status-updates {} :stickers/packs-pending #{} :settings/change-password {} - :keycard {:nfc-enabled? false - :pin {:original [] - :confirmation [] - :current [] - :puk [] - :enter-step :original}} + :keycard {} :theme :light}) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index 1140b2f44f..88ba4dd30f 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -24,6 +24,8 @@ status-im.contexts.communities.overview.events status-im.contexts.communities.sharing.events status-im.contexts.contact.blocking.events + status-im.contexts.keycard.effects + status-im.contexts.keycard.events status-im.contexts.onboarding.common.overlay.events status-im.contexts.onboarding.events status-im.contexts.profile.events @@ -56,6 +58,9 @@ :theme/init-theme nil :network/listen-to-network-info nil :effects.biometric/get-supported-type nil + :effects.keycard/register-card-events nil + :effects.keycard/check-nfc-enabled nil + :effects.keycard/retrieve-pairings nil ;;app starting flow continues in get-profiles-overview :profile/get-profiles-overview #(rf/dispatch [:profile/get-profiles-overview-success %]) :effects.font/get-font-file-for-initials-avatar diff --git a/src/status_im/subs/keycard.cljs b/src/status_im/subs/keycard.cljs new file mode 100644 index 0000000000..e412ea303f --- /dev/null +++ b/src/status_im/subs/keycard.cljs @@ -0,0 +1,25 @@ +(ns status-im.subs.keycard + (:require [re-frame.core :as re-frame])) + +;; KEYCARD + +(re-frame/reg-sub + :keycard/nfc-enabled? + (fn [db] + (get-in db [:keycard :nfc-enabled?]))) + +(re-frame/reg-sub + :keycard/connected? + (fn [db] + (get-in db [:keycard :card-connected?]))) + +(re-frame/reg-sub + :keycard/pin + (fn [db] + (get-in db [:keycard :pin]))) + +(re-frame/reg-sub + :keycard/pin-retry-counter + (fn [db] + (get-in db [:keycard :application-info :pin-retry-counter]))) + diff --git a/src/status_im/subs/root.cljs b/src/status_im/subs/root.cljs index ee04ebfeda..4f1287f8de 100644 --- a/src/status_im/subs/root.cljs +++ b/src/status_im/subs/root.cljs @@ -9,6 +9,7 @@ status-im.subs.community.account-selection status-im.subs.contact status-im.subs.general + status-im.subs.keycard status-im.subs.messages status-im.subs.onboarding status-im.subs.pairing