From 9058c54933a72cefed18b55543a64cc99b012197 Mon Sep 17 00:00:00 2001 From: Andrea Maria Piana Date: Fri, 1 Jun 2018 12:08:16 +0200 Subject: [PATCH] Allow scanning qr codes for custom bootnodes Signed-off-by: Andrea Maria Piana --- src/status_im/models/bootnode.cljs | 63 +++++++++++ .../edit_bootnode/events.cljs | 50 +++------ .../edit_bootnode/styles.cljs | 24 ++++- .../edit_bootnode/views.cljs | 68 +++++++----- .../edit_mailserver/events.cljs | 4 +- test/cljs/status_im/test/models/bootnode.cljs | 102 ++++++++++++++++++ test/cljs/status_im/test/runner.cljs | 4 +- .../edit_bootnode/events.cljs | 19 ---- 8 files changed, 244 insertions(+), 90 deletions(-) create mode 100644 src/status_im/models/bootnode.cljs create mode 100644 test/cljs/status_im/test/models/bootnode.cljs delete mode 100644 test/cljs/status_im/test/ui/screens/bootnodes_settings/edit_bootnode/events.cljs diff --git a/src/status_im/models/bootnode.cljs b/src/status_im/models/bootnode.cljs new file mode 100644 index 0000000000..2c8bb92318 --- /dev/null +++ b/src/status_im/models/bootnode.cljs @@ -0,0 +1,63 @@ +(ns status-im.models.bootnode + (:require + [clojure.string :as string] + [status-im.ui.screens.accounts.utils :as accounts.utils] + [status-im.utils.handlers-macro :as handlers-macro] + [status-im.utils.ethereum.core :as ethereum])) + +(def address-regex #"enode://[a-zA-Z0-9]+:?(.*)\@\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b:(\d{1,5})") + +(defn valid-address? [address] + (re-matches address-regex address)) + +(defn- build [id bootnode-name address chain] + {:address address + :chain chain + :id (string/replace id "-" "") + :name bootnode-name}) + +(defn fetch [id cofx] + (let [network (get-in cofx [:db :network])] + (get-in cofx [:db :account/account :bootnodes network id]))) + +(defn set-input [input-key value {:keys [db]}] + {:db (update + db + :bootnodes/manage + assoc + input-key + {:value value + :error (case input-key + :id false + :name (string/blank? value) + :url (not (valid-address? value)))})}) + +(defn edit [id {:keys [db] :as cofx}] + (let [{:keys [id + address + name]} (fetch id cofx) + fxs (handlers-macro/merge-fx + cofx + (set-input :id id) + (set-input :url (str address)) + (set-input :name (str name)))] + (assoc fxs :dispatch [:navigate-to :edit-bootnode]))) + +(defn save [{{:bootnodes/keys [manage] :account/keys [account] :as db} :db :as cofx}] + (let [{:keys [name url]} manage + network (:network db) + bootnode (build + (:random-id cofx) + (:value name) + (:value url) + network) + new-bootnodes (assoc-in + (:bootnodes account) + [network (:id bootnode)] + bootnode)] + + (handlers-macro/merge-fx + cofx + {:db (dissoc db :bootnodes/manage) + :dispatch [:navigate-back]} + (accounts.utils/account-update {:bootnodes new-bootnodes})))) diff --git a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/events.cljs b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/events.cljs index d03aec22a1..372a5bb5b7 100644 --- a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/events.cljs +++ b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/events.cljs @@ -2,50 +2,26 @@ (:require [clojure.string :as string] [re-frame.core :as re-frame] [status-im.utils.handlers :refer [register-handler] :as handlers] - [status-im.utils.handlers-macro :as handlers-macro] - [status-im.ui.screens.accounts.utils :as accounts.utils] - [status-im.utils.ethereum.core :as ethereum] - [status-im.utils.types :as types] - [status-im.utils.inbox :as utils.inbox])) - -(defn- new-bootnode [id bootnode-name address chain] - {:address address - :chain chain - :id (string/replace id "-" "") - :name bootnode-name}) - -(defn save-new-bootnode [{{:bootnodes/keys [manage] :account/keys [account] :as db} :db :as cofx} _] - (let [{:keys [name url]} manage - network (:network db) - bootnode (new-bootnode - (:random-id cofx) - (:value name) - (:value url) - network) - new-bootnodes (assoc-in (:bootnodes account) [network (:id bootnode)] bootnode)] - - (handlers-macro/merge-fx cofx - {:db (dissoc db :bootnodes/manage) - :dispatch [:navigate-back]} - (accounts.utils/account-update {:bootnodes new-bootnodes})))) + [status-im.models.bootnode :as models.bootnode])) (handlers/register-handler-fx :save-new-bootnode [(re-frame/inject-cofx :random-id)] - save-new-bootnode) + (fn [cofx _] + (models.bootnode/save cofx))) (handlers/register-handler-fx :bootnode-set-input - (fn [{db :db} [_ input-key value]] - {:db (update db :bootnodes/manage assoc input-key {:value value - :error (if (= input-key :name) - (string/blank? value) - (not (utils.inbox/valid-enode-address? value)))})})) + (fn [cofx [_ input-key value]] + (models.bootnode/set-input input-key value cofx))) (handlers/register-handler-fx :edit-bootnode - (fn [{db :db} _] - {:db (update-in db [:bootnodes/manage] assoc - :name {:error true} - :url {:error true}) - :dispatch [:navigate-to :edit-bootnode]})) + (fn [cofx [_ bootnode-id]] + (models.bootnode/edit bootnode-id cofx))) + +(handlers/register-handler-fx + :set-bootnode-from-qr + (fn [cofx [_ _ url]] + (assoc (models.bootnode/set-input :url url cofx) + :dispatch [:navigate-back]))) diff --git a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/styles.cljs b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/styles.cljs index e5f881dbb0..3a5c8be3cd 100644 --- a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/styles.cljs +++ b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/styles.cljs @@ -1,14 +1,30 @@ (ns status-im.ui.screens.bootnodes-settings.edit-bootnode.styles - (:require-macros [status-im.utils.styles :refer [defstyle]])) + (:require-macros [status-im.utils.styles :refer [defstyle]]) + (:require [status-im.ui.components.styles :as styles] + [status-im.ui.components.colors :as colors])) + +(def input-container + {:flex-direction :row + :align-items :center + :justify-content :space-between + :border-radius styles/border-radius + :height 52 + :margin-top 15}) + +(defstyle input + {:flex 1 + :font-size 15 + :letter-spacing -0.2 + :android {:padding 0}}) + +(def qr-code + {:margin-right 14}) (def edit-bootnode-view {:flex 1 :margin-horizontal 16 :margin-vertical 15}) -(def input-container - {:margin-bottom 15}) - (def bottom-container {:flex-direction :row :margin-horizontal 12 diff --git a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs index 02440ef671..cc8458b72c 100644 --- a/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs +++ b/src/status_im/ui/screens/bootnodes_settings/edit_bootnode/views.cljs @@ -6,37 +6,53 @@ [status-im.i18n :as i18n] [status-im.ui.components.styles :as components.styles] [status-im.ui.components.common.common :as components.common] + [status-im.ui.components.colors :as colors] + [status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.text-input.view :as text-input] [status-im.ui.screens.bootnodes-settings.edit-bootnode.styles :as styles])) +(def qr-code + [react/touchable-highlight {:on-press #(re-frame/dispatch [:scan-qr-code + {:toolbar-title (i18n/label :t/add-bootnode)} + :set-bootnode-from-qr]) + :style styles/qr-code} + [react/view + [vector-icons/icon :icons/qr {:color colors/blue}]]]) + (views/defview edit-bootnode [] (views/letsubs [manage-bootnode [:get-manage-bootnode] is-valid? [:manage-bootnode-valid?]] - [react/view components.styles/flex - [status-bar/status-bar] - [react/keyboard-avoiding-view components.styles/flex - [toolbar/simple-toolbar (i18n/label :t/add-bootnode)] - [react/scroll-view - [react/view styles/edit-bootnode-view - [text-input/text-input-with-label - {:label (i18n/label :t/name) - :placeholder (i18n/label :t/specify-name) - :container styles/input-container - :default-value (get-in manage-bootnode [:name :value]) - :on-change-text #(re-frame/dispatch [:bootnode-set-input :name %]) - :auto-focus true}] - [text-input/text-input-with-label - {:label (i18n/label :t/bootnode-address) - :placeholder (i18n/label :t/specify-bootnode-address) - :container styles/input-container - :default-value (get-in manage-bootnode [:url :value]) - :on-change-text #(re-frame/dispatch [:bootnode-set-input :url %])}]]] - [react/view styles/bottom-container - [react/view components.styles/flex] - [components.common/bottom-button - {:forward? true - :label (i18n/label :t/save) - :disabled? (not is-valid?) - :on-press #(re-frame/dispatch [:save-new-bootnode])}]]]])) + (let [url (get-in manage-bootnode [:url :value]) + name (get-in manage-bootnode [:name :value])] + + [react/view components.styles/flex + [status-bar/status-bar] + [react/keyboard-avoiding-view components.styles/flex + [toolbar/simple-toolbar (i18n/label :t/add-bootnode)] + [react/scroll-view + [react/view styles/edit-bootnode-view + [text-input/text-input-with-label + {:label (i18n/label :t/name) + :placeholder (i18n/label :t/specify-name) + :style styles/input + :container styles/input-container + :default-value name + :on-change-text #(re-frame/dispatch [:bootnode-set-input :name %]) + :auto-focus true}] + [text-input/text-input-with-label + {:label (i18n/label :t/bootnode-address) + :placeholder (i18n/label :t/specify-bootnode-address) + :content qr-code + :style styles/input + :container styles/input-container + :default-value url + :on-change-text #(re-frame/dispatch [:bootnode-set-input :url %])}]]] + [react/view styles/bottom-container + [react/view components.styles/flex] + [components.common/bottom-button + {:forward? true + :label (i18n/label :t/save) + :disabled? (not is-valid?) + :on-press #(re-frame/dispatch [:save-new-bootnode])}]]]]))) diff --git a/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/events.cljs b/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/events.cljs index 976c2b0d37..dfd04e0aca 100644 --- a/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/events.cljs +++ b/src/status_im/ui/screens/offline_messaging_settings/edit_mailserver/events.cljs @@ -27,6 +27,6 @@ (handlers/register-handler-fx :set-mailserver-from-qr - (fn [cofx [_ _ contact-identity]] - (assoc (models.mailserver/set-input :url contact-identity cofx) + (fn [cofx [_ _ url]] + (assoc (models.mailserver/set-input :url url cofx) :dispatch [:navigate-back]))) diff --git a/test/cljs/status_im/test/models/bootnode.cljs b/test/cljs/status_im/test/models/bootnode.cljs new file mode 100644 index 0000000000..00f082d8ca --- /dev/null +++ b/test/cljs/status_im/test/models/bootnode.cljs @@ -0,0 +1,102 @@ +(ns status-im.test.models.bootnode + (:require [cljs.test :refer-macros [deftest is testing]] + [status-im.models.bootnode :as model])) + +(def bootnode-id "1da276e34126e93babf24ec88aac1a7602b4cbb2e11b0961d0ab5e989ca9c261aa7f7c1c85f15550a5f1e5a5ca2305b53b9280cf5894d5ecf7d257b173136d40") +(def host "167.99.209.61:30504") + +(def valid-bootnode-address (str "enode://" bootnode-id "@" host)) + +(deftest save-bootnode + (testing "adding a bootnode" + (let [new-bootnode {:name {:value "name"} + :url {:value "url"}} + expected {"mainnet_rpc" {"someid" {:name "name" + :address "url" + :chain "mainnet_rpc" + :id "someid"}}} + actual (model/save + {:random-id "some-id" + :db {:bootnodes/manage new-bootnode + :network "mainnet_rpc" + :account/account {}}})] + (is (= expected (get-in actual [:db :account/account :bootnodes])))))) + +(deftest set-input-bootnode + (testing "it validates names" + (testing "correct name" + (is (= {:db {:bootnodes/manage {:name {:value "value" + :error false}}}} + (model/set-input :name "value" {})))) + (testing "blank name" + (is (= {:db {:bootnodes/manage {:name {:value "" + :error true}}}} + (model/set-input :name "" {}))))) + (testing "it validates bootnode url" + (testing "correct url" + (is (= {:db {:bootnodes/manage {:url {:value valid-bootnode-address + :error false}}}} + (model/set-input :url valid-bootnode-address {})))) + (testing "broken url" + (is (= {:db {:bootnodes/manage {:url {:value "broken" + :error true}}}} + (model/set-input :url "broken" {})))))) + +(deftest edit-bootnode + (let [db {:network "mainnet_rpc" + :account/account + {:bootnodes + {"mainnet_rpc" + {"a" {:id "a" + :name "name" + :address valid-bootnode-address}}}}} + cofx {:db db}] + (testing "when no id is given" + (let [actual (model/edit nil cofx)] + (testing "it resets :bootnodes/manage" + (is (= {:id {:value nil + :error false} + :url {:value "" + :error true} + :name {:value "" + :error true}} + (-> actual :db :bootnodes/manage)))) + (testing "it navigates to edit-bootnode view" + (is (= [:navigate-to :edit-bootnode] + (-> actual :dispatch)))))) + (testing "when an id is given" + (testing "when the bootnode is in the list" + (let [actual (model/edit "a" cofx)] + (testing "it populates the fields with the correct values" + (is (= {:id {:value "a" + :error false} + :url {:value valid-bootnode-address + :error false} + :name {:value "name" + :error false}} + (-> actual :db :bootnodes/manage)))) + (testing "it navigates to edit-bootnode view" + (is (= [:navigate-to :edit-bootnode] + (-> actual :dispatch)))))) + (testing "when the bootnode is not in the list" + (let [actual (model/edit "not-existing" cofx)] + (testing "it populates the fields with the correct values" + (is (= {:id {:value nil + :error false} + :url {:value "" + :error true} + :name {:value "" + :error true}} + (-> actual :db :bootnodes/manage)))) + (testing "it navigates to edit-bootnode view" + (is (= [:navigate-to :edit-bootnode] + (-> actual :dispatch))))))))) + +(deftest fetch-bootnode + (testing "it fetches the bootnode from the db" + (let [cofx {:db {:network "mainnet_rpc" + :account/account {:bootnodes {"mainnet_rpc" + {"a" {:id "a" + :name "name" + :address "enode://old-id:old-password@url:port"}}}}}}] + (is (model/fetch "a" cofx))))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 3e67939d76..2bfd684f2b 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -11,6 +11,7 @@ [status-im.test.profile.events] [status-im.test.bots.events] [status-im.test.models.mailserver] + [status-im.test.models.bootnode] [status-im.test.transport.core] [status-im.test.chat.models] [status-im.test.chat.models.input] @@ -36,7 +37,6 @@ [status-im.test.utils.mixpanel] [status-im.test.utils.prices] [status-im.test.ui.screens.network-settings.edit-network.events] - [status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events] [status-im.test.ui.screens.accounts.login.events])) (enable-console-print!) @@ -58,6 +58,7 @@ 'status-im.test.profile.events 'status-im.test.data-store.realm.core 'status-im.test.models.mailserver + 'status-im.test.models.bootnode 'status-im.test.bots.events 'status-im.test.transport.core 'status-im.test.wallet.subs @@ -84,5 +85,4 @@ 'status-im.test.utils.mixpanel 'status-im.test.utils.prices 'status-im.test.ui.screens.network-settings.edit-network.events - 'status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events 'status-im.test.ui.screens.accounts.login.events) diff --git a/test/cljs/status_im/test/ui/screens/bootnodes_settings/edit_bootnode/events.cljs b/test/cljs/status_im/test/ui/screens/bootnodes_settings/edit_bootnode/events.cljs deleted file mode 100644 index 4d781a664b..0000000000 --- a/test/cljs/status_im/test/ui/screens/bootnodes_settings/edit_bootnode/events.cljs +++ /dev/null @@ -1,19 +0,0 @@ -(ns status-im.test.ui.screens.bootnodes-settings.edit-bootnode.events - (:require [cljs.test :refer-macros [deftest is testing]] - [status-im.ui.screens.bootnodes-settings.edit-bootnode.events :as events])) - -(deftest add-new-bootnode - (testing "adding a bootnode" - (let [new-bootnode {:name {:value "name"} - :url {:value "url"}} - expected {"mainnet_rpc" {"someid" {:name "name" - :address "url" - :chain "mainnet_rpc" - :id "someid"}}} - actual (events/save-new-bootnode - {:random-id "some-id" - :db {:bootnodes/manage new-bootnode - :network "mainnet_rpc" - :account/account {}}} - nil)] - (is (= expected (get-in actual [:db :account/account :bootnodes]))))))