[BUG #2025] Ensure QR code data is properly parsed

This commit is contained in:
Julien Eluard 2017-10-06 13:50:57 +02:00 committed by Roman Volosovskyi
parent 0a94d10559
commit 3b6b5e2da1
15 changed files with 148 additions and 94 deletions

View File

@ -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)))

View File

@ -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]

View File

@ -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"

View File

@ -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)}]])]

View File

@ -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->

View File

@ -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}]]]]))

View File

@ -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

View File

@ -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]]))

View File

@ -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 []

View File

@ -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

View 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)))))))

View File

@ -1,3 +0,0 @@
(ns status-im.test.handlers
(:require [cljs.test :refer-macros [deftest is]]
[status-im.ui.screens.events :as events]))

View 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}))))))

View File

@ -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

View 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}))))