mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 01:39:44 +01:00
Merge pull request #4040 from MetaMask/dm-docs-2
Even more documentation for various controllers and libs.
This commit is contained in:
commit
ac334d7b1a
@ -4,6 +4,24 @@ const BN = require('ethereumjs-util').BN
|
||||
|
||||
class BalanceController {
|
||||
|
||||
/**
|
||||
* Controller responsible for storing and updating an account's balance.
|
||||
*
|
||||
* @typedef {Object} BalanceController
|
||||
* @param {Object} opts Initialize various properties of the class.
|
||||
* @property {string} address A base 16 hex string. The account address which has the balance managed by this
|
||||
* BalanceController.
|
||||
* @property {AccountTracker} accountTracker Stores and updates the users accounts
|
||||
* for which this BalanceController manages balance.
|
||||
* @property {TransactionController} txController Stores, tracks and manages transactions. Here used to create a listener for
|
||||
* transaction updates.
|
||||
* @property {BlockTracker} blockTracker Tracks updates to blocks. On new blocks, this BalanceController updates its balance
|
||||
* @property {Object} store The store for the ethBalance
|
||||
* @property {string} store.ethBalance A base 16 hex string. The balance for the current account.
|
||||
* @property {PendingBalanceCalculator} balanceCalc Used to calculate the accounts balance with possible pending
|
||||
* transaction costs taken into account.
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
this._validateParams(opts)
|
||||
const { address, accountTracker, txController, blockTracker } = opts
|
||||
@ -26,6 +44,11 @@ class BalanceController {
|
||||
this._registerUpdates()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the ethBalance property to the current pending balance
|
||||
*
|
||||
* @returns {Promise<void>} Promises undefined
|
||||
*/
|
||||
async updateBalance () {
|
||||
const balance = await this.balanceCalc.getBalance()
|
||||
this.store.updateState({
|
||||
@ -33,6 +56,15 @@ class BalanceController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
|
||||
* - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
|
||||
* - when the current account changes (i.e. a new account is selected)
|
||||
* - when there is a block update
|
||||
*
|
||||
* @private
|
||||
*
|
||||
*/
|
||||
_registerUpdates () {
|
||||
const update = this.updateBalance.bind(this)
|
||||
|
||||
@ -51,6 +83,14 @@ class BalanceController {
|
||||
this.blockTracker.on('block', update)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the balance, as a base 16 hex string, of the account at this BalanceController's current address.
|
||||
* If the current account has no balance, returns undefined.
|
||||
*
|
||||
* @returns {Promise<BN|void>} Promises a BN with a value equal to the balance of the current account, or undefined
|
||||
* if the current account has no balance
|
||||
*
|
||||
*/
|
||||
async _getBalance () {
|
||||
const { accounts } = this.accountTracker.store.getState()
|
||||
const entry = accounts[this.address]
|
||||
@ -58,6 +98,14 @@ class BalanceController {
|
||||
return balance ? new BN(balance.substring(2), 16) : undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
|
||||
* TransactionController passed to this BalanceController during construction.
|
||||
*
|
||||
* @private
|
||||
* @returns {Promise<array>} Promises an array of transaction objects.
|
||||
*
|
||||
*/
|
||||
async _getPendingTransactions () {
|
||||
const pending = this.txController.getFilteredTxList({
|
||||
from: this.address,
|
||||
@ -67,6 +115,14 @@ class BalanceController {
|
||||
return pending
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the passed options have all required properties.
|
||||
*
|
||||
* @param {Object} opts The options object to validate
|
||||
* @throws {string} Throw a custom error indicating that address, accountTracker, txController and blockTracker are
|
||||
* missing and at least one is required
|
||||
*
|
||||
*/
|
||||
_validateParams (opts) {
|
||||
const { address, accountTracker, txController, blockTracker } = opts
|
||||
if (!address || !accountTracker || !txController || !blockTracker) {
|
||||
|
@ -10,6 +10,22 @@ const POLLING_INTERVAL = 4 * 60 * 1000
|
||||
|
||||
class BlacklistController {
|
||||
|
||||
/**
|
||||
* Responsible for polling for and storing an up to date 'eth-phishing-detect' config.json file, while
|
||||
* exposing a method that can check whether a given url is a phishing attempt. The 'eth-phishing-detect'
|
||||
* config.json file contains a fuzzylist, whitelist and blacklist.
|
||||
*
|
||||
*
|
||||
* @typedef {Object} BlacklistController
|
||||
* @param {object} opts Overrides the defaults for the initial state of this.store
|
||||
* @property {object} store The the store of the current phishing config
|
||||
* @property {object} store.phishing Contains fuzzylist, whitelist and blacklist arrays. @see
|
||||
* {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
|
||||
* @property {object} _phishingDetector The PhishingDetector instantiated by passing store.phishing to
|
||||
* PhishingDetector.
|
||||
* @property {object} _phishingUpdateIntervalRef Id of the interval created to periodically update the blacklist
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
const initState = extend({
|
||||
phishing: PHISHING_DETECTION_CONFIG,
|
||||
@ -22,16 +38,28 @@ class BlacklistController {
|
||||
this._phishingUpdateIntervalRef = null
|
||||
}
|
||||
|
||||
//
|
||||
// PUBLIC METHODS
|
||||
//
|
||||
|
||||
/**
|
||||
* Given a url, returns the result of checking if that url is in the store.phishing blacklist
|
||||
*
|
||||
* @param {string} hostname The hostname portion of a url; the one that will be checked against the white and
|
||||
* blacklists of store.phishing
|
||||
* @returns {boolean} Whether or not the passed hostname is on our phishing blacklist
|
||||
*
|
||||
*/
|
||||
checkForPhishing (hostname) {
|
||||
if (!hostname) return false
|
||||
const { result } = this._phishingDetector.check(hostname)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries `https://api.infura.io/v2/blacklist` for an updated blacklist config. This is passed to this._phishingDetector
|
||||
* to update our phishing detector instance, and is updated in the store. The new phishing config is returned
|
||||
*
|
||||
*
|
||||
* @returns {Promise<object>} Promises the updated blacklist config for the phishingDetector
|
||||
*
|
||||
*/
|
||||
async updatePhishingList () {
|
||||
const response = await fetch('https://api.infura.io/v2/blacklist')
|
||||
const phishing = await response.json()
|
||||
@ -40,6 +68,11 @@ class BlacklistController {
|
||||
return phishing
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiates the updating of the local blacklist at a set interval. The update is done via this.updatePhishingList().
|
||||
* Also, this method store a reference to that interval at this._phishingUpdateIntervalRef
|
||||
*
|
||||
*/
|
||||
scheduleUpdates () {
|
||||
if (this._phishingUpdateIntervalRef) return
|
||||
this.updatePhishingList().catch(log.warn)
|
||||
@ -48,10 +81,14 @@ class BlacklistController {
|
||||
}, POLLING_INTERVAL)
|
||||
}
|
||||
|
||||
//
|
||||
// PRIVATE METHODS
|
||||
//
|
||||
|
||||
/**
|
||||
* Sets this._phishingDetector to a new PhishingDetector instance.
|
||||
* @see {@link https://github.com/MetaMask/eth-phishing-detect}
|
||||
*
|
||||
* @private
|
||||
* @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
|
||||
*
|
||||
*/
|
||||
_setupPhishingDetector (config) {
|
||||
this._phishingDetector = new PhishingDetector(config)
|
||||
}
|
||||
|
@ -8,8 +8,8 @@ class PreferencesController {
|
||||
*
|
||||
* @typedef {Object} PreferencesController
|
||||
* @param {object} opts Overrides the defaults for the initial state of this.store
|
||||
* @property {object} store The an object containing a users preferences, stored in local storage
|
||||
* @property {array} store.frequentRpcList A list of custom rpcs to provide the user
|
||||
* @property {object} store The stored object containing a users preferences, stored in local storage
|
||||
* @property {array} store.frequentRpcList A list of custom rpcs to provide the user
|
||||
* @property {string} store.currentAccountTab Indicates the selected tab in the ui
|
||||
* @property {array} store.tokens The tokens the user wants display in their token lists
|
||||
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
|
||||
|
@ -6,6 +6,23 @@ const log = require('loglevel')
|
||||
|
||||
class RecentBlocksController {
|
||||
|
||||
/**
|
||||
* Controller responsible for storing, updating and managing the recent history of blocks. Blocks are back filled
|
||||
* upon the controller's construction and then the list is updated when the given block tracker gets a 'block' event
|
||||
* (indicating that there is a new block to process).
|
||||
*
|
||||
* @typedef {Object} RecentBlocksController
|
||||
* @param {object} opts Contains objects necessary for tracking blocks and querying the blockchain
|
||||
* @param {BlockTracker} opts.blockTracker Contains objects necessary for tracking blocks and querying the blockchain
|
||||
* @param {BlockTracker} opts.provider The provider used to create a new EthQuery instance.
|
||||
* @property {BlockTracker} blockTracker Points to the passed BlockTracker. On RecentBlocksController construction,
|
||||
* listens for 'block' events so that new blocks can be processed and added to storage.
|
||||
* @property {EthQuery} ethQuery Points to the EthQuery instance created with the passed provider
|
||||
* @property {number} historyLength The maximum length of blocks to track
|
||||
* @property {object} store Stores the recentBlocks
|
||||
* @property {array} store.recentBlocks Contains all recent blocks, up to a total that is equal to this.historyLength
|
||||
*
|
||||
*/
|
||||
constructor (opts = {}) {
|
||||
const { blockTracker, provider } = opts
|
||||
this.blockTracker = blockTracker
|
||||
@ -21,12 +38,23 @@ class RecentBlocksController {
|
||||
this.backfill()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets store.recentBlocks to an empty array
|
||||
*
|
||||
*/
|
||||
resetState () {
|
||||
this.store.updateState({
|
||||
recentBlocks: [],
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a new block and modifies it with this.mapTransactionsToPrices. Then adds that block to the recentBlocks
|
||||
* array in storage. If the recentBlocks array contains the maximum number of blocks, the oldest block is removed.
|
||||
*
|
||||
* @param {object} newBlock The new block to modify and add to the recentBlocks array
|
||||
*
|
||||
*/
|
||||
processBlock (newBlock) {
|
||||
const block = this.mapTransactionsToPrices(newBlock)
|
||||
|
||||
@ -40,6 +68,15 @@ class RecentBlocksController {
|
||||
this.store.updateState(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a new block and modifies it with this.mapTransactionsToPrices. Adds that block to the recentBlocks
|
||||
* array in storage, but only if the recentBlocks array contains fewer than the maximum permitted.
|
||||
*
|
||||
* Unlike this.processBlock, backfillBlock adds the modified new block to the beginning of the recent block array.
|
||||
*
|
||||
* @param {object} newBlock The new block to modify and add to the beginning of the recentBlocks array
|
||||
*
|
||||
*/
|
||||
backfillBlock (newBlock) {
|
||||
const block = this.mapTransactionsToPrices(newBlock)
|
||||
|
||||
@ -52,6 +89,14 @@ class RecentBlocksController {
|
||||
this.store.updateState(state)
|
||||
}
|
||||
|
||||
/**
|
||||
* Receives a block and gets the gasPrice of each of its transactions. These gas prices are added to the block at a
|
||||
* new property, and the block's transactions are removed.
|
||||
*
|
||||
* @param {object} newBlock The block to modify. It's transaction array will be replaced by a gasPrices array.
|
||||
* @returns {object} The modified block.
|
||||
*
|
||||
*/
|
||||
mapTransactionsToPrices (newBlock) {
|
||||
const block = extend(newBlock, {
|
||||
gasPrices: newBlock.transactions.map((tx) => {
|
||||
@ -62,6 +107,16 @@ class RecentBlocksController {
|
||||
return block
|
||||
}
|
||||
|
||||
/**
|
||||
* On this.blockTracker's first 'block' event after this RecentBlocksController's instantiation, the store.recentBlocks
|
||||
* array is populated with this.historyLength number of blocks. The block number of the this.blockTracker's first
|
||||
* 'block' event is used to iteratively generate all the numbers of the previous blocks, which are obtained by querying
|
||||
* the blockchain. These blocks are backfilled so that the recentBlocks array is ordered from oldest to newest.
|
||||
*
|
||||
* Each iteration over the block numbers is delayed by 100 milliseconds.
|
||||
*
|
||||
* @returns {Promise<void>} Promises undefined
|
||||
*/
|
||||
async backfill() {
|
||||
this.blockTracker.once('block', async (block) => {
|
||||
let blockNum = block.number
|
||||
@ -90,12 +145,25 @@ class RecentBlocksController {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A helper for this.backfill. Provides an easy way to ensure a 100 millisecond delay using await
|
||||
*
|
||||
* @returns {Promise<void>} Promises undefined
|
||||
*
|
||||
*/
|
||||
async wait () {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(resolve, 100)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses EthQuery to get a block that has a given block number.
|
||||
*
|
||||
* @param {number} number The number of the block to get
|
||||
* @returns {Promise<object>} Promises A block with the passed number
|
||||
*
|
||||
*/
|
||||
async getBlockByNumber (number) {
|
||||
const bn = new BN(number)
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -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 successful
|
||||
*
|
||||
*/
|
||||
_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 successfully 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({
|
||||
|
@ -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 an 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) {
|
||||
|
@ -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) {
|
||||
|
@ -3,8 +3,37 @@ const ObservableStore = require('obs-store')
|
||||
const ethUtil = require('ethereumjs-util')
|
||||
const createId = require('./random-id')
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @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 +43,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 +91,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<object>} 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 +153,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<object>} 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 +198,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 +214,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 +230,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
|
||||
|
@ -5,10 +5,18 @@ const width = 360
|
||||
|
||||
class NotificationManager {
|
||||
|
||||
//
|
||||
// Public
|
||||
//
|
||||
/**
|
||||
* A collection of methods for controlling the showing and hiding of the notification popup.
|
||||
*
|
||||
* @typedef {Object} NotificationManager
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Either brings an existing MetaMask notification window into focus, or creates a new notification window. New
|
||||
* notification windows are given a 'popup' type.
|
||||
*
|
||||
*/
|
||||
showPopup () {
|
||||
this._getPopup((err, popup) => {
|
||||
if (err) throw err
|
||||
@ -29,6 +37,10 @@ class NotificationManager {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes a MetaMask notification if it window exists.
|
||||
*
|
||||
*/
|
||||
closePopup () {
|
||||
// closes notification popup
|
||||
this._getPopup((err, popup) => {
|
||||
@ -38,10 +50,14 @@ class NotificationManager {
|
||||
})
|
||||
}
|
||||
|
||||
//
|
||||
// Private
|
||||
//
|
||||
|
||||
/**
|
||||
* Checks all open MetaMask windows, and returns the first one it finds that is a notification window (i.e. has the
|
||||
* type 'popup')
|
||||
*
|
||||
* @private
|
||||
* @param {Function} cb A node style callback that to whcih the found notification window will be passed.
|
||||
*
|
||||
*/
|
||||
_getPopup (cb) {
|
||||
this._getWindows((err, windows) => {
|
||||
if (err) throw err
|
||||
@ -49,6 +65,13 @@ class NotificationManager {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all open MetaMask windows.
|
||||
*
|
||||
* @private
|
||||
* @param {Function} cb A node style callback that to which the windows will be passed.
|
||||
*
|
||||
*/
|
||||
_getWindows (cb) {
|
||||
// Ignore in test environment
|
||||
if (!extension.windows) {
|
||||
@ -60,6 +83,13 @@ class NotificationManager {
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an array of windows, returns the first that has a 'popup' type, or null if no such window exists.
|
||||
*
|
||||
* @private
|
||||
* @param {array} windows An array of objects containing data about the open MetaMask extension windows.
|
||||
*
|
||||
*/
|
||||
_getPopupIn (windows) {
|
||||
return windows ? windows.find((win) => {
|
||||
// Returns notification popup
|
||||
|
@ -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<string>} 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)
|
||||
}
|
||||
|
@ -5,8 +5,37 @@ const createId = require('./random-id')
|
||||
const hexRe = /^[0-9A-Fa-f]+$/g
|
||||
const log = require('loglevel')
|
||||
|
||||
/**
|
||||
* 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({
|
||||
@ -16,15 +45,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)
|
||||
@ -45,24 +96,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<object>} 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
|
||||
@ -70,19 +159,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<object>} 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}".')
|
||||
@ -94,6 +205,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) {
|
||||
@ -102,6 +222,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
|
||||
@ -109,6 +236,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)
|
||||
|
@ -3,11 +3,19 @@ const log = require('loglevel')
|
||||
|
||||
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<void>} Promises undefined
|
||||
*
|
||||
*/
|
||||
verifyAccounts (createdAccounts, seedWords) {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
|
@ -5,7 +5,36 @@ const assert = require('assert')
|
||||
const sigUtil = require('eth-sig-util')
|
||||
const log = require('loglevel')
|
||||
|
||||
/**
|
||||
* Represents, and contains data about, an 'eth_signTypedData' type signature request. These are created when a
|
||||
* signature for an eth_signTypedData call is requested.
|
||||
*
|
||||
* @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.
|
||||
* @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 +44,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 +96,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 +113,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<object>} 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 +176,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<object>} 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 +202,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 +226,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 +243,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
|
||||
|
Loading…
Reference in New Issue
Block a user