status-react/src/status_im/network/core.cljs

261 lines
11 KiB
Clojure

(ns status-im.network.core
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.accounts.update.core :as accounts.update]
[status-im.i18n :as i18n]
[status-im.native-module.core :as status]
[status-im.network.net-info :as net-info]
[status-im.mailserver.core :as mailserver]
[status-im.utils.ethereum.core :as ethereum]
[status-im.ui.screens.navigation :as navigation]
[status-im.fleet.core :as fleet-core]
[status-im.utils.fx :as fx]
[status-im.utils.handlers :as handlers]
[status-im.utils.http :as http]
[status-im.utils.types :as types]))
(def url-regex
#"https?://(www\.)?[-a-zA-Z0-9@:%._\+~#=]{2,256}(\.[a-z]{2,6})?\b([-a-zA-Z0-9@:%_\+.~#?&//=]*)")
(defn valid-rpc-url? [url]
(boolean (re-matches url-regex (str url))))
(def default-manage
{:name {:value ""}
:url {:value ""}
:chain {:value :mainnet}})
(defn validate-string [{:keys [value]}]
{:value value
:error (string/blank? value)})
(defn validate-network-id [{:keys [value]}]
{:value value
:error (and (not (string/blank? value))
(= (int value) 0))})
(defn validate-url [{:keys [value]}]
{:value value
:error (not (valid-rpc-url? value))})
(defn validate-manage [manage]
(-> manage
(update :url validate-url)
(update :name validate-string)
(update :chain validate-string)
(update :network-id validate-network-id)))
(defn valid-manage? [manage]
(->> (validate-manage manage)
vals
(map :error)
(not-any? identity)))
(defn new-network [random-id network-name upstream-url type network-id]
(let [data-dir (str "/ethereum/" (name type) "_rpc")
config {:NetworkId (or (when network-id (int network-id))
(ethereum/chain-keyword->chain-id type))
:DataDir data-dir
:UpstreamConfig {:Enabled true
:URL upstream-url}}]
{:id (string/replace random-id "-" "")
:name network-name
:config config}))
(defn get-chain [{:keys [db]}]
(let [network (get (:networks (:account/account db)) (:network db))]
(ethereum/network->chain-keyword network)))
(fx/defn set-input
[{:keys [db]} input-key value]
{:db (-> db
(update-in [:networks/manage input-key] assoc :value value)
(update-in [:networks/manage] validate-manage))})
(defn- action-handler
([handler]
(action-handler handler nil nil))
([handler data cofx]
(when handler
(handler data cofx))))
(fx/defn save
[{{:network/keys [manage] :account/keys [account] :as db} :db
random-id-generator :random-id-generator :as cofx}
{:keys [data success-event on-success on-failure]}]
(let [data (or data manage)]
(if (valid-manage? data)
(let [{:keys [name url chain network-id]} data
network (new-network (random-id-generator)
(:value name)
(:value url)
(:value chain)
(:value network-id))
new-networks (merge {(:id network) network} (:networks account))]
(fx/merge cofx
{:db (dissoc db :networks/manage)}
#(action-handler on-success (:id network) %)
(accounts.update/account-update
{:networks new-networks}
{:success-event success-event})))
(action-handler on-failure))))
;; No edit functionality actually implemented
(fx/defn edit
[{db :db}]
{:db (assoc db :networks/manage (validate-manage default-manage))
:dispatch [:navigate-to :edit-network]})
(fx/defn connect-success [{:keys [db now] :as cofx} {:keys [network-id on-success client-version]}]
(let [current-network (get-in db [:account/account :networks (:network db)])]
(if (ethereum/network-with-upstream-rpc? current-network)
(fx/merge cofx
#(action-handler on-success {:network-id network-id :client-version client-version} %)
(accounts.update/account-update
{:network network-id
:last-updated now}
{:success-event [:accounts.update.callback/save-settings-success]}))
(fx/merge cofx
{:ui/show-confirmation {:title (i18n/label :t/close-app-title)
:content (i18n/label :t/close-app-content)
:confirm-button-text (i18n/label :t/close-app-button)
:on-accept #(re-frame/dispatch [:network.ui/save-non-rpc-network-pressed network-id])
:on-cancel nil}}
#(action-handler on-success {:network-id network-id :client-version client-version} %)))))
(defn connect-failure [{:keys [network-id on-failure reason]}]
(action-handler on-failure
{:network-id network-id :reason reason}
nil))
(fx/defn connect [{:keys [db] :as cofx} {:keys [network-id on-success on-failure]}]
(if-let [config (get-in db [:account/account :networks network-id :config])]
(if-let [upstream-url (get-in config [:UpstreamConfig :URL])]
{:http-post {:url upstream-url
:data (types/clj->json {:jsonrpc "2.0"
:method "web3_clientVersion"
:id 1})
:opts {:headers {"Content-Type" "application/json"}}
:success-event-creator (fn [{:keys [response-body]}]
(if-let [client-version (:result (http/parse-payload response-body))]
[::connect-success {:network-id network-id
:on-success on-success
:client-version client-version}]
[::connect-failure {:network-id network-id
:on-failure on-failure
:reason (i18n/label :t/network-invalid-url)}]))
:failure-event-creator (fn [{:keys [response-body status-code]}]
(let [reason (if status-code
(i18n/label :t/network-invalid-status-code {:code status-code})
(str response-body))]
[::connect-failure {:network-id network-id
:on-failure on-failure
:reason reason}]))}}
(connect-success cofx {:network-id network-id
:on-success on-success
:client-version ""}))
(connect-failure {:network-id network-id
:on-failure on-failure
:reason "A network with the specified id doesn't exist"})))
(handlers/register-handler-fx
::connect-success
(fn [cofx [_ data]]
(connect-success cofx data)))
(handlers/register-handler-fx
::connect-failure
(fn [_ [_ data]]
(connect-failure data)))
(fx/defn delete
[{{:account/keys [account]} :db :as cofx} {:keys [network on-success on-failure]}]
(let [current-network? (= (:network account) network)]
(if (or current-network?
(not (get-in account [:networks network])))
(fx/merge cofx
{:ui/show-error (i18n/label :t/delete-network-error)}
#(action-handler on-failure network %))
(fx/merge cofx
{:ui/show-confirmation {:title (i18n/label :t/delete-network-title)
:content (i18n/label :t/delete-network-confirmation)
:confirm-button-text (i18n/label :t/delete)
:on-accept #(re-frame/dispatch [:network.ui/remove-network-confirmed network])
:on-cancel nil}}
#(action-handler on-success network %)))))
(fx/defn save-non-rpc-network
[{:keys [db now] :as cofx} network]
(accounts.update/account-update cofx
{:network network
:last-updated now}
{:success-event [:network.callback/non-rpc-network-saved]}))
(fx/defn remove-network
[{:keys [db now] :as cofx} network]
(let [networks (dissoc (get-in db [:account/account :networks]) network)]
(accounts.update/account-update cofx
{:networks networks
:last-updated now}
{:success-event [:navigate-back]})))
(fx/defn save-network
[cofx]
(save cofx
{:data (get-in cofx [:db :networks/manage])
:success-event [:navigate-back]}))
(fx/defn handle-connection-status-change
[{:keys [db] :as cofx} is-connected?]
(fx/merge cofx
{:db (assoc db :network-status (if is-connected? :online :offline))}
(mailserver/network-connection-status-changed is-connected?)))
(defn- navigate-to-network-details
[cofx network show-warning?]
(fx/merge cofx
(when show-warning?
{:utils/show-popup {:title "LES support is experimental!"
:content "Use at your own risk!"}})
(navigation/navigate-to-cofx :network-details {:networks/selected-network network})))
(defn- not-supported-warning [fleet]
(str (name fleet) " does not support LES!\n"
"Please, select one of the supported fleets:"
(map name fleet-core/fleets-with-les)))
(fx/defn open-network-details
[cofx network]
(let [db (:db cofx)
rpc-network? (get-in network [:config :UpstreamConfig :Enabled] false)
fleet (fleet-core/current-fleet db nil)
fleet-supports-les? (fleet-core/fleet-supports-les? fleet)]
(if (or rpc-network? fleet-supports-les?)
(navigate-to-network-details cofx network (not rpc-network?))
;; Otherwise, we show an explanation dialog to a user if the current fleet does not suport LES
{:utils/show-popup {:title "LES not supported"
:content (not-supported-warning fleet)}})))
(fx/defn handle-network-status-change
[cofx data]
{:network/notify-status-go data})
(re-frame/reg-fx
:network/listen-to-network-status
(fn []
(let [callback-event #(re-frame/dispatch [:network/network-status-changed %])]
(net-info/net-info callback-event)
(net-info/add-net-info-listener callback-event))))
(re-frame/reg-fx
:network/listen-to-connection-status
(fn []
(let [callback-event #(re-frame/dispatch [:network/connection-status-changed %])]
(net-info/is-connected? callback-event)
(net-info/add-connection-listener callback-event))))
(re-frame/reg-fx
:network/notify-status-go
(fn [data]
(status/connection-change data)))