implemented functionality to request user action from dapps

fixed qr code js api

Signed-off-by: Goran Jovic <goranjovic@gmail.com>
This commit is contained in:
Andrey Shovkoplyas 2018-10-02 16:14:35 +02:00 committed by Goran Jovic
parent cb94f9f6da
commit 1b6d51ff11
No known key found for this signature in database
GPG Key ID: D429D1A9B2EB8A8E
10 changed files with 205 additions and 112 deletions

View File

@ -6,8 +6,9 @@ function bridgeSend(data){
WebViewBridge.send(JSON.stringify(data)); WebViewBridge.send(JSON.stringify(data));
} }
function sendAPIrequest(permission) { function sendAPIrequest(permission, params) {
var messageId = callbackId++; var messageId = callbackId++;
var params = params || {};
bridgeSend({ bridgeSend({
type: 'api-request', type: 'api-request',
@ -16,7 +17,30 @@ function sendAPIrequest(permission) {
host: window.location.hostname host: window.location.hostname
}); });
return new Promise(function (resolve, reject) { callbacks[messageId] = {resolve: resolve, reject: reject};}); return new Promise(function (resolve, reject) {
params['resolve'] = resolve;
params['reject'] = reject;
callbacks[messageId] = params;
});
}
function qrCodeResponse(data, callback){
var result = data.data;
var regex = new RegExp(callback.regex);
if (!result) {
if (callback.reject) {
callback.reject(new Error("Cancelled"));
}
}
else if (regex.test(result)) {
if (callback.resolve) {
callback.resolve(result);
}
} else {
if (callback.reject) {
callback.reject(new Error("Doesn't match"));
}
}
} }
WebViewBridge.onMessage = function (message) { WebViewBridge.onMessage = function (message) {
@ -25,9 +49,10 @@ WebViewBridge.onMessage = function (message) {
var callback = callbacks[id]; var callback = callbacks[id];
if (callback) { if (callback) {
if (data.type === "api-response") { if (data.type === "api-response") {
if (data.isAllowed) { if (data.permission == 'qr-code'){
qrCodeResponse(data, callback);
} else if (data.isAllowed) {
callback.resolve(data.data); callback.resolve(data.data);
} else { } else {
callback.reject(new Error("Denied")); callback.reject(new Error("Denied"));
@ -44,22 +69,6 @@ WebViewBridge.onMessage = function (message) {
callback.callback(data.error, data.result); 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);
}
}
}
} }
} }
}; };
@ -73,9 +82,8 @@ StatusAPI.prototype.getContactCode = function () {
var StatusHttpProvider = function () {}; var StatusHttpProvider = function () {};
StatusHttpProvider.prototype.isStatus = true; StatusHttpProvider.prototype.isStatus = true;
StatusHttpProvider.prototype.isConnected = function () { return true; };
StatusHttpProvider.prototype.status = new StatusAPI(); StatusHttpProvider.prototype.status = new StatusAPI();
StatusHttpProvider.prototype.isConnected = function () { return true; };
function web3Response (payload, result){ function web3Response (payload, result){
return {id: payload.id, return {id: payload.id,
@ -140,19 +148,13 @@ StatusHttpProvider.prototype.sendAsync = function (payload, callback) {
} }
}; };
StatusHttpProvider.prototype.scanQRCode = function (regex) {
return new Promise(function (resolve, reject) {
var messageId = callbackId++;
callbacks[messageId] = {resolve: resolve, reject: reject, regex: regex};
bridgeSend({type: 'scan-qr-code',
messageId: messageId});
});
};
StatusHttpProvider.prototype.enable = function () { StatusHttpProvider.prototype.enable = function () {
return new Promise(function (resolve, reject) { setTimeout(resolve, 1000);}); return new Promise(function (resolve, reject) { setTimeout(resolve, 1000);});
}; };
StatusHttpProvider.prototype.scanQRCode = function (regex) {
return sendAPIrequest('qr-code', {regex: regex});
};
} }
var protocol = window.location.protocol var protocol = window.location.protocol

View File

@ -6,8 +6,9 @@ function bridgeSend(data){
WebViewBridge.send(JSON.stringify(data)); WebViewBridge.send(JSON.stringify(data));
} }
function sendAPIrequest(permission) { function sendAPIrequest(permission, params) {
var messageId = callbackId++; var messageId = callbackId++;
var params = params || {};
bridgeSend({ bridgeSend({
type: 'api-request', type: 'api-request',
@ -16,7 +17,30 @@ function sendAPIrequest(permission) {
host: window.location.hostname host: window.location.hostname
}); });
return new Promise(function (resolve, reject) { callbacks[messageId] = {resolve: resolve, reject: reject};}); return new Promise(function (resolve, reject) {
params['resolve'] = resolve;
params['reject'] = reject;
callbacks[messageId] = params;
});
}
function qrCodeResponse(data, callback){
var result = data.data;
var regex = new RegExp(callback.regex);
if (!result) {
if (callback.reject) {
callback.reject(new Error("Cancelled"));
}
}
else if (regex.test(result)) {
if (callback.resolve) {
callback.resolve(result);
}
} else {
if (callback.reject) {
callback.reject(new Error("Doesn't match"));
}
}
} }
WebViewBridge.onMessage = function (message) { WebViewBridge.onMessage = function (message) {
@ -24,38 +48,31 @@ WebViewBridge.onMessage = function (message) {
var id = data.messageId; var id = data.messageId;
var callback = callbacks[id]; var callback = callbacks[id];
if (callback) if (callback) {
{ if (data.type === "api-response") {
if (data.type === "api-response") if (data.permission == 'qr-code'){
{ qrCodeResponse(data, callback);
if (data.isAllowed) } else if (data.isAllowed) {
{ if (data.permission == 'web3') {
if (data.permission == 'web3')
{
window.currentAccountAddress = data.data; window.currentAccountAddress = data.data;
callback.resolve(); callback.resolve();
} } else {
else
{
callback.resolve(data.data); callback.resolve(data.data);
} }
} } else {
else
{
callback.reject(new Error("Denied")); callback.reject(new Error("Denied"));
} }
} } else if (data.type === "web3-send-async-callback") {
else if (data.type === "web3-send-async-callback") var id = data.messageId;
{ var callback = callbacks[id];
if (callback.results) if (callback) {
{ if (callback.results) {
callback.results.push(data.error || data.result); callback.results.push(data.error || data.result);
if (callback.results.length == callback.num) if (callback.results.length == callback.num)
callback.callback(undefined, callback.results); callback.callback(undefined, callback.results);
} } else {
else callback.callback(data.error, data.result);
{ }
callback.callback(data.error, data.result);
} }
} }
} }
@ -91,9 +108,16 @@ StatusAPI.prototype.getContactCode = function () {
var ReadOnlyProvider = function () {}; var ReadOnlyProvider = function () {};
ReadOnlyProvider.prototype.isStatus = true; ReadOnlyProvider.prototype.isStatus = true;
ReadOnlyProvider.prototype.status = new StatusAPI();
ReadOnlyProvider.prototype.isConnected = function () { return true; }; ReadOnlyProvider.prototype.isConnected = function () { return true; };
ReadOnlyProvider.prototype.status = new StatusAPI(); ReadOnlyProvider.prototype.enable = function () {
return sendAPIrequest('web3');
};
ReadOnlyProvider.prototype.scanQRCode = function (regex) {
return sendAPIrequest('qr-code', {regex: regex});
};
ReadOnlyProvider.prototype.send = function (payload) { ReadOnlyProvider.prototype.send = function (payload) {
if (payload.method == "eth_uninstallFilter"){ if (payload.method == "eth_uninstallFilter"){
@ -107,10 +131,6 @@ ReadOnlyProvider.prototype.send = function (payload) {
} }
}; };
ReadOnlyProvider.prototype.enable = function () {
return sendAPIrequest('web3');
};
ReadOnlyProvider.prototype.sendAsync = function (payload, callback) { ReadOnlyProvider.prototype.sendAsync = function (payload, callback) {
var syncResponse = getSyncResponse(payload); var syncResponse = getSyncResponse(payload);
@ -143,7 +163,6 @@ ReadOnlyProvider.prototype.sendAsync = function (payload, callback) {
} }
}; };
} }
console.log("ReadOnlyProvider"); console.log("ReadOnlyProvider");

View File

@ -257,11 +257,18 @@
(web3-send-async cofx payload message-id)))) (web3-send-async cofx payload message-id))))
(fx/defn handle-scanned-qr-code (fx/defn handle-scanned-qr-code
[cofx data message] [cofx data {:keys [dapp-name permission message-id]}]
(fx/merge cofx (fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] false)
(send-to-bridge (assoc message :result data)) (browser.permissions/send-response-to-bridge permission message-id true data)
(browser.permissions/process-next-permission dapp-name)
(navigation/navigate-back))) (navigation/navigate-back)))
(fx/defn handle-canceled-qr-code
[cofx {:keys [dapp-name permission message-id]}]
(fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] false)
(browser.permissions/send-response-to-bridge permission message-id true nil)
(browser.permissions/process-next-permission dapp-name)))
(fx/defn process-bridge-message (fx/defn process-bridge-message
[{:keys [db] :as cofx} message] [{:keys [db] :as cofx} message]
(let [{:browser/keys [options browsers]} db (let [{:browser/keys [options browsers]} db
@ -283,13 +290,6 @@
(= type constants/web3-send-async-read-only) (= type constants/web3-send-async-read-only)
(web3-send-async-read-only cofx dapp-name payload messageId) (web3-send-async-read-only cofx dapp-name payload messageId)
(= type constants/scan-qr-code)
(qr-scanner/scan-qr-code cofx
{:modal? false}
(merge {:handler :browser.bridge.callback/qr-code-scanned}
{:type constants/scan-qr-code-callback
:data data}))
(= type constants/api-request) (= type constants/api-request)
(browser.permissions/process-permission cofx dapp-name permission messageId)))) (browser.permissions/process-permission cofx dapp-name permission messageId))))

View File

@ -16,6 +16,7 @@
(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 (spec/nilable list?))
(spec/def :browser/yielding-control? (spec/nilable boolean?))
(spec/def :browser/options (spec/def :browser/options
(spec/nilable (spec/nilable
@ -27,6 +28,7 @@
:browser/show-tooltip :browser/show-tooltip
:browser/show-permission :browser/show-permission
:browser/pending-permissions :browser/pending-permissions
:browser/yielding-control?
:browser/error?]))) :browser/error?])))
(spec/def :browser/browser (spec/def :browser/browser

View File

@ -3,16 +3,39 @@
[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]
[status-im.utils.fx :as fx])) [status-im.utils.fx :as fx]
[status-im.qr-scanner.core :as qr-scanner]))
(def supported-permissions (def supported-permissions
{constants/dapp-permission-contact-code {:title (i18n/label :t/wants-to-access-profile) {constants/dapp-permission-qr-code {:yield-control? true
:allowed? true}
constants/dapp-permission-contact-code {:title (i18n/label :t/wants-to-access-profile)
:description (i18n/label :t/your-contact-code) :description (i18n/label :t/your-contact-code)
:icon :icons/profile-active} :icon :icons/profile-active}
constants/dapp-permission-web3 {:title (i18n/label :t/dapp-would-like-to-connect-wallet) constants/dapp-permission-web3 {:title (i18n/label :t/dapp-would-like-to-connect-wallet)
:description (i18n/label :t/allowing-authorizes-this-dapp) :description (i18n/label :t/allowing-authorizes-this-dapp)
:icon :icons/wallet-active}}) :icon :icons/wallet-active}})
(fx/defn permission-yield-control
[{:keys [db] :as cofx} dapp-name permission message-id]
(cond
(= permission constants/dapp-permission-qr-code)
(fx/merge (assoc-in cofx [:db :browser/options :yielding-control?] true)
(qr-scanner/scan-qr-code {:modal? false}
{:handler :browser.bridge.callback/qr-code-scanned
:cancel-handler :browser.bridge.callback/qr-code-canceled
:data {:dapp-name dapp-name
:permission permission
:message-id message-id}}))))
(fx/defn permission-show-permission
[{:keys [db] :as cofx} dapp-name permission message-id yield-control?]
{:db (assoc-in db [:browser/options :show-permission]
{:requested-permission permission
:message-id message-id
:dapp-name dapp-name
:yield-control? yield-control?})})
(defn get-permission-data [cofx allowed-permission] (defn get-permission-data [cofx allowed-permission]
(let [account (get-in cofx [:db :account/account])] (let [account (get-in cofx [:db :account/account])]
(get {constants/dapp-permission-contact-code (:public-key account) (get {constants/dapp-permission-contact-code (:public-key account)
@ -22,13 +45,13 @@
(fx/defn send-response-to-bridge (fx/defn send-response-to-bridge
"Send response to the bridge. If the permission is allowed, send data associated "Send response to the bridge. If the permission is allowed, send data associated
with the permission" with the permission"
[{:keys [db] :as cofx} permission message-id allowed?] [{:keys [db] :as cofx} permission message-id allowed? data]
{:browser/send-to-bridge {:message (cond-> {:type constants/api-response {:browser/send-to-bridge {:message (cond-> {:type constants/api-response
:isAllowed allowed? :isAllowed allowed?
:permission permission :permission permission
:messageId message-id} :messageId message-id}
allowed? allowed?
(assoc :data (get-permission-data cofx permission))) (assoc :data data))
:webview (:webview-bridge db)}}) :webview (:webview-bridge db)}})
(fx/defn update-dapp-permissions (fx/defn update-dapp-permissions
@ -47,33 +70,46 @@
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]
(if (get-in db [:browser/options :show-permission]) (let [{:keys [show-permission yielding-control?]} (get db :browser/options)]
{:db db} (if (or show-permission yielding-control?)
(let [pending-permissions (get-in db [:browser/options :pending-permissions]) {:db db}
next-permission (last pending-permissions)] (let [pending-permissions (get-in db [:browser/options :pending-permissions])
(when next-permission next-permission (last pending-permissions)
{:db (-> db new-cofx (update-in cofx [:db :browser/options :pending-permissions] butlast)]
(update-in [:browser/options :pending-permissions] butlast) (when-let [{:keys [yield-control? permission message-id allowed?]} next-permission]
(assoc-in [:browser/options :show-permission] (if (and yield-control? allowed?)
{:requested-permission (:permission next-permission) (permission-yield-control new-cofx dapp-name permission message-id)
:message-id (:message-id next-permission) (permission-show-permission new-cofx dapp-name permission message-id yield-control?)))))))
:dapp-name dapp-name}))}))))
(fx/defn send-response-and-process-next-permission
[{:keys [db] :as cofx} dapp-name requested-permission message-id]
(fx/merge cofx
(send-response-to-bridge requested-permission
message-id
true
(get-permission-data cofx requested-permission))
(process-next-permission dapp-name)))
(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}] [{:keys [db] :as cofx}]
(let [{:keys [requested-permission message-id dapp-name]} (get-in db [:browser/options :show-permission])] (let [{:keys [requested-permission message-id dapp-name yield-control?]}
(get-in db [:browser/options :show-permission])]
(fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil) (fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(update-dapp-permissions dapp-name requested-permission true) (update-dapp-permissions dapp-name requested-permission true)
(send-response-to-bridge requested-permission message-id true) (if yield-control?
(process-next-permission dapp-name)))) (permission-yield-control dapp-name requested-permission message-id)
(send-response-and-process-next-permission dapp-name requested-permission message-id)))))
(fx/defn deny-permission (fx/defn deny-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}] [{:keys [db] :as cofx}]
(let [{:keys [requested-permission message-id dapp-name]} (get-in db [:browser/options :show-permission])] (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) (fx/merge (assoc-in cofx [:db :browser/options :show-permission] nil)
(send-response-to-bridge requested-permission message-id false) (send-response-to-bridge requested-permission
message-id
false
(get-permission-data cofx requested-permission))
(process-next-permission dapp-name)))) (process-next-permission dapp-name))))
(fx/defn process-permission (fx/defn process-permission
@ -81,12 +117,21 @@
If supported permission is already granted, return the result immediatly to the bridge 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 permission message-id] [cofx dapp-name permission message-id]
(let [allowed-permissions (set (get-in cofx [:db :dapps/permissions dapp-name :permissions])) (let [allowed-permissions (set (get-in cofx [:db :dapps/permissions dapp-name :permissions]))
permission-allowed? (boolean (allowed-permissions permission)) permission-allowed? (boolean (allowed-permissions permission))
permission-supported? ((set (keys supported-permissions)) permission)] supported-permission (get supported-permissions permission)]
(if (or permission-allowed? (not permission-supported?)) (cond
(send-response-to-bridge cofx permission message-id permission-allowed?) (not supported-permission)
(send-response-to-bridge cofx permission message-id false nil)
(and (or permission-allowed? (:allowed? supported-permission)) (not (:yield-control? supported-permission)))
(send-response-to-bridge cofx permission message-id true (get-permission-data cofx permission))
:else
(process-next-permission (update-in cofx [:db :browser/options :pending-permissions] (process-next-permission (update-in cofx [:db :browser/options :pending-permissions]
conj {:permission permission conj {:permission permission
:message-id message-id}) :allowed? (or permission-allowed?
(:allowed? supported-permission))
:yield-control? (:yield-control? supported-permission)
:message-id message-id})
dapp-name)))) dapp-name))))

View File

@ -191,6 +191,7 @@
(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 dapp-permission-qr-code "qr-code")
(def ^:const api-response "api-response") (def ^:const api-response "api-response")
(def ^:const api-request "api-request") (def ^:const api-request "api-request")
(def ^:const history-state-changed "history-state-changed") (def ^:const history-state-changed "history-state-changed")

View File

@ -419,8 +419,13 @@
(handlers/register-handler-fx (handlers/register-handler-fx
:browser.bridge.callback/qr-code-scanned :browser.bridge.callback/qr-code-scanned
(fn [cofx [_ _ data message]] (fn [cofx [_ _ data qr-code-data]]
(browser/handle-scanned-qr-code cofx data message))) (browser/handle-scanned-qr-code cofx data (:data qr-code-data))))
(handlers/register-handler-fx
:browser.bridge.callback/qr-code-canceled
(fn [cofx [_ _ qr-code-data]]
(browser/handle-canceled-qr-code cofx (:data qr-code-data))))
;; qr-scanner module ;; qr-scanner module
@ -434,6 +439,11 @@
(fn [cofx [_ context data]] (fn [cofx [_ context data]]
(qr-scanner/set-qr-code cofx context data))) (qr-scanner/set-qr-code cofx context data)))
(handlers/register-handler-fx
:qr-scanner.callback/scan-qr-code-cancel
(fn [cofx [_ context]]
(qr-scanner/set-qr-code-cancel cofx context)))
;; privacy-policy module ;; privacy-policy module
(handlers/register-handler-fx (handlers/register-handler-fx

View File

@ -23,3 +23,12 @@
(dissoc :current-qr-context))} (dissoc :current-qr-context))}
(when-let [qr-codes (:qr-codes db)] (when-let [qr-codes (:qr-codes db)]
{:dispatch [(:handler qr-codes) context data (dissoc qr-codes :handler)]}))) {:dispatch [(:handler qr-codes) context data (dissoc qr-codes :handler)]})))
(fx/defn set-qr-code-cancel
[{:keys [db]} context]
(merge {:db (-> db
(update :qr-codes dissoc context)
(dissoc :current-qr-context))}
(when-let [qr-codes (:qr-codes db)]
(when-let [handler (:cancel-handler qr-codes)]
{:dispatch [handler context qr-codes]}))))

View File

@ -7,13 +7,18 @@
[status-im.ui.components.camera :as camera] [status-im.ui.components.camera :as camera]
[status-im.ui.components.status-bar.view :as status-bar] [status-im.ui.components.status-bar.view :as status-bar]
[status-im.ui.components.toolbar.view :as toolbar] [status-im.ui.components.toolbar.view :as toolbar]
[status-im.ui.screens.qr-scanner.styles :as styles])) [status-im.ui.screens.qr-scanner.styles :as styles]
[status-im.ui.components.toolbar.actions :as actions]))
(defview qr-scanner-toolbar [title hide-nav?] (defview qr-scanner-toolbar [title identifier]
(letsubs [modal [:get :modal]] [react/view
[react/view [status-bar/status-bar]
[status-bar/status-bar] [toolbar/toolbar nil
[toolbar/simple-toolbar title]])) [toolbar/nav-button (actions/back
#(do
(re-frame/dispatch [:qr-scanner.callback/scan-qr-code-cancel identifier])
(re-frame/dispatch [:navigate-back])))]
[toolbar/content-title title]]])
(defn on-barcode-read [identifier data] (defn on-barcode-read [identifier data]
(re-frame/dispatch [:qr-scanner.callback/scan-qr-code-success identifier (camera/get-qr-code-data data)])) (re-frame/dispatch [:qr-scanner.callback/scan-qr-code-success identifier (camera/get-qr-code-data data)]))
@ -23,7 +28,7 @@
camera-initialized? (reagent/atom false) camera-initialized? (reagent/atom false)
barcode-read? (reagent/atom false)] barcode-read? (reagent/atom false)]
[react/view styles/barcode-scanner-container [react/view styles/barcode-scanner-container
[qr-scanner-toolbar (or (:toolbar-title identifier) (i18n/label :t/scan-qr)) (not @camera-initialized?)] [qr-scanner-toolbar (or (:toolbar-title identifier) (i18n/label :t/scan-qr)) identifier]
[camera/camera {:onBarCodeRead #(if (:multiple? identifier) [camera/camera {:onBarCodeRead #(if (:multiple? identifier)
(on-barcode-read identifier %) (on-barcode-read identifier %)
(when-not @barcode-read? (when-not @barcode-read?

View File

@ -28,7 +28,7 @@
:messageId 1 :messageId 1
:permission "contact-code"}))] :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" :message-id 1})) {:requested-permission "contact-code" :dapp-name "test.com" :message-id 1 :yield-control? nil}))
(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"