From f6e497bbcc264d2fba4455ac03c33f46a5c2acc7 Mon Sep 17 00:00:00 2001 From: Paul Shapiro Date: Thu, 6 Sep 2018 12:35:29 -0400 Subject: [PATCH] updated monero_utils to support calls which are safe over IPC to an electron remote from renderer proc's require --- .eslintignore | 1 - monero_utils/MyMoneroCoreBridge.js | 3 + monero_utils/__IPCSafe_remote_monero_utils.js | 75 +++++++++++ monero_utils/__bridged_fns_spec.js | 47 +++++++ monero_utils/monero_sendingFunds_utils.js | 121 +++++++++--------- monero_utils/monero_utils.js | 87 ++++++++++++- 6 files changed, 267 insertions(+), 67 deletions(-) create mode 100644 monero_utils/__IPCSafe_remote_monero_utils.js create mode 100644 monero_utils/__bridged_fns_spec.js diff --git a/.eslintignore b/.eslintignore index 3aeb9b4..23ed173 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1,3 +1,2 @@ node_modules/* -local_modules/cryptonote_utils/** diff --git a/monero_utils/MyMoneroCoreBridge.js b/monero_utils/MyMoneroCoreBridge.js index 6a99044..09301c9 100644 --- a/monero_utils/MyMoneroCoreBridge.js +++ b/monero_utils/MyMoneroCoreBridge.js @@ -497,6 +497,9 @@ module.exports = function(options) { const instance = new MyMoneroCoreBridge(thisModule); resolve(instance); + }).catch(function(e) { + console.error("Error loading MyMoneroCoreCpp:", e); + reject(e); }); }); }; \ No newline at end of file diff --git a/monero_utils/__IPCSafe_remote_monero_utils.js b/monero_utils/__IPCSafe_remote_monero_utils.js new file mode 100644 index 0000000..20e0151 --- /dev/null +++ b/monero_utils/__IPCSafe_remote_monero_utils.js @@ -0,0 +1,75 @@ +// Copyright (c) 2014-2018, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +"use strict"; +// +// NOTE: You will never need to require this file directly. See / use monero_utils.js. +// +const ENVIRONMENT_IS_WEB = typeof window==="object"; +const ENVIRONMENT_IS_WORKER = typeof importScripts==="function"; +const ENVIRONMENT_IS_NODE = typeof process==="object" && process.browser !== true && typeof require==="function" && ENVIRONMENT_IS_WORKER == false; // we want this to be true for Electron but not for a WebView +if (!ENVIRONMENT_IS_NODE || ENVIRONMENT_IS_WEB) { + throw "Not expecting this module to be included in this environment: non-node or web" +} +var coreBridge_instance = null; +const local_fns = {}; +const fn_names = require('./__bridged_fns_spec').bridgedFn_names; +for (const i in fn_names) { + const name = fn_names[i] + local_fns[name] = function() + { + if (coreBridge_instance === null) { + throw "Expected coreBridge_instance to have been loaded by the time " + name + " was called" + } + // + // Note how we're not getting ret.err_msg and using it to 'throw' + // here… we're just passing it back over from remote as JSON + // + return coreBridge_instance[name].apply(coreBridge_instance, arguments); // We are not intercepting the err_msg here -- because we will kill the remote fn call if we throw -- so we'll let the renderer-side throw + } +} +// +// Cannot export a promise, though, because this must be safe for IPC to electron 'remote'... +local_fns.isReady = false; +// +module.exports = local_fns; +// +// +const coreBridgeLoading_promise = require("./MyMoneroCoreBridge")({}); +coreBridgeLoading_promise.then(function(this__coreBridge_instance) +{ + coreBridge_instance = this__coreBridge_instance; + // + local_fns.isReady = true; + // +}); +coreBridgeLoading_promise.catch(function(e) +{ + console.log("Error: ", e); + // this may be insufficient… being able to throw would be nice +}); diff --git a/monero_utils/__bridged_fns_spec.js b/monero_utils/__bridged_fns_spec.js new file mode 100644 index 0000000..bf04d04 --- /dev/null +++ b/monero_utils/__bridged_fns_spec.js @@ -0,0 +1,47 @@ +// Copyright (c) 2014-2018, MyMonero.com +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +// This file is here merely to share configuration +// +exports.bridgedFn_names = +[ + "is_subaddress", + "is_integrated_address", + "new_payment_id", + "new__int_addr_from_addr_and_short_pid", + "new_fake_address_for_rct_tx", + "decode_address", + "newly_created_wallet", + "are_equal_mnemonics", + "mnemonic_from_seed", + "seed_and_keys_from_mnemonic", + "validate_components_for_login", + "generate_key_image", + "create_signed_transaction", + "create_signed_transaction__nonIPCsafe" +]; diff --git a/monero_utils/monero_sendingFunds_utils.js b/monero_utils/monero_sendingFunds_utils.js index c633898..0630e74 100644 --- a/monero_utils/monero_sendingFunds_utils.js +++ b/monero_utils/monero_sendingFunds_utils.js @@ -28,8 +28,6 @@ // "use strict"; // -const async = require("async"); -// const monero_config = require("./monero_config"); const monero_utils_promise = require('./monero_utils') const monero_amount_format_utils = require("./monero_amount_format_utils"); @@ -259,6 +257,7 @@ function SendFunds( [targetDescription], // requires a list of descriptions - but SendFunds was // not written with multiple target support as MyMonero does not yet support it nettype, + monero_utils, function(err, moneroReady_targetDescriptions) { if (err) { __trampolineFor_err_withErr(err); @@ -737,69 +736,69 @@ function new_moneroReadyTargetDescriptions_fromTargetDescriptions( monero_openalias_utils, targetDescriptions, nettype, + monero_utils, fn, // fn: (err, moneroReady_targetDescriptions) -> Void + // TODO: remove this fn - this is a sync method now ) { // parse & normalize the target descriptions by mapping them to currency (Monero)-ready addresses & amounts // some pure function declarations for the map we'll do on targetDescriptions - async.mapSeries( - targetDescriptions, - function(targetDescription, cb) { - if (!targetDescription.address && !targetDescription.amount) { - // PSNote: is this check rigorous enough? - const errStr = - "Please supply a target address and a target amount."; - const err = new Error(errStr); - cb(err); - return; - } - const targetDescription_address = targetDescription.address; - const targetDescription_amount = "" + targetDescription.amount; // we are converting it to a string here because parseMoney expects a string - // now verify/parse address and amount - if ( - monero_openalias_utils.DoesStringContainPeriodChar_excludingAsXMRAddress_qualifyingAsPossibleOAAddress( - targetDescription_address, - ) == true - ) { - throw "You must resolve this OA address to a Monero address before calling SendFunds"; - } - // otherwise this should be a normal, single Monero public address - try { - monero_utils.decode_address(targetDescription_address, nettype); // verify that the address is valid - } catch (e) { - const errStr = - "Couldn't decode address " + - targetDescription_address + - ": " + - e; - const err = new Error(errStr); - cb(err); - return; - } - // amount: - var moneroReady_amountToSend; // possibly need this ; here for the JS parser - try { - moneroReady_amountToSend = monero_amount_format_utils.parseMoney( - targetDescription_amount, - ); - } catch (e) { - const errStr = - "Couldn't parse amount " + - targetDescription_amount + - ": " + - e; - const err = new Error(errStr); - cb(err); - return; - } - cb(null, { - address: targetDescription_address, - amount: moneroReady_amountToSend, - }); - }, - function(err, moneroReady_targetDescriptions) { - fn(err, moneroReady_targetDescriptions); - }, - ); + // + const moneroReady_targetDescriptions = []; + for (var i = 0 ; i < targetDescriptions.length ; i++) { + const targetDescription = targetDescriptions[i]; + if (!targetDescription.address && !targetDescription.amount) { + // PSNote: is this check rigorous enough? + const errStr = + "Please supply a target address and a target amount."; + const err = new Error(errStr); + fn(err); + return; + } + const targetDescription_address = targetDescription.address; + const targetDescription_amount = "" + targetDescription.amount; // we are converting it to a string here because parseMoney expects a string + // now verify/parse address and amount + if ( + monero_openalias_utils.DoesStringContainPeriodChar_excludingAsXMRAddress_qualifyingAsPossibleOAAddress( + targetDescription_address, + ) == true + ) { + throw "You must resolve this OA address to a Monero address before calling SendFunds"; + } + // otherwise this should be a normal, single Monero public address + try { + monero_utils.decode_address(targetDescription_address, nettype); // verify that the address is valid + } catch (e) { + const errStr = + "Couldn't decode address " + + targetDescription_address + + ": " + + e; + const err = new Error(errStr); + fn(err); + return; + } + // amount: + var moneroReady_amountToSend; // possibly need this ; here for the JS parser + try { + moneroReady_amountToSend = monero_amount_format_utils.parseMoney( + targetDescription_amount, + ); + } catch (e) { + const errStr = + "Couldn't parse amount " + + targetDescription_amount + + ": " + + e; + const err = new Error(errStr); + fn(err); + return; + } + moneroReady_targetDescriptions.push({ + address: targetDescription_address, + amount: moneroReady_amountToSend, + }); + } + fn(null, moneroReady_targetDescriptions); } function __randomIndex(list) { return Math.floor(Math.random() * list.length); diff --git a/monero_utils/monero_utils.js b/monero_utils/monero_utils.js index 5ec9ca1..d07e4af 100644 --- a/monero_utils/monero_utils.js +++ b/monero_utils/monero_utils.js @@ -28,11 +28,88 @@ // "use strict"; // -const promise = require('./MyMoneroCoreBridge')({}) // this returns a promise -promise.catch(function(e) +const ENVIRONMENT_IS_WEB = typeof window==="object"; +const ENVIRONMENT_IS_WORKER = typeof importScripts==="function"; +const ENVIRONMENT_IS_NODE = typeof process==="object" && process.browser !== true && typeof require==="function" && ENVIRONMENT_IS_WORKER == false; // we want this to be true for Electron but not for a WebView +const wants_electronRemote = (ENVIRONMENT_IS_NODE&&ENVIRONMENT_IS_WEB)/*this may become insufficient*/ + || (typeof window !== 'undefined' && window.IsElectronRendererProcess == true); +// +const moneroUtils_promise = new Promise(function(resolve, reject) { - console.log("Error: ", e); - // this may be insufficient… being able to throw would be nice + function _didLoad(coreBridge_instance) + { + if (coreBridge_instance == null) { + throw "Unable to make coreBridge_instance" + } + const local_fns = {}; + const fn_names = require('./__bridged_fns_spec').bridgedFn_names; + for (const i in fn_names) { + const name = fn_names[i] + local_fns[name] = function() + { + const retVal = coreBridge_instance[name].apply(coreBridge_instance, arguments); // called on the cached value + if (typeof retVal === "object") { + const err_msg = retVal.err_msg + if (typeof err_msg !== 'undefined' && err_msg) { + throw err_msg; // because we can't throw from electron remote w/o killing fn call + // ... and because parsing out this err_msg everywhere is sorta inefficient + } + } + return retVal; + } + } + resolve(local_fns); + } + if (wants_electronRemote) { + // Require file again except on the main process ... + // this avoids a host of issues running wasm on the renderer side, + // for right now until we can load such files raw w/o unsafe-eval + // script-src CSP. makes calls synchronous. if that is a perf problem + // we can make API async. + // + // Resolves relative to the entrypoint of the main process. + const remoteModule = require('electron').remote.require( + "../mymonero_core_js/monero_utils/__IPCSafe_remote_monero_utils" + ); + // Oftentimes this will be ready right away.. somehow.. but just in case.. the readiness + // state promise behavior should be preserved by the following codepath... + var _try; + function __retryAfter(attemptN) + { + console.warn("Checking remote module readiness again after a few ms...") + setTimeout(function() + { + _try(attemptN + 1) + }, 10) + } + _try = function(attemptN) + { + if (attemptN > 10000) { + throw "Expected remote module to be ready" + } + if (remoteModule.isReady) { + _didLoad(remoteModule); + } else { + __retryAfter(attemptN) + } + } + _try(0) + } else { + const coreBridgeLoading_promise = require('./MyMoneroCoreBridge')({}); // this returns a promise + coreBridgeLoading_promise.catch(function(e) + { + console.error("Error: ", e); + // this may be insufficient… being able to throw would be nice + reject(e); + }); + coreBridgeLoading_promise.then(_didLoad); + } }); // -module.exports = promise \ No newline at end of file +// +// Since we actually are constructing bridge functions we technically have the export ready +// synchronously but that would lose the ability to wait until the core bridge is actually ready. +// +// TODO: in future, possibly return function which takes options instead to support better env. +// +module.exports = moneroUtils_promise; \ No newline at end of file