From 8459fef358a05f8ae278e8f8b4f9758f57efaf2e Mon Sep 17 00:00:00 2001 From: yenda Date: Fri, 21 Sep 2018 15:41:40 +0200 Subject: [PATCH] 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 --- src/status_im/browser/core.cljs | 315 ++++++++++++++++++ .../{ui/screens => }/browser/db.cljs | 16 +- src/status_im/browser/permissions.cljs | 125 +++++++ src/status_im/events.cljs | 119 +++++++ src/status_im/init/core.cljs | 2 +- src/status_im/models/browser.cljs | 195 ----------- src/status_im/models/dev_server.cljs | 13 +- src/status_im/models/wallet.cljs | 20 +- .../ui/components/list_selection.cljs | 4 +- .../ui/screens/add_new/open_dapp/views.cljs | 6 +- src/status_im/ui/screens/browser/events.cljs | 175 ---------- .../ui/screens/browser/navigation.cljs | 6 - .../ui/screens/browser/permissions/views.cljs | 30 +- .../screens/browser/site_blocked/views.cljs | 16 +- src/status_im/ui/screens/browser/styles.cljs | 6 +- src/status_im/ui/screens/browser/subs.cljs | 18 +- src/status_im/ui/screens/browser/views.cljs | 87 ++--- .../ui/screens/chat/message/message.cljs | 2 +- src/status_im/ui/screens/contacts/subs.cljs | 23 +- src/status_im/ui/screens/db.cljs | 2 +- src/status_im/ui/screens/events.cljs | 15 +- .../ui/screens/hardwallet/connect/views.cljs | 4 +- src/status_im/ui/screens/home/subs.cljs | 2 +- src/status_im/ui/screens/home/views.cljs | 4 +- .../ui/screens/home/views/inner_item.cljs | 48 ++- .../screens/wallet/collectibles/events.cljs | 7 +- src/status_im/utils/universal_links/core.cljs | 2 +- test/cljs/status_im/test/browser/core.cljs | 127 +++++++ test/cljs/status_im/test/browser/events.cljs | 213 ------------ .../status_im/test/browser/permissions.cljs | 87 +++++ test/cljs/status_im/test/runner.cljs | 6 +- .../test/ui/screens/add-new/models.cljs | 2 +- .../test/utils/universal_links/core.cljs | 4 +- 33 files changed, 943 insertions(+), 758 deletions(-) create mode 100644 src/status_im/browser/core.cljs rename src/status_im/{ui/screens => }/browser/db.cljs (78%) create mode 100644 src/status_im/browser/permissions.cljs delete mode 100644 src/status_im/models/browser.cljs delete mode 100644 src/status_im/ui/screens/browser/events.cljs delete mode 100644 src/status_im/ui/screens/browser/navigation.cljs create mode 100644 test/cljs/status_im/test/browser/core.cljs delete mode 100644 test/cljs/status_im/test/browser/events.cljs create mode 100644 test/cljs/status_im/test/browser/permissions.cljs diff --git a/src/status_im/browser/core.cljs b/src/status_im/browser/core.cljs new file mode 100644 index 0000000000..f849a2adc9 --- /dev/null +++ b/src/status_im/browser/core.cljs @@ -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))) diff --git a/src/status_im/ui/screens/browser/db.cljs b/src/status_im/browser/db.cljs similarity index 78% rename from src/status_im/ui/screens/browser/db.cljs rename to src/status_im/browser/db.cljs index 1ed4188b16..810654dd09 100644 --- a/src/status_im/ui/screens/browser/db.cljs +++ b/src/status_im/browser/db.cljs @@ -1,6 +1,6 @@ -(ns status-im.ui.screens.browser.db - (:require-macros [status-im.utils.db :refer [allowed-keys]]) - (:require [cljs.spec.alpha :as spec])) +(ns status-im.browser.db + (: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/timestamp (spec/nilable int?)) @@ -9,12 +9,16 @@ (spec/def :browser/error? (spec/nilable boolean?)) (spec/def :browser/history (spec/nilable vector?)) (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/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?)) -(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/nilable @@ -25,7 +29,9 @@ :browser/url-editing? :browser/show-tooltip :browser/show-permission - :browser/permissions-queue + :browser/pending-permissions + :browser/allowed-permissions + :browser/requested-permissions :browser/error?]))) (spec/def :browser/browser diff --git a/src/status_im/browser/permissions.cljs b/src/status_im/browser/permissions.cljs new file mode 100644 index 0000000000..f26f170a11 --- /dev/null +++ b/src/status_im/browser/permissions.cljs @@ -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)))) diff --git a/src/status_im/events.cljs b/src/status_im/events.cljs index c672ee2f34..e3e3b3826a 100644 --- a/src/status_im/events.cljs +++ b/src/status_im/events.cljs @@ -7,6 +7,8 @@ [status-im.accounts.recover.core :as accounts.recover] [status-im.accounts.update.core :as accounts.update] [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.fleet.core :as fleet] [status-im.hardwallet.core :as hardwallet] @@ -522,3 +524,120 @@ :hardwallet.ui/go-to-settings-button-pressed (fn [_ _] {: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))) diff --git a/src/status_im/init/core.cljs b/src/status_im/init/core.cljs index 7bd874993a..93431d2183 100644 --- a/src/status_im/init/core.cljs +++ b/src/status_im/init/core.cljs @@ -7,7 +7,7 @@ [status-im.data-store.core :as data-store] [status-im.data-store.realm.core :as realm] [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.dev-server :as models.dev-server] [status-im.protocol.core :as protocol] diff --git a/src/status_im/models/browser.cljs b/src/status_im/models/browser.cljs deleted file mode 100644 index 45630fb9a6..0000000000 --- a/src/status_im/models/browser.cljs +++ /dev/null @@ -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)})) diff --git a/src/status_im/models/dev_server.cljs b/src/status_im/models/dev_server.cljs index acaa79219c..e4636bb958 100644 --- a/src/status_im/models/dev_server.cljs +++ b/src/status_im/models/dev_server.cljs @@ -1,6 +1,8 @@ (ns status-im.models.dev-server - (:require [status-im.network.core :as network] - [clojure.string :as string])) + (:require [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 [{{:account/keys [account]} :db}] @@ -17,9 +19,10 @@ {:dev-server/respond [200 {:message "Pong!"}]}) (defmethod process-request! [:POST "dapp" "open"] - [{{:keys [url]} :data}] - {:dispatch [:open-url-in-browser url] - :dev-server/respond [200 {:message "URL has been opened."}]}) + [{{:keys [url]} :data cofx :cofx}] + (handlers-macro/merge-fx cofx + {:dev-server/respond [200 {:message "URL has been opened."}]} + (browser/open-url url))) (defmethod process-request! [:POST "network" nil] [{:keys [cofx data]}] diff --git a/src/status_im/models/wallet.cljs b/src/status_im/models/wallet.cljs index b06066bd66..34b1a7242a 100644 --- a/src/status_im/models/wallet.cljs +++ b/src/status_im/models/wallet.cljs @@ -110,18 +110,18 @@ [second_param first_param])))) (defn web3-error-callback [fx {:keys [webview-bridge]} {:keys [message-id]} message] - (assoc fx :send-to-bridge-fx [{:type constants/web3-send-async-callback - :messageId message-id - :error message} - webview-bridge])) + (assoc fx :browser/send-to-bridge {:message {:type constants/web3-send-async-callback + :messageId message-id + :error message} + :webview webview-bridge})) (defn dapp-complete-transaction [id result method message-id webview] - (cond-> {:send-to-bridge-fx [{:type constants/web3-send-async-callback - :messageId message-id - :result {:jsonrpc "2.0" - :id (int id) - :result result}} - webview] + (cond-> {:browser/send-to-bridge {:message {:type constants/web3-send-async-callback + :messageId message-id + :result {:jsonrpc "2.0" + :id (int id) + :result result}} + :webview webview} :dispatch [:navigate-back]} (= method constants/web3-personal-sign) diff --git a/src/status_im/ui/components/list_selection.cljs b/src/status_im/ui/components/list_selection.cljs index 0aa832e7b7..e44f2aa896 100644 --- a/src/status_im/ui/components/list_selection.cljs +++ b/src/status_im/ui/components/list_selection.cljs @@ -31,7 +31,7 @@ (defn browse [link] (show {:title (i18n/label :t/browsing-title) :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) :action #(.openURL react/linking (http/normalize-url link))}] :cancel-text (i18n/label :t/browsing-cancel)})) @@ -39,5 +39,5 @@ (defn browse-dapp [link] (show {:title (i18n/label :t/browsing-title) :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)})) diff --git a/src/status_im/ui/screens/add_new/open_dapp/views.cljs b/src/status_im/ui/screens/add_new/open_dapp/views.cljs index b9e613c7cf..db22101e6c 100644 --- a/src/status_im/ui/screens/add_new/open_dapp/views.cljs +++ b/src/status_im/ui/screens/add_new/open_dapp/views.cljs @@ -29,9 +29,7 @@ [components/separator] [react/view add-new.styles/input-container [react/text-input {:on-change-text #(reset! url-text %) - :on-submit-editing #(do - (re-frame/dispatch [:navigate-to :home]) - (re-frame/dispatch [:open-url-in-browser @url-text])) + :on-submit-editing #(re-frame/dispatch [:browser.ui/dapp-url-submitted @url-text]) :placeholder (i18n/label :t/enter-url) :auto-capitalize :none :auto-correct false @@ -62,7 +60,7 @@ :icon :icons/address :icon-opts {:color colors/blue} :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}]] [react/view styles/description-container [react/i18n-text {:style styles/gray-label :key :description}] diff --git a/src/status_im/ui/screens/browser/events.cljs b/src/status_im/ui/screens/browser/events.cljs deleted file mode 100644 index 7de4e25817..0000000000 --- a/src/status_im/ui/screens/browser/events.cljs +++ /dev/null @@ -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))) \ No newline at end of file diff --git a/src/status_im/ui/screens/browser/navigation.cljs b/src/status_im/ui/screens/browser/navigation.cljs deleted file mode 100644 index 7d4befd608..0000000000 --- a/src/status_im/ui/screens/browser/navigation.cljs +++ /dev/null @@ -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)) diff --git a/src/status_im/ui/screens/browser/permissions/views.cljs b/src/status_im/ui/screens/browser/permissions/views.cljs index b1c3d3d69f..613884f20c 100644 --- a/src/status_im/ui/screens/browser/permissions/views.cljs +++ b/src/status_im/ui/screens/browser/permissions/views.cljs @@ -1,20 +1,18 @@ (ns status-im.ui.screens.browser.permissions.views - (:require-macros [status-im.utils.views :as views]) - (:require [status-im.ui.components.animation :as anim] - [status-im.ui.components.react :as react] - [status-im.ui.screens.browser.styles :as styles] - [re-frame.core :as re-frame] + (:require [re-frame.core :as re-frame] + [reagent.core :as reagent] + [status-im.browser.permissions :as browser.permissions] [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.icons.vector-icons :as icons] - [status-im.ui.components.colors :as colors] - [reagent.core :as reagent] - [status-im.models.browser :as model] - [status-im.ui.components.chat-icon.screen :as chat-icon.screen])) + [status-im.ui.components.react :as react] + [status-im.ui.screens.browser.styles :as styles]) + (:require-macros [status-im.utils.views :as views])) -(views/defview permissions-panel [{:keys [dapp? name] :as browser} {:keys [requested-permission params]}] - (views/letsubs [dapp [:get-dapp-by-name name] - bottom-anim-value (anim/create-value -354) +(views/defview permissions-panel [{:keys [dapp? name dapp] :as browser} {:keys [requested-permission dapp-name]}] + (views/letsubs [bottom-anim-value (anim/create-value -354) alpha-value (anim/create-value 0) hide-panel #(anim/start (anim/parallel @@ -27,8 +25,7 @@ (anim/timing alpha-value {:toValue 0.6 :duration 500})]))} (let [_ (when-not requested-permission (js/setTimeout hide-panel 10)) - {:keys [dapp-name]} params - {:keys [title description icon]} (get model/permissions requested-permission)] + {:keys [title description icon]} (get browser.permissions/supported-permissions requested-permission)] [react/view styles/permissions-panel-container [react/animated-view {:style (styles/permissions-panel-background alpha-value)}] [react/animated-view {:style (styles/permissions-panel bottom-anim-value)} @@ -55,11 +52,10 @@ [react/text {:style styles/permissions-panel-description-label} description] [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)}] [react/view {:width 16}] - [components.common/button {:on-press #(re-frame/dispatch [:next-dapp-permission params requested-permission - (:permissions-data params)]) + [components.common/button {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-allowed dapp-name requested-permission]) :label (i18n/label :t/allow)}]] ;; TODO (andrey) will be in next PR #_[react/view {:flex-direction :row :margin-top 19} diff --git a/src/status_im/ui/screens/browser/site_blocked/views.cljs b/src/status_im/ui/screens/browser/site_blocked/views.cljs index 518a1f67d7..89d352852b 100644 --- a/src/status_im/ui/screens/browser/site_blocked/views.cljs +++ b/src/status_im/ui/screens/browser/site_blocked/views.cljs @@ -1,14 +1,12 @@ (ns status-im.ui.screens.browser.site-blocked.views - (:require-macros [status-im.utils.views :as views]) - (: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] + (:require [re-frame.core :as re-frame] [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.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 [] [react/text {:on-press #(.openURL react/linking "status-im://chat/public/status") @@ -30,7 +28,7 @@ [react/view styles/buttons-container [components.common/button {:on-press (fn [] (let [handler (if can-go-back? - :browser-nav-back + :browser.ui/previous-page-button-pressed :navigate-back)] (re-frame/dispatch [handler]))) :label (i18n/label :t/browsing-site-blocked-go-back)}]]]]) diff --git a/src/status_im/ui/screens/browser/styles.cljs b/src/status_im/ui/screens/browser/styles.cljs index 4768cfaa58..317eed69a6 100644 --- a/src/status_im/ui/screens/browser/styles.cljs +++ b/src/status_im/ui/screens/browser/styles.cljs @@ -1,6 +1,6 @@ (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}) @@ -170,4 +170,4 @@ (def permissions-panel-permissions-label {:color colors/blue :font-size 14 - :margin-left 10}) \ No newline at end of file + :margin-left 10}) diff --git a/src/status_im/ui/screens/browser/subs.cljs b/src/status_im/ui/screens/browser/subs.cljs index d0de004105..ec270ff251 100644 --- a/src/status_im/ui/screens/browser/subs.cljs +++ b/src/status_im/ui/screens/browser/subs.cljs @@ -1,14 +1,28 @@ (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 :browsers (fn [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 :get-current-browser :<- [:get :browser/options] - :<- [:browsers] + :<- [:browser/browsers] (fn [[options browsers]] (get browsers (:browser-id options)))) diff --git a/src/status_im/ui/screens/browser/views.cljs b/src/status_im/ui/screens/browser/views.cljs index 813d274d92..aa5df3f55f 100644 --- a/src/status_im/ui/screens/browser/views.cljs +++ b/src/status_im/ui/screens/browser/views.cljs @@ -1,54 +1,43 @@ (ns status-im.ui.screens.browser.views - (:require-macros [status-im.utils.slurp :refer [slurp]] - [status-im.utils.views :as views]) - (:require [clojure.string :as string] - [cljs.reader :as reader] - [reagent.core :as reagent] + (:require [cljs.reader :as reader] [re-frame.core :as re-frame] + [reagent.core :as reagent] + [status-im.browser.core :as browser] [status-im.i18n :as i18n] [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.toolbar.actions :as actions] - [status-im.ui.components.tooltip.views :as tooltip] + [status-im.ui.components.react :as react] + [status-im.ui.components.status-bar.view :as status-bar] [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.site-blocked.views :as site-blocked.views] [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.models.browser :as model] [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 (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?] - (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://")))] +(defn toolbar-content [url {:keys [secure?] :as browser} url-editing?] + (let [url-text (atom url)] [react/view [react/view (styles/toolbar-content false) - [react/touchable-highlight {:on-press #(re-frame/dispatch [:update-browser-options - {:show-tooltip (if secure? :secure :not-secure)}])} + [react/touchable-highlight {:on-press #(re-frame/dispatch [:browser.ui/lock-pressed secure?])} (if secure? [icons/icon :icons/lock {:color colors/green}] [icons/icon :icons/lock-opened])] (if url-editing? [react/text-input {:on-change-text #(reset! url-text %) - :on-blur #(re-frame/dispatch [:update-browser-options {:url-editing? false}]) - :on-submit-editing #(do - (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])) + :on-blur #(re-frame/dispatch [:browser.ui/url-input-blured]) + :on-submit-editing #(re-frame/dispatch [:browser.ui/url-submitted @url-text]) :placeholder (i18n/label :t/enter-url) :auto-capitalize :none :auto-correct false @@ -56,7 +45,7 @@ :default-value url :ellipsize :end :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)]])]])) (defn toolbar [webview error? url browser browser-id url-editing?] @@ -67,8 +56,8 @@ (.sendToBridge @webview "navigate-to-blank")) (re-frame/dispatch [:navigate-back]) (when error? - (re-frame/dispatch [:remove-browser browser-id]))))] - [toolbar-content url browser error? url-editing?] + (re-frame/dispatch [:browser.ui/remove-browser-pressed browser-id]))))] + [toolbar-content url browser url-editing?] [toolbar.view/actions [{:icon :icons/wallet :icon-opts {:color :black :accessibility-label :wallet-modal-button} @@ -83,13 +72,6 @@ [react/text {:style styles/web-view-error-text} (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] (when url (let [domain-name (nth (re-find #"^\w+://(www\.)?([^/:]+)" url) 2)] @@ -97,13 +79,13 @@ (defn navigation [webview browser can-go-back? can-go-forward?] [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?) :style (when-not can-go-back? styles/disabled-button) - :accessibility-label :previou-page-button} + :accessibility-label :previous-page-button} [react/view [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?) :style (merge styles/forward-button (when-not can-go-forward? styles/disabled-button)) @@ -115,7 +97,7 @@ [icons/icon :icons/refresh]]]) ;; 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 [{:keys [webview error? url browser browser-id unsafe? can-go-back? can-go-forward? url-editing? resolving? network-id address @@ -141,11 +123,10 @@ :bounces false :local-storage-enabled true :render-error web-view-error - :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 - :loading? false}]) + :on-navigation-state-change #(re-frame/dispatch [:browser/navigation-state-changed % error?]) + :on-bridge-message #(re-frame/dispatch [:browser/bridge-message-received %]) + :on-load #(re-frame/dispatch [:browser/loading-started]) + :on-error #(re-frame/dispatch [:browser/error-occured]) :injected-on-start-loading-java-script (str (not opt-in?) js-res/web3 (get-inject-js url) (if opt-in? @@ -157,7 +138,7 @@ :injected-java-script js-res/webview-js}]) (when (or loading? resolving?) [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?] [permissions.views/permissions-anim-panel browser show-permission] (when show-tooltip @@ -165,7 +146,7 @@ (if (= show-tooltip :secure) (i18n/label :t/browser-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/letsubs [webview (atom nil) @@ -174,9 +155,9 @@ {: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) - can-go-forward? (model/can-go-forward? browser) - url (model/get-current-url browser) + (let [can-go-back? (browser/can-go-back? browser) + can-go-forward? (browser/can-go-forward? browser) + url (browser/get-current-url browser) opt-in? (:web3-opt-in? settings)] [browser-component {:webview webview :dapp? dapp? diff --git a/src/status_im/ui/screens/chat/message/message.cljs b/src/status_im/ui/screens/chat/message/message.cljs index ddde2c11bf..a0b6bb562c 100644 --- a/src/status_im/ui/screens/chat/message/message.cljs +++ b/src/status_im/ui/screens/chat/message/message.cljs @@ -168,7 +168,7 @@ (defn text-message [{:keys [content timestamp-str group-chat outgoing] :as 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) collapsible? (should-collapse? content group-chat) collapsed? (reagent/atom collapsible?) diff --git a/src/status_im/ui/screens/contacts/subs.cljs b/src/status_im/ui/screens/contacts/subs.cljs index b48293b589..d38071d6bd 100644 --- a/src/status_im/ui/screens/contacts/subs.cljs +++ b/src/status_im/ui/screens/contacts/subs.cljs @@ -1,14 +1,16 @@ (ns status-im.ui.screens.contacts.subs (: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.identicon :as identicon] - [status-im.utils.contacts :as utils.contacts])) + [status-im.utils.identicon :as identicon])) (reg-sub :get-current-contact-identity :contacts/identity) (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 :<- [:get-contacts] @@ -70,10 +72,17 @@ (get all-contacts identity') (utils.contacts/whisper-id->new-contact identity'))))) -(reg-sub :get-dapp-by-name - :<- [:get-dapps] - (fn [dapps [_ name]] - (first (filter #(= (:name %) name) (apply concat (map :data dapps)))))) +(reg-sub :contacts/dapps-by-name + :<- [:all-dapps] + (fn [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 :<- [:get-contacts] diff --git a/src/status_im/ui/screens/db.cljs b/src/status_im/ui/screens/db.cljs index cbbdfee0fc..c6f9bf2409 100644 --- a/src/status_im/ui/screens/db.cljs +++ b/src/status_im/ui/screens/db.cljs @@ -15,7 +15,7 @@ status-im.ui.screens.profile.db status-im.ui.screens.network-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.new-public-chat.db)) diff --git a/src/status_im/ui/screens/events.cljs b/src/status_im/ui/screens/events.cljs index 515b70a8bc..18be10d893 100644 --- a/src/status_im/ui/screens/events.cljs +++ b/src/status_im/ui/screens/events.cljs @@ -2,13 +2,10 @@ (:require status-im.events status-im.chat.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.new-chat.events status-im.ui.screens.group.chat-settings.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.web3.events 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.etheremon.events status-im.ui.screens.wallet.collectibles.superrare.events - status-im.ui.screens.browser.events status-im.utils.keychain.events [re-frame.core :as re-frame] + [status-im.hardwallet.core :as hardwallet] [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.ui.screens.db :refer [app-db]] - [status-im.utils.datetime :as time] - [status-im.utils.random :as random] + [status-im.ui.components.permissions :as permissions] + [status-im.utils.dimensions :as dimensions] [status-im.utils.handlers :as handlers] [status-im.utils.handlers-macro :as handlers-macro] [status-im.utils.http :as http] - [status-im.utils.utils :as utils] - [status-im.hardwallet.core :as hardwallet])) + [status-im.utils.utils :as utils])) (defn- http-get [{:keys [url response-validator success-event-creator failure-event-creator timeout-ms]}] (let [on-success #(re-frame/dispatch (success-event-creator %)) diff --git a/src/status_im/ui/screens/hardwallet/connect/views.cljs b/src/status_im/ui/screens/hardwallet/connect/views.cljs index 9e0a0c1ee7..271191b49d 100644 --- a/src/status_im/ui/screens/hardwallet/connect/views.cljs +++ b/src/status_im/ui/screens/hardwallet/connect/views.cljs @@ -22,7 +22,7 @@ [toolbar/actions [{:icon :icons/info :icon-opts {:color :black :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-card-image-container [react/image {:source (:hardwallet-card resources/ui) @@ -57,4 +57,4 @@ (i18n/label :t/turn-nfc-on)] [react/text {:style styles/go-to-settings-text :on-press #(re-frame/dispatch [:hardwallet.ui/go-to-settings-button-pressed])} - (i18n/label :t/go-to-settings)]]])]]]])) \ No newline at end of file + (i18n/label :t/go-to-settings)]]])]]]])) diff --git a/src/status_im/ui/screens/home/subs.cljs b/src/status_im/ui/screens/home/subs.cljs index 209a9387d6..31c2782e50 100644 --- a/src/status_im/ui/screens/home/subs.cljs +++ b/src/status_im/ui/screens/home/subs.cljs @@ -3,6 +3,6 @@ (re-frame/reg-sub :home-items :<- [:get-active-chats] - :<- [:browsers] + :<- [:browser/browsers] (fn [[chats browsers]] (sort-by #(-> % second :timestamp) > (merge chats browsers)))) diff --git a/src/status_im/ui/screens/home/views.cljs b/src/status_im/ui/screens/home/views.cljs index d1cea2ff1a..dafab421e6 100644 --- a/src/status_im/ui/screens/home/views.cljs +++ b/src/status_im/ui/screens/home/views.cljs @@ -39,7 +39,9 @@ (views/defview home-list-item [[home-item-id home-item]] (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/home-list-chat-item-inner-view inner-item/home-list-browser-item-inner-view) diff --git a/src/status_im/ui/screens/home/views/inner_item.cljs b/src/status_im/ui/screens/home/views/inner_item.cljs index e641da8b24..e42cb35ae6 100644 --- a/src/status_im/ui/screens/home/views/inner_item.cljs +++ b/src/status_im/ui/screens/home/views/inner_item.cljs @@ -18,7 +18,7 @@ [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.common.common :as components.common] - [status-im.models.browser :as model])) + [status-im.browser.core :as browser])) (defview command-short-preview [message] (letsubs [id->command [:get-id->command]] @@ -105,27 +105,25 @@ [message-content-text last-message] [unviewed-indicator chat-id]]]]]))) -(defview home-list-browser-item-inner-view [{:keys [name] :as browser}] - (letsubs [dapp [:get-dapp-by-name name] - 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-icon-container - (if dapp - [chat-icon.screen/dapp-icon-browser dapp 36] - [react/view styles/browser-icon-container - [vector-icons/icon :icons/discover {:color component.styles/color-light-gray6}]])] - [react/view styles/chat-info-container - [react/view styles/item-upper-container - [react/view styles/name-view - [react/view {:flex-shrink 1} - [react/text {:style styles/name-text - :accessibility-label :chat-name-text - :number-of-lines 1} - name]]]] - [react/view styles/item-lower-container - [react/view styles/last-message-container - [react/text {:style styles/last-message-text - :accessibility-label :chat-url-text - :number-of-lines 1} - (or url (i18n/label :t/dapp))]]]]]])) +(defn home-list-browser-item-inner-view [{:keys [dapp url name browser-id] :as browser}] + [react/touchable-highlight {:on-press #(re-frame/dispatch [:browser.ui/browser-item-selected browser-id])} + [react/view styles/chat-container + [react/view styles/chat-icon-container + (if dapp + [chat-icon.screen/dapp-icon-browser dapp 36] + [react/view styles/browser-icon-container + [vector-icons/icon :icons/discover {:color component.styles/color-light-gray6}]])] + [react/view styles/chat-info-container + [react/view styles/item-upper-container + [react/view styles/name-view + [react/view {:flex-shrink 1} + [react/text {:style styles/name-text + :accessibility-label :chat-name-text + :number-of-lines 1} + name]]]] + [react/view styles/item-lower-container + [react/view styles/last-message-container + [react/text {:style styles/last-message-text + :accessibility-label :chat-url-text + :number-of-lines 1} + (or url (i18n/label :t/dapp))]]]]]]) diff --git a/src/status_im/ui/screens/wallet/collectibles/events.cljs b/src/status_im/ui/screens/wallet/collectibles/events.cljs index 9d3ab839da..4fc7c8a612 100644 --- a/src/status_im/ui/screens/wallet/collectibles/events.cljs +++ b/src/status_im/ui/screens/wallet/collectibles/events.cljs @@ -3,7 +3,8 @@ [status-im.utils.handlers :as handlers] [status-im.utils.ethereum.erc721 :as erc721] [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)) @@ -64,5 +65,5 @@ (handlers/register-handler-fx :open-collectible-in-browser - (fn [_ [_ data]] - {:dispatch [:open-url-in-browser data]})) + (fn [cofx [_ url]] + (browser/open-url url cofx))) diff --git a/src/status_im/utils/universal_links/core.cljs b/src/status_im/utils/universal_links/core.cljs index c937ad7d45..7ceb05f28a 100644 --- a/src/status_im/utils/universal_links/core.cljs +++ b/src/status_im/utils/universal_links/core.cljs @@ -39,7 +39,7 @@ (defn handle-browse [url cofx] (log/info "universal-links: handling browse " url) - {:browse url}) + {:browser/show-browser-selection url}) (defn handle-public-chat [public-chat cofx] (log/info "universal-links: handling public chat " public-chat) diff --git a/test/cljs/status_im/test/browser/core.cljs b/test/cljs/status_im/test/browser/core.cljs new file mode 100644 index 0000000000..8919b2baad --- /dev/null +++ b/test/cljs/status_im/test/browser/core.cljs @@ -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")))))))))))) diff --git a/test/cljs/status_im/test/browser/events.cljs b/test/cljs/status_im/test/browser/events.cljs deleted file mode 100644 index 9f5cf321da..0000000000 --- a/test/cljs/status_im/test/browser/events.cljs +++ /dev/null @@ -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))))))) diff --git a/test/cljs/status_im/test/browser/permissions.cljs b/test/cljs/status_im/test/browser/permissions.cljs new file mode 100644 index 0000000000..8f0780fa24 --- /dev/null +++ b/test/cljs/status_im/test/browser/permissions.cljs @@ -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"))))))))) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index 1f24e9d66a..fb7999ad6a 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -4,7 +4,8 @@ [status-im.test.contacts.events] [status-im.test.contacts.subs] [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.transactions.subs] [status-im.test.wallet.transactions.views] @@ -114,4 +115,5 @@ 'status-im.test.accounts.recover.core 'status-im.test.ui.screens.currency-settings.models 'status-im.test.ui.screens.wallet.db - 'status-im.test.browser.events) + 'status-im.test.browser.core + 'status-im.test.browser.permissions) diff --git a/test/cljs/status_im/test/ui/screens/add-new/models.cljs b/test/cljs/status_im/test/ui/screens/add-new/models.cljs index 6a75e13aef..f3d401c085 100644 --- a/test/cljs/status_im/test/ui/screens/add-new/models.cljs +++ b/test/cljs/status_im/test/ui/screens/add-new/models.cljs @@ -12,7 +12,7 @@ (is (= :my-profile (get-in (models/handle-qr-code "0x04e1433c1a8ad71280e6d4b1814aa3958ba6eb451da47ea1d4a4bfc4a04969c445548f3bd9d40fa7e4356aa62075b4d7615179ef1332f1d6a7c59b96c4ab8e04c1" cofx) [:db :view-id])))) (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"))) (testing "handle invalid qr code" (is (:utils/show-popup (models/handle-qr-code "a random string" cofx))))) diff --git a/test/cljs/status_im/test/utils/universal_links/core.cljs b/test/cljs/status_im/test/utils/universal_links/core.cljs index 660be87dc4..8f5fecf5ba 100644 --- a/test/cljs/status_im/test/utils/universal_links/core.cljs +++ b/test/cljs/status_im/test/utils/universal_links/core.cljs @@ -25,8 +25,8 @@ (testing "it open the dapps" (is (= "status-im://browse/www.cryptokitties.co" - (:browse (links/handle-url "status-im://browse/www.cryptokitties.co" - {:db db})))))) + (:browser/show-browser-selection (links/handle-url "status-im://browse/www.cryptokitties.co" + {:db db})))))) (testing "a user profile link" (testing "it loads the profile" (let [actual (links/handle-url "status-im://user/0x04fbce10971e1cd7253b98c7b7e54de3729ca57ce41a2bfb0d1c4e0a26f72c4b6913c3487fa1b4bb86125770f1743fb4459da05c1cbe31d938814cfaf36e252073"