Introduce extended DApp API [#5042]

Signed-off-by: Andrey Shovkoplyas <motor4ik@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2018-07-18 16:48:08 +03:00
parent 2e57b1b8cf
commit 61374add53
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
16 changed files with 296 additions and 38 deletions

View File

@ -1,20 +1,40 @@
(function () {
function bridgeSend(data){
WebViewBridge.send(JSON.stringify(data));
}
var history = window.history;
var pushState = history.pushState;
history.pushState = function(state) {
setTimeout(function () {
WebViewBridge.send(JSON.stringify({
type: 'navStateChange',
navState: { url: location.href, title: document.title }
}))
bridgeSend({
type: 'history-state-changed',
navState: { url: location.href, title: document.title }
});
}, 100);
return pushState.apply(history, arguments);
};
WebViewBridge.onMessage = function (messageString) {
console.log("received from react-native: " + messageString);
window.addEventListener('message', function (event) {
if (!event.data || !event.data.type) { return; }
if (event.data.type === 'STATUS_API_REQUEST') {
bridgeSend({
type: 'status-api-request',
permissions: event.data.permissions,
host: window.location.hostname
});
}
});
if (messageString === "navigate-to-blank")
WebViewBridge.onMessage = function (message) {
data = JSON.parse(message);
if (data.type === "navigate-to-blank")
window.location.href = "about:blank";
else if (data.type === "status-api-success")
window.STATUS_API = data.data;
window.postMessage({ type: 'STATUS_API_SUCCESS', permissions: data.keys }, "*");
};
}());

View File

@ -247,3 +247,8 @@
(ethereum/sha3 "Transfer(address,address,uint256)"))
(def regx-emoji #"^((?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F|[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF])+$")
(def ^:const dapp-permission-contact-code "CONTACT_CODE")
(def ^:const status-api-success "status-api-success")
(def ^:const status-api-request "status-api-request")
(def ^:const history-state-changed "history-state-changed")

View File

@ -0,0 +1,16 @@
(ns status-im.data-store.dapp-permissions
(:require [re-frame.core :as re-frame]
[status-im.data-store.realm.core :as core]))
(re-frame/reg-cofx
:data-store/all-dapp-permissions
(fn [cofx _]
(assoc cofx :all-dapp-permissions (-> @core/account-realm
(core/get-all :dapp-permissions)
(core/all-clj :dapp-permissions)))))
(defn save-dapp-permissions
"Returns tx function for saving dapp permissions"
[permissions]
(fn [realm]
(core/create realm :dapp-permissions permissions true)))

View File

@ -7,7 +7,8 @@
[status-im.data-store.realm.schemas.account.v5.core :as v5]
[status-im.data-store.realm.schemas.account.v6.core :as v6]
[status-im.data-store.realm.schemas.account.v7.core :as v7]
[status-im.data-store.realm.schemas.account.v8.core :as v8]))
[status-im.data-store.realm.schemas.account.v8.core :as v8]
[status-im.data-store.realm.schemas.account.v9.core :as v9]))
;; TODO(oskarth): Add failing test if directory vXX exists but isn't in schemas.
@ -35,4 +36,7 @@
:migration v7/migration}
{:schema v8/schema
:schemaVersion 8
:migration v8/migration}])
:migration v8/migration}
{:schema v9/schema
:schemaVersion 9
:migration v9/migration}])

View File

@ -0,0 +1,26 @@
(ns status-im.data-store.realm.schemas.account.v9.core
(:require [status-im.data-store.realm.schemas.account.v5.chat :as chat]
[status-im.data-store.realm.schemas.account.v6.transport :as transport]
[status-im.data-store.realm.schemas.account.v1.contact :as contact]
[status-im.data-store.realm.schemas.account.v7.message :as message]
[status-im.data-store.realm.schemas.account.v1.request :as request]
[status-im.data-store.realm.schemas.account.v1.user-status :as user-status]
[status-im.data-store.realm.schemas.account.v1.local-storage :as local-storage]
[status-im.data-store.realm.schemas.account.v2.mailserver :as mailserver]
[status-im.data-store.realm.schemas.account.v8.browser :as browser]
[status-im.data-store.realm.schemas.account.v9.dapp-permissions :as dapp-permissions]
[taoensso.timbre :as log]))
(def schema [chat/schema
transport/schema
contact/schema
message/schema
request/schema
mailserver/schema
user-status/schema
local-storage/schema
browser/schema
dapp-permissions/schema])
(defn migration [old-realm new-realm]
(log/debug "migrating v9 account database: " old-realm new-realm))

View File

@ -0,0 +1,7 @@
(ns status-im.data-store.realm.schemas.account.v9.dapp-permissions)
(def schema {:name :dapp-permissions
:primaryKey :dapp
:properties {:dapp :string
:permissions {:type "string[]"
:optional true}}})

View File

@ -1,5 +1,8 @@
(ns status-im.models.browser
(:require [status-im.data-store.browser :as browser-store]))
(:require [status-im.data-store.browser :as browser-store]
[status-im.data-store.dapp-permissions :as dapp-permissions]
[status-im.i18n :as i18n]
[status-im.constants :as constants]))
(defn get-current-url [{:keys [history history-index]}]
(when (and history-index history)
@ -23,7 +26,7 @@
history (:history browser)
history-url (try (nth history history-index) (catch js/Error _))]
(when (not= history-url url)
(let [slash? (= url (str history-url "/"))
(let [slash? (= url (str history-url "/"))
new-history (if slash?
(assoc history history-index url)
(conj (subvec history 0 (inc history-index)) url))
@ -35,4 +38,39 @@
(defn update-browser-and-navigate [cofx browser]
(merge (update-browser-fx cofx browser)
{:dispatch [:navigate-to :browser (:browser-id browser)]}))
{:dispatch [:navigate-to :browser (:browser-id browser)]}))
(def permissions {constants/dapp-permission-contact-code {:label (i18n/label :t/your-contact-code)}})
(defn update-dapp-permissions-fx [{:keys [db]} permissions]
{:db (assoc-in db [:dapps/permissions (:dapp permissions)] permissions)
:data-store/tx [(dapp-permissions/save-dapp-permissions permissions)]})
(defn request-permission [cofx
{:keys [dapp-name index requested-permissions permissions-allowed user-permissions
permissions-data webview]
:as params}]
;; 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]}
{:show-dapp-permission-confirmation-fx [requested-permission params]})
{:dispatch [:next-dapp-permission params]}))
(assoc (update-dapp-permissions-fx cofx {:dapp dapp-name
:permissions (vec (set (concat (keys permissions-allowed)
user-permissions)))})
:send-to-bridge-fx [permissions-allowed webview])))
(defn next-permission [cofx params & [permission permissions-data]]
(request-permission
cofx
(cond-> params
true
(update :index inc)
(and permission permissions-data)
(assoc-in [:permissions-allowed permission] (get permissions-data permission)))))

View File

@ -30,6 +30,7 @@
:on "On"
:off "Off"
:mailserver-connection-error "Could not connect to mailserver"
:dont-allow "Don't Allow"
:custom "Custom"
:camera-access-error "To grant the required camera permission, please go to your system settings and make sure that Status > Camera is selected."
@ -693,4 +694,7 @@
:dapp "ÐApp"
:selected "Selected"
:selected-dapps "Selected ÐApps"
:browser-warning "Connection is not proven secure. Make sure you trust this site before signing transactions or entering personal data."})
:browser-warning "Connection is not proven secure. Make sure you trust this site before signing transactions or entering personal data."
:make-sure-you-trust-dapp "Make sure that you trust this DApp"
:would-like-to-access "Would like to Access"
:your-contact-code "Your Contact Code"})

View File

@ -3,10 +3,7 @@
(:require [cljs.spec.alpha :as spec]))
(spec/def :browser/browser-id (spec/nilable string?))
(spec/def :browser/contact (spec/nilable string?))
(spec/def :browser/timestamp (spec/nilable int?))
(spec/def :browser/url (spec/nilable string?))
(spec/def :browser/photo-path (spec/nilable string?))
(spec/def :browser/name (spec/nilable string?))
(spec/def :browser/dapp? (spec/nilable boolean?))
(spec/def :browser/error? (spec/nilable boolean?))
@ -28,3 +25,13 @@
:browser/history-index]))
(spec/def :browser/browsers (spec/nilable (spec/map-of :global/not-empty-string :browser/browser)))
(spec/def :dapp/dapp (spec/nilable string?))
(spec/def :dapp/permissions (spec/nilable vector?))
(spec/def :dapp/permission-map
(allowed-keys
:req-un [:dapp/dapp]
:opt-un [:dapp/permissions]))
(spec/def :dapps/permissions (spec/nilable (spec/map-of :global/not-empty-string :dapp/permission-map)))

View File

@ -8,7 +8,10 @@
[status-im.utils.universal-links.core :as utils.universal-links]
[status-im.data-store.browser :as browser-store]
[status-im.utils.http :as http]
[status-im.models.browser :as model]))
[status-im.models.browser :as model]
[status-im.utils.platform :as platform]
[status-im.utils.utils :as utils]
[status-im.constants :as constants]))
(re-frame/reg-fx
:browse
@ -17,6 +20,25 @@
(utils.universal-links/open! link)
(list-selection/browse link))))
(re-frame/reg-fx
:send-to-bridge-fx
(fn [[permissions-allowed webview]]
(.sendToBridge @webview (.stringify js/JSON (clj->js {:type constants/status-api-success
:data permissions-allowed
:keys (keys permissions-allowed)})))))
(re-frame/reg-fx
:show-dapp-permission-confirmation-fx
(fn [[permission {:keys [dapp-name permissions-data] :as params}]]
(utils/show-confirmation
{:ios-confirm-style "default"}
(str "\"" dapp-name "\" " (i18n/label :t/would-like-to-access) " " (:label (get model/permissions permission)))
(i18n/label :t/make-sure-you-trust-dapp)
nil
#(re-frame/dispatch [:next-dapp-permission params permission permissions-data])
#(re-frame/dispatch [:next-dapp-permission params])
(i18n/label :t/dont-allow))))
(handlers/register-handler-fx
:initialize-browsers
[(re-frame/inject-cofx :data-store/all-browsers)]
@ -24,6 +46,13 @@
(let [browsers (into {} (map #(vector (:browser-id %) %) all-stored-browsers))]
{:db (assoc db :browser/browsers browsers)})))
(handlers/register-handler-fx
:initialize-dapp-permissions
[(re-frame/inject-cofx :data-store/all-dapp-permissions)]
(fn [{:keys [db all-dapp-permissions]} _]
(let [dapp-permissions (into {} (map #(vector (:dapp %) %) all-dapp-permissions))]
{:db (assoc db :dapps/permissions dapp-permissions)})))
(handlers/register-handler-fx
:browse-link-from-message
(fn [_ [_ link]]
@ -90,4 +119,32 @@
[re-frame/trim-v]
(fn [cofx [{:keys [history-index] :as browser}]]
(when (< history-index (dec (count (:history browser))))
(nav-update-browser cofx browser (inc history-index)))))
(nav-update-browser cofx browser (inc history-index)))))
(handlers/register-handler-fx
:on-bridge-message
[re-frame/trim-v]
(fn [{:keys [db] :as cofx} [{{:keys [url]} :navState :keys [type host permissions]} browser webview]]
(cond
(and (= type constants/history-state-changed) platform/ios? (not= "about:blank" url))
(model/update-browser-history-fx cofx browser url false)
(= type constants/status-api-request)
(let [{:account/keys [account]} db
{:keys [dapp? name]} browser
dapp-name (if dapp? name host)]
(model/request-permission
cofx
{:dapp-name dapp-name
:webview webview
: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)}})))))
(handlers/register-handler-fx
:next-dapp-permission
[re-frame/trim-v]
(fn [cofx [params permission permissions-data]]
(model/next-permission cofx params permission permissions-data)))

View File

@ -18,7 +18,6 @@
[status-im.ui.components.toolbar.actions :as actions]
[status-im.ui.components.tooltip.views :as tooltip]
[status-im.models.browser :as model]
[status-im.utils.platform :as platform]
[status-im.utils.http :as http]))
(views/defview toolbar-content-dapp [name]
@ -67,13 +66,6 @@
[react/view styles/web-view-loading
[components/activity-indicator {:animating true}]]))
(defn on-bridge-message [message browser]
(let [{:strs [type navState]} (js->clj (.parse js/JSON message))
{:strs [url]} navState]
(when (and platform/ios? (= type "navStateChange"))
(when (not= "about:blank" url)
(re-frame/dispatch [:update-browser-on-nav-change browser url false])))))
(defn on-navigation-change [event browser]
(let [{:strs [url loading]} (js->clj event)]
(when (not= "about:blank" url)
@ -116,7 +108,11 @@
:render-error web-view-error
:render-loading web-view-loading
:on-navigation-state-change #(on-navigation-change % browser)
:on-bridge-message #(on-bridge-message % browser)
:on-bridge-message #(re-frame/dispatch [:on-bridge-message
(js->clj (.parse js/JSON %)
:keywordize-keys true)
browser
webview])
:on-load #(re-frame/dispatch [:update-browser-options {:error? false}])
:on-error #(re-frame/dispatch [:update-browser-options {:error? true}])
:injected-on-start-loading-java-script (str js-res/web3

View File

@ -207,7 +207,8 @@
:transport/chats
:transport/discovery-filter
:desktop/desktop
:dimensions/window]
:dimensions/window
:dapps/permissions]
:opt-un
[::current-public-key
::modal

View File

@ -372,6 +372,7 @@
[:load-contacts]
[:initialize-chats]
[:initialize-browsers]
[:initialize-dapp-permissions]
[:send-account-update-if-needed]
[:process-pending-messages]
[:update-wallet]

View File

@ -35,9 +35,6 @@
(chat-events/start-chat chat-id {:navigation-replace? true})
(input-events/select-chat-input-command send-command nil true)))))
(defn get-current-account [db]
(:account/account db))
(defn valid-name? [name]
(spec/valid? :profile/name name))
@ -101,7 +98,7 @@
(handlers/register-handler-fx
:my-profile/enter-two-random-words
(fn [{:keys [db]} []]
(let [{:keys [mnemonic]} (get-current-account db)
(let [{:keys [mnemonic]} (:account/account db)
shuffled-mnemonic (shuffle (map-indexed vector (clojure.string/split mnemonic #" ")))]
{:db (assoc db :my-profile/seed {:step :first-word
:first-word (first shuffled-mnemonic)

View File

@ -25,13 +25,16 @@
(show-confirmation title content confirm-button-text on-accept nil))
([title content confirm-button-text on-accept on-cancel]
(show-confirmation nil title content confirm-button-text on-accept on-cancel))
([{:keys [ios-confirm-style] :or {ios-confirm-style "destructive"}} title content confirm-button-text on-accept on-cancel]
([ios-style title content confirm-button-text on-accept on-cancel]
(show-confirmation ios-style title content confirm-button-text on-accept on-cancel nil))
([{:keys [ios-confirm-style] :or {ios-confirm-style "destructive"}}
title content confirm-button-text on-accept on-cancel cancel-button-text]
(.alert (.-Alert rn-dependencies/react-native)
title
content
;; Styles are only relevant on iOS. On Android first button is 'neutral' and second is 'positive'
(clj->js
(vector (merge {:text (i18n/label :t/cancel)
(vector (merge {:text (or cancel-button-text (i18n/label :t/cancel))
:style "cancel"
:accessibility-label :cancel-button}
(when on-cancel {:onPress on-cancel}))

View File

@ -17,7 +17,21 @@
(re-frame/reg-cofx
:data-store/all-browsers
(fn [coeffects _]
(assoc coeffects :all-stored-browsers []))))
(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])))))
(deftest browser-events
@ -63,8 +77,7 @@
(re-frame/dispatch [:open-browser (first (vals @browsers))])
(let [browser (re-frame/subscribe [:get-current-browser])
options (re-frame/subscribe [:get :browser/options])
(let [browser (re-frame/subscribe [:get-current-browser])
dapp2-url2 (str dapp2-url "/nav2")
dapp2-url3 (str dapp2-url "/nav3")]
@ -106,4 +119,67 @@
(re-frame/dispatch [:browser-nav-forward @browser])
(is (= 1 (:history-index @browser)))
(is (= [dapp2-url dapp2-url3] (:history @browser))))))))
(is (= [dapp2-url dapp2-url3] (:history @browser))))))
(re-frame/dispatch [:initialize-dapp-permissions])
(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 {:type "status-api-request"
:host dapp-name
:permissions ["FAKE_PERMISSION"]}
nil nil])
(is (= {:dapp dapp-name
:permissions []}
(get @dapps-permissions dapp-name)))
(re-frame/dispatch [:on-bridge-message {:type "status-api-request"
:host dapp-name
:permissions ["CONTACT_CODE"]}
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 {: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 {: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 {:type "status-api-request"
:host dapp-name2
:permissions ["CONTACT_CODE"]}
nil nil])
(is (= 2 (count @dapps-permissions)))
(is (= {:dapp dapp-name2
:permissions []}
(get @dapps-permissions dapp-name2)))))))