[#4772] Resolve ENS domains in browser

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2018-08-30 13:51:35 +03:00
parent b7b6a252e1
commit c96bc5aa8b
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
11 changed files with 310 additions and 50 deletions

View File

@ -11,7 +11,8 @@
com.taoensso/timbre {:mvn/version "4.10.0"}
hickory {:mvn/version "0.7.1"}
com.cognitect/transit-cljs {:mvn/version "0.8.248"}
status-im/pluto {:mvn/version "iteration-2-SNAPSHOT"}}
status-im/pluto {:mvn/version "iteration-2-SNAPSHOT"}
mvxcvi/alphabase {:mvn/version "1.0.0"}}
:aliases
{:dev {:extra-deps

View File

@ -11,7 +11,8 @@
[com.taoensso/timbre "4.10.0"]
[hickory "0.7.1"]
[com.cognitect/transit-cljs "0.8.248"]
[status-im/pluto "iteration-2-SNAPSHOT"]]
[status-im/pluto "iteration-2-SNAPSHOT"]
[mvxcvi/alphabase "1.0.0"]]
:plugins [[lein-cljsbuild "1.1.7"]
[lein-re-frisk "0.5.8"]
[lein-cljfmt "0.5.7"]

View File

@ -5,7 +5,13 @@
[status-im.data-store.dapp-permissions :as dapp-permissions]
[status-im.i18n :as i18n]
[status-im.ui.screens.browser.default-dapps :as default-dapps]
[status-im.utils.http :as http]))
[status-im.utils.http :as http]
[clojure.string :as string]
[status-im.utils.ethereum.resolver :as resolver]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.ens :as ens]
[status-im.utils.multihash :as multihash]
[status-im.utils.handlers-macro :as handlers-macro]))
(defn get-current-url [{:keys [history history-index]}]
(when (and history-index history)
@ -24,13 +30,13 @@
(assoc browser :dapp? true :name (:name dapp))
(assoc browser :dapp? false :name (i18n/label :t/browser)))))
(defn update-browser-fx [{:keys [db now]} browser]
(defn update-browser-fx [browser {:keys [db now]}]
(let [updated-browser (check-if-dapp-in-list (assoc browser :timestamp now))]
{:db (update-in db [:browser/browsers (:browser-id updated-browser)]
merge updated-browser)
:data-store/tx [(browser-store/save-browser-tx updated-browser)]}))
(defn update-browser-history-fx [cofx browser url loading?]
(defn update-browser-history-fx [browser url loading? cofx]
(when-not loading?
(let [history-index (:history-index browser)
history (:history browser)
@ -43,12 +49,41 @@
new-index (if slash?
history-index
(dec (count new-history)))]
(update-browser-fx cofx
(assoc browser :history new-history :history-index new-index)))))))
(update-browser-fx (assoc browser :history new-history :history-index new-index)
cofx))))))
(defn update-browser-and-navigate [cofx browser]
(merge (update-browser-fx cofx browser)
{:dispatch [:navigate-to :browser (:browser-id browser)]}))
(defn ens? [host]
(string/ends-with? host ".eth"))
(defn ens-multihash-callback [hex]
(let [hash (when hex (multihash/base58 (multihash/create :sha2-256 (subs hex 2))))]
(if (and hash (not= hash resolver/default-hash))
(re-frame/dispatch [:ens-multihash-resolved hash])
(re-frame/dispatch [:update-browser-options {:resolving? false}]))))
(defn resolve-multihash-fx [host loading error? {{:keys [web3 network] :as db} :db}]
(let [network (get-in db [:account/account :networks network])
chain (ethereum/network->chain-keyword network)]
(if (and (not loading) (not error?) (ens? host))
{:db (assoc-in db [:browser/options :resolving?] true)
:resolve-ens-multihash {:web3 web3
:registry (get ens/ens-registries
chain)
:ens-name host
:cb ens-multihash-callback}}
{})))
(defn update-new-browser-and-navigate [host browser cofx]
(handlers-macro/merge-fx
cofx
{:dispatch [:navigate-to :browser {:browser-id (:browser-id browser)
:resolving? (ens? host)}]}
(update-browser-fx browser)
(resolve-multihash-fx host false false)))
(defn update-browser-and-navigate [browser cofx]
(merge (update-browser-fx browser cofx)
{:dispatch [:navigate-to :browser {:browser-id (:browser-id browser)}]}))
(def permissions {constants/dapp-permission-contact-code {:title (i18n/label :t/wants-to-access-profile)
:description (i18n/label :t/your-contact-code)

View File

@ -10,6 +10,7 @@
(spec/def :browser/history (spec/nilable vector?))
(spec/def :browser/history-index (spec/nilable int?))
(spec/def :browser/loading? (spec/nilable boolean?))
(spec/def :browser/resolving? (spec/nilable boolean?))
(spec/def :browser/url-editing? (spec/nilable boolean?))
(spec/def :browser/show-tooltip (spec/nilable keyword?))
(spec/def :browser/show-permission (spec/nilable map?))
@ -19,6 +20,7 @@
(allowed-keys
:opt-un [:browser/browser-id
:browser/loading?
:browser/resolving?
:browser/url-editing?
:browser/show-tooltip
:browser/show-permission

View File

@ -13,7 +13,8 @@
[status-im.utils.random :as random]
[status-im.utils.types :as types]
[status-im.utils.universal-links.core :as utils.universal-links]
[taoensso.timbre :as log]))
[taoensso.timbre :as log]
[status-im.utils.ethereum.resolver :as resolver]))
(re-frame/reg-fx
:browse
@ -39,72 +40,89 @@
(fn [[message webview]]
(.sendToBridge webview (types/clj->json message))))
(re-frame/reg-fx
:resolve-ens-multihash
(fn [{:keys [web3 registry ens-name cb]}]
(resolver/content web3 registry ens-name cb)))
(handlers/register-handler-fx
:browse-link-from-message
(fn [_ [_ link]]
{:browse link}))
(handlers/register-handler-fx
:ens-multihash-resolved
(fn [{:keys [db] :as cofx} [_ hash]]
(let [options (:browser/options db)
browsers (:browser/browsers db)
browser (get browsers (:browser-id options))
history-index (:history-index browser)]
(handlers-macro/merge-fx
cofx
{:db (assoc-in db [:browser/options :resolving?] false)}
(model/update-browser-fx
(assoc-in browser [:history history-index] (str "https://ipfs.infura.io/ipfs/" hash)))))))
(handlers/register-handler-fx
:open-url-in-browser
[re-frame/trim-v]
(fn [cofx [url]]
(let [normalized-url (http/normalize-and-decode-url url)]
(model/update-browser-and-navigate cofx {:browser-id (or (http/url-host normalized-url) (random/id))
(fn [cofx [_ url]]
(let [normalized-url (http/normalize-and-decode-url url)
host (http/url-host normalized-url)]
(model/update-new-browser-and-navigate
host
{:browser-id (or host (random/id))
:history-index 0
:history [normalized-url]}))))
:history [normalized-url]}
cofx))))
(handlers/register-handler-fx
:send-to-bridge
[re-frame/trim-v]
(fn [cofx [message]]
(fn [cofx [_ message]]
{:send-to-bridge-fx [message (get-in cofx [:db :webview-bridge])]}))
(handlers/register-handler-fx
:open-browser
[re-frame/trim-v]
(fn [cofx [browser]]
(model/update-browser-and-navigate cofx browser)))
(fn [cofx [_ browser]]
(model/update-browser-and-navigate browser cofx)))
(handlers/register-handler-fx
:update-browser-on-nav-change
[re-frame/trim-v]
(fn [cofx [browser url loading]]
(model/update-browser-history-fx cofx browser url loading)))
(fn [cofx [_ browser url loading error?]]
(let [host (http/url-host url)]
(handlers-macro/merge-fx
cofx
(model/resolve-multihash-fx host loading error?)
(model/update-browser-history-fx browser url loading)))))
(handlers/register-handler-fx
:update-browser-options
[re-frame/trim-v]
(fn [{:keys [db]} [options]]
(fn [{:keys [db]} [_ options]]
{:db (update db :browser/options merge options)}))
(handlers/register-handler-fx
:remove-browser
[re-frame/trim-v]
(fn [{:keys [db]} [browser-id]]
(fn [{:keys [db]} [_ browser-id]]
{:db (update-in db [:browser/browsers] dissoc browser-id)
:data-store/tx [(browser-store/remove-browser-tx browser-id)]}))
(defn nav-update-browser [cofx browser history-index]
(model/update-browser-fx cofx (assoc browser :history-index history-index)))
(model/update-browser-fx (assoc browser :history-index history-index) cofx))
(handlers/register-handler-fx
:browser-nav-back
[re-frame/trim-v]
(fn [cofx [{:keys [history-index] :as browser}]]
(fn [cofx [_ {:keys [history-index] :as browser}]]
(when (pos? history-index)
(nav-update-browser cofx browser (dec history-index)))))
(handlers/register-handler-fx
:browser-nav-forward
[re-frame/trim-v]
(fn [cofx [{:keys [history-index] :as browser}]]
(fn [cofx [_ {:keys [history-index] :as browser}]]
(when (< history-index (dec (count (:history browser))))
(nav-update-browser cofx browser (inc history-index)))))
(handlers/register-handler-fx
:on-bridge-message
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [message]]
(fn [{:keys [db] :as cofx} [_ message]]
(let [{:browser/keys [options browsers]} db
{:keys [browser-id]} options
browser (get browsers browser-id)
@ -113,7 +131,7 @@
(cond
(and (= type constants/history-state-changed) platform/ios? (not= "about:blank" url))
(model/update-browser-history-fx cofx browser url false)
(model/update-browser-history-fx browser url false cofx)
(= type constants/web3-send-async)
(model/web3-send-async payload messageId cofx)
@ -127,7 +145,6 @@
(handlers/register-handler-fx
:check-permissions-queue
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} _]
(let [{:keys [show-permission permissions-queue]} (:browser/options db)]
(when (and (nil? show-permission) (last permissions-queue))
@ -145,8 +162,7 @@
(handlers/register-handler-fx
:next-dapp-permission
[re-frame/trim-v]
(fn [cofx [params permission permissions-data]]
(fn [cofx [_ params permission permissions-data]]
(model/next-permission {:params params
:permission permission
:permissions-data permissions-data}

View File

@ -2,5 +2,5 @@
(:require [status-im.ui.screens.navigation :as navigation]))
(defmethod navigation/preload-data! :browser
[db [_ _ browser-id]]
(assoc db :browser/options {:browser-id browser-id}))
[db [_ _ options]]
(assoc db :browser/options options))

View File

@ -46,6 +46,7 @@
(re-frame/dispatch [:update-browser-on-nav-change
browser
(http/normalize-and-decode-url @url-text)
false
false]))
:placeholder (i18n/label :t/enter-url)
:auto-capitalize :none
@ -81,12 +82,12 @@
[react/text {:style styles/web-view-error-text}
(str desc)]]))
(defn on-navigation-change [event browser]
(defn on-navigation-change [event browser error?]
(let [{:strs [url loading]} (js->clj event)]
(when platform/ios?
(re-frame/dispatch [:update-browser-options {:loading? loading}]))
(when (not= "about:blank" url)
(re-frame/dispatch [:update-browser-on-nav-change browser url loading]))))
(re-frame/dispatch [:update-browser-on-nav-change browser url loading error?]))))
(defn get-inject-js [url]
(let [domain-name (nth (re-find #"^\w+://(www\.)?([^/:]+)" url) 2)]
@ -115,7 +116,7 @@
(views/letsubs [webview (atom nil)
{:keys [address]} [:get-current-account]
{:keys [browser-id dapp? name] :as browser} [:get-current-browser]
{:keys [error? loading? url-editing? show-tooltip show-permission]} [:get :browser/options]
{:keys [error? loading? url-editing? show-tooltip show-permission resolving?]} [:get :browser/options]
rpc-url [:get :rpc-url]
network-id [:get-network-id]]
(let [can-go-back? (model/can-go-back? browser)
@ -131,12 +132,12 @@
:ref #(do
(reset! webview %)
(re-frame/dispatch [:set :webview-bridge %]))
:source {:uri url}
:source (when-not resolving? {:uri url})
:java-script-enabled true
:bounces false
:local-storage-enabled true
:render-error web-view-error
:on-navigation-state-change #(on-navigation-change % browser)
:on-navigation-state-change #(on-navigation-change % browser error?)
:on-bridge-message #(re-frame/dispatch [:on-bridge-message %])
:on-load #(re-frame/dispatch [:update-browser-options {:error? false}])
:on-error #(re-frame/dispatch [:update-browser-options {:error? true
@ -148,7 +149,7 @@
(ethereum/normalized-address address)
(str network-id)))
:injected-java-script js-res/webview-js}]
(when loading?
(when (or loading? resolving?)
[react/view styles/web-view-loading
[components/activity-indicator {:animating true}]])]
[navigation webview browser can-go-back? can-go-forward?]

View File

@ -101,7 +101,7 @@
(spec/def :navigation/prev-view-id (spec/nilable keyword?))
;; navigation screen params
(spec/def :navigation.screen-params/network-details (allowed-keys :req [:networks/selected-network]))
(spec/def :navigation.screen-params/browser (spec/nilable string?))
(spec/def :navigation.screen-params/browser (spec/nilable map?))
(spec/def :navigation.screen-params.profile-qr-viewer/contact (spec/nilable map?))
(spec/def :navigation.screen-params.profile-qr-viewer/source (spec/nilable keyword?))
(spec/def :navigation.screen-params.profile-qr-viewer/value (spec/nilable string?))

View File

@ -70,6 +70,11 @@
(namehash ens-name))
(fn [_ address] (cb (ethereum/hex->address address)))))
(defn content [web3 resolver ens-name cb]
(ethereum/call web3
(ethereum/call-params resolver "content(bytes32)" (namehash ens-name))
(fn [_ hash] (cb hash))))
(def ABI-hash "0x2203ab56")
(def pubkey-hash "0xc8690233")

View File

@ -0,0 +1,14 @@
(ns status-im.utils.ethereum.resolver
(:require [status-im.utils.ethereum.ens :as ens]))
(def default-address "0x0000000000000000000000000000000000000000")
(def default-hash "0x0000000000000000000000000000000000000000000000000000000000000000")
(defn content [web3 registry ens-name cb]
(ens/resolver web3
registry
ens-name
(fn [address]
(if (and address (not= address default-address))
(ens/content web3 address ens-name cb)
(cb nil)))))

View File

@ -0,0 +1,185 @@
(ns status-im.utils.multihash
"Core multihash type definition and helper methods."
(:require
[alphabase.base58 :as b58]
[alphabase.bytes :as bytes]
[alphabase.hex :as hex]))
(def ^:const algorithm-codes
"Map of information about the available content hashing algorithms."
{:sha1 0x11
:sha2-256 0x12
:sha2-512 0x13
:sha3 0x14
:blake2b 0x40
:blake2s 0x41})
(defn app-code?
"True if the given code number is assigned to the application-specfic range.
Returns nil if the argument is not an integer."
[code]
(when (integer? code)
(< 0 code 0x10)))
(defn get-algorithm
"Looks up an algorithm by keyword name or code number. Returns `nil` if the
value does not map to any valid algorithm."
[value]
(cond
(keyword? value)
(when-let [code (get algorithm-codes value)]
{:code code, :name value})
(not (integer? value))
nil
(app-code? value)
{:code value, :name (keyword (str "app-" value))}
:else
(some #(when (= value (val %))
{:code value, :name (key %)})
algorithm-codes)))
;; ## Multihash Type
;; Multihash identifiers have two properties:
;;
;; - `code` is a numeric code for an algorithm entry in `algorithm-codes` or an
;; application-specific algorithm code.
;; - `hex-digest` is a string holding the hex-encoded algorithm output.
;;
;; Multihash values also support metadata.
(deftype Multihash [code hex-digest _meta]
Object
(toString
[this]
(str "hash:" (name (:name (get-algorithm code))) \: hex-digest))
(-equiv
[this that]
(cond
(identical? this that) true
(instance? Multihash that)
(and (= code (:code that))
(= hex-digest (:hex-digest that)))
:else false))
IHash
(-hash
[this]
(hash-combine code hex-digest))
IComparable
(-compare
[this that]
(cond
(= this that) 0
(< code (:code that)) -1
(> code (:code that)) 1
:else (compare hex-digest (:hex-digest that))))
ILookup
(-lookup
[this k]
(-lookup this k nil))
(-lookup
[this k not-found]
(case k
:code code
:algorithm (:name (get-algorithm code))
:length (/ (count hex-digest) 2)
:digest (hex/decode hex-digest)
:hex-digest hex-digest
not-found))
IMeta
(-meta
[this]
_meta)
IWithMeta
(-with-meta
[this meta-map]
(Multihash. code hex-digest meta-map)))
(defn create
"Constructs a new Multihash identifier. Accepts either a numeric algorithm
code or a keyword name as the first argument. The digest may either by a byte
array or a hex string."
[algorithm digest]
(let [algo (get-algorithm algorithm)]
(when-not (integer? (:code algo))
(throw (ex-info
(str "Argument " (pr-str algorithm) " does not "
"represent a valid hash algorithm.")
{:algorithm algorithm})))
(let [hex-digest (if (string? digest) digest (hex/encode digest))
byte-len (/ (count hex-digest) 2)]
(when (< 127 byte-len)
(throw (ex-info (str "Digest length must be less than 128 bytes: "
byte-len)
{:length byte-len})))
(when-let [err (hex/validate hex-digest)]
(throw (ex-info err {:hex-digest hex-digest})))
(->Multihash (:code algo) hex-digest nil))))
;; ## Encoding and Decoding
(defn encode
"Encodes a multihash into a binary representation."
^bytes
[mhash]
(let [length (:length mhash)
encoded (bytes/byte-array (+ length 2))]
(bytes/set-byte encoded 0 (:code mhash))
(bytes/set-byte encoded 1 length)
(bytes/copy (:digest mhash) 0 encoded 2 length)
encoded))
(defn hex
"Encodes a multihash into a hexadecimal string."
[mhash]
(when mhash
(hex/encode (encode mhash))))
(defn base58
"Encodes a multihash into a Base-58 string."
[mhash]
(when mhash
(b58/encode (encode mhash))))
(defn decode-array
"Decodes a byte array directly into multihash. Throws `ex-info` with a `:type`
of `:multihash/bad-input` if the data is malformed or invalid."
[^bytes encoded]
(let [encoded-size (alength encoded)
min-size 3]
(when (< encoded-size min-size)
(throw (ex-info
(str "Cannot read multihash from byte array: " encoded-size
" is less than the minimum of " min-size)
{:type :multihash/bad-input}))))
(let [code (bytes/get-byte encoded 0)
length (bytes/get-byte encoded 1)
payload (- (alength encoded) 2)]
(when-not (pos? length)
(throw (ex-info
(str "Encoded length " length " is invalid")
{:type :multihash/bad-input})))
(when (< payload length)
(throw (ex-info
(str "Encoded digest length " length " exceeds actual "
"remaining payload of " payload " bytes")
{:type :multihash/bad-input})))
(let [digest (bytes/byte-array length)]
(bytes/copy encoded 2 digest 0 length)
(create code digest))))