Allow scanning qr codes for custom bootnodes
Signed-off-by: Andrea Maria Piana <andrea.maria.piana@gmail.com>
This commit is contained in:
parent
b345b4a79a
commit
9058c54933
|
@ -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}))))
|
|
@ -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])))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])}]]]])))
|
||||
|
|
|
@ -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])))
|
||||
|
|
|
@ -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)))))
|
|
@ -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)
|
||||
|
|
|
@ -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]))))))
|
Loading…
Reference in New Issue