Refactor browser and permissions

- move db and models ns to browser module
- remove browser navigation/preload-data
- rewrite dapp-permission logic and move code to permissions ns
- rewrite all browser events according to guidelines and move them to
`status-im.events` ns
- rewrite tests for browser and permissions
This commit is contained in:
yenda 2018-09-21 15:41:40 +02:00
parent c1014fd772
commit 8459fef358
No known key found for this signature in database
GPG Key ID: 0095623C0069DCE6
33 changed files with 943 additions and 758 deletions

View File

@ -0,0 +1,315 @@
(ns status-im.browser.core
(:require [clojure.string :as string]
[re-frame.core :as re-frame]
[status-im.browser.permissions :as browser.permissions]
[status-im.constants :as constants]
[status-im.data-store.browser :as browser-store]
[status-im.i18n :as i18n]
[status-im.js-dependencies :as dependencies]
[status-im.native-module.core :as status]
[status-im.ui.components.list-selection :as list-selection]
[status-im.ui.screens.browser.default-dapps :as default-dapps]
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.ethereum.ens :as ens]
[status-im.utils.ethereum.resolver :as resolver]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.http :as http]
[status-im.utils.multihash :as multihash]
[status-im.utils.platform :as platform]
[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]))
(defn initialize-browsers
[{:keys [db all-stored-browsers]}]
(let [browsers (into {} (map #(vector (:browser-id %) %) all-stored-browsers))]
{:db (assoc db :browser/browsers browsers)}))
(defn initialize-dapp-permissions
[{:keys [db all-dapp-permissions]}]
(let [dapp-permissions (into {} (map #(vector (:dapp %) %) all-dapp-permissions))]
{:db (assoc db :dapps/permissions dapp-permissions)}))
(defn get-current-url [{:keys [history history-index]}]
(when (and history-index history)
(nth history history-index)))
(defn secure? [{:keys [error? dapp?] :as browser}]
(or dapp?
(and (not error?)
(string/starts-with? (get-current-url browser) "https://"))))
(defn remove-browser [browser-id {:keys [db]}]
{:db (update-in db [:browser/browsers] dissoc browser-id)
:data-store/tx [(browser-store/remove-browser-tx browser-id)]})
(defn check-if-dapp-in-list [{:keys [history history-index] :as browser}]
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))
dapp (first (filter #(= history-host (http/url-host (:dapp-url %))) (apply concat (mapv :data default-dapps/all))))]
(if dapp
;;TODO(yenda): the consequence of this is that if user goes to a different
;;url from a dapp browser, the name of the browser in the home screen will
;;change
(assoc browser :dapp? true :name (:name dapp))
(assoc browser :dapp? false :name (i18n/label :t/browser)))))
(defn check-if-phishing-url [{:keys [history history-index] :as browser}]
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))]
(assoc browser :unsafe? (dependencies/phishing-detect history-host))))
(defn update-browser
[{:keys [browser-id history history-index error? dapp?] :as browser}
{:keys [db now]}]
(let [updated-browser (-> (assoc browser :timestamp now)
(check-if-dapp-in-list)
(check-if-phishing-url))]
{:db (update-in db
[:browser/browsers browser-id]
merge updated-browser)
:data-store/tx [(browser-store/save-browser-tx updated-browser)]}))
(defn get-current-browser [db]
(get-in db [:browser/browsers (get-in db [:browser/options :browser-id])]))
(defn can-go-back? [{:keys [history-index]}]
(pos? history-index))
(defn navigate-to-previous-page
[cofx]
(let [{:keys [history-index] :as browser} (get-current-browser (:db cofx))]
(when (can-go-back? browser)
(update-browser (assoc browser :history-index (dec history-index)) cofx))))
(defn can-go-forward? [{:keys [history-index history]}]
(< history-index (dec (count history))))
(defn navigate-to-next-page
[cofx]
(let [{:keys [history-index] :as browser} (get-current-browser (:db cofx))]
(when (can-go-forward? browser)
(update-browser (assoc browser :history-index (inc history-index)) cofx))))
(defn update-browser-history
;; TODO: not clear how this works
[browser url loading? cofx]
(when-not loading?
(let [history-index (:history-index browser)
history (:history browser)
history-url (get-current-url browser)]
(when (not= history-url url)
(let [slash? (= url (str history-url "/"))
new-history (if slash?
(assoc history history-index url)
(conj (subvec history 0 (inc history-index)) url))
new-index (if slash?
history-index
(dec (count new-history)))]
(update-browser (assoc browser
:history new-history
:history-index new-index)
cofx))))))
(defn ens? [host]
(and (string? host)
(string/ends-with? host ".eth")))
(defn resolve-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 [:browser.callback/resolve-ens-multihash-success hash])
(re-frame/dispatch [:browser.callback/resolve-ens-multihash-error]))))
(defn resolve-ens-multihash-success
[hash {:keys [db] :as cofx}]
(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)}
(update-browser (assoc-in browser [:history history-index]
(str "https://ipfs.infura.io/ipfs/" hash))))))
(defn resolve-ens-multihash
[host loading? error? {{:keys [web3 network] :as db} :db}]
(when (and (not loading?)
(not error?)
(ens? host))
(let [network (get-in db [:account/account :networks network])
chain (ethereum/network->chain-keyword network)]
{:db (assoc-in db [:browser/options :resolving?] true)
:browser/resolve-ens-multihash {:web3 web3
:registry (get ens/ens-registries
chain)
:ens-name host
:cb resolve-ens-multihash-callback}})))
(defn update-browser-option
[option-key option-value {:keys [db]}]
{:db (assoc-in db [:browser/options option-key] option-value)})
(defn handle-browser-error [cofx]
(handlers-macro/merge-fx cofx
(update-browser-option :error? true)
(update-browser-option :loading? false)))
(defn update-browser-loading-option
[loading? cofx]
;; TODO(yenda) why are we doing this ?
(when platform/ios?
(update-browser-option :loading? loading? cofx)))
(defn update-browser-on-nav-change
[browser url loading? error? cofx]
(when (not= "about:blank" url)
(let [host (http/url-host url)]
(handlers-macro/merge-fx cofx
(resolve-ens-multihash host loading? error?)
(update-browser-history browser url loading?)))))
(defn navigation-state-changed [event error? cofx]
(let [browser (get-current-browser (:db cofx))
{:strs [url loading]} (js->clj event)]
(handlers-macro/merge-fx cofx
(update-browser-loading-option loading)
(update-browser-on-nav-change browser url loading error?))))
(defn open-url-in-current-browser
"Opens a url in the current browser, which mean no new entry is added to the home page
and history of the current browser is updated so that the user can navigate back to the
origin url"
;; TODO(yenda) is that desirable ?
[url cofx]
(let [browser (get-current-browser (:db cofx))
normalized-url (http/normalize-and-decode-url url)
host (http/url-host normalized-url)]
(handlers-macro/merge-fx cofx
(update-browser-option :url-editing? false)
(resolve-ens-multihash host false false)
(update-browser-history browser normalized-url false))))
(defn navigate-to-browser
[{{:keys [view-id]} :db :as cofx}]
(if (= view-id :dapp-description)
(navigation/navigate-reset
{:index 1
:actions [{:routeName :home}
{:routeName :browser}]}
cofx)
(navigation/navigate-to-cofx :browser nil cofx)))
(defn open-url
"Opens a url in the browser. If a host can be extracted from the url and
there is already a browser for this host, this browser is reused
If the browser is reused, the history is flushed"
[url {:keys [db] :as cofx}]
(let [normalized-url (http/normalize-and-decode-url url)
host (http/url-host normalized-url)
browser {:browser-id (or host (random/id))
:history-index 0
:history [normalized-url]}]
(handlers-macro/merge-fx cofx
{:db (assoc db :browser/options
{:browser-id (:browser-id browser)})}
(navigate-to-browser)
(update-browser browser)
(resolve-ens-multihash host false false))))
(defn open-existing-browser
"Opens an existing browser with it's history"
[browser-id {:keys [db] :as cofx}]
(let [browser (get-in db [:browser/browsers browser-id])]
(handlers-macro/merge-fx cofx
{:db (assoc db :browser/options
{:browser-id browser-id})}
(update-browser browser)
(navigation/navigate-to-cofx :browser nil))))
(defn web3-send-async
[{:keys [method] :as payload} message-id {:keys [db]}]
(if (or (= method constants/web3-send-transaction)
(= method constants/web3-personal-sign))
{:db (update-in db [:wallet :transactions-queue] conj {:message-id message-id :payload payload})
;;TODO(yenda): refactor check-dapps-transactions-queue to remove this dispatch
:dispatch [:check-dapps-transactions-queue]}
{:browser/call-rpc [payload
#(re-frame/dispatch [:browser.callback/call-rpc
{:type constants/web3-send-async-callback
:messageId message-id
:error %1
:result %2}])]}))
(defn send-to-bridge [message cofx]
{:browser/send-to-bridge {:message message
:webview (get-in cofx [:db :webview-bridge])}})
(defn web3-send-async-read-only
[dapp-name {:keys [method] :as payload} message-id {:keys [db] :as cofx}]
(let [{:dapps/keys [permissions]} db]
(if (and (#{"eth_accounts" "eth_coinbase" "eth_sendTransaction" "eth_sign"
"eth_signTypedData" "personal_sign" "personal_ecRecover"} method)
(not (some #{"WEB3"} (get-in permissions [dapp-name :permissions]))))
(send-to-bridge {:type constants/web3-send-async-callback
:messageId message-id
:error "Denied"}
cofx)
(web3-send-async payload message-id cofx))))
(defn process-bridge-message
[message {:keys [db] :as cofx}]
(let [{:browser/keys [options browsers]} db
{:keys [browser-id]} options
browser (get browsers browser-id)
data (types/json->clj message)
{{:keys [url]} :navState :keys [type host permissions payload messageId]} data
{:keys [dapp? name]} browser
dapp-name (if dapp? name host)]
(cond
(and (= type constants/history-state-changed)
platform/ios?
(not= "about:blank" url))
(update-browser-history browser url false cofx)
(= type constants/web3-send-async)
(web3-send-async payload messageId cofx)
(= type constants/web3-send-async-read-only)
(web3-send-async-read-only dapp-name payload messageId cofx)
(= type constants/status-api-request)
(browser.permissions/process-permissions dapp-name permissions cofx))))
(defn handle-message-link [link cofx]
(if (utils.universal-links/universal-link? link)
(utils.universal-links/handle-url link cofx)
{:browser/show-browser-selection link}))
(re-frame/reg-fx
:browser/resolve-ens-multihash
(fn [{:keys [web3 registry ens-name cb]}]
(resolver/content web3 registry ens-name cb)))
(re-frame/reg-fx
:browser/send-to-bridge
(fn [{:keys [message webview]}]
(.sendToBridge webview (types/clj->json message))))
(re-frame/reg-fx
:browser/call-rpc
(fn [[payload callback]]
(status/call-rpc
(types/clj->json payload)
(fn [response]
(if (= "" response)
(do
(log/warn :web3-response-error)
(callback "web3-response-error" nil))
(callback nil (.parse js/JSON response)))))))
(re-frame/reg-fx
:browser/show-browser-selection
(fn [link]
(list-selection/browse link)))

View File

@ -1,6 +1,6 @@
(ns status-im.ui.screens.browser.db (ns status-im.browser.db
(:require-macros [status-im.utils.db :refer [allowed-keys]]) (:require [cljs.spec.alpha :as spec])
(:require [cljs.spec.alpha :as spec])) (:require-macros [status-im.utils.db :refer [allowed-keys]]))
(spec/def :browser/browser-id (spec/nilable string?)) (spec/def :browser/browser-id (spec/nilable string?))
(spec/def :browser/timestamp (spec/nilable int?)) (spec/def :browser/timestamp (spec/nilable int?))
@ -9,12 +9,16 @@
(spec/def :browser/error? (spec/nilable boolean?)) (spec/def :browser/error? (spec/nilable boolean?))
(spec/def :browser/history (spec/nilable vector?)) (spec/def :browser/history (spec/nilable vector?))
(spec/def :browser/history-index (spec/nilable int?)) (spec/def :browser/history-index (spec/nilable int?))
(spec/def :browser/unsafe? (spec/nilable boolean?))
(spec/def :browser/loading? (spec/nilable boolean?)) (spec/def :browser/loading? (spec/nilable boolean?))
(spec/def :browser/resolving? (spec/nilable boolean?)) (spec/def :browser/resolving? (spec/nilable boolean?))
(spec/def :browser/url-editing? (spec/nilable boolean?)) (spec/def :browser/url-editing? (spec/nilable boolean?))
(spec/def :browser/show-tooltip (spec/nilable keyword?)) (spec/def :browser/show-tooltip (spec/nilable keyword?))
(spec/def :browser/show-permission (spec/nilable map?)) (spec/def :browser/show-permission (spec/nilable map?))
(spec/def :browser/permissions-queue (spec/nilable any?))
(spec/def :browser/pending-permissions set?)
(spec/def :browser/allowed-permissions set?)
(spec/def :browser/requested-permissions set?)
(spec/def :browser/options (spec/def :browser/options
(spec/nilable (spec/nilable
@ -25,7 +29,9 @@
:browser/url-editing? :browser/url-editing?
:browser/show-tooltip :browser/show-tooltip
:browser/show-permission :browser/show-permission
:browser/permissions-queue :browser/pending-permissions
:browser/allowed-permissions
:browser/requested-permissions
:browser/error?]))) :browser/error?])))
(spec/def :browser/browser (spec/def :browser/browser

View File

@ -0,0 +1,125 @@
(ns status-im.browser.permissions
(:require [clojure.set :as set]
[status-im.constants :as constants]
[status-im.data-store.dapp-permissions :as dapp-permissions]
[status-im.i18n :as i18n]
[status-im.utils.ethereum.core :as ethereum]
[status-im.utils.handlers-macro :as handlers-macro]))
(def supported-permissions
{constants/dapp-permission-contact-code {:title (i18n/label :t/wants-to-access-profile)
:description (i18n/label :t/your-contact-code)
:icon :icons/profile-active}
constants/dapp-permission-web3 {:title (i18n/label :t/dapp-would-like-to-connect-wallet)
:description (i18n/label :t/allowing-authorizes-this-dapp)
:icon :icons/wallet-active}})
(defn get-pending-permissions [db]
(get-in db [:browser/options :pending-permissions]))
(defn remove-pending-permission [db pending-permission]
(update-in db [:browser/options :pending-permissions] disj pending-permission))
(defn get-allowed-permissions [db]
(get-in db [:browser/options :allowed-permissions]))
(defn get-requested-permissions [db]
(get-in db [:browser/options :requested-permissions]))
(defn add-allowed-permission [db allowed-permission]
(update-in db [:browser/options :allowed-permissions] conj allowed-permission))
(defn get-permissions-data [allowed-permissions cofx]
(let [account (get-in cofx [:db :account/account])]
(select-keys {constants/dapp-permission-contact-code (:public-key account)
constants/dapp-permission-web3 (ethereum/normalized-address
(:address account))}
(vec allowed-permissions))))
(defn send-permissions-data-to-bridge
"If there is granted permissions, return the data to the bridge
If no permission were granted and dapp requested web3 permission,
return `web3-permission-request-denied` message type
Otherwise do nothing"
;;TODO(yenda): this was the behavior of the code prior to refactoring
;;if this is not the intended behavior please create an issue for that
[{:keys [db] :as cofx}]
(let [allowed-permissions (get-allowed-permissions db)
requested-permissions (get-requested-permissions db)
new-db (update db :browser/options dissoc
:pending-permissions
:allowed-permissions
:requested-permissions)]
(cond
(not-empty allowed-permissions)
{:db new-db
:browser/send-to-bridge {:message {:type constants/status-api-success
:data (get-permissions-data allowed-permissions cofx)
:keys (vec allowed-permissions)}
:webview (:webview-bridge db)}}
(and (empty? allowed-permissions)
(requested-permissions constants/dapp-permission-web3))
{:db new-db
:browser/send-to-bridge {:message {:type constants/web3-permission-request-denied}
:webview (:webview-bridge db)}}
:else
{:db new-db})))
(defn update-dapp-permissions
[dapp-name {:keys [db]}]
(let [allowed-permissions-set (get-allowed-permissions db)
allowed-permissions {:dapp dapp-name
:permissions (vec allowed-permissions-set)}]
(when (not-empty allowed-permissions-set)
{:db (assoc-in db [:dapps/permissions dapp-name] allowed-permissions)
:data-store/tx [(dapp-permissions/save-dapp-permissions allowed-permissions)]})))
(defn process-next-permission
"Process next permission by removing it from pending permissions
and prompting user
if there is no pending permissions left, save all granted permissions
and return the result to the bridge"
[dapp-name {:keys [db] :as cofx}]
(let [pending-permissions (get-pending-permissions db)
next-permission (first pending-permissions)]
(if next-permission
{:db (-> db
(remove-pending-permission next-permission)
(assoc-in [:browser/options :show-permission]
{:requested-permission next-permission
:dapp-name dapp-name}))}
(handlers-macro/merge-fx cofx
{:db (assoc-in db [:browser/options :show-permission] nil)}
(update-dapp-permissions dapp-name)
(send-permissions-data-to-bridge)))))
(defn allow-permission
"Add permission to set of allowed permission and process next permission"
[dapp-name permission {:keys [db] :as cofx}]
(handlers-macro/merge-fx cofx
{:db (add-allowed-permission db permission)}
(process-next-permission dapp-name)))
(defn process-permissions
"Process the permissions requested by a dapp
If all supported permissions are already granted, return the result immediatly
to the bridge
Otherwise process the first permission which will prompt user"
[dapp-name requested-permissions cofx]
(let [requested-permissions-set (set requested-permissions)
current-dapp-permissions (get-in cofx [:db :dapps/permissions dapp-name :permissions])
current-dapp-permissions-set (set current-dapp-permissions)
supported-permissions-set (set (keys supported-permissions))
allowed-permissions (set/intersection requested-permissions-set
current-dapp-permissions-set)
pending-permissions (-> requested-permissions-set
(set/intersection supported-permissions-set)
(set/difference current-dapp-permissions-set))
new-cofx (update-in cofx [:db :browser/options] merge
{:pending-permissions pending-permissions
:allowed-permissions allowed-permissions
:requested-permissions requested-permissions-set})]
(if (empty? pending-permissions)
(send-permissions-data-to-bridge new-cofx)
(process-next-permission dapp-name new-cofx))))

View File

@ -7,6 +7,8 @@
[status-im.accounts.recover.core :as accounts.recover] [status-im.accounts.recover.core :as accounts.recover]
[status-im.accounts.update.core :as accounts.update] [status-im.accounts.update.core :as accounts.update]
[status-im.bootnodes.core :as bootnodes] [status-im.bootnodes.core :as bootnodes]
[status-im.browser.core :as browser]
[status-im.browser.permissions :as browser.permissions]
[status-im.data-store.core :as data-store] [status-im.data-store.core :as data-store]
[status-im.fleet.core :as fleet] [status-im.fleet.core :as fleet]
[status-im.hardwallet.core :as hardwallet] [status-im.hardwallet.core :as hardwallet]
@ -522,3 +524,120 @@
:hardwallet.ui/go-to-settings-button-pressed :hardwallet.ui/go-to-settings-button-pressed
(fn [_ _] (fn [_ _]
{:hardwallet/open-nfc-settings nil})) {:hardwallet/open-nfc-settings nil}))
(handlers/register-handler-fx
:hardwallet.ui/connect-info-button-pressed
(fn [cofx _]
(browser/open-url "https://hardwallet.status.im" cofx)))
;; browser module
(handlers/register-handler-fx
:browser.ui/browser-item-selected
(fn [cofx [_ browser-id]]
(browser/open-existing-browser browser-id cofx)))
(handlers/register-handler-fx
:browser.ui/url-input-pressed
(fn [cofx _]
(browser/update-browser-option :url-editing? true cofx)))
(handlers/register-handler-fx
:browser.ui/url-input-blured
(fn [cofx _]
(browser/update-browser-option :url-editing? false cofx)))
(handlers/register-handler-fx
:browser.ui/url-submitted
(fn [cofx [_ url]]
(browser/open-url-in-current-browser url cofx)))
(handlers/register-handler-fx
:browser.ui/message-link-pressed
(fn [cofx [_ link]]
(browser/handle-message-link link cofx)))
(handlers/register-handler-fx
:browser.ui/remove-browser-pressed
(fn [cofx [_ browser-id]]
(browser/remove-browser browser-id cofx)))
(handlers/register-handler-fx
:browser.ui/lock-pressed
(fn [cofx [_ secure?]]
(browser/update-browser-option :show-tooltip (if secure? :secure :not-secure) cofx)))
(handlers/register-handler-fx
:browser.ui/close-tooltip-pressed
(fn [cofx _]
(browser/update-browser-option :show-tooltip nil cofx)))
(handlers/register-handler-fx
:browser.ui/previous-page-button-pressed
(fn [cofx _]
(browser/navigate-to-previous-page cofx)))
(handlers/register-handler-fx
:browser.ui/next-page-button-pressed
(fn [cofx _]
(browser/navigate-to-next-page cofx)))
(handlers/register-handler-fx
:browser/navigation-state-changed
(fn [cofx [_ event error?]]
(browser/navigation-state-changed event error? cofx)))
(handlers/register-handler-fx
:browser/bridge-message-received
(fn [cofx [_ message]]
(browser/process-bridge-message message cofx)))
(handlers/register-handler-fx
:browser/error-occured
(fn [cofx _]
(browser/handle-browser-error cofx)))
(handlers/register-handler-fx
:browser/loading-started
(fn [cofx _]
(browser/update-browser-option :error? false cofx)))
(handlers/register-handler-fx
:browser.callback/resolve-ens-multihash-success
(fn [cofx [_ hash]]
(browser/resolve-ens-multihash-success hash cofx)))
(handlers/register-handler-fx
:browser.callback/resolve-ens-multihash-error
(fn [cofx _]
(browser/update-browser-option :resolving? false cofx)))
(handlers/register-handler-fx
:browser.callback/call-rpc
(fn [cofx [_ message]]
(browser/send-to-bridge message cofx)))
(handlers/register-handler-fx
:browser.permissions.ui/dapp-permission-allowed
(fn [cofx [_ dapp-name permission]]
(browser.permissions/allow-permission dapp-name permission cofx)))
(handlers/register-handler-fx
:browser.permissions.ui/dapp-permission-denied
(fn [cofx [_ dapp-name]]
(browser.permissions/process-next-permission dapp-name cofx)))
(handlers/register-handler-fx
:browser.ui/open-in-status-option-selected
(fn [cofx [_ url]]
(browser/open-url url cofx)))
(handlers/register-handler-fx
:browser.ui/open-dapp-button-pressed
(fn [cofx [_ dapp-url]]
(browser/open-url dapp-url cofx)))
(handlers/register-handler-fx
:browser.ui/dapp-url-submitted
(fn [cofx [_ dapp-url]]
(browser/open-url dapp-url cofx)))

View File

@ -7,7 +7,7 @@
[status-im.data-store.core :as data-store] [status-im.data-store.core :as data-store]
[status-im.data-store.realm.core :as realm] [status-im.data-store.realm.core :as realm]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.models.browser :as browser] [status-im.browser.core :as browser]
[status-im.models.contacts :as models.contacts] [status-im.models.contacts :as models.contacts]
[status-im.models.dev-server :as models.dev-server] [status-im.models.dev-server :as models.dev-server]
[status-im.protocol.core :as protocol] [status-im.protocol.core :as protocol]

View File

@ -1,195 +0,0 @@
(ns status-im.models.browser
(:require [re-frame.core :as re-frame]
[clojure.string :as string]
[status-im.constants :as constants]
[status-im.data-store.browser :as browser-store]
[status-im.data-store.dapp-permissions :as dapp-permissions]
[status-im.i18n :as i18n]
[status-im.js-dependencies :as dependencies]
[status-im.ui.screens.browser.default-dapps :as default-dapps]
[status-im.utils.http :as http]
[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]
[status-im.ui.screens.navigation :as navigation]))
(defn get-current-url [{:keys [history history-index]}]
(when (and history-index history)
(nth history history-index)))
(defn can-go-back? [{:keys [history-index]}]
(pos? history-index))
(defn can-go-forward? [{:keys [history-index history]}]
(< history-index (dec (count history))))
(defn check-if-dapp-in-list [{:keys [history history-index] :as browser}]
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))
dapp (first (filter #(= history-host (http/url-host (:dapp-url %))) (apply concat (mapv :data default-dapps/all))))]
(if dapp
(assoc browser :dapp? true :name (:name dapp))
(assoc browser :dapp? false :name (i18n/label :t/browser)))))
(defn check-if-phishing-url [{:keys [history history-index] :as browser}]
(let [history-host (http/url-host (try (nth history history-index) (catch js/Error _)))]
(assoc browser :unsafe? (dependencies/phishing-detect history-host))))
(defn update-browser-fx [browser {:keys [db now]}]
(let [updated-browser (-> (assoc browser :timestamp now)
(check-if-dapp-in-list)
(check-if-phishing-url))]
{: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 [browser url loading? cofx]
(when-not loading?
(let [history-index (:history-index browser)
history (:history browser)
history-url (try (nth history history-index) (catch js/Error _))]
(when (not= history-url url)
(let [slash? (= url (str history-url "/"))
new-history (if slash?
(assoc history history-index url)
(conj (subvec history 0 (inc history-index)) url))
new-index (if slash?
history-index
(dec (count new-history)))]
(update-browser-fx (assoc browser :history new-history :history-index new-index)
cofx))))))
(defn ens? [host]
(and (string? 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 navigate-to-browser
[{{:keys [view-id]} :db :as cofx}]
(if (= view-id :dapp-description)
(navigation/navigate-reset
{:index 1
:actions [{:routeName :home}
{:routeName :browser}]}
cofx)
(navigation/navigate-to-cofx :browser nil cofx)))
(defn update-new-browser-and-navigate
[host browser {:keys [db] :as cofx}]
(handlers-macro/merge-fx
cofx
{:db (assoc db :browser/options
{:browser-id (:browser-id browser)
:resolving? (ens? host)})}
(navigate-to-browser)
(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)
:icon :icons/profile-active}
constants/dapp-permission-web3 {:title (i18n/label :t/dapp-would-like-to-connect-wallet)
:description (i18n/label :t/allowing-authorizes-this-dapp)
:icon :icons/wallet-active}})
(defn update-dapp-permissions-fx [{:keys [db]} permissions]
{:db (-> db
(assoc-in [:browser/options :show-permission] nil)
(assoc-in [:dapps/permissions (:dapp permissions)] permissions))
:data-store/tx [(dapp-permissions/save-dapp-permissions permissions)]})
(defn request-permission [{:keys [dapp-name index requested-permissions permissions-allowed user-permissions
permissions-data]
:as params}
{:keys [db] :as cofx}]
;; iterate all requested permissions
(if (< index (count requested-permissions))
(let [requested-permission (get requested-permissions index)]
;; if requested permission exists and valid continue if not decline permission
(if (and requested-permission (get permissions requested-permission))
;; if permission already allowed go to next, if not, show confirmation dialog
(if ((set user-permissions) requested-permission)
{:dispatch [:next-dapp-permission params requested-permission permissions-data]}
{:db (assoc-in db [:browser/options :show-permission] {:requested-permission requested-permission
:params params})})
{:dispatch [:next-dapp-permission params]}))
(cond-> (update-dapp-permissions-fx cofx {:dapp dapp-name
:permissions (vec (set (concat (keys permissions-allowed)
user-permissions)))})
(not (zero? (count permissions-allowed)))
(assoc :send-to-bridge-fx [{:type constants/status-api-success
:data permissions-allowed
:keys (keys permissions-allowed)}
(:webview-bridge db)])
(and (zero? (count permissions-allowed)) (= constants/dapp-permission-web3 (first requested-permissions)))
(assoc :send-to-bridge-fx [{:type constants/web3-permission-request-denied}
(:webview-bridge db)])
true
(assoc :dispatch [:check-permissions-queue]))))
(defn next-permission [{:keys [params permission permissions-data]} cofx]
(request-permission
(cond-> params
true
(update :index inc)
(and permission permissions-data)
(assoc-in [:permissions-allowed permission] (get permissions-data permission)))
cofx))
(defn web3-send-async [{:keys [method] :as payload} message-id {:keys [db]}]
(if (or (= method constants/web3-send-transaction)
(= method constants/web3-personal-sign))
{:db (update-in db [:wallet :transactions-queue] conj {:message-id message-id :payload payload})
:dispatch [:check-dapps-transactions-queue]}
{:call-rpc [payload
#(re-frame/dispatch [:send-to-bridge
{:type constants/web3-send-async-callback
:messageId message-id
:error %1
:result %2}])]}))
(defn web3-send-async-read-only [dapp-name {:keys [method] :as payload} message-id {:keys [db] :as cofx}]
(let [{:dapps/keys [permissions]} db]
(if (and (#{"eth_accounts" "eth_coinbase" "eth_sendTransaction" "eth_sign"
"eth_signTypedData" "personal_sign" "personal_ecRecover"} method)
(not (some #{"WEB3"} (get-in permissions [dapp-name :permissions]))))
{:dispatch [:send-to-bridge
{:type constants/web3-send-async-callback
:messageId message-id
:error "Denied"}]}
(web3-send-async payload message-id cofx))))
(defn initialize-browsers
[{:keys [db all-stored-browsers]}]
(let [browsers (into {} (map #(vector (:browser-id %) %) all-stored-browsers))]
{:db (assoc db :browser/browsers browsers)}))
(defn initialize-dapp-permissions
[{:keys [db all-dapp-permissions]}]
(let [dapp-permissions (into {} (map #(vector (:dapp %) %) all-dapp-permissions))]
{:db (assoc db :dapps/permissions dapp-permissions)}))

View File

@ -1,6 +1,8 @@
(ns status-im.models.dev-server (ns status-im.models.dev-server
(:require [status-im.network.core :as network] (:require [clojure.string :as string]
[clojure.string :as string])) [status-im.browser.core :as browser]
[status-im.network.core :as network]
[status-im.utils.handlers-macro :as handlers-macro]))
(defn start-if-needed (defn start-if-needed
[{{:account/keys [account]} :db}] [{{:account/keys [account]} :db}]
@ -17,9 +19,10 @@
{:dev-server/respond [200 {:message "Pong!"}]}) {:dev-server/respond [200 {:message "Pong!"}]})
(defmethod process-request! [:POST "dapp" "open"] (defmethod process-request! [:POST "dapp" "open"]
[{{:keys [url]} :data}] [{{:keys [url]} :data cofx :cofx}]
{:dispatch [:open-url-in-browser url] (handlers-macro/merge-fx cofx
:dev-server/respond [200 {:message "URL has been opened."}]}) {:dev-server/respond [200 {:message "URL has been opened."}]}
(browser/open-url url)))
(defmethod process-request! [:POST "network" nil] (defmethod process-request! [:POST "network" nil]
[{:keys [cofx data]}] [{:keys [cofx data]}]

View File

@ -110,18 +110,18 @@
[second_param first_param])))) [second_param first_param]))))
(defn web3-error-callback [fx {:keys [webview-bridge]} {:keys [message-id]} message] (defn web3-error-callback [fx {:keys [webview-bridge]} {:keys [message-id]} message]
(assoc fx :send-to-bridge-fx [{:type constants/web3-send-async-callback (assoc fx :browser/send-to-bridge {:message {:type constants/web3-send-async-callback
:messageId message-id :messageId message-id
:error message} :error message}
webview-bridge])) :webview webview-bridge}))
(defn dapp-complete-transaction [id result method message-id webview] (defn dapp-complete-transaction [id result method message-id webview]
(cond-> {:send-to-bridge-fx [{:type constants/web3-send-async-callback (cond-> {:browser/send-to-bridge {:message {:type constants/web3-send-async-callback
:messageId message-id :messageId message-id
:result {:jsonrpc "2.0" :result {:jsonrpc "2.0"
:id (int id) :id (int id)
:result result}} :result result}}
webview] :webview webview}
:dispatch [:navigate-back]} :dispatch [:navigate-back]}
(= method constants/web3-personal-sign) (= method constants/web3-personal-sign)

View File

@ -31,7 +31,7 @@
(defn browse [link] (defn browse [link]
(show {:title (i18n/label :t/browsing-title) (show {:title (i18n/label :t/browsing-title)
:options [{:label (i18n/label :t/browsing-open-in-status) :options [{:label (i18n/label :t/browsing-open-in-status)
:action #(re-frame/dispatch [:open-url-in-browser link])} :action #(re-frame/dispatch [:browser.ui/open-in-status-option-selected link])}
{:label (i18n/label :t/browsing-open-in-web-browser) {:label (i18n/label :t/browsing-open-in-web-browser)
:action #(.openURL react/linking (http/normalize-url link))}] :action #(.openURL react/linking (http/normalize-url link))}]
:cancel-text (i18n/label :t/browsing-cancel)})) :cancel-text (i18n/label :t/browsing-cancel)}))
@ -39,5 +39,5 @@
(defn browse-dapp [link] (defn browse-dapp [link]
(show {:title (i18n/label :t/browsing-title) (show {:title (i18n/label :t/browsing-title)
:options [{:label (i18n/label :t/browsing-open-in-status) :options [{:label (i18n/label :t/browsing-open-in-status)
:action #(re-frame/dispatch [:open-url-in-browser link])}] :action #(re-frame/dispatch [:browser.ui/open-in-status-option-selected link])}]
:cancel-text (i18n/label :t/browsing-cancel)})) :cancel-text (i18n/label :t/browsing-cancel)}))

View File

@ -29,9 +29,7 @@
[components/separator] [components/separator]
[react/view add-new.styles/input-container [react/view add-new.styles/input-container
[react/text-input {:on-change-text #(reset! url-text %) [react/text-input {:on-change-text #(reset! url-text %)
:on-submit-editing #(do :on-submit-editing #(re-frame/dispatch [:browser.ui/dapp-url-submitted @url-text])
(re-frame/dispatch [:navigate-to :home])
(re-frame/dispatch [:open-url-in-browser @url-text]))
:placeholder (i18n/label :t/enter-url) :placeholder (i18n/label :t/enter-url)
:auto-capitalize :none :auto-capitalize :none
:auto-correct false :auto-correct false
@ -62,7 +60,7 @@
:icon :icons/address :icon :icons/address
:icon-opts {:color colors/blue} :icon-opts {:color colors/blue}
:accessibility-label :open-dapp-button :accessibility-label :open-dapp-button
:on-press #(re-frame/dispatch [:open-url-in-browser dapp-url])}] :on-press #(re-frame/dispatch [:browser.ui/open-dapp-button-pressed dapp-url])}]
[components/separator {:margin-left 72}]] [components/separator {:margin-left 72}]]
[react/view styles/description-container [react/view styles/description-container
[react/i18n-text {:style styles/gray-label :key :description}] [react/i18n-text {:style styles/gray-label :key :description}]

View File

@ -1,175 +0,0 @@
(ns status-im.ui.screens.browser.events
(:require [re-frame.core :as re-frame]
[status-im.constants :as constants]
[status-im.data-store.browser :as browser-store]
[status-im.models.browser :as model]
[status-im.native-module.core :as status]
[status-im.ui.components.list-selection :as list-selection]
status-im.ui.screens.browser.navigation
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.http :as http]
[status-im.utils.platform :as platform]
[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]
[status-im.utils.ethereum.resolver :as resolver]
[status-im.utils.ethereum.core :as ethereum]))
(re-frame/reg-fx
:browse
(fn [link]
(if (utils.universal-links/universal-link? link)
(utils.universal-links/open! link)
(list-selection/browse link))))
(re-frame/reg-fx
:call-rpc
(fn [[payload callback]]
(status/call-rpc
(types/clj->json payload)
(fn [response]
(if (= "" response)
(do
(log/warn :web3-response-error)
(callback "web3-response-error" nil))
(callback nil (.parse js/JSON response)))))))
(re-frame/reg-fx
:send-to-bridge-fx
(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
(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]}
cofx))))
(handlers/register-handler-fx
:send-to-bridge
(fn [cofx [_ message]]
{:send-to-bridge-fx [message (get-in cofx [:db :webview-bridge])]}))
(handlers/register-handler-fx
:open-browser
(fn [cofx [_ browser]]
(model/update-browser-and-navigate browser cofx)))
(handlers/register-handler-fx
:update-browser-on-nav-change
(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
(fn [{:keys [db]} [_ options]]
{:db (update db :browser/options merge options)}))
(handlers/register-handler-fx
:remove-browser
(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 (assoc browser :history-index history-index) cofx))
(handlers/register-handler-fx
:browser-nav-back
(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
(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
(fn [{:keys [db] :as cofx} [_ message]]
(let [{:browser/keys [options browsers]} db
{:keys [browser-id]} options
browser (get browsers browser-id)
data (types/json->clj message)
{{:keys [url]} :navState :keys [type host permissions payload messageId]} data
{:keys [dapp? name]} browser
dapp-name (if dapp? name host)]
(cond
(and (= type constants/history-state-changed) platform/ios? (not= "about:blank" url))
(model/update-browser-history-fx browser url false cofx)
(= type constants/web3-send-async)
(model/web3-send-async payload messageId cofx)
(= type constants/web3-send-async-read-only)
(model/web3-send-async-read-only dapp-name payload messageId cofx)
(= type constants/status-api-request)
{:db (update-in db [:browser/options :permissions-queue] conj {:dapp-name dapp-name
:permissions permissions})
:dispatch [:check-permissions-queue]}))))
(handlers/register-handler-fx
:check-permissions-queue
(fn [{:keys [db] :as cofx} _]
(let [{:keys [show-permission permissions-queue]} (:browser/options db)]
(when (and (nil? show-permission) (last permissions-queue))
(let [{:keys [dapp-name permissions]} (last permissions-queue)
{:account/keys [account]} db]
(handlers-macro/merge-fx
cofx
{:db (update-in db [:browser/options :permissions-queue] drop-last)}
(model/request-permission
{:dapp-name dapp-name
:index 0
:user-permissions (get-in db [:dapps/permissions dapp-name :permissions])
:requested-permissions permissions
:permissions-data {constants/dapp-permission-contact-code (:public-key account)
constants/dapp-permission-web3 (ethereum/normalized-address
(:address account))}})))))))
(handlers/register-handler-fx
:next-dapp-permission
(fn [cofx [_ params permission permissions-data]]
(model/next-permission {:params params
:permission permission
:permissions-data permissions-data}
cofx)))

View File

@ -1,6 +0,0 @@
(ns status-im.ui.screens.browser.navigation
(:require [status-im.ui.screens.navigation :as navigation]))
(defmethod navigation/preload-data! :browser
[db [_ _ options]]
(assoc db :browser/options options))

View File

@ -1,20 +1,18 @@
(ns status-im.ui.screens.browser.permissions.views (ns status-im.ui.screens.browser.permissions.views
(:require-macros [status-im.utils.views :as views]) (:require [re-frame.core :as re-frame]
(:require [status-im.ui.components.animation :as anim] [reagent.core :as reagent]
[status-im.ui.components.react :as react] [status-im.browser.permissions :as browser.permissions]
[status-im.ui.screens.browser.styles :as styles]
[re-frame.core :as re-frame]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.animation :as anim]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.colors :as colors] [status-im.ui.components.react :as react]
[reagent.core :as reagent] [status-im.ui.screens.browser.styles :as styles])
[status-im.models.browser :as model] (:require-macros [status-im.utils.views :as views]))
[status-im.ui.components.chat-icon.screen :as chat-icon.screen]))
(views/defview permissions-panel [{:keys [dapp? name] :as browser} {:keys [requested-permission params]}] (views/defview permissions-panel [{:keys [dapp? name dapp] :as browser} {:keys [requested-permission dapp-name]}]
(views/letsubs [dapp [:get-dapp-by-name name] (views/letsubs [bottom-anim-value (anim/create-value -354)
bottom-anim-value (anim/create-value -354)
alpha-value (anim/create-value 0) alpha-value (anim/create-value 0)
hide-panel #(anim/start hide-panel #(anim/start
(anim/parallel (anim/parallel
@ -27,8 +25,7 @@
(anim/timing alpha-value {:toValue 0.6 (anim/timing alpha-value {:toValue 0.6
:duration 500})]))} :duration 500})]))}
(let [_ (when-not requested-permission (js/setTimeout hide-panel 10)) (let [_ (when-not requested-permission (js/setTimeout hide-panel 10))
{:keys [dapp-name]} params {:keys [title description icon]} (get browser.permissions/supported-permissions requested-permission)]
{:keys [title description icon]} (get model/permissions requested-permission)]
[react/view styles/permissions-panel-container [react/view styles/permissions-panel-container
[react/animated-view {:style (styles/permissions-panel-background alpha-value)}] [react/animated-view {:style (styles/permissions-panel-background alpha-value)}]
[react/animated-view {:style (styles/permissions-panel bottom-anim-value)} [react/animated-view {:style (styles/permissions-panel bottom-anim-value)}
@ -55,11 +52,10 @@
[react/text {:style styles/permissions-panel-description-label} [react/text {:style styles/permissions-panel-description-label}
description] description]
[react/view {:flex-direction :row :margin-top 14} [react/view {:flex-direction :row :margin-top 14}
[components.common/button {:on-press #(re-frame/dispatch [:next-dapp-permission params]) [components.common/button {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-denied dapp-name])
:label (i18n/label :t/deny)}] :label (i18n/label :t/deny)}]
[react/view {:width 16}] [react/view {:width 16}]
[components.common/button {:on-press #(re-frame/dispatch [:next-dapp-permission params requested-permission [components.common/button {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-allowed dapp-name requested-permission])
(:permissions-data params)])
:label (i18n/label :t/allow)}]] :label (i18n/label :t/allow)}]]
;; TODO (andrey) will be in next PR ;; TODO (andrey) will be in next PR
#_[react/view {:flex-direction :row :margin-top 19} #_[react/view {:flex-direction :row :margin-top 19}

View File

@ -1,14 +1,12 @@
(ns status-im.ui.screens.browser.site-blocked.views (ns status-im.ui.screens.browser.site-blocked.views
(:require-macros [status-im.utils.views :as views]) (:require [re-frame.core :as re-frame]
(:require [reagent.core :as reagent]
[re-frame.core :as re-frame]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.components.button.view :as button]
[status-im.ui.screens.browser.site-blocked.styles :as styles]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
[status-im.ui.components.icons.vector-icons :as vector-icons])) [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.react :as react]
[status-im.ui.screens.browser.site-blocked.styles :as styles])
(:require-macros [status-im.utils.views :as views]))
(defn chat-link [] (defn chat-link []
[react/text {:on-press #(.openURL react/linking "status-im://chat/public/status") [react/text {:on-press #(.openURL react/linking "status-im://chat/public/status")
@ -30,7 +28,7 @@
[react/view styles/buttons-container [react/view styles/buttons-container
[components.common/button {:on-press (fn [] [components.common/button {:on-press (fn []
(let [handler (if can-go-back? (let [handler (if can-go-back?
:browser-nav-back :browser.ui/previous-page-button-pressed
:navigate-back)] :navigate-back)]
(re-frame/dispatch [handler]))) (re-frame/dispatch [handler])))
:label (i18n/label :t/browsing-site-blocked-go-back)}]]]]) :label (i18n/label :t/browsing-site-blocked-go-back)}]]]])

View File

@ -1,6 +1,6 @@
(ns status-im.ui.screens.browser.styles (ns status-im.ui.screens.browser.styles
(:require-macros [status-im.utils.styles :refer [defstyle defnstyle]]) (:require [status-im.ui.components.colors :as colors])
(:require [status-im.ui.components.colors :as colors])) (:require-macros [status-im.utils.styles :refer [defnstyle defstyle]]))
(def browser {:flex 1}) (def browser {:flex 1})

View File

@ -1,14 +1,28 @@
(ns status-im.ui.screens.browser.subs (ns status-im.ui.screens.browser.subs
(:require [re-frame.core :as re-frame])) (:require [re-frame.core :as re-frame]
[status-im.browser.core :as browser]))
(re-frame/reg-sub (re-frame/reg-sub
:browsers :browsers
(fn [db _] (fn [db _]
(:browser/browsers db))) (:browser/browsers db)))
(re-frame/reg-sub
:browser/browsers
:<- [:browsers]
:<- [:contacts/dapps-by-name]
(fn [[browsers dapps]]
(reduce (fn [acc [k {:keys [dapp? name] :as browser}]]
(cond-> (update acc k assoc
:url (browser/get-current-url browser)
:secure? (browser/secure? browser))
dapp? (assoc-in [k :dapp] (get dapps name))))
browsers
browsers)))
(re-frame/reg-sub (re-frame/reg-sub
:get-current-browser :get-current-browser
:<- [:get :browser/options] :<- [:get :browser/options]
:<- [:browsers] :<- [:browser/browsers]
(fn [[options browsers]] (fn [[options browsers]]
(get browsers (:browser-id options)))) (get browsers (:browser-id options))))

View File

@ -1,54 +1,43 @@
(ns status-im.ui.screens.browser.views (ns status-im.ui.screens.browser.views
(:require-macros [status-im.utils.slurp :refer [slurp]] (:require [cljs.reader :as reader]
[status-im.utils.views :as views])
(:require [clojure.string :as string]
[cljs.reader :as reader]
[reagent.core :as reagent]
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[reagent.core :as reagent]
[status-im.browser.core :as browser]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.ui.components.colors :as colors] [status-im.ui.components.colors :as colors]
[status-im.ui.components.react :as react]
[status-im.ui.components.react :as components]
[status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar.view]
[status-im.ui.components.webview-bridge :as components.webview-bridge]
[status-im.ui.components.icons.vector-icons :as icons] [status-im.ui.components.icons.vector-icons :as icons]
[status-im.ui.components.toolbar.actions :as actions] [status-im.ui.components.react :as react]
[status-im.ui.components.tooltip.views :as tooltip] [status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.styles :as components.styles] [status-im.ui.components.styles :as components.styles]
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.toolbar.view :as toolbar.view]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.ui.components.webview-bridge :as components.webview-bridge]
[status-im.ui.screens.browser.permissions.views :as permissions.views] [status-im.ui.screens.browser.permissions.views :as permissions.views]
[status-im.ui.screens.browser.site-blocked.views :as site-blocked.views] [status-im.ui.screens.browser.site-blocked.views :as site-blocked.views]
[status-im.ui.screens.browser.styles :as styles] [status-im.ui.screens.browser.styles :as styles]
[status-im.utils.js-resources :as js-res]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.models.browser :as model]
[status-im.utils.http :as http] [status-im.utils.http :as http]
[status-im.utils.platform :as platform])) [status-im.utils.js-resources :as js-res])
(:require-macros
[status-im.utils.slurp :refer [slurp]]
[status-im.utils.views :as views]))
(def browser-config (def browser-config
(reader/read-string (slurp "./src/status_im/utils/browser_config.edn"))) (reader/read-string (slurp "./src/status_im/utils/browser_config.edn")))
(defn toolbar-content [url {:keys [dapp? history history-index] :as browser} error? url-editing?] (defn toolbar-content [url {:keys [secure?] :as browser} url-editing?]
(let [url-text (atom url) (let [url-text (atom url)]
history-url (try (nth history history-index) (catch js/Error _))
secure? (or dapp? (and (not error?) (string/starts-with? history-url "https://")))]
[react/view [react/view
[react/view (styles/toolbar-content false) [react/view (styles/toolbar-content false)
[react/touchable-highlight {:on-press #(re-frame/dispatch [:update-browser-options [react/touchable-highlight {:on-press #(re-frame/dispatch [:browser.ui/lock-pressed secure?])}
{:show-tooltip (if secure? :secure :not-secure)}])}
(if secure? (if secure?
[icons/icon :icons/lock {:color colors/green}] [icons/icon :icons/lock {:color colors/green}]
[icons/icon :icons/lock-opened])] [icons/icon :icons/lock-opened])]
(if url-editing? (if url-editing?
[react/text-input {:on-change-text #(reset! url-text %) [react/text-input {:on-change-text #(reset! url-text %)
:on-blur #(re-frame/dispatch [:update-browser-options {:url-editing? false}]) :on-blur #(re-frame/dispatch [:browser.ui/url-input-blured])
:on-submit-editing #(do :on-submit-editing #(re-frame/dispatch [:browser.ui/url-submitted @url-text])
(re-frame/dispatch [:update-browser-options {:url-editing? false}])
(re-frame/dispatch [:update-browser-on-nav-change
browser
(http/normalize-and-decode-url @url-text)
false
false]))
:placeholder (i18n/label :t/enter-url) :placeholder (i18n/label :t/enter-url)
:auto-capitalize :none :auto-capitalize :none
:auto-correct false :auto-correct false
@ -56,7 +45,7 @@
:default-value url :default-value url
:ellipsize :end :ellipsize :end
:style styles/url-input}] :style styles/url-input}]
[react/touchable-highlight {:style {:flex 1} :on-press #(re-frame/dispatch [:update-browser-options {:url-editing? true}])} [react/touchable-highlight {:style {:flex 1} :on-press #(re-frame/dispatch [:browser.ui/url-input-pressed])}
[react/text {:style styles/url-text} (http/url-host url)]])]])) [react/text {:style styles/url-text} (http/url-host url)]])]]))
(defn toolbar [webview error? url browser browser-id url-editing?] (defn toolbar [webview error? url browser browser-id url-editing?]
@ -67,8 +56,8 @@
(.sendToBridge @webview "navigate-to-blank")) (.sendToBridge @webview "navigate-to-blank"))
(re-frame/dispatch [:navigate-back]) (re-frame/dispatch [:navigate-back])
(when error? (when error?
(re-frame/dispatch [:remove-browser browser-id]))))] (re-frame/dispatch [:browser.ui/remove-browser-pressed browser-id]))))]
[toolbar-content url browser error? url-editing?] [toolbar-content url browser url-editing?]
[toolbar.view/actions [{:icon :icons/wallet [toolbar.view/actions [{:icon :icons/wallet
:icon-opts {:color :black :icon-opts {:color :black
:accessibility-label :wallet-modal-button} :accessibility-label :wallet-modal-button}
@ -83,13 +72,6 @@
[react/text {:style styles/web-view-error-text} [react/text {:style styles/web-view-error-text}
(str desc)]])) (str desc)]]))
(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 error?]))))
(defn get-inject-js [url] (defn get-inject-js [url]
(when url (when url
(let [domain-name (nth (re-find #"^\w+://(www\.)?([^/:]+)" url) 2)] (let [domain-name (nth (re-find #"^\w+://(www\.)?([^/:]+)" url) 2)]
@ -97,13 +79,13 @@
(defn navigation [webview browser can-go-back? can-go-forward?] (defn navigation [webview browser can-go-back? can-go-forward?]
[react/view styles/toolbar [react/view styles/toolbar
[react/touchable-highlight {:on-press #(re-frame/dispatch [:browser-nav-back browser]) [react/touchable-highlight {:on-press #(re-frame/dispatch [:browser.ui/previous-page-button-pressed])
:disabled (not can-go-back?) :disabled (not can-go-back?)
:style (when-not can-go-back? styles/disabled-button) :style (when-not can-go-back? styles/disabled-button)
:accessibility-label :previou-page-button} :accessibility-label :previous-page-button}
[react/view [react/view
[icons/icon :icons/arrow-left]]] [icons/icon :icons/arrow-left]]]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:browser-nav-forward browser]) [react/touchable-highlight {:on-press #(re-frame/dispatch [:browser.ui/next-page-button-pressed])
:disabled (not can-go-forward?) :disabled (not can-go-forward?)
:style (merge styles/forward-button :style (merge styles/forward-button
(when-not can-go-forward? styles/disabled-button)) (when-not can-go-forward? styles/disabled-button))
@ -115,7 +97,7 @@
[icons/icon :icons/refresh]]]) [icons/icon :icons/refresh]]])
;; should-component-update is called only when component's props are changed, ;; should-component-update is called only when component's props are changed,
;; that's why it can't be used in `brwoser`, because `url` comes from subs ;; that's why it can't be used in `browser`, because `url` comes from subs
(views/defview browser-component (views/defview browser-component
[{:keys [webview error? url browser browser-id unsafe? can-go-back? [{:keys [webview error? url browser browser-id unsafe? can-go-back?
can-go-forward? url-editing? resolving? network-id address can-go-forward? url-editing? resolving? network-id address
@ -141,11 +123,10 @@
:bounces false :bounces false
:local-storage-enabled true :local-storage-enabled true
:render-error web-view-error :render-error web-view-error
:on-navigation-state-change #(on-navigation-change % browser error?) :on-navigation-state-change #(re-frame/dispatch [:browser/navigation-state-changed % error?])
:on-bridge-message #(re-frame/dispatch [:on-bridge-message %]) :on-bridge-message #(re-frame/dispatch [:browser/bridge-message-received %])
:on-load #(re-frame/dispatch [:update-browser-options {:error? false}]) :on-load #(re-frame/dispatch [:browser/loading-started])
:on-error #(re-frame/dispatch [:update-browser-options {:error? true :on-error #(re-frame/dispatch [:browser/error-occured])
:loading? false}])
:injected-on-start-loading-java-script (str (not opt-in?) js-res/web3 :injected-on-start-loading-java-script (str (not opt-in?) js-res/web3
(get-inject-js url) (get-inject-js url)
(if opt-in? (if opt-in?
@ -157,7 +138,7 @@
:injected-java-script js-res/webview-js}]) :injected-java-script js-res/webview-js}])
(when (or loading? resolving?) (when (or loading? resolving?)
[react/view styles/web-view-loading [react/view styles/web-view-loading
[components/activity-indicator {:animating true}]])] [react/activity-indicator {:animating true}]])]
[navigation webview browser can-go-back? can-go-forward?] [navigation webview browser can-go-back? can-go-forward?]
[permissions.views/permissions-anim-panel browser show-permission] [permissions.views/permissions-anim-panel browser show-permission]
(when show-tooltip (when show-tooltip
@ -165,7 +146,7 @@
(if (= show-tooltip :secure) (if (= show-tooltip :secure)
(i18n/label :t/browser-secure) (i18n/label :t/browser-secure)
(i18n/label :t/browser-not-secure)) (i18n/label :t/browser-not-secure))
#(re-frame/dispatch [:update-browser-options {:show-tooltip nil}])])]) #(re-frame/dispatch [:browser.ui/close-tooltip-pressed])])])
(views/defview browser [] (views/defview browser []
(views/letsubs [webview (atom nil) (views/letsubs [webview (atom nil)
@ -174,9 +155,9 @@
{:keys [error? loading? url-editing? show-tooltip show-permission resolving?]} [:get :browser/options] {:keys [error? loading? url-editing? show-tooltip show-permission resolving?]} [:get :browser/options]
rpc-url [:get :rpc-url] rpc-url [:get :rpc-url]
network-id [:get-network-id]] network-id [:get-network-id]]
(let [can-go-back? (model/can-go-back? browser) (let [can-go-back? (browser/can-go-back? browser)
can-go-forward? (model/can-go-forward? browser) can-go-forward? (browser/can-go-forward? browser)
url (model/get-current-url browser) url (browser/get-current-url browser)
opt-in? (:web3-opt-in? settings)] opt-in? (:web3-opt-in? settings)]
[browser-component {:webview webview [browser-component {:webview webview
:dapp? dapp? :dapp? dapp?

View File

@ -168,7 +168,7 @@
(defn text-message (defn text-message
[{:keys [content timestamp-str group-chat outgoing] :as message}] [{:keys [content timestamp-str group-chat outgoing] :as message}]
[message-view message [message-view message
(let [parsed-text (cached-parse-text content :browse-link-from-message) (let [parsed-text (cached-parse-text content :browser.ui/message-link-pressed)
ref (reagent/atom nil) ref (reagent/atom nil)
collapsible? (should-collapse? content group-chat) collapsible? (should-collapse? content group-chat)
collapsed? (reagent/atom collapsible?) collapsed? (reagent/atom collapsible?)

View File

@ -1,14 +1,16 @@
(ns status-im.ui.screens.contacts.subs (ns status-im.ui.screens.contacts.subs
(:require [re-frame.core :refer [reg-sub subscribe]] (:require [re-frame.core :refer [reg-sub subscribe]]
[status-im.utils.contacts :as utils.contacts]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
[status-im.utils.identicon :as identicon] [status-im.utils.identicon :as identicon]))
[status-im.utils.contacts :as utils.contacts]))
(reg-sub :get-current-contact-identity :contacts/identity) (reg-sub :get-current-contact-identity :contacts/identity)
(reg-sub :get-contacts :contacts/contacts) (reg-sub :get-contacts :contacts/contacts)
(reg-sub :get-dapps :contacts/dapps) (reg-sub :get-dapps
(fn [db]
(:contacts/dapps db)))
(reg-sub :get-current-contact (reg-sub :get-current-contact
:<- [:get-contacts] :<- [:get-contacts]
@ -70,10 +72,17 @@
(get all-contacts identity') (get all-contacts identity')
(utils.contacts/whisper-id->new-contact identity'))))) (utils.contacts/whisper-id->new-contact identity')))))
(reg-sub :get-dapp-by-name (reg-sub :contacts/dapps-by-name
:<- [:get-dapps] :<- [:all-dapps]
(fn [dapps [_ name]] (fn [dapps]
(first (filter #(= (:name %) name) (apply concat (map :data dapps)))))) (reduce (fn [dapps-by-name category]
(merge dapps-by-name
(reduce (fn [acc {:keys [name] :as dapp}]
(assoc acc name dapp))
{}
(:data category))))
{}
dapps)))
(reg-sub :get-contact-name-by-identity (reg-sub :get-contact-name-by-identity
:<- [:get-contacts] :<- [:get-contacts]

View File

@ -15,7 +15,7 @@
status-im.ui.screens.profile.db status-im.ui.screens.profile.db
status-im.ui.screens.network-settings.db status-im.ui.screens.network-settings.db
status-im.ui.screens.offline-messaging-settings.db status-im.ui.screens.offline-messaging-settings.db
status-im.ui.screens.browser.db status-im.browser.db
status-im.ui.screens.add-new.db status-im.ui.screens.add-new.db
status-im.ui.screens.add-new.new-public-chat.db)) status-im.ui.screens.add-new.new-public-chat.db))

View File

@ -2,13 +2,10 @@
(:require status-im.events (:require status-im.events
status-im.chat.events status-im.chat.events
status-im.dev-server.events status-im.dev-server.events
[status-im.models.contacts :as models.contacts]
status-im.ui.screens.add-new.events status-im.ui.screens.add-new.events
status-im.ui.screens.add-new.new-chat.events status-im.ui.screens.add-new.new-chat.events
status-im.ui.screens.group.chat-settings.events status-im.ui.screens.group.chat-settings.events
status-im.ui.screens.group.events status-im.ui.screens.group.events
[status-im.ui.screens.navigation :as navigation]
[status-im.utils.dimensions :as dimensions]
status-im.utils.universal-links.events status-im.utils.universal-links.events
status-im.web3.events status-im.web3.events
status-im.ui.screens.add-new.new-chat.navigation status-im.ui.screens.add-new.new-chat.navigation
@ -25,21 +22,17 @@
status-im.ui.screens.wallet.collectibles.cryptostrikers.events status-im.ui.screens.wallet.collectibles.cryptostrikers.events
status-im.ui.screens.wallet.collectibles.etheremon.events status-im.ui.screens.wallet.collectibles.etheremon.events
status-im.ui.screens.wallet.collectibles.superrare.events status-im.ui.screens.wallet.collectibles.superrare.events
status-im.ui.screens.browser.events
status-im.utils.keychain.events status-im.utils.keychain.events
[re-frame.core :as re-frame] [re-frame.core :as re-frame]
[status-im.hardwallet.core :as hardwallet]
[status-im.native-module.core :as status] [status-im.native-module.core :as status]
[status-im.ui.components.permissions :as permissions]
[status-im.transport.core :as transport]
[status-im.transport.inbox :as inbox] [status-im.transport.inbox :as inbox]
[status-im.ui.screens.db :refer [app-db]] [status-im.ui.components.permissions :as permissions]
[status-im.utils.datetime :as time] [status-im.utils.dimensions :as dimensions]
[status-im.utils.random :as random]
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro] [status-im.utils.handlers-macro :as handlers-macro]
[status-im.utils.http :as http] [status-im.utils.http :as http]
[status-im.utils.utils :as utils] [status-im.utils.utils :as utils]))
[status-im.hardwallet.core :as hardwallet]))
(defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}] (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}]
(let [on-success #(re-frame/dispatch (success-event-creator %)) (let [on-success #(re-frame/dispatch (success-event-creator %))

View File

@ -22,7 +22,7 @@
[toolbar/actions [{:icon :icons/info [toolbar/actions [{:icon :icons/info
:icon-opts {:color :black :icon-opts {:color :black
:accessibility-label :hardwallet-connect-info-button} :accessibility-label :hardwallet-connect-info-button}
:handler #(re-frame/dispatch [:open-url-in-browser "https://hardwallet.status.im"])}]]] :handler #(re-frame/dispatch [:hardwallet.ui/connect-info-button-pressed])}]]]
[react/view styles/hardwallet-connect [react/view styles/hardwallet-connect
[react/view styles/hardwallet-card-image-container [react/view styles/hardwallet-card-image-container
[react/image {:source (:hardwallet-card resources/ui) [react/image {:source (:hardwallet-card resources/ui)

View File

@ -3,6 +3,6 @@
(re-frame/reg-sub :home-items (re-frame/reg-sub :home-items
:<- [:get-active-chats] :<- [:get-active-chats]
:<- [:browsers] :<- [:browser/browsers]
(fn [[chats browsers]] (fn [[chats browsers]]
(sort-by #(-> % second :timestamp) > (merge chats browsers)))) (sort-by #(-> % second :timestamp) > (merge chats browsers))))

View File

@ -39,7 +39,9 @@
(views/defview home-list-item [[home-item-id home-item]] (views/defview home-list-item [[home-item-id home-item]]
(views/letsubs [swiped? [:delete-swipe-position home-item-id]] (views/letsubs [swiped? [:delete-swipe-position home-item-id]]
(let [delete-action (if (:chat-id home-item) :remove-chat-and-navigate-home :remove-browser) (let [delete-action (if (:chat-id home-item)
:remove-chat-and-navigate-home
:browser.ui/remove-browser-pressed)
inner-item-view (if (:chat-id home-item) inner-item-view (if (:chat-id home-item)
inner-item/home-list-chat-item-inner-view inner-item/home-list-chat-item-inner-view
inner-item/home-list-browser-item-inner-view) inner-item/home-list-browser-item-inner-view)

View File

@ -18,7 +18,7 @@
[status-im.ui.components.icons.vector-icons :as vector-icons] [status-im.ui.components.icons.vector-icons :as vector-icons]
[status-im.ui.components.chat-icon.screen :as chat-icon.screen] [status-im.ui.components.chat-icon.screen :as chat-icon.screen]
[status-im.ui.components.common.common :as components.common] [status-im.ui.components.common.common :as components.common]
[status-im.models.browser :as model])) [status-im.browser.core :as browser]))
(defview command-short-preview [message] (defview command-short-preview [message]
(letsubs [id->command [:get-id->command]] (letsubs [id->command [:get-id->command]]
@ -105,10 +105,8 @@
[message-content-text last-message] [message-content-text last-message]
[unviewed-indicator chat-id]]]]]))) [unviewed-indicator chat-id]]]]])))
(defview home-list-browser-item-inner-view [{:keys [name] :as browser}] (defn home-list-browser-item-inner-view [{:keys [dapp url name browser-id] :as browser}]
(letsubs [dapp [:get-dapp-by-name name] [react/touchable-highlight {:on-press #(re-frame/dispatch [:browser.ui/browser-item-selected browser-id])}
url (model/get-current-url browser)]
[react/touchable-highlight {:on-press #(re-frame/dispatch [:open-browser browser])}
[react/view styles/chat-container [react/view styles/chat-container
[react/view styles/chat-icon-container [react/view styles/chat-icon-container
(if dapp (if dapp
@ -128,4 +126,4 @@
[react/text {:style styles/last-message-text [react/text {:style styles/last-message-text
:accessibility-label :chat-url-text :accessibility-label :chat-url-text
:number-of-lines 1} :number-of-lines 1}
(or url (i18n/label :t/dapp))]]]]]])) (or url (i18n/label :t/dapp))]]]]]])

View File

@ -3,7 +3,8 @@
[status-im.utils.handlers :as handlers] [status-im.utils.handlers :as handlers]
[status-im.utils.ethereum.erc721 :as erc721] [status-im.utils.ethereum.erc721 :as erc721]
[status-im.utils.ethereum.tokens :as tokens] [status-im.utils.ethereum.tokens :as tokens]
[status-im.utils.money :as money])) [status-im.utils.money :as money]
[status-im.browser.core :as browser]))
(defmulti load-collectible-fx (fn [symbol _] symbol)) (defmulti load-collectible-fx (fn [symbol _] symbol))
@ -64,5 +65,5 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:open-collectible-in-browser :open-collectible-in-browser
(fn [_ [_ data]] (fn [cofx [_ url]]
{:dispatch [:open-url-in-browser data]})) (browser/open-url url cofx)))

View File

@ -39,7 +39,7 @@
(defn handle-browse [url cofx] (defn handle-browse [url cofx]
(log/info "universal-links: handling browse " url) (log/info "universal-links: handling browse " url)
{:browse url}) {:browser/show-browser-selection url})
(defn handle-public-chat [public-chat cofx] (defn handle-public-chat [public-chat cofx]
(log/info "universal-links: handling public chat " public-chat) (log/info "universal-links: handling public chat " public-chat)

View File

@ -0,0 +1,127 @@
(ns status-im.test.browser.core
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.browser.core :as browser]
[status-im.utils.types :as types]
[status-im.utils.handlers-macro :as handlers-macro]))
(defn has-navigated-to-browser? [result]
(and (= (get result :status-im.ui.screens.navigation/navigate-to)
:browser)
(= (get-in result [:db :view-id])
:browser)))
(defn has-wrong-properties?
[result dapp-url expected-browser]
(let [browser (get-in result [:db :browser/browsers dapp-url])]
(reduce (fn [acc k]
(if (= (k browser)
(k expected-browser))
acc
(conj acc [k (str "was expecting " (k expected-browser) " got " (k browser))])))
nil
(keys expected-browser))))
(deftest browser-test
(let [dapp1-url "cryptokitties.co"
dapp2-url "http://test2.com"]
(testing "user opens a dapp"
(let [result-open (browser/open-url dapp1-url {:now 1})]
(is (= dapp1-url (get-in result-open [:db :browser/options :browser-id]))
"browser-id should be dapp1-url")
(is (has-navigated-to-browser? result-open)
"should navigate to :browser")
(is (not (has-wrong-properties? result-open
dapp1-url
{:browser-id "cryptokitties.co"
:history-index 0
:history ["http://cryptokitties.co"]
:dapp? true
:name "CryptoKitties"
:timestamp 1}))
"some properties of the browser are not correct")
(testing "then a second dapp"
(let [result-open-2 (browser/open-url dapp2-url {:db (:db result-open)
:now 2})
dapp2-host "test2.com"]
(is (= dapp2-host (get-in result-open-2 [:db :browser/options :browser-id]))
"browser-id should be dapp2 host")
(is (has-navigated-to-browser? result-open-2)
"should navigate to :browser")
(is (not (has-wrong-properties? result-open-2
dapp2-host
{:browser-id "test2.com"
:history-index 0
:history ["http://test2.com"]
:dapp? false
:timestamp 2}))
"some properties of the browser are not correct")
(testing "then removes the second dapp"
(let [result-remove-2 (browser/remove-browser dapp2-host {:db (:db result-open-2)})]
(is (= #{dapp1-url}
(set (keys (get-in result-remove-2 [:db :browser/browsers]))))
"the second dapp shouldn't be in the browser list anymore")))))
(testing "then opens the dapp again"
(let [result-open-existing (browser/open-existing-browser dapp1-url {:db (:db result-open)
:now 2})
dapp1-url2 (str "http://" dapp1-url "/nav2")
browser (get-in result-open-existing [:db :browser/browsers dapp1-url])]
(is (not (has-wrong-properties? result-open-existing
dapp1-url
{:browser-id "cryptokitties.co"
:history-index 0
:history ["http://cryptokitties.co"]
:dapp? true
:name "CryptoKitties"
:timestamp 2}))
"some properties of the browser are not correct")
(is (nil? (browser/navigate-to-next-page result-open-existing))
"nothing should happen if user tries to navigate to next page")
(is (nil? (browser/navigate-to-previous-page result-open-existing))
"nothing should happen if user tries to navigate to previous page")
(testing "then navigates to a new url in the dapp"
(let [result-navigate (browser/navigation-state-changed
(clj->js {"url" dapp1-url2
"loading" false})
false
{:db (:db result-open-existing)
:now 4})]
(is (not (has-wrong-properties? result-navigate
dapp1-url
{:browser-id "cryptokitties.co"
:history-index 1
:history ["http://cryptokitties.co" dapp1-url2]
:dapp? true
:name "CryptoKitties"
:timestamp 4}))
"some properties of the browser are not correct")
(testing "then navigates to previous page"
(let [result-previous (browser/navigate-to-previous-page {:db (:db result-navigate)
:now 5})]
(is (not (has-wrong-properties? result-previous
dapp1-url
{:browser-id "cryptokitties.co"
:history-index 0
:history ["http://cryptokitties.co" dapp1-url2]
:dapp? true
:name "CryptoKitties"
:timestamp 5}))
"some properties of the browser are not correct")
(testing "then navigates to next page")
(let [result-next (browser/navigate-to-next-page {:db (:db result-previous)
:now 6})]
(is (not (has-wrong-properties? result-next
dapp1-url
{:browser-id "cryptokitties.co"
:history-index 1
:history ["http://cryptokitties.co" dapp1-url2]
:dapp? true
:name "CryptoKitties"
:timestamp 6}))
"some properties of the browser are not correct"))))))))))))

View File

@ -1,213 +0,0 @@
(ns status-im.test.browser.events
(:require [cljs.test :refer-macros [deftest is testing]]
[day8.re-frame.test :refer-macros [run-test-sync]]
[status-im.init.core :as init]
status-im.ui.screens.db
status-im.ui.screens.subs
[re-frame.core :as re-frame]
[status-im.models.browser :as model]
[status-im.utils.types :as types]
[status-im.utils.handlers :as handlers]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.models.browser :as browser]))
(defn test-fixtures []
(re-frame/reg-fx :init/init-store #())
(re-frame/reg-fx :browse #())
(re-frame/reg-fx :data-store/tx #())
(re-frame/reg-cofx
:data-store/all-browsers
(fn [coeffects _]
(assoc coeffects :all-stored-browsers [])))
(re-frame/reg-cofx
:data-store/all-dapp-permissions
(fn [coeffects _]
(assoc coeffects :all-dapp-permissions [])))
(re-frame/reg-fx :send-to-bridge-fx #())
(re-frame/reg-fx
:show-dapp-permission-confirmation-fx
(fn [[permission {:keys [dapp-name permissions-data index] :as params}]]
(if (and (= dapp-name "test.com") (#{0 1} index))
(re-frame/dispatch [:next-dapp-permission params permission permissions-data])
(re-frame/dispatch [:next-dapp-permission params]))))
(handlers/register-handler-fx
[(re-frame/inject-cofx :data-store/all-browsers)
(re-frame/inject-cofx :data-store/all-dapp-permissions)]
:initialize-test
(fn [cofx [_]]
(handlers-macro/merge-fx cofx
(init/initialize-app-db)
(browser/initialize-browsers)
(browser/initialize-dapp-permissions)))))
(deftest browser-events
(run-test-sync
(test-fixtures)
(re-frame/dispatch [:initialize-test])
(let [browsers (re-frame/subscribe [:browsers])
dapp1-url "cryptokitties.co"
dapp2-url "http://test2.com"]
(testing "open and remove dapps"
(is (zero? (count @browsers)))
(re-frame/dispatch [:open-url-in-browser dapp1-url])
(is (= 1 (count @browsers)))
(re-frame/dispatch [:open-url-in-browser dapp2-url])
(is (= 2 (count @browsers)))
(let [browser1 (first (vals @browsers))
browser2 (second (vals @browsers))]
(is (and (:dapp? browser1)
(not (:dapp? browser2))))
(is (and (zero? (:history-index browser1))
(zero? (:history-index browser2))))
(is (and (= [(str "http://" dapp1-url) (:history browser1)])
(= [dapp2-url] (:history browser2)))))
(re-frame/dispatch [:remove-browser dapp1-url])
(is (= 1 (count @browsers))))
(testing "navigate dapp"
(re-frame/dispatch [:open-browser (first (vals @browsers))])
(let [browser (re-frame/subscribe [:get-current-browser])
dapp2-url2 (str dapp2-url "/nav2")
dapp2-url3 (str dapp2-url "/nav3")]
(is (zero? (:history-index @browser)))
(is (= [dapp2-url] (:history @browser)))
(is (and (not (model/can-go-back? @browser))
(not (model/can-go-forward? @browser))))
(re-frame/dispatch [:browser-nav-back])
(re-frame/dispatch [:browser-nav-forward])
(re-frame/dispatch [:update-browser-on-nav-change @browser dapp2-url2 false])
(is (= 1 (:history-index @browser)))
(is (= [dapp2-url dapp2-url2] (:history @browser)))
(is (and (model/can-go-back? @browser)
(not (model/can-go-forward? @browser))))
(re-frame/dispatch [:browser-nav-back @browser])
(is (zero? (:history-index @browser)))
(is (= [dapp2-url dapp2-url2] (:history @browser)))
(is (and (not (model/can-go-back? @browser))
(model/can-go-forward? @browser)))
(re-frame/dispatch [:update-browser-on-nav-change @browser dapp2-url3 false])
(is (= 1 (:history-index @browser)))
(is (= [dapp2-url dapp2-url3] (:history @browser)))
(re-frame/dispatch [:browser-nav-back @browser])
(is (zero? (:history-index @browser)))
(is (= [dapp2-url dapp2-url3] (:history @browser)))
(re-frame/dispatch [:browser-nav-forward @browser])
(is (= 1 (:history-index @browser)))
(is (= [dapp2-url dapp2-url3] (:history @browser))))))
(let [dapps-permissions (re-frame/subscribe [:get :dapps/permissions])
dapp-name "test.com"
dapp-name2 "test2.org"]
(testing "dapps permissions"
(is (zero? (count @dapps-permissions)))
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]})
nil nil])
(re-frame/dispatch [:next-dapp-permission
{:dapp-name dapp-name
:index 0
:requested-permissions ["FAKE_PERMISSION"]
:permissions-data "Data"}])
(is (= {:dapp dapp-name
:permissions []}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE"]})
nil nil])
(re-frame/dispatch [:next-dapp-permission
{:dapp-name dapp-name
:index 0
:requested-permissions ["CONTACT_CODE"]
:permissions-data {"CONTACT_CODE" "Data"}}
"CONTACT_CODE"
{"CONTACT_CODE" "Data"}])
(is (= 1 (count @dapps-permissions)))
(is (= {:dapp dapp-name
:permissions ["CONTACT_CODE"]}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE" "FAKE_PERMISSION"]})
nil nil])
(is (= 1 (count @dapps-permissions)))
(is (= {:dapp dapp-name
:permissions ["CONTACT_CODE"]}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]})
nil nil])
(is (= 1 (count @dapps-permissions)))
(is (= {:dapp dapp-name
:permissions ["CONTACT_CODE"]}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name2
:permissions ["CONTACT_CODE"]})
nil nil])
(re-frame/dispatch [:next-dapp-permission
{:dapp-name dapp-name2
:index 0
:requested-permissions ["CONTACT_CODE" "FAKE_PERMISSION"]
:permissions-data "Data"}])
(is (= 2 (count @dapps-permissions)))
(is (= {:dapp dapp-name2
:permissions []}
(get @dapps-permissions dapp-name2)))))))

View File

@ -0,0 +1,87 @@
(ns status-im.test.browser.permissions
(:require [cljs.test :refer-macros [deftest is testing]]
[status-im.browser.permissions :as permissions]
[status-im.utils.types :as types]
[status-im.utils.handlers-macro :as handlers-macro]
[status-im.browser.core :as browser]))
(deftest permissions-test
(let [dapp-name "test.com"
dapp-name2 "test2.org"
cofx {:db (assoc-in (:db (browser/open-url dapp-name {}))
[:account/account :public-key] "public-key")}]
(testing "dapps permissions are initialized"
(is (zero? (count (get-in cofx [:db :dapps/permissions]))))
(is (= dapp-name (get-in cofx [:db :browser/options :browser-id]))))
(testing "receiving an unsupported permission"
(is (nil? (:browser/send-to-bridge (browser/process-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]})
cofx)))
"nothing should happen"))
(testing "receiving a supported permission and an unsupported one"
(let [result-ask (browser/process-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE" "FAKE_PERMISSION"]})
cofx)]
(is (= (get-in result-ask [:db :browser/options :show-permission])
{:requested-permission "CONTACT_CODE", :dapp-name "test.com"}))
(is (zero? (count (get-in result-ask [:db :dapps/permissions]))))
(testing "then user accepts the supported permission"
(let [accept-result (permissions/allow-permission dapp-name "CONTACT_CODE" {:db (:db result-ask)})]
(is (= (get-in accept-result [:browser/send-to-bridge :message])
{:type "status-api-success"
:data {"CONTACT_CODE" "public-key"}
:keys ["CONTACT_CODE"]})
"the data should have been sent to the bridge")
(is (= (get-in accept-result [:db :dapps/permissions])
{"test.com" {:dapp "test.com", :permissions ["CONTACT_CODE"]}})
"the dapp should now have CONTACT_CODE permission")
(testing "then dapps asks for permission again"
(let [result-ask-again (browser/process-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE"]})
{:db (:db accept-result)})]
(is (= (get-in result-ask-again
[:browser/send-to-bridge :message])
{:type "status-api-success"
:data {"CONTACT_CODE" "public-key"}
:keys ["CONTACT_CODE"]})
"the response should be immediatly sent to the bridge")))
(testing "then user switch to another dapp that asks for permissions"
(let [new-dapp (browser/open-url dapp-name2 {:db (:db accept-result)})
result-ask2 (browser/process-bridge-message (types/clj->json {:type "status-api-request"
:host dapp-name2
:permissions ["CONTACT_CODE" "FAKE_PERMISSION"]})
{:db (:db new-dapp)})]
(is (= (get-in result-ask2 [:db :dapps/permissions])
{"test.com" {:dapp "test.com", :permissions ["CONTACT_CODE"]}})
"there should only be permissions for dapp-name at that point")
(is (nil? (get-in result-ask2
[:browser/send-to-bridge :message]))
"no message should be sent to the bridge")
(testing "then user accepts permission for dapp-name2"
(let [accept-result2 (permissions/allow-permission dapp-name2 "CONTACT_CODE" {:db (:db result-ask2)})]
(is (= (get-in accept-result2 [:db :dapps/permissions])
{"test.com" {:dapp "test.com" :permissions ["CONTACT_CODE"]}
"test2.org" {:dapp "test2.org" :permissions ["CONTACT_CODE"]}})
"there should be permissions for both dapps now")
(is (= (get-in accept-result2
[:browser/send-to-bridge :message])
{:type "status-api-success"
:data {"CONTACT_CODE" "public-key"}
:keys ["CONTACT_CODE"]})
"the response should be sent to the bridge")))))
(testing "then user refuses the permission"
(let [result-refuse (permissions/process-next-permission dapp-name {:db (:db result-ask)})]
(is (zero? (count (get-in result-refuse [:db :dapps/permissions])))
"no permissions should be granted")
(is (nil? (get-in result-refuse [:browser/send-to-bridge :message]))
"no message should be sent to bridge")))))))))

View File

@ -4,7 +4,8 @@
[status-im.test.contacts.events] [status-im.test.contacts.events]
[status-im.test.contacts.subs] [status-im.test.contacts.subs]
[status-im.test.data-store.realm.core] [status-im.test.data-store.realm.core]
[status-im.test.browser.events] [status-im.test.browser.core]
[status-im.test.browser.permissions]
[status-im.test.wallet.subs] [status-im.test.wallet.subs]
[status-im.test.wallet.transactions.subs] [status-im.test.wallet.transactions.subs]
[status-im.test.wallet.transactions.views] [status-im.test.wallet.transactions.views]
@ -114,4 +115,5 @@
'status-im.test.accounts.recover.core 'status-im.test.accounts.recover.core
'status-im.test.ui.screens.currency-settings.models 'status-im.test.ui.screens.currency-settings.models
'status-im.test.ui.screens.wallet.db 'status-im.test.ui.screens.wallet.db
'status-im.test.browser.events) 'status-im.test.browser.core
'status-im.test.browser.permissions)

View File

@ -12,7 +12,7 @@
(is (= :my-profile (is (= :my-profile
(get-in (models/handle-qr-code "0x04e1433c1a8ad71280e6d4b1814aa3958ba6eb451da47ea1d4a4bfc4a04969c445548f3bd9d40fa7e4356aa62075b4d7615179ef1332f1d6a7c59b96c4ab8e04c1" cofx) [:db :view-id])))) (get-in (models/handle-qr-code "0x04e1433c1a8ad71280e6d4b1814aa3958ba6eb451da47ea1d4a4bfc4a04969c445548f3bd9d40fa7e4356aa62075b4d7615179ef1332f1d6a7c59b96c4ab8e04c1" cofx) [:db :view-id]))))
(testing "handle universal link" (testing "handle universal link"
(is (= (:browse (models/handle-qr-code "status-im://browse/www.cryptokitties.co" cofx)) (is (= (:browser/show-browser-selection (models/handle-qr-code "status-im://browse/www.cryptokitties.co" cofx))
"status-im://browse/www.cryptokitties.co"))) "status-im://browse/www.cryptokitties.co")))
(testing "handle invalid qr code" (testing "handle invalid qr code"
(is (:utils/show-popup (models/handle-qr-code "a random string" cofx))))) (is (:utils/show-popup (models/handle-qr-code "a random string" cofx)))))

View File

@ -25,7 +25,7 @@
(testing "it open the dapps" (testing "it open the dapps"
(is (is
(= "status-im://browse/www.cryptokitties.co" (= "status-im://browse/www.cryptokitties.co"
(:browse (links/handle-url "status-im://browse/www.cryptokitties.co" (:browser/show-browser-selection (links/handle-url "status-im://browse/www.cryptokitties.co"
{:db db})))))) {:db db}))))))
(testing "a user profile link" (testing "a user profile link"
(testing "it loads the profile" (testing "it loads the profile"