From 9f12c26d44a0d78f28af25056857b993f80bbd95 Mon Sep 17 00:00:00 2001 From: Dan Date: Thu, 19 Apr 2018 13:08:56 -0230 Subject: Even more documentation for various controllers and libs. --- app/scripts/lib/account-tracker.js | 81 ++++++++++++-- app/scripts/lib/extractEthjsErrorMessage.js | 23 ++-- app/scripts/lib/getObjStructure.js | 17 +++ app/scripts/lib/message-manager.js | 145 +++++++++++++++++++++++++- app/scripts/lib/notification-manager.js | 9 +- app/scripts/lib/pending-balance-calculator.js | 38 ++++++- app/scripts/lib/personal-message-manager.js | 142 ++++++++++++++++++++++++- app/scripts/lib/seed-phrase-verifier.js | 18 +++- app/scripts/lib/typed-message-manager.js | 138 ++++++++++++++++++++++++ 9 files changed, 571 insertions(+), 40 deletions(-) (limited to 'app/scripts/lib') diff --git a/app/scripts/lib/account-tracker.js b/app/scripts/lib/account-tracker.js index 8c3dd8c71..1b707b2bb 100644 --- a/app/scripts/lib/account-tracker.js +++ b/app/scripts/lib/account-tracker.js @@ -16,6 +16,24 @@ function noop () {} class AccountTracker extends EventEmitter { + /** + * This module is responsible for tracking any number of accounts and caching their current balances & transaction + * counts. + * + * It also tracks transaction hashes, and checks their inclusion status on each new block. + * + * @typedef {Object} AccountTracker + * @param {Object} opts Initialize various properties of the class. + * @property {Object} store The stored object containing all accounts to track, as well as the current block's gas limit. + * @property {Object} store.accounts The accounts currently stored in this AccountTracker + * @property {string} store.currentBlockGasLimit A hex string indicating the gas limit of the current block + * @property {Object} _provider A provider needed to create the EthQuery instance used within this AccountTracker. + * @property {EthQuery} _query An EthQuery instance used to access account information from the blockchain + * @property {BlockTracker} _blockTracker A BlockTracker instance. Needed to ensure that accounts and their info updates + * when a new block is created. + * @property {Object} _currentBlockNumber Reference to a property on the _blockTracker: the number (i.e. an id) of the the current block + * + */ constructor (opts = {}) { super() @@ -34,10 +52,17 @@ class AccountTracker extends EventEmitter { this._currentBlockNumber = this._blockTracker.currentBlock } - // - // public - // - + /** + * Ensures that the locally stored accounts are in sync with a set of accounts stored externally to this + * AccountTracker. + * + * Once this AccountTracker's accounts are up to date with those referenced by the passed addresses, each + * of these accounts are given an updated balance via Ethquery. + * + * @param {array} address The array of hex addresses for accounts with which this AccountTracker's accounts should be + * in sync + * + */ syncWithAddresses (addresses) { const accounts = this.store.getState().accounts const locals = Object.keys(accounts) @@ -61,6 +86,13 @@ class AccountTracker extends EventEmitter { this._updateAccounts() } + /** + * Adds a new address to this AccountTracker's accounts object, which points to an empty object. This object will be + * given a balance as long this._currentBlockNumber is defined. + * + * @param {string} address A hex address of a new account to store in this AccountTracker's accounts object + * + */ addAccount (address) { const accounts = this.store.getState().accounts accounts[address] = {} @@ -69,16 +101,27 @@ class AccountTracker extends EventEmitter { this._updateAccount(address) } + /** + * Removes an account from this AccountTracker's accounts object + * + * @param {string} address A hex address of a the account to remove + * + */ removeAccount (address) { const accounts = this.store.getState().accounts delete accounts[address] this.store.updateState({ accounts }) } - // - // private - // - + /** + * Given a block, updates this AccountTracker's currentBlockGasLimit, and then updates each local account's balance + * via EthQuery + * + * @private + * @param {object} block Data about the block that contains the data to update to. + * @fires 'block' The updated state, if all account updates are successfull + * + */ _updateForBlock (block) { this._currentBlockNumber = block.number const currentBlockGasLimit = block.gasLimit @@ -93,12 +136,26 @@ class AccountTracker extends EventEmitter { }) } + /** + * Calls this._updateAccount for each account in this.store + * + * @param {Function} cb A callback to pass to this._updateAccount, called after each account is succesfully updated + * + */ _updateAccounts (cb = noop) { const accounts = this.store.getState().accounts const addresses = Object.keys(accounts) async.each(addresses, this._updateAccount.bind(this), cb) } + /** + * Updates the current balance of an account. Gets an updated balance via this._getAccount. + * + * @private + * @param {string} address A hex address of a the account to be updated + * @param {Function} cb A callback to call once the account at address is successfully update + * + */ _updateAccount (address, cb = noop) { this._getAccount(address, (err, result) => { if (err) return cb(err) @@ -113,6 +170,14 @@ class AccountTracker extends EventEmitter { }) } + /** + * Gets the current balance of an account via EthQuery. + * + * @private + * @param {string} address A hex address of a the account to query + * @param {Function} cb A callback to call once the account at address is successfully update + * + */ _getAccount (address, cb = noop) { const query = this._query async.parallel({ diff --git a/app/scripts/lib/extractEthjsErrorMessage.js b/app/scripts/lib/extractEthjsErrorMessage.js index bac541735..3f0fb0232 100644 --- a/app/scripts/lib/extractEthjsErrorMessage.js +++ b/app/scripts/lib/extractEthjsErrorMessage.js @@ -4,17 +4,18 @@ const errorLabelPrefix = 'Error: ' module.exports = extractEthjsErrorMessage -// -// ethjs-rpc provides overly verbose error messages -// if we detect this type of message, we extract the important part -// Below is an example input and output -// -// Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced -// -// Transaction Failed: replacement transaction underpriced -// - - +/** + * Extracts the important part of an ethjs-rpc error message. If the passed error is not anis isEthjsRpcError, the error + * is returned unchanged. + * + * @param {string} errorMessage The error message to parse + * @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError + * + * @example + * // returns 'Transaction Failed: replacement transaction underpriced' + * extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`) + * +*/ function extractEthjsErrorMessage(errorMessage) { const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug) if (isEthjsRpcError) { diff --git a/app/scripts/lib/getObjStructure.js b/app/scripts/lib/getObjStructure.js index 3db389507..52250d3fb 100644 --- a/app/scripts/lib/getObjStructure.js +++ b/app/scripts/lib/getObjStructure.js @@ -14,6 +14,15 @@ module.exports = getObjStructure // } // } +/** + * Creates an object that represents the structure of the given object. It replaces all values with the result of their + * type. + * + * @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class. + * @returns {object} The "mapped" version of a deep clone of the passed object, with each non-object property value + * replaced with the javascript type of that value. + * + */ function getObjStructure(obj) { const structure = clone(obj) return deepMap(structure, (value) => { @@ -21,6 +30,14 @@ function getObjStructure(obj) { }) } +/** + * Modifies all the properties and deeply nested of a passed object. Iterates recursively over all nested objects and + * their properties, and covers the entire depth of the object. At each property value which is not an object is modified. + * + * @param {object} target The object to modify + * @param {Function} visit The modifier to apply to each non-object property value + * @returns {object} The modified object + */ function deepMap(target = {}, visit) { Object.entries(target).forEach(([key, value]) => { if (typeof value === 'object' && value !== null) { diff --git a/app/scripts/lib/message-manager.js b/app/scripts/lib/message-manager.js index f52e048e0..52b897720 100644 --- a/app/scripts/lib/message-manager.js +++ b/app/scripts/lib/message-manager.js @@ -3,8 +3,45 @@ const ObservableStore = require('obs-store') const ethUtil = require('ethereumjs-util') const createId = require('./random-id') +var msgData = { + id: msgId, + msgParams: msgParams, + time: time, + status: 'unapproved', + type: 'eth_sign', +} + +/** + * Represents, and contains data about, an 'eth_sign' type signature request. These are created when a signature for + * an eth_sign call is requested. + * + * @see {@link https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign} + * + * @typedef {Object} Message + * @property {number} id An id to track and identify the message object + * @property {object} msgParams The parameters to pass to the eth_sign method once the signature request is approved. + * @property {object} msgParams.metamaskId Added to msgParams for tracking and identification within Metamask. + * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request + * @property {number} time The epoch time at which the this message was created + * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected' + * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' with + * always have a 'eth_sign' type. + * + */ module.exports = class MessageManager extends EventEmitter { + + /** + * Controller in charge of managing - storing, adding, removing, updating - Messages. + * + * @typedef {Object} MessageManager + * @param {object} opts @deprecated + * @property {object} memStore The observable store where Messages are saved with persistance. + * @property {object} memStore.unapprovedMsgs A collection of all Messages in the 'unapproved' state + * @property {number} memStore.unapprovedMsgCount The count of all Messages in this.memStore.unapprobedMsgs + * @property {array} messages Holds all messages that have been created by this MessageManager + * + */ constructor (opts) { super() this.memStore = new ObservableStore({ @@ -14,15 +51,35 @@ module.exports = class MessageManager extends EventEmitter { this.messages = [] } + /** + * A getter for the number of 'unapproved' Messages in this.messages + * + * @returns {number} The number of 'unapproved' Messages in this.messages + * + */ get unapprovedMsgCount () { return Object.keys(this.getUnapprovedMsgs()).length } + /** + * A getter for the 'unapproved' Messages in this.messages + * + * @returns {object} An index of Message ids to Messages, for all 'unapproved' Messages in this.messages + * + */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } + /** + * Creates a new Message with an 'unapproved' status using the passed msgParams. this.addMsg is called to add the + * new Message to this.messages, and to save the unapproved Messages from that list to this.memStore. + * + * @param {object} msgParams The params for the eth_sign call to be made after the message is approved. + * @returns {number} The id of the newly created message. + * + */ addUnapprovedMessage (msgParams) { msgParams.data = normalizeMsgData(msgParams.data) // create txData obj with parameters and meta data @@ -42,24 +99,61 @@ module.exports = class MessageManager extends EventEmitter { return msgId } + /** + * Adds a passed Message to this.messages, and calls this._saveMsgList() to save the unapproved Messages from that + * list to this.memStore. + * + * @param {Message} msg The Message to add to this.messages + * + */ addMsg (msg) { this.messages.push(msg) this._saveMsgList() } + /** + * Returns a specified Message. + * + * @param {number} msgId The id of the Message to get + * @returns {Message|undefined} The Message with the id that matches the passed msgId, or undefined if no Message has that id. + * + */ getMsg (msgId) { return this.messages.find(msg => msg.id === msgId) } + /** + * Approves a Message. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise with + * any the message params modified for proper signing. + * + * @param {object} msgParams The msgParams to be used when eth_sign is called, plus data added by Metamask. + * @param {object} msgParams.metamaskId Added to msgParams for tracking and identification within Metamask. + * @returns {Promise} Promises the msgParams object with metamaskId removed. + * + */ approveMessage (msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForSigning(msgParams) } + /** + * Sets a Message status to 'approved' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the Message to approve. + * + */ setMsgStatusApproved (msgId) { this._setMsgStatus(msgId, 'approved') } + /** + * Sets a Message status to 'signed' via a call to this._setMsgStatus and updates that Message in this.messages by + * adding the raw signature data of the signature request to the Message + * + * @param {number} msgId The id of the Message to sign. + * @param {buffer} rawSig The raw data of the signature request + * + */ setMsgStatusSigned (msgId, rawSig) { const msg = this.getMsg(msgId) msg.rawSig = rawSig @@ -67,19 +161,40 @@ module.exports = class MessageManager extends EventEmitter { this._setMsgStatus(msgId, 'signed') } + /** + * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams + * + * @param {object} msgParams The msgParams to modify + * @returns {Promise} Promises the msgParams with the metamaskId property removed + * + */ prepMsgForSigning (msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } + /** + * Sets a Message status to 'rejected' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the Message to reject. + * + */ rejectMsg (msgId) { this._setMsgStatus(msgId, 'rejected') } - // - // PRIVATE METHODS - // - + /** + * Updates the status of a Message in this.messages via a call to this._updateMsg + * + * @private + * @param {number} msgId The id of the Message to update. + * @param {string} status The new status of the Message. + * @throws A 'MessageManager - Message not found for id: "${msgId}".' if there is no Message in this.messages with an + * id equal to the passed msgId + * @fires An event with a name equal to `${msgId}:${status}`. The Message is also fired. + * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along with the message + * + */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) if (!msg) throw new Error('MessageManager - Message not found for id: "${msgId}".') @@ -91,6 +206,14 @@ module.exports = class MessageManager extends EventEmitter { } } + /** + * Sets a Message in this.messages to the passed Message if the ids are equal. Then saves the unapprovedMsg list to + * storage via this._saveMsgList + * + * @private + * @param {msg} Message A Message that will replace an existing Message (with the same id) in this.messages + * + */ _updateMsg (msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { @@ -99,6 +222,13 @@ module.exports = class MessageManager extends EventEmitter { this._saveMsgList() } + /** + * Saves the unapproved messages, and their count, to this.memStore + * + * @private + * @fires 'updateBadge' + * + */ _saveMsgList () { const unapprovedMsgs = this.getUnapprovedMsgs() const unapprovedMsgCount = Object.keys(unapprovedMsgs).length @@ -108,6 +238,13 @@ module.exports = class MessageManager extends EventEmitter { } +/** + * A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex. + * + * @param {any} data The buffer data to convert to a hex + * @returns {string} A hex string conversion of the buffer data + * + */ function normalizeMsgData (data) { if (data.slice(0, 2) === '0x') { // data is already hex diff --git a/app/scripts/lib/notification-manager.js b/app/scripts/lib/notification-manager.js index 1fcb7cf69..ce4825d5b 100644 --- a/app/scripts/lib/notification-manager.js +++ b/app/scripts/lib/notification-manager.js @@ -5,9 +5,12 @@ const width = 360 class NotificationManager { - // - // Public - // + /** + * A collection of methods for controlling the showing and hiding of the notification popup. + * + * @typedef {Object} NotificationManager + * + */ showPopup () { this._getPopup((err, popup) => { diff --git a/app/scripts/lib/pending-balance-calculator.js b/app/scripts/lib/pending-balance-calculator.js index 6ae526463..0f1dc19a9 100644 --- a/app/scripts/lib/pending-balance-calculator.js +++ b/app/scripts/lib/pending-balance-calculator.js @@ -3,16 +3,28 @@ const normalize = require('eth-sig-util').normalize class PendingBalanceCalculator { - // Must be initialized with two functions: - // getBalance => Returns a promise of a BN of the current balance in Wei - // getPendingTransactions => Returns an array of TxMeta Objects, - // which have txParams properties, which include value, gasPrice, and gas, - // all in a base=16 hex format. + /** + * Used for calculating a users "pending balance": their current balance minus the total possible cost of all their + * pending transactions. + * + * @typedef {Object} PendingBalanceCalculator + * @param {Function} getBalance Returns a promise of a BN of the current balance in Wei + * @param {Function} getPendingTransactions Returns an array of TxMeta Objects, which have txParams properties, + * which include value, gasPrice, and gas, all in a base=16 hex format. + * + */ constructor ({ getBalance, getPendingTransactions }) { this.getPendingTransactions = getPendingTransactions this.getNetworkBalance = getBalance } + /** + * Returns the users "pending balance": their current balance minus the total possible cost of all their + * pending transactions. + * + * @returns {Promise} Promises a base 16 hex string that contains the user's "pending balance" + * + */ async getBalance () { const results = await Promise.all([ this.getNetworkBalance(), @@ -29,6 +41,15 @@ class PendingBalanceCalculator { return `0x${balance.sub(pendingValue).toString(16)}` } + /** + * Calculates the maximum possible cost of a single transaction, based on the value, gas price and gas limit. + * + * @param {object} tx Contains all that data about a transaction. + * @property {object} tx.txParams Contains data needed to calculate the maximum cost of the transaction: gas, + * gasLimit and value. + * + * @returns {string} Returns a base 16 hex string that contains the maximum possible cost of the transaction. + */ calculateMaxCost (tx) { const txValue = tx.txParams.value const value = this.hexToBn(txValue) @@ -42,6 +63,13 @@ class PendingBalanceCalculator { return value.add(gasCost) } + /** + * Converts a hex string to a BN object + * + * @param {string} hex A number represented as a hex string + * @returns {Object} A BN object + * + */ hexToBn (hex) { return new BN(normalize(hex).substring(2), 16) } diff --git a/app/scripts/lib/personal-message-manager.js b/app/scripts/lib/personal-message-manager.js index 6602f5aa8..3c502329c 100644 --- a/app/scripts/lib/personal-message-manager.js +++ b/app/scripts/lib/personal-message-manager.js @@ -4,8 +4,37 @@ const ethUtil = require('ethereumjs-util') const createId = require('./random-id') const hexRe = /^[0-9A-Fa-f]+$/g +/** + * Represents, and contains data about, an 'personal_sign' type signature request. These are created when a + * signature for an personal_sign call is requested. + * + * @see {@link https://web3js.readthedocs.io/en/1.0/web3-eth-personal.html#sign} + * + * @typedef {Object} PersonalMessage + * @property {number} id An id to track and identify the message object + * @property {object} msgParams The parameters to pass to the personal_sign method once the signature request is + * approved. + * @property {object} msgParams.metamaskId Added to msgParams for tracking and identification within Metamask. + * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request + * @property {number} time The epoch time at which the this message was created + * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected' + * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will + * always have a 'personal_sign' type. + * + */ module.exports = class PersonalMessageManager extends EventEmitter { + /** + * Controller in charge of managing - storing, adding, removing, updating - PersonalMessage. + * + * @typedef {Object} PersonalMessageManager + * @param {object} opts @deprecated + * @property {object} memStore The observable store where PersonalMessage are saved with persistance. + * @property {object} memStore.unapprovedPersonalMsgs A collection of all PersonalMessages in the 'unapproved' state + * @property {number} memStore.unapprovedPersonalMsgCount The count of all PersonalMessages in this.memStore.unapprobedMsgs + * @property {array} messages Holds all messages that have been created by this PersonalMessageManager + * + */ constructor (opts) { super() this.memStore = new ObservableStore({ @@ -15,15 +44,37 @@ module.exports = class PersonalMessageManager extends EventEmitter { this.messages = [] } + /** + * A getter for the number of 'unapproved' PersonalMessages in this.messages + * + * @returns {number} The number of 'unapproved' PersonalMessages in this.messages + * + */ get unapprovedPersonalMsgCount () { return Object.keys(this.getUnapprovedMsgs()).length } + /** + * A getter for the 'unapproved' PersonalMessages in this.messages + * + * @returns {object} An index of PersonalMessage ids to PersonalMessages, for all 'unapproved' PersonalMessages in + * this.messages + * + */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } + /** + * Creates a new PersonalMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new PersonalMessage to this.messages, and to save the unapproved PersonalMessages from that list to + * this.memStore. + * + * @param {object} msgParams The params for the eth_sign call to be made after the message is approved. + * @returns {number} The id of the newly created PersonalMessage. + * + */ addUnapprovedMessage (msgParams) { log.debug(`PersonalMessageManager addUnapprovedMessage: ${JSON.stringify(msgParams)}`) msgParams.data = this.normalizeMsgData(msgParams.data) @@ -44,24 +95,62 @@ module.exports = class PersonalMessageManager extends EventEmitter { return msgId } + /** + * Adds a passed PersonalMessage to this.messages, and calls this._saveMsgList() to save the unapproved PersonalMessages from that + * list to this.memStore. + * + * @param {Message} msg The PersonalMessage to add to this.messages + * + */ addMsg (msg) { this.messages.push(msg) this._saveMsgList() } + /** + * Returns a specified PersonalMessage. + * + * @param {number} msgId The id of the PersonalMessage to get + * @returns {PersonalMessage|undefined} The PersonalMessage with the id that matches the passed msgId, or undefined + * if no PersonalMessage has that id. + * + */ getMsg (msgId) { return this.messages.find(msg => msg.id === msgId) } + /** + * Approves a PersonalMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise + * with any the message params modified for proper signing. + * + * @param {object} msgParams The msgParams to be used when eth_sign is called, plus data added by Metamask. + * @param {object} msgParams.metamaskId Added to msgParams for tracking and identification within Metamask. + * @returns {Promise} Promises the msgParams object with metamaskId removed. + * + */ approveMessage (msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForSigning(msgParams) } + /** + * Sets a PersonalMessage status to 'approved' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the PersonalMessage to approve. + * + */ setMsgStatusApproved (msgId) { this._setMsgStatus(msgId, 'approved') } + /** + * Sets a PersonalMessage status to 'signed' via a call to this._setMsgStatus and updates that PersonalMessage in + * this.messages by adding the raw signature data of the signature request to the PersonalMessage + * + * @param {number} msgId The id of the PersonalMessage to sign. + * @param {buffer} rawSig The raw data of the signature request + * + */ setMsgStatusSigned (msgId, rawSig) { const msg = this.getMsg(msgId) msg.rawSig = rawSig @@ -69,19 +158,41 @@ module.exports = class PersonalMessageManager extends EventEmitter { this._setMsgStatus(msgId, 'signed') } + /** + * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams + * + * @param {object} msgParams The msgParams to modify + * @returns {Promise} Promises the msgParams with the metamaskId property removed + * + */ prepMsgForSigning (msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } + /** + * Sets a PersonalMessage status to 'rejected' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the PersonalMessage to reject. + * + */ rejectMsg (msgId) { this._setMsgStatus(msgId, 'rejected') } - // - // PRIVATE METHODS - // - + /** + * Updates the status of a PersonalMessage in this.messages via a call to this._updateMsg + * + * @private + * @param {number} msgId The id of the PersonalMessage to update. + * @param {string} status The new status of the PersonalMessage. + * @throws A 'PersonalMessageManager - PersonalMessage not found for id: "${msgId}".' if there is no PersonalMessage + * in this.messages with an id equal to the passed msgId + * @fires An event with a name equal to `${msgId}:${status}`. The PersonalMessage is also fired. + * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along + * with the PersonalMessage + * + */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) if (!msg) throw new Error('PersonalMessageManager - Message not found for id: "${msgId}".') @@ -93,6 +204,15 @@ module.exports = class PersonalMessageManager extends EventEmitter { } } + /** + * Sets a PersonalMessage in this.messages to the passed PersonalMessage if the ids are equal. Then saves the + * unapprovedPersonalMsgs index to storage via this._saveMsgList + * + * @private + * @param {msg} PersonalMessage A PersonalMessage that will replace an existing PersonalMessage (with the same + * id) in this.messages + * + */ _updateMsg (msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { @@ -101,6 +221,13 @@ module.exports = class PersonalMessageManager extends EventEmitter { this._saveMsgList() } + /** + * Saves the unapproved PersonalMessages, and their count, to this.memStore + * + * @private + * @fires 'updateBadge' + * + */ _saveMsgList () { const unapprovedPersonalMsgs = this.getUnapprovedMsgs() const unapprovedPersonalMsgCount = Object.keys(unapprovedPersonalMsgs).length @@ -108,6 +235,13 @@ module.exports = class PersonalMessageManager extends EventEmitter { this.emit('updateBadge') } + /** + * A helper function that converts raw buffer data to a hex, or just returns the data if it is already formatted as a hex. + * + * @param {any} data The buffer data to convert to a hex + * @returns {string} A hex string conversion of the buffer data + * + */ normalizeMsgData (data) { try { const stripped = ethUtil.stripHexPrefix(data) diff --git a/app/scripts/lib/seed-phrase-verifier.js b/app/scripts/lib/seed-phrase-verifier.js index 9cea22029..826e73306 100644 --- a/app/scripts/lib/seed-phrase-verifier.js +++ b/app/scripts/lib/seed-phrase-verifier.js @@ -2,11 +2,19 @@ const KeyringController = require('eth-keyring-controller') const seedPhraseVerifier = { - // Verifies if the seed words can restore the accounts. - // - // The seed words can recreate the primary keyring and the accounts belonging to it. - // The created accounts in the primary keyring are always the same. - // The keyring always creates the accounts in the same sequence. + /** + * Verifies if the seed words can restore the accounts. + * + * Key notes: + * - The seed words can recreate the primary keyring and the accounts belonging to it. + * - The created accounts in the primary keyring are always the same. + * - The keyring always creates the accounts in the same sequence. + * + * @param {array} createdAccounts The accounts to restore + * @param {string} seedWords The seed words to verify + * @returns {Promise} Promises undefined + * + */ verifyAccounts (createdAccounts, seedWords) { return new Promise((resolve, reject) => { diff --git a/app/scripts/lib/typed-message-manager.js b/app/scripts/lib/typed-message-manager.js index 8b760790e..8716ebf9a 100644 --- a/app/scripts/lib/typed-message-manager.js +++ b/app/scripts/lib/typed-message-manager.js @@ -4,8 +4,38 @@ const createId = require('./random-id') const assert = require('assert') const sigUtil = require('eth-sig-util') +/** + * Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a + * signature for an eth_signTypedData call is requested. + * + * @see {@link } + * + * @typedef {Object} TypedMessage + * @property {number} id An id to track and identify the message object + * @property {object} msgParams The parameters to pass to the eth_signTypedData method once the signature request is + * approved. + * @property {object} msgParams.metamaskId Added to msgParams for tracking and identification within Metamask. + * @property {object} msgParams.from The address that is making the signature request. + * @property {string} msgParams.data A hex string conversion of the raw buffer data of the signature request + * @property {number} time The epoch time at which the this message was created + * @property {string} status Indicates whether the signature request is 'unapproved', 'approved', 'signed' or 'rejected' + * @property {string} type The json-prc signing method for which a signature request has been made. A 'Message' will + * always have a 'eth_signTypedData' type. + * + */ module.exports = class TypedMessageManager extends EventEmitter { + /** + * Controller in charge of managing - storing, adding, removing, updating - TypedMessage. + * + * @typedef {Object} TypedMessage + * @param {object} opts @deprecated + * @property {object} memStore The observable store where TypedMessage are saved with persistance. + * @property {object} memStore.unapprovedTypedMessages A collection of all TypedMessages in the 'unapproved' state + * @property {number} memStore.unapprovedTypedMessagesCount The count of all TypedMessages in this.memStore.unapprobedMsgs + * @property {array} messages Holds all messages that have been created by this TypedMessage + * + */ constructor (opts) { super() this.memStore = new ObservableStore({ @@ -15,15 +45,37 @@ module.exports = class TypedMessageManager extends EventEmitter { this.messages = [] } + /** + * A getter for the number of 'unapproved' TypedMessages in this.messages + * + * @returns {number} The number of 'unapproved' TypedMessages in this.messages + * + */ get unapprovedTypedMessagesCount () { return Object.keys(this.getUnapprovedMsgs()).length } + /** + * A getter for the 'unapproved' TypedMessages in this.messages + * + * @returns {object} An index of TypedMessage ids to TypedMessages, for all 'unapproved' TypedMessages in + * this.messages + * + */ getUnapprovedMsgs () { return this.messages.filter(msg => msg.status === 'unapproved') .reduce((result, msg) => { result[msg.id] = msg; return result }, {}) } + /** + * Creates a new TypedMessage with an 'unapproved' status using the passed msgParams. this.addMsg is called to add + * the new TypedMessage to this.messages, and to save the unapproved TypedMessages from that list to + * this.memStore. Before any of this is done, msgParams are validated + * + * @param {object} msgParams The params for the eth_sign call to be made after the message is approved. + * @returns {number} The id of the newly created TypedMessage. + * + */ addUnapprovedMessage (msgParams) { this.validateParams(msgParams) @@ -45,6 +97,12 @@ module.exports = class TypedMessageManager extends EventEmitter { return msgId } + /** + * Helper method for this.addUnapprovedMessage. Validates that the passed params have the required properties. + * + * @param {object} params The params to validate + * + */ validateParams (params) { assert.equal(typeof params, 'object', 'Params should ben an object.') assert.ok('data' in params, 'Params must include a data field.') @@ -56,24 +114,62 @@ module.exports = class TypedMessageManager extends EventEmitter { }, 'Expected EIP712 typed data') } + /** + * Adds a passed TypedMessage to this.messages, and calls this._saveMsgList() to save the unapproved TypedMessages from that + * list to this.memStore. + * + * @param {Message} msg The TypedMessage to add to this.messages + * + */ addMsg (msg) { this.messages.push(msg) this._saveMsgList() } + /** + * Returns a specified TypedMessage. + * + * @param {number} msgId The id of the TypedMessage to get + * @returns {TypedMessage|undefined} The TypedMessage with the id that matches the passed msgId, or undefined + * if no TypedMessage has that id. + * + */ getMsg (msgId) { return this.messages.find(msg => msg.id === msgId) } + /** + * Approves a TypedMessage. Sets the message status via a call to this.setMsgStatusApproved, and returns a promise + * with any the message params modified for proper signing. + * + * @param {object} msgParams The msgParams to be used when eth_sign is called, plus data added by Metamask. + * @param {object} msgParams.metamaskId Added to msgParams for tracking and identification within Metamask. + * @returns {Promise} Promises the msgParams object with metamaskId removed. + * + */ approveMessage (msgParams) { this.setMsgStatusApproved(msgParams.metamaskId) return this.prepMsgForSigning(msgParams) } + /** + * Sets a TypedMessage status to 'approved' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the TypedMessage to approve. + * + */ setMsgStatusApproved (msgId) { this._setMsgStatus(msgId, 'approved') } + /** + * Sets a TypedMessage status to 'signed' via a call to this._setMsgStatus and updates that TypedMessage in + * this.messages by adding the raw signature data of the signature request to the TypedMessage + * + * @param {number} msgId The id of the TypedMessage to sign. + * @param {buffer} rawSig The raw data of the signature request + * + */ setMsgStatusSigned (msgId, rawSig) { const msg = this.getMsg(msgId) msg.rawSig = rawSig @@ -81,11 +177,24 @@ module.exports = class TypedMessageManager extends EventEmitter { this._setMsgStatus(msgId, 'signed') } + /** + * Removes the metamaskId property from passed msgParams and returns a promise which resolves the updated msgParams + * + * @param {object} msgParams The msgParams to modify + * @returns {Promise} Promises the msgParams with the metamaskId property removed + * + */ prepMsgForSigning (msgParams) { delete msgParams.metamaskId return Promise.resolve(msgParams) } + /** + * Sets a TypedMessage status to 'rejected' via a call to this._setMsgStatus. + * + * @param {number} msgId The id of the TypedMessage to reject. + * + */ rejectMsg (msgId) { this._setMsgStatus(msgId, 'rejected') } @@ -94,6 +203,19 @@ module.exports = class TypedMessageManager extends EventEmitter { // PRIVATE METHODS // + /** + * Updates the status of a TypedMessage in this.messages via a call to this._updateMsg + * + * @private + * @param {number} msgId The id of the TypedMessage to update. + * @param {string} status The new status of the TypedMessage. + * @throws A 'TypedMessageManager - TypedMessage not found for id: "${msgId}".' if there is no TypedMessage + * in this.messages with an id equal to the passed msgId + * @fires An event with a name equal to `${msgId}:${status}`. The TypedMessage is also fired. + * @fires If status is 'rejected' or 'signed', an event with a name equal to `${msgId}:finished` is fired along + * with the TypedMessage + * + */ _setMsgStatus (msgId, status) { const msg = this.getMsg(msgId) if (!msg) throw new Error('TypedMessageManager - Message not found for id: "${msgId}".') @@ -105,6 +227,15 @@ module.exports = class TypedMessageManager extends EventEmitter { } } + /** + * Sets a TypedMessage in this.messages to the passed TypedMessage if the ids are equal. Then saves the + * unapprovedTypedMsgs index to storage via this._saveMsgList + * + * @private + * @param {msg} TypedMessage A TypedMessage that will replace an existing TypedMessage (with the same + * id) in this.messages + * + */ _updateMsg (msg) { const index = this.messages.findIndex((message) => message.id === msg.id) if (index !== -1) { @@ -113,6 +244,13 @@ module.exports = class TypedMessageManager extends EventEmitter { this._saveMsgList() } + /** + * Saves the unapproved TypedMessages, and their count, to this.memStore + * + * @private + * @fires 'updateBadge' + * + */ _saveMsgList () { const unapprovedTypedMessages = this.getUnapprovedMsgs() const unapprovedTypedMessagesCount = Object.keys(unapprovedTypedMessages).length -- cgit