introduced new status js api according latest web3 opt-in api

This commit is contained in:
Andrey Shovkoplyas 2018-09-28 15:40:19 +02:00
parent 81b2afebc1
commit a1d0dcc0ec
No known key found for this signature in database
GPG Key ID: EAAB7C8622D860A4
9 changed files with 237 additions and 262 deletions

View File

@ -6,70 +6,77 @@ function bridgeSend(data){
WebViewBridge.send(JSON.stringify(data)); WebViewBridge.send(JSON.stringify(data));
} }
window.addEventListener('message', function (event) { function sendAPIrequest(permission) {
if (!event.data || !event.data.type) { return; } var messageId = callbackId++;
if (event.data.type === 'STATUS_API_REQUEST') {
bridgeSend({ bridgeSend({
type: 'status-api-request', type: 'api-request',
permissions: event.data.permissions, permission: permission,
host: window.location.hostname messageId: messageId,
}); host: window.location.hostname
} });
});
return new Promise(function (resolve, reject) { callbacks[messageId] = {resolve: resolve, reject: reject};});
}
WebViewBridge.onMessage = function (message) { WebViewBridge.onMessage = function (message) {
data = JSON.parse(message); data = JSON.parse(message);
var id = data.messageId;
var callback = callbacks[id];
if (data.type === "status-api-success") if (callback) {
{
window.dispatchEvent(new CustomEvent('statusapi', { detail: { permissions: data.keys,
data: data.data
} }));
}
else if (data.type === "web3-send-async-callback") if (data.type === "api-response") {
{ if (data.isAllowed) {
var id = data.messageId; callback.resolve(data.data);
var callback = callbacks[id];
if (callback) {
if (callback.results)
{
callback.results.push(data.error || data.result);
if (callback.results.length == callback.num)
callback.callback(undefined, callback.results);
}
else
{
callback.callback(data.error, data.result);
}
}
}
else if (data.type === "scan-qr-code-callback")
{
var id = data.data.messageId;
var callback = callbacks[id];
if (callback) {
var result = data.result;
var regex = new RegExp(callback.regex);
if (regex.test(result)) {
if (callback.resolve) {
callback.resolve(result);
}
} else { } else {
if (callback.reject) { callback.reject(new Error("Denied"));
callback.reject(result); }
} else if (data.type === "web3-send-async-callback") {
var id = data.messageId;
var callback = callbacks[id];
if (callback) {
if (callback.results) {
callback.results.push(data.error || data.result);
if (callback.results.length == callback.num)
callback.callback(undefined, callback.results);
} else {
callback.callback(data.error, data.result);
}
}
} else if (data.type === "scan-qr-code-callback") {
var id = data.data.messageId;
var callback = callbacks[id];
if (callback) {
var result = data.result;
var regex = new RegExp(callback.regex);
if (regex.test(result)) {
if (callback.resolve) {
callback.resolve(result);
}
} else {
if (callback.reject) {
callback.reject(result);
}
} }
} }
} }
} }
}; };
var StatusAPI = function () {};
StatusAPI.prototype.getContactCode = function () {
return sendAPIrequest('contact-code');
};
var StatusHttpProvider = function () {}; var StatusHttpProvider = function () {};
StatusHttpProvider.prototype.isStatus = true; StatusHttpProvider.prototype.isStatus = true;
StatusHttpProvider.prototype.isConnected = function () { return true; }; StatusHttpProvider.prototype.isConnected = function () { return true; };
StatusHttpProvider.prototype.status = new StatusAPI();
function web3Response (payload, result){ function web3Response (payload, result){
return {id: payload.id, return {id: payload.id,
jsonrpc: "2.0", jsonrpc: "2.0",

View File

@ -1,52 +1,52 @@
if(typeof ReadOnlyProvider === "undefined"){ if(typeof ReadOnlyProvider === "undefined"){
var callbackId = 0; var callbackId = 0;
var callbacks = {}; var callbacks = {};
var ethereumPromise = {};
function bridgeSend(data){ function bridgeSend(data){
WebViewBridge.send(JSON.stringify(data)); WebViewBridge.send(JSON.stringify(data));
} }
window.addEventListener('message', function (event) { function sendAPIrequest(permission) {
if (!event.data || !event.data.type) { return; } var messageId = callbackId++;
if (event.data.type === 'STATUS_API_REQUEST') {
bridgeSend({ bridgeSend({
type: 'status-api-request', type: 'api-request',
permissions: event.data.permissions, permission: permission,
host: window.location.hostname messageId: messageId,
}); host: window.location.hostname
} });
});
return new Promise(function (resolve, reject) { callbacks[messageId] = {resolve: resolve, reject: reject};});
}
WebViewBridge.onMessage = function (message) { WebViewBridge.onMessage = function (message) {
data = JSON.parse(message); data = JSON.parse(message);
var id = data.messageId;
var callback = callbacks[id];
if (data.type === "status-api-success") if (callback)
{ {
if (data.keys == 'WEB3') if (data.type === "api-response")
{ {
ethereumPromise.allowed = true; if (data.isAllowed)
window.currentAccountAddress = data.data["WEB3"]; {
ethereumPromise.resolve(); if (data.permission == 'web3')
{
window.currentAccountAddress = data.data;
callback.resolve();
}
else
{
callback.resolve(data.data);
}
}
else
{
callback.reject(new Error("Denied"));
}
} }
else else if (data.type === "web3-send-async-callback")
{ {
window.dispatchEvent(new CustomEvent('statusapi', { detail: { permissions: data.keys,
data: data.data
} }));
}
}
else if (data.type === "web3-permission-request-denied")
{
ethereumPromise.reject(new Error("Denied"));
}
else if (data.type === "web3-send-async-callback")
{
var id = data.messageId;
var callback = callbacks[id];
if (callback) {
if (callback.results) if (callback.results)
{ {
callback.results.push(data.error || data.result); callback.results.push(data.error || data.result);
@ -82,11 +82,19 @@ function getSyncResponse (payload) {
} }
} }
var StatusAPI = function () {};
StatusAPI.prototype.getContactCode = function () {
return sendAPIrequest('contact-code');
};
var ReadOnlyProvider = function () {}; var ReadOnlyProvider = function () {};
ReadOnlyProvider.prototype.isStatus = true; ReadOnlyProvider.prototype.isStatus = true;
ReadOnlyProvider.prototype.isConnected = function () { return true; }; ReadOnlyProvider.prototype.isConnected = function () { return true; };
ReadOnlyProvider.prototype.status = new StatusAPI();
ReadOnlyProvider.prototype.send = function (payload) { ReadOnlyProvider.prototype.send = function (payload) {
if (payload.method == "eth_uninstallFilter"){ if (payload.method == "eth_uninstallFilter"){
this.sendAsync(payload, function (res, err) {}) this.sendAsync(payload, function (res, err) {})
@ -100,15 +108,7 @@ ReadOnlyProvider.prototype.send = function (payload) {
}; };
ReadOnlyProvider.prototype.enable = function () { ReadOnlyProvider.prototype.enable = function () {
bridgeSend({ return sendAPIrequest('web3');
type: 'status-api-request',
permissions: ['WEB3'],
host: window.location.hostname
});
return new Promise(function (resolve, reject) {
ethereumPromise.resolve = resolve;
ethereumPromise.reject = reject;
});
}; };
ReadOnlyProvider.prototype.sendAsync = function (payload, callback) { ReadOnlyProvider.prototype.sendAsync = function (payload, callback) {

View File

@ -268,7 +268,7 @@
{:keys [browser-id]} options {:keys [browser-id]} options
browser (get browsers browser-id) browser (get browsers browser-id)
data (types/json->clj message) data (types/json->clj message)
{{:keys [url]} :navState :keys [type host permissions payload messageId]} data {{:keys [url]} :navState :keys [type host permission payload messageId]} data
{:keys [dapp? name]} browser {:keys [dapp? name]} browser
dapp-name (if dapp? name host)] dapp-name (if dapp? name host)]
(cond (cond
@ -290,8 +290,8 @@
{:type constants/scan-qr-code-callback {:type constants/scan-qr-code-callback
:data data})) :data data}))
(= type constants/status-api-request) (= type constants/api-request)
(browser.permissions/process-permissions cofx dapp-name permissions)))) (browser.permissions/process-permission cofx dapp-name permission messageId))))
(fx/defn handle-message-link (fx/defn handle-message-link
[cofx link] [cofx link]

View File

@ -15,10 +15,7 @@
(spec/def :browser/url-editing? (spec/nilable boolean?)) (spec/def :browser/url-editing? (spec/nilable boolean?))
(spec/def :browser/show-tooltip (spec/nilable keyword?)) (spec/def :browser/show-tooltip (spec/nilable keyword?))
(spec/def :browser/show-permission (spec/nilable map?)) (spec/def :browser/show-permission (spec/nilable map?))
(spec/def :browser/pending-permissions (spec/nilable list?))
(spec/def :browser/pending-permissions set?)
(spec/def :browser/allowed-permissions set?)
(spec/def :browser/requested-permissions set?)
(spec/def :browser/options (spec/def :browser/options
(spec/nilable (spec/nilable
@ -30,8 +27,6 @@
:browser/show-tooltip :browser/show-tooltip
:browser/show-permission :browser/show-permission
:browser/pending-permissions :browser/pending-permissions
:browser/allowed-permissions
:browser/requested-permissions
:browser/error?]))) :browser/error?])))
(spec/def :browser/browser (spec/def :browser/browser

View File

@ -1,6 +1,5 @@
(ns status-im.browser.permissions (ns status-im.browser.permissions
(:require [clojure.set :as set] (:require [status-im.constants :as constants]
[status-im.constants :as constants]
[status-im.data-store.dapp-permissions :as dapp-permissions] [status-im.data-store.dapp-permissions :as dapp-permissions]
[status-im.i18n :as i18n] [status-im.i18n :as i18n]
[status-im.utils.ethereum.core :as ethereum] [status-im.utils.ethereum.core :as ethereum]
@ -14,112 +13,77 @@
:description (i18n/label :t/allowing-authorizes-this-dapp) :description (i18n/label :t/allowing-authorizes-this-dapp)
:icon :icons/wallet-active}}) :icon :icons/wallet-active}})
(defn get-pending-permissions [db] (defn get-permission-data [cofx allowed-permission]
(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])] (let [account (get-in cofx [:db :account/account])]
(select-keys {constants/dapp-permission-contact-code (:public-key account) (get {constants/dapp-permission-contact-code (:public-key account)
constants/dapp-permission-web3 (ethereum/normalized-address constants/dapp-permission-web3 (ethereum/normalized-address (:address account))}
(:address account))} allowed-permission)))
(vec allowed-permissions))))
(fx/defn send-permissions-data-to-bridge (fx/defn send-permission-data-to-bridge
"If there is granted permissions, return the data to the bridge "If there is granted permission, return the data to the bridge"
If no permission were granted and dapp requested web3 permission, [{:keys [db] :as cofx} permission message-id allowed?]
return `web3-permission-request-denied` message type {:browser/send-to-bridge {:message (cond-> {:type constants/api-response
Otherwise do nothing" :isAllowed allowed?
;;TODO(yenda): this was the behavior of the code prior to refactoring :permission permission
;;if this is not the intended behavior please create an issue for that :messageId message-id}
[{:keys [db] :as cofx}] allowed?
(let [allowed-permissions (get-allowed-permissions db) (assoc :data (get-permission-data cofx permission)))
requested-permissions (get-requested-permissions db) :webview (:webview-bridge 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})))
(fx/defn update-dapp-permissions (fx/defn update-dapp-permissions
[{:keys [db]} dapp-name] [{:keys [db]} dapp-name permission allowed?]
(let [allowed-permissions-set (get-allowed-permissions db) (let [dapp-permissions-set (set (get-in db [:dapps/permissions dapp-name :permissions]))
allowed-permissions {:dapp dapp-name allowed-permissions-set (if allowed?
(conj dapp-permissions-set permission)
(disj dapp-permissions-set permission))
allowed-permissions {:dapp dapp-name
:permissions (vec allowed-permissions-set)}] :permissions (vec allowed-permissions-set)}]
(when (not-empty allowed-permissions-set) {:db (assoc-in db [:dapps/permissions dapp-name] allowed-permissions)
{:db (assoc-in db [:dapps/permissions dapp-name] allowed-permissions) :data-store/tx [(dapp-permissions/save-dapp-permissions allowed-permissions)]}))
:data-store/tx [(dapp-permissions/save-dapp-permissions allowed-permissions)]})))
(fx/defn process-next-permission (fx/defn process-next-permission
"Process next permission by removing it from pending permissions "Process next permission by removing it from pending permissions and prompting user
and prompting user
if there is no pending permissions left, save all granted permissions if there is no pending permissions left, save all granted permissions
and return the result to the bridge" and return the result to the bridge"
[{:keys [db] :as cofx} dapp-name] [{:keys [db] :as cofx} dapp-name]
(let [pending-permissions (get-pending-permissions db) (if (get-in db [:browser/options :show-permission])
next-permission (first pending-permissions)] {:db db}
(if next-permission (let [pending-permissions (get-in db [:browser/options :pending-permissions])
{:db (-> db next-permission (last pending-permissions)]
(remove-pending-permission next-permission) (when next-permission
(assoc-in [:browser/options :show-permission] {:db (-> db
{:requested-permission next-permission (update-in [:browser/options :pending-permissions] butlast)
:dapp-name dapp-name}))} (assoc-in [:browser/options :show-permission]
(fx/merge cofx {:requested-permission (:permission next-permission)
{:db (assoc-in db [:browser/options :show-permission] nil)} :message-id (:message-id next-permission)
(update-dapp-permissions dapp-name) :dapp-name dapp-name}))}))))
(send-permissions-data-to-bridge)))))
(fx/defn allow-permission (fx/defn allow-permission
"Add permission to set of allowed permission and process next permission" "Add permission to set of allowed permission and process next permission"
[{:keys [db] :as cofx} dapp-name permission] [{:keys [db] :as cofx}]
(fx/merge cofx (let [{:keys [requested-permission message-id dapp-name]} (get-in db [:browser/options :show-permission])]
{:db (add-allowed-permission db permission)} (fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(process-next-permission dapp-name))) (update-dapp-permissions dapp-name requested-permission true)
(send-permission-data-to-bridge requested-permission message-id true))))
(fx/defn process-permissions (fx/defn deny-permission
"Process the permissions requested by a dapp "Add permission to set of allowed permission and process next permission"
If all supported permissions are already granted, return the result immediatly [{:keys [db] :as cofx}]
to the bridge (let [{:keys [requested-permission message-id dapp-name]} (get-in db [:browser/options :show-permission])]
(fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(send-permission-data-to-bridge requested-permission message-id false))))
(fx/defn process-permission
"Process the permission requested by a dapp
If supported permission is already granted, return the result immediatly to the bridge
Otherwise process the first permission which will prompt user" Otherwise process the first permission which will prompt user"
[cofx dapp-name requested-permissions] [cofx dapp-name permission message-id]
(let [requested-permissions-set (set requested-permissions) (let [allowed-permissions (set (get-in cofx [:db :dapps/permissions dapp-name :permissions]))
current-dapp-permissions (get-in cofx [:db :dapps/permissions dapp-name :permissions]) permission-allowed? (boolean (allowed-permissions permission))
current-dapp-permissions-set (set current-dapp-permissions) permission-supported? ((set (keys supported-permissions)) permission)]
supported-permissions-set (set (keys supported-permissions)) (if (or permission-allowed? (not permission-supported?))
allowed-permissions (set/intersection requested-permissions-set (send-permission-data-to-bridge cofx permission message-id permission-allowed?)
current-dapp-permissions-set) (process-next-permission (update-in cofx [:db :browser/options :pending-permissions]
pending-permissions (-> requested-permissions-set conj {:permission permission
(set/intersection supported-permissions-set) :message-id message-id})
(set/difference current-dapp-permissions-set)) dapp-name))))
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 new-cofx dapp-name))))

View File

@ -189,11 +189,10 @@
(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 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 dapp-permission-contact-code "contact-code")
(def ^:const dapp-permission-web3 "WEB3") (def ^:const dapp-permission-web3 "web3")
(def ^:const status-api-success "status-api-success") (def ^:const api-response "api-response")
(def ^:const status-api-request "status-api-request") (def ^:const api-request "api-request")
(def ^:const web3-permission-request-denied "web3-permission-request-denied")
(def ^:const history-state-changed "history-state-changed") (def ^:const history-state-changed "history-state-changed")
(def ^:const web3-send-async "web3-send-async") (def ^:const web3-send-async "web3-send-async")
(def ^:const web3-send-async-read-only "web3-send-async-read-only") (def ^:const web3-send-async-read-only "web3-send-async-read-only")

View File

@ -837,11 +837,16 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:browser.permissions.ui/dapp-permission-allowed :browser.permissions.ui/dapp-permission-allowed
(fn [cofx [_ dapp-name permission]] (fn [cofx _]
(browser.permissions/allow-permission cofx dapp-name permission))) (browser.permissions/allow-permission cofx)))
(handlers/register-handler-fx (handlers/register-handler-fx
:browser.permissions.ui/dapp-permission-denied :browser.permissions.ui/dapp-permission-denied
(fn [cofx _]
(browser.permissions/deny-permission cofx)))
(handlers/register-handler-fx
:browser.permissions.ui/permission-animation-finished
(fn [cofx [_ dapp-name]] (fn [cofx [_ dapp-name]]
(browser.permissions/process-next-permission cofx dapp-name))) (browser.permissions/process-next-permission cofx dapp-name)))

View File

@ -11,14 +11,19 @@
[status-im.ui.screens.browser.styles :as styles]) [status-im.ui.screens.browser.styles :as styles])
(:require-macros [status-im.utils.views :as views])) (:require-macros [status-im.utils.views :as views]))
(views/defview permissions-panel [{:keys [dapp? name dapp] :as browser} {:keys [requested-permission dapp-name]}] (views/defview permissions-panel [{:keys [dapp? dapp]} {:keys [requested-permission dapp-name]}]
(views/letsubs [bottom-anim-value (anim/create-value -354) (views/letsubs [bottom-anim-value (anim/create-value -354)
alpha-value (anim/create-value 0) alpha-value (anim/create-value 0)
hide-panel #(anim/start hide-panel (fn []
(anim/parallel (js/setTimeout #(re-frame/dispatch
[(anim/spring bottom-anim-value {:toValue -354}) [:browser.permissions.ui/permission-animation-finished
(anim/timing alpha-value {:toValue 0 dapp-name])
:duration 500})]))] 600)
(anim/start
(anim/parallel
[(anim/spring bottom-anim-value {:toValue -354})
(anim/timing alpha-value {:toValue 0
:duration 500})])))]
{:component-did-mount #(anim/start {:component-did-mount #(anim/start
(anim/parallel (anim/parallel
[(anim/spring bottom-anim-value {:toValue -20}) [(anim/spring bottom-anim-value {:toValue -20})
@ -52,16 +57,13 @@
[react/text {:style styles/permissions-panel-description-label} [react/text {:style styles/permissions-panel-description-label}
description] description]
[react/view {:flex-direction :row :margin-top 14} [react/view {:flex-direction :row :margin-top 14}
[components.common/button {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-denied dapp-name]) [components.common/button
:label (i18n/label :t/deny)}] {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-denied])
:label (i18n/label :t/deny)}]
[react/view {:width 16}] [react/view {:width 16}]
[components.common/button {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-allowed dapp-name requested-permission]) [components.common/button
:label (i18n/label :t/allow)}]] {:on-press #(re-frame/dispatch [:browser.permissions.ui/dapp-permission-allowed])
;; TODO (andrey) will be in next PR :label (i18n/label :t/allow)}]]]])))
#_[react/view {:flex-direction :row :margin-top 19}
[icons/icon :icons/settings {:color colors/blue}]
[react/text {:style styles/permissions-panel-permissions-label}
(i18n/label :t/manage-permissions)]]]])))
;; NOTE (andrey) we need this complex function, to show animation before component will be unmounted ;; NOTE (andrey) we need this complex function, to show animation before component will be unmounted
(defn permissions-anim-panel [browser show-permission] (defn permissions-anim-panel [browser show-permission]

View File

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