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));
}
function sendAPIrequest(permission) {
function sendAPIrequest(permission, params) {
var messageId = callbackId++;
var params = params || {};
bridgeSend({
type: 'api-request',
@ -16,7 +17,30 @@ function sendAPIrequest(permission) {
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) {
@ -25,9 +49,10 @@ WebViewBridge.onMessage = function (message) {
var callback = callbacks[id];
if (callback) {
if (data.type === "api-response") {
if (data.isAllowed) {
if (data.permission == 'qr-code'){
qrCodeResponse(data, callback);
} else if (data.isAllowed) {
callback.resolve(data.data);
} else {
callback.reject(new Error("Denied"));
@ -44,22 +69,6 @@ WebViewBridge.onMessage = function (message) {
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 () {};
StatusHttpProvider.prototype.isStatus = true;
StatusHttpProvider.prototype.isConnected = function () { return true; };
StatusHttpProvider.prototype.status = new StatusAPI();
StatusHttpProvider.prototype.isConnected = function () { return true; };
function web3Response (payload, result){
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 () {
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

View File

@ -6,8 +6,9 @@ function bridgeSend(data){
WebViewBridge.send(JSON.stringify(data));
}
function sendAPIrequest(permission) {
function sendAPIrequest(permission, params) {
var messageId = callbackId++;
var params = params || {};
bridgeSend({
type: 'api-request',
@ -16,7 +17,30 @@ function sendAPIrequest(permission) {
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) {
@ -24,38 +48,31 @@ WebViewBridge.onMessage = function (message) {
var id = data.messageId;
var callback = callbacks[id];
if (callback)
{
if (data.type === "api-response")
{
if (data.isAllowed)
{
if (data.permission == 'web3')
{
if (callback) {
if (data.type === "api-response") {
if (data.permission == 'qr-code'){
qrCodeResponse(data, callback);
} else if (data.isAllowed) {
if (data.permission == 'web3') {
window.currentAccountAddress = data.data;
callback.resolve();
}
else
{
} else {
callback.resolve(data.data);
}
}
else
{
} else {
callback.reject(new Error("Denied"));
}
}
else if (data.type === "web3-send-async-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 === "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);
}
}
}
}
@ -91,9 +108,16 @@ StatusAPI.prototype.getContactCode = function () {
var ReadOnlyProvider = function () {};
ReadOnlyProvider.prototype.isStatus = true;
ReadOnlyProvider.prototype.status = new StatusAPI();
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) {
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) {
var syncResponse = getSyncResponse(payload);
@ -143,7 +163,6 @@ ReadOnlyProvider.prototype.sendAsync = function (payload, callback) {
}
};
}
console.log("ReadOnlyProvider");

View File

@ -257,11 +257,18 @@
(web3-send-async cofx payload message-id))))
(fx/defn handle-scanned-qr-code
[cofx data message]
(fx/merge cofx
(send-to-bridge (assoc message :result data))
[cofx data {: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 data)
(browser.permissions/process-next-permission dapp-name)
(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
[{:keys [db] :as cofx} message]
(let [{:browser/keys [options browsers]} db
@ -283,13 +290,6 @@
(= type constants/web3-send-async-read-only)
(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)
(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-permission (spec/nilable map?))
(spec/def :browser/pending-permissions (spec/nilable list?))
(spec/def :browser/yielding-control? (spec/nilable boolean?))
(spec/def :browser/options
(spec/nilable
@ -27,6 +28,7 @@
:browser/show-tooltip
:browser/show-permission
:browser/pending-permissions
:browser/yielding-control?
:browser/error?])))
(spec/def :browser/browser

View File

@ -3,16 +3,39 @@
[status-im.data-store.dapp-permissions :as dapp-permissions]
[status-im.i18n :as i18n]
[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
{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)
: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}})
(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]
(let [account (get-in cofx [:db :account/account])]
(get {constants/dapp-permission-contact-code (:public-key account)
@ -22,13 +45,13 @@
(fx/defn send-response-to-bridge
"Send response to the bridge. If the permission is allowed, send data associated
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
:isAllowed allowed?
:permission permission
:messageId message-id}
allowed?
(assoc :data (get-permission-data cofx permission)))
(assoc :data data))
:webview (:webview-bridge db)}})
(fx/defn update-dapp-permissions
@ -47,33 +70,46 @@
if there is no pending permissions left, save all granted permissions
and return the result to the bridge"
[{:keys [db] :as cofx} dapp-name]
(if (get-in db [:browser/options :show-permission])
{:db db}
(let [pending-permissions (get-in db [:browser/options :pending-permissions])
next-permission (last pending-permissions)]
(when next-permission
{:db (-> db
(update-in [:browser/options :pending-permissions] butlast)
(assoc-in [:browser/options :show-permission]
{:requested-permission (:permission next-permission)
:message-id (:message-id next-permission)
:dapp-name dapp-name}))}))))
(let [{:keys [show-permission yielding-control?]} (get db :browser/options)]
(if (or show-permission yielding-control?)
{:db db}
(let [pending-permissions (get-in db [:browser/options :pending-permissions])
next-permission (last pending-permissions)
new-cofx (update-in cofx [:db :browser/options :pending-permissions] butlast)]
(when-let [{:keys [yield-control? permission message-id allowed?]} next-permission]
(if (and yield-control? allowed?)
(permission-yield-control new-cofx dapp-name permission message-id)
(permission-show-permission new-cofx dapp-name permission message-id yield-control?)))))))
(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
"Add permission to set of allowed permission and process next permission"
[{: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)
(update-dapp-permissions dapp-name requested-permission true)
(send-response-to-bridge requested-permission message-id true)
(process-next-permission dapp-name))))
(if yield-control?
(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
"Add permission to set of allowed permission and process next permission"
[{:keys [db] :as cofx}]
(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-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))))
(fx/defn process-permission
@ -81,12 +117,21 @@
If supported permission is already granted, return the result immediatly to the bridge
Otherwise process the first permission which will prompt user"
[cofx dapp-name permission message-id]
(let [allowed-permissions (set (get-in cofx [:db :dapps/permissions dapp-name :permissions]))
permission-allowed? (boolean (allowed-permissions permission))
permission-supported? ((set (keys supported-permissions)) permission)]
(if (or permission-allowed? (not permission-supported?))
(send-response-to-bridge cofx permission message-id permission-allowed?)
(let [allowed-permissions (set (get-in cofx [:db :dapps/permissions dapp-name :permissions]))
permission-allowed? (boolean (allowed-permissions permission))
supported-permission (get supported-permissions permission)]
(cond
(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]
conj {:permission permission
:message-id message-id})
conj {:permission permission
:allowed? (or permission-allowed?
(:allowed? supported-permission))
:yield-control? (:yield-control? supported-permission)
:message-id message-id})
dapp-name))))

View File

@ -191,6 +191,7 @@
(def ^:const dapp-permission-contact-code "contact-code")
(def ^:const dapp-permission-web3 "web3")
(def ^:const dapp-permission-qr-code "qr-code")
(def ^:const api-response "api-response")
(def ^:const api-request "api-request")
(def ^:const history-state-changed "history-state-changed")

View File

@ -419,8 +419,13 @@
(handlers/register-handler-fx
:browser.bridge.callback/qr-code-scanned
(fn [cofx [_ _ data message]]
(browser/handle-scanned-qr-code cofx data message)))
(fn [cofx [_ _ data qr-code-data]]
(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
@ -434,6 +439,11 @@
(fn [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
(handlers/register-handler-fx

View File

@ -23,3 +23,12 @@
(dissoc :current-qr-context))}
(when-let [qr-codes (:qr-codes db)]
{: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.status-bar.view :as status-bar]
[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?]
(letsubs [modal [:get :modal]]
[react/view
[status-bar/status-bar]
[toolbar/simple-toolbar title]]))
(defview qr-scanner-toolbar [title identifier]
[react/view
[status-bar/status-bar]
[toolbar/toolbar nil
[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]
(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)
barcode-read? (reagent/atom false)]
[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)
(on-barcode-read identifier %)
(when-not @barcode-read?

View File

@ -28,7 +28,7 @@
:messageId 1
:permission "contact-code"}))]
(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]))))
(testing "then user accepts the supported permission"