mirror of
https://github.com/status-im/status-mobile.git
synced 2025-01-13 18:25:45 +00:00
[BUG #2025] Ensure QR code data is properly parsed
This commit is contained in:
parent
0a94d10559
commit
3b6b5e2da1
@ -27,3 +27,7 @@
|
||||
|
||||
(defn camera [props]
|
||||
(r/create-element default-camera (clj->js (merge {:inverted true} props))))
|
||||
|
||||
(defn get-qr-code-data [code]
|
||||
(when (= "QR_CODE" (.-type code))
|
||||
(.-data code)))
|
@ -136,12 +136,18 @@
|
||||
:delimiter delimiter
|
||||
:separator separator})))))
|
||||
|
||||
(def default-option-value "<no value>")
|
||||
|
||||
(defn label-options [options]
|
||||
;; i18n ignores nil value, leading to misleading messages
|
||||
(into {} (for [[k v] options] [k (or v default-option-value)])))
|
||||
|
||||
(defn label
|
||||
([path] (label path {}))
|
||||
([path options]
|
||||
(if (exists? rn-dependencies/i18n.t)
|
||||
(let [options (update options :amount label-number)]
|
||||
(.t rn-dependencies/i18n (name path) (clj->js options)))
|
||||
(.t rn-dependencies/i18n (name path) (clj->js (label-options options))))
|
||||
(name path))))
|
||||
|
||||
(defn label-pluralize [count path & options]
|
||||
|
@ -413,7 +413,7 @@
|
||||
:wallet-choose-recipient "Choose Recipient"
|
||||
:wallet-choose-from-contacts "Choose From Contacts"
|
||||
:wallet-address-from-clipboard "Use Address From Clipboard"
|
||||
:wallet-invalid-address "Address is invalid"
|
||||
:wallet-invalid-address "Invalid address: \n {{data}}"
|
||||
:wallet-browse-photos "Browse Photos"
|
||||
:validation-amount-invalid "Amount is not valid"
|
||||
:validation-amount-invalid-number "Amount is not a valid number"
|
||||
|
@ -6,7 +6,8 @@
|
||||
[status-im.components.icons.vector-icons :as vi]
|
||||
[status-im.components.status-bar :refer [status-bar]]
|
||||
[status-im.i18n :refer [label]]
|
||||
[status-im.ui.screens.profile.qr-code.styles :as styles])
|
||||
[status-im.ui.screens.profile.qr-code.styles :as styles]
|
||||
[status-im.utils.eip.eip67 :as eip67])
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]]))
|
||||
|
||||
(defview qr-code-view []
|
||||
@ -34,10 +35,7 @@
|
||||
:height (.-height layout)}]))}
|
||||
(when (:width dimensions)
|
||||
[react/view {:style (styles/qr-code-container dimensions)}
|
||||
[qr-code {:value (if amount?
|
||||
(prn-str {:address (get contact qr-source)
|
||||
:amount amount})
|
||||
(str "ethereum:" (get contact qr-source)))
|
||||
[qr-code {:value (eip67/generate-uri (get contact qr-source) (when amount? {:value amount}))
|
||||
:size (- (min (:width dimensions)
|
||||
(:height dimensions))
|
||||
80)}]])]
|
||||
|
@ -1,10 +1,11 @@
|
||||
(ns status-im.ui.screens.qr-scanner.events
|
||||
(:require [re-frame.core :refer [after dispatch debug enrich]]
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.components.camera :as camera]
|
||||
[status-im.ui.screens.navigation :as nav]
|
||||
[status-im.utils.handlers :as u :refer [register-handler]]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.i18n :as i18n]))
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip67 :as eip67]))
|
||||
|
||||
(defmethod nav/preload-data! :qr-scanner
|
||||
[db [_ _ identifier]]
|
||||
@ -15,16 +16,16 @@
|
||||
|
||||
(defn navigate-to-scanner
|
||||
[_ [_ identifier]]
|
||||
(dispatch [:request-permissions
|
||||
[:camera]
|
||||
(fn []
|
||||
(camera/request-access
|
||||
#(if % (dispatch [:navigate-to :qr-scanner identifier])
|
||||
(utils/show-popup (i18n/label :t/error)
|
||||
(i18n/label :t/camera-access-error)))))]))
|
||||
(re-frame/dispatch [:request-permissions
|
||||
[:camera]
|
||||
(fn []
|
||||
(camera/request-access
|
||||
#(if % (re-frame/dispatch [:navigate-to :qr-scanner identifier])
|
||||
(utils/show-popup (i18n/label :t/error)
|
||||
(i18n/label :t/camera-access-error)))))]))
|
||||
|
||||
(register-handler :scan-qr-code
|
||||
(after navigate-to-scanner)
|
||||
(re-frame/after navigate-to-scanner)
|
||||
set-current-identifier)
|
||||
|
||||
(register-handler :clear-qr-code
|
||||
@ -34,7 +35,7 @@
|
||||
(defn handle-qr-request
|
||||
[db [_ context data]]
|
||||
(when-let [handler (get-in db [:qr-codes context])]
|
||||
(dispatch [handler context data])))
|
||||
(re-frame/dispatch [handler context (:address (eip67/parse-uri data))])))
|
||||
|
||||
(defn clear-qr-request [db [_ context]]
|
||||
(-> db
|
||||
@ -44,7 +45,7 @@
|
||||
(defn navigate-back!
|
||||
[{:keys [view-id]} _]
|
||||
(when (= :qr-scanner view-id)
|
||||
(dispatch [:navigate-back])))
|
||||
(re-frame/dispatch [:navigate-back])))
|
||||
|
||||
(register-handler :set-qr-code
|
||||
(u/handlers->
|
||||
|
@ -1,50 +1,42 @@
|
||||
(ns status-im.ui.screens.qr-scanner.views
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :refer [subscribe dispatch dispatch-sync]]
|
||||
[status-im.components.react :refer [view
|
||||
image]]
|
||||
[status-im.components.camera :refer [camera]]
|
||||
[status-im.components.styles :refer [icon-search
|
||||
icon-back]]
|
||||
[status-im.components.status-bar :refer [status-bar]]
|
||||
[status-im.components.toolbar.view :refer [toolbar]]
|
||||
[status-im.components.toolbar.actions :as act]
|
||||
[status-im.components.toolbar.styles :refer [toolbar-background1]]
|
||||
[status-im.ui.screens.qr-scanner.styles :as st]
|
||||
[status-im.utils.types :refer [json->clj]]
|
||||
[clojure.string :as str]
|
||||
[reagent.core :as r]))
|
||||
(:require [reagent.core :as reagent]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.components.react :as react]
|
||||
[status-im.components.camera :as camera]
|
||||
[status-im.components.status-bar :as status-bar]
|
||||
[status-im.components.toolbar.view :as toolbar]
|
||||
[status-im.components.toolbar.actions :as action]
|
||||
[status-im.components.toolbar.styles :as toolbar.styles]
|
||||
[status-im.ui.screens.qr-scanner.styles :as styles]))
|
||||
|
||||
(defview qr-scanner-toolbar [title hide-nav?]
|
||||
(letsubs [modal [:get :modal]]
|
||||
[view
|
||||
[status-bar]
|
||||
[toolbar {:title title
|
||||
:background-color toolbar-background1
|
||||
:hide-nav? hide-nav?
|
||||
:nav-action (when modal
|
||||
(act/back #(dispatch [:navigate-back])))}]]))
|
||||
[react/view
|
||||
[status-bar/status-bar]
|
||||
[toolbar/toolbar {:title title
|
||||
:background-color toolbar.styles/toolbar-background1
|
||||
:hide-nav? hide-nav?
|
||||
:nav-action (when modal
|
||||
(action/back #(re-frame/dispatch [:navigate-back])))}]]))
|
||||
|
||||
(defview qr-scanner []
|
||||
(letsubs [identifier [:get :current-qr-context]
|
||||
camera-initialized? (r/atom false)]
|
||||
[view st/barcode-scanner-container
|
||||
camera-initialized? (reagent/atom false)]
|
||||
[react/view styles/barcode-scanner-container
|
||||
[qr-scanner-toolbar (:toolbar-title identifier) (not @camera-initialized?)]
|
||||
[camera {:onBarCodeRead (fn [code]
|
||||
(let [data (-> (.-data code)
|
||||
(str/replace #"ethereum:" ""))]
|
||||
(dispatch [:set-qr-code identifier data])))
|
||||
;:barCodeTypes [:qr]
|
||||
:ref #(reset! camera-initialized? true)
|
||||
:captureAudio false
|
||||
:style st/barcode-scanner}]
|
||||
[view st/rectangle-container
|
||||
[view st/rectangle
|
||||
[image {:source {:uri :corner_left_top}
|
||||
:style st/corner-left-top}]
|
||||
[image {:source {:uri :corner_right_top}
|
||||
:style st/corner-right-top}]
|
||||
[image {:source {:uri :corner_right_bottom}
|
||||
:style st/corner-right-bottom}]
|
||||
[image {:source {:uri :corner_left_bottom}
|
||||
:style st/corner-left-bottom}]]]]))
|
||||
[camera/camera {:onBarCodeRead #(re-frame/dispatch [:set-qr-code identifier (camera/get-qr-code-data %)])
|
||||
;:barCodeTypes [:qr]
|
||||
:ref #(reset! camera-initialized? true)
|
||||
:captureAudio false
|
||||
:style styles/barcode-scanner}]
|
||||
[react/view styles/rectangle-container
|
||||
[react/view styles/rectangle
|
||||
[react/image {:source {:uri :corner_left_top}
|
||||
:style styles/corner-left-top}]
|
||||
[react/image {:source {:uri :corner_right_top}
|
||||
:style styles/corner-right-top}]
|
||||
[react/image {:source {:uri :corner_right_bottom}
|
||||
:style styles/corner-right-bottom}]
|
||||
[react/image {:source {:uri :corner_left_bottom}
|
||||
:style styles/corner-left-bottom}]]]]))
|
||||
|
@ -1,5 +1,6 @@
|
||||
(ns status-im.ui.screens.wallet.choose-recipient.events
|
||||
(:require [status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip67 :as eip67]
|
||||
[status-im.utils.handlers :as handlers]))
|
||||
|
||||
(handlers/register-handler-db
|
||||
@ -14,13 +15,14 @@
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:choose-recipient
|
||||
(fn [{{:keys [web3] :as db} :db} [_ address name]]
|
||||
(fn [{{:keys [web3] :as db} :db} [_ data name]]
|
||||
(let [{:keys [view-id]} db
|
||||
address (:address (eip67/parse-uri data))
|
||||
valid-address? (.isAddress web3 address)]
|
||||
(cond-> {:db db}
|
||||
(= :choose-recipient view-id) (assoc :dispatch [:navigate-back])
|
||||
valid-address? (update :db #(choose-address-and-name % address name))
|
||||
(not valid-address?) (assoc :show-error (i18n/label :t/wallet-invalid-address))))))
|
||||
(not valid-address?) (assoc :show-error (i18n/label :t/wallet-invalid-address {:data data}))))))
|
||||
|
||||
(handlers/register-handler-fx
|
||||
:wallet-open-send-transaction
|
||||
|
@ -1,21 +1,16 @@
|
||||
(ns status-im.ui.screens.wallet.choose-recipient.views
|
||||
(:require-macros [status-im.utils.views :refer [defview letsubs]])
|
||||
(:require [re-frame.core :as re-frame]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.components.camera :as camera]
|
||||
[status-im.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.components.react :as react]
|
||||
[status-im.components.status-bar :as status-bar]
|
||||
[status-im.components.toolbar-new.view :as toolbar]
|
||||
[status-im.components.toolbar-new.actions :as act]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.ui.screens.wallet.styles :as wallet.styles]
|
||||
[status-im.components.react :as react]
|
||||
[status-im.components.icons.vector-icons :as vector-icons]
|
||||
[status-im.ui.screens.wallet.choose-recipient.styles :as styles]
|
||||
[status-im.utils.platform :as platform]
|
||||
[status-im.components.status-bar :as status-bar]
|
||||
[status-im.components.camera :as camera]
|
||||
[clojure.string :as string]))
|
||||
|
||||
(defn- show-not-implemented! []
|
||||
(utils/show-popup "TODO" "Not implemented yet!"))
|
||||
[status-im.ui.screens.wallet.styles :as wallet.styles]))
|
||||
|
||||
(defn choose-from-contacts []
|
||||
(re-frame/dispatch [:navigate-to-modal
|
||||
@ -88,15 +83,10 @@
|
||||
{:width (.-width layout)
|
||||
:height (.-height layout)}]))}
|
||||
(when (or platform/android?
|
||||
camera-permitted?)
|
||||
[camera/camera {:style styles/preview
|
||||
:aspect :fill
|
||||
:captureAudio false
|
||||
:torchMode (camera/set-torch camera-flashlight)
|
||||
:onBarCodeRead (fn [code]
|
||||
(let [data (-> code
|
||||
.-data
|
||||
(string/replace #"ethereum:" ""))]
|
||||
(re-frame/dispatch [:choose-recipient data nil])))}])
|
||||
camera-permitted?)[camera/camera {:style styles/preview
|
||||
:aspect :fill
|
||||
:captureAudio false
|
||||
:torchMode (camera/set-torch camera-flashlight)
|
||||
:onBarCodeRead #(re-frame/dispatch [:choose-recipient (camera/get-qr-code-data %) nil])}])
|
||||
[viewfinder camera-dimensions]]
|
||||
[recipient-buttons]]))
|
||||
|
@ -13,6 +13,7 @@
|
||||
[status-im.ui.screens.wallet.request.styles :as styles]
|
||||
[status-im.components.styles :as components.styles]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.utils.eip.eip67 :as eip67]
|
||||
[status-im.utils.platform :as platform]))
|
||||
|
||||
(defn toolbar-view []
|
||||
@ -30,8 +31,7 @@
|
||||
(views/defview qr-code []
|
||||
(views/letsubs [account [:get-current-account]]
|
||||
[components.qr-code/qr-code
|
||||
{:value (.stringify js/JSON (clj->js {:address (:address account)
|
||||
:amount 0}))
|
||||
{:value (eip67/generate-uri (:address account))
|
||||
:size 256}]))
|
||||
|
||||
(views/defview request-transaction []
|
||||
|
@ -1,13 +1,13 @@
|
||||
(ns status-im.ui.screens.wallet.send.events
|
||||
(:require [re-frame.core :as re-frame]
|
||||
(:require [clojure.string :as string]
|
||||
[re-frame.core :as re-frame]
|
||||
[status-im.i18n :as i18n]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.handlers :as handlers]
|
||||
[status-im.ui.screens.wallet.db :as wallet.db]
|
||||
[status-im.native-module.core :as status]
|
||||
[status-im.utils.types :as types]
|
||||
[clojure.string :as string]
|
||||
[status-im.utils.money :as money]
|
||||
[status-im.utils.utils :as utils]
|
||||
[status-im.i18n :as i18n]))
|
||||
[status-im.utils.utils :as utils]))
|
||||
|
||||
;;;; FX
|
||||
|
||||
|
40
src/status_im/utils/eip/eip67.cljs
Normal file
40
src/status_im/utils/eip/eip67.cljs
Normal file
@ -0,0 +1,40 @@
|
||||
(ns status-im.utils.eip.eip67
|
||||
"Utility function related to [EIP67](https://github.com/ethereum/EIPs/issues/67)"
|
||||
(:require [clojure.string :as string]))
|
||||
|
||||
(def scheme "ethereum")
|
||||
(def scheme-separator ":")
|
||||
(def parameters-separator "?")
|
||||
(def parameter-separator "&")
|
||||
(def key-value-separator "=")
|
||||
|
||||
(def key-value-format (str "([^" parameter-separator key-value-separator "]+)"))
|
||||
(def parameters-pattern (re-pattern (str key-value-format key-value-separator key-value-format)))
|
||||
|
||||
(defn- parse-parameters [s]
|
||||
(when s
|
||||
(into {} (for [[_ k v] (re-seq parameters-pattern s)]
|
||||
[(keyword k) v]))))
|
||||
|
||||
(defn parse-uri
|
||||
"Parse a EIP 67 URI as a map of keyword / strings. Parsed map will contain at least the key `address`.
|
||||
Invalid URI will be parsed as `nil`."
|
||||
[s]
|
||||
(when (and s (string/starts-with? s scheme))
|
||||
(let [[address parameters] (string/split (string/replace s (str scheme scheme-separator) "") parameters-separator)]
|
||||
(when-not (zero? (count address))
|
||||
(merge
|
||||
{:address address}
|
||||
(parse-parameters parameters))))))
|
||||
|
||||
(defn- generate-parameter-string [m]
|
||||
(string/join parameter-separator (for [[k v] m]
|
||||
(str (name k) key-value-separator v))))
|
||||
|
||||
(defn generate-uri
|
||||
"Generate a EIP 67 URI based on `address` and an optional map of extra properties.
|
||||
No validation of address format is performed."
|
||||
([address] (generate-uri address nil))
|
||||
([address m]
|
||||
(when address
|
||||
(str scheme scheme-separator address (when m (str parameters-separator (generate-parameter-string m)))))))
|
@ -1,3 +0,0 @@
|
||||
(ns status-im.test.handlers
|
||||
(:require [cljs.test :refer-macros [deftest is]]
|
||||
[status-im.ui.screens.events :as events]))
|
6
test/cljs/status_im/test/i18n.cljs
Normal file
6
test/cljs/status_im/test/i18n.cljs
Normal file
@ -0,0 +1,6 @@
|
||||
(ns status-im.test.i18n
|
||||
(:require [cljs.test :refer-macros [deftest is]]
|
||||
[status-im.i18n :as i18n]))
|
||||
|
||||
(deftest label-options
|
||||
(is (not (nil? (:key (i18n/label-options {:key nil}))))))
|
@ -8,7 +8,7 @@
|
||||
[status-im.test.profile.events]
|
||||
[status-im.test.chat.models.input]
|
||||
[status-im.test.components.main-tabs]
|
||||
[status-im.test.handlers]
|
||||
[status-im.test.i18n]
|
||||
[status-im.test.utils.utils]
|
||||
[status-im.test.utils.money]
|
||||
[status-im.test.utils.clocks]
|
||||
@ -34,7 +34,7 @@
|
||||
'status-im.test.wallet.transactions.subs
|
||||
'status-im.test.chat.models.input
|
||||
'status-im.test.components.main-tabs
|
||||
'status-im.test.handlers
|
||||
'status-im.test.i18n
|
||||
'status-im.test.utils.utils
|
||||
'status-im.test.utils.money
|
||||
'status-im.test.utils.clocks
|
||||
|
18
test/cljs/status_im/test/utils/eip/eip67.cljs
Normal file
18
test/cljs/status_im/test/utils/eip/eip67.cljs
Normal file
@ -0,0 +1,18 @@
|
||||
(ns status-im.test.utils.eip.eip67
|
||||
(:require [cljs.test :refer-macros [deftest is testing]]
|
||||
[status-im.utils.eip.eip67 :as eip67]))
|
||||
|
||||
(deftest parse-uri
|
||||
(is (= nil (eip67/parse-uri nil)))
|
||||
(is (= nil (eip67/parse-uri "random")))
|
||||
(is (= nil (eip67/parse-uri "ethereum:")))
|
||||
(is (= nil (eip67/parse-uri "ethereum:?value=1")))
|
||||
(is (= nil (eip67/parse-uri "bitcoin:0x1234")))
|
||||
(is (= {:address "0x1234"} (eip67/parse-uri "ethereum:0x1234")))
|
||||
(is (= {:address "0x1234" :to "0x5678" :value "1"} (eip67/parse-uri "ethereum:0x1234?to=0x5678&value=1"))))
|
||||
|
||||
(deftest generate-uri
|
||||
(is (= nil (eip67/generate-uri nil)))
|
||||
(is (= "ethereum:0x1234" (eip67/generate-uri "0x1234")))
|
||||
(is (= "ethereum:0x1234?to=0x5678" (eip67/generate-uri "0x1234" {:to "0x5678"})))
|
||||
(is (= "ethereum:0x1234?to=0x5678&value=1" (eip67/generate-uri "0x1234" {:to "0x5678" :value 1}))))
|
Loading…
x
Reference in New Issue
Block a user