From 41bde612126bb78d9ae4a3d290eaef5468bbb4e8 Mon Sep 17 00:00:00 2001 From: Andrey Shovkoplyas Date: Tue, 5 May 2020 07:43:55 +0200 Subject: [PATCH] [#10414] Implement support for latest version of eip-1193 --- resources/js/hook-require.js | 14 - resources/js/provider.js | 459 +++++++++++++------------- resources/js/webview.js | 2 +- shadow-cljs.edn | 2 + src/status_im/utils/js_resources.cljs | 3 +- 5 files changed, 237 insertions(+), 243 deletions(-) delete mode 100644 resources/js/hook-require.js diff --git a/resources/js/hook-require.js b/resources/js/hook-require.js deleted file mode 100644 index 162d1340da..0000000000 --- a/resources/js/hook-require.js +++ /dev/null @@ -1,14 +0,0 @@ -const m = require('module'); -const originalLoader = m._load; - -/* - Hook `require` so that RN abuse of require does not break when running tests in nodejs. -*/ - -m._load = function hookedLoader(request, parent, isMain) { - if (request.match(/.jpeg|.jpg|.png$/)) { - return { uri: request }; - } - - return originalLoader(request, parent, isMain); -}; diff --git a/resources/js/provider.js b/resources/js/provider.js index 8b5dd18efc..a9d07c910b 100644 --- a/resources/js/provider.js +++ b/resources/js/provider.js @@ -1,242 +1,249 @@ -if(typeof EthereumProvider === "undefined"){ -var callbackId = 0; -var callbacks = {}; +(function(){ + if(typeof EthereumProvider === "undefined"){ + var callbackId = 0; + var callbacks = {}; -bridgeSend = function (data) { - ReactNativeWebView.postMessage(JSON.stringify(data)); -} - -function sendAPIrequest(permission, params) { - var messageId = callbackId++; - var params = params || {}; - - bridgeSend({ - type: 'api-request', - permission: permission, - messageId: messageId, - params: params - }); - - 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")); - } - } -} - -function Unauthorized() { - this.name = "Unauthorized"; - this.id = 4100; - this.message = "The requested method and/or account has not been authorized by the user."; -} -Unauthorized.prototype = Object.create(Error.prototype); - -function UserRejectedRequest() { - this.name = "UserRejectedRequest"; - this.id = 4001; - this.message = "The user rejected the request."; -} -UserRejectedRequest.prototype = Object.create(Error.prototype); - -ReactNativeWebView.onMessage = function (message) -{ - data = JSON.parse(message); - var id = data.messageId; - var callback = callbacks[id]; - - if (callback) { - if (data.type === "api-response") { - if (data.permission == 'qr-code'){ - qrCodeResponse(data, callback); - } else if (data.isAllowed) { - if (data.permission == 'web3') { - currentAccountAddress = data.data[0]; - } - callback.resolve(data.data); - } else { - callback.reject(new UserRejectedRequest()); - } - } - else if (data.type === "web3-send-async-callback") - { - if (callback.beta) - { - if (data.error) - { - if (data.error.code == 4100) - callback.reject(new Unauthorized()); - else - //TODO probably if rpc returns empty result we need to call resolve with empty data? - callback.reject(data.error); - } - else{ - // TODO : according to https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1193.md#examples - // TODO : we need to return data.result.result here, but for some reason some dapps (uniswap) - // TODO : expects jsonrpc - callback.resolve(data.result); - } - } - else 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); - } - } - } -}; - -function web3Response (payload, result){ - return {id: payload.id, - jsonrpc: "2.0", - result: result}; -} - -function getSyncResponse (payload) { - if (payload.method == "eth_accounts" && (typeof currentAccountAddress !== "undefined")) { - return web3Response(payload, [currentAccountAddress]) - } else if (payload.method == "eth_coinbase" && (typeof currentAccountAddress !== "undefined")) { - return web3Response(payload, currentAccountAddress) - } else if (payload.method == "net_version" || payload.method == "eth_chainId"){ - return web3Response(payload, networkId) - } else if (payload.method == "eth_uninstallFilter"){ - return web3Response(payload, true); - } else { - return null; - } -} - -var StatusAPI = function () {}; - -StatusAPI.prototype.getContactCode = function () { - return sendAPIrequest('contact-code'); -}; - -var EthereumProvider = function () {}; - -EthereumProvider.prototype.isStatus = true; -EthereumProvider.prototype.status = new StatusAPI(); -EthereumProvider.prototype.isConnected = function () { return true; }; - -EthereumProvider.prototype.enable = function () { - return sendAPIrequest('web3'); -}; - -EthereumProvider.prototype.scanQRCode = function (regex) { - return sendAPIrequest('qr-code', {regex: regex}); -}; - -//Support for legacy send method -EthereumProvider.prototype.sendSync = function (payload) -{ - if (payload.method == "eth_uninstallFilter"){ - this.sendAsync(payload, function (res, err) {}) - } - var syncResponse = getSyncResponse(payload); - if (syncResponse){ - return syncResponse; - } else { - return web3Response(payload, null); - } -}; - -EthereumProvider.prototype.send = function (method, params = []) -{ - if (!method) { - return new Error('Request is not valid.'); + var bridgeSend = function (data) { + ReactNativeWebView.postMessage(JSON.stringify(data)); } - if (!(params instanceof Array)) { - return new Error('Params is not a valid array.'); - } + function sendAPIrequest(permission, params) { + var messageId = callbackId++; + var params = params || {}; - //Support for legacy send method - if (typeof method !== 'string') { - return this.sendSync(method); - } + bridgeSend({ + type: 'api-request', + permission: permission, + messageId: messageId, + params: params + }); - if (method == 'eth_requestAccounts'){ - return sendAPIrequest('web3'); - } - - var syncResponse = getSyncResponse({method: method}); - if (syncResponse){ return new Promise(function (resolve, reject) { - resolve(syncResponse); - }); + params['resolve'] = resolve; + params['reject'] = reject; + callbacks[messageId] = params; + }); } - var messageId = callbackId++; - var payload = {id: messageId, - jsonrpc: "2.0", - method: method, - params: 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")); + } + } + } - bridgeSend({type: 'web3-send-async-read-only', - messageId: messageId, - payload: payload}); + function Unauthorized() { + this.name = "Unauthorized"; + this.id = 4100; + this.code = 4100; + this.message = "The requested method and/or account has not been authorized by the user."; + } + Unauthorized.prototype = Object.create(Error.prototype); - return new Promise(function (resolve, reject) { - callbacks[messageId] = {beta: true, - resolve: resolve, - reject: reject}; - }); -}; + function UserRejectedRequest() { + this.name = "UserRejectedRequest"; + this.id = 4001; + this.code = 4001; + this.message = "The user rejected the request."; + } + UserRejectedRequest.prototype = Object.create(Error.prototype); + ReactNativeWebView.onMessage = function (message) + { + data = JSON.parse(message); + var id = data.messageId; + var callback = callbacks[id]; -//Support for legacy sendAsync method -EthereumProvider.prototype.sendAsync = function (payload, callback) -{ - var syncResponse = getSyncResponse(payload); - if (syncResponse && callback) { - callback(null, syncResponse); - } - else - { - var messageId = callbackId++; + if (callback) { + if (data.type === "api-response") { + if (data.permission == 'qr-code'){ + qrCodeResponse(data, callback); + } else if (data.isAllowed) { + if (data.permission == 'web3') { + window.statusAppcurrentAccountAddress = data.data[0]; + } + callback.resolve(data.data); + } else { + callback.reject(new UserRejectedRequest()); + } + } + else if (data.type === "web3-send-async-callback") + { + if (callback.beta) + { + if (data.error) + { + if (data.error.code == 4100) + callback.reject(new Unauthorized()); + else + callback.reject(data.error); + } + else + { + callback.resolve(data.result); + } + } + else 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); + } + } + } + }; - if (Array.isArray(payload)) - { - callbacks[messageId] = {num: payload.length, - results: [], - callback: callback}; - for (var i in payload) { - bridgeSend({type: 'web3-send-async-read-only', - messageId: messageId, - payload: payload[i]}); - } + function web3Response (payload, result){ + return {id: payload.id, + jsonrpc: "2.0", + result: result}; + } + + function getSyncResponse (payload) { + if (payload.method == "eth_accounts" && (typeof window.statusAppcurrentAccountAddress !== "undefined")) { + return web3Response(payload, [window.statusAppcurrentAccountAddress]) + } else if (payload.method == "eth_coinbase" && (typeof window.statusAppcurrentAccountAddress !== "undefined")) { + return web3Response(payload, window.statusAppcurrentAccountAddress) + } else if (payload.method == "net_version" || payload.method == "eth_chainId"){ + return web3Response(payload, window.statusAppNetworkId) + } else if (payload.method == "eth_uninstallFilter"){ + return web3Response(payload, true); + } else { + return null; + } + } + + var StatusAPI = function () {}; + + StatusAPI.prototype.getContactCode = function () { + return sendAPIrequest('contact-code'); + }; + + var EthereumProvider = function () {}; + + EthereumProvider.prototype.isStatus = true; + EthereumProvider.prototype.status = new StatusAPI(); + EthereumProvider.prototype.isConnected = function () { return true; }; + + EthereumProvider.prototype.enable = function () { + return sendAPIrequest('web3'); + }; + + EthereumProvider.prototype.scanQRCode = function (regex) { + return sendAPIrequest('qr-code', {regex: regex}); + }; + + EthereumProvider.prototype.request = function (requestArguments) + { + if (!requestArguments) { + return new Error('Request is not valid.'); + } + var method = requestArguments.method; + + if (!method) { + return new Error('Request is not valid.'); + } + + //Support for legacy send method + if (typeof method !== 'string') { + return this.sendSync(method); + } + + if (method == 'eth_requestAccounts'){ + return sendAPIrequest('web3'); + } + + var syncResponse = getSyncResponse({method: method}); + if (syncResponse){ + return new Promise(function (resolve, reject) { + resolve(syncResponse); + }); + } + + var messageId = callbackId++; + var payload = {id: messageId, + jsonrpc: "2.0", + method: method, + params: params}; + + bridgeSend({type: 'web3-send-async-read-only', + messageId: messageId, + payload: payload}); + + return new Promise(function (resolve, reject) { + callbacks[messageId] = {beta: true, + resolve: resolve, + reject: reject}; + }); + }; + + // (DEPRECATED) Support for legacy send method + EthereumProvider.prototype.send = function (method, params = []) + { + return this.request({method: method, params: params}); + } + + // (DEPRECATED) Support for legacy sendSync method + EthereumProvider.prototype.sendSync = function (payload) + { + if (payload.method == "eth_uninstallFilter"){ + this.sendAsync(payload, function (res, err) {}) + } + var syncResponse = getSyncResponse(payload); + if (syncResponse){ + return syncResponse; + } else { + return web3Response(payload, null); + } + }; + + // (DEPRECATED) Support for legacy sendAsync method + EthereumProvider.prototype.sendAsync = function (payload, callback) + { + var syncResponse = getSyncResponse(payload); + if (syncResponse && callback) { + callback(null, syncResponse); } else { - callbacks[messageId] = {callback: callback}; - bridgeSend({type: 'web3-send-async-read-only', - messageId: messageId, - payload: payload}); - } - } -}; -} + var messageId = callbackId++; -ethereum = new EthereumProvider(); + if (Array.isArray(payload)) + { + callbacks[messageId] = {num: payload.length, + results: [], + callback: callback}; + for (var i in payload) { + bridgeSend({type: 'web3-send-async-read-only', + messageId: messageId, + payload: payload[i]}); + } + } + else + { + callbacks[messageId] = {callback: callback}; + bridgeSend({type: 'web3-send-async-read-only', + messageId: messageId, + payload: payload}); + } + } + }; + } + + window.ethereum = new EthereumProvider(); +})(); \ No newline at end of file diff --git a/resources/js/webview.js b/resources/js/webview.js index ed65ffce0b..ff56d69192 100644 --- a/resources/js/webview.js +++ b/resources/js/webview.js @@ -10,4 +10,4 @@ }, 100); return pushState.apply(history, arguments); }; -}()); \ No newline at end of file +})(); \ No newline at end of file diff --git a/shadow-cljs.edn b/shadow-cljs.edn index 45eeb1eaa0..f5b7063069 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -30,6 +30,8 @@ :http {:port 3449 :host "0.0.0.0"} + :cache-blockers #{status-im.utils.js-resources} + :builds {:android {:target :react-native :output-dir "app" diff --git a/src/status_im/utils/js_resources.cljs b/src/status_im/utils/js_resources.cljs index cff97e1e85..3704ced5f7 100644 --- a/src/status_im/utils/js_resources.cljs +++ b/src/status_im/utils/js_resources.cljs @@ -3,7 +3,6 @@ (def webview-js (slurp "resources/js/webview.js")) (def provider-file (slurp "resources/js/provider.js")) - (defn ethereum-provider [network-id] - (str "var networkId = \"" network-id "\";" + (str "window.statusAppNetworkId = \"" network-id "\";" provider-file))