From b0e86d124ebc17c89d38f023bce496e0adf0ac24 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Wed, 16 Nov 2022 16:25:57 +0000 Subject: [PATCH 01/22] split bridge logic --- index.js | 655 +----------------------------------------- ledger-keyring-mv2.js | 180 ++++++++++++ ledger-keyring.js | 621 +++++++++++++++++++++++++++++++++++++++ package.json | 4 +- 4 files changed, 811 insertions(+), 649 deletions(-) create mode 100644 ledger-keyring-mv2.js create mode 100644 ledger-keyring.js diff --git a/index.js b/index.js index 9c11576c..92d76760 100644 --- a/index.js +++ b/index.js @@ -1,651 +1,10 @@ -const { EventEmitter } = require('events') -const HDKey = require('hdkey') -const ethUtil = require('ethereumjs-util') -const sigUtil = require('eth-sig-util') -const { TransactionFactory } = require('@ethereumjs/tx') +const { LedgerKeyring, KEYRING_TYPE } = require('./ledger-keyring') +const LedgerKeyringMv2 = require('./ledger-keyring-mv2') -const pathBase = 'm' -const hdPathString = `${pathBase}/44'/60'/0'` -const type = 'Ledger Hardware' +// Keep default behaviour by exporting Mv2 version as default +module.exports = LedgerKeyringMv2 -const BRIDGE_URL = 'https://metamask.github.io/eth-ledger-bridge-keyring' +module.exports.LedgerKeyringMv2 = LedgerKeyringMv2 -const MAX_INDEX = 1000 -const NETWORK_API_URLS = { - ropsten: 'http://api-ropsten.etherscan.io', - kovan: 'http://api-kovan.etherscan.io', - rinkeby: 'https://api-rinkeby.etherscan.io', - mainnet: 'https://api.etherscan.io', -} - -const CONNECTION_EVENT = 'ledger-connection-change' - -class LedgerBridgeKeyring extends EventEmitter { - constructor (opts = {}) { - super() - this.accountDetails = {} - this.bridgeUrl = null - this.type = type - this.page = 0 - this.perPage = 5 - this.unlockedAccount = 0 - this.hdk = new HDKey() - this.paths = {} - this.iframe = null - this.network = 'mainnet' - this.implementFullBIP44 = false - this.deserialize(opts) - - this.iframeLoaded = false - this._setupIframe() - - this.currentMessageId = 0 - this.messageCallbacks = {} - this._setupListener() - } - - serialize () { - return Promise.resolve({ - hdPath: this.hdPath, - accounts: this.accounts, - accountDetails: this.accountDetails, - bridgeUrl: this.bridgeUrl, - implementFullBIP44: false, - }) - } - - deserialize (opts = {}) { - this.hdPath = opts.hdPath || hdPathString - this.bridgeUrl = opts.bridgeUrl || BRIDGE_URL - this.accounts = opts.accounts || [] - this.accountDetails = opts.accountDetails || {} - if (!opts.accountDetails) { - this._migrateAccountDetails(opts) - } - - this.implementFullBIP44 = opts.implementFullBIP44 || false - - // Remove accounts that don't have corresponding account details - this.accounts = this.accounts - .filter((account) => Object.keys(this.accountDetails).includes(ethUtil.toChecksumAddress(account))) - - return Promise.resolve() - } - - _migrateAccountDetails (opts) { - if (this._isLedgerLiveHdPath() && opts.accountIndexes) { - for (const account of Object.keys(opts.accountIndexes)) { - this.accountDetails[account] = { - bip44: true, - hdPath: this._getPathForIndex(opts.accountIndexes[account]), - } - } - } - - // try to migrate non-LedgerLive accounts too - if (!this._isLedgerLiveHdPath()) { - this.accounts - .filter((account) => !Object.keys(this.accountDetails).includes(ethUtil.toChecksumAddress(account))) - .forEach((account) => { - try { - this.accountDetails[ethUtil.toChecksumAddress(account)] = { - bip44: false, - hdPath: this._pathFromAddress(account), - } - } catch (e) { - console.log(`failed to migrate account ${account}`) - } - }) - } - } - - isUnlocked () { - return Boolean(this.hdk && this.hdk.publicKey) - } - - isConnected () { - return this.isDeviceConnected - } - - setAccountToUnlock (index) { - this.unlockedAccount = parseInt(index, 10) - } - - setHdPath (hdPath) { - // Reset HDKey if the path changes - if (this.hdPath !== hdPath) { - this.hdk = new HDKey() - } - this.hdPath = hdPath - } - - unlock (hdPath, updateHdk = true) { - if (this.isUnlocked() && !hdPath) { - return Promise.resolve('already unlocked') - } - const path = hdPath ? this._toLedgerPath(hdPath) : this.hdPath - return new Promise((resolve, reject) => { - this._sendMessage({ - action: 'ledger-unlock', - params: { - hdPath: path, - }, - }, - ({ success, payload }) => { - if (success) { - if (updateHdk) { - this.hdk.publicKey = Buffer.from(payload.publicKey, 'hex') - this.hdk.chainCode = Buffer.from(payload.chainCode, 'hex') - } - resolve(payload.address) - } else { - reject(payload.error || new Error('Unknown error')) - } - }) - }) - } - - addAccounts (n = 1) { - - return new Promise((resolve, reject) => { - this.unlock() - .then(async (_) => { - const from = this.unlockedAccount - const to = from + n - for (let i = from; i < to; i++) { - const path = this._getPathForIndex(i) - let address - if (this._isLedgerLiveHdPath()) { - address = await this.unlock(path) - } else { - address = this._addressFromIndex(pathBase, i) - } - this.accountDetails[ethUtil.toChecksumAddress(address)] = { - // TODO: consider renaming this property, as the current name is misleading - // It's currently used to represent whether an account uses the Ledger Live path. - bip44: this._isLedgerLiveHdPath(), - hdPath: path, - } - - if (!this.accounts.includes(address)) { - this.accounts.push(address) - } - this.page = 0 - } - resolve(this.accounts) - }) - .catch(reject) - }) - } - - getFirstPage () { - this.page = 0 - return this.__getPage(1) - } - - getNextPage () { - return this.__getPage(1) - } - - getPreviousPage () { - return this.__getPage(-1) - } - - getAccounts () { - return Promise.resolve(this.accounts.slice()) - } - - removeAccount (address) { - if (!this.accounts.map((a) => a.toLowerCase()).includes(address.toLowerCase())) { - throw new Error(`Address ${address} not found in this keyring`) - } - this.accounts = this.accounts.filter((a) => a.toLowerCase() !== address.toLowerCase()) - delete this.accountDetails[ethUtil.toChecksumAddress(address)] - } - - attemptMakeApp () { - return new Promise((resolve, reject) => { - this._sendMessage({ - action: 'ledger-make-app', - }, ({ success, error }) => { - if (success) { - resolve(true) - } else { - reject(error) - } - }) - }) - } - - updateTransportMethod (transportType) { - return new Promise((resolve, reject) => { - // If the iframe isn't loaded yet, let's store the desired transportType value and - // optimistically return a successful promise - if (!this.iframeLoaded) { - this.delayedPromise = { - resolve, - reject, - transportType, - } - return - } - - this._sendMessage({ - action: 'ledger-update-transport', - params: { transportType }, - }, ({ success }) => { - if (success) { - resolve(true) - } else { - reject(new Error('Ledger transport could not be updated')) - } - }) - }) - } - - // tx is an instance of the ethereumjs-transaction class. - signTransaction (address, tx) { - let rawTxHex - // transactions built with older versions of ethereumjs-tx have a - // getChainId method that newer versions do not. Older versions are mutable - // while newer versions default to being immutable. Expected shape and type - // of data for v, r and s differ (Buffer (old) vs BN (new)) - if (typeof tx.getChainId === 'function') { - // In this version of ethereumjs-tx we must add the chainId in hex format - // to the initial v value. The chainId must be included in the serialized - // transaction which is only communicated to ethereumjs-tx in this - // value. In newer versions the chainId is communicated via the 'Common' - // object. - tx.v = ethUtil.bufferToHex(tx.getChainId()) - tx.r = '0x00' - tx.s = '0x00' - - rawTxHex = tx.serialize().toString('hex') - - return this._signTransaction(address, rawTxHex, (payload) => { - tx.v = Buffer.from(payload.v, 'hex') - tx.r = Buffer.from(payload.r, 'hex') - tx.s = Buffer.from(payload.s, 'hex') - return tx - }) - } - - // The below `encode` call is only necessary for legacy transactions, as `getMessageToSign` - // calls `rlp.encode` internally for non-legacy transactions. As per the "Transaction Execution" - // section of the ethereum yellow paper, transactions need to be "well-formed RLP, with no additional - // trailing bytes". - - // Note also that `getMessageToSign` will return valid RLP for all transaction types, whereas the - // `serialize` method will not for any transaction type except legacy. This is because `serialize` includes - // empty r, s and v values in the encoded rlp. This is why we use `getMessageToSign` here instead of `serialize`. - const messageToSign = tx.getMessageToSign(false) - - rawTxHex = Buffer.isBuffer(messageToSign) - ? messageToSign.toString('hex') - : ethUtil.rlp.encode(messageToSign).toString('hex') - - return this._signTransaction(address, rawTxHex, (payload) => { - // Because tx will be immutable, first get a plain javascript object that - // represents the transaction. Using txData here as it aligns with the - // nomenclature of ethereumjs/tx. - const txData = tx.toJSON() - // The fromTxData utility expects a type to support transactions with a type other than 0 - txData.type = tx.type - // The fromTxData utility expects v,r and s to be hex prefixed - txData.v = ethUtil.addHexPrefix(payload.v) - txData.r = ethUtil.addHexPrefix(payload.r) - txData.s = ethUtil.addHexPrefix(payload.s) - // Adopt the 'common' option from the original transaction and set the - // returned object to be frozen if the original is frozen. - return TransactionFactory.fromTxData(txData, { common: tx.common, freeze: Object.isFrozen(tx) }) - }) - } - - _signTransaction (address, rawTxHex, handleSigning) { - return new Promise((resolve, reject) => { - this.unlockAccountByAddress(address) - .then((hdPath) => { - this._sendMessage({ - action: 'ledger-sign-transaction', - params: { - tx: rawTxHex, - hdPath, - }, - }, - ({ success, payload }) => { - if (success) { - - const newOrMutatedTx = handleSigning(payload) - const valid = newOrMutatedTx.verifySignature() - if (valid) { - resolve(newOrMutatedTx) - } else { - reject(new Error('Ledger: The transaction signature is not valid')) - } - } else { - reject(payload.error || new Error('Ledger: Unknown error while signing transaction')) - } - }) - }) - .catch(reject) - }) - } - - signMessage (withAccount, data) { - return this.signPersonalMessage(withAccount, data) - } - - // For personal_sign, we need to prefix the message: - signPersonalMessage (withAccount, message) { - return new Promise((resolve, reject) => { - this.unlockAccountByAddress(withAccount) - .then((hdPath) => { - this._sendMessage({ - action: 'ledger-sign-personal-message', - params: { - hdPath, - message: ethUtil.stripHexPrefix(message), - }, - }, - ({ success, payload }) => { - if (success) { - let v = parseInt(payload.v, 10) - v = v.toString(16) - if (v.length < 2) { - v = `0${v}` - } - const signature = `0x${payload.r}${payload.s}${v}` - const addressSignedWith = sigUtil.recoverPersonalSignature({ data: message, sig: signature }) - if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { - reject(new Error('Ledger: The signature doesnt match the right address')) - } - resolve(signature) - } else { - reject(payload.error || new Error('Ledger: Unknown error while signing message')) - } - }) - }) - .catch(reject) - }) - } - - async unlockAccountByAddress (address) { - const checksummedAddress = ethUtil.toChecksumAddress(address) - if (!Object.keys(this.accountDetails).includes(checksummedAddress)) { - throw new Error(`Ledger: Account for address '${checksummedAddress}' not found`) - } - const { hdPath } = this.accountDetails[checksummedAddress] - const unlockedAddress = await this.unlock(hdPath, false) - - // unlock resolves to the address for the given hdPath as reported by the ledger device - // if that address is not the requested address, then this account belongs to a different device or seed - if (unlockedAddress.toLowerCase() !== address.toLowerCase()) { - throw new Error(`Ledger: Account ${address} does not belong to the connected device`) - } - return hdPath - } - - async signTypedData (withAccount, data, options = {}) { - const isV4 = options.version === 'V4' - if (!isV4) { - throw new Error('Ledger: Only version 4 of typed data signing is supported') - } - - const { - domain, - types, - primaryType, - message, - } = sigUtil.TypedDataUtils.sanitizeData(data) - const domainSeparatorHex = sigUtil.TypedDataUtils.hashStruct('EIP712Domain', domain, types, isV4).toString('hex') - const hashStructMessageHex = sigUtil.TypedDataUtils.hashStruct(primaryType, message, types, isV4).toString('hex') - - const hdPath = await this.unlockAccountByAddress(withAccount) - const { success, payload } = await new Promise((resolve) => { - this._sendMessage({ - action: 'ledger-sign-typed-data', - params: { - hdPath, - domainSeparatorHex, - hashStructMessageHex, - }, - }, - (result) => resolve(result)) - }) - - if (success) { - let v = parseInt(payload.v, 10) - v = v.toString(16) - if (v.length < 2) { - v = `0${v}` - } - const signature = `0x${payload.r}${payload.s}${v}` - const addressSignedWith = sigUtil.recoverTypedSignature_v4({ - data, - sig: signature, - }) - if (ethUtil.toChecksumAddress(addressSignedWith) !== ethUtil.toChecksumAddress(withAccount)) { - throw new Error('Ledger: The signature doesnt match the right address') - } - return signature - } - throw payload.error || new Error('Ledger: Unknown error while signing message') - } - - exportAccount () { - throw new Error('Not supported on this device') - } - - forgetDevice () { - this.accounts = [] - this.page = 0 - this.unlockedAccount = 0 - this.paths = {} - this.accountDetails = {} - this.hdk = new HDKey() - } - - /* PRIVATE METHODS */ - - _setupIframe () { - this.iframe = document.createElement('iframe') - this.iframe.src = this.bridgeUrl - this.iframe.allow = `hid 'src'` - this.iframe.onload = async () => { - // If the ledger live preference was set before the iframe is loaded, - // set it after the iframe has loaded - this.iframeLoaded = true - if (this.delayedPromise) { - try { - const result = await this.updateTransportMethod( - this.delayedPromise.transportType, - ) - this.delayedPromise.resolve(result) - } catch (e) { - this.delayedPromise.reject(e) - } finally { - delete this.delayedPromise - } - } - } - document.head.appendChild(this.iframe) - } - - _getOrigin () { - const tmp = this.bridgeUrl.split('/') - tmp.splice(-1, 1) - return tmp.join('/') - } - - _sendMessage (msg, cb) { - msg.target = 'LEDGER-IFRAME' - - this.currentMessageId += 1 - msg.messageId = this.currentMessageId - - this.messageCallbacks[this.currentMessageId] = cb - this.iframe.contentWindow.postMessage(msg, '*') - } - - _setupListener () { - this._eventListener = ({ origin, data }) => { - if (origin !== this._getOrigin()) { - return false - } - - if (data) { - if (this.messageCallbacks[data.messageId]) { - this.messageCallbacks[data.messageId](data) - } else if (data.action === CONNECTION_EVENT) { - this.isDeviceConnected = data.payload.connected - } - } - - return undefined - } - window.addEventListener('message', this._eventListener) - } - - destroy () { - window.removeEventListener('message', this._eventListener) - } - - async __getPage (increment) { - - this.page += increment - - if (this.page <= 0) { - this.page = 1 - } - const from = (this.page - 1) * this.perPage - const to = from + this.perPage - - await this.unlock() - let accounts - if (this._isLedgerLiveHdPath()) { - accounts = await this._getAccountsBIP44(from, to) - } else { - accounts = this._getAccountsLegacy(from, to) - } - return accounts - } - - async _getAccountsBIP44 (from, to) { - const accounts = [] - - for (let i = from; i < to; i++) { - const path = this._getPathForIndex(i) - const address = await this.unlock(path) - const valid = this.implementFullBIP44 ? await this._hasPreviousTransactions(address) : true - accounts.push({ - address, - balance: null, - index: i, - }) - // PER BIP44 - // "Software should prevent a creation of an account if - // a previous account does not have a transaction history - // (meaning none of its addresses have been used before)." - if (!valid) { - break - } - } - return accounts - } - - _getAccountsLegacy (from, to) { - const accounts = [] - - for (let i = from; i < to; i++) { - const address = this._addressFromIndex(pathBase, i) - accounts.push({ - address, - balance: null, - index: i, - }) - this.paths[ethUtil.toChecksumAddress(address)] = i - } - return accounts - } - - _padLeftEven (hex) { - return hex.length % 2 === 0 ? hex : `0${hex}` - } - - _normalize (buf) { - return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase()) - } - - // eslint-disable-next-line no-shadow - _addressFromIndex (pathBase, i) { - const dkey = this.hdk.derive(`${pathBase}/${i}`) - const address = ethUtil - .publicToAddress(dkey.publicKey, true) - .toString('hex') - return ethUtil.toChecksumAddress(`0x${address}`) - } - - _pathFromAddress (address) { - const checksummedAddress = ethUtil.toChecksumAddress(address) - let index = this.paths[checksummedAddress] - if (typeof index === 'undefined') { - for (let i = 0; i < MAX_INDEX; i++) { - if (checksummedAddress === this._addressFromIndex(pathBase, i)) { - index = i - break - } - } - } - - if (typeof index === 'undefined') { - throw new Error('Unknown address') - } - return this._getPathForIndex(index) - } - - _toAscii (hex) { - let str = '' - let i = 0 - const l = hex.length - if (hex.substring(0, 2) === '0x') { - i = 2 - } - for (; i < l; i += 2) { - const code = parseInt(hex.substr(i, 2), 16) - str += String.fromCharCode(code) - } - - return str - } - - _getPathForIndex (index) { - // Check if the path is BIP 44 (Ledger Live) - return this._isLedgerLiveHdPath() ? `m/44'/60'/${index}'/0/0` : `${this.hdPath}/${index}` - } - - _isLedgerLiveHdPath () { - return this.hdPath === `m/44'/60'/0'/0/0` - } - - _toLedgerPath (path) { - return path.toString().replace('m/', '') - } - - async _hasPreviousTransactions (address) { - const apiUrl = this._getApiUrl() - const response = await window.fetch(`${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1&offset=1`) - const parsedResponse = await response.json() - if (parsedResponse.status !== '0' && parsedResponse.result.length > 0) { - return true - } - return false - } - - _getApiUrl () { - return NETWORK_API_URLS[this.network] || NETWORK_API_URLS.mainnet - } - -} - -LedgerBridgeKeyring.type = type -module.exports = LedgerBridgeKeyring +module.exports.LedgerKeyring = LedgerKeyring +module.exports.KEYRING_TYPE = KEYRING_TYPE diff --git a/ledger-keyring-mv2.js b/ledger-keyring-mv2.js new file mode 100644 index 00000000..217b6299 --- /dev/null +++ b/ledger-keyring-mv2.js @@ -0,0 +1,180 @@ +const { + LedgerKeyring, + KEYRING_TYPE, + CONNECTION_EVENT, +} = require('./ledger-keyring') + +class LedgerKeyringMv2 extends LedgerKeyring { + constructor (opts = {}) { + super(opts) + + this.iframe = null + } + + init () { + this.iframeLoaded = false + this._setupIframe() + + this.currentMessageId = 0 + this.messageCallbacks = {} + this._setupListener() + + return Promise.resolve() + } + + destroy () { + window.removeEventListener('message', this._eventListener) + + return Promise.resolve() + } + + _setupIframe () { + this.iframe = document.createElement('iframe') + this.iframe.src = this.bridgeUrl + this.iframe.allow = `hid 'src'` + this.iframe.onload = async () => { + // If the ledger live preference was set before the iframe is loaded, + // set it after the iframe has loaded + this.iframeLoaded = true + if (this.delayedPromise) { + try { + const result = await this.updateTransportMethod( + this.delayedPromise.transportType, + ) + this.delayedPromise.resolve(result) + } catch (e) { + this.delayedPromise.reject(e) + } finally { + delete this.delayedPromise + } + } + } + document.head.appendChild(this.iframe) + } + + _setupListener () { + this._eventListener = ({ origin, data }) => { + if (origin !== this._getOrigin()) { + return false + } + + if (data) { + if (this.messageCallbacks[data.messageId]) { + this.messageCallbacks[data.messageId](data) + } else if (data.action === CONNECTION_EVENT) { + this.isDeviceConnected = data.payload.connected + } + } + + return undefined + } + window.addEventListener('message', this._eventListener) + } + + _sendMessage (msg, cb) { + msg.target = 'LEDGER-IFRAME' + + this.currentMessageId += 1 + msg.messageId = this.currentMessageId + + this.messageCallbacks[this.currentMessageId] = cb + this.iframe.contentWindow.postMessage(msg, '*') + } + + attemptMakeApp () { + return new Promise((resolve, reject) => { + this._sendMessage( + { + action: 'ledger-make-app', + }, + ({ success, error }) => { + if (success) { + resolve(true) + } else { + reject(error) + } + }, + ) + }) + } + + updateTransportMethod (transportType) { + return new Promise((resolve, reject) => { + // If the iframe isn't loaded yet, let's store the desired transportType value and + // optimistically return a successful promise + if (!this.iframeLoaded) { + this.delayedPromise = { + resolve, + reject, + transportType, + } + return + } + + this._sendMessage( + { + action: 'ledger-update-transport', + params: { transportType }, + }, + ({ success }) => { + if (success) { + resolve(true) + } else { + reject(new Error('Ledger transport could not be updated')) + } + }, + ) + }) + } + + _getPublicKey (params) { + return new Promise((resolve) => { + this._sendMessage( + { + action: 'ledger-unlock', + params, + }, + (result) => resolve(result), + ) + }) + } + + _deviceSignTransaction (params) { + return new Promise((resolve) => { + this._sendMessage( + { + action: 'ledger-sign-transaction', + params, + }, + (result) => resolve(result), + ) + }) + } + + _deviceSignMessage (params) { + return new Promise((resolve) => { + this._sendMessage( + { + action: 'ledger-sign-personal-message', + params, + }, + (result) => resolve(result), + ) + }) + } + + _deviceSignTypedData (params) { + return new Promise((resolve) => { + this._sendMessage( + { + action: 'ledger-sign-typed-data', + params, + }, + (result) => resolve(result), + ) + }) + } +} + +LedgerKeyringMv2.type = KEYRING_TYPE +module.exports = LedgerKeyringMv2 diff --git a/ledger-keyring.js b/ledger-keyring.js new file mode 100644 index 00000000..f51ae48d --- /dev/null +++ b/ledger-keyring.js @@ -0,0 +1,621 @@ +const { EventEmitter } = require('events') +const HDKey = require('hdkey') +const ethUtil = require('ethereumjs-util') +const sigUtil = require('eth-sig-util') +const { TransactionFactory } = require('@ethereumjs/tx') + +const pathBase = 'm' +const hdPathString = `${pathBase}/44'/60'/0'` +const type = 'Ledger Hardware' + +const BRIDGE_URL = 'https://metamask.github.io/eth-ledger-bridge-keyring' + +const MAX_INDEX = 1000 +const NETWORK_API_URLS = { + ropsten: 'http://api-ropsten.etherscan.io', + kovan: 'http://api-kovan.etherscan.io', + rinkeby: 'https://api-rinkeby.etherscan.io', + mainnet: 'https://api.etherscan.io', +} + +const CONNECTION_EVENT = 'ledger-connection-change' + +class LedgerKeyring extends EventEmitter { + constructor (opts = {}) { + super() + this.accountDetails = {} + this.bridgeUrl = null + this.type = type + this.page = 0 + this.perPage = 5 + this.unlockedAccount = 0 + this.hdk = new HDKey() + this.paths = {} + this.network = 'mainnet' + this.implementFullBIP44 = false + this.deserialize(opts) + } + + /** + * Performs any asynchronous initialisation + * + * @returns {Promise} + */ + init () { + throw new Error('Method init not implemented') + } + + destroy () { + throw new Error('Method destroy not implemented') + } + + /** + * Gets public key from device + * + * @returns {Promise} + */ + _getPublicKey (_params) { + throw new Error('Method _getPublicKey not implemented') + } + + /** + * Signs transaction using the device + * + * @returns {Promise} + */ + _deviceSignTransaction (_params) { + throw new Error('Method _deviceSignTransaction not implemented') + } + + /** + * Signs message using the device + * + * @returns {Promise} + */ + _deviceSignMessage (_params) { + throw new Error('Method _deviceSignMessage not implemented') + } + + /** + * Signs typed data using the device + * + * @returns {Promise} + */ + _deviceSignTypedData (_params) { + throw new Error('Method _deviceSignTypedData not implemented') + } + + serialize () { + return Promise.resolve({ + hdPath: this.hdPath, + accounts: this.accounts, + accountDetails: this.accountDetails, + bridgeUrl: this.bridgeUrl, + implementFullBIP44: false, + }) + } + + deserialize (opts = {}) { + this.hdPath = opts.hdPath || hdPathString + this.bridgeUrl = opts.bridgeUrl || BRIDGE_URL + this.accounts = opts.accounts || [] + this.accountDetails = opts.accountDetails || {} + if (!opts.accountDetails) { + this._migrateAccountDetails(opts) + } + + this.implementFullBIP44 = opts.implementFullBIP44 || false + + // Remove accounts that don't have corresponding account details + this.accounts = this.accounts.filter((account) => Object.keys(this.accountDetails).includes( + ethUtil.toChecksumAddress(account), + )) + + return Promise.resolve() + } + + _migrateAccountDetails (opts) { + if (this._isLedgerLiveHdPath() && opts.accountIndexes) { + for (const account of Object.keys(opts.accountIndexes)) { + this.accountDetails[account] = { + bip44: true, + hdPath: this._getPathForIndex(opts.accountIndexes[account]), + } + } + } + + // try to migrate non-LedgerLive accounts too + if (!this._isLedgerLiveHdPath()) { + this.accounts + .filter( + (account) => !Object.keys(this.accountDetails).includes( + ethUtil.toChecksumAddress(account), + ), + ) + .forEach((account) => { + try { + this.accountDetails[ethUtil.toChecksumAddress(account)] = { + bip44: false, + hdPath: this._pathFromAddress(account), + } + } catch (e) { + console.log(`failed to migrate account ${account}`) + } + }) + } + } + + isUnlocked () { + return Boolean(this.hdk && this.hdk.publicKey) + } + + isConnected () { + return this.isDeviceConnected + } + + setAccountToUnlock (index) { + this.unlockedAccount = parseInt(index, 10) + } + + setHdPath (hdPath) { + // Reset HDKey if the path changes + if (this.hdPath !== hdPath) { + this.hdk = new HDKey() + } + this.hdPath = hdPath + } + + async unlock (hdPath, updateHdk = true) { + if (this.isUnlocked() && !hdPath) { + return 'already unlocked' + } + const path = hdPath ? this._toLedgerPath(hdPath) : this.hdPath + + const { success, payload } = await this._getPublicKey({ + hdPath: path, + }) + + if (success) { + if (updateHdk) { + this.hdk.publicKey = Buffer.from(payload.publicKey, 'hex') + this.hdk.chainCode = Buffer.from(payload.chainCode, 'hex') + } + return payload.address + } + throw payload.error || new Error('Unknown error') + + } + + addAccounts (n = 1) { + return new Promise((resolve, reject) => { + this.unlock() + .then(async (_) => { + const from = this.unlockedAccount + const to = from + n + for (let i = from; i < to; i++) { + const path = this._getPathForIndex(i) + let address + if (this._isLedgerLiveHdPath()) { + address = await this.unlock(path) + } else { + address = this._addressFromIndex(pathBase, i) + } + this.accountDetails[ethUtil.toChecksumAddress(address)] = { + // TODO: consider renaming this property, as the current name is misleading + // It's currently used to represent whether an account uses the Ledger Live path. + bip44: this._isLedgerLiveHdPath(), + hdPath: path, + } + + if (!this.accounts.includes(address)) { + this.accounts.push(address) + } + this.page = 0 + } + resolve(this.accounts) + }) + .catch(reject) + }) + } + + getFirstPage () { + this.page = 0 + return this.__getPage(1) + } + + getNextPage () { + return this.__getPage(1) + } + + getPreviousPage () { + return this.__getPage(-1) + } + + getAccounts () { + return Promise.resolve(this.accounts.slice()) + } + + removeAccount (address) { + if ( + !this.accounts.map((a) => a.toLowerCase()).includes(address.toLowerCase()) + ) { + throw new Error(`Address ${address} not found in this keyring`) + } + this.accounts = this.accounts.filter( + (a) => a.toLowerCase() !== address.toLowerCase(), + ) + delete this.accountDetails[ethUtil.toChecksumAddress(address)] + } + + attemptMakeApp () { + throw new Error('Method attemptMakeApp not implemented') + } + + updateTransportMethod (_transportType) { + throw new Error('Method transportType not implemented') + } + + // tx is an instance of the ethereumjs-transaction class. + signTransaction (address, tx) { + let rawTxHex + // transactions built with older versions of ethereumjs-tx have a + // getChainId method that newer versions do not. Older versions are mutable + // while newer versions default to being immutable. Expected shape and type + // of data for v, r and s differ (Buffer (old) vs BN (new)) + if (typeof tx.getChainId === 'function') { + // In this version of ethereumjs-tx we must add the chainId in hex format + // to the initial v value. The chainId must be included in the serialized + // transaction which is only communicated to ethereumjs-tx in this + // value. In newer versions the chainId is communicated via the 'Common' + // object. + tx.v = ethUtil.bufferToHex(tx.getChainId()) + tx.r = '0x00' + tx.s = '0x00' + + rawTxHex = tx.serialize().toString('hex') + + return this._signTransaction(address, rawTxHex, (payload) => { + tx.v = Buffer.from(payload.v, 'hex') + tx.r = Buffer.from(payload.r, 'hex') + tx.s = Buffer.from(payload.s, 'hex') + return tx + }) + } + + // The below `encode` call is only necessary for legacy transactions, as `getMessageToSign` + // calls `rlp.encode` internally for non-legacy transactions. As per the "Transaction Execution" + // section of the ethereum yellow paper, transactions need to be "well-formed RLP, with no additional + // trailing bytes". + + // Note also that `getMessageToSign` will return valid RLP for all transaction types, whereas the + // `serialize` method will not for any transaction type except legacy. This is because `serialize` includes + // empty r, s and v values in the encoded rlp. This is why we use `getMessageToSign` here instead of `serialize`. + const messageToSign = tx.getMessageToSign(false) + + rawTxHex = Buffer.isBuffer(messageToSign) + ? messageToSign.toString('hex') + : ethUtil.rlp.encode(messageToSign).toString('hex') + + return this._signTransaction(address, rawTxHex, (payload) => { + // Because tx will be immutable, first get a plain javascript object that + // represents the transaction. Using txData here as it aligns with the + // nomenclature of ethereumjs/tx. + const txData = tx.toJSON() + // The fromTxData utility expects a type to support transactions with a type other than 0 + txData.type = tx.type + // The fromTxData utility expects v,r and s to be hex prefixed + txData.v = ethUtil.addHexPrefix(payload.v) + txData.r = ethUtil.addHexPrefix(payload.r) + txData.s = ethUtil.addHexPrefix(payload.s) + // Adopt the 'common' option from the original transaction and set the + // returned object to be frozen if the original is frozen. + return TransactionFactory.fromTxData(txData, { + common: tx.common, + freeze: Object.isFrozen(tx), + }) + }) + } + + async _signTransaction (address, rawTxHex, handleSigning) { + const hdPath = await this.unlockAccountByAddress(address) + + const { success, payload } = await this._deviceSignTransaction({ + tx: rawTxHex, + hdPath, + }) + + if (success) { + const newOrMutatedTx = handleSigning(payload) + const valid = newOrMutatedTx.verifySignature() + if (valid) { + return newOrMutatedTx + } + throw new Error('Ledger: The transaction signature is not valid') + + } + + throw ( + payload.error || + new Error('Ledger: Unknown error while signing transaction') + ) + } + + signMessage (withAccount, data) { + return this.signPersonalMessage(withAccount, data) + } + + // For personal_sign, we need to prefix the message: + async signPersonalMessage (withAccount, message) { + const hdPath = await this.unlockAccountByAddress(withAccount) + + const { success, payload } = await this._deviceSignMessage({ + hdPath, + message: ethUtil.stripHexPrefix(message), + }) + + if (success) { + let v = payload.v - 27 + v = v.toString(16) + if (v.length < 2) { + v = `0${v}` + } + const signature = `0x${payload.r}${payload.s}${v}` + const addressSignedWith = sigUtil.recoverPersonalSignature({ + data: message, + sig: signature, + }) + if ( + ethUtil.toChecksumAddress(addressSignedWith) !== + ethUtil.toChecksumAddress(withAccount) + ) { + throw new Error('Ledger: The signature doesnt match the right address') + } + return signature + } + + throw ( + payload.error || new Error('Ledger: Unknown error while signing message') + ) + } + + async unlockAccountByAddress (address) { + const checksummedAddress = ethUtil.toChecksumAddress(address) + if (!Object.keys(this.accountDetails).includes(checksummedAddress)) { + throw new Error( + `Ledger: Account for address '${checksummedAddress}' not found`, + ) + } + const { hdPath } = this.accountDetails[checksummedAddress] + const unlockedAddress = await this.unlock(hdPath, false) + + // unlock resolves to the address for the given hdPath as reported by the ledger device + // if that address is not the requested address, then this account belongs to a different device or seed + if (unlockedAddress.toLowerCase() !== address.toLowerCase()) { + throw new Error( + `Ledger: Account ${address} does not belong to the connected device`, + ) + } + return hdPath + } + + async signTypedData (withAccount, data, options = {}) { + const isV4 = options.version === 'V4' + if (!isV4) { + throw new Error( + 'Ledger: Only version 4 of typed data signing is supported', + ) + } + + const { domain, types, primaryType, message } = + sigUtil.TypedDataUtils.sanitizeData(data) + const domainSeparatorHex = sigUtil.TypedDataUtils.hashStruct( + 'EIP712Domain', + domain, + types, + isV4, + ).toString('hex') + const hashStructMessageHex = sigUtil.TypedDataUtils.hashStruct( + primaryType, + message, + types, + isV4, + ).toString('hex') + + const hdPath = await this.unlockAccountByAddress(withAccount) + const { success, payload } = await this._deviceSignTypedData({ + hdPath, + domainSeparatorHex, + hashStructMessageHex, + }) + + if (success) { + let v = parseInt(payload.v, 10) + v = v.toString(16) + if (v.length < 2) { + v = `0${v}` + } + const signature = `0x${payload.r}${payload.s}${v}` + const addressSignedWith = sigUtil.recoverTypedSignature_v4({ + data, + sig: signature, + }) + if ( + ethUtil.toChecksumAddress(addressSignedWith) !== + ethUtil.toChecksumAddress(withAccount) + ) { + throw new Error('Ledger: The signature doesnt match the right address') + } + return signature + } + throw ( + payload.error || new Error('Ledger: Unknown error while signing message') + ) + } + + exportAccount () { + throw new Error('Not supported on this device') + } + + forgetDevice () { + this.accounts = [] + this.page = 0 + this.unlockedAccount = 0 + this.paths = {} + this.accountDetails = {} + this.hdk = new HDKey() + } + + /* PRIVATE METHODS */ + + _getOrigin () { + const tmp = this.bridgeUrl.split('/') + tmp.splice(-1, 1) + return tmp.join('/') + } + + async __getPage (increment) { + this.page += increment + + if (this.page <= 0) { + this.page = 1 + } + const from = (this.page - 1) * this.perPage + const to = from + this.perPage + + await this.unlock() + let accounts + if (this._isLedgerLiveHdPath()) { + accounts = await this._getAccountsBIP44(from, to) + } else { + accounts = this._getAccountsLegacy(from, to) + } + return accounts + } + + async _getAccountsBIP44 (from, to) { + const accounts = [] + + for (let i = from; i < to; i++) { + const path = this._getPathForIndex(i) + const address = await this.unlock(path) + const valid = this.implementFullBIP44 + ? await this._hasPreviousTransactions(address) + : true + accounts.push({ + address, + balance: null, + index: i, + }) + // PER BIP44 + // "Software should prevent a creation of an account if + // a previous account does not have a transaction history + // (meaning none of its addresses have been used before)." + if (!valid) { + break + } + } + return accounts + } + + _getAccountsLegacy (from, to) { + const accounts = [] + + for (let i = from; i < to; i++) { + const address = this._addressFromIndex(pathBase, i) + accounts.push({ + address, + balance: null, + index: i, + }) + this.paths[ethUtil.toChecksumAddress(address)] = i + } + return accounts + } + + _padLeftEven (hex) { + return hex.length % 2 === 0 ? hex : `0${hex}` + } + + _normalize (buf) { + return this._padLeftEven(ethUtil.bufferToHex(buf).toLowerCase()) + } + + // eslint-disable-next-line no-shadow + _addressFromIndex (pathBase, i) { + const dkey = this.hdk.derive(`${pathBase}/${i}`) + const address = ethUtil + .publicToAddress(dkey.publicKey, true) + .toString('hex') + return ethUtil.toChecksumAddress(`0x${address}`) + } + + _pathFromAddress (address) { + const checksummedAddress = ethUtil.toChecksumAddress(address) + let index = this.paths[checksummedAddress] + if (typeof index === 'undefined') { + for (let i = 0; i < MAX_INDEX; i++) { + if (checksummedAddress === this._addressFromIndex(pathBase, i)) { + index = i + break + } + } + } + + if (typeof index === 'undefined') { + throw new Error('Unknown address') + } + return this._getPathForIndex(index) + } + + _toAscii (hex) { + let str = '' + let i = 0 + const l = hex.length + if (hex.substring(0, 2) === '0x') { + i = 2 + } + for (; i < l; i += 2) { + const code = parseInt(hex.substr(i, 2), 16) + str += String.fromCharCode(code) + } + + return str + } + + _getPathForIndex (index) { + // Check if the path is BIP 44 (Ledger Live) + return this._isLedgerLiveHdPath() + ? `m/44'/60'/${index}'/0/0` + : `${this.hdPath}/${index}` + } + + _isLedgerLiveHdPath () { + return this.hdPath === `m/44'/60'/0'/0/0` + } + + _toLedgerPath (path) { + return path.toString().replace('m/', '') + } + + async _hasPreviousTransactions (address) { + const apiUrl = this._getApiUrl() + const response = await window.fetch( + `${apiUrl}/api?module=account&action=txlist&address=${address}&tag=latest&page=1&offset=1`, + ) + const parsedResponse = await response.json() + if (parsedResponse.status !== '0' && parsedResponse.result.length > 0) { + return true + } + return false + } + + _getApiUrl () { + return NETWORK_API_URLS[this.network] || NETWORK_API_URLS.mainnet + } +} + +module.exports = { + LedgerKeyring, + KEYRING_TYPE: type, + CONNECTION_EVENT, +} diff --git a/package.json b/package.json index 049f0afa..b59aa93c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,9 @@ "description": "A MetaMask compatible keyring, for ledger hardware wallets", "main": "index.js", "files": [ - "index.js" + "index.js", + "ledger-keyring.js", + "ledger-keyring-mv2.js" ], "engines": { "node": ">=14.0.0" From ee891119eaf52af49c187b01a72023d54f2c0c89 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Wed, 16 Nov 2022 20:03:52 +0000 Subject: [PATCH 02/22] refactor for simplicity --- ledger-keyring-mv2.js | 60 +++++++++++----- ledger-keyring.js | 164 +++++++++++++++++++++--------------------- 2 files changed, 124 insertions(+), 100 deletions(-) diff --git a/ledger-keyring-mv2.js b/ledger-keyring-mv2.js index 217b6299..544a70c8 100644 --- a/ledger-keyring-mv2.js +++ b/ledger-keyring-mv2.js @@ -71,16 +71,6 @@ class LedgerKeyringMv2 extends LedgerKeyring { window.addEventListener('message', this._eventListener) } - _sendMessage (msg, cb) { - msg.target = 'LEDGER-IFRAME' - - this.currentMessageId += 1 - msg.messageId = this.currentMessageId - - this.messageCallbacks[this.currentMessageId] = cb - this.iframe.contentWindow.postMessage(msg, '*') - } - attemptMakeApp () { return new Promise((resolve, reject) => { this._sendMessage( @@ -128,52 +118,86 @@ class LedgerKeyringMv2 extends LedgerKeyring { } _getPublicKey (params) { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this._sendMessage( { action: 'ledger-unlock', params, }, - (result) => resolve(result), + ({ success, payload }) => { + if (success) { + resolve(payload) + } + // eslint-disable-next-line prefer-promise-reject-errors + reject(payload && payload.error) + }, ) }) } _deviceSignTransaction (params) { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this._sendMessage( { action: 'ledger-sign-transaction', params, }, - (result) => resolve(result), + ({ success, payload }) => { + if (success) { + resolve(payload) + } + // eslint-disable-next-line prefer-promise-reject-errors + reject(payload && payload.error) + }, ) }) } _deviceSignMessage (params) { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this._sendMessage( { action: 'ledger-sign-personal-message', params, }, - (result) => resolve(result), + ({ success, payload }) => { + if (success) { + resolve(payload) + } + // eslint-disable-next-line prefer-promise-reject-errors + reject(payload && payload.error) + }, ) }) } _deviceSignTypedData (params) { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { this._sendMessage( { action: 'ledger-sign-typed-data', params, }, - (result) => resolve(result), + ({ success, payload }) => { + if (success) { + resolve(payload) + } + // eslint-disable-next-line prefer-promise-reject-errors + reject(payload && payload.error) + }, ) }) } + + _sendMessage (msg, cb) { + msg.target = 'LEDGER-IFRAME' + + this.currentMessageId += 1 + msg.messageId = this.currentMessageId + + this.messageCallbacks[this.currentMessageId] = cb + this.iframe.contentWindow.postMessage(msg, '*') + } } LedgerKeyringMv2.type = KEYRING_TYPE diff --git a/ledger-keyring.js b/ledger-keyring.js index f51ae48d..c7ad5dee 100644 --- a/ledger-keyring.js +++ b/ledger-keyring.js @@ -171,19 +171,20 @@ class LedgerKeyring extends EventEmitter { } const path = hdPath ? this._toLedgerPath(hdPath) : this.hdPath - const { success, payload } = await this._getPublicKey({ - hdPath: path, - }) - - if (success) { - if (updateHdk) { - this.hdk.publicKey = Buffer.from(payload.publicKey, 'hex') - this.hdk.chainCode = Buffer.from(payload.chainCode, 'hex') - } - return payload.address + let payload + try { + payload = await this._getPublicKey({ + hdPath: path, + }) + } catch (error) { + throw error || new Error('Unknown error') } - throw payload.error || new Error('Unknown error') + if (updateHdk) { + this.hdk.publicKey = Buffer.from(payload.publicKey, 'hex') + this.hdk.chainCode = Buffer.from(payload.chainCode, 'hex') + } + return payload.address } addAccounts (n = 1) { @@ -251,10 +252,6 @@ class LedgerKeyring extends EventEmitter { throw new Error('Method attemptMakeApp not implemented') } - updateTransportMethod (_transportType) { - throw new Error('Method transportType not implemented') - } - // tx is an instance of the ethereumjs-transaction class. signTransaction (address, tx) { let rawTxHex @@ -319,25 +316,24 @@ class LedgerKeyring extends EventEmitter { async _signTransaction (address, rawTxHex, handleSigning) { const hdPath = await this.unlockAccountByAddress(address) - const { success, payload } = await this._deviceSignTransaction({ - tx: rawTxHex, - hdPath, - }) - - if (success) { - const newOrMutatedTx = handleSigning(payload) - const valid = newOrMutatedTx.verifySignature() - if (valid) { - return newOrMutatedTx - } - throw new Error('Ledger: The transaction signature is not valid') - + let payload + try { + payload = await this._deviceSignTransaction({ + tx: rawTxHex, + hdPath, + }) + } catch (error) { + throw ( + error || new Error('Ledger: Unknown error while signing transaction') + ) } - throw ( - payload.error || - new Error('Ledger: Unknown error while signing transaction') - ) + const newOrMutatedTx = handleSigning(payload) + const valid = newOrMutatedTx.verifySignature() + if (valid) { + return newOrMutatedTx + } + throw new Error('Ledger: The transaction signature is not valid') } signMessage (withAccount, data) { @@ -348,34 +344,35 @@ class LedgerKeyring extends EventEmitter { async signPersonalMessage (withAccount, message) { const hdPath = await this.unlockAccountByAddress(withAccount) - const { success, payload } = await this._deviceSignMessage({ - hdPath, - message: ethUtil.stripHexPrefix(message), - }) - - if (success) { - let v = payload.v - 27 - v = v.toString(16) - if (v.length < 2) { - v = `0${v}` - } - const signature = `0x${payload.r}${payload.s}${v}` - const addressSignedWith = sigUtil.recoverPersonalSignature({ - data: message, - sig: signature, + let payload + try { + payload = await this._deviceSignMessage({ + hdPath, + message: ethUtil.stripHexPrefix(message), }) - if ( - ethUtil.toChecksumAddress(addressSignedWith) !== - ethUtil.toChecksumAddress(withAccount) - ) { - throw new Error('Ledger: The signature doesnt match the right address') - } - return signature + } catch (error) { + throw ( + error || new Error('Ledger: Unknown error while signing message') + ) } - throw ( - payload.error || new Error('Ledger: Unknown error while signing message') - ) + let v = payload.v - 27 + v = v.toString(16) + if (v.length < 2) { + v = `0${v}` + } + const signature = `0x${payload.r}${payload.s}${v}` + const addressSignedWith = sigUtil.recoverPersonalSignature({ + data: message, + sig: signature, + }) + if ( + ethUtil.toChecksumAddress(addressSignedWith) !== + ethUtil.toChecksumAddress(withAccount) + ) { + throw new Error('Ledger: The signature doesnt match the right address') + } + return signature } async unlockAccountByAddress (address) { @@ -422,34 +419,37 @@ class LedgerKeyring extends EventEmitter { ).toString('hex') const hdPath = await this.unlockAccountByAddress(withAccount) - const { success, payload } = await this._deviceSignTypedData({ - hdPath, - domainSeparatorHex, - hashStructMessageHex, - }) - if (success) { - let v = parseInt(payload.v, 10) - v = v.toString(16) - if (v.length < 2) { - v = `0${v}` - } - const signature = `0x${payload.r}${payload.s}${v}` - const addressSignedWith = sigUtil.recoverTypedSignature_v4({ - data, - sig: signature, + let payload + try { + payload = await this._deviceSignTypedData({ + hdPath, + domainSeparatorHex, + hashStructMessageHex, }) - if ( - ethUtil.toChecksumAddress(addressSignedWith) !== - ethUtil.toChecksumAddress(withAccount) - ) { - throw new Error('Ledger: The signature doesnt match the right address') - } - return signature + } catch (error) { + throw ( + error || new Error('Ledger: Unknown error while signing message') + ) } - throw ( - payload.error || new Error('Ledger: Unknown error while signing message') - ) + + let v = parseInt(payload.v, 10) + v = v.toString(16) + if (v.length < 2) { + v = `0${v}` + } + const signature = `0x${payload.r}${payload.s}${v}` + const addressSignedWith = sigUtil.recoverTypedSignature_v4({ + data, + sig: signature, + }) + if ( + ethUtil.toChecksumAddress(addressSignedWith) !== + ethUtil.toChecksumAddress(withAccount) + ) { + throw new Error('Ledger: The signature doesnt match the right address') + } + return signature } exportAccount () { From eaa0d5506fa345e9afa62791ac002aa263d959a0 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 17 Nov 2022 11:25:50 +0000 Subject: [PATCH 03/22] refactor and types --- ledger-keyring.js => base-ledger-keyring.js | 6 ++- index.js | 7 +-- ledger-keyring-mv2.js | 12 ++--- package.json | 4 +- ...ring.js => test-eth-ledger-keyring-mv2.js} | 14 +++--- types/index.d.ts | 50 +++++++++++++++++++ 6 files changed, 69 insertions(+), 24 deletions(-) rename ledger-keyring.js => base-ledger-keyring.js (99%) rename test/{test-eth-ledger-bridge-keyring.js => test-eth-ledger-keyring-mv2.js} (98%) create mode 100644 types/index.d.ts diff --git a/ledger-keyring.js b/base-ledger-keyring.js similarity index 99% rename from ledger-keyring.js rename to base-ledger-keyring.js index c7ad5dee..2c0985ae 100644 --- a/ledger-keyring.js +++ b/base-ledger-keyring.js @@ -20,7 +20,7 @@ const NETWORK_API_URLS = { const CONNECTION_EVENT = 'ledger-connection-change' -class LedgerKeyring extends EventEmitter { +class BaseLedgerKeyring extends EventEmitter { constructor (opts = {}) { super() this.accountDetails = {} @@ -614,8 +614,10 @@ class LedgerKeyring extends EventEmitter { } } +BaseLedgerKeyring.type = type + module.exports = { - LedgerKeyring, + BaseLedgerKeyring, KEYRING_TYPE: type, CONNECTION_EVENT, } diff --git a/index.js b/index.js index 92d76760..6d9489eb 100644 --- a/index.js +++ b/index.js @@ -1,10 +1,7 @@ -const { LedgerKeyring, KEYRING_TYPE } = require('./ledger-keyring') +const { BaseLedgerKeyring, KEYRING_TYPE } = require('./base-ledger-keyring') const LedgerKeyringMv2 = require('./ledger-keyring-mv2') -// Keep default behaviour by exporting Mv2 version as default -module.exports = LedgerKeyringMv2 - module.exports.LedgerKeyringMv2 = LedgerKeyringMv2 -module.exports.LedgerKeyring = LedgerKeyring +module.exports.BaseLedgerKeyring = BaseLedgerKeyring module.exports.KEYRING_TYPE = KEYRING_TYPE diff --git a/ledger-keyring-mv2.js b/ledger-keyring-mv2.js index 544a70c8..8a5ab5bb 100644 --- a/ledger-keyring-mv2.js +++ b/ledger-keyring-mv2.js @@ -1,16 +1,10 @@ const { - LedgerKeyring, + BaseLedgerKeyring, KEYRING_TYPE, CONNECTION_EVENT, -} = require('./ledger-keyring') - -class LedgerKeyringMv2 extends LedgerKeyring { - constructor (opts = {}) { - super(opts) - - this.iframe = null - } +} = require('./base-ledger-keyring') +class LedgerKeyringMv2 extends BaseLedgerKeyring { init () { this.iframeLoaded = false this._setupIframe() diff --git a/package.json b/package.json index b59aa93c..987ec6c3 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,12 @@ "version": "0.13.0", "description": "A MetaMask compatible keyring, for ledger hardware wallets", "main": "index.js", + "types": "types/index.d.ts", "files": [ "index.js", "ledger-keyring.js", - "ledger-keyring-mv2.js" + "ledger-keyring-mv2.js", + "types/index.d.ts" ], "engines": { "node": ">=14.0.0" diff --git a/test/test-eth-ledger-bridge-keyring.js b/test/test-eth-ledger-keyring-mv2.js similarity index 98% rename from test/test-eth-ledger-bridge-keyring.js rename to test/test-eth-ledger-keyring-mv2.js index c995d5de..9d8c32ce 100644 --- a/test/test-eth-ledger-bridge-keyring.js +++ b/test/test-eth-ledger-keyring-mv2.js @@ -11,7 +11,7 @@ const { TransactionFactory } = require('@ethereumjs/tx') const Common = require('@ethereumjs/common').default const sigUtil = require('eth-sig-util') -const LedgerBridgeKeyring = require('..') +const { LedgerKeyringMv2 } = require('..') const { expect } = chai @@ -85,7 +85,7 @@ describe('LedgerBridgeKeyring', function () { beforeEach(function () { sandbox = chai.spy.sandbox() - keyring = new LedgerBridgeKeyring() + keyring = new LedgerKeyringMv2() keyring.hdk = fakeHdKey }) @@ -95,20 +95,20 @@ describe('LedgerBridgeKeyring', function () { describe('Keyring.type', function () { it('is a class property that returns the type string.', function () { - const { type } = LedgerBridgeKeyring + const { type } = LedgerKeyringMv2 assert.equal(typeof type, 'string') }) it('returns the correct value', function () { const { type } = keyring - const correct = LedgerBridgeKeyring.type + const correct = LedgerKeyringMv2.type assert.equal(type, correct) }) }) describe('constructor', function () { it('constructs', function (done) { - const t = new LedgerBridgeKeyring({ hdPath: `m/44'/60'/0'` }) + const t = new LedgerKeyringMv2({ hdPath: `m/44'/60'/0'` }) assert.equal(typeof t, 'object') t.getAccounts() .then((accounts) => { @@ -215,7 +215,7 @@ describe('LedgerBridgeKeyring', function () { }) }) it('should update hdk.publicKey if updateHdk is true', function (done) { - const ledgerKeyring = new LedgerBridgeKeyring() + const ledgerKeyring = new LedgerKeyringMv2() ledgerKeyring.hdk = { publicKey: 'ABC' } sandbox.on(ledgerKeyring, '_sendMessage', (_, cb) => { @@ -237,7 +237,7 @@ describe('LedgerBridgeKeyring', function () { }) }) it('should not update hdk.publicKey if updateHdk is false', function (done) { - const ledgerKeyring = new LedgerBridgeKeyring() + const ledgerKeyring = new LedgerKeyringMv2() ledgerKeyring.hdk = { publicKey: 'ABC' } sandbox.on(ledgerKeyring, '_sendMessage', (_, cb) => { diff --git a/types/index.d.ts b/types/index.d.ts new file mode 100644 index 00000000..cd662132 --- /dev/null +++ b/types/index.d.ts @@ -0,0 +1,50 @@ +export const KEYRING_TYPE: string; + +export type GetPublicKeyPayload = { hdPath: string }; +export type GetPublicKeyResponse = { + publicKey: string; + address: string; + chainCode?: string | undefined; +}; + +export type LedgerSignatureResponse = { + v: string; + s: string; + r: string; +}; + +export type LedgerSignTransactionPayload = { hdPath: string; rawTxHex: string }; + +export type LedgerSignMessagePayload = { hdPath: string; message: string }; + +export type LedgerSignTypedDataPayload = { + hdPath: string; + domainSeparatorHex: string; + hashStructMessageHex: string; +}; + +export class BaseLedgerKeyring { + static type: string; + + public constructor(opts: Record); + + public init(): Promise; + + public dispose(): Promise; + + protected _getPublicKey( + payload: GetPublicKeyPayload + ): Promise; + + protected _deviceSignTransaction( + payload: LedgerSignTransactionPayload + ): Promise; + + protected _deviceSignMessage( + payload: LedgerSignMessagePayload + ): Promise; + + protected _deviceSignTypedData( + payload: LedgerSignTypedDataPayload + ): Promise; +} From 7701b665c02cdbe4f56eb0b1c8154c77965efdcb Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 17 Nov 2022 13:03:09 +0000 Subject: [PATCH 04/22] add test for init --- test/test-eth-ledger-keyring-mv2.js | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test/test-eth-ledger-keyring-mv2.js b/test/test-eth-ledger-keyring-mv2.js index 9d8c32ce..e0d8e90c 100644 --- a/test/test-eth-ledger-keyring-mv2.js +++ b/test/test-eth-ledger-keyring-mv2.js @@ -118,6 +118,29 @@ describe('LedgerBridgeKeyring', function () { }) }) + describe('init', function () { + it('should set up the iFrame', function () { + const iframeMock = {} + sandbox.on(global.document, 'createElement', () => iframeMock) + sandbox.on(global.document.head, 'appendChild', () => true) + sandbox.on(global.window, 'addEventListener', () => true) + + keyring.init() + + expect(global.document.createElement).to.have.been.called() + expect(global.document.createElement) + .to.have.been.called.with('iframe') + + expect(global.document.head.appendChild).to.have.been.called() + expect(global.document.head.appendChild) + .to.have.been.called.with(iframeMock) + + expect(global.window.addEventListener).to.have.been.called() + expect(global.window.addEventListener) + .to.have.been.called.with('message', keyring._eventListener) + }) + }) + describe('serialize', function () { it('serializes an instance', function (done) { keyring.serialize() From be2a0840066da007eb715bca88da9f201570ae53 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 17 Nov 2022 13:34:59 +0000 Subject: [PATCH 05/22] types fix --- base-ledger-keyring.js | 4 ---- index.js | 3 +-- ledger-keyring-mv2.js | 9 +++------ types/index.d.ts | 27 ++++++++++++++++----------- 4 files changed, 20 insertions(+), 23 deletions(-) diff --git a/base-ledger-keyring.js b/base-ledger-keyring.js index 2c0985ae..2b2310b6 100644 --- a/base-ledger-keyring.js +++ b/base-ledger-keyring.js @@ -18,8 +18,6 @@ const NETWORK_API_URLS = { mainnet: 'https://api.etherscan.io', } -const CONNECTION_EVENT = 'ledger-connection-change' - class BaseLedgerKeyring extends EventEmitter { constructor (opts = {}) { super() @@ -618,6 +616,4 @@ BaseLedgerKeyring.type = type module.exports = { BaseLedgerKeyring, - KEYRING_TYPE: type, - CONNECTION_EVENT, } diff --git a/index.js b/index.js index 6d9489eb..d4016b63 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,6 @@ -const { BaseLedgerKeyring, KEYRING_TYPE } = require('./base-ledger-keyring') +const { BaseLedgerKeyring } = require('./base-ledger-keyring') const LedgerKeyringMv2 = require('./ledger-keyring-mv2') module.exports.LedgerKeyringMv2 = LedgerKeyringMv2 module.exports.BaseLedgerKeyring = BaseLedgerKeyring -module.exports.KEYRING_TYPE = KEYRING_TYPE diff --git a/ledger-keyring-mv2.js b/ledger-keyring-mv2.js index 8a5ab5bb..eea93def 100644 --- a/ledger-keyring-mv2.js +++ b/ledger-keyring-mv2.js @@ -1,8 +1,6 @@ -const { - BaseLedgerKeyring, - KEYRING_TYPE, - CONNECTION_EVENT, -} = require('./base-ledger-keyring') +const { BaseLedgerKeyring } = require('./base-ledger-keyring') + +const CONNECTION_EVENT = 'ledger-connection-change' class LedgerKeyringMv2 extends BaseLedgerKeyring { init () { @@ -194,5 +192,4 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { } } -LedgerKeyringMv2.type = KEYRING_TYPE module.exports = LedgerKeyringMv2 diff --git a/types/index.d.ts b/types/index.d.ts index cd662132..a6cac62b 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,5 +1,3 @@ -export const KEYRING_TYPE: string; - export type GetPublicKeyPayload = { hdPath: string }; export type GetPublicKeyResponse = { publicKey: string; @@ -7,25 +5,32 @@ export type GetPublicKeyResponse = { chainCode?: string | undefined; }; -export type LedgerSignatureResponse = { - v: string; +export type LedgerSignTransactionPayload = { hdPath: string; rawTxHex: string }; +export type LedgerSignTransactionResponse = { s: string; + v: string; r: string; }; -export type LedgerSignTransactionPayload = { hdPath: string; rawTxHex: string }; - export type LedgerSignMessagePayload = { hdPath: string; message: string }; +export type LedgerSignMessageResponse = { + v: number; + s: string; + r: string; +}; export type LedgerSignTypedDataPayload = { hdPath: string; domainSeparatorHex: string; hashStructMessageHex: string; }; +export type LedgerSignTypedDataResponse = { + v: number; + s: string; + r: string; +}; export class BaseLedgerKeyring { - static type: string; - public constructor(opts: Record); public init(): Promise; @@ -38,13 +43,13 @@ export class BaseLedgerKeyring { protected _deviceSignTransaction( payload: LedgerSignTransactionPayload - ): Promise; + ): Promise; protected _deviceSignMessage( payload: LedgerSignMessagePayload - ): Promise; + ): Promise; protected _deviceSignTypedData( payload: LedgerSignTypedDataPayload - ): Promise; + ): Promise; } From 2e1c545744e150b39dcb655a1fff68b3000bf70c Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Mon, 21 Nov 2022 10:42:26 +0000 Subject: [PATCH 06/22] Update types/index.d.ts Co-authored-by: legobeat <109787230+legobeat@users.noreply.github.com> --- types/index.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types/index.d.ts b/types/index.d.ts index a6cac62b..81eba0c3 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -2,7 +2,7 @@ export type GetPublicKeyPayload = { hdPath: string }; export type GetPublicKeyResponse = { publicKey: string; address: string; - chainCode?: string | undefined; + chainCode?: string; }; export type LedgerSignTransactionPayload = { hdPath: string; rawTxHex: string }; From e78cab345bbddbac07b7b7664e22bb80895b4e08 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Mon, 20 Feb 2023 20:01:02 +0000 Subject: [PATCH 07/22] composition refactor --- index.js | 8 +- ...-keyring-mv2.js => ledger-bridge-iframe.js | 25 ++++--- base-ledger-keyring.js => ledger-keyring.js | 74 ++++++------------- package.json | 2 +- types/index.d.ts | 16 ++-- 5 files changed, 49 insertions(+), 76 deletions(-) rename ledger-keyring-mv2.js => ledger-bridge-iframe.js (92%) rename base-ledger-keyring.js => ledger-keyring.js (92%) diff --git a/index.js b/index.js index d4016b63..a0edc7e2 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,6 @@ -const { BaseLedgerKeyring } = require('./base-ledger-keyring') -const LedgerKeyringMv2 = require('./ledger-keyring-mv2') +const { LedgerKeyring } = require('./ledger-keyring') +const LedgerBridgeIframe = require('./ledger-bridge-iframe') -module.exports.LedgerKeyringMv2 = LedgerKeyringMv2 +module.exports.LedgerBridgeIframe = LedgerBridgeIframe -module.exports.BaseLedgerKeyring = BaseLedgerKeyring +module.exports.LedgerKeyring = LedgerKeyring diff --git a/ledger-keyring-mv2.js b/ledger-bridge-iframe.js similarity index 92% rename from ledger-keyring-mv2.js rename to ledger-bridge-iframe.js index eea93def..2269c04d 100644 --- a/ledger-keyring-mv2.js +++ b/ledger-bridge-iframe.js @@ -1,11 +1,10 @@ -const { BaseLedgerKeyring } = require('./base-ledger-keyring') - const CONNECTION_EVENT = 'ledger-connection-change' -class LedgerKeyringMv2 extends BaseLedgerKeyring { - init () { +class LedgerBridgeIframe { + init (bridgeUrl) { + this.bridgeUrl = bridgeUrl this.iframeLoaded = false - this._setupIframe() + this._setupIframe(bridgeUrl) this.currentMessageId = 0 this.messageCallbacks = {} @@ -44,6 +43,12 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { document.head.appendChild(this.iframe) } + _getOrigin () { + const tmp = this.bridgeUrl.split('/') + tmp.splice(-1, 1) + return tmp.join('/') + } + _setupListener () { this._eventListener = ({ origin, data }) => { if (origin !== this._getOrigin()) { @@ -109,7 +114,7 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { }) } - _getPublicKey (params) { + getPublicKey (params) { return new Promise((resolve, reject) => { this._sendMessage( { @@ -127,7 +132,7 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { }) } - _deviceSignTransaction (params) { + deviceSignTransaction (params) { return new Promise((resolve, reject) => { this._sendMessage( { @@ -145,7 +150,7 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { }) } - _deviceSignMessage (params) { + deviceSignMessage (params) { return new Promise((resolve, reject) => { this._sendMessage( { @@ -163,7 +168,7 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { }) } - _deviceSignTypedData (params) { + deviceSignTypedData (params) { return new Promise((resolve, reject) => { this._sendMessage( { @@ -192,4 +197,4 @@ class LedgerKeyringMv2 extends BaseLedgerKeyring { } } -module.exports = LedgerKeyringMv2 +module.exports = LedgerBridgeIframe diff --git a/base-ledger-keyring.js b/ledger-keyring.js similarity index 92% rename from base-ledger-keyring.js rename to ledger-keyring.js index 2b2310b6..9f3b48c9 100644 --- a/base-ledger-keyring.js +++ b/ledger-keyring.js @@ -18,8 +18,8 @@ const NETWORK_API_URLS = { mainnet: 'https://api.etherscan.io', } -class BaseLedgerKeyring extends EventEmitter { - constructor (opts = {}) { +class LedgerKeyring extends EventEmitter { + constructor ({ bridge } = {}) { super() this.accountDetails = {} this.bridgeUrl = null @@ -31,7 +31,12 @@ class BaseLedgerKeyring extends EventEmitter { this.paths = {} this.network = 'mainnet' this.implementFullBIP44 = false - this.deserialize(opts) + + if (!bridge) { + throw new Error('Bridge is a required dependency for the keyring') + } + + this.bridge = bridge } /** @@ -40,47 +45,16 @@ class BaseLedgerKeyring extends EventEmitter { * @returns {Promise} */ init () { - throw new Error('Method init not implemented') - } - - destroy () { - throw new Error('Method destroy not implemented') + return this.bridge.init(this.bridgeUrl) } /** - * Gets public key from device + * Performs any asynchronous cleaning * - * @returns {Promise} - */ - _getPublicKey (_params) { - throw new Error('Method _getPublicKey not implemented') - } - - /** - * Signs transaction using the device - * - * @returns {Promise} - */ - _deviceSignTransaction (_params) { - throw new Error('Method _deviceSignTransaction not implemented') - } - - /** - * Signs message using the device - * - * @returns {Promise} - */ - _deviceSignMessage (_params) { - throw new Error('Method _deviceSignMessage not implemented') - } - - /** - * Signs typed data using the device - * - * @returns {Promise} + * @returns {Promise} */ - _deviceSignTypedData (_params) { - throw new Error('Method _deviceSignTypedData not implemented') + destroy () { + return this.bridge.destroy() } serialize () { @@ -148,7 +122,7 @@ class BaseLedgerKeyring extends EventEmitter { } isConnected () { - return this.isDeviceConnected + return this.bridge.isDeviceConnected } setAccountToUnlock (index) { @@ -171,7 +145,7 @@ class BaseLedgerKeyring extends EventEmitter { let payload try { - payload = await this._getPublicKey({ + payload = await this.bridge.getPublicKey({ hdPath: path, }) } catch (error) { @@ -247,7 +221,7 @@ class BaseLedgerKeyring extends EventEmitter { } attemptMakeApp () { - throw new Error('Method attemptMakeApp not implemented') + return this.bridge.attemptMakeApp() } // tx is an instance of the ethereumjs-transaction class. @@ -316,7 +290,7 @@ class BaseLedgerKeyring extends EventEmitter { let payload try { - payload = await this._deviceSignTransaction({ + payload = await this.bridge.deviceSignTransaction({ tx: rawTxHex, hdPath, }) @@ -344,7 +318,7 @@ class BaseLedgerKeyring extends EventEmitter { let payload try { - payload = await this._deviceSignMessage({ + payload = await this.bridge.deviceSignMessage({ hdPath, message: ethUtil.stripHexPrefix(message), }) @@ -420,7 +394,7 @@ class BaseLedgerKeyring extends EventEmitter { let payload try { - payload = await this._deviceSignTypedData({ + payload = await this.bridge.deviceSignTypedData({ hdPath, domainSeparatorHex, hashStructMessageHex, @@ -465,12 +439,6 @@ class BaseLedgerKeyring extends EventEmitter { /* PRIVATE METHODS */ - _getOrigin () { - const tmp = this.bridgeUrl.split('/') - tmp.splice(-1, 1) - return tmp.join('/') - } - async __getPage (increment) { this.page += increment @@ -612,8 +580,8 @@ class BaseLedgerKeyring extends EventEmitter { } } -BaseLedgerKeyring.type = type +LedgerKeyring.type = type module.exports = { - BaseLedgerKeyring, + LedgerKeyring, } diff --git a/package.json b/package.json index 987ec6c3..2856a992 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "files": [ "index.js", "ledger-keyring.js", - "ledger-keyring-mv2.js", + "ledger-bridge-iframe.js", "types/index.d.ts" ], "engines": { diff --git a/types/index.d.ts b/types/index.d.ts index 81eba0c3..5a013e1c 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -30,26 +30,26 @@ export type LedgerSignTypedDataResponse = { r: string; }; -export class BaseLedgerKeyring { - public constructor(opts: Record); +export interface LedgerBridge { + init(): Promise; - public init(): Promise; + destroy(): Promise; - public dispose(): Promise; + attemptMakeApp(): Promise; - protected _getPublicKey( + getPublicKey( payload: GetPublicKeyPayload ): Promise; - protected _deviceSignTransaction( + deviceSignTransaction( payload: LedgerSignTransactionPayload ): Promise; - protected _deviceSignMessage( + deviceSignMessage( payload: LedgerSignMessagePayload ): Promise; - protected _deviceSignTypedData( + deviceSignTypedData( payload: LedgerSignTypedDataPayload ): Promise; } From 2dc890dcdaf599d556bd39f562627894a4155f6d Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Mon, 20 Feb 2023 21:32:31 +0000 Subject: [PATCH 08/22] working refactor --- index.js | 9 ++-- ledger-bridge-iframe.js | 102 ++++++++++++++++++++-------------------- ledger-keyring.js | 15 +++--- 3 files changed, 64 insertions(+), 62 deletions(-) diff --git a/index.js b/index.js index a0edc7e2..5cdb1e3c 100644 --- a/index.js +++ b/index.js @@ -1,6 +1,7 @@ const { LedgerKeyring } = require('./ledger-keyring') -const LedgerBridgeIframe = require('./ledger-bridge-iframe') +const { LedgerBridgeIframe } = require('./ledger-bridge-iframe') -module.exports.LedgerBridgeIframe = LedgerBridgeIframe - -module.exports.LedgerKeyring = LedgerKeyring +module.exports = { + LedgerKeyring, + LedgerBridgeIframe, +} diff --git a/ledger-bridge-iframe.js b/ledger-bridge-iframe.js index 2269c04d..605b22c9 100644 --- a/ledger-bridge-iframe.js +++ b/ledger-bridge-iframe.js @@ -19,55 +19,6 @@ class LedgerBridgeIframe { return Promise.resolve() } - _setupIframe () { - this.iframe = document.createElement('iframe') - this.iframe.src = this.bridgeUrl - this.iframe.allow = `hid 'src'` - this.iframe.onload = async () => { - // If the ledger live preference was set before the iframe is loaded, - // set it after the iframe has loaded - this.iframeLoaded = true - if (this.delayedPromise) { - try { - const result = await this.updateTransportMethod( - this.delayedPromise.transportType, - ) - this.delayedPromise.resolve(result) - } catch (e) { - this.delayedPromise.reject(e) - } finally { - delete this.delayedPromise - } - } - } - document.head.appendChild(this.iframe) - } - - _getOrigin () { - const tmp = this.bridgeUrl.split('/') - tmp.splice(-1, 1) - return tmp.join('/') - } - - _setupListener () { - this._eventListener = ({ origin, data }) => { - if (origin !== this._getOrigin()) { - return false - } - - if (data) { - if (this.messageCallbacks[data.messageId]) { - this.messageCallbacks[data.messageId](data) - } else if (data.action === CONNECTION_EVENT) { - this.isDeviceConnected = data.payload.connected - } - } - - return undefined - } - window.addEventListener('message', this._eventListener) - } - attemptMakeApp () { return new Promise((resolve, reject) => { this._sendMessage( @@ -186,6 +137,55 @@ class LedgerBridgeIframe { }) } + _setupIframe () { + this.iframe = document.createElement('iframe') + this.iframe.src = this.bridgeUrl + this.iframe.allow = `hid 'src'` + this.iframe.onload = async () => { + // If the ledger live preference was set before the iframe is loaded, + // set it after the iframe has loaded + this.iframeLoaded = true + if (this.delayedPromise) { + try { + const result = await this.updateTransportMethod( + this.delayedPromise.transportType, + ) + this.delayedPromise.resolve(result) + } catch (e) { + this.delayedPromise.reject(e) + } finally { + delete this.delayedPromise + } + } + } + document.head.appendChild(this.iframe) + } + + _getOrigin () { + const tmp = this.bridgeUrl.split('/') + tmp.splice(-1, 1) + return tmp.join('/') + } + + _setupListener () { + this._eventListener = ({ origin, data }) => { + if (origin !== this._getOrigin()) { + return false + } + + if (data) { + if (this.messageCallbacks[data.messageId]) { + this.messageCallbacks[data.messageId](data) + } else if (data.action === CONNECTION_EVENT) { + this.isDeviceConnected = data.payload.connected + } + } + + return undefined + } + window.addEventListener('message', this._eventListener) + } + _sendMessage (msg, cb) { msg.target = 'LEDGER-IFRAME' @@ -197,4 +197,6 @@ class LedgerBridgeIframe { } } -module.exports = LedgerBridgeIframe +module.exports = { + LedgerBridgeIframe, +} diff --git a/ledger-keyring.js b/ledger-keyring.js index 9f3b48c9..d72d230e 100644 --- a/ledger-keyring.js +++ b/ledger-keyring.js @@ -45,7 +45,7 @@ class LedgerKeyring extends EventEmitter { * @returns {Promise} */ init () { - return this.bridge.init(this.bridgeUrl) + return this.bridge.init(this.bridgeUrl || BRIDGE_URL) } /** @@ -224,6 +224,10 @@ class LedgerKeyring extends EventEmitter { return this.bridge.attemptMakeApp() } + updateTransportMethod (transportType) { + return this.bridge.updateTransportMethod(transportType) + } + // tx is an instance of the ethereumjs-transaction class. signTransaction (address, tx) { let rawTxHex @@ -323,9 +327,7 @@ class LedgerKeyring extends EventEmitter { message: ethUtil.stripHexPrefix(message), }) } catch (error) { - throw ( - error || new Error('Ledger: Unknown error while signing message') - ) + throw error || new Error('Ledger: Unknown error while signing message') } let v = payload.v - 27 @@ -400,9 +402,7 @@ class LedgerKeyring extends EventEmitter { hashStructMessageHex, }) } catch (error) { - throw ( - error || new Error('Ledger: Unknown error while signing message') - ) + throw error || new Error('Ledger: Unknown error while signing message') } let v = parseInt(payload.v, 10) @@ -581,7 +581,6 @@ class LedgerKeyring extends EventEmitter { } LedgerKeyring.type = type - module.exports = { LedgerKeyring, } From 301dacca7ec4af6b3e7458277a48ba08924cc6e3 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Tue, 7 Mar 2023 11:42:01 +0000 Subject: [PATCH 09/22] tests --- index.js | 4 +- ...ridge-iframe.js => ledger-iframe-bridge.js | 67 +--- ledger-keyring.js | 6 +- test/ledger-iframe-bridge.test.js | 340 ++++++++++++++++++ ...-keyring-mv2.js => ledger-keyring.test.js} | 185 +++++----- 5 files changed, 446 insertions(+), 156 deletions(-) rename ledger-bridge-iframe.js => ledger-iframe-bridge.js (71%) create mode 100644 test/ledger-iframe-bridge.test.js rename test/{test-eth-ledger-keyring-mv2.js => ledger-keyring.test.js} (80%) diff --git a/index.js b/index.js index 5cdb1e3c..6e16be48 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ const { LedgerKeyring } = require('./ledger-keyring') -const { LedgerBridgeIframe } = require('./ledger-bridge-iframe') +const { LedgerIframeBridge } = require('./ledger-iframe-bridge') module.exports = { LedgerKeyring, - LedgerBridgeIframe, + LedgerIframeBridge, } diff --git a/ledger-bridge-iframe.js b/ledger-iframe-bridge.js similarity index 71% rename from ledger-bridge-iframe.js rename to ledger-iframe-bridge.js index 605b22c9..40666c38 100644 --- a/ledger-bridge-iframe.js +++ b/ledger-iframe-bridge.js @@ -1,10 +1,11 @@ +const { BRIDGE_URL } = require('./ledger-keyring') + const CONNECTION_EVENT = 'ledger-connection-change' -class LedgerBridgeIframe { - init (bridgeUrl) { - this.bridgeUrl = bridgeUrl +class LedgerIframeBridge { + init () { this.iframeLoaded = false - this._setupIframe(bridgeUrl) + this._setupIframe() this.currentMessageId = 0 this.messageCallbacks = {} @@ -66,64 +67,26 @@ class LedgerBridgeIframe { } getPublicKey (params) { - return new Promise((resolve, reject) => { - this._sendMessage( - { - action: 'ledger-unlock', - params, - }, - ({ success, payload }) => { - if (success) { - resolve(payload) - } - // eslint-disable-next-line prefer-promise-reject-errors - reject(payload && payload.error) - }, - ) - }) + return this._deviceActionMessage('ledger-unlock', params) } deviceSignTransaction (params) { - return new Promise((resolve, reject) => { - this._sendMessage( - { - action: 'ledger-sign-transaction', - params, - }, - ({ success, payload }) => { - if (success) { - resolve(payload) - } - // eslint-disable-next-line prefer-promise-reject-errors - reject(payload && payload.error) - }, - ) - }) + return this._deviceActionMessage('ledger-sign-transaction', params) } deviceSignMessage (params) { - return new Promise((resolve, reject) => { - this._sendMessage( - { - action: 'ledger-sign-personal-message', - params, - }, - ({ success, payload }) => { - if (success) { - resolve(payload) - } - // eslint-disable-next-line prefer-promise-reject-errors - reject(payload && payload.error) - }, - ) - }) + return this._deviceActionMessage('ledger-sign-personal-message', params) } deviceSignTypedData (params) { + return this._deviceActionMessage('ledger-sign-typed-data', params) + } + + _deviceActionMessage (action, params) { return new Promise((resolve, reject) => { this._sendMessage( { - action: 'ledger-sign-typed-data', + action, params, }, ({ success, payload }) => { @@ -139,7 +102,7 @@ class LedgerBridgeIframe { _setupIframe () { this.iframe = document.createElement('iframe') - this.iframe.src = this.bridgeUrl + this.iframe.src = BRIDGE_URL this.iframe.allow = `hid 'src'` this.iframe.onload = async () => { // If the ledger live preference was set before the iframe is loaded, @@ -198,5 +161,5 @@ class LedgerBridgeIframe { } module.exports = { - LedgerBridgeIframe, + LedgerIframeBridge, } diff --git a/ledger-keyring.js b/ledger-keyring.js index d72d230e..16d93751 100644 --- a/ledger-keyring.js +++ b/ledger-keyring.js @@ -22,7 +22,6 @@ class LedgerKeyring extends EventEmitter { constructor ({ bridge } = {}) { super() this.accountDetails = {} - this.bridgeUrl = null this.type = type this.page = 0 this.perPage = 5 @@ -45,7 +44,7 @@ class LedgerKeyring extends EventEmitter { * @returns {Promise} */ init () { - return this.bridge.init(this.bridgeUrl || BRIDGE_URL) + return this.bridge.init() } /** @@ -62,14 +61,12 @@ class LedgerKeyring extends EventEmitter { hdPath: this.hdPath, accounts: this.accounts, accountDetails: this.accountDetails, - bridgeUrl: this.bridgeUrl, implementFullBIP44: false, }) } deserialize (opts = {}) { this.hdPath = opts.hdPath || hdPathString - this.bridgeUrl = opts.bridgeUrl || BRIDGE_URL this.accounts = opts.accounts || [] this.accountDetails = opts.accountDetails || {} if (!opts.accountDetails) { @@ -582,5 +579,6 @@ class LedgerKeyring extends EventEmitter { LedgerKeyring.type = type module.exports = { + BRIDGE_URL, LedgerKeyring, } diff --git a/test/ledger-iframe-bridge.test.js b/test/ledger-iframe-bridge.test.js new file mode 100644 index 00000000..48b3fb4d --- /dev/null +++ b/test/ledger-iframe-bridge.test.js @@ -0,0 +1,340 @@ +global.document = require('./document.shim') +global.window = require('./window.shim') + +const chai = require('chai') +const spies = require('chai-spies') + +const { LedgerIframeBridge } = require('../ledger-iframe-bridge') + +const { expect } = chai + +chai.use(spies) + +describe('LedgerIframeBridge', function () { + let bridge + let sandbox + + beforeEach(function () { + bridge = new LedgerIframeBridge() + sandbox = chai.spy.sandbox() + }) + + afterEach(function () { + sandbox.restore() + }) + + describe('init', function () { + it('should set up the iFrame', async function () { + const iframeMock = {} + sandbox.on(global.document, 'createElement', () => iframeMock) + sandbox.on(global.document.head, 'appendChild') + sandbox.on(global.window, 'addEventListener') + + await bridge.init() + + expect(global.document.createElement).to.have.been.called() + expect(global.document.createElement) + .to.have.been.called.with('iframe') + + expect(global.document.head.appendChild).to.have.been.called() + expect(global.document.head.appendChild) + .to.have.been.called.with(iframeMock) + + expect(global.window.addEventListener).to.have.been.called() + expect(global.window.addEventListener) + .to.have.been.called.with('message', bridge._eventListener) + + expect(bridge.iframeLoaded).to.equal(false) + expect(bridge.currentMessageId).to.equal(0) + expect(bridge.messageCallbacks).to.deep.equal({}) + }) + }) + + describe('destroy', function () { + it('should remove the message event listener', async function () { + sandbox.on(global.window, 'removeEventListener') + + await bridge.destroy() + + expect(global.window.removeEventListener).to.have.been.called() + expect(global.window.removeEventListener) + .to.have.been.called.with('message', bridge._eventListener) + }) + }) + + describe('attemptMakeApp', function () { + it('should successfully send a ledger-make-app message', async function () { + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: true }) + }) + + const result = await bridge.attemptMakeApp() + + expect(result).to.equal(true) + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ action: 'ledger-make-app' }) + }) + + it('should throw an error when a ledger-make-app message is not successful', async function () { + const errorMessage = 'Ledger Error' + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: false, error: new Error(errorMessage) }) + }) + + try { + await bridge.attemptMakeApp() + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal(errorMessage) + } + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ action: 'ledger-make-app' }) + }) + }) + + describe('updateTransportMethod', function () { + it('should successfully send a ledger-update-transport message', async function () { + bridge.iframeLoaded = true + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: true }) + }) + + const transportType = 'u2f' + + const result = await bridge.updateTransportMethod(transportType) + + expect(result).to.equal(true) + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-update-transport', + params: { transportType }, + }) + }) + + it('should throw an error when a ledger-update-transport message is not successful', async function () { + bridge.iframeLoaded = true + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: false }) + }) + + const transportType = 'u2f' + + try { + await bridge.updateTransportMethod(transportType) + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal('Ledger transport could not be updated') + } + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-update-transport', + params: { transportType }, + }) + }) + }) + + describe('getPublicKey', function () { + it('should successfully send a ledger-unlock message', async function () { + const payload = {} + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: true, payload }) + }) + + const params = {} + + const result = await bridge.getPublicKey(params) + + expect(result).to.equal(payload) + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-unlock', + params, + }) + }) + + it('should throw an error when a ledger-unlock message is not successful', async function () { + const errorMessage = 'Ledger Error' + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: false, payload: { error: new Error(errorMessage) } }) + }) + + const params = {} + + try { + await bridge.getPublicKey(params) + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal(errorMessage) + } + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-unlock', + params, + }) + }) + }) + + describe('deviceSignTransaction', function () { + it('should successfully send a ledger-sign-transaction message', async function () { + const payload = {} + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: true, payload }) + }) + + const params = {} + + const result = await bridge.deviceSignTransaction(params) + + expect(result).to.equal(payload) + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-sign-transaction', + params, + }) + }) + + it('should throw an error when a ledger-sign-transaction message is not successful', async function () { + const errorMessage = 'Ledger Error' + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: false, payload: { error: new Error(errorMessage) } }) + }) + + const params = {} + + try { + await bridge.deviceSignTransaction(params) + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal(errorMessage) + } + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-sign-transaction', + params, + }) + }) + }) + + describe('deviceSignMessage', function () { + it('should successfully send a ledger-sign-personal-message message', async function () { + const payload = {} + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: true, payload }) + }) + + const params = {} + + const result = await bridge.deviceSignMessage(params) + + expect(result).to.equal(payload) + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-sign-personal-message', + params, + }) + }) + + it('should throw an error when a ledger-sign-personal-message message is not successful', async function () { + const errorMessage = 'Ledger Error' + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: false, payload: { error: new Error(errorMessage) } }) + }) + + const params = {} + + try { + await bridge.deviceSignMessage(params) + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal(errorMessage) + } + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-sign-personal-message', + params, + }) + }) + }) + + describe('deviceSignTypedData', function () { + it('should successfully send a ledger-sign-typed-data message', async function () { + const payload = {} + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: true, payload }) + }) + + const params = {} + + const result = await bridge.deviceSignTypedData(params) + + expect(result).to.equal(payload) + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-sign-typed-data', + params, + }) + }) + + it('should throw an error when a ledger-sign-typed-data message is not successful', async function () { + const errorMessage = 'Ledger Error' + + sandbox.on(bridge, '_sendMessage', (_, callback) => { + callback({ success: false, payload: { error: new Error(errorMessage) } }) + }) + + const params = {} + + try { + await bridge.deviceSignTypedData(params) + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal(errorMessage) + } + + expect(bridge._sendMessage).to.have.been.called() + expect(bridge._sendMessage) + .to.have.been.called.with({ + action: 'ledger-sign-typed-data', + params, + }) + }) + }) +}) diff --git a/test/test-eth-ledger-keyring-mv2.js b/test/ledger-keyring.test.js similarity index 80% rename from test/test-eth-ledger-keyring-mv2.js rename to test/ledger-keyring.test.js index e0d8e90c..0b9437c0 100644 --- a/test/test-eth-ledger-keyring-mv2.js +++ b/test/ledger-keyring.test.js @@ -1,6 +1,3 @@ -global.document = require('./document.shim') -global.window = require('./window.shim') - const assert = require('assert') const chai = require('chai') const spies = require('chai-spies') @@ -11,7 +8,7 @@ const { TransactionFactory } = require('@ethereumjs/tx') const Common = require('@ethereumjs/common').default const sigUtil = require('eth-sig-util') -const { LedgerKeyringMv2 } = require('..') +const { LedgerKeyring } = require('../ledger-keyring') const { expect } = chai @@ -72,9 +69,9 @@ const fakeTypeTwoTx = TransactionFactory.fromTxData({ chai.use(spies) -describe('LedgerBridgeKeyring', function () { - +describe('LedgerKeyring', function () { let keyring + let bridge let sandbox async function basicSetupToUnlockOneAccount (accountIndex = 0) { @@ -84,9 +81,23 @@ describe('LedgerBridgeKeyring', function () { } beforeEach(function () { + // eslint-disable-next-line no-empty-function + const noop = () => {} + bridge = { + init: noop, + destroy: noop, + attemptMakeApp: noop, + updateTransportMethod: noop, + getPublicKey: noop, + deviceSignTransaction: noop, + deviceSignMessage: noop, + deviceSignTypedData: noop, + } + sandbox = chai.spy.sandbox() - keyring = new LedgerKeyringMv2() + keyring = new LedgerKeyring({ bridge }) keyring.hdk = fakeHdKey + keyring.deserialize() }) afterEach(function () { @@ -95,49 +106,37 @@ describe('LedgerBridgeKeyring', function () { describe('Keyring.type', function () { it('is a class property that returns the type string.', function () { - const { type } = LedgerKeyringMv2 + const { type } = LedgerKeyring assert.equal(typeof type, 'string') }) it('returns the correct value', function () { const { type } = keyring - const correct = LedgerKeyringMv2.type + const correct = LedgerKeyring.type assert.equal(type, correct) }) }) describe('constructor', function () { - it('constructs', function (done) { - const t = new LedgerKeyringMv2({ hdPath: `m/44'/60'/0'` }) - assert.equal(typeof t, 'object') - t.getAccounts() - .then((accounts) => { - assert.equal(Array.isArray(accounts), true) - done() - }) + it('throws if a bridge is not provided', function () { + try { + // eslint-disable-next-line no-unused-vars + const _ = new LedgerKeyring({ hdPath: `m/44'/60'/0'`, bridge }) + } catch (error) { + expect(error).to.be.an('error') + expect(error.name).to.be.equal('Error') + expect(error.message).to.be.equal('Bridge is a required dependency for the keyring') + } }) }) describe('init', function () { - it('should set up the iFrame', function () { - const iframeMock = {} - sandbox.on(global.document, 'createElement', () => iframeMock) - sandbox.on(global.document.head, 'appendChild', () => true) - sandbox.on(global.window, 'addEventListener', () => true) - - keyring.init() + it('should call bridge init', async function () { + sandbox.on(bridge, 'init', () => Promise.resolve()) - expect(global.document.createElement).to.have.been.called() - expect(global.document.createElement) - .to.have.been.called.with('iframe') + await keyring.init() - expect(global.document.head.appendChild).to.have.been.called() - expect(global.document.head.appendChild) - .to.have.been.called.with(iframeMock) - - expect(global.window.addEventListener).to.have.been.called() - expect(global.window.addEventListener) - .to.have.been.called.with('message', keyring._eventListener) + expect(bridge.init).to.have.been.called() }) }) @@ -145,7 +144,6 @@ describe('LedgerBridgeKeyring', function () { it('serializes an instance', function (done) { keyring.serialize() .then((output) => { - assert.equal(output.bridgeUrl, 'https://metamask.github.io/eth-ledger-bridge-keyring') assert.equal(output.hdPath, `m/44'/60'/0'`) assert.equal(Array.isArray(output.accounts), true) assert.equal(output.accounts.length, 0) @@ -175,7 +173,6 @@ describe('LedgerBridgeKeyring', function () { return keyring.serialize() }).then((serialized) => { assert.equal(serialized.accounts.length, 1, 'restores 1 account') - assert.equal(serialized.bridgeUrl, 'https://metamask.github.io/eth-ledger-bridge-keyring', 'restores bridgeUrl') assert.equal(serialized.hdPath, someHdPath, 'restores hdPath') assert.deepEqual(serialized.accountDetails, accountDetails, 'restores accountDetails') }) @@ -238,46 +235,38 @@ describe('LedgerBridgeKeyring', function () { }) }) it('should update hdk.publicKey if updateHdk is true', function (done) { - const ledgerKeyring = new LedgerKeyringMv2() - ledgerKeyring.hdk = { publicKey: 'ABC' } - - sandbox.on(ledgerKeyring, '_sendMessage', (_, cb) => { - cb({ - success: true, - payload: { - publicKey: - '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', - chainCode: - 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', - address: fakeAccounts[1], - }, - }) + keyring.hdk = { publicKey: 'ABC' } + + sandbox.on(bridge, 'getPublicKey', (_) => { + return { + publicKey: + '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', + chainCode: + 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', + address: fakeAccounts[1], + } }) - ledgerKeyring.unlock(`m/44'/60'/0'/1`).then((_) => { - assert.notDeepEqual(ledgerKeyring.hdk.publicKey, 'ABC') + keyring.unlock(`m/44'/60'/0'/1`).then((_) => { + assert.notDeepEqual(keyring.hdk.publicKey, 'ABC') done() }) }) it('should not update hdk.publicKey if updateHdk is false', function (done) { - const ledgerKeyring = new LedgerKeyringMv2() - ledgerKeyring.hdk = { publicKey: 'ABC' } - - sandbox.on(ledgerKeyring, '_sendMessage', (_, cb) => { - cb({ - success: true, - payload: { - publicKey: - '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', - chainCode: - 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', - address: fakeAccounts[1], - }, - }) + keyring.hdk = { publicKey: 'ABC' } + + sandbox.on(bridge, 'getPublicKey', (_) => { + return { + publicKey: + '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', + chainCode: + 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', + address: fakeAccounts[1], + } }) - ledgerKeyring.unlock(`m/44'/60'/0'/1`, false).then((_) => { - assert.deepEqual(ledgerKeyring.hdk.publicKey, 'ABC') + keyring.unlock(`m/44'/60'/0'/1`, false).then((_) => { + assert.deepEqual(keyring.hdk.publicKey, 'ABC') done() }) }) @@ -517,18 +506,18 @@ describe('LedgerBridgeKeyring', function () { describe('using old versions of ethereumjs/tx', function () { it('should pass serialized transaction to ledger and return signed tx', async function () { await basicSetupToUnlockOneAccount() - sandbox.on(keyring, '_sendMessage', (msg, cb) => { - assert.deepStrictEqual(msg.params, { + sandbox.on(bridge, 'deviceSignTransaction', (params) => { + assert.deepStrictEqual(params, { hdPath: "m/44'/60'/0'/0", tx: fakeTx.serialize().toString('hex'), }) - cb({ success: true, payload: { v: '0x1', r: '0x0', s: '0x0' } }) + return { v: '0x1', r: '0x0', s: '0x0' } }) sandbox.on(fakeTx, 'verifySignature', () => true) const returnedTx = await keyring.signTransaction(fakeAccounts[0], fakeTx) - expect(keyring._sendMessage).to.have.been.called() + expect(keyring.bridge.deviceSignTransaction).to.have.been.called() expect(returnedTx).to.have.property('v') expect(returnedTx).to.have.property('r') expect(returnedTx).to.have.property('s') @@ -547,16 +536,16 @@ describe('LedgerBridgeKeyring', function () { await basicSetupToUnlockOneAccount() sandbox.on(newFakeTx, 'verifySignature', () => true) - sandbox.on(keyring, '_sendMessage', (msg, cb) => { - assert.deepStrictEqual(msg.params, { + sandbox.on(bridge, 'deviceSignTransaction', (params) => { + assert.deepStrictEqual(params, { hdPath: "m/44'/60'/0'/0", tx: ethUtil.rlp.encode(newFakeTx.getMessageToSign(false)).toString('hex'), }) - cb({ success: true, payload: expectedRSV }) + return expectedRSV }) const returnedTx = await keyring.signTransaction(fakeAccounts[0], newFakeTx, common) - expect(keyring._sendMessage).to.have.been.called() + expect(keyring.bridge.deviceSignTransaction).to.have.been.called() expect(returnedTx.toJSON()).to.deep.equal({ ...newFakeTx.toJSON(), ...expectedRSV }) }) @@ -571,16 +560,16 @@ describe('LedgerBridgeKeyring', function () { await basicSetupToUnlockOneAccount() sandbox.on(fakeTypeTwoTx, 'verifySignature', () => true) - sandbox.on(keyring, '_sendMessage', (msg, cb) => { - assert.deepStrictEqual(msg.params, { + sandbox.on(bridge, 'deviceSignTransaction', (params) => { + assert.deepStrictEqual(params, { hdPath: "m/44'/60'/0'/0", tx: fakeTypeTwoTx.getMessageToSign(false).toString('hex'), }) - cb({ success: true, payload: expectedRSV }) + return expectedRSV }) const returnedTx = await keyring.signTransaction(fakeAccounts[0], fakeTypeTwoTx, commonEIP1559) - expect(keyring._sendMessage).to.have.been.called() + expect(keyring.bridge.deviceSignTransaction).to.have.been.called() expect(returnedTx.toJSON()).to.deep.equal({ ...fakeTypeTwoTx.toJSON(), ...expectedRSV }) }) }) @@ -589,34 +578,34 @@ describe('LedgerBridgeKeyring', function () { describe('signPersonalMessage', function () { it('should call create a listener waiting for the iframe response', async function () { await basicSetupToUnlockOneAccount() - sandbox.on(keyring, '_sendMessage', (msg, cb) => { - assert.deepStrictEqual(msg.params, { + sandbox.on(bridge, 'deviceSignMessage', (params) => { + assert.deepStrictEqual(params, { hdPath: "m/44'/60'/0'/0", message: 'some msg', }) - cb({ success: true, payload: { v: '0x1', r: '0x0', s: '0x0' } }) + return { v: '0x1', r: '0x0', s: '0x0' } }) sandbox.on(sigUtil, 'recoverPersonalSignature', () => fakeAccounts[0]) await keyring.signPersonalMessage(fakeAccounts[0], 'some msg') - expect(keyring._sendMessage).to.have.been.called() + expect(keyring.bridge.deviceSignMessage).to.have.been.called() }) }) describe('signMessage', function () { it('should call create a listener waiting for the iframe response', async function () { await basicSetupToUnlockOneAccount() - sandbox.on(keyring, '_sendMessage', (msg, cb) => { - assert.deepStrictEqual(msg.params, { + sandbox.on(bridge, 'deviceSignMessage', (params) => { + assert.deepStrictEqual(params, { hdPath: "m/44'/60'/0'/0", message: 'some msg', }) - cb({ success: true, payload: { v: '0x1', r: '0x0', s: '0x0' } }) + return { v: '0x1', r: '0x0', s: '0x0' } }) sandbox.on(sigUtil, 'recoverPersonalSignature', () => fakeAccounts[0]) await keyring.signMessage(fakeAccounts[0], 'some msg') - expect(keyring._sendMessage).to.have.been.called() + expect(keyring.bridge.deviceSignMessage).to.have.been.called() }) }) @@ -700,8 +689,8 @@ describe('LedgerBridgeKeyring', function () { }) it('should resolve properly when called', async function () { - sandbox.on(keyring, '_sendMessage', (_, cb) => { - cb({ success: true, payload: { v: '27', r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32' } }) + sandbox.on(bridge, 'deviceSignTypedData', (_) => { + return { v: '27', r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32' } }) const result = await keyring.signTypedData(fakeAccounts[15], fixtureData, options) @@ -709,9 +698,9 @@ describe('LedgerBridgeKeyring', function () { }) it('should error when address does not match', async function () { - sandbox.on(keyring, '_sendMessage', (_, cb) => { + sandbox.on(bridge, 'deviceSignTypedData', (_) => { // Changing v to 28 should cause a validation error - cb({ success: true, payload: { v: '28', r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32' } }) + return { v: '28', r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32' } }) assert.rejects(keyring.signTypedData(fakeAccounts[15], fixtureData, options), new Error('Ledger: The signature doesnt match the right address')) @@ -719,12 +708,12 @@ describe('LedgerBridgeKeyring', function () { }) describe('destroy', function () { - it('should remove the message event listener', function () { - sandbox.on(global.window, 'removeEventListener', () => true) - keyring.destroy() - expect(global.window.removeEventListener).to.have.been.called() - expect(global.window.removeEventListener) - .to.have.been.called.with('message', keyring._eventListener) + it('should remove the message event listener', async function () { + sandbox.on(bridge, 'destroy', () => Promise.resolve()) + + await keyring.destroy() + + expect(bridge.destroy).to.have.been.called() }) }) }) From 502d03241f68b64668e4d04f105ca67b6c8c97bf Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Tue, 7 Mar 2023 13:20:12 +0000 Subject: [PATCH 10/22] types and package.json fix --- ledger-iframe-bridge.js | 20 +++++++++----------- ledger-keyring.js | 5 ++++- package.json | 2 +- test/ledger-keyring.test.js | 2 ++ types/index.d.ts | 20 ++++++++++---------- 5 files changed, 26 insertions(+), 23 deletions(-) diff --git a/ledger-iframe-bridge.js b/ledger-iframe-bridge.js index 40666c38..7c9b9cb4 100644 --- a/ledger-iframe-bridge.js +++ b/ledger-iframe-bridge.js @@ -1,15 +1,13 @@ -const { BRIDGE_URL } = require('./ledger-keyring') - const CONNECTION_EVENT = 'ledger-connection-change' class LedgerIframeBridge { - init () { + init (bridgeUrl) { this.iframeLoaded = false - this._setupIframe() + this._setupIframe(bridgeUrl) this.currentMessageId = 0 this.messageCallbacks = {} - this._setupListener() + this._setupListener(bridgeUrl) return Promise.resolve() } @@ -100,9 +98,9 @@ class LedgerIframeBridge { }) } - _setupIframe () { + _setupIframe (bridgeUrl) { this.iframe = document.createElement('iframe') - this.iframe.src = BRIDGE_URL + this.iframe.src = bridgeUrl this.iframe.allow = `hid 'src'` this.iframe.onload = async () => { // If the ledger live preference was set before the iframe is loaded, @@ -124,15 +122,15 @@ class LedgerIframeBridge { document.head.appendChild(this.iframe) } - _getOrigin () { - const tmp = this.bridgeUrl.split('/') + _getOrigin (bridgeUrl) { + const tmp = bridgeUrl.split('/') tmp.splice(-1, 1) return tmp.join('/') } - _setupListener () { + _setupListener (bridgeUrl) { this._eventListener = ({ origin, data }) => { - if (origin !== this._getOrigin()) { + if (origin !== this._getOrigin(bridgeUrl)) { return false } diff --git a/ledger-keyring.js b/ledger-keyring.js index 16d93751..7a301211 100644 --- a/ledger-keyring.js +++ b/ledger-keyring.js @@ -22,6 +22,7 @@ class LedgerKeyring extends EventEmitter { constructor ({ bridge } = {}) { super() this.accountDetails = {} + this.bridgeUrl = null this.type = type this.page = 0 this.perPage = 5 @@ -44,7 +45,7 @@ class LedgerKeyring extends EventEmitter { * @returns {Promise} */ init () { - return this.bridge.init() + return this.bridge.init(this.bridgeUrl || BRIDGE_URL) } /** @@ -61,12 +62,14 @@ class LedgerKeyring extends EventEmitter { hdPath: this.hdPath, accounts: this.accounts, accountDetails: this.accountDetails, + bridgeUrl: this.bridgeUrl, implementFullBIP44: false, }) } deserialize (opts = {}) { this.hdPath = opts.hdPath || hdPathString + this.bridgeUrl = opts.bridgeUrl || BRIDGE_URL this.accounts = opts.accounts || [] this.accountDetails = opts.accountDetails || {} if (!opts.accountDetails) { diff --git a/package.json b/package.json index 2856a992..997834ed 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "files": [ "index.js", "ledger-keyring.js", - "ledger-bridge-iframe.js", + "ledger-iframe-bridge.js", "types/index.d.ts" ], "engines": { diff --git a/test/ledger-keyring.test.js b/test/ledger-keyring.test.js index 0b9437c0..6e1c7134 100644 --- a/test/ledger-keyring.test.js +++ b/test/ledger-keyring.test.js @@ -144,6 +144,7 @@ describe('LedgerKeyring', function () { it('serializes an instance', function (done) { keyring.serialize() .then((output) => { + assert.equal(output.bridgeUrl, 'https://metamask.github.io/eth-ledger-bridge-keyring') assert.equal(output.hdPath, `m/44'/60'/0'`) assert.equal(Array.isArray(output.accounts), true) assert.equal(output.accounts.length, 0) @@ -173,6 +174,7 @@ describe('LedgerKeyring', function () { return keyring.serialize() }).then((serialized) => { assert.equal(serialized.accounts.length, 1, 'restores 1 account') + assert.equal(serialized.bridgeUrl, 'https://metamask.github.io/eth-ledger-bridge-keyring', 'restores bridgeUrl') assert.equal(serialized.hdPath, someHdPath, 'restores hdPath') assert.deepEqual(serialized.accountDetails, accountDetails, 'restores accountDetails') }) diff --git a/types/index.d.ts b/types/index.d.ts index 5a013e1c..0cae26d2 100644 --- a/types/index.d.ts +++ b/types/index.d.ts @@ -1,25 +1,25 @@ -export type GetPublicKeyPayload = { hdPath: string }; +export type GetPublicKeyParams = { hdPath: string }; export type GetPublicKeyResponse = { publicKey: string; address: string; chainCode?: string; }; -export type LedgerSignTransactionPayload = { hdPath: string; rawTxHex: string }; +export type LedgerSignTransactionParams = { hdPath: string; tx: string }; export type LedgerSignTransactionResponse = { s: string; v: string; r: string; }; -export type LedgerSignMessagePayload = { hdPath: string; message: string }; +export type LedgerSignMessageParams = { hdPath: string; message: string }; export type LedgerSignMessageResponse = { v: number; s: string; r: string; }; -export type LedgerSignTypedDataPayload = { +export type LedgerSignTypedDataParams = { hdPath: string; domainSeparatorHex: string; hashStructMessageHex: string; @@ -37,19 +37,19 @@ export interface LedgerBridge { attemptMakeApp(): Promise; - getPublicKey( - payload: GetPublicKeyPayload - ): Promise; + updateTransportMethod(transportType: string): Promise; + + getPublicKey(params: GetPublicKeyParams): Promise; deviceSignTransaction( - payload: LedgerSignTransactionPayload + params: LedgerSignTransactionParams ): Promise; deviceSignMessage( - payload: LedgerSignMessagePayload + params: LedgerSignMessageParams ): Promise; deviceSignTypedData( - payload: LedgerSignTypedDataPayload + params: LedgerSignTypedDataParams ): Promise; } From 5b923b509663ad213db360ad6bddf7b49de34dfb Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 28 Apr 2023 13:11:58 +0100 Subject: [PATCH 11/22] jest --- jest.config.js | 204 +++ package.json | 11 +- src/ledger-iframe-bridge.test.ts | 200 ++- src/ledger-iframe-bridge.ts | 37 +- src/ledger-keyring.test.ts | 439 +++--- yarn.lock | 2273 +++++++++++++++++++++++++++--- 6 files changed, 2659 insertions(+), 505 deletions(-) create mode 100644 jest.config.js diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..fd1d6398 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,204 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/configuration + */ + +module.exports = { + // All imported modules in your tests should be mocked automatically + // automock: false, + + // Stop running tests after `n` failures + // bail: 0, + + // The directory where Jest should store its cached dependency information + // cacheDirectory: "/private/var/folders/fk/c3y07g0576j8_2s9m01pk4qw0000gn/T/jest_dx", + + // Automatically clear mock calls, instances and results before every test. + // This does not remove any mock implementation that may have been provided, + // so we disable it. + // clearMocks: true, + + // Indicates whether the coverage information should be collected while executing the test + collectCoverage: true, + + // An array of glob patterns indicating a set of files for which coverage information should be collected + collectCoverageFrom: ['./src/**/*.ts'], + + // The directory where Jest should output its coverage files + coverageDirectory: 'coverage', + + // An array of regexp pattern strings used to skip coverage collection + // coveragePathIgnorePatterns: [ + // "/node_modules/" + // ], + + // Indicates which provider should be used to instrument code for coverage + coverageProvider: 'babel', + + // A list of reporter names that Jest uses when writing coverage reports + coverageReporters: ['html', 'json-summary', 'text'], + + // An object that configures minimum threshold enforcement for coverage results + coverageThreshold: { + global: { + branches: 59, + functions: 81, + lines: 78, + statements: 78, + }, + }, + + // A path to a custom dependency extractor + // dependencyExtractor: undefined, + + // Make calling deprecated APIs throw helpful error messages + // errorOnDeprecated: false, + + // Force coverage collection from ignored files using an array of glob patterns + // forceCoverageMatch: [], + + // A path to a module which exports an async function that is triggered once before all test suites + // globalSetup: undefined, + + // A path to a module which exports an async function that is triggered once after all test suites + // globalTeardown: undefined, + + // A set of global variables that need to be available in all test environments + // globals: {}, + + // The maximum amount of workers used to run your tests. Can be specified as % or a number. E.g. maxWorkers: 10% will use 10% of your CPU amount + 1 as the maximum worker number. maxWorkers: 2 will use a maximum of 2 workers. + // maxWorkers: "50%", + + // An array of directory names to be searched recursively up from the requiring module's location + // moduleDirectories: [ + // "node_modules" + // ], + + // An array of file extensions your modules use + // moduleFileExtensions: [ + // "js", + // "jsx", + // "ts", + // "tsx", + // "json", + // "node" + // ], + + // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module + // moduleNameMapper: {}, + + // An array of regexp pattern strings, matched against all module paths before considered 'visible' to the module loader + // modulePathIgnorePatterns: [], + + // Activates notifications for test results + // notify: false, + + // An enum that specifies notification mode. Requires { notify: true } + // notifyMode: "failure-change", + + // A preset that is used as a base for Jest's configuration + preset: 'ts-jest', + + // Run tests from one or more projects + // projects: undefined, + + // Use this configuration option to add custom reporters to Jest + // reporters: undefined, + + // "resetMocks" resets all mocks, including mocked modules, to jest.fn(), + // between each test case. + resetMocks: true, + + // Reset the module registry before running each individual test + // resetModules: false, + + // A path to a custom resolver + // resolver: undefined, + + // "restoreMocks" restores all mocks created using jest.spyOn to their + // original implementations, between each test. It does not affect mocked + // modules. + restoreMocks: true, + + // The root directory that Jest should scan for tests and modules within + // rootDir: undefined, + + // A list of paths to directories that Jest should use to search for files in + // roots: [ + // "" + // ], + + // Allows you to use a custom runner instead of Jest's default test runner + // runner: "jest-runner", + + // The paths to modules that run some code to configure or set up the testing environment before each test + // setupFiles: [], + + // A list of paths to modules that run some code to configure or set up the testing framework before each test + // setupFilesAfterEnv: [], + + // The number of seconds after which a test is considered as slow and reported as such in the results. + // slowTestThreshold: 5, + + // A list of paths to snapshot serializer modules Jest should use for snapshot testing + // snapshotSerializers: [], + + // The test environment that will be used for testing + // testEnvironment: "jest-environment-node", + + // Options that will be passed to the testEnvironment + // testEnvironmentOptions: {}, + + // Adds a location field to test results + // testLocationInResults: false, + + // The glob patterns Jest uses to detect test files + // testMatch: [ + // "**/__tests__/**/*.[jt]s?(x)", + // "**/?(*.)+(spec|test).[tj]s?(x)" + // ], + + // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped + // testPathIgnorePatterns: [ + // "/node_modules/" + // ], + + // The regexp pattern or array of patterns that Jest uses to detect test files + // testRegex: [], + + // This option allows the use of a custom results processor + // testResultsProcessor: undefined, + + // This option allows use of a custom test runner + // testRunner: "jest-circus/runner", + + // Reduce the default test timeout from 5s to 2.5s + testTimeout: 2500, + + // This option sets the URL for the jsdom environment. It is reflected in properties such as location.href + // testURL: "http://localhost", + + // Setting this value to "fake" allows the use of fake timers for functions such as "setTimeout" + // timers: "real", + + // A map from regular expressions to paths to transformers + // transform: undefined, + + // An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation + // transformIgnorePatterns: [ + // "/node_modules/", + // "\\.pnp\\.[^\\/]+$" + // ], + + // An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them + // unmockedModulePathPatterns: undefined, + + // Indicates whether each individual test should be reported during the run + // verbose: undefined, + + // An array of regexp patterns that are matched against all source file paths before re-running tests in watch mode + // watchPathIgnorePatterns: [], + + // Whether to use watchman for file crawling + // watchman: true, +}; \ No newline at end of file diff --git a/package.json b/package.json index 18481db4..cbdeb6fa 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "lint:fix": "yarn lint:eslint --fix && yarn lint:misc --write && yarn lint:dependencies", "lint:misc": "prettier '**/*.json' '**/*.md' '!CHANGELOG.md' '**/*.yml' '!.yarnrc.yml' --ignore-path .gitignore --no-error-on-unmatched-pattern", "prepack": "./scripts/prepack.sh", - "test": "mocha -r ts-node/register src/**/*.ts" + "test": "jest", + "test:watch": "jest --watch" }, "dependencies": { "@ethereumjs/tx": "^4.1.1", @@ -48,7 +49,7 @@ "@ledgerhq/hw-app-eth": "^6.32.0", "@metamask/eslint-config": "^11.0.1", "@metamask/eslint-config-browser": "^11.0.0", - "@metamask/eslint-config-mocha": "^11.0.0", + "@metamask/eslint-config-jest": "^11.0.0", "@metamask/eslint-config-nodejs": "^11.0.0", "@metamask/eslint-config-typescript": "^11.0.0", "@types/chai": "^4.3.4", @@ -56,7 +57,7 @@ "@types/eth-sig-util": "^2.1.1", "@types/ethereumjs-tx": "^1.0.1", "@types/hdkey": "^2.0.1", - "@types/mocha": "^5.0.0", + "@types/jest": "^28.1.6", "@types/node": "^14.0.0", "@typescript-eslint/eslint-plugin": "^5.43.0", "@typescript-eslint/parser": "^5.43.0", @@ -66,15 +67,17 @@ "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-import": "^2.26.0", + "eslint-plugin-jest": "^27.1.5", "eslint-plugin-jsdoc": "^39.6.2", "eslint-plugin-mocha": "^10.1.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prettier": "^4.2.1", "ethereumjs-tx": "^1.3.4", - "mocha": "^5.0.4", + "jest": "^28.1.3", "prettier": "^2.7.1", "prettier-plugin-packagejson": "^2.2.12", "rimraf": "^4.1.2", + "ts-jest": "^28.0.7", "ts-node": "^10.7.0", "typedoc": "^0.23.15", "typescript": "~4.8.4" diff --git a/src/ledger-iframe-bridge.test.ts b/src/ledger-iframe-bridge.test.ts index f47fc43b..42264f2f 100644 --- a/src/ledger-iframe-bridge.test.ts +++ b/src/ledger-iframe-bridge.test.ts @@ -1,8 +1,4 @@ import { hasProperty } from '@metamask/utils'; -import { strict as assert } from 'assert'; -import chai, { expect } from 'chai'; -import spies from 'chai-spies'; - import { LedgerIframeBridge } from './ledger-iframe-bridge'; import documentShim from '../test/document.shim'; import windowShim from '../test/window.shim'; @@ -15,8 +11,6 @@ type HTMLIFrameElementShim = HTMLIFrameElement; // eslint-disable-next-line no-restricted-globals type WindowShim = Window; -chai.use(spies); - /** * Checks if the iframe provided has a valid contentWindow * and onload function. @@ -53,7 +47,6 @@ async function simulateIFrameLoad(iframe?: HTMLIFrameElementShim) { describe('LedgerIframeBridge', function () { let bridge: LedgerIframeBridge; - let sandbox: ChaiSpies.Sandbox; /** * Stubs the postMessage function of the keyring iframe. @@ -69,56 +62,56 @@ describe('LedgerIframeBridge', function () { throw new Error('the iframe is not valid'); } - sandbox.on(bridgeInstance.iframe.contentWindow, 'postMessage', fn); + jest + .spyOn(bridgeInstance.iframe.contentWindow, 'postMessage') + .mockImplementation(fn); } beforeEach(async function () { - sandbox = chai.spy.sandbox(); - bridge = new LedgerIframeBridge(); await bridge.init('bridgeUrl'); await simulateIFrameLoad(bridge.iframe); }); afterEach(function () { - sandbox.restore(); + jest.clearAllMocks(); }); describe('init', function () { it('sets up the listener and iframe', async function () { bridge = new LedgerIframeBridge(); - sandbox.on(global.window, 'addEventListener'); + const addEventListenerSpy = jest.spyOn(global.window, 'addEventListener'); const bridgeUrl = 'bridgeUrl'; await bridge.init(bridgeUrl); - expect(global.window.addEventListener).to.have.been.called(); - - expect(bridge.bridgeUrl).to.equal(bridgeUrl); - - expect(bridge.iframeLoaded).to.equal(false); + expect(addEventListenerSpy).toHaveBeenCalledTimes(1); + expect(bridge.bridgeUrl).toBe(bridgeUrl); + expect(bridge.iframeLoaded).toBe(false); await simulateIFrameLoad(bridge.iframe); - - expect(bridge.iframeLoaded).to.equal(true); + expect(bridge.iframeLoaded).toBe(true); }); }); describe('destroy', function () { it('removes the message event listener', async function () { - sandbox.on(global.window, 'removeEventListener'); + const removeEventListenerSpy = jest.spyOn( + global.window, + 'removeEventListener', + ); await bridge.destroy(); - expect(global.window.removeEventListener).to.have.been.called(); + expect(removeEventListenerSpy).toHaveBeenCalledTimes(1); }); }); describe('attemptMakeApp', function () { it('sends and processes a successful ledger-make-app message', async function () { stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-make-app', messageId: 1, target: 'LEDGER-IFRAME', @@ -131,17 +124,16 @@ describe('LedgerIframeBridge', function () { const result = await bridge.attemptMakeApp(); - expect(result).to.equal(true); + expect(result).toBe(true); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); it('throws an error when a ledger-make-app message is not successful', async function () { const errorMessage = 'Ledger Error'; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-make-app', messageId: 1, target: 'LEDGER-IFRAME', @@ -153,16 +145,9 @@ describe('LedgerIframeBridge', function () { } as any); }); - try { - await bridge.attemptMakeApp(); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal(errorMessage); - } + await expect(bridge.attemptMakeApp()).rejects.toThrowError(errorMessage); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); @@ -173,7 +158,7 @@ describe('LedgerIframeBridge', function () { const transportType = 'u2f'; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-update-transport', params: { transportType }, messageId: 1, @@ -187,10 +172,9 @@ describe('LedgerIframeBridge', function () { const result = await bridge.updateTransportMethod(transportType); - expect(result).to.equal(true); + expect(result).toBe(true); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); it('throws an error when a ledger-update-transport message is not successful', async function () { @@ -199,7 +183,7 @@ describe('LedgerIframeBridge', function () { const transportType = 'u2f'; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-update-transport', params: { transportType }, messageId: 1, @@ -211,28 +195,23 @@ describe('LedgerIframeBridge', function () { } as any); }); - try { - await bridge.updateTransportMethod(transportType); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal( - 'Ledger transport could not be updated', - ); - } - - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + await expect( + bridge.updateTransportMethod(transportType), + ).rejects.toThrowError('Ledger transport could not be updated'); + + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); describe('getPublicKey', function () { it('sends and processes a successful ledger-unlock message', async function () { const payload = {}; - const params = {}; + const params = { + hdPath: "m/44'/60'/0'/0", + }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-unlock', params, messageId: 1, @@ -247,18 +226,19 @@ describe('LedgerIframeBridge', function () { const result = await bridge.getPublicKey(params); - expect(result).to.equal(payload); + expect(result).toBe(payload); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); it('throws an error when a ledger-unlock message is not successful', async function () { const errorMessage = 'Ledger Error'; - const params = {}; + const params = { + hdPath: "m/44'/60'/0'/0", + }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-unlock', params, messageId: 1, @@ -271,26 +251,24 @@ describe('LedgerIframeBridge', function () { } as any); }); - try { - await bridge.getPublicKey(params); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal(errorMessage); - } + await expect(bridge.getPublicKey(params)).rejects.toThrowError( + errorMessage, + ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); describe('deviceSignTransaction', function () { it('sends and processes a successful ledger-sign-transaction message', async function () { const payload = {}; - const params = {}; + const params = { + hdPath: "m/44'/60'/0'/0", + tx: '', + }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-sign-transaction', params, messageId: 1, @@ -305,18 +283,17 @@ describe('LedgerIframeBridge', function () { const result = await bridge.deviceSignTransaction(params); - expect(result).to.equal(payload); + expect(result).toBe(payload); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); it('throws an error when a ledger-sign-transaction message is not successful', async function () { const errorMessage = 'Ledger Error'; - const params = {}; + const params = { hdPath: "m/44'/60'/0'/0", tx: '' }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-sign-transaction', params, messageId: 1, @@ -329,26 +306,21 @@ describe('LedgerIframeBridge', function () { } as any); }); - try { - await bridge.deviceSignTransaction(params); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal(errorMessage); - } + await expect(bridge.deviceSignTransaction(params)).rejects.toThrowError( + errorMessage, + ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); describe('deviceSignMessage', function () { it('sends and processes a successful ledger-sign-personal-message message', async function () { const payload = {}; - const params = {}; + const params = { hdPath: "m/44'/60'/0'/0", message: '' }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-sign-personal-message', params, messageId: 1, @@ -363,18 +335,17 @@ describe('LedgerIframeBridge', function () { const result = await bridge.deviceSignMessage(params); - expect(result).to.equal(payload); + expect(result).toBe(payload); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); it('throws an error when a ledger-sign-personal-message message is not successful', async function () { const errorMessage = 'Ledger Error'; - const params = {}; + const params = { hdPath: "m/44'/60'/0'/0", message: '' }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-sign-personal-message', params, messageId: 1, @@ -387,26 +358,25 @@ describe('LedgerIframeBridge', function () { } as any); }); - try { - await bridge.deviceSignMessage(params); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal(errorMessage); - } + await expect(bridge.deviceSignMessage(params)).rejects.toThrowError( + errorMessage, + ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); describe('deviceSignTypedData', function () { it('sends and processes a successful ledger-sign-typed-data message', async function () { const payload = {}; - const params = {}; + const params = { + hdPath: "m/44'/60'/0'/0", + domainSeparatorHex: '', + hashStructMessageHex: '', + }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-sign-typed-data', params, messageId: 1, @@ -421,18 +391,21 @@ describe('LedgerIframeBridge', function () { const result = await bridge.deviceSignTypedData(params); - expect(result).to.equal(payload); + expect(result).toBe(payload); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); it('throws an error when a ledger-sign-typed-data message is not successful', async function () { const errorMessage = 'Ledger Error'; - const params = {}; + const params = { + hdPath: "m/44'/60'/0'/0", + domainSeparatorHex: '', + hashStructMessageHex: '', + }; stubKeyringIFramePostMessage(bridge, (message) => { - assert.deepStrictEqual(message, { + expect(message).toStrictEqual({ action: 'ledger-sign-typed-data', params, messageId: 1, @@ -445,16 +418,11 @@ describe('LedgerIframeBridge', function () { } as any); }); - try { - await bridge.deviceSignTypedData(params); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal(errorMessage); - } + await expect(bridge.deviceSignTypedData(params)).rejects.toThrowError( + errorMessage, + ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.iframe?.contentWindow?.postMessage).to.have.been.called(); + expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); }); diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index 758348e3..af448d5b 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -1,5 +1,16 @@ import type LedgerHwAppEth from '@ledgerhq/hw-app-eth'; import { hasProperty } from '@metamask/utils'; +import { + GetPublicKeyParams, + GetPublicKeyResponse, + LedgerBridge, + LedgerSignMessageParams, + LedgerSignMessageResponse, + LedgerSignTransactionParams, + LedgerSignTransactionResponse, + LedgerSignTypedDataParams, + LedgerSignTypedDataResponse, +} from './ledger-bridge'; const LEDGER_IFRAME_ID = 'LEDGER-IFRAME'; @@ -68,7 +79,7 @@ function isConnectionChangedResponse( ); } -export class LedgerIframeBridge { +export class LedgerIframeBridge implements LedgerBridge { iframe?: HTMLIFrameElement; iframeLoaded = false; @@ -83,7 +94,7 @@ export class LedgerIframeBridge { {}; delayedPromise?: { - resolve: (value: unknown) => void; + resolve: (value: boolean) => void; reject: (error: unknown) => void; transportType: string; }; @@ -100,7 +111,7 @@ export class LedgerIframeBridge { window.removeEventListener('message', this.#eventListener.bind(this)); } - async attemptMakeApp() { + async attemptMakeApp(): Promise { return new Promise((resolve, reject) => { this.#sendMessage( { @@ -117,7 +128,7 @@ export class LedgerIframeBridge { }); } - async updateTransportMethod(transportType: string) { + async updateTransportMethod(transportType: string): Promise { return new Promise((resolve, reject) => { // If the iframe isn't loaded yet, let's store the desired transportType value and // optimistically return a successful promise @@ -146,25 +157,33 @@ export class LedgerIframeBridge { }); } - async getPublicKey(params: any) { + async getPublicKey( + params: GetPublicKeyParams, + ): Promise { return this.#deviceActionMessage(IFrameMessageAction.LedgerUnlock, params); } - async deviceSignTransaction(params: any) { + async deviceSignTransaction( + params: LedgerSignTransactionParams, + ): Promise { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignTransaction, params, ); } - async deviceSignMessage(params: any) { + async deviceSignMessage( + params: LedgerSignMessageParams, + ): Promise { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignPersonalMessage, params, ); } - async deviceSignTypedData(params: any) { + async deviceSignTypedData( + params: LedgerSignTypedDataParams, + ): Promise { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignTypedData, params, @@ -172,7 +191,7 @@ export class LedgerIframeBridge { } async #deviceActionMessage(action: IFrameMessageAction, params: any) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { this.#sendMessage( { action, diff --git a/src/ledger-keyring.test.ts b/src/ledger-keyring.test.ts index 9fb18248..df121f2f 100644 --- a/src/ledger-keyring.test.ts +++ b/src/ledger-keyring.test.ts @@ -1,8 +1,5 @@ import { Common, Chain, Hardfork } from '@ethereumjs/common'; import { TransactionFactory } from '@ethereumjs/tx'; -import { strict as assert } from 'assert'; -import chai, { expect } from 'chai'; -import spies from 'chai-spies'; import sigUtil from 'eth-sig-util'; import EthereumTx from 'ethereumjs-tx'; import * as ethUtil from 'ethereumjs-util'; @@ -10,6 +7,7 @@ import HDKey from 'hdkey'; import { LedgerBridge } from './ledger-bridge'; import { AccountDetails, LedgerKeyring } from './ledger-keyring'; +import { LedgerIframeBridge } from './ledger-iframe-bridge'; const fakeAccounts = [ '0xF30952A1c534CDE7bC471380065726fa8686dfB3', @@ -76,12 +74,9 @@ const fakeTypeTwoTx = TransactionFactory.fromTxData( { common: commonEIP1559, freeze: false }, ); -chai.use(spies); - describe('LedgerKeyring', function () { let keyring: LedgerKeyring; let bridge: LedgerBridge; - let sandbox: ChaiSpies.Sandbox; /** * Sets up the keyring to unlock one account. @@ -92,7 +87,9 @@ describe('LedgerKeyring', function () { async function basicSetupToUnlockOneAccount(accountIndex = 0) { keyring.setAccountToUnlock(accountIndex); await keyring.addAccounts(); - sandbox.on(keyring, 'unlock', async () => fakeAccounts[accountIndex]); + jest + .spyOn(keyring, 'unlock') + .mockResolvedValue(fakeAccounts[accountIndex] as string); } beforeEach(async function () { @@ -109,54 +106,56 @@ describe('LedgerKeyring', function () { deviceSignTypedData: noop, } as any; - sandbox = chai.spy.sandbox(); keyring = new LedgerKeyring({ bridge }); keyring.hdk = fakeHdKey; await keyring.deserialize(); }); afterEach(function () { - sandbox.restore(); + jest.clearAllMocks(); }); describe('Keyring.type', function () { it('is a class property that returns the type string.', function () { const { type } = LedgerKeyring; - assert.equal(typeof type, 'string'); + expect(typeof type).toBe('string'); }); it('returns the correct value', function () { const { type } = keyring; const correct = LedgerKeyring.type; - assert.equal(type, correct); + expect(type).toBe(correct); }); }); describe('constructor', function () { + it('constructs', async function () { + const ledgerKeyring = new LedgerKeyring({ + bridge: new LedgerIframeBridge(), + }); + expect(typeof ledgerKeyring).toBe('object'); + + const accounts = await ledgerKeyring.getAccounts(); + expect(Array.isArray(accounts)).toBe(true); + }); + it('throws if a bridge is not provided', function () { - try { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const _ = new LedgerKeyring({ - bridge: undefined as unknown as LedgerBridge, - }); - } catch (error: any) { - expect(error).to.be.an('error'); - expect(error.name).to.be.equal('Error'); - expect(error.message).to.be.equal( - 'Bridge is a required dependency for the keyring', - ); - } + expect( + () => + new LedgerKeyring({ + bridge: undefined as unknown as LedgerBridge, + }), + ).toThrowError('Bridge is a required dependency for the keyring'); }); }); describe('init', function () { it('should call bridge init', async function () { - sandbox.on(bridge, 'init', async () => Promise.resolve()); + jest.spyOn(bridge, 'init').mockResolvedValue(undefined); await keyring.init(); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.init).to.have.been.called(); + expect(bridge.init).toHaveBeenCalledTimes(1); }); }); @@ -164,13 +163,12 @@ describe('LedgerKeyring', function () { it('serializes an instance', async function () { const output = await keyring.serialize(); - assert.equal( - output.bridgeUrl, + expect(output.bridgeUrl).toBe( 'https://metamask.github.io/eth-ledger-bridge-keyring', ); - assert.equal(output.hdPath, `m/44'/60'/0'`); - assert.equal(Array.isArray(output.accounts), true); - assert.equal(output.accounts.length, 0); + expect(output.hdPath).toBe(`m/44'/60'/0'`); + expect(Array.isArray(output.accounts)).toBe(true); + expect(output.accounts).toHaveLength(0); }); }); @@ -191,18 +189,12 @@ describe('LedgerKeyring', function () { }); const serialized = await keyring.serialize(); - assert.equal(serialized.accounts.length, 1, 'restores 1 account'); - assert.equal( - serialized.bridgeUrl, + expect(serialized.accounts).toHaveLength(1); + expect(serialized.bridgeUrl).toBe( 'https://metamask.github.io/eth-ledger-bridge-keyring', - 'restores bridgeUrl', - ); - assert.equal(serialized.hdPath, someHdPath, 'restores hdPath'); - assert.deepEqual( - serialized.accountDetails, - accountDetails, - 'restores accountDetails', ); + expect(serialized.hdPath).toBe(someHdPath); + expect(serialized.accountDetails).toStrictEqual(accountDetails); }); it('should migrate accountIndexes to accountDetails', async function () { @@ -217,9 +209,9 @@ describe('LedgerKeyring', function () { hdPath: someHdPath, }); - assert.equal(keyring.hdPath, someHdPath); - assert.equal(keyring.accounts[0], account); - assert.deepEqual(keyring.accountDetails[checksum], { + expect(keyring.hdPath).toBe(someHdPath); + expect(keyring.accounts[0]).toBe(account); + expect(keyring.accountDetails[checksum]).toStrictEqual({ bip44: true, hdPath: `m/44'/60'/1'/0/0`, }); @@ -234,9 +226,9 @@ describe('LedgerKeyring', function () { hdPath: someHdPath, }); - assert.equal(keyring.hdPath, someHdPath); - assert.equal(keyring.accounts[0], account); - assert.deepEqual(keyring.accountDetails[checksum], { + expect(keyring.hdPath).toStrictEqual(someHdPath); + expect(keyring.accounts[0]).toBe(account); + expect(keyring.accountDetails[checksum]).toStrictEqual({ bip44: false, hdPath: `m/44'/60'/0'/1`, }); @@ -245,72 +237,68 @@ describe('LedgerKeyring', function () { describe('isUnlocked', function () { it('should return true if we have a public key', function () { - assert.equal(keyring.isUnlocked(), true); + expect(keyring.isUnlocked()).toBe(true); }); }); describe('unlock', function () { it('should resolve if we have a public key', async function () { - await keyring.unlock(); + expect(async () => { + await keyring.unlock(); + }).not.toThrow(); }); it('should update hdk.publicKey if updateHdk is true', async function () { // @ts-expect-error we want to bypass the set publicKey property set method keyring.hdk = { publicKey: 'ABC' }; - sandbox.on(bridge, 'getPublicKey', (_) => { - return { - publicKey: - '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', - chainCode: - 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', - address: fakeAccounts[1], - }; + jest.spyOn(bridge, 'getPublicKey').mockResolvedValue({ + publicKey: + '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', + chainCode: + 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', + address: fakeAccounts[1], }); await keyring.unlock(`m/44'/60'/0'/1`); - assert.notDeepEqual(keyring.hdk.publicKey, 'ABC'); + expect(keyring.hdk.publicKey).not.toBe('ABC'); }); it('should not update hdk.publicKey if updateHdk is false', async function () { // @ts-expect-error we want to bypass the publicKey property set method keyring.hdk = { publicKey: 'ABC' }; - sandbox.on(bridge, 'getPublicKey', (_) => { - return { - publicKey: - '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', - chainCode: - 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', - address: fakeAccounts[1], - }; + jest.spyOn(bridge, 'getPublicKey').mockResolvedValue({ + publicKey: + '04197ced33b63059074b90ddecb9400c45cbc86210a20317b539b8cae84e573342149c3384ae45f27db68e75823323e97e03504b73ecbc47f5922b9b8144345e5a', + chainCode: + 'ba0fb16e01c463d1635ec36f5adeb93a838adcd1526656c55f828f1e34002a8b', + address: fakeAccounts[1], }); await keyring.unlock(`m/44'/60'/0'/1`, false); - assert.deepEqual(keyring.hdk.publicKey, 'ABC'); + expect(keyring.hdk.publicKey).toBe('ABC'); }); }); describe('setHdPath', function () { - it('should set the hdPath', function (done) { + it('should set the hdPath', function () { const someHDPath = `m/44'/99'/0`; keyring.setHdPath(someHDPath); - assert.equal(keyring.hdPath, someHDPath); - done(); + expect(keyring.hdPath).toBe(someHDPath); }); - it('should reset the HDKey if the path changes', function (done) { + it('should reset the HDKey if the path changes', function () { const someHDPath = `m/44'/99'/0`; keyring.setHdPath(someHDPath); - assert.equal(keyring.hdk.publicKey, null); - done(); + expect(keyring.hdk.publicKey).toBeNull(); }); }); describe('setAccountToUnlock', function () { it('should set unlockedAccount', function () { keyring.setAccountToUnlock(3); - assert.equal(keyring.unlockedAccount, 3); + expect(keyring.unlockedAccount).toBe(3); }); }); @@ -319,7 +307,7 @@ describe('LedgerKeyring', function () { it('returns a single account', async function () { keyring.setAccountToUnlock(0); const accounts = await keyring.addAccounts(); - assert.equal(accounts.length, 1); + expect(accounts).toHaveLength(1); }); }); @@ -327,26 +315,24 @@ describe('LedgerKeyring', function () { it('returns that number of accounts', async function () { keyring.setAccountToUnlock(0); const accounts = await keyring.addAccounts(5); - assert.equal(accounts.length, 5); + expect(accounts).toHaveLength(5); }); it('returns the expected accounts', async function () { keyring.setAccountToUnlock(0); const accounts = await keyring.addAccounts(3); - assert.equal(accounts[0], fakeAccounts[0]); - assert.equal(accounts[1], fakeAccounts[1]); - assert.equal(accounts[2], fakeAccounts[2]); + expect(accounts[0]).toBe(fakeAccounts[0]); + expect(accounts[1]).toBe(fakeAccounts[1]); + expect(accounts[2]).toBe(fakeAccounts[2]); }); }); it('stores account details for bip44 accounts', async function () { keyring.setHdPath(`m/44'/60'/0'/0/0`); keyring.setAccountToUnlock(1); - sandbox.on(keyring, 'unlock', async () => - Promise.resolve(fakeAccounts[0]), - ); + jest.spyOn(keyring, 'unlock').mockResolvedValue(fakeAccounts[0]); const accounts = await keyring.addAccounts(1); - assert.deepEqual(keyring.accountDetails[accounts[0] as string], { + expect(keyring.accountDetails[accounts[0] as string]).toStrictEqual({ bip44: true, hdPath: `m/44'/60'/1'/0/0`, }); @@ -356,7 +342,7 @@ describe('LedgerKeyring', function () { keyring.setHdPath(`m/44'/60'/0'`); keyring.setAccountToUnlock(2); const accounts = await keyring.addAccounts(1); - assert.deepEqual(keyring.accountDetails[accounts[0] as string], { + expect(keyring.accountDetails[accounts[0] as string]).toStrictEqual({ bip44: false, hdPath: `m/44'/60'/0'/2`, }); @@ -369,9 +355,9 @@ describe('LedgerKeyring', function () { keyring.setAccountToUnlock(1); const accounts = await keyring.addAccounts(1); - assert.equal(accounts.length, 2); - assert.equal(accounts[0], fakeAccounts[0]); - assert.equal(accounts[1], fakeAccounts[1]); + expect(accounts).toHaveLength(2); + expect(accounts[0]).toBe(fakeAccounts[0]); + expect(accounts[1]).toBe(fakeAccounts[1]); }); }); }); @@ -381,10 +367,10 @@ describe('LedgerKeyring', function () { it('should remove that account', async function () { keyring.setAccountToUnlock(0); const accounts = await keyring.addAccounts(); - assert.equal(accounts.length, 1); + expect(accounts).toHaveLength(1); keyring.removeAccount(fakeAccounts[0]); const accountsAfterRemoval = await keyring.getAccounts(); - assert.equal(accountsAfterRemoval.length, 0); + expect(accountsAfterRemoval).toHaveLength(0); }); }); @@ -393,7 +379,7 @@ describe('LedgerKeyring', function () { const unexistingAccount = '0x0000000000000000000000000000000000000000'; expect(() => { keyring.removeAccount(unexistingAccount); - }).to.throw(`Address ${unexistingAccount} not found in this keyring`); + }).toThrow(`Address ${unexistingAccount} not found in this keyring`); }); }); }); @@ -401,30 +387,30 @@ describe('LedgerKeyring', function () { describe('getFirstPage', function () { it('should set the currentPage to 1', async function () { await keyring.getFirstPage(); - assert.equal(keyring.page, 1); + expect(keyring.page).toBe(1); }); it('should return the list of accounts for current page', async function () { const accounts = await keyring.getFirstPage(); - assert.equal(accounts.length, keyring.perPage); - expect(accounts[0]?.address, fakeAccounts[0]); - expect(accounts[1]?.address, fakeAccounts[1]); - expect(accounts[2]?.address, fakeAccounts[2]); - expect(accounts[3]?.address, fakeAccounts[3]); - expect(accounts[4]?.address, fakeAccounts[4]); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); }); }); describe('getNextPage', function () { it('should return the list of accounts for current page', async function () { const accounts = await keyring.getNextPage(); - assert.equal(accounts.length, keyring.perPage); - expect(accounts[0]?.address, fakeAccounts[0]); - expect(accounts[1]?.address, fakeAccounts[1]); - expect(accounts[2]?.address, fakeAccounts[2]); - expect(accounts[3]?.address, fakeAccounts[3]); - expect(accounts[4]?.address, fakeAccounts[4]); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); }); }); @@ -434,12 +420,12 @@ describe('LedgerKeyring', function () { await keyring.getNextPage(); const accounts = await keyring.getPreviousPage(); - assert.equal(accounts.length, keyring.perPage); - expect(accounts[0]?.address, fakeAccounts[0]); - expect(accounts[1]?.address, fakeAccounts[1]); - expect(accounts[2]?.address, fakeAccounts[2]); - expect(accounts[3]?.address, fakeAccounts[3]); - expect(accounts[4]?.address, fakeAccounts[4]); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); }); it('should be able to go back to the previous page', async function () { @@ -447,12 +433,12 @@ describe('LedgerKeyring', function () { await keyring.getNextPage(); const accounts = await keyring.getPreviousPage(); - assert.equal(accounts.length, keyring.perPage); - expect(accounts[0]?.address, fakeAccounts[0]); - expect(accounts[1]?.address, fakeAccounts[1]); - expect(accounts[2]?.address, fakeAccounts[2]); - expect(accounts[3]?.address, fakeAccounts[3]); - expect(accounts[4]?.address, fakeAccounts[4]); + expect(accounts).toHaveLength(keyring.perPage); + expect(accounts[0]?.address).toBe(fakeAccounts[0]); + expect(accounts[1]?.address).toBe(fakeAccounts[1]); + expect(accounts[2]?.address).toBe(fakeAccounts[2]); + expect(accounts[3]?.address).toBe(fakeAccounts[3]); + expect(accounts[4]?.address).toBe(fakeAccounts[4]); }); }); @@ -466,13 +452,13 @@ describe('LedgerKeyring', function () { }); it('returns an array of accounts', function () { - assert.equal(Array.isArray(accounts), true); - assert.equal(accounts.length, 1); + expect(Array.isArray(accounts)).toBe(true); + expect(accounts).toHaveLength(1); }); it('returns the expected', function () { const expectedAccount = fakeAccounts[accountIndex]; - assert.equal(accounts[0], expectedAccount); + expect(accounts[0]).toStrictEqual(expectedAccount); }); }); @@ -480,7 +466,7 @@ describe('LedgerKeyring', function () { it('should throw an error because it is not supported', function () { expect(() => { keyring.exportAccount(); - }).to.throw('Not supported on this device'); + }).toThrow('Not supported on this device'); }); }); @@ -495,8 +481,8 @@ describe('LedgerKeyring', function () { const accounts = await keyring.getAccounts(); - assert.equal(keyring.isUnlocked(), false); - assert.equal(accounts.length, 0); + expect(keyring.isUnlocked()).toBe(false); + expect(accounts).toHaveLength(0); }); }); @@ -504,26 +490,27 @@ describe('LedgerKeyring', function () { describe('using old versions of ethereumjs/tx', function () { it('should pass serialized transaction to ledger and return signed tx', async function () { await basicSetupToUnlockOneAccount(); - sandbox.on(bridge, 'deviceSignTransaction', (params) => { - assert.deepStrictEqual(params, { - hdPath: "m/44'/60'/0'/0", - tx: fakeTx.serialize().toString('hex'), + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockImplementation((params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + tx: fakeTx.serialize().toString('hex'), + }); + return Promise.resolve({ v: '0x1', r: '0x0', s: '0x0' }); }); - return { v: '0x1', r: '0x0', s: '0x0' }; - }); - sandbox.on(fakeTx, 'verifySignature', () => true); + jest.spyOn(fakeTx, 'verifySignature').mockReturnValue(true); const returnedTx = await keyring.signTransaction( fakeAccounts[0], fakeTx, ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignTransaction).to.have.been.called(); - expect(returnedTx).to.have.property('v'); - expect(returnedTx).to.have.property('r'); - expect(returnedTx).to.have.property('s'); + expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); + expect(returnedTx).toHaveProperty('v'); + expect(returnedTx).toHaveProperty('r'); + expect(returnedTx).toHaveProperty('s'); }); }); @@ -538,32 +525,41 @@ describe('LedgerKeyring', function () { await basicSetupToUnlockOneAccount(); - const signedNewFakeTx = TransactionFactory.fromTxData({ - ...newFakeTx.toJSON(), - ...expectedRSV, - }); + const signedNewFakeTx = TransactionFactory.fromTxData( + { + ...newFakeTx.toJSON(), + ...expectedRSV, + }, + { freeze: false }, + ); - sandbox.on(TransactionFactory, 'fromTxData', () => signedNewFakeTx); - sandbox.on(signedNewFakeTx, 'verifySignature', () => true); - sandbox.on(bridge, 'deviceSignTransaction', (params) => { - assert.deepStrictEqual(params, { - hdPath: "m/44'/60'/0'/0", - tx: ethUtil.rlp - .encode(newFakeTx.getMessageToSign(false)) - .toString('hex'), + jest + .spyOn(TransactionFactory, 'fromTxData') + .mockReturnValue(signedNewFakeTx); + + jest + .spyOn(signedNewFakeTx, 'verifySignature') + .mockImplementation(() => true); + + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockImplementation((params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + tx: ethUtil.rlp + .encode(newFakeTx.getMessageToSign(false)) + .toString('hex'), + }); + return Promise.resolve(expectedRSV); }); - return expectedRSV; - }); const returnedTx = await keyring.signTransaction( fakeAccounts[0], newFakeTx, ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignTransaction).to.have.been.called(); - - expect(returnedTx.toJSON()).to.deep.equal(signedNewFakeTx.toJSON()); + expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); + expect(returnedTx.toJSON()).toStrictEqual(signedNewFakeTx.toJSON()); }); it('should pass correctly encoded EIP1559 transaction to ledger and return signed tx', async function () { @@ -584,27 +580,32 @@ describe('LedgerKeyring', function () { }, { common: commonEIP1559, freeze: false }, ); - sandbox.on(TransactionFactory, 'fromTxData', () => signedFakeTypeTwoTx); - sandbox.on(signedFakeTypeTwoTx, 'verifySignature', () => true); - - sandbox.on(fakeTypeTwoTx, 'verifySignature', () => true); - sandbox.on(bridge, 'deviceSignTransaction', (params) => { - assert.deepStrictEqual(params, { - hdPath: "m/44'/60'/0'/0", - tx: fakeTypeTwoTx.getMessageToSign(false).toString('hex'), + jest + .spyOn(TransactionFactory, 'fromTxData') + .mockReturnValue(signedFakeTypeTwoTx); + jest + .spyOn(signedFakeTypeTwoTx, 'verifySignature') + .mockReturnValue(true); + + jest.spyOn(fakeTypeTwoTx, 'verifySignature').mockReturnValue(true); + jest + .spyOn(keyring.bridge, 'deviceSignTransaction') + .mockImplementation((params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + tx: fakeTypeTwoTx.getMessageToSign(false).toString('hex'), + }); + return Promise.resolve(expectedRSV); }); - return expectedRSV; - }); const returnedTx = await keyring.signTransaction( fakeAccounts[0], fakeTypeTwoTx, ); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignTransaction).to.have.been.called(); + expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); - expect(returnedTx.toJSON()).to.deep.equal(signedFakeTypeTwoTx.toJSON()); + expect(returnedTx.toJSON()).toStrictEqual(signedFakeTypeTwoTx.toJSON()); }); }); }); @@ -612,36 +613,42 @@ describe('LedgerKeyring', function () { describe('signPersonalMessage', function () { it('should call create a listener waiting for the iframe response', async function () { await basicSetupToUnlockOneAccount(); - sandbox.on(bridge, 'deviceSignMessage', (params) => { - assert.deepStrictEqual(params, { - hdPath: "m/44'/60'/0'/0", - message: 'some message', + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockImplementation((params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + message: 'some message', + }); + return Promise.resolve({ v: 1, r: '0x0', s: '0x0' }); }); - return { v: 1, r: '0x0', s: '0x0' }; - }); - sandbox.on(sigUtil, 'recoverPersonalSignature', () => fakeAccounts[0]); + jest + .spyOn(sigUtil, 'recoverPersonalSignature') + .mockReturnValue(fakeAccounts[0]); await keyring.signPersonalMessage(fakeAccounts[0], 'some message'); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignMessage).to.have.been.called(); + expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); }); }); describe('signMessage', function () { it('should call create a listener waiting for the iframe response', async function () { await basicSetupToUnlockOneAccount(); - sandbox.on(bridge, 'deviceSignMessage', (params) => { - assert.deepStrictEqual(params, { - hdPath: "m/44'/60'/0'/0", - message: 'some message', + jest + .spyOn(keyring.bridge, 'deviceSignMessage') + .mockImplementation((params) => { + expect(params).toStrictEqual({ + hdPath: "m/44'/60'/0'/0", + message: 'some message', + }); + return Promise.resolve({ v: 1, r: '0x0', s: '0x0' }); }); - return { v: 1, r: '0x0', s: '0x0' }; - }); - sandbox.on(sigUtil, 'recoverPersonalSignature', () => fakeAccounts[0]); + jest + .spyOn(sigUtil, 'recoverPersonalSignature') + .mockReturnValue(fakeAccounts[0]); await keyring.signMessage(fakeAccounts[0], 'some message'); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(keyring.bridge.deviceSignMessage).to.have.been.called(); + expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); }); }); @@ -649,7 +656,7 @@ describe('LedgerKeyring', function () { it('should unlock the given account if found on device', async function () { await basicSetupToUnlockOneAccount(); await keyring.unlockAccountByAddress(fakeAccounts[0]).then((hdPath) => { - assert.equal(hdPath, "m/44'/60'/0'/0"); + expect(hdPath).toBe("m/44'/60'/0'/0"); }); }); @@ -658,16 +665,12 @@ describe('LedgerKeyring', function () { const incorrectAccount = fakeAccounts[1]; keyring.setAccountToUnlock(0); await keyring.addAccounts(); - sandbox.on(keyring, 'unlock', async (_) => - Promise.resolve(incorrectAccount), - ); + jest.spyOn(keyring, 'unlock').mockResolvedValue(incorrectAccount); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - assert.rejects( + await expect( keyring.unlockAccountByAddress(requestedAccount), - new Error( - `Ledger: Account ${fakeAccounts[0]} does not belong to the connected device`, - ), + ).rejects.toThrow( + `Ledger: Account ${fakeAccounts[0]} does not belong to the connected device`, ); }); }); @@ -727,58 +730,58 @@ describe('LedgerKeyring', function () { const options = { version: 'V4' }; beforeEach(async function () { - sandbox.on(keyring, 'unlockAccountByAddress', async (_) => - Promise.resolve(`m/44'/60'/15'`), - ); + jest + .spyOn(keyring, 'unlockAccountByAddress') + .mockResolvedValue(`m/44'/60'/15'`); await basicSetupToUnlockOneAccount(15); }); it('should resolve properly when called', async function () { - sandbox.on(bridge, 'deviceSignTypedData', (_) => { - return { - v: 27, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - }; - }); + jest + .spyOn(keyring.bridge, 'deviceSignTypedData') + .mockImplementation(() => { + return Promise.resolve({ + v: 27, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + }); + }); const result = await keyring.signTypedData( fakeAccounts[15], fixtureData, options, ); - assert.strictEqual( - result, + expect(result).toBe( '0x72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b946759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e321b', ); }); it('should error when address does not match', async function () { - sandbox.on(bridge, 'deviceSignTypedData', (_) => { - // Changing v to 28 should cause a validation error - return { - v: 28, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - }; - }); + jest + .spyOn(keyring.bridge, 'deviceSignTypedData') + .mockImplementation(() => { + // Changing v to 28 should cause a validation error + return Promise.resolve({ + v: 28, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + }); + }); - // eslint-disable-next-line @typescript-eslint/no-floating-promises - assert.rejects( + await expect( keyring.signTypedData(fakeAccounts[15], fixtureData, options), - new Error('Ledger: The signature doesnt match the right address'), - ); + ).rejects.toThrow('Ledger: The signature doesnt match the right address'); }); }); describe('destroy', function () { it('should remove the message event listener', async function () { - sandbox.on(bridge, 'destroy', async () => Promise.resolve()); + jest.spyOn(keyring.bridge, 'destroy').mockResolvedValue(undefined); await keyring.destroy(); - // eslint-disable-next-line @typescript-eslint/unbound-method - expect(bridge.destroy).to.have.been.called(); + expect(bridge.destroy).toHaveBeenCalled(); }); }); }); diff --git a/yarn.lock b/yarn.lock index 993f4033..ccd97fea 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5,6 +5,16 @@ __metadata: version: 6 cacheKey: 8 +"@ampproject/remapping@npm:^2.2.0": + version: 2.2.1 + resolution: "@ampproject/remapping@npm:2.2.1" + dependencies: + "@jridgewell/gen-mapping": ^0.3.0 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 03c04fd526acc64a1f4df22651186f3e5ef0a9d6d6530ce4482ec9841269cf7a11dbb8af79237c282d721c5312024ff17529cd72cc4768c11e999b58e2302079 + languageName: node + linkType: hard + "@babel/code-frame@npm:^7.0.0, @babel/code-frame@npm:^7.18.6": version: 7.18.6 resolution: "@babel/code-frame@npm:7.18.6" @@ -14,6 +24,45 @@ __metadata: languageName: node linkType: hard +"@babel/code-frame@npm:^7.12.13, @babel/code-frame@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/code-frame@npm:7.21.4" + dependencies: + "@babel/highlight": ^7.18.6 + checksum: e5390e6ec1ac58dcef01d4f18eaf1fd2f1325528661ff6d4a5de8979588b9f5a8e852a54a91b923846f7a5c681b217f0a45c2524eb9560553160cd963b7d592c + languageName: node + linkType: hard + +"@babel/compat-data@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/compat-data@npm:7.21.4" + checksum: 5f8b98c66f2ffba9f3c3a82c0cf354c52a0ec5ad4797b370dc32bdcd6e136ac4febe5e93d76ce76e175632e2dbf6ce9f46319aa689fcfafa41b6e49834fa4b66 + languageName: node + linkType: hard + +"@babel/core@npm:^7.11.6, @babel/core@npm:^7.12.3": + version: 7.21.4 + resolution: "@babel/core@npm:7.21.4" + dependencies: + "@ampproject/remapping": ^2.2.0 + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.4 + "@babel/helper-compilation-targets": ^7.21.4 + "@babel/helper-module-transforms": ^7.21.2 + "@babel/helpers": ^7.21.0 + "@babel/parser": ^7.21.4 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.4 + "@babel/types": ^7.21.4 + convert-source-map: ^1.7.0 + debug: ^4.1.0 + gensync: ^1.0.0-beta.2 + json5: ^2.2.2 + semver: ^6.3.0 + checksum: a3beebb2cc79908a02f27a07dc381bcb34e8ecc58fa99f568ad0934c49e12111fc977ee9c5b51eb7ea2da66f63155d37c4dd96b6472eaeecfc35843ccb56bf3d + languageName: node + linkType: hard + "@babel/generator@npm:^7.21.3": version: 7.21.3 resolution: "@babel/generator@npm:7.21.3" @@ -26,6 +75,33 @@ __metadata: languageName: node linkType: hard +"@babel/generator@npm:^7.21.4, @babel/generator@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/generator@npm:7.21.4" + dependencies: + "@babel/types": ^7.21.4 + "@jridgewell/gen-mapping": ^0.3.2 + "@jridgewell/trace-mapping": ^0.3.17 + jsesc: ^2.5.1 + checksum: 9ffbb526a53bb8469b5402f7b5feac93809b09b2a9f82fcbfcdc5916268a65dae746a1f2479e03ba4fb0776facd7c892191f63baa61ab69b2cfdb24f7b92424d + languageName: node + linkType: hard + +"@babel/helper-compilation-targets@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/helper-compilation-targets@npm:7.21.4" + dependencies: + "@babel/compat-data": ^7.21.4 + "@babel/helper-validator-option": ^7.21.0 + browserslist: ^4.21.3 + lru-cache: ^5.1.1 + semver: ^6.3.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: bf9c7d3e7e6adff9222c05d898724cd4ee91d7eb9d52222c7ad2a22955620c2872cc2d9bdf0e047df8efdb79f4e3af2a06b53f509286145feccc4d10ddc318be + languageName: node + linkType: hard + "@babel/helper-environment-visitor@npm:^7.18.9": version: 7.18.9 resolution: "@babel/helper-environment-visitor@npm:7.18.9" @@ -52,6 +128,47 @@ __metadata: languageName: node linkType: hard +"@babel/helper-module-imports@npm:^7.18.6": + version: 7.21.4 + resolution: "@babel/helper-module-imports@npm:7.21.4" + dependencies: + "@babel/types": ^7.21.4 + checksum: bd330a2edaafeb281fbcd9357652f8d2666502567c0aad71db926e8499c773c9ea9c10dfaae30122452940326d90c8caff5c649ed8e1bf15b23f858758d3abc6 + languageName: node + linkType: hard + +"@babel/helper-module-transforms@npm:^7.21.2": + version: 7.21.2 + resolution: "@babel/helper-module-transforms@npm:7.21.2" + dependencies: + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-module-imports": ^7.18.6 + "@babel/helper-simple-access": ^7.20.2 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/helper-validator-identifier": ^7.19.1 + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.2 + "@babel/types": ^7.21.2 + checksum: 8a1c129a4f90bdf97d8b6e7861732c9580f48f877aaaafbc376ce2482febebcb8daaa1de8bc91676d12886487603f8c62a44f9e90ee76d6cac7f9225b26a49e1 + languageName: node + linkType: hard + +"@babel/helper-plugin-utils@npm:^7.0.0, @babel/helper-plugin-utils@npm:^7.10.4, @babel/helper-plugin-utils@npm:^7.12.13, @babel/helper-plugin-utils@npm:^7.14.5, @babel/helper-plugin-utils@npm:^7.20.2, @babel/helper-plugin-utils@npm:^7.8.0": + version: 7.20.2 + resolution: "@babel/helper-plugin-utils@npm:7.20.2" + checksum: f6cae53b7fdb1bf3abd50fa61b10b4470985b400cc794d92635da1e7077bb19729f626adc0741b69403d9b6e411cddddb9c0157a709cc7c4eeb41e663be5d74b + languageName: node + linkType: hard + +"@babel/helper-simple-access@npm:^7.20.2": + version: 7.20.2 + resolution: "@babel/helper-simple-access@npm:7.20.2" + dependencies: + "@babel/types": ^7.20.2 + checksum: ad1e96ee2e5f654ffee2369a586e5e8d2722bf2d8b028a121b4c33ebae47253f64d420157b9f0a8927aea3a9e0f18c0103e74fdd531815cf3650a0a4adca11a1 + languageName: node + linkType: hard + "@babel/helper-split-export-declaration@npm:^7.18.6": version: 7.18.6 resolution: "@babel/helper-split-export-declaration@npm:7.18.6" @@ -75,6 +192,24 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-option@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helper-validator-option@npm:7.21.0" + checksum: 8ece4c78ffa5461fd8ab6b6e57cc51afad59df08192ed5d84b475af4a7193fc1cb794b59e3e7be64f3cdc4df7ac78bf3dbb20c129d7757ae078e6279ff8c2f07 + languageName: node + linkType: hard + +"@babel/helpers@npm:^7.21.0": + version: 7.21.0 + resolution: "@babel/helpers@npm:7.21.0" + dependencies: + "@babel/template": ^7.20.7 + "@babel/traverse": ^7.21.0 + "@babel/types": ^7.21.0 + checksum: 9370dad2bb665c551869a08ac87c8bdafad53dbcdce1f5c5d498f51811456a3c005d9857562715151a0f00b2e912ac8d89f56574f837b5689f5f5072221cdf54 + languageName: node + linkType: hard + "@babel/highlight@npm:^7.18.6": version: 7.18.6 resolution: "@babel/highlight@npm:7.18.6" @@ -95,6 +230,15 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.1.0, @babel/parser@npm:^7.14.7, @babel/parser@npm:^7.21.4": + version: 7.21.4 + resolution: "@babel/parser@npm:7.21.4" + bin: + parser: ./bin/babel-parser.js + checksum: de610ecd1bff331766d0c058023ca11a4f242bfafefc42caf926becccfb6756637d167c001987ca830dd4b34b93c629a4cef63f8c8c864a8564cdfde1989ac77 + languageName: node + linkType: hard + "@babel/parser@npm:^7.16.4, @babel/parser@npm:^7.20.7, @babel/parser@npm:^7.21.3": version: 7.21.3 resolution: "@babel/parser@npm:7.21.3" @@ -104,7 +248,150 @@ __metadata: languageName: node linkType: hard -"@babel/template@npm:^7.20.7": +"@babel/plugin-syntax-async-generators@npm:^7.8.4": + version: 7.8.4 + resolution: "@babel/plugin-syntax-async-generators@npm:7.8.4" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 7ed1c1d9b9e5b64ef028ea5e755c0be2d4e5e4e3d6cf7df757b9a8c4cfa4193d268176d0f1f7fbecdda6fe722885c7fda681f480f3741d8a2d26854736f05367 + languageName: node + linkType: hard + +"@babel/plugin-syntax-bigint@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-bigint@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 3a10849d83e47aec50f367a9e56a6b22d662ddce643334b087f9828f4c3dd73bdc5909aaeabe123fed78515767f9ca43498a0e621c438d1cd2802d7fae3c9648 + languageName: node + linkType: hard + +"@babel/plugin-syntax-class-properties@npm:^7.8.3": + version: 7.12.13 + resolution: "@babel/plugin-syntax-class-properties@npm:7.12.13" + dependencies: + "@babel/helper-plugin-utils": ^7.12.13 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 24f34b196d6342f28d4bad303612d7ff566ab0a013ce89e775d98d6f832969462e7235f3e7eaf17678a533d4be0ba45d3ae34ab4e5a9dcbda5d98d49e5efa2fc + languageName: node + linkType: hard + +"@babel/plugin-syntax-import-meta@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-import-meta@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 166ac1125d10b9c0c430e4156249a13858c0366d38844883d75d27389621ebe651115cb2ceb6dc011534d5055719fa1727b59f39e1ab3ca97820eef3dcab5b9b + languageName: node + linkType: hard + +"@babel/plugin-syntax-json-strings@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-json-strings@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bf5aea1f3188c9a507e16efe030efb996853ca3cadd6512c51db7233cc58f3ac89ff8c6bdfb01d30843b161cfe7d321e1bf28da82f7ab8d7e6bc5464666f354a + languageName: node + linkType: hard + +"@babel/plugin-syntax-logical-assignment-operators@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-logical-assignment-operators@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: aff33577037e34e515911255cdbb1fd39efee33658aa00b8a5fd3a4b903585112d037cce1cc9e4632f0487dc554486106b79ccd5ea63a2e00df4363f6d4ff886 + languageName: node + linkType: hard + +"@babel/plugin-syntax-nullish-coalescing-operator@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-nullish-coalescing-operator@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 87aca4918916020d1fedba54c0e232de408df2644a425d153be368313fdde40d96088feed6c4e5ab72aac89be5d07fef2ddf329a15109c5eb65df006bf2580d1 + languageName: node + linkType: hard + +"@babel/plugin-syntax-numeric-separator@npm:^7.8.3": + version: 7.10.4 + resolution: "@babel/plugin-syntax-numeric-separator@npm:7.10.4" + dependencies: + "@babel/helper-plugin-utils": ^7.10.4 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 01ec5547bd0497f76cc903ff4d6b02abc8c05f301c88d2622b6d834e33a5651aa7c7a3d80d8d57656a4588f7276eba357f6b7e006482f5b564b7a6488de493a1 + languageName: node + linkType: hard + +"@babel/plugin-syntax-object-rest-spread@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-object-rest-spread@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: fddcf581a57f77e80eb6b981b10658421bc321ba5f0a5b754118c6a92a5448f12a0c336f77b8abf734841e102e5126d69110a306eadb03ca3e1547cab31f5cbf + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-catch-binding@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-catch-binding@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: 910d90e72bc90ea1ce698e89c1027fed8845212d5ab588e35ef91f13b93143845f94e2539d831dc8d8ededc14ec02f04f7bd6a8179edd43a326c784e7ed7f0b9 + languageName: node + linkType: hard + +"@babel/plugin-syntax-optional-chaining@npm:^7.8.3": + version: 7.8.3 + resolution: "@babel/plugin-syntax-optional-chaining@npm:7.8.3" + dependencies: + "@babel/helper-plugin-utils": ^7.8.0 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: eef94d53a1453361553c1f98b68d17782861a04a392840341bc91780838dd4e695209c783631cf0de14c635758beafb6a3a65399846ffa4386bff90639347f30 + languageName: node + linkType: hard + +"@babel/plugin-syntax-top-level-await@npm:^7.8.3": + version: 7.14.5 + resolution: "@babel/plugin-syntax-top-level-await@npm:7.14.5" + dependencies: + "@babel/helper-plugin-utils": ^7.14.5 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: bbd1a56b095be7820029b209677b194db9b1d26691fe999856462e66b25b281f031f3dfd91b1619e9dcf95bebe336211833b854d0fb8780d618e35667c2d0d7e + languageName: node + linkType: hard + +"@babel/plugin-syntax-typescript@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/plugin-syntax-typescript@npm:7.21.4" + dependencies: + "@babel/helper-plugin-utils": ^7.20.2 + peerDependencies: + "@babel/core": ^7.0.0-0 + checksum: a59ce2477b7ae8c8945dc37dda292fef9ce46a6507b3d76b03ce7f3a6c9451a6567438b20a78ebcb3955d04095fd1ccd767075a863f79fcc30aa34dcfa441fe0 + languageName: node + linkType: hard + +"@babel/template@npm:^7.20.7, @babel/template@npm:^7.3.3": version: 7.20.7 resolution: "@babel/template@npm:7.20.7" dependencies: @@ -133,6 +420,35 @@ __metadata: languageName: node linkType: hard +"@babel/traverse@npm:^7.21.0, @babel/traverse@npm:^7.21.2, @babel/traverse@npm:^7.21.4, @babel/traverse@npm:^7.7.2": + version: 7.21.4 + resolution: "@babel/traverse@npm:7.21.4" + dependencies: + "@babel/code-frame": ^7.21.4 + "@babel/generator": ^7.21.4 + "@babel/helper-environment-visitor": ^7.18.9 + "@babel/helper-function-name": ^7.21.0 + "@babel/helper-hoist-variables": ^7.18.6 + "@babel/helper-split-export-declaration": ^7.18.6 + "@babel/parser": ^7.21.4 + "@babel/types": ^7.21.4 + debug: ^4.1.0 + globals: ^11.1.0 + checksum: f22f067c2d9b6497abf3d4e53ea71f3aa82a21f2ed434dd69b8c5767f11f2a4c24c8d2f517d2312c9e5248e5c69395fdca1c95a2b3286122c75f5783ddb6f53c + languageName: node + linkType: hard + +"@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.2, @babel/types@npm:^7.21.2, @babel/types@npm:^7.21.4, @babel/types@npm:^7.3.0, @babel/types@npm:^7.3.3": + version: 7.21.4 + resolution: "@babel/types@npm:7.21.4" + dependencies: + "@babel/helper-string-parser": ^7.19.4 + "@babel/helper-validator-identifier": ^7.19.1 + to-fast-properties: ^2.0.0 + checksum: 587bc55a91ce003b0f8aa10d70070f8006560d7dc0360dc0406d306a2cb2a10154e2f9080b9c37abec76907a90b330a536406cb75e6bdc905484f37b75c73219 + languageName: node + linkType: hard + "@babel/types@npm:^7.18.6, @babel/types@npm:^7.20.7, @babel/types@npm:^7.21.0, @babel/types@npm:^7.21.3, @babel/types@npm:^7.8.3": version: 7.21.3 resolution: "@babel/types@npm:7.21.3" @@ -144,6 +460,13 @@ __metadata: languageName: node linkType: hard +"@bcoe/v8-coverage@npm:^0.2.3": + version: 0.2.3 + resolution: "@bcoe/v8-coverage@npm:0.2.3" + checksum: 850f9305536d0f2bd13e9e0881cb5f02e4f93fad1189f7b2d4bebf694e3206924eadee1068130d43c11b750efcc9405f88a8e42ef098b6d75239c0f047de1a27 + languageName: node + linkType: hard + "@chainsafe/as-sha256@npm:^0.3.1": version: 0.3.1 resolution: "@chainsafe/as-sha256@npm:0.3.1" @@ -586,6 +909,268 @@ __metadata: languageName: node linkType: hard +"@istanbuljs/load-nyc-config@npm:^1.0.0": + version: 1.1.0 + resolution: "@istanbuljs/load-nyc-config@npm:1.1.0" + dependencies: + camelcase: ^5.3.1 + find-up: ^4.1.0 + get-package-type: ^0.1.0 + js-yaml: ^3.13.1 + resolve-from: ^5.0.0 + checksum: d578da5e2e804d5c93228450a1380e1a3c691de4953acc162f387b717258512a3e07b83510a936d9fab03eac90817473917e24f5d16297af3867f59328d58568 + languageName: node + linkType: hard + +"@istanbuljs/schema@npm:^0.1.2": + version: 0.1.3 + resolution: "@istanbuljs/schema@npm:0.1.3" + checksum: 5282759d961d61350f33d9118d16bcaed914ebf8061a52f4fa474b2cb08720c9c81d165e13b82f2e5a8a212cc5af482f0c6fc1ac27b9e067e5394c9a6ed186c9 + languageName: node + linkType: hard + +"@jest/console@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/console@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + "@types/node": "*" + chalk: ^4.0.0 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 + slash: ^3.0.0 + checksum: fe50d98d26d02ce2901c76dff4bd5429a33c13affb692c9ebf8a578ca2f38a5dd854363d40d6c394f215150791fd1f692afd8e730a4178dda24107c8dfd9750a + languageName: node + linkType: hard + +"@jest/core@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/core@npm:28.1.3" + dependencies: + "@jest/console": ^28.1.3 + "@jest/reporters": ^28.1.3 + "@jest/test-result": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + ci-info: ^3.2.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + jest-changed-files: ^28.1.3 + jest-config: ^28.1.3 + jest-haste-map: ^28.1.3 + jest-message-util: ^28.1.3 + jest-regex-util: ^28.0.2 + jest-resolve: ^28.1.3 + jest-resolve-dependencies: ^28.1.3 + jest-runner: ^28.1.3 + jest-runtime: ^28.1.3 + jest-snapshot: ^28.1.3 + jest-util: ^28.1.3 + jest-validate: ^28.1.3 + jest-watcher: ^28.1.3 + micromatch: ^4.0.4 + pretty-format: ^28.1.3 + rimraf: ^3.0.0 + slash: ^3.0.0 + strip-ansi: ^6.0.0 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: cb79f34bafc4637e7130df12257f5b29075892a2be2c7f45c6d4c0420853e80b5dae11016e652530eb234f4c44c00910cdca3c2cd86275721860725073f7d9b4 + languageName: node + linkType: hard + +"@jest/environment@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/environment@npm:28.1.3" + dependencies: + "@jest/fake-timers": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/node": "*" + jest-mock: ^28.1.3 + checksum: 14c496b84aef951df33128cea68988e9de43b2e9d62be9f9c4308d4ac307fa345642813679f80d0a4cedeb900cf6f0b6bb2b92ce089528e8721f72295fdc727f + languageName: node + linkType: hard + +"@jest/expect-utils@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/expect-utils@npm:28.1.3" + dependencies: + jest-get-type: ^28.0.2 + checksum: 808ea3a68292a7e0b95490fdd55605c430b4cf209ea76b5b61bfb2a1badcb41bc046810fe4e364bd5fe04663978aa2bd73d8f8465a761dd7c655aeb44cf22987 + languageName: node + linkType: hard + +"@jest/expect@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/expect@npm:28.1.3" + dependencies: + expect: ^28.1.3 + jest-snapshot: ^28.1.3 + checksum: 4197f6fdddc33dc45ba4e838f992fc61839c421d7aed0dfe665ef9c2f172bb1df8a8cac9cecee272b40e744a326da521d5e182709fe82a0b936055bfffa3b473 + languageName: node + linkType: hard + +"@jest/fake-timers@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/fake-timers@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + "@sinonjs/fake-timers": ^9.1.2 + "@types/node": "*" + jest-message-util: ^28.1.3 + jest-mock: ^28.1.3 + jest-util: ^28.1.3 + checksum: cec14d5b14913a54dce64a62912c5456235f5d90b509ceae19c727565073114dae1aaf960ac6be96b3eb94789a3a758b96b72c8fca7e49a6ccac415fbc0321e1 + languageName: node + linkType: hard + +"@jest/globals@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/globals@npm:28.1.3" + dependencies: + "@jest/environment": ^28.1.3 + "@jest/expect": ^28.1.3 + "@jest/types": ^28.1.3 + checksum: 3504bb23de629d466c6f2b6b75d2e1c1b10caccbbcfb7eaa82d22cc37711c8e364c243929581184846605c023b475ea6c42c2e3ea5994429a988d8d527af32cd + languageName: node + linkType: hard + +"@jest/reporters@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/reporters@npm:28.1.3" + dependencies: + "@bcoe/v8-coverage": ^0.2.3 + "@jest/console": ^28.1.3 + "@jest/test-result": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 + "@jridgewell/trace-mapping": ^0.3.13 + "@types/node": "*" + chalk: ^4.0.0 + collect-v8-coverage: ^1.0.0 + exit: ^0.1.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + istanbul-lib-coverage: ^3.0.0 + istanbul-lib-instrument: ^5.1.0 + istanbul-lib-report: ^3.0.0 + istanbul-lib-source-maps: ^4.0.0 + istanbul-reports: ^3.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 + jest-worker: ^28.1.3 + slash: ^3.0.0 + string-length: ^4.0.1 + strip-ansi: ^6.0.0 + terminal-link: ^2.0.0 + v8-to-istanbul: ^9.0.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + checksum: a7440887ce837922cbeaa64c3232eb48aae02aa9123f29fc4280ad3e1afe4b35dcba171ba1d5fd219037c396c5152d9c2d102cff1798dd5ae3bd33ac4759ae0a + languageName: node + linkType: hard + +"@jest/schemas@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/schemas@npm:28.1.3" + dependencies: + "@sinclair/typebox": ^0.24.1 + checksum: 3cf1d4b66c9c4ffda58b246de1ddcba8e6ad085af63dccdf07922511f13b68c0cc480a7bc620cb4f3099a6f134801c747e1df7bfc7a4ef4dceefbdea3e31e1de + languageName: node + linkType: hard + +"@jest/source-map@npm:^28.1.2": + version: 28.1.2 + resolution: "@jest/source-map@npm:28.1.2" + dependencies: + "@jridgewell/trace-mapping": ^0.3.13 + callsites: ^3.0.0 + graceful-fs: ^4.2.9 + checksum: b82a5c2e93d35d86779c61a02ccb967d1b5cd2e9dd67d26d8add44958637cbbb99daeeb8129c7653389cb440dc2a2f5ae4d2183dc453c67669ff98938b775a3a + languageName: node + linkType: hard + +"@jest/test-result@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/test-result@npm:28.1.3" + dependencies: + "@jest/console": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/istanbul-lib-coverage": ^2.0.0 + collect-v8-coverage: ^1.0.0 + checksum: 957a5dd2fd2e84aabe86698f93c0825e96128ccaa23abf548b159a9b08ac74e4bde7acf4bec48479243dbdb27e4ea1b68c171846d21fb64855c6b55cead9ef27 + languageName: node + linkType: hard + +"@jest/test-sequencer@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/test-sequencer@npm:28.1.3" + dependencies: + "@jest/test-result": ^28.1.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^28.1.3 + slash: ^3.0.0 + checksum: 13f8905e6d1ec8286694146f7be3cf90eff801bbdea5e5c403e6881444bb390ed15494c7b9948aa94bd7e9c9a851e0d3002ed6e7371d048b478596e5b23df953 + languageName: node + linkType: hard + +"@jest/transform@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/transform@npm:28.1.3" + dependencies: + "@babel/core": ^7.11.6 + "@jest/types": ^28.1.3 + "@jridgewell/trace-mapping": ^0.3.13 + babel-plugin-istanbul: ^6.1.1 + chalk: ^4.0.0 + convert-source-map: ^1.4.0 + fast-json-stable-stringify: ^2.0.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^28.1.3 + jest-regex-util: ^28.0.2 + jest-util: ^28.1.3 + micromatch: ^4.0.4 + pirates: ^4.0.4 + slash: ^3.0.0 + write-file-atomic: ^4.0.1 + checksum: dadf618936e0aa84342f07f532801d5bed43cdf95d1417b929e4f8782c872cff1adc84096d5a287a796d0039a2691c06d8450cce5a713a8b52fbb9f872a1e760 + languageName: node + linkType: hard + +"@jest/types@npm:^28.1.3": + version: 28.1.3 + resolution: "@jest/types@npm:28.1.3" + dependencies: + "@jest/schemas": ^28.1.3 + "@types/istanbul-lib-coverage": ^2.0.0 + "@types/istanbul-reports": ^3.0.0 + "@types/node": "*" + "@types/yargs": ^17.0.8 + chalk: ^4.0.0 + checksum: 1e258d9c063fcf59ebc91e46d5ea5984674ac7ae6cae3e50aa780d22b4405bf2c925f40350bf30013839eb5d4b5e521d956ddf8f3b7c78debef0e75a07f57350 + languageName: node + linkType: hard + +"@jridgewell/gen-mapping@npm:^0.3.0": + version: 0.3.3 + resolution: "@jridgewell/gen-mapping@npm:0.3.3" + dependencies: + "@jridgewell/set-array": ^1.0.1 + "@jridgewell/sourcemap-codec": ^1.4.10 + "@jridgewell/trace-mapping": ^0.3.9 + checksum: 4a74944bd31f22354fc01c3da32e83c19e519e3bbadafa114f6da4522ea77dd0c2842607e923a591d60a76699d819a2fbb6f3552e277efdb9b58b081390b60ab + languageName: node + linkType: hard + "@jridgewell/gen-mapping@npm:^0.3.2": version: 0.3.2 resolution: "@jridgewell/gen-mapping@npm:0.3.2" @@ -628,6 +1213,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.12, @jridgewell/trace-mapping@npm:^0.3.13": + version: 0.3.18 + resolution: "@jridgewell/trace-mapping@npm:0.3.18" + dependencies: + "@jridgewell/resolve-uri": 3.1.0 + "@jridgewell/sourcemap-codec": 1.4.14 + checksum: 0572669f855260808c16fe8f78f5f1b4356463b11d3f2c7c0b5580c8ba1cbf4ae53efe9f627595830856e57dbac2325ac17eb0c3dd0ec42102e6f227cc289c02 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:^0.3.17, @jridgewell/trace-mapping@npm:^0.3.9": version: 0.3.17 resolution: "@jridgewell/trace-mapping@npm:0.3.17" @@ -748,14 +1343,14 @@ __metadata: languageName: node linkType: hard -"@metamask/eslint-config-mocha@npm:^11.0.0": +"@metamask/eslint-config-jest@npm:^11.0.0": version: 11.1.0 - resolution: "@metamask/eslint-config-mocha@npm:11.1.0" + resolution: "@metamask/eslint-config-jest@npm:11.1.0" peerDependencies: "@metamask/eslint-config": ^11.0.0 eslint: ^8.27.0 - eslint-plugin-mocha: ^10.1.0 - checksum: 3ed0217b2c9a6351c587fc97a4eda21ad2df8e52bbd21a1c91636570f02fa5e147a122093c576d6a6cf7e06886887daf917ec93a56293f1fdafd9a7b2289f2b5 + eslint-plugin-jest: ^27.1.5 + checksum: a649cf317327532d0d18795ac3bc74766e649e821ca3130dac288b16d5243ae72b5bda4eb85c678f1b4f9c073904ac6162b740c237b67921e64ce4f40c6ceb77 languageName: node linkType: hard @@ -807,7 +1402,7 @@ __metadata: "@ledgerhq/hw-app-eth": ^6.32.0 "@metamask/eslint-config": ^11.0.1 "@metamask/eslint-config-browser": ^11.0.0 - "@metamask/eslint-config-mocha": ^11.0.0 + "@metamask/eslint-config-jest": ^11.0.0 "@metamask/eslint-config-nodejs": ^11.0.0 "@metamask/eslint-config-typescript": ^11.0.0 "@metamask/utils": ^5.0.0 @@ -816,7 +1411,7 @@ __metadata: "@types/eth-sig-util": ^2.1.1 "@types/ethereumjs-tx": ^1.0.1 "@types/hdkey": ^2.0.1 - "@types/mocha": ^5.0.0 + "@types/jest": ^28.1.6 "@types/node": ^14.0.0 "@typescript-eslint/eslint-plugin": ^5.43.0 "@typescript-eslint/parser": ^5.43.0 @@ -826,6 +1421,7 @@ __metadata: eslint: ^8.27.0 eslint-config-prettier: ^8.5.0 eslint-plugin-import: ^2.26.0 + eslint-plugin-jest: ^27.1.5 eslint-plugin-jsdoc: ^39.6.2 eslint-plugin-mocha: ^10.1.0 eslint-plugin-node: ^11.1.0 @@ -834,10 +1430,11 @@ __metadata: ethereumjs-tx: ^1.3.4 ethereumjs-util: ^7.0.9 hdkey: 0.8.0 - mocha: ^5.0.4 + jest: ^28.1.3 prettier: ^2.7.1 prettier-plugin-packagejson: ^2.2.12 rimraf: ^4.1.2 + ts-jest: ^28.0.7 ts-node: ^10.7.0 typedoc: ^0.23.15 typescript: ~4.8.4 @@ -988,6 +1585,31 @@ __metadata: languageName: node linkType: hard +"@sinclair/typebox@npm:^0.24.1": + version: 0.24.51 + resolution: "@sinclair/typebox@npm:0.24.51" + checksum: fd0d855e748ef767eb19da1a60ed0ab928e91e0f358c1dd198d600762c0015440b15755e96d1176e2a0db7e09c6a64ed487828ee10dd0c3e22f61eb09c478cd0 + languageName: node + linkType: hard + +"@sinonjs/commons@npm:^1.7.0": + version: 1.8.6 + resolution: "@sinonjs/commons@npm:1.8.6" + dependencies: + type-detect: 4.0.8 + checksum: 7d3f8c1e85f30cd4e83594fc19b7a657f14d49eb8d95a30095631ce15e906c869e0eff96c5b93dffea7490c00418b07f54582ba49c6560feb2a8c34c0b16832d + languageName: node + linkType: hard + +"@sinonjs/fake-timers@npm:^9.1.2": + version: 9.1.2 + resolution: "@sinonjs/fake-timers@npm:9.1.2" + dependencies: + "@sinonjs/commons": ^1.7.0 + checksum: 7d3aef54e17c1073101cb64d953157c19d62a40e261a30923fa1ee337b049c5f29cc47b1f0c477880f42b5659848ba9ab897607ac8ea4acd5c30ddcfac57fca6 + languageName: node + linkType: hard + "@tootallnate/once@npm:2": version: 2.0.0 resolution: "@tootallnate/once@npm:2.0.0" @@ -1023,6 +1645,47 @@ __metadata: languageName: node linkType: hard +"@types/babel__core@npm:^7.1.14": + version: 7.20.0 + resolution: "@types/babel__core@npm:7.20.0" + dependencies: + "@babel/parser": ^7.20.7 + "@babel/types": ^7.20.7 + "@types/babel__generator": "*" + "@types/babel__template": "*" + "@types/babel__traverse": "*" + checksum: 49b601a0a7637f1f387442c8156bd086cfd10ff4b82b0e1994e73a6396643b5435366fb33d6b604eade8467cca594ef97adcbc412aede90bb112ebe88d0ad6df + languageName: node + linkType: hard + +"@types/babel__generator@npm:*": + version: 7.6.4 + resolution: "@types/babel__generator@npm:7.6.4" + dependencies: + "@babel/types": ^7.0.0 + checksum: 20effbbb5f8a3a0211e95959d06ae70c097fb6191011b73b38fe86deebefad8e09ee014605e0fd3cdaedc73d158be555866810e9166e1f09e4cfd880b874dcb0 + languageName: node + linkType: hard + +"@types/babel__template@npm:*": + version: 7.4.1 + resolution: "@types/babel__template@npm:7.4.1" + dependencies: + "@babel/parser": ^7.1.0 + "@babel/types": ^7.0.0 + checksum: 649fe8b42c2876be1fd28c6ed9b276f78152d5904ec290b6c861d9ef324206e0a5c242e8305c421ac52ecf6358fa7e32ab7a692f55370484825c1df29b1596ee + languageName: node + linkType: hard + +"@types/babel__traverse@npm:*, @types/babel__traverse@npm:^7.0.6": + version: 7.18.5 + resolution: "@types/babel__traverse@npm:7.18.5" + dependencies: + "@babel/types": ^7.3.0 + checksum: b9e7f39eb84626cc8f83ebf75a621d47f04b53cb085a3ea738a9633d57cf65208e503b1830db91aa5e297bc2ba761681ac0b0cbfb7a3d56afcfb2296212668ef + languageName: node + linkType: hard + "@types/bn.js@npm:*, @types/bn.js@npm:^5.1.0": version: 5.1.1 resolution: "@types/bn.js@npm:5.1.1" @@ -1085,6 +1748,15 @@ __metadata: languageName: node linkType: hard +"@types/graceful-fs@npm:^4.1.3": + version: 4.1.6 + resolution: "@types/graceful-fs@npm:4.1.6" + dependencies: + "@types/node": "*" + checksum: c3070ccdc9ca0f40df747bced1c96c71a61992d6f7c767e8fd24bb6a3c2de26e8b84135ede000b7e79db530a23e7e88dcd9db60eee6395d0f4ce1dae91369dd4 + languageName: node + linkType: hard + "@types/hdkey@npm:^2.0.1": version: 2.0.1 resolution: "@types/hdkey@npm:2.0.1" @@ -1094,6 +1766,41 @@ __metadata: languageName: node linkType: hard +"@types/istanbul-lib-coverage@npm:*, @types/istanbul-lib-coverage@npm:^2.0.0, @types/istanbul-lib-coverage@npm:^2.0.1": + version: 2.0.4 + resolution: "@types/istanbul-lib-coverage@npm:2.0.4" + checksum: a25d7589ee65c94d31464c16b72a9dc81dfa0bea9d3e105ae03882d616e2a0712a9c101a599ec482d297c3591e16336962878cb3eb1a0a62d5b76d277a890ce7 + languageName: node + linkType: hard + +"@types/istanbul-lib-report@npm:*": + version: 3.0.0 + resolution: "@types/istanbul-lib-report@npm:3.0.0" + dependencies: + "@types/istanbul-lib-coverage": "*" + checksum: 656398b62dc288e1b5226f8880af98087233cdb90100655c989a09f3052b5775bf98ba58a16c5ae642fb66c61aba402e07a9f2bff1d1569e3b306026c59f3f36 + languageName: node + linkType: hard + +"@types/istanbul-reports@npm:^3.0.0": + version: 3.0.1 + resolution: "@types/istanbul-reports@npm:3.0.1" + dependencies: + "@types/istanbul-lib-report": "*" + checksum: f1ad54bc68f37f60b30c7915886b92f86b847033e597f9b34f2415acdbe5ed742fa559a0a40050d74cdba3b6a63c342cac1f3a64dba5b68b66a6941f4abd7903 + languageName: node + linkType: hard + +"@types/jest@npm:^28.1.6": + version: 28.1.8 + resolution: "@types/jest@npm:28.1.8" + dependencies: + expect: ^28.0.0 + pretty-format: ^28.0.0 + checksum: d4cd36158a3ae1d4b42cc48a77c95de74bc56b84cf81e09af3ee0399c34f4a7da8ab9e787570f10004bd642f9e781b0033c37327fbbf4a8e4b6e37e8ee3693a7 + languageName: node + linkType: hard + "@types/json-schema@npm:^7.0.9": version: 7.0.11 resolution: "@types/json-schema@npm:7.0.11" @@ -1115,13 +1822,6 @@ __metadata: languageName: node linkType: hard -"@types/mocha@npm:^5.0.0": - version: 5.2.7 - resolution: "@types/mocha@npm:5.2.7" - checksum: 446e64292f37f4eac9221161f4c1026dd4a0dbeea0f16c4617227fcf5a1286b53d97e36a32ff7cb3a0435ab78791dd43e9ac1006f92abedb47d7d1d7636cb55c - languageName: node - linkType: hard - "@types/ms@npm:*": version: 0.7.31 resolution: "@types/ms@npm:0.7.31" @@ -1159,6 +1859,13 @@ __metadata: languageName: node linkType: hard +"@types/prettier@npm:^2.1.5": + version: 2.7.2 + resolution: "@types/prettier@npm:2.7.2" + checksum: b47d76a5252265f8d25dd2fe2a5a61dc43ba0e6a96ffdd00c594cb4fd74c1982c2e346497e3472805d97915407a09423804cc2110a0b8e1b22cffcab246479b7 + languageName: node + linkType: hard + "@types/secp256k1@npm:^4.0.1": version: 4.0.3 resolution: "@types/secp256k1@npm:4.0.3" @@ -1175,6 +1882,29 @@ __metadata: languageName: node linkType: hard +"@types/stack-utils@npm:^2.0.0": + version: 2.0.1 + resolution: "@types/stack-utils@npm:2.0.1" + checksum: 205fdbe3326b7046d7eaf5e494d8084f2659086a266f3f9cf00bccc549c8e36e407f88168ad4383c8b07099957ad669f75f2532ed4bc70be2b037330f7bae019 + languageName: node + linkType: hard + +"@types/yargs-parser@npm:*": + version: 21.0.0 + resolution: "@types/yargs-parser@npm:21.0.0" + checksum: b2f4c8d12ac18a567440379909127cf2cec393daffb73f246d0a25df36ea983b93b7e9e824251f959e9f928cbc7c1aab6728d0a0ff15d6145f66cec2be67d9a2 + languageName: node + linkType: hard + +"@types/yargs@npm:^17.0.8": + version: 17.0.24 + resolution: "@types/yargs@npm:17.0.24" + dependencies: + "@types/yargs-parser": "*" + checksum: 5f3ac4dc4f6e211c1627340160fbe2fd247ceba002190da6cf9155af1798450501d628c9165a183f30a224fc68fa5e700490d740ff4c73e2cdef95bc4e8ba7bf + languageName: node + linkType: hard + "@typescript-eslint/eslint-plugin@npm:^5.43.0": version: 5.56.0 resolution: "@typescript-eslint/eslint-plugin@npm:5.56.0" @@ -1226,6 +1956,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/scope-manager@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/scope-manager@npm:5.59.1" + dependencies: + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/visitor-keys": 5.59.1 + checksum: ae7758181d0f18d1ad20abf95164553fa98c20410968d538ac7abd430ec59f69e30d4da16ad968d029feced1ed49abc65daf6685c996eb4529d798e8320204ff + languageName: node + linkType: hard + "@typescript-eslint/type-utils@npm:5.56.0": version: 5.56.0 resolution: "@typescript-eslint/type-utils@npm:5.56.0" @@ -1250,6 +1990,13 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/types@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/types@npm:5.59.1" + checksum: 40ea7ccf59c4951797d3761e53c866a5979e07fbdabef9dc07d3a3f625a99d4318d5329ae8e628cdfdc0bb9bb6e6d8dfb740f33c7bf318e63fa0a863b9ae85c7 + languageName: node + linkType: hard + "@typescript-eslint/typescript-estree@npm:5.56.0": version: 5.56.0 resolution: "@typescript-eslint/typescript-estree@npm:5.56.0" @@ -1268,6 +2015,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/typescript-estree@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/typescript-estree@npm:5.59.1" + dependencies: + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/visitor-keys": 5.59.1 + debug: ^4.3.4 + globby: ^11.1.0 + is-glob: ^4.0.3 + semver: ^7.3.7 + tsutils: ^3.21.0 + peerDependenciesMeta: + typescript: + optional: true + checksum: e33081937225f38e717ac2f9e90c4a8c6b71b701923eea3e03be76d8c466f0d3c6a4ec1d65c9fc1da4f1989416d386305353c5b53aa736d3af9503061001e3eb + languageName: node + linkType: hard + "@typescript-eslint/utils@npm:5.56.0": version: 5.56.0 resolution: "@typescript-eslint/utils@npm:5.56.0" @@ -1286,6 +2051,24 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/utils@npm:^5.10.0": + version: 5.59.1 + resolution: "@typescript-eslint/utils@npm:5.59.1" + dependencies: + "@eslint-community/eslint-utils": ^4.2.0 + "@types/json-schema": ^7.0.9 + "@types/semver": ^7.3.12 + "@typescript-eslint/scope-manager": 5.59.1 + "@typescript-eslint/types": 5.59.1 + "@typescript-eslint/typescript-estree": 5.59.1 + eslint-scope: ^5.1.1 + semver: ^7.3.7 + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + checksum: ca32c90efa57e937ebf812221e070c0604ca99f900fbca60578b42d40c923d5a94fd9503cf5918ecd75b687b68a1be562f7c6593a329bc40b880c95036a021c0 + languageName: node + linkType: hard + "@typescript-eslint/visitor-keys@npm:5.56.0": version: 5.56.0 resolution: "@typescript-eslint/visitor-keys@npm:5.56.0" @@ -1296,6 +2079,16 @@ __metadata: languageName: node linkType: hard +"@typescript-eslint/visitor-keys@npm:5.59.1": + version: 5.59.1 + resolution: "@typescript-eslint/visitor-keys@npm:5.59.1" + dependencies: + "@typescript-eslint/types": 5.59.1 + eslint-visitor-keys: ^3.3.0 + checksum: f98e399147310cad67de718a8a6336f053d46753bade380c89ddac3dd49512555c3f613636b255ce0b5e2b004654d1c167eb5e53fc8085148b637a5afc20cdd8 + languageName: node + linkType: hard + "@vue/compiler-core@npm:3.2.47": version: 3.2.47 resolution: "@vue/compiler-core@npm:3.2.47" @@ -1440,6 +2233,15 @@ __metadata: languageName: node linkType: hard +"ansi-escapes@npm:^4.2.1": + version: 4.3.2 + resolution: "ansi-escapes@npm:4.3.2" + dependencies: + type-fest: ^0.21.3 + checksum: 93111c42189c0a6bed9cdb4d7f2829548e943827ee8479c74d6e0b22ee127b2a21d3f8b5ca57723b8ef78ce011fbfc2784350eb2bde3ccfccf2f575fa8489815 + languageName: node + linkType: hard + "ansi-regex@npm:^2.0.0": version: 2.1.1 resolution: "ansi-regex@npm:2.1.1" @@ -1479,7 +2281,14 @@ __metadata: languageName: node linkType: hard -"anymatch@npm:~3.1.2": +"ansi-styles@npm:^5.0.0": + version: 5.2.0 + resolution: "ansi-styles@npm:5.2.0" + checksum: d7f4e97ce0623aea6bc0d90dcd28881ee04cba06c570b97fd3391bd7a268eedfd9d5e2dd4fdcbdd82b8105df5faf6f24aaedc08eaf3da898e702db5948f63469 + languageName: node + linkType: hard + +"anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" dependencies: @@ -1674,6 +2483,82 @@ __metadata: languageName: node linkType: hard +"babel-jest@npm:^28.1.3": + version: 28.1.3 + resolution: "babel-jest@npm:28.1.3" + dependencies: + "@jest/transform": ^28.1.3 + "@types/babel__core": ^7.1.14 + babel-plugin-istanbul: ^6.1.1 + babel-preset-jest: ^28.1.3 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + slash: ^3.0.0 + peerDependencies: + "@babel/core": ^7.8.0 + checksum: 57ccd2296e1839687b5df2fd138c3d00717e0369e385254b012ccd4ee70e75f5d5c8e6cfcdf92d155015b468cfebb847b38e69bb5805d8aaf730e20575127cc6 + languageName: node + linkType: hard + +"babel-plugin-istanbul@npm:^6.1.1": + version: 6.1.1 + resolution: "babel-plugin-istanbul@npm:6.1.1" + dependencies: + "@babel/helper-plugin-utils": ^7.0.0 + "@istanbuljs/load-nyc-config": ^1.0.0 + "@istanbuljs/schema": ^0.1.2 + istanbul-lib-instrument: ^5.0.4 + test-exclude: ^6.0.0 + checksum: cb4fd95738219f232f0aece1116628cccff16db891713c4ccb501cddbbf9272951a5df81f2f2658dfdf4b3e7b236a9d5cbcf04d5d8c07dd5077297339598061a + languageName: node + linkType: hard + +"babel-plugin-jest-hoist@npm:^28.1.3": + version: 28.1.3 + resolution: "babel-plugin-jest-hoist@npm:28.1.3" + dependencies: + "@babel/template": ^7.3.3 + "@babel/types": ^7.3.3 + "@types/babel__core": ^7.1.14 + "@types/babel__traverse": ^7.0.6 + checksum: 648d89f9d80f6450ce7e50d0c32eb91b7f26269b47c3e37aaf2e0f2f66a980978345bd6b8c9b8c3aa6a8252ad2bc2c9fb50630e9895622c9a0972af5f70ed20e + languageName: node + linkType: hard + +"babel-preset-current-node-syntax@npm:^1.0.0": + version: 1.0.1 + resolution: "babel-preset-current-node-syntax@npm:1.0.1" + dependencies: + "@babel/plugin-syntax-async-generators": ^7.8.4 + "@babel/plugin-syntax-bigint": ^7.8.3 + "@babel/plugin-syntax-class-properties": ^7.8.3 + "@babel/plugin-syntax-import-meta": ^7.8.3 + "@babel/plugin-syntax-json-strings": ^7.8.3 + "@babel/plugin-syntax-logical-assignment-operators": ^7.8.3 + "@babel/plugin-syntax-nullish-coalescing-operator": ^7.8.3 + "@babel/plugin-syntax-numeric-separator": ^7.8.3 + "@babel/plugin-syntax-object-rest-spread": ^7.8.3 + "@babel/plugin-syntax-optional-catch-binding": ^7.8.3 + "@babel/plugin-syntax-optional-chaining": ^7.8.3 + "@babel/plugin-syntax-top-level-await": ^7.8.3 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: d118c2742498c5492c095bc8541f4076b253e705b5f1ad9a2e7d302d81a84866f0070346662355c8e25fc02caa28dc2da8d69bcd67794a0d60c4d6fab6913cc8 + languageName: node + linkType: hard + +"babel-preset-jest@npm:^28.1.3": + version: 28.1.3 + resolution: "babel-preset-jest@npm:28.1.3" + dependencies: + babel-plugin-jest-hoist: ^28.1.3 + babel-preset-current-node-syntax: ^1.0.0 + peerDependencies: + "@babel/core": ^7.0.0 + checksum: 8248a4a5ca4242cc06ad13b10b9183ad2664da8fb0da060c352223dcf286f0ce9c708fa17901dc44ecabec25e6d309e5e5b9830a61dd777c3925f187a345a47d + languageName: node + linkType: hard + "balanced-match@npm:^1.0.0": version: 1.0.2 resolution: "balanced-match@npm:1.0.2" @@ -1806,13 +2691,6 @@ __metadata: languageName: node linkType: hard -"browser-stdout@npm:1.3.1": - version: 1.3.1 - resolution: "browser-stdout@npm:1.3.1" - checksum: b717b19b25952dd6af483e368f9bcd6b14b87740c3d226c2977a65e84666ffd67000bddea7d911f111a9b6ddc822b234de42d52ab6507bce4119a4cc003ef7b3 - languageName: node - linkType: hard - "browserify-aes@npm:^1.0.6, browserify-aes@npm:^1.2.0": version: 1.2.0 resolution: "browserify-aes@npm:1.2.0" @@ -1827,6 +2705,29 @@ __metadata: languageName: node linkType: hard +"browserslist@npm:^4.21.3": + version: 4.21.5 + resolution: "browserslist@npm:4.21.5" + dependencies: + caniuse-lite: ^1.0.30001449 + electron-to-chromium: ^1.4.284 + node-releases: ^2.0.8 + update-browserslist-db: ^1.0.10 + bin: + browserslist: cli.js + checksum: 9755986b22e73a6a1497fd8797aedd88e04270be33ce66ed5d85a1c8a798292a65e222b0f251bafa1c2522261e237d73b08b58689d4920a607e5a53d56dc4706 + languageName: node + linkType: hard + +"bs-logger@npm:0.x": + version: 0.2.6 + resolution: "bs-logger@npm:0.2.6" + dependencies: + fast-json-stable-stringify: 2.x + checksum: d34bdaf68c64bd099ab97c3ea608c9ae7d3f5faa1178b3f3f345acd94e852e608b2d4f9103fb2e503f5e69780e98293df41691b84be909b41cf5045374d54606 + languageName: node + linkType: hard + "bs58@npm:^2.0.1": version: 2.0.1 resolution: "bs58@npm:2.0.1" @@ -1854,6 +2755,22 @@ __metadata: languageName: node linkType: hard +"bser@npm:2.1.1": + version: 2.1.1 + resolution: "bser@npm:2.1.1" + dependencies: + node-int64: ^0.4.0 + checksum: 9ba4dc58ce86300c862bffc3ae91f00b2a03b01ee07f3564beeeaf82aa243b8b03ba53f123b0b842c190d4399b94697970c8e7cf7b1ea44b61aa28c3526a4449 + languageName: node + linkType: hard + +"buffer-from@npm:^1.0.0": + version: 1.1.2 + resolution: "buffer-from@npm:1.1.2" + checksum: 0448524a562b37d4d7ed9efd91685a5b77a50672c556ea254ac9a6d30e3403a517d8981f10e565db24e8339413b43c97ca2951f10e399c6125a0d8911f5679bb + languageName: node + linkType: hard + "buffer-xor@npm:^1.0.3": version: 1.0.3 resolution: "buffer-xor@npm:1.0.3" @@ -1904,6 +2821,13 @@ __metadata: languageName: node linkType: hard +"camelcase@npm:^5.3.1": + version: 5.3.1 + resolution: "camelcase@npm:5.3.1" + checksum: e6effce26b9404e3c0f301498184f243811c30dfe6d0b9051863bd8e4034d09c8c2923794f280d6827e5aa055f6c434115ff97864a16a963366fb35fd673024b + languageName: node + linkType: hard + "camelcase@npm:^6.2.0": version: 6.3.0 resolution: "camelcase@npm:6.3.0" @@ -1911,6 +2835,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001449": + version: 1.0.30001481 + resolution: "caniuse-lite@npm:1.0.30001481" + checksum: 8200a043c191b4fd4fe0beda37a58fd61869c895ab93f87bdd0420e5927453f48434d716ce9da8552ff6c3ecc4dcd1366354cda3a134f3cc844af741574a7cab + languageName: node + linkType: hard + "case@npm:^1.6.3": version: 1.6.3 resolution: "case@npm:1.6.3" @@ -1970,6 +2901,13 @@ __metadata: languageName: node linkType: hard +"char-regex@npm:^1.0.2": + version: 1.0.2 + resolution: "char-regex@npm:1.0.2" + checksum: b563e4b6039b15213114626621e7a3d12f31008bdce20f9c741d69987f62aeaace7ec30f6018890ad77b2e9b4d95324c9f5acfca58a9441e3b1dcdd1e2525d17 + languageName: node + linkType: hard + "check-error@npm:^1.0.2": version: 1.0.2 resolution: "check-error@npm:1.0.2" @@ -2003,6 +2941,13 @@ __metadata: languageName: node linkType: hard +"ci-info@npm:^3.2.0": + version: 3.8.0 + resolution: "ci-info@npm:3.8.0" + checksum: d0a4d3160497cae54294974a7246202244fff031b0a6ea20dd57b10ec510aa17399c41a1b0982142c105f3255aff2173e5c0dd7302ee1b2f28ba3debda375098 + languageName: node + linkType: hard + "cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": version: 1.0.4 resolution: "cipher-base@npm:1.0.4" @@ -2013,6 +2958,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.0.0": + version: 1.2.2 + resolution: "cjs-module-lexer@npm:1.2.2" + checksum: 977f3f042bd4f08e368c890d91eecfbc4f91da0bc009a3c557bc4dfbf32022ad1141244ac1178d44de70fc9f3dea7add7cd9a658a34b9fae98a55d8f92331ce5 + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -2031,6 +2983,17 @@ __metadata: languageName: node linkType: hard +"cliui@npm:^8.0.1": + version: 8.0.1 + resolution: "cliui@npm:8.0.1" + dependencies: + string-width: ^4.2.0 + strip-ansi: ^6.0.1 + wrap-ansi: ^7.0.0 + checksum: 79648b3b0045f2e285b76fb2e24e207c6db44323581e421c3acbd0e86454cba1b37aea976ab50195a49e7384b871e6dfb2247ad7dec53c02454ac6497394cb56 + languageName: node + linkType: hard + "cmd-shim@npm:^6.0.0": version: 6.0.1 resolution: "cmd-shim@npm:6.0.1" @@ -2038,6 +3001,13 @@ __metadata: languageName: node linkType: hard +"co@npm:^4.6.0": + version: 4.6.0 + resolution: "co@npm:4.6.0" + checksum: 5210d9223010eb95b29df06a91116f2cf7c8e0748a9013ed853b53f362ea0e822f1e5bb054fb3cefc645239a4cf966af1f6133a3b43f40d591f3b68ed6cf0510 + languageName: node + linkType: hard + "code-point-at@npm:^1.0.0": version: 1.1.0 resolution: "code-point-at@npm:1.1.0" @@ -2055,6 +3025,13 @@ __metadata: languageName: node linkType: hard +"collect-v8-coverage@npm:^1.0.0": + version: 1.0.1 + resolution: "collect-v8-coverage@npm:1.0.1" + checksum: 4efe0a1fccd517b65478a2364b33dadd0a43fc92a56f59aaece9b6186fe5177b2de471253587de7c91516f07c7268c2f6770b6cbcffc0e0ece353b766ec87e55 + languageName: node + linkType: hard + "color-convert@npm:^1.9.0": version: 1.9.3 resolution: "color-convert@npm:1.9.3" @@ -2105,13 +3082,6 @@ __metadata: languageName: node linkType: hard -"commander@npm:2.15.1": - version: 2.15.1 - resolution: "commander@npm:2.15.1" - checksum: a1b6b66a98cd1862084fcb230d11f56d3af0e5a42c307158d987464d65112fd9f8de2a682895247d2e475f925895e70e5bda21379b1286b21c55dd5c17f0d5fa - languageName: node - linkType: hard - "comment-parser@npm:1.3.1": version: 1.3.1 resolution: "comment-parser@npm:1.3.1" @@ -2133,6 +3103,13 @@ __metadata: languageName: node linkType: hard +"convert-source-map@npm:^1.4.0, convert-source-map@npm:^1.6.0, convert-source-map@npm:^1.7.0": + version: 1.9.0 + resolution: "convert-source-map@npm:1.9.0" + checksum: dc55a1f28ddd0e9485ef13565f8f756b342f9a46c4ae18b843fe3c30c675d058d6a4823eff86d472f187b176f0adf51ea7b69ea38be34be4a63cbbf91b0593c8 + languageName: node + linkType: hard + "core-util-is@npm:1.0.2": version: 1.0.2 resolution: "core-util-is@npm:1.0.2" @@ -2230,15 +3207,6 @@ __metadata: languageName: node linkType: hard -"debug@npm:3.1.0": - version: 3.1.0 - resolution: "debug@npm:3.1.0" - dependencies: - ms: 2.0.0 - checksum: 0b52718ab957254a5b3ca07fc34543bc778f358620c206a08452251eb7fc193c3ea3505072acbf4350219c14e2d71ceb7bdaa0d3370aa630b50da790458d08b3 - languageName: node - linkType: hard - "debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4": version: 4.3.4 resolution: "debug@npm:4.3.4" @@ -2260,6 +3228,13 @@ __metadata: languageName: node linkType: hard +"dedent@npm:^0.7.0": + version: 0.7.0 + resolution: "dedent@npm:0.7.0" + checksum: 87de191050d9a40dd70cad01159a0bcf05ecb59750951242070b6abf9569088684880d00ba92a955b4058804f16eeaf91d604f283929b4f614d181cd7ae633d2 + languageName: node + linkType: hard + "deep-eql@npm:^4.1.2": version: 4.1.3 resolution: "deep-eql@npm:4.1.3" @@ -2276,6 +3251,13 @@ __metadata: languageName: node linkType: hard +"deepmerge@npm:^4.2.2": + version: 4.3.1 + resolution: "deepmerge@npm:4.3.1" + checksum: 2024c6a980a1b7128084170c4cf56b0fd58a63f2da1660dcfe977415f27b17dbe5888668b59d0b063753f3220719d5e400b7f113609489c90160bb9a5518d052 + languageName: node + linkType: hard + "define-lazy-prop@npm:^2.0.0": version: 2.0.0 resolution: "define-lazy-prop@npm:2.0.0" @@ -2361,6 +3343,13 @@ __metadata: languageName: node linkType: hard +"detect-newline@npm:^3.0.0": + version: 3.1.0 + resolution: "detect-newline@npm:3.1.0" + checksum: ae6cd429c41ad01b164c59ea36f264a2c479598e61cba7c99da24175a7ab80ddf066420f2bec9a1c57a6bead411b4655ff15ad7d281c000a89791f48cbe939e7 + languageName: node + linkType: hard + "detect-newline@npm:^4.0.0": version: 4.0.0 resolution: "detect-newline@npm:4.0.0" @@ -2368,10 +3357,10 @@ __metadata: languageName: node linkType: hard -"diff@npm:3.5.0": - version: 3.5.0 - resolution: "diff@npm:3.5.0" - checksum: 00842950a6551e26ce495bdbce11047e31667deea546527902661f25cc2e73358967ebc78cf86b1a9736ec3e14286433225f9970678155753a6291c3bca5227b +"diff-sequences@npm:^28.1.1": + version: 28.1.1 + resolution: "diff-sequences@npm:28.1.1" + checksum: e2529036505567c7ca5a2dea86b6bcd1ca0e3ae63bf8ebf529b8a99cfa915bbf194b7021dc1c57361a4017a6d95578d4ceb29fabc3232a4f4cb866a2726c7690 languageName: node linkType: hard @@ -2430,6 +3419,13 @@ __metadata: languageName: node linkType: hard +"electron-to-chromium@npm:^1.4.284": + version: 1.4.376 + resolution: "electron-to-chromium@npm:1.4.376" + checksum: 881351d25e0e983432c10844540bb664ee4c54f781b81b7247c36d6e617dc85305fd87ffb5de6d9630c6a54f4432afd8e97565a11c62bb77b63051e43cb8a942 + languageName: node + linkType: hard + "elliptic@npm:6.5.4, elliptic@npm:^6.5.2, elliptic@npm:^6.5.4": version: 6.5.4 resolution: "elliptic@npm:6.5.4" @@ -2445,6 +3441,13 @@ __metadata: languageName: node linkType: hard +"emittery@npm:^0.10.2": + version: 0.10.2 + resolution: "emittery@npm:0.10.2" + checksum: ee3e21788b043b90885b18ea756ec3105c1cedc50b29709c92b01e239c7e55345d4bb6d3aef4ddbaf528eef448a40b3bb831bad9ee0fc9c25cbf1367ab1ab5ac + languageName: node + linkType: hard + "emoji-regex@npm:^8.0.0": version: 8.0.0 resolution: "emoji-regex@npm:8.0.0" @@ -2564,13 +3567,20 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:1.0.5, escape-string-regexp@npm:^1.0.5": +"escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" checksum: 6092fda75c63b110c706b6a9bfde8a612ad595b628f0bd2147eea1d3406723020810e591effc7db1da91d80a71a737a313567c5abb3813e8d9c71f4aa595b410 languageName: node linkType: hard +"escape-string-regexp@npm:^2.0.0": + version: 2.0.0 + resolution: "escape-string-regexp@npm:2.0.0" + checksum: 9f8a2d5743677c16e85c810e3024d54f0c8dea6424fad3c79ef6666e81dd0846f7437f5e729dfcdac8981bc9e5294c39b4580814d114076b8d36318f46ae4395 + languageName: node + linkType: hard + "escape-string-regexp@npm:^4.0.0": version: 4.0.0 resolution: "escape-string-regexp@npm:4.0.0" @@ -2644,8 +3654,25 @@ __metadata: semver: ^6.3.0 tsconfig-paths: ^3.14.1 peerDependencies: - eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 - checksum: f500571a380167e25d72a4d925ef9a7aae8899eada57653e5f3051ec3d3c16d08271fcefe41a30a9a2f4fefc232f066253673ee4ea77b30dba65ae173dade85d + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 + checksum: f500571a380167e25d72a4d925ef9a7aae8899eada57653e5f3051ec3d3c16d08271fcefe41a30a9a2f4fefc232f066253673ee4ea77b30dba65ae173dade85d + languageName: node + linkType: hard + +"eslint-plugin-jest@npm:^27.1.5": + version: 27.2.1 + resolution: "eslint-plugin-jest@npm:27.2.1" + dependencies: + "@typescript-eslint/utils": ^5.10.0 + peerDependencies: + "@typescript-eslint/eslint-plugin": ^5.0.0 + eslint: ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + jest: + optional: true + checksum: 579a4d26304cc6748b2e6dff6c965ea7a21b618d8b051eb02727d25cf5c7767f6db8ef5237531635ff77e242b983b973e7cb8c820a4d20d5bda73358c452a8ab languageName: node linkType: hard @@ -3032,6 +4059,43 @@ __metadata: languageName: node linkType: hard +"execa@npm:^5.0.0": + version: 5.1.1 + resolution: "execa@npm:5.1.1" + dependencies: + cross-spawn: ^7.0.3 + get-stream: ^6.0.0 + human-signals: ^2.1.0 + is-stream: ^2.0.0 + merge-stream: ^2.0.0 + npm-run-path: ^4.0.1 + onetime: ^5.1.2 + signal-exit: ^3.0.3 + strip-final-newline: ^2.0.0 + checksum: fba9022c8c8c15ed862847e94c252b3d946036d7547af310e344a527e59021fd8b6bb0723883ea87044dc4f0201f949046993124a42ccb0855cae5bf8c786343 + languageName: node + linkType: hard + +"exit@npm:^0.1.2": + version: 0.1.2 + resolution: "exit@npm:0.1.2" + checksum: abc407f07a875c3961e4781dfcb743b58d6c93de9ab263f4f8c9d23bb6da5f9b7764fc773f86b43dd88030444d5ab8abcb611cb680fba8ca075362b77114bba3 + languageName: node + linkType: hard + +"expect@npm:^28.0.0, expect@npm:^28.1.3": + version: 28.1.3 + resolution: "expect@npm:28.1.3" + dependencies: + "@jest/expect-utils": ^28.1.3 + jest-get-type: ^28.0.2 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 + checksum: 101e0090de300bcafedb7dbfd19223368a2251ce5fe0105bbb6de5720100b89fb6b64290ebfb42febc048324c76d6a4979cdc4b61eb77747857daf7a5de9b03d + languageName: node + linkType: hard + "extend@npm:~3.0.2": version: 3.0.2 resolution: "extend@npm:3.0.2" @@ -3080,7 +4144,7 @@ __metadata: languageName: node linkType: hard -"fast-json-stable-stringify@npm:^2.0.0": +"fast-json-stable-stringify@npm:2.x, fast-json-stable-stringify@npm:^2.0.0": version: 2.1.0 resolution: "fast-json-stable-stringify@npm:2.1.0" checksum: b191531e36c607977e5b1c47811158733c34ccb3bfde92c44798929e9b4154884378536d26ad90dfecd32e1ffc09c545d23535ad91b3161a27ddbb8ebe0cbecb @@ -3103,6 +4167,15 @@ __metadata: languageName: node linkType: hard +"fb-watchman@npm:^2.0.0": + version: 2.0.2 + resolution: "fb-watchman@npm:2.0.2" + dependencies: + bser: 2.1.1 + checksum: b15a124cef28916fe07b400eb87cbc73ca082c142abf7ca8e8de6af43eca79ca7bd13eb4d4d48240b3bd3136eaac40d16e42d6edf87a8e5d1dd8070626860c78 + languageName: node + linkType: hard + "file-entry-cache@npm:^6.0.1": version: 6.0.1 resolution: "file-entry-cache@npm:6.0.1" @@ -3128,6 +4201,16 @@ __metadata: languageName: node linkType: hard +"find-up@npm:^4.0.0, find-up@npm:^4.1.0": + version: 4.1.0 + resolution: "find-up@npm:4.1.0" + dependencies: + locate-path: ^5.0.0 + path-exists: ^4.0.0 + checksum: 4c172680e8f8c1f78839486e14a43ef82e9decd0e74145f40707cc42e7420506d5ec92d9a11c22bd2c48fb0c384ea05dd30e10dd152fefeec6f2f75282a8b844 + languageName: node + linkType: hard + "find-up@npm:^5.0.0": version: 5.0.0 resolution: "find-up@npm:5.0.0" @@ -3208,7 +4291,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:~2.3.2": +"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -3218,7 +4301,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@~2.3.2#~builtin": +"fsevents@patch:fsevents@^2.3.2#~builtin, fsevents@patch:fsevents@~2.3.2#~builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#~builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -3285,6 +4368,13 @@ __metadata: languageName: node linkType: hard +"gensync@npm:^1.0.0-beta.2": + version: 1.0.0-beta.2 + resolution: "gensync@npm:1.0.0-beta.2" + checksum: a7437e58c6be12aa6c90f7730eac7fa9833dc78872b4ad2963d2031b00a3367a93f98aec75f9aaac7220848e4026d67a8655e870b24f20a543d103c0d65952ec + languageName: node + linkType: hard + "get-caller-file@npm:^2.0.5": version: 2.0.5 resolution: "get-caller-file@npm:2.0.5" @@ -3310,6 +4400,20 @@ __metadata: languageName: node linkType: hard +"get-package-type@npm:^0.1.0": + version: 0.1.0 + resolution: "get-package-type@npm:0.1.0" + checksum: bba0811116d11e56d702682ddef7c73ba3481f114590e705fc549f4d868972263896af313c57a25c076e3c0d567e11d919a64ba1b30c879be985fc9d44f96148 + languageName: node + linkType: hard + +"get-stream@npm:^6.0.0": + version: 6.0.1 + resolution: "get-stream@npm:6.0.1" + checksum: e04ecece32c92eebf5b8c940f51468cd53554dcbb0ea725b2748be583c9523d00128137966afce410b9b051eb2ef16d657cd2b120ca8edafcf5a65e81af63cad + languageName: node + linkType: hard + "get-symbol-description@npm:^1.0.0": version: 1.0.0 resolution: "get-symbol-description@npm:1.0.0" @@ -3354,20 +4458,6 @@ __metadata: languageName: node linkType: hard -"glob@npm:7.1.2": - version: 7.1.2 - resolution: "glob@npm:7.1.2" - dependencies: - fs.realpath: ^1.0.0 - inflight: ^1.0.4 - inherits: 2 - minimatch: ^3.0.4 - once: ^1.3.0 - path-is-absolute: ^1.0.0 - checksum: 821460a6cbd4e1f7feff8c24fb3eaecc2014569bd7dfd80c411fe15a5ec6f23cfdb7181574220fb52f8164cb8e9c558b68a36def4aa2a6b971641e838b8b7675 - languageName: node - linkType: hard - "glob@npm:^7.1.3, glob@npm:^7.1.4": version: 7.2.3 resolution: "glob@npm:7.2.3" @@ -3482,7 +4572,7 @@ __metadata: languageName: node linkType: hard -"graceful-fs@npm:^4.2.3, graceful-fs@npm:^4.2.6": +"graceful-fs@npm:^4.2.3, graceful-fs@npm:^4.2.6, graceful-fs@npm:^4.2.9": version: 4.2.11 resolution: "graceful-fs@npm:4.2.11" checksum: ac85f94da92d8eb6b7f5a8b20ce65e43d66761c55ce85ac96df6865308390da45a8d3f0296dd3a663de65d30ba497bd46c696cc1e248c72b13d6d567138a4fc7 @@ -3496,13 +4586,6 @@ __metadata: languageName: node linkType: hard -"growl@npm:1.10.5": - version: 1.10.5 - resolution: "growl@npm:1.10.5" - checksum: 4b86685de6831cebcbb19f93870bea624afee61124b0a20c49017013987cd129e73a8c4baeca295728f41d21265e1f859d25ef36731b142ca59c655fea94bb1a - languageName: node - linkType: hard - "har-schema@npm:^2.0.0": version: 2.0.0 resolution: "har-schema@npm:2.0.0" @@ -3621,15 +4704,6 @@ __metadata: languageName: node linkType: hard -"he@npm:1.1.1": - version: 1.1.1 - resolution: "he@npm:1.1.1" - bin: - he: bin/he - checksum: 714f98d831e912202d67d4e0b456c8b63747220e11d847069d1c3eead7c1e3ed7be28e56fd7ca3425a7ef8e857340801e8f3cec036bf00f8ebe4a2519235112f - languageName: node - linkType: hard - "hmac-drbg@npm:^1.0.1": version: 1.0.1 resolution: "hmac-drbg@npm:1.0.1" @@ -3641,6 +4715,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: d2df2da3ad40ca9ee3a39c5cc6475ef67c8f83c234475f24d8e9ce0dc80a2c82df8e1d6fa78ddd1e9022a586ea1bd247a615e80a5cd9273d90111ddda7d9e974 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.0": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -3680,6 +4761,13 @@ __metadata: languageName: node linkType: hard +"human-signals@npm:^2.1.0": + version: 2.1.0 + resolution: "human-signals@npm:2.1.0" + checksum: b87fd89fce72391625271454e70f67fe405277415b48bcc0117ca73d31fa23a4241787afdc8d67f5a116cf37258c052f59ea82daffa72364d61351423848e3b8 + languageName: node + linkType: hard + "humanize-ms@npm:^1.2.1": version: 1.2.1 resolution: "humanize-ms@npm:1.2.1" @@ -3722,6 +4810,18 @@ __metadata: languageName: node linkType: hard +"import-local@npm:^3.0.2": + version: 3.1.0 + resolution: "import-local@npm:3.1.0" + dependencies: + pkg-dir: ^4.2.0 + resolve-cwd: ^3.0.0 + bin: + import-local-fixture: fixtures/cli.js + checksum: bfcdb63b5e3c0e245e347f3107564035b128a414c4da1172a20dc67db2504e05ede4ac2eee1252359f78b0bfd7b19ef180aec427c2fce6493ae782d73a04cddd + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -3890,6 +4990,13 @@ __metadata: languageName: node linkType: hard +"is-generator-fn@npm:^2.0.0": + version: 2.1.0 + resolution: "is-generator-fn@npm:2.1.0" + checksum: a6ad5492cf9d1746f73b6744e0c43c0020510b59d56ddcb78a91cbc173f09b5e6beff53d75c9c5a29feb618bfef2bf458e025ecf3a57ad2268e2fb2569f56215 + languageName: node + linkType: hard + "is-glob@npm:^4.0.0, is-glob@npm:^4.0.1, is-glob@npm:^4.0.3, is-glob@npm:~4.0.1": version: 4.0.3 resolution: "is-glob@npm:4.0.3" @@ -3969,6 +5076,13 @@ __metadata: languageName: node linkType: hard +"is-stream@npm:^2.0.0": + version: 2.0.1 + resolution: "is-stream@npm:2.0.1" + checksum: b8e05ccdf96ac330ea83c12450304d4a591f9958c11fd17bed240af8d5ffe08aedafa4c0f4cfccd4d28dc9d4d129daca1023633d5c11601a6cbc77521f6fae66 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -4046,6 +5160,497 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": + version: 3.2.0 + resolution: "istanbul-lib-coverage@npm:3.2.0" + checksum: a2a545033b9d56da04a8571ed05c8120bf10e9bce01cf8633a3a2b0d1d83dff4ac4fe78d6d5673c27fc29b7f21a41d75f83a36be09f82a61c367b56aa73c1ff9 + languageName: node + linkType: hard + +"istanbul-lib-instrument@npm:^5.0.4, istanbul-lib-instrument@npm:^5.1.0": + version: 5.2.1 + resolution: "istanbul-lib-instrument@npm:5.2.1" + dependencies: + "@babel/core": ^7.12.3 + "@babel/parser": ^7.14.7 + "@istanbuljs/schema": ^0.1.2 + istanbul-lib-coverage: ^3.2.0 + semver: ^6.3.0 + checksum: bf16f1803ba5e51b28bbd49ed955a736488381e09375d830e42ddeb403855b2006f850711d95ad726f2ba3f1ae8e7366de7e51d2b9ac67dc4d80191ef7ddf272 + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0": + version: 3.0.0 + resolution: "istanbul-lib-report@npm:3.0.0" + dependencies: + istanbul-lib-coverage: ^3.0.0 + make-dir: ^3.0.0 + supports-color: ^7.1.0 + checksum: 3f29eb3f53c59b987386e07fe772d24c7f58c6897f34c9d7a296f4000de7ae3de9eb95c3de3df91dc65b134c84dee35c54eee572a56243e8907c48064e34ff1b + languageName: node + linkType: hard + +"istanbul-lib-source-maps@npm:^4.0.0": + version: 4.0.1 + resolution: "istanbul-lib-source-maps@npm:4.0.1" + dependencies: + debug: ^4.1.1 + istanbul-lib-coverage: ^3.0.0 + source-map: ^0.6.1 + checksum: 21ad3df45db4b81852b662b8d4161f6446cd250c1ddc70ef96a585e2e85c26ed7cd9c2a396a71533cfb981d1a645508bc9618cae431e55d01a0628e7dec62ef2 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.1.3": + version: 3.1.5 + resolution: "istanbul-reports@npm:3.1.5" + dependencies: + html-escaper: ^2.0.0 + istanbul-lib-report: ^3.0.0 + checksum: 7867228f83ed39477b188ea07e7ccb9b4f5320b6f73d1db93a0981b7414fa4ef72d3f80c4692c442f90fc250d9406e71d8d7ab65bb615cb334e6292b73192b89 + languageName: node + linkType: hard + +"jest-changed-files@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-changed-files@npm:28.1.3" + dependencies: + execa: ^5.0.0 + p-limit: ^3.1.0 + checksum: c78af14a68b9b19101623ae7fde15a2488f9b3dbe8cca12a05c4a223bc9bfd3bf41ee06830f20fb560c52434435d6153c9cc6cf450b1f7b03e5e7f96a953a6a6 + languageName: node + linkType: hard + +"jest-circus@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-circus@npm:28.1.3" + dependencies: + "@jest/environment": ^28.1.3 + "@jest/expect": ^28.1.3 + "@jest/test-result": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/node": "*" + chalk: ^4.0.0 + co: ^4.6.0 + dedent: ^0.7.0 + is-generator-fn: ^2.0.0 + jest-each: ^28.1.3 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-runtime: ^28.1.3 + jest-snapshot: ^28.1.3 + jest-util: ^28.1.3 + p-limit: ^3.1.0 + pretty-format: ^28.1.3 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: b635e60a9c92adaefc3f24def8eba691e7c2fdcf6c9fa640cddf2eb8c8b26ee62eab73ebb88798fd7c52a74c1495a984e39b748429b610426f02e9d3d56e09b2 + languageName: node + linkType: hard + +"jest-cli@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-cli@npm:28.1.3" + dependencies: + "@jest/core": ^28.1.3 + "@jest/test-result": ^28.1.3 + "@jest/types": ^28.1.3 + chalk: ^4.0.0 + exit: ^0.1.2 + graceful-fs: ^4.2.9 + import-local: ^3.0.2 + jest-config: ^28.1.3 + jest-util: ^28.1.3 + jest-validate: ^28.1.3 + prompts: ^2.0.1 + yargs: ^17.3.1 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: fb424576bf38346318daddee3fcc597cd78cb8dda1759d09c529d8ba1a748f2765c17b00671072a838826e59465a810ff8a232bc6ba2395c131bf3504425a363 + languageName: node + linkType: hard + +"jest-config@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-config@npm:28.1.3" + dependencies: + "@babel/core": ^7.11.6 + "@jest/test-sequencer": ^28.1.3 + "@jest/types": ^28.1.3 + babel-jest: ^28.1.3 + chalk: ^4.0.0 + ci-info: ^3.2.0 + deepmerge: ^4.2.2 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-circus: ^28.1.3 + jest-environment-node: ^28.1.3 + jest-get-type: ^28.0.2 + jest-regex-util: ^28.0.2 + jest-resolve: ^28.1.3 + jest-runner: ^28.1.3 + jest-util: ^28.1.3 + jest-validate: ^28.1.3 + micromatch: ^4.0.4 + parse-json: ^5.2.0 + pretty-format: ^28.1.3 + slash: ^3.0.0 + strip-json-comments: ^3.1.1 + peerDependencies: + "@types/node": "*" + ts-node: ">=9.0.0" + peerDependenciesMeta: + "@types/node": + optional: true + ts-node: + optional: true + checksum: ddabffd3a3a8cb6c2f58f06cdf3535157dbf8c70bcde3e5c3de7bee6a8d617840ffc8cffb0083e38c6814f2a08c225ca19f58898efaf4f351af94679f22ce6bc + languageName: node + linkType: hard + +"jest-diff@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-diff@npm:28.1.3" + dependencies: + chalk: ^4.0.0 + diff-sequences: ^28.1.1 + jest-get-type: ^28.0.2 + pretty-format: ^28.1.3 + checksum: fa8583e0ccbe775714ce850b009be1b0f6b17a4b6759f33ff47adef27942ebc610dbbcc8a5f7cfb7f12b3b3b05afc9fb41d5f766674616025032ff1e4f9866e0 + languageName: node + linkType: hard + +"jest-docblock@npm:^28.1.1": + version: 28.1.1 + resolution: "jest-docblock@npm:28.1.1" + dependencies: + detect-newline: ^3.0.0 + checksum: 22fca68d988ecb2933bc65f448facdca85fc71b4bd0a188ea09a5ae1b0cc3a049a2a6ec7e7eaa2542c1d5cb5e5145e420a3df4fa280f5070f486c44da1d36151 + languageName: node + linkType: hard + +"jest-each@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-each@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + chalk: ^4.0.0 + jest-get-type: ^28.0.2 + jest-util: ^28.1.3 + pretty-format: ^28.1.3 + checksum: 5c5b8ccb1484e58b027bea682cfa020a45e5bf5379cc7c23bdec972576c1dc3c3bf03df2b78416cefc1a58859dd33b7cf5fff54c370bc3c0f14a3e509eb87282 + languageName: node + linkType: hard + +"jest-environment-node@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-environment-node@npm:28.1.3" + dependencies: + "@jest/environment": ^28.1.3 + "@jest/fake-timers": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/node": "*" + jest-mock: ^28.1.3 + jest-util: ^28.1.3 + checksum: 1048fe306a6a8b0880a4c66278ebb57479f29c12cff89aab3aa79ab77a8859cf17ab8aa9919fd21c329a7db90e35581b43664e694ad453d5b04e00f3c6420469 + languageName: node + linkType: hard + +"jest-get-type@npm:^28.0.2": + version: 28.0.2 + resolution: "jest-get-type@npm:28.0.2" + checksum: 5281d7c89bc8156605f6d15784f45074f4548501195c26e9b188742768f72d40948252d13230ea905b5349038865a1a8eeff0e614cc530ff289dfc41fe843abd + languageName: node + linkType: hard + +"jest-haste-map@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-haste-map@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + "@types/graceful-fs": ^4.1.3 + "@types/node": "*" + anymatch: ^3.0.3 + fb-watchman: ^2.0.0 + fsevents: ^2.3.2 + graceful-fs: ^4.2.9 + jest-regex-util: ^28.0.2 + jest-util: ^28.1.3 + jest-worker: ^28.1.3 + micromatch: ^4.0.4 + walker: ^1.0.8 + dependenciesMeta: + fsevents: + optional: true + checksum: d05fdc108645fc2b39fcd4001952cc7a8cb550e93494e98c1e9ab1fc542686f6ac67177c132e564cf94fe8f81503f3f8db8b825b9b713dc8c5748aec63ba4688 + languageName: node + linkType: hard + +"jest-leak-detector@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-leak-detector@npm:28.1.3" + dependencies: + jest-get-type: ^28.0.2 + pretty-format: ^28.1.3 + checksum: 2e976a4880cf9af11f53a19f6a3820e0f90b635a900737a5427fc42e337d5628ba446dcd7c020ecea3806cf92bc0bbf6982ed62a9cd84e5a13d8751aa30fbbb7 + languageName: node + linkType: hard + +"jest-matcher-utils@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-matcher-utils@npm:28.1.3" + dependencies: + chalk: ^4.0.0 + jest-diff: ^28.1.3 + jest-get-type: ^28.0.2 + pretty-format: ^28.1.3 + checksum: 6b34f0cf66f6781e92e3bec97bf27796bd2ba31121e5c5997218d9adba6deea38a30df5203937d6785b68023ed95cbad73663cc9aad6fb0cb59aeb5813a58daf + languageName: node + linkType: hard + +"jest-message-util@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-message-util@npm:28.1.3" + dependencies: + "@babel/code-frame": ^7.12.13 + "@jest/types": ^28.1.3 + "@types/stack-utils": ^2.0.0 + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + micromatch: ^4.0.4 + pretty-format: ^28.1.3 + slash: ^3.0.0 + stack-utils: ^2.0.3 + checksum: 1f266854166dcc6900d75a88b54a25225a2f3710d463063ff1c99021569045c35c7d58557b25447a17eb3a65ce763b2f9b25550248b468a9d4657db365f39e96 + languageName: node + linkType: hard + +"jest-mock@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-mock@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + "@types/node": "*" + checksum: a573bf8e5f12f4c29c661266c31b5c6b69a28d3195b83049983bce025b2b1a0152351567e89e63b102ef817034c2a3aa97eda4e776f3bae2aee54c5765573aa7 + languageName: node + linkType: hard + +"jest-pnp-resolver@npm:^1.2.2": + version: 1.2.3 + resolution: "jest-pnp-resolver@npm:1.2.3" + peerDependencies: + jest-resolve: "*" + peerDependenciesMeta: + jest-resolve: + optional: true + checksum: db1a8ab2cb97ca19c01b1cfa9a9c8c69a143fde833c14df1fab0766f411b1148ff0df878adea09007ac6a2085ec116ba9a996a6ad104b1e58c20adbf88eed9b2 + languageName: node + linkType: hard + +"jest-regex-util@npm:^28.0.2": + version: 28.0.2 + resolution: "jest-regex-util@npm:28.0.2" + checksum: 0ea8c5c82ec88bc85e273c0ec82e0c0f35f7a1e2d055070e50f0cc2a2177f848eec55f73e37ae0d045c3db5014c42b2f90ac62c1ab3fdb354d2abd66a9e08add + languageName: node + linkType: hard + +"jest-resolve-dependencies@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-resolve-dependencies@npm:28.1.3" + dependencies: + jest-regex-util: ^28.0.2 + jest-snapshot: ^28.1.3 + checksum: 4eea9ec33aefc1c71dc5956391efbcc7be76bda986b366ab3931d99c5f7ed01c9ebd7520e405ea2c76e1bb2c7ce504be6eca2b9831df16564d1e625500f3bfe7 + languageName: node + linkType: hard + +"jest-resolve@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-resolve@npm:28.1.3" + dependencies: + chalk: ^4.0.0 + graceful-fs: ^4.2.9 + jest-haste-map: ^28.1.3 + jest-pnp-resolver: ^1.2.2 + jest-util: ^28.1.3 + jest-validate: ^28.1.3 + resolve: ^1.20.0 + resolve.exports: ^1.1.0 + slash: ^3.0.0 + checksum: df61a490c93f4f4cf52135e43d6a4fcacb07b0b7d4acc6319e9289529c1d14f2d8e1638e095dbf96f156834802755e38db68caca69dba21a3261ee711d4426b6 + languageName: node + linkType: hard + +"jest-runner@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-runner@npm:28.1.3" + dependencies: + "@jest/console": ^28.1.3 + "@jest/environment": ^28.1.3 + "@jest/test-result": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/node": "*" + chalk: ^4.0.0 + emittery: ^0.10.2 + graceful-fs: ^4.2.9 + jest-docblock: ^28.1.1 + jest-environment-node: ^28.1.3 + jest-haste-map: ^28.1.3 + jest-leak-detector: ^28.1.3 + jest-message-util: ^28.1.3 + jest-resolve: ^28.1.3 + jest-runtime: ^28.1.3 + jest-util: ^28.1.3 + jest-watcher: ^28.1.3 + jest-worker: ^28.1.3 + p-limit: ^3.1.0 + source-map-support: 0.5.13 + checksum: 32405cd970fa6b11e039192dae699fd1bcc6f61f67d50605af81d193f24dd4373b25f5fcc1c571a028ec1b02174e8a4b6d0d608772063fb06f08a5105693533b + languageName: node + linkType: hard + +"jest-runtime@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-runtime@npm:28.1.3" + dependencies: + "@jest/environment": ^28.1.3 + "@jest/fake-timers": ^28.1.3 + "@jest/globals": ^28.1.3 + "@jest/source-map": ^28.1.2 + "@jest/test-result": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 + chalk: ^4.0.0 + cjs-module-lexer: ^1.0.0 + collect-v8-coverage: ^1.0.0 + execa: ^5.0.0 + glob: ^7.1.3 + graceful-fs: ^4.2.9 + jest-haste-map: ^28.1.3 + jest-message-util: ^28.1.3 + jest-mock: ^28.1.3 + jest-regex-util: ^28.0.2 + jest-resolve: ^28.1.3 + jest-snapshot: ^28.1.3 + jest-util: ^28.1.3 + slash: ^3.0.0 + strip-bom: ^4.0.0 + checksum: b17c40af858e74dafa4f515ef3711c1e9ef3d4ad7d74534ee0745422534bc04fd166d4eceb62a3aa7dc951505d6f6d2a81d16e90bebb032be409ec0500974a36 + languageName: node + linkType: hard + +"jest-snapshot@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-snapshot@npm:28.1.3" + dependencies: + "@babel/core": ^7.11.6 + "@babel/generator": ^7.7.2 + "@babel/plugin-syntax-typescript": ^7.7.2 + "@babel/traverse": ^7.7.2 + "@babel/types": ^7.3.3 + "@jest/expect-utils": ^28.1.3 + "@jest/transform": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/babel__traverse": ^7.0.6 + "@types/prettier": ^2.1.5 + babel-preset-current-node-syntax: ^1.0.0 + chalk: ^4.0.0 + expect: ^28.1.3 + graceful-fs: ^4.2.9 + jest-diff: ^28.1.3 + jest-get-type: ^28.0.2 + jest-haste-map: ^28.1.3 + jest-matcher-utils: ^28.1.3 + jest-message-util: ^28.1.3 + jest-util: ^28.1.3 + natural-compare: ^1.4.0 + pretty-format: ^28.1.3 + semver: ^7.3.5 + checksum: 2a46a5493f1fb50b0a236a21f25045e7f46a244f9f3ae37ef4fbcd40249d0d68bb20c950ce77439e4e2cac985b05c3061c90b34739bf6069913a1199c8c716e1 + languageName: node + linkType: hard + +"jest-util@npm:^28.0.0, jest-util@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-util@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + "@types/node": "*" + chalk: ^4.0.0 + ci-info: ^3.2.0 + graceful-fs: ^4.2.9 + picomatch: ^2.2.3 + checksum: fd6459742c941f070223f25e38a2ac0719aad92561591e9fb2a50d602a5d19d754750b79b4074327a42b00055662b95da3b006542ceb8b54309da44d4a62e721 + languageName: node + linkType: hard + +"jest-validate@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-validate@npm:28.1.3" + dependencies: + "@jest/types": ^28.1.3 + camelcase: ^6.2.0 + chalk: ^4.0.0 + jest-get-type: ^28.0.2 + leven: ^3.1.0 + pretty-format: ^28.1.3 + checksum: 95e0513b3803c3372a145cda86edbdb33d9dfeaa18818176f2d581e821548ceac9a179f065b6d4671a941de211354efd67f1fff8789a4fb89962565c85f646db + languageName: node + linkType: hard + +"jest-watcher@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-watcher@npm:28.1.3" + dependencies: + "@jest/test-result": ^28.1.3 + "@jest/types": ^28.1.3 + "@types/node": "*" + ansi-escapes: ^4.2.1 + chalk: ^4.0.0 + emittery: ^0.10.2 + jest-util: ^28.1.3 + string-length: ^4.0.1 + checksum: 8f6d674a4865e7df251f71544f1b51f06fd36b5a3a61f2ac81aeb81fa2a196be354fba51d0f97911c88f67cd254583b3a22ee124bf2c5b6ee2fadec27356c207 + languageName: node + linkType: hard + +"jest-worker@npm:^28.1.3": + version: 28.1.3 + resolution: "jest-worker@npm:28.1.3" + dependencies: + "@types/node": "*" + merge-stream: ^2.0.0 + supports-color: ^8.0.0 + checksum: e921c9a1b8f0909da9ea07dbf3592f95b653aef3a8bb0cbcd20fc7f9a795a1304adecac31eecb308992c167e8d7e75c522061fec38a5928ace0f9571c90169ca + languageName: node + linkType: hard + +"jest@npm:^28.1.3": + version: 28.1.3 + resolution: "jest@npm:28.1.3" + dependencies: + "@jest/core": ^28.1.3 + "@jest/types": ^28.1.3 + import-local: ^3.0.2 + jest-cli: ^28.1.3 + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + bin: + jest: bin/jest.js + checksum: b9dcb542eb7c16261c281cdc2bf37155dbb3f1205bae0b567f05051db362c85ddd4b765f126591efb88f6d298eb10336d0aa6c7d5373b4d53f918137a9a70182 + languageName: node + linkType: hard + "js-sdsl@npm:^4.1.4": version: 4.3.0 resolution: "js-sdsl@npm:4.3.0" @@ -4067,7 +5672,7 @@ __metadata: languageName: node linkType: hard -"js-yaml@npm:^3.14.0": +"js-yaml@npm:^3.13.1, js-yaml@npm:^3.14.0": version: 3.14.1 resolution: "js-yaml@npm:3.14.1" dependencies: @@ -4159,7 +5764,7 @@ __metadata: languageName: node linkType: hard -"json5@npm:^2.1.3": +"json5@npm:^2.1.3, json5@npm:^2.2.1, json5@npm:^2.2.2": version: 2.2.3 resolution: "json5@npm:2.2.3" bin: @@ -4199,6 +5804,20 @@ __metadata: languageName: node linkType: hard +"kleur@npm:^3.0.3": + version: 3.0.3 + resolution: "kleur@npm:3.0.3" + checksum: df82cd1e172f957bae9c536286265a5cdbd5eeca487cb0a3b2a7b41ef959fc61f8e7c0e9aeea9c114ccf2c166b6a8dd45a46fd619c1c569d210ecd2765ad5169 + languageName: node + linkType: hard + +"leven@npm:^3.1.0": + version: 3.1.0 + resolution: "leven@npm:3.1.0" + checksum: 638401d534585261b6003db9d99afd244dfe82d75ddb6db5c0df412842d5ab30b2ef18de471aaec70fe69a46f17b4ae3c7f01d8a4e6580ef7adb9f4273ad1e55 + languageName: node + linkType: hard + "levn@npm:^0.4.1": version: 0.4.1 resolution: "levn@npm:0.4.1" @@ -4216,6 +5835,15 @@ __metadata: languageName: node linkType: hard +"locate-path@npm:^5.0.0": + version: 5.0.0 + resolution: "locate-path@npm:5.0.0" + dependencies: + p-locate: ^4.1.0 + checksum: 83e51725e67517287d73e1ded92b28602e3ae5580b301fe54bfb76c0c723e3f285b19252e375712316774cf52006cb236aed5704692c32db0d5d089b69696e30 + languageName: node + linkType: hard + "locate-path@npm:^6.0.0": version: 6.0.0 resolution: "locate-path@npm:6.0.0" @@ -4225,6 +5853,13 @@ __metadata: languageName: node linkType: hard +"lodash.memoize@npm:4.x": + version: 4.1.2 + resolution: "lodash.memoize@npm:4.1.2" + checksum: 9ff3942feeccffa4f1fafa88d32f0d24fdc62fd15ded5a74a5f950ff5f0c6f61916157246744c620173dddf38d37095a92327d5fd3861e2063e736a5c207d089 + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -4259,6 +5894,15 @@ __metadata: languageName: node linkType: hard +"lru-cache@npm:^5.1.1": + version: 5.1.1 + resolution: "lru-cache@npm:5.1.1" + dependencies: + yallist: ^3.0.2 + checksum: c154ae1cbb0c2206d1501a0e94df349653c92c8cbb25236d7e85190bcaf4567a03ac6eb43166fabfa36fd35623694da7233e88d9601fbf411a9a481d85dbd2cb + languageName: node + linkType: hard + "lru-cache@npm:^6.0.0": version: 6.0.0 resolution: "lru-cache@npm:6.0.0" @@ -4291,7 +5935,16 @@ __metadata: languageName: node linkType: hard -"make-error@npm:^1.1.1": +"make-dir@npm:^3.0.0": + version: 3.1.0 + resolution: "make-dir@npm:3.1.0" + dependencies: + semver: ^6.0.0 + checksum: 484200020ab5a1fdf12f393fe5f385fc8e4378824c940fba1729dcd198ae4ff24867bc7a5646331e50cead8abff5d9270c456314386e629acec6dff4b8016b78 + languageName: node + linkType: hard + +"make-error@npm:1.x, make-error@npm:^1.1.1": version: 1.3.6 resolution: "make-error@npm:1.3.6" checksum: b86e5e0e25f7f777b77fabd8e2cbf15737972869d852a22b7e73c17623928fccb826d8e46b9951501d3f20e51ad74ba8c59ed584f610526a48f8ccf88aaec402 @@ -4322,6 +5975,15 @@ __metadata: languageName: node linkType: hard +"makeerror@npm:1.0.12": + version: 1.0.12 + resolution: "makeerror@npm:1.0.12" + dependencies: + tmpl: 1.0.5 + checksum: b38a025a12c8146d6eeea5a7f2bf27d51d8ad6064da8ca9405fcf7bf9b54acd43e3b30ddd7abb9b1bfa4ddb266019133313482570ddb207de568f71ecfcf6060 + languageName: node + linkType: hard + "marked@npm:^4.2.12": version: 4.2.12 resolution: "marked@npm:4.2.12" @@ -4342,6 +6004,13 @@ __metadata: languageName: node linkType: hard +"merge-stream@npm:^2.0.0": + version: 2.0.0 + resolution: "merge-stream@npm:2.0.0" + checksum: 6fa4dcc8d86629705cea944a4b88ef4cb0e07656ebf223fa287443256414283dd25d91c1cd84c77987f2aec5927af1a9db6085757cb43d90eb170ebf4b47f4f4 + languageName: node + linkType: hard + "merge2@npm:^1.3.0, merge2@npm:^1.4.1": version: 1.4.1 resolution: "merge2@npm:1.4.1" @@ -4375,6 +6044,13 @@ __metadata: languageName: node linkType: hard +"mimic-fn@npm:^2.1.0": + version: 2.1.0 + resolution: "mimic-fn@npm:2.1.0" + checksum: d2421a3444848ce7f84bd49115ddacff29c15745db73f54041edc906c14b131a38d05298dae3081667627a59b2eb1ca4b436ff2e1b80f69679522410418b478a + languageName: node + linkType: hard + "minimalistic-assert@npm:^1.0.0, minimalistic-assert@npm:^1.0.1": version: 1.0.1 resolution: "minimalistic-assert@npm:1.0.1" @@ -4389,15 +6065,6 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:3.0.4": - version: 3.0.4 - resolution: "minimatch@npm:3.0.4" - dependencies: - brace-expansion: ^1.1.7 - checksum: 66ac295f8a7b59788000ea3749938b0970344c841750abd96694f80269b926ebcafad3deeb3f1da2522978b119e6ae3a5869b63b13a7859a456b3408bd18a078 - languageName: node - linkType: hard - "minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" @@ -4425,13 +6092,6 @@ __metadata: languageName: node linkType: hard -"minimist@npm:0.0.8": - version: 0.0.8 - resolution: "minimist@npm:0.0.8" - checksum: 042f8b626b1fa44dffc23bac55771425ac4ee9d267b56f9064c07713e516e1799f3ba933bb628d2475a210caf7dcdb98161611baa1f0daf49309a944cb4bc48f - languageName: node - linkType: hard - "minimist@npm:^1.2.0, minimist@npm:^1.2.6": version: 1.2.8 resolution: "minimist@npm:1.2.8" @@ -4516,17 +6176,6 @@ __metadata: languageName: node linkType: hard -"mkdirp@npm:0.5.1": - version: 0.5.1 - resolution: "mkdirp@npm:0.5.1" - dependencies: - minimist: 0.0.8 - bin: - mkdirp: bin/cmd.js - checksum: ed1ab49bb1d06c88dba7cfe930a3186f2605b5465aab7c8f24119baaba6e38f9ab4ac1695c68f476c65a48df2a69a8495049cd6e26c360ea082151a0771343d2 - languageName: node - linkType: hard - "mkdirp@npm:^1.0.3, mkdirp@npm:^1.0.4": version: 1.0.4 resolution: "mkdirp@npm:1.0.4" @@ -4536,35 +6185,6 @@ __metadata: languageName: node linkType: hard -"mocha@npm:^5.0.4": - version: 5.2.0 - resolution: "mocha@npm:5.2.0" - dependencies: - browser-stdout: 1.3.1 - commander: 2.15.1 - debug: 3.1.0 - diff: 3.5.0 - escape-string-regexp: 1.0.5 - glob: 7.1.2 - growl: 1.10.5 - he: 1.1.1 - minimatch: 3.0.4 - mkdirp: 0.5.1 - supports-color: 5.4.0 - bin: - _mocha: ./bin/_mocha - mocha: ./bin/mocha - checksum: 08d37a9fa0e67141d8e062356a6915402788fb4d7b1ff9cb7311efa140aa3f255c98f6fa64697981d721d3a41f4eb4d9a28fc986f84499456f1978c0ea2d4109 - languageName: node - linkType: hard - -"ms@npm:2.0.0": - version: 2.0.0 - resolution: "ms@npm:2.0.0" - checksum: 0e6a22b8b746d2e0b65a430519934fefd41b6db0682e3477c10f60c76e947c4c0ad06f63ffdf1d78d335f83edee8c0aa928aa66a36c7cd95b69b26f468d527f4 - languageName: node - linkType: hard - "ms@npm:2.1.2": version: 2.1.2 resolution: "ms@npm:2.1.2" @@ -4691,6 +6311,20 @@ __metadata: languageName: node linkType: hard +"node-int64@npm:^0.4.0": + version: 0.4.0 + resolution: "node-int64@npm:0.4.0" + checksum: d0b30b1ee6d961851c60d5eaa745d30b5c95d94bc0e74b81e5292f7c42a49e3af87f1eb9e89f59456f80645d679202537de751b7d72e9e40ceea40c5e449057e + languageName: node + linkType: hard + +"node-releases@npm:^2.0.8": + version: 2.0.10 + resolution: "node-releases@npm:2.0.10" + checksum: d784ecde25696a15d449c4433077f5cce620ed30a1656c4abf31282bfc691a70d9618bae6868d247a67914d1be5cc4fde22f65a05f4398cdfb92e0fc83cadfbc + languageName: node + linkType: hard + "nopt@npm:^5.0.0": version: 5.0.0 resolution: "nopt@npm:5.0.0" @@ -4734,6 +6368,15 @@ __metadata: languageName: node linkType: hard +"npm-run-path@npm:^4.0.1": + version: 4.0.1 + resolution: "npm-run-path@npm:4.0.1" + dependencies: + path-key: ^3.0.0 + checksum: 5374c0cea4b0bbfdfae62da7bbdf1e1558d338335f4cacf2515c282ff358ff27b2ecb91ffa5330a8b14390ac66a1e146e10700440c1ab868208430f56b5f4d23 + languageName: node + linkType: hard + "npmlog@npm:^4.1.2": version: 4.1.2 resolution: "npmlog@npm:4.1.2" @@ -4825,6 +6468,15 @@ __metadata: languageName: node linkType: hard +"onetime@npm:^5.1.2": + version: 5.1.2 + resolution: "onetime@npm:5.1.2" + dependencies: + mimic-fn: ^2.1.0 + checksum: 2478859ef817fc5d4e9c2f9e5728512ddd1dbc9fb7829ad263765bb6d3b91ce699d6e2332eef6b7dff183c2f490bd3349f1666427eaba4469fba0ac38dfd0d34 + languageName: node + linkType: hard + "open@npm:^8.4.0": version: 8.4.2 resolution: "open@npm:8.4.2" @@ -4850,7 +6502,16 @@ __metadata: languageName: node linkType: hard -"p-limit@npm:^3.0.2": +"p-limit@npm:^2.2.0": + version: 2.3.0 + resolution: "p-limit@npm:2.3.0" + dependencies: + p-try: ^2.0.0 + checksum: 84ff17f1a38126c3314e91ecfe56aecbf36430940e2873dadaa773ffe072dc23b7af8e46d4b6485d302a11673fe94c6b67ca2cfbb60c989848b02100d0594ac1 + languageName: node + linkType: hard + +"p-limit@npm:^3.0.2, p-limit@npm:^3.1.0": version: 3.1.0 resolution: "p-limit@npm:3.1.0" dependencies: @@ -4859,6 +6520,15 @@ __metadata: languageName: node linkType: hard +"p-locate@npm:^4.1.0": + version: 4.1.0 + resolution: "p-locate@npm:4.1.0" + dependencies: + p-limit: ^2.2.0 + checksum: 513bd14a455f5da4ebfcb819ef706c54adb09097703de6aeaa5d26fe5ea16df92b48d1ac45e01e3944ce1e6aa2a66f7f8894742b8c9d6e276e16cd2049a2b870 + languageName: node + linkType: hard + "p-locate@npm:^5.0.0": version: 5.0.0 resolution: "p-locate@npm:5.0.0" @@ -4877,6 +6547,13 @@ __metadata: languageName: node linkType: hard +"p-try@npm:^2.0.0": + version: 2.2.0 + resolution: "p-try@npm:2.2.0" + checksum: f8a8e9a7693659383f06aec604ad5ead237c7a261c18048a6e1b5b85a5f8a067e469aa24f5bc009b991ea3b058a87f5065ef4176793a200d4917349881216cae + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -4886,7 +6563,7 @@ __metadata: languageName: node linkType: hard -"parse-json@npm:^5.0.0": +"parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" dependencies: @@ -4912,7 +6589,7 @@ __metadata: languageName: node linkType: hard -"path-key@npm:^3.1.0": +"path-key@npm:^3.0.0, path-key@npm:^3.1.0": version: 3.1.1 resolution: "path-key@npm:3.1.1" checksum: 55cd7a9dd4b343412a8386a743f9c746ef196e57c823d90ca3ab917f90ab9f13dd0ded27252ba49dbdfcab2b091d998bc446f6220cd3cea65db407502a740020 @@ -4977,13 +6654,29 @@ __metadata: languageName: node linkType: hard -"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.3.1": +"picomatch@npm:^2.0.4, picomatch@npm:^2.2.1, picomatch@npm:^2.2.3, picomatch@npm:^2.3.1": version: 2.3.1 resolution: "picomatch@npm:2.3.1" checksum: 050c865ce81119c4822c45d3c84f1ced46f93a0126febae20737bd05ca20589c564d6e9226977df859ed5e03dc73f02584a2b0faad36e896936238238b0446cf languageName: node linkType: hard +"pirates@npm:^4.0.4": + version: 4.0.5 + resolution: "pirates@npm:4.0.5" + checksum: c9994e61b85260bec6c4fc0307016340d9b0c4f4b6550a957afaaff0c9b1ad58fbbea5cfcf083860a25cb27a375442e2b0edf52e2e1e40e69934e08dcc52d227 + languageName: node + linkType: hard + +"pkg-dir@npm:^4.2.0": + version: 4.2.0 + resolution: "pkg-dir@npm:4.2.0" + dependencies: + find-up: ^4.0.0 + checksum: 9863e3f35132bf99ae1636d31ff1e1e3501251d480336edb1c211133c8d58906bed80f154a1d723652df1fda91e01c7442c2eeaf9dc83157c7ae89087e43c8d6 + languageName: node + linkType: hard + "please-upgrade-node@npm:^3.2.0": version: 3.2.0 resolution: "please-upgrade-node@npm:3.2.0" @@ -5044,6 +6737,18 @@ __metadata: languageName: node linkType: hard +"pretty-format@npm:^28.0.0, pretty-format@npm:^28.1.3": + version: 28.1.3 + resolution: "pretty-format@npm:28.1.3" + dependencies: + "@jest/schemas": ^28.1.3 + ansi-regex: ^5.0.1 + ansi-styles: ^5.0.0 + react-is: ^18.0.0 + checksum: e69f857358a3e03d271252d7524bec758c35e44680287f36c1cb905187fbc82da9981a6eb07edfd8a03bc3cbeebfa6f5234c13a3d5b59f2bbdf9b4c4053e0a7f + languageName: node + linkType: hard + "process-nextick-args@npm:~2.0.0": version: 2.0.1 resolution: "process-nextick-args@npm:2.0.1" @@ -5068,6 +6773,16 @@ __metadata: languageName: node linkType: hard +"prompts@npm:^2.0.1": + version: 2.4.2 + resolution: "prompts@npm:2.4.2" + dependencies: + kleur: ^3.0.3 + sisteransi: ^1.0.5 + checksum: d8fd1fe63820be2412c13bfc5d0a01909acc1f0367e32396962e737cb2fc52d004f3302475d5ce7d18a1e8a79985f93ff04ee03007d091029c3f9104bffc007d + languageName: node + linkType: hard + "psl@npm:^1.1.28": version: 1.9.0 resolution: "psl@npm:1.9.0" @@ -5122,6 +6837,13 @@ __metadata: languageName: node linkType: hard +"react-is@npm:^18.0.0": + version: 18.2.0 + resolution: "react-is@npm:18.2.0" + checksum: e72d0ba81b5922759e4aff17e0252bd29988f9642ed817f56b25a3e217e13eea8a7f2322af99a06edb779da12d5d636e9fda473d620df9a3da0df2a74141d53e + languageName: node + linkType: hard + "read-cmd-shim@npm:^4.0.0": version: 4.0.0 resolution: "read-cmd-shim@npm:4.0.0" @@ -5234,6 +6956,15 @@ __metadata: languageName: node linkType: hard +"resolve-cwd@npm:^3.0.0": + version: 3.0.0 + resolution: "resolve-cwd@npm:3.0.0" + dependencies: + resolve-from: ^5.0.0 + checksum: 546e0816012d65778e580ad62b29e975a642989108d9a3c5beabfb2304192fa3c9f9146fbdfe213563c6ff51975ae41bac1d3c6e047dd9572c94863a057b4d81 + languageName: node + linkType: hard + "resolve-from@npm:^4.0.0": version: 4.0.0 resolution: "resolve-from@npm:4.0.0" @@ -5241,6 +6972,20 @@ __metadata: languageName: node linkType: hard +"resolve-from@npm:^5.0.0": + version: 5.0.0 + resolution: "resolve-from@npm:5.0.0" + checksum: 4ceeb9113e1b1372d0cd969f3468fa042daa1dd9527b1b6bb88acb6ab55d8b9cd65dbf18819f9f9ddf0db804990901dcdaade80a215e7b2c23daae38e64f5bdf + languageName: node + linkType: hard + +"resolve.exports@npm:^1.1.0": + version: 1.1.1 + resolution: "resolve.exports@npm:1.1.1" + checksum: 485aa10082eb388a569d696e17ad7b16f4186efc97dd34eadd029d95b811f21ffee13b1b733198bb4584dbb3cb296aa6f141835221fb7613b9606b84f1386655 + languageName: node + linkType: hard + "resolve@npm:^1.10.1, resolve@npm:^1.18.1, resolve@npm:^1.20.0, resolve@npm:^1.22.1": version: 1.22.1 resolution: "resolve@npm:1.22.1" @@ -5281,7 +7026,7 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": +"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2": version: 3.0.2 resolution: "rimraf@npm:3.0.2" dependencies: @@ -5440,7 +7185,18 @@ __metadata: languageName: node linkType: hard -"semver@npm:^6.1.0, semver@npm:^6.3.0": +"semver@npm:7.x": + version: 7.5.0 + resolution: "semver@npm:7.5.0" + dependencies: + lru-cache: ^6.0.0 + bin: + semver: bin/semver.js + checksum: 2d266937756689a76f124ffb4c1ea3e1bbb2b263219f90ada8a11aebebe1280b13bb76cca2ca96bdee3dbc554cbc0b24752eb895b2a51577aa644427e9229f2b + languageName: node + linkType: hard + +"semver@npm:^6.0.0, semver@npm:^6.1.0, semver@npm:^6.3.0": version: 6.3.0 resolution: "semver@npm:6.3.0" bin: @@ -5525,13 +7281,20 @@ __metadata: languageName: node linkType: hard -"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.7": +"signal-exit@npm:^3.0.0, signal-exit@npm:^3.0.3, signal-exit@npm:^3.0.7": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" checksum: a2f098f247adc367dffc27845853e9959b9e88b01cb301658cfe4194352d8d2bb32e18467c786a7fe15f1d44b233ea35633d076d5e737870b7139949d1ab6318 languageName: node linkType: hard +"sisteransi@npm:^1.0.5": + version: 1.0.5 + resolution: "sisteransi@npm:1.0.5" + checksum: aba6438f46d2bfcef94cf112c835ab395172c75f67453fe05c340c770d3c402363018ae1ab4172a1026a90c47eaccf3af7b6ff6fa749a680c2929bd7fa2b37a4 + languageName: node + linkType: hard + "slash@npm:^3.0.0": version: 3.0.0 resolution: "slash@npm:3.0.0" @@ -5604,7 +7367,17 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.6.1": +"source-map-support@npm:0.5.13": + version: 0.5.13 + resolution: "source-map-support@npm:0.5.13" + dependencies: + buffer-from: ^1.0.0 + source-map: ^0.6.0 + checksum: 933550047b6c1a2328599a21d8b7666507427c0f5ef5eaadd56b5da0fd9505e239053c66fe181bf1df469a3b7af9d775778eee283cbb7ae16b902ddc09e93a97 + languageName: node + linkType: hard + +"source-map@npm:^0.6.0, source-map@npm:^0.6.1": version: 0.6.1 resolution: "source-map@npm:0.6.1" checksum: 59ce8640cf3f3124f64ac289012c2b8bd377c238e316fb323ea22fbfe83da07d81e000071d7242cad7a23cd91c7de98e4df8830ec3f133cb6133a5f6e9f67bc2 @@ -5679,6 +7452,25 @@ __metadata: languageName: node linkType: hard +"stack-utils@npm:^2.0.3": + version: 2.0.6 + resolution: "stack-utils@npm:2.0.6" + dependencies: + escape-string-regexp: ^2.0.0 + checksum: 052bf4d25bbf5f78e06c1d5e67de2e088b06871fa04107ca8d3f0e9d9263326e2942c8bedee3545795fc77d787d443a538345eef74db2f8e35db3558c6f91ff7 + languageName: node + linkType: hard + +"string-length@npm:^4.0.1": + version: 4.0.2 + resolution: "string-length@npm:4.0.2" + dependencies: + char-regex: ^1.0.2 + strip-ansi: ^6.0.0 + checksum: ce85533ef5113fcb7e522bcf9e62cb33871aa99b3729cec5595f4447f660b0cefd542ca6df4150c97a677d58b0cb727a3fe09ac1de94071d05526c73579bf505 + languageName: node + linkType: hard + "string-width@npm:^1.0.1": version: 1.0.2 resolution: "string-width@npm:1.0.2" @@ -5777,6 +7569,20 @@ __metadata: languageName: node linkType: hard +"strip-bom@npm:^4.0.0": + version: 4.0.0 + resolution: "strip-bom@npm:4.0.0" + checksum: 9dbcfbaf503c57c06af15fe2c8176fb1bf3af5ff65003851a102749f875a6dbe0ab3b30115eccf6e805e9d756830d3e40ec508b62b3f1ddf3761a20ebe29d3f3 + languageName: node + linkType: hard + +"strip-final-newline@npm:^2.0.0": + version: 2.0.0 + resolution: "strip-final-newline@npm:2.0.0" + checksum: 69412b5e25731e1938184b5d489c32e340605bb611d6140344abc3421b7f3c6f9984b21dff296dfcf056681b82caa3bb4cc996a965ce37bcfad663e92eae9c64 + languageName: node + linkType: hard + "strip-hex-prefix@npm:1.0.0": version: 1.0.0 resolution: "strip-hex-prefix@npm:1.0.0" @@ -5800,15 +7606,6 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:5.4.0": - version: 5.4.0 - resolution: "supports-color@npm:5.4.0" - dependencies: - has-flag: ^3.0.0 - checksum: bc84f495b07a5cdfd871243d94a5d390972e5203ca07b189f49467c46102df348044c411ce9be872f77265f6c65bea0052c4898b9b7dac25f4a45253d23caa5b - languageName: node - linkType: hard - "supports-color@npm:^5.3.0": version: 5.5.0 resolution: "supports-color@npm:5.5.0" @@ -5818,7 +7615,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^7.1.0": +"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0": version: 7.2.0 resolution: "supports-color@npm:7.2.0" dependencies: @@ -5827,6 +7624,25 @@ __metadata: languageName: node linkType: hard +"supports-color@npm:^8.0.0": + version: 8.1.1 + resolution: "supports-color@npm:8.1.1" + dependencies: + has-flag: ^4.0.0 + checksum: c052193a7e43c6cdc741eb7f378df605636e01ad434badf7324f17fb60c69a880d8d8fcdcb562cf94c2350e57b937d7425ab5b8326c67c2adc48f7c87c1db406 + languageName: node + linkType: hard + +"supports-hyperlinks@npm:^2.0.0": + version: 2.3.0 + resolution: "supports-hyperlinks@npm:2.3.0" + dependencies: + has-flag: ^4.0.0 + supports-color: ^7.0.0 + checksum: 9ee0de3c8ce919d453511b2b1588a8205bd429d98af94a01df87411391010fe22ca463f268c84b2ce2abad019dfff8452aa02806eeb5c905a8d7ad5c4f4c52b8 + languageName: node + linkType: hard + "supports-preserve-symlinks-flag@npm:^1.0.0": version: 1.0.0 resolution: "supports-preserve-symlinks-flag@npm:1.0.0" @@ -5858,6 +7674,27 @@ __metadata: languageName: node linkType: hard +"terminal-link@npm:^2.0.0": + version: 2.1.1 + resolution: "terminal-link@npm:2.1.1" + dependencies: + ansi-escapes: ^4.2.1 + supports-hyperlinks: ^2.0.0 + checksum: ce3d2cd3a438c4a9453947aa664581519173ea40e77e2534d08c088ee6dda449eabdbe0a76d2a516b8b73c33262fedd10d5270ccf7576ae316e3db170ce6562f + languageName: node + linkType: hard + +"test-exclude@npm:^6.0.0": + version: 6.0.0 + resolution: "test-exclude@npm:6.0.0" + dependencies: + "@istanbuljs/schema": ^0.1.2 + glob: ^7.1.4 + minimatch: ^3.0.4 + checksum: 3b34a3d77165a2cb82b34014b3aba93b1c4637a5011807557dc2f3da826c59975a5ccad765721c4648b39817e3472789f9b0fa98fc854c5c1c7a1e632aacdc28 + languageName: node + linkType: hard + "text-table@npm:^0.2.0": version: 0.2.0 resolution: "text-table@npm:0.2.0" @@ -5875,6 +7712,13 @@ __metadata: languageName: node linkType: hard +"tmpl@npm:1.0.5": + version: 1.0.5 + resolution: "tmpl@npm:1.0.5" + checksum: cd922d9b853c00fe414c5a774817be65b058d54a2d01ebb415840960406c669a0fc632f66df885e24cb022ec812739199ccbdb8d1164c3e513f85bfca5ab2873 + languageName: node + linkType: hard + "to-fast-properties@npm:^2.0.0": version: 2.0.0 resolution: "to-fast-properties@npm:2.0.0" @@ -5901,6 +7745,39 @@ __metadata: languageName: node linkType: hard +"ts-jest@npm:^28.0.7": + version: 28.0.8 + resolution: "ts-jest@npm:28.0.8" + dependencies: + bs-logger: 0.x + fast-json-stable-stringify: 2.x + jest-util: ^28.0.0 + json5: ^2.2.1 + lodash.memoize: 4.x + make-error: 1.x + semver: 7.x + yargs-parser: ^21.0.1 + peerDependencies: + "@babel/core": ">=7.0.0-beta.0 <8" + "@jest/types": ^28.0.0 + babel-jest: ^28.0.0 + jest: ^28.0.0 + typescript: ">=4.3" + peerDependenciesMeta: + "@babel/core": + optional: true + "@jest/types": + optional: true + babel-jest: + optional: true + esbuild: + optional: true + bin: + ts-jest: cli.js + checksum: c72e9292709e77ce47ac7813cb24feaa9d01dc983598d29a821f224b5cc190dc7d67e17379cef089095404c00b9d582ee91c727916f9ec289cb1b723df408ae3 + languageName: node + linkType: hard + "ts-node@npm:^10.7.0": version: 10.9.1 resolution: "ts-node@npm:10.9.1" @@ -6015,7 +7892,7 @@ __metadata: languageName: node linkType: hard -"type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": +"type-detect@npm:4.0.8, type-detect@npm:^4.0.0, type-detect@npm:^4.0.5": version: 4.0.8 resolution: "type-detect@npm:4.0.8" checksum: 62b5628bff67c0eb0b66afa371bd73e230399a8d2ad30d852716efcc4656a7516904570cd8631a49a3ce57c10225adf5d0cbdcb47f6b0255fe6557c453925a15 @@ -6029,6 +7906,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^0.21.3": + version: 0.21.3 + resolution: "type-fest@npm:0.21.3" + checksum: e6b32a3b3877f04339bae01c193b273c62ba7bfc9e325b8703c4ee1b32dc8fe4ef5dfa54bf78265e069f7667d058e360ae0f37be5af9f153b22382cd55a9afe0 + languageName: node + linkType: hard + "typed-array-length@npm:^1.0.4": version: 1.0.4 resolution: "typed-array-length@npm:1.0.4" @@ -6106,6 +7990,20 @@ __metadata: languageName: node linkType: hard +"update-browserslist-db@npm:^1.0.10": + version: 1.0.11 + resolution: "update-browserslist-db@npm:1.0.11" + dependencies: + escalade: ^3.1.1 + picocolors: ^1.0.0 + peerDependencies: + browserslist: ">= 4.21.0" + bin: + update-browserslist-db: cli.js + checksum: b98327518f9a345c7cad5437afae4d2ae7d865f9779554baf2a200fdf4bac4969076b679b1115434bd6557376bdd37ca7583d0f9b8f8e302d7d4cc1e91b5f231 + languageName: node + linkType: hard + "uri-js@npm:^4.2.2": version: 4.4.1 resolution: "uri-js@npm:4.4.1" @@ -6138,6 +8036,17 @@ __metadata: languageName: node linkType: hard +"v8-to-istanbul@npm:^9.0.1": + version: 9.1.0 + resolution: "v8-to-istanbul@npm:9.1.0" + dependencies: + "@jridgewell/trace-mapping": ^0.3.12 + "@types/istanbul-lib-coverage": ^2.0.1 + convert-source-map: ^1.6.0 + checksum: 2069d59ee46cf8d83b4adfd8a5c1a90834caffa9f675e4360f1157ffc8578ef0f763c8f32d128334424159bb6b01f3876acd39cd13297b2769405a9da241f8d1 + languageName: node + linkType: hard + "verror@npm:1.10.0": version: 1.10.0 resolution: "verror@npm:1.10.0" @@ -6163,6 +8072,15 @@ __metadata: languageName: node linkType: hard +"walker@npm:^1.0.8": + version: 1.0.8 + resolution: "walker@npm:1.0.8" + dependencies: + makeerror: 1.0.12 + checksum: ad7a257ea1e662e57ef2e018f97b3c02a7240ad5093c392186ce0bcf1f1a60bbadd520d073b9beb921ed99f64f065efb63dfc8eec689a80e569f93c1c5d5e16c + languageName: node + linkType: hard + "which-boxed-primitive@npm:^1.0.2": version: 1.0.2 resolution: "which-boxed-primitive@npm:1.0.2" @@ -6235,6 +8153,16 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^4.0.1": + version: 4.0.2 + resolution: "write-file-atomic@npm:4.0.2" + dependencies: + imurmurhash: ^0.1.4 + signal-exit: ^3.0.7 + checksum: 5da60bd4eeeb935eec97ead3df6e28e5917a6bd317478e4a85a5285e8480b8ed96032bbcc6ecd07b236142a24f3ca871c924ec4a6575e623ec1b11bf8c1c253c + languageName: node + linkType: hard + "write-file-atomic@npm:^5.0.0": version: 5.0.0 resolution: "write-file-atomic@npm:5.0.0" @@ -6267,6 +8195,13 @@ __metadata: languageName: node linkType: hard +"yallist@npm:^3.0.2": + version: 3.1.1 + resolution: "yallist@npm:3.1.1" + checksum: 48f7bb00dc19fc635a13a39fe547f527b10c9290e7b3e836b9a8f1ca04d4d342e85714416b3c2ab74949c9c66f9cebb0473e6bc353b79035356103b47641285d + languageName: node + linkType: hard + "yallist@npm:^4.0.0": version: 4.0.0 resolution: "yallist@npm:4.0.0" @@ -6288,6 +8223,13 @@ __metadata: languageName: node linkType: hard +"yargs-parser@npm:^21.0.1, yargs-parser@npm:^21.1.1": + version: 21.1.1 + resolution: "yargs-parser@npm:21.1.1" + checksum: ed2d96a616a9e3e1cc7d204c62ecc61f7aaab633dcbfab2c6df50f7f87b393993fe6640d017759fe112d0cb1e0119f2b4150a87305cc873fd90831c6a58ccf1c + languageName: node + linkType: hard + "yargs@npm:^16.1.0, yargs@npm:^16.2.0": version: 16.2.0 resolution: "yargs@npm:16.2.0" @@ -6303,6 +8245,21 @@ __metadata: languageName: node linkType: hard +"yargs@npm:^17.3.1": + version: 17.7.2 + resolution: "yargs@npm:17.7.2" + dependencies: + cliui: ^8.0.1 + escalade: ^3.1.1 + get-caller-file: ^2.0.5 + require-directory: ^2.1.1 + string-width: ^4.2.3 + y18n: ^5.0.5 + yargs-parser: ^21.1.1 + checksum: 73b572e863aa4a8cbef323dd911d79d193b772defd5a51aab0aca2d446655216f5002c42c5306033968193bdbf892a7a4c110b0d77954a7fdf563e653967b56a + languageName: node + linkType: hard + "yn@npm:3.1.1": version: 3.1.1 resolution: "yn@npm:3.1.1" From 59ec1cc9d5553f70b27d2a39f2fd510c6a32b0f9 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 28 Apr 2023 13:17:45 +0100 Subject: [PATCH 12/22] coverage up --- jest.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jest.config.js b/jest.config.js index d79019c5..722b10a7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,10 +41,10 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 59.25, - functions: 81.94, - lines: 78.54, - statements: 78.49, + branches: 64.1, + functions: 87.5, + lines: 82.1, + statements: 82.01, }, }, From 6f2902994d1476141f6a9c3c19f9b3643088d190 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Fri, 28 Apr 2023 14:23:56 +0100 Subject: [PATCH 13/22] linting --- src/ledger-iframe-bridge.test.ts | 31 ++++++++++------ src/ledger-iframe-bridge.ts | 1 + src/ledger-keyring.test.ts | 62 +++++++++++++++++--------------- 3 files changed, 56 insertions(+), 38 deletions(-) diff --git a/src/ledger-iframe-bridge.test.ts b/src/ledger-iframe-bridge.test.ts index 42264f2f..ebe67e37 100644 --- a/src/ledger-iframe-bridge.test.ts +++ b/src/ledger-iframe-bridge.test.ts @@ -1,4 +1,5 @@ import { hasProperty } from '@metamask/utils'; + import { LedgerIframeBridge } from './ledger-iframe-bridge'; import documentShim from '../test/document.shim'; import windowShim from '../test/window.shim'; @@ -126,6 +127,7 @@ describe('LedgerIframeBridge', function () { expect(result).toBe(true); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); @@ -145,8 +147,9 @@ describe('LedgerIframeBridge', function () { } as any); }); - await expect(bridge.attemptMakeApp()).rejects.toThrowError(errorMessage); + await expect(bridge.attemptMakeApp()).rejects.toThrow(errorMessage); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); @@ -174,6 +177,7 @@ describe('LedgerIframeBridge', function () { expect(result).toBe(true); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); @@ -195,10 +199,11 @@ describe('LedgerIframeBridge', function () { } as any); }); - await expect( - bridge.updateTransportMethod(transportType), - ).rejects.toThrowError('Ledger transport could not be updated'); + await expect(bridge.updateTransportMethod(transportType)).rejects.toThrow( + 'Ledger transport could not be updated', + ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); @@ -228,6 +233,7 @@ describe('LedgerIframeBridge', function () { expect(result).toBe(payload); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); @@ -251,10 +257,9 @@ describe('LedgerIframeBridge', function () { } as any); }); - await expect(bridge.getPublicKey(params)).rejects.toThrowError( - errorMessage, - ); + await expect(bridge.getPublicKey(params)).rejects.toThrow(errorMessage); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); @@ -285,6 +290,7 @@ describe('LedgerIframeBridge', function () { expect(result).toBe(payload); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); @@ -306,10 +312,11 @@ describe('LedgerIframeBridge', function () { } as any); }); - await expect(bridge.deviceSignTransaction(params)).rejects.toThrowError( + await expect(bridge.deviceSignTransaction(params)).rejects.toThrow( errorMessage, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); @@ -337,6 +344,7 @@ describe('LedgerIframeBridge', function () { expect(result).toBe(payload); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); @@ -358,10 +366,11 @@ describe('LedgerIframeBridge', function () { } as any); }); - await expect(bridge.deviceSignMessage(params)).rejects.toThrowError( + await expect(bridge.deviceSignMessage(params)).rejects.toThrow( errorMessage, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); @@ -393,6 +402,7 @@ describe('LedgerIframeBridge', function () { expect(result).toBe(payload); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); @@ -418,10 +428,11 @@ describe('LedgerIframeBridge', function () { } as any); }); - await expect(bridge.deviceSignTypedData(params)).rejects.toThrowError( + await expect(bridge.deviceSignTypedData(params)).rejects.toThrow( errorMessage, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.iframe?.contentWindow?.postMessage).toHaveBeenCalled(); }); }); diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index af448d5b..854bc14b 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -1,5 +1,6 @@ import type LedgerHwAppEth from '@ledgerhq/hw-app-eth'; import { hasProperty } from '@metamask/utils'; + import { GetPublicKeyParams, GetPublicKeyResponse, diff --git a/src/ledger-keyring.test.ts b/src/ledger-keyring.test.ts index 86062f02..f6481698 100644 --- a/src/ledger-keyring.test.ts +++ b/src/ledger-keyring.test.ts @@ -6,8 +6,8 @@ import * as ethUtil from 'ethereumjs-util'; import HDKey from 'hdkey'; import { LedgerBridge } from './ledger-bridge'; -import { AccountDetails, LedgerKeyring } from './ledger-keyring'; import { LedgerIframeBridge } from './ledger-iframe-bridge'; +import { AccountDetails, LedgerKeyring } from './ledger-keyring'; const fakeAccounts = [ '0xF30952A1c534CDE7bC471380065726fa8686dfB3', @@ -145,7 +145,7 @@ describe('LedgerKeyring', function () { new LedgerKeyring({ bridge: undefined as unknown as LedgerBridge, }), - ).toThrowError('Bridge is a required dependency for the keyring'); + ).toThrow('Bridge is a required dependency for the keyring'); }); }); @@ -155,6 +155,7 @@ describe('LedgerKeyring', function () { await keyring.init(); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.init).toHaveBeenCalledTimes(1); }); }); @@ -492,12 +493,12 @@ describe('LedgerKeyring', function () { await basicSetupToUnlockOneAccount(); jest .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockImplementation((params) => { + .mockImplementation(async (params) => { expect(params).toStrictEqual({ hdPath: "m/44'/60'/0'/0", tx: fakeTx.serialize().toString('hex'), }); - return Promise.resolve({ v: '0x1', r: '0x0', s: '0x0' }); + return { v: '0x1', r: '0x0', s: '0x0' }; }); jest.spyOn(fakeTx, 'verifySignature').mockReturnValue(true); @@ -507,6 +508,7 @@ describe('LedgerKeyring', function () { fakeTx, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); expect(returnedTx).toHaveProperty('v'); expect(returnedTx).toHaveProperty('r'); @@ -543,14 +545,14 @@ describe('LedgerKeyring', function () { jest .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockImplementation((params) => { + .mockImplementation(async (params) => { expect(params).toStrictEqual({ hdPath: "m/44'/60'/0'/0", tx: ethUtil.rlp .encode(newFakeTx.getMessageToSign(false)) .toString('hex'), }); - return Promise.resolve(expectedRSV); + return expectedRSV; }); const returnedTx = await keyring.signTransaction( @@ -558,6 +560,7 @@ describe('LedgerKeyring', function () { newFakeTx, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); expect(returnedTx.toJSON()).toStrictEqual(signedNewFakeTx.toJSON()); }); @@ -590,12 +593,12 @@ describe('LedgerKeyring', function () { jest.spyOn(fakeTypeTwoTx, 'verifySignature').mockReturnValue(true); jest .spyOn(keyring.bridge, 'deviceSignTransaction') - .mockImplementation((params) => { + .mockImplementation(async (params) => { expect(params).toStrictEqual({ hdPath: "m/44'/60'/0'/0", tx: fakeTypeTwoTx.getMessageToSign(false).toString('hex'), }); - return Promise.resolve(expectedRSV); + return expectedRSV; }); const returnedTx = await keyring.signTransaction( @@ -603,8 +606,8 @@ describe('LedgerKeyring', function () { fakeTypeTwoTx, ); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(keyring.bridge.deviceSignTransaction).toHaveBeenCalled(); - expect(returnedTx.toJSON()).toStrictEqual(signedFakeTypeTwoTx.toJSON()); }); }); @@ -615,18 +618,21 @@ describe('LedgerKeyring', function () { await basicSetupToUnlockOneAccount(); jest .spyOn(keyring.bridge, 'deviceSignMessage') - .mockImplementation((params) => { + .mockImplementation(async (params) => { expect(params).toStrictEqual({ hdPath: "m/44'/60'/0'/0", message: 'some message', }); - return Promise.resolve({ v: 1, r: '0x0', s: '0x0' }); + return { v: 1, r: '0x0', s: '0x0' }; }); jest .spyOn(sigUtil, 'recoverPersonalSignature') .mockReturnValue(fakeAccounts[0]); + await keyring.signPersonalMessage(fakeAccounts[0], 'some message'); + + // eslint-disable-next-line @typescript-eslint/unbound-method expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); }); }); @@ -636,18 +642,21 @@ describe('LedgerKeyring', function () { await basicSetupToUnlockOneAccount(); jest .spyOn(keyring.bridge, 'deviceSignMessage') - .mockImplementation((params) => { + .mockImplementation(async (params) => { expect(params).toStrictEqual({ hdPath: "m/44'/60'/0'/0", message: 'some message', }); - return Promise.resolve({ v: 1, r: '0x0', s: '0x0' }); + return { v: 1, r: '0x0', s: '0x0' }; }); jest .spyOn(sigUtil, 'recoverPersonalSignature') .mockReturnValue(fakeAccounts[0]); + await keyring.signMessage(fakeAccounts[0], 'some message'); + + // eslint-disable-next-line @typescript-eslint/unbound-method expect(keyring.bridge.deviceSignMessage).toHaveBeenCalled(); }); }); @@ -739,13 +748,11 @@ describe('LedgerKeyring', function () { it('should resolve properly when called', async function () { jest .spyOn(keyring.bridge, 'deviceSignTypedData') - .mockImplementation(() => { - return Promise.resolve({ - v: 27, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - }); - }); + .mockImplementation(async () => ({ + v: 27, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + })); const result = await keyring.signTypedData( fakeAccounts[15], @@ -760,14 +767,12 @@ describe('LedgerKeyring', function () { it('should error when address does not match', async function () { jest .spyOn(keyring.bridge, 'deviceSignTypedData') - .mockImplementation(() => { - // Changing v to 28 should cause a validation error - return Promise.resolve({ - v: 28, - r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', - s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', - }); - }); + // Changing v to 28 should cause a validation error + .mockImplementation(async () => ({ + v: 28, + r: '72d4e38a0e582e09a620fd38e236fe687a1ec782206b56d576f579c026a7e5b9', + s: '46759735981cd0c3efb02d36df28bb2feedfec3d90e408efc93f45b894946e32', + })); await expect( keyring.signTypedData(fakeAccounts[15], fixtureData, options), @@ -781,6 +786,7 @@ describe('LedgerKeyring', function () { await keyring.destroy(); + // eslint-disable-next-line @typescript-eslint/unbound-method expect(bridge.destroy).toHaveBeenCalled(); }); }); From 6dfe79659b426162c04ef0f6fc8b4dedf6f33f23 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 4 May 2023 09:56:31 +0100 Subject: [PATCH 14/22] better types --- src/ledger-iframe-bridge.test.ts | 27 +++++++++++++++++++-------- src/ledger-iframe-bridge.ts | 5 +++-- src/ledger-keyring.test.ts | 14 +------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/ledger-iframe-bridge.test.ts b/src/ledger-iframe-bridge.test.ts index ebe67e37..d57c57a6 100644 --- a/src/ledger-iframe-bridge.test.ts +++ b/src/ledger-iframe-bridge.test.ts @@ -1,6 +1,9 @@ import { hasProperty } from '@metamask/utils'; -import { LedgerIframeBridge } from './ledger-iframe-bridge'; +import { + IFrameMessageAction, + LedgerIframeBridge, +} from './ledger-iframe-bridge'; import documentShim from '../test/document.shim'; import windowShim from '../test/window.shim'; @@ -113,14 +116,16 @@ describe('LedgerIframeBridge', function () { it('sends and processes a successful ledger-make-app message', async function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-make-app', + action: IFrameMessageAction.LedgerMakeApp, messageId: 1, target: 'LEDGER-IFRAME', }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerMakeApp, + messageId: 1, success: true, - } as any); + }); }); const result = await bridge.attemptMakeApp(); @@ -136,15 +141,17 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-make-app', + action: IFrameMessageAction.LedgerMakeApp, messageId: 1, target: 'LEDGER-IFRAME', }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerMakeApp, + messageId: 1, error: new Error(errorMessage), success: false, - } as any); + }); }); await expect(bridge.attemptMakeApp()).rejects.toThrow(errorMessage); @@ -162,15 +169,17 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-update-transport', + action: IFrameMessageAction.LedgerUpdateTransport, params: { transportType }, messageId: 1, target: 'LEDGER-IFRAME', }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerUpdateTransport, + messageId: 1, success: true, - } as any); + }); }); const result = await bridge.updateTransportMethod(transportType); @@ -195,8 +204,10 @@ describe('LedgerIframeBridge', function () { }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerUpdateTransport, + messageId: 1, success: false, - } as any); + }); }); await expect(bridge.updateTransportMethod(transportType)).rejects.toThrow( diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index 854bc14b..e00bb968 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -15,7 +15,7 @@ import { const LEDGER_IFRAME_ID = 'LEDGER-IFRAME'; -enum IFrameMessageAction { +export enum IFrameMessageAction { LedgerConnectionChange = 'ledger-connection-change', LedgerUnlock = 'ledger-unlock', LedgerMakeApp = 'ledger-make-app', @@ -62,7 +62,7 @@ type IFrameMessageResponse = { success: boolean; action: IFrameMessageAction; messageId: number; - payload: IFrameMessageResponsePayload; + payload?: IFrameMessageResponsePayload; error?: unknown; }; @@ -250,6 +250,7 @@ export class LedgerIframeBridge implements LedgerBridge { messageCallback(params.data); } else if ( params.data.action === IFrameMessageAction.LedgerConnectionChange && + params.data.payload && isConnectionChangedResponse(params.data.payload) ) { this.isDeviceConnected = params.data.payload.connected; diff --git a/src/ledger-keyring.test.ts b/src/ledger-keyring.test.ts index f6481698..4c15cd8f 100644 --- a/src/ledger-keyring.test.ts +++ b/src/ledger-keyring.test.ts @@ -93,19 +93,7 @@ describe('LedgerKeyring', function () { } beforeEach(async function () { - // eslint-disable-next-line @typescript-eslint/no-empty-function - const noop = () => {}; - bridge = { - init: noop, - destroy: noop, - attemptMakeApp: noop, - updateTransportMethod: noop, - getPublicKey: noop, - deviceSignTransaction: noop, - deviceSignMessage: noop, - deviceSignTypedData: noop, - } as any; - + bridge = new LedgerIframeBridge(); keyring = new LedgerKeyring({ bridge }); keyring.hdk = fakeHdKey; await keyring.deserialize(); From 23e1305e9ebbd97979f38cf5a1a9237f969b7b1b Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 4 May 2023 14:41:18 +0100 Subject: [PATCH 15/22] types --- jest.config.js | 8 +-- src/index.ts | 1 + src/ledger-iframe-bridge.test.ts | 118 ++++++++++++++++++++----------- src/ledger-iframe-bridge.ts | 49 ++++++++----- src/ledger-keyring.ts | 10 --- 5 files changed, 112 insertions(+), 74 deletions(-) diff --git a/jest.config.js b/jest.config.js index 722b10a7..d79019c5 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,10 +41,10 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 64.1, - functions: 87.5, - lines: 82.1, - statements: 82.01, + branches: 59.25, + functions: 81.94, + lines: 78.54, + statements: 78.49, }, }, diff --git a/src/index.ts b/src/index.ts index 25abc17b..b08ff492 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,2 +1,3 @@ export * from './ledger-keyring'; export * from './ledger-iframe-bridge'; +export * from './ledger-bridge'; diff --git a/src/ledger-iframe-bridge.test.ts b/src/ledger-iframe-bridge.test.ts index d57c57a6..46d0ae89 100644 --- a/src/ledger-iframe-bridge.test.ts +++ b/src/ledger-iframe-bridge.test.ts @@ -49,6 +49,8 @@ async function simulateIFrameLoad(iframe?: HTMLIFrameElementShim) { return await iframe.onload(); } +const LEDGER_IFRAME_ID = 'LEDGER-IFRAME'; + describe('LedgerIframeBridge', function () { let bridge: LedgerIframeBridge; @@ -118,7 +120,7 @@ describe('LedgerIframeBridge', function () { expect(message).toStrictEqual({ action: IFrameMessageAction.LedgerMakeApp, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, }); bridge.messageCallbacks[message.messageId]?.({ @@ -143,14 +145,14 @@ describe('LedgerIframeBridge', function () { expect(message).toStrictEqual({ action: IFrameMessageAction.LedgerMakeApp, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, }); bridge.messageCallbacks[message.messageId]?.({ action: IFrameMessageAction.LedgerMakeApp, messageId: 1, - error: new Error(errorMessage), success: false, + error: new Error(errorMessage), }); }); @@ -170,9 +172,9 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ action: IFrameMessageAction.LedgerUpdateTransport, - params: { transportType }, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params: { transportType }, }); bridge.messageCallbacks[message.messageId]?.({ @@ -198,9 +200,9 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ action: 'ledger-update-transport', - params: { transportType }, messageId: 1, - target: 'LEDGER-IFRAME', + params: { transportType }, + target: LEDGER_IFRAME_ID, }); bridge.messageCallbacks[message.messageId]?.({ @@ -221,23 +223,29 @@ describe('LedgerIframeBridge', function () { describe('getPublicKey', function () { it('sends and processes a successful ledger-unlock message', async function () { - const payload = {}; + const payload = { + publicKey: '', + address: '', + chainCode: '', + }; const params = { hdPath: "m/44'/60'/0'/0", }; stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-unlock', + action: IFrameMessageAction.LedgerUnlock, params, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerUnlock, + messageId: 1, success: true, payload, - } as any); + }); }); const result = await bridge.getPublicKey(params); @@ -256,16 +264,18 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-unlock', - params, + action: IFrameMessageAction.LedgerUnlock, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerUnlock, + messageId: 1, success: false, payload: { error: new Error(errorMessage) }, - } as any); + }); }); await expect(bridge.getPublicKey(params)).rejects.toThrow(errorMessage); @@ -277,7 +287,11 @@ describe('LedgerIframeBridge', function () { describe('deviceSignTransaction', function () { it('sends and processes a successful ledger-sign-transaction message', async function () { - const payload = {}; + const payload = { + v: '', + r: '', + s: '', + }; const params = { hdPath: "m/44'/60'/0'/0", tx: '', @@ -285,16 +299,18 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-sign-transaction', - params, + action: IFrameMessageAction.LedgerSignTransaction, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerSignTransaction, + messageId: 1, success: true, payload, - } as any); + }); }); const result = await bridge.deviceSignTransaction(params); @@ -311,16 +327,18 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-sign-transaction', - params, + action: IFrameMessageAction.LedgerSignTransaction, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerSignTransaction, + messageId: 1, success: false, payload: { error: new Error(errorMessage) }, - } as any); + }); }); await expect(bridge.deviceSignTransaction(params)).rejects.toThrow( @@ -334,21 +352,27 @@ describe('LedgerIframeBridge', function () { describe('deviceSignMessage', function () { it('sends and processes a successful ledger-sign-personal-message message', async function () { - const payload = {}; + const payload = { + v: 0, + r: '', + s: '', + }; const params = { hdPath: "m/44'/60'/0'/0", message: '' }; stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-sign-personal-message', - params, + action: IFrameMessageAction.LedgerSignPersonalMessage, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerSignPersonalMessage, + messageId: 1, success: true, payload, - } as any); + }); }); const result = await bridge.deviceSignMessage(params); @@ -365,16 +389,18 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-sign-personal-message', - params, + action: IFrameMessageAction.LedgerSignPersonalMessage, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerSignPersonalMessage, + messageId: 1, success: false, payload: { error: new Error(errorMessage) }, - } as any); + }); }); await expect(bridge.deviceSignMessage(params)).rejects.toThrow( @@ -388,7 +414,11 @@ describe('LedgerIframeBridge', function () { describe('deviceSignTypedData', function () { it('sends and processes a successful ledger-sign-typed-data message', async function () { - const payload = {}; + const payload = { + v: 0, + r: '', + s: '', + }; const params = { hdPath: "m/44'/60'/0'/0", domainSeparatorHex: '', @@ -397,16 +427,18 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-sign-typed-data', - params, + action: IFrameMessageAction.LedgerSignTypedData, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerSignTypedData, + messageId: 1, success: true, payload, - } as any); + }); }); const result = await bridge.deviceSignTypedData(params); @@ -427,16 +459,18 @@ describe('LedgerIframeBridge', function () { stubKeyringIFramePostMessage(bridge, (message) => { expect(message).toStrictEqual({ - action: 'ledger-sign-typed-data', - params, + action: IFrameMessageAction.LedgerSignTypedData, messageId: 1, - target: 'LEDGER-IFRAME', + target: LEDGER_IFRAME_ID, + params, }); bridge.messageCallbacks[message.messageId]?.({ + action: IFrameMessageAction.LedgerSignTypedData, + messageId: 1, success: false, payload: { error: new Error(errorMessage) }, - } as any); + }); }); await expect(bridge.deviceSignTypedData(params)).rejects.toThrow( diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index e00bb968..0ebfe555 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -51,20 +51,34 @@ type IFramePostMessage = IFrameMessage & { target: typeof LEDGER_IFRAME_ID; }; -type IFrameMessageResponsePayload = { error?: Error } & ( +type IFrameMessageResponsePayload = | GetAddressPayload | SignTransactionPayload | SignMessagePayload - | ConnectionChangedPayload -); + | ConnectionChangedPayload; + +// type IFrameMessageResponse = { +// success: boolean; +// action: IFrameMessageAction; +// messageId: number; +// payload?: IFrameMessageResponsePayload; +// error?: unknown; +// }; type IFrameMessageResponse = { - success: boolean; action: IFrameMessageAction; messageId: number; - payload?: IFrameMessageResponsePayload; - error?: unknown; -}; +} & ( + | { + success: true; + payload?: IFrameMessageResponsePayload; + } + | { + success: false; + payload?: { error: Error }; + error?: unknown; + } +); /** * Check if the given payload is a ConnectionChangedPayload. @@ -118,11 +132,11 @@ export class LedgerIframeBridge implements LedgerBridge { { action: IFrameMessageAction.LedgerMakeApp, }, - ({ success, error }: IFrameMessageResponse) => { - if (success) { + (response) => { + if (response.success) { resolve(true); } else { - reject(error); + reject(response.error); } }, ); @@ -149,10 +163,9 @@ export class LedgerIframeBridge implements LedgerBridge { }, ({ success }) => { if (success) { - resolve(true); - } else { - reject(new Error('Ledger transport could not be updated')); + return resolve(true); } + reject(new Error('Ledger transport could not be updated')); }, ); }); @@ -198,12 +211,11 @@ export class LedgerIframeBridge implements LedgerBridge { action, params, }, - ({ success, payload }) => { - if (success) { - resolve(payload); + (response) => { + if (response.success) { + return resolve(response.payload); } - // eslint-disable-next-line prefer-promise-reject-errors - reject(payload?.error); + reject(response.payload?.error); }, ); }); @@ -250,6 +262,7 @@ export class LedgerIframeBridge implements LedgerBridge { messageCallback(params.data); } else if ( params.data.action === IFrameMessageAction.LedgerConnectionChange && + params.data.success && params.data.payload && isConnectionChangedResponse(params.data.payload) ) { diff --git a/src/ledger-keyring.ts b/src/ledger-keyring.ts index 065c0c08..bef198d3 100644 --- a/src/ledger-keyring.ts +++ b/src/ledger-keyring.ts @@ -25,16 +25,6 @@ enum NetworkApiUrls { Mainnet = 'https://api.etherscan.io', } -export enum IFrameMessageAction { - LedgerConnectionChange = 'ledger-connection-change', - LedgerUnlock = 'ledger-unlock', - LedgerMakeApp = 'ledger-make-app', - LedgerUpdateTransport = 'ledger-update-transport', - LedgerSignTransaction = 'ledger-sign-transaction', - LedgerSignPersonalMessage = 'ledger-sign-personal-message', - LedgerSignTypedData = 'ledger-sign-typed-data', -} - type SignTransactionPayload = Awaited< ReturnType >; From 0d1b010fb9a9e7912c838379ab443557c6bdaeb7 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 4 May 2023 14:44:11 +0100 Subject: [PATCH 16/22] lint --- src/ledger-iframe-bridge.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index 0ebfe555..421af631 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -165,7 +165,7 @@ export class LedgerIframeBridge implements LedgerBridge { if (success) { return resolve(true); } - reject(new Error('Ledger transport could not be updated')); + return reject(new Error('Ledger transport could not be updated')); }, ); }); @@ -215,7 +215,7 @@ export class LedgerIframeBridge implements LedgerBridge { if (response.success) { return resolve(response.payload); } - reject(response.payload?.error); + return reject(response.payload?.error); }, ); }); From 2c58c0e49a758415658b362ffe54d396964be8bc Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 4 May 2023 16:17:41 +0100 Subject: [PATCH 17/22] more types --- src/ledger-iframe-bridge.ts | 156 +++++++++++++++++++----------------- 1 file changed, 84 insertions(+), 72 deletions(-) diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index 421af631..b199f778 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -1,5 +1,4 @@ import type LedgerHwAppEth from '@ledgerhq/hw-app-eth'; -import { hasProperty } from '@metamask/utils'; import { GetPublicKeyParams, @@ -25,6 +24,43 @@ export enum IFrameMessageAction { LedgerSignTypedData = 'ledger-sign-typed-data', } +type IFrameMessageResponse = { + action: TAction; + messageId: number; +} & ( + | { + action: IFrameMessageAction.LedgerConnectionChange; + payload: ConnectionChangedPayload; + } + | ({ + action: IFrameMessageAction.LedgerMakeApp; + } & ({ success: true } | { success: false; error?: unknown })) + | { + action: IFrameMessageAction.LedgerUpdateTransport; + success: boolean; + } + | ({ + action: IFrameMessageAction.LedgerUnlock; + } & ( + | { success: true; payload: GetAddressPayload } + | { success: false; payload: { error: Error } } + )) + | ({ + action: IFrameMessageAction.LedgerSignTransaction; + } & ( + | { success: true; payload: SignTransactionPayload } + | { success: false; payload: { error: Error } } + )) + | ({ + action: + | IFrameMessageAction.LedgerSignPersonalMessage + | IFrameMessageAction.LedgerSignTypedData; + } & ( + | { success: true; payload: SignMessagePayload } + | { success: false; payload: { error: Error } } + )) +); + type GetAddressPayload = Awaited> & { chainCode: string; }; @@ -41,58 +77,16 @@ type ConnectionChangedPayload = { connected: boolean; }; -type IFrameMessage = { - action: IFrameMessageAction; +type IFrameMessage = { + action: TAction; params?: Readonly>; }; -type IFramePostMessage = IFrameMessage & { - messageId: number; - target: typeof LEDGER_IFRAME_ID; -}; - -type IFrameMessageResponsePayload = - | GetAddressPayload - | SignTransactionPayload - | SignMessagePayload - | ConnectionChangedPayload; - -// type IFrameMessageResponse = { -// success: boolean; -// action: IFrameMessageAction; -// messageId: number; -// payload?: IFrameMessageResponsePayload; -// error?: unknown; -// }; - -type IFrameMessageResponse = { - action: IFrameMessageAction; - messageId: number; -} & ( - | { - success: true; - payload?: IFrameMessageResponsePayload; - } - | { - success: false; - payload?: { error: Error }; - error?: unknown; - } -); - -/** - * Check if the given payload is a ConnectionChangedPayload. - * - * @param payload - IFrame message response payload to check. - * @returns Returns `true` if payload is a ConnectionChangedPayload. - */ -function isConnectionChangedResponse( - payload: IFrameMessageResponsePayload, -): payload is ConnectionChangedPayload { - return ( - hasProperty(payload, 'connected') && typeof payload.connected === 'boolean' - ); -} +type IFramePostMessage = + IFrameMessage & { + messageId: number; + target: typeof LEDGER_IFRAME_ID; + }; export class LedgerIframeBridge implements LedgerBridge { iframe?: HTMLIFrameElement; @@ -105,8 +99,10 @@ export class LedgerIframeBridge implements LedgerBridge { currentMessageId = 0; - messageCallbacks: Record void> = - {}; + messageCallbacks: Record< + number, + (response: IFrameMessageResponse) => void + > = {}; delayedPromise?: { resolve: (value: boolean) => void; @@ -174,7 +170,10 @@ export class LedgerIframeBridge implements LedgerBridge { async getPublicKey( params: GetPublicKeyParams, ): Promise { - return this.#deviceActionMessage(IFrameMessageAction.LedgerUnlock, params); + return this.#deviceActionMessage( + IFrameMessageAction.LedgerUnlock, + params, + ) as Promise; } async deviceSignTransaction( @@ -183,7 +182,7 @@ export class LedgerIframeBridge implements LedgerBridge { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignTransaction, params, - ); + ) as Promise; } async deviceSignMessage( @@ -192,7 +191,7 @@ export class LedgerIframeBridge implements LedgerBridge { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignPersonalMessage, params, - ); + ) as Promise; } async deviceSignTypedData( @@ -201,21 +200,32 @@ export class LedgerIframeBridge implements LedgerBridge { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignTypedData, params, - ); + ) as Promise; } - async #deviceActionMessage(action: IFrameMessageAction, params: any) { - return new Promise((resolve, reject) => { + async #deviceActionMessage( + action: + | IFrameMessageAction.LedgerUnlock + | IFrameMessageAction.LedgerSignTransaction + | IFrameMessageAction.LedgerSignPersonalMessage + | IFrameMessageAction.LedgerSignTypedData, + params: + | GetPublicKeyParams + | LedgerSignTransactionParams + | LedgerSignMessageParams + | LedgerSignTypedDataParams, + ) { + return new Promise((resolve, reject) => { this.#sendMessage( { action, params, }, - (response) => { - if (response.success) { - return resolve(response.payload); + ({ success, payload }) => { + if (success) { + return resolve(payload); } - return reject(response.payload?.error); + return reject(payload.error); }, ); }); @@ -251,7 +261,10 @@ export class LedgerIframeBridge implements LedgerBridge { return tmp.join('/'); } - #eventListener(params: { origin: string; data: IFrameMessageResponse }) { + #eventListener(params: { + origin: string; + data: IFrameMessageResponse; + }) { if (params.origin !== this.#getOrigin()) { return; } @@ -261,29 +274,28 @@ export class LedgerIframeBridge implements LedgerBridge { if (messageCallback) { messageCallback(params.data); } else if ( - params.data.action === IFrameMessageAction.LedgerConnectionChange && - params.data.success && - params.data.payload && - isConnectionChangedResponse(params.data.payload) + params.data.action === IFrameMessageAction.LedgerConnectionChange ) { this.isDeviceConnected = params.data.payload.connected; } } } - #sendMessage( - message: IFrameMessage, - callback: (response: IFrameMessageResponse) => void, + #sendMessage( + message: IFrameMessage, + callback: (response: IFrameMessageResponse) => void, ) { this.currentMessageId += 1; - const postMsg: IFramePostMessage = { + const postMsg: IFramePostMessage = { ...message, messageId: this.currentMessageId, target: LEDGER_IFRAME_ID, }; - this.messageCallbacks[this.currentMessageId] = callback; + this.messageCallbacks[this.currentMessageId] = callback as ( + response: IFrameMessageResponse, + ) => void; if (!this.iframeLoaded || !this.iframe || !this.iframe.contentWindow) { throw new Error('The iframe is not loaded yet'); From 4fa5fb49ad043b702368a9313b30bd24e98bbf87 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 4 May 2023 16:25:42 +0100 Subject: [PATCH 18/22] coverage --- jest.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/jest.config.js b/jest.config.js index d79019c5..8ae22185 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,10 +41,10 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 59.25, - functions: 81.94, - lines: 78.54, - statements: 78.49, + branches: 65.09, + functions: 88.57, + lines: 81.57, + statements: 81.49, }, }, From 4f8208aa45de582ca6a98890271a8679c7891bfe Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 4 May 2023 16:34:41 +0100 Subject: [PATCH 19/22] types --- src/ledger-bridge.ts | 34 +++++++++++++++------------------- src/ledger-iframe-bridge.ts | 29 +++++++---------------------- 2 files changed, 22 insertions(+), 41 deletions(-) diff --git a/src/ledger-bridge.ts b/src/ledger-bridge.ts index 754aeb9f..bcee7afe 100644 --- a/src/ledger-bridge.ts +++ b/src/ledger-bridge.ts @@ -1,34 +1,30 @@ +import type LedgerHwAppEth from '@ledgerhq/hw-app-eth'; + export type GetPublicKeyParams = { hdPath: string }; -export type GetPublicKeyResponse = { - publicKey: string; - address: string; - chainCode?: string; +export type GetPublicKeyResponse = Awaited< + ReturnType +> & { + chainCode: string; }; export type LedgerSignTransactionParams = { hdPath: string; tx: string }; -export type LedgerSignTransactionResponse = { - s: string; - v: string; - r: string; -}; +export type LedgerSignTransactionResponse = Awaited< + ReturnType +>; export type LedgerSignMessageParams = { hdPath: string; message: string }; -export type LedgerSignMessageResponse = { - v: number; - s: string; - r: string; -}; +export type LedgerSignMessageResponse = Awaited< + ReturnType +>; export type LedgerSignTypedDataParams = { hdPath: string; domainSeparatorHex: string; hashStructMessageHex: string; }; -export type LedgerSignTypedDataResponse = { - v: number; - s: string; - r: string; -}; +export type LedgerSignTypedDataResponse = Awaited< + ReturnType +>; // eslint-disable-next-line @typescript-eslint/consistent-type-definitions export interface LedgerBridge { diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index b199f778..3228fefa 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -1,5 +1,3 @@ -import type LedgerHwAppEth from '@ledgerhq/hw-app-eth'; - import { GetPublicKeyParams, GetPublicKeyResponse, @@ -30,7 +28,7 @@ type IFrameMessageResponse = { } & ( | { action: IFrameMessageAction.LedgerConnectionChange; - payload: ConnectionChangedPayload; + payload: { connected: boolean }; } | ({ action: IFrameMessageAction.LedgerMakeApp; @@ -42,13 +40,13 @@ type IFrameMessageResponse = { | ({ action: IFrameMessageAction.LedgerUnlock; } & ( - | { success: true; payload: GetAddressPayload } + | { success: true; payload: GetPublicKeyResponse } | { success: false; payload: { error: Error } } )) | ({ action: IFrameMessageAction.LedgerSignTransaction; } & ( - | { success: true; payload: SignTransactionPayload } + | { success: true; payload: LedgerSignTransactionResponse } | { success: false; payload: { error: Error } } )) | ({ @@ -56,27 +54,14 @@ type IFrameMessageResponse = { | IFrameMessageAction.LedgerSignPersonalMessage | IFrameMessageAction.LedgerSignTypedData; } & ( - | { success: true; payload: SignMessagePayload } + | { + success: true; + payload: LedgerSignMessageResponse | LedgerSignTypedDataResponse; + } | { success: false; payload: { error: Error } } )) ); -type GetAddressPayload = Awaited> & { - chainCode: string; -}; - -type SignMessagePayload = Awaited< - ReturnType ->; - -type SignTransactionPayload = Awaited< - ReturnType ->; - -type ConnectionChangedPayload = { - connected: boolean; -}; - type IFrameMessage = { action: TAction; params?: Readonly>; From 06275fad86d6450de60f4b533098cfb2a0ea3a5c Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Tue, 9 May 2023 10:21:49 +0100 Subject: [PATCH 20/22] use signature overloads --- src/ledger-iframe-bridge.ts | 46 +++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 17 deletions(-) diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index 3228fefa..bd4afbe0 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -155,10 +155,7 @@ export class LedgerIframeBridge implements LedgerBridge { async getPublicKey( params: GetPublicKeyParams, ): Promise { - return this.#deviceActionMessage( - IFrameMessageAction.LedgerUnlock, - params, - ) as Promise; + return this.#deviceActionMessage(IFrameMessageAction.LedgerUnlock, params); } async deviceSignTransaction( @@ -167,7 +164,7 @@ export class LedgerIframeBridge implements LedgerBridge { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignTransaction, params, - ) as Promise; + ); } async deviceSignMessage( @@ -176,7 +173,7 @@ export class LedgerIframeBridge implements LedgerBridge { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignPersonalMessage, params, - ) as Promise; + ); } async deviceSignTypedData( @@ -185,20 +182,35 @@ export class LedgerIframeBridge implements LedgerBridge { return this.#deviceActionMessage( IFrameMessageAction.LedgerSignTypedData, params, - ) as Promise; + ); } async #deviceActionMessage( - action: - | IFrameMessageAction.LedgerUnlock - | IFrameMessageAction.LedgerSignTransaction - | IFrameMessageAction.LedgerSignPersonalMessage - | IFrameMessageAction.LedgerSignTypedData, - params: - | GetPublicKeyParams - | LedgerSignTransactionParams - | LedgerSignMessageParams - | LedgerSignTypedDataParams, + action: IFrameMessageAction.LedgerUnlock, + params: GetPublicKeyParams, + ): Promise; + + async #deviceActionMessage( + action: IFrameMessageAction.LedgerSignTransaction, + params: LedgerSignTransactionParams, + ): Promise; + + async #deviceActionMessage( + action: IFrameMessageAction.LedgerSignPersonalMessage, + params: LedgerSignMessageParams, + ): Promise; + + async #deviceActionMessage( + action: IFrameMessageAction.LedgerSignTypedData, + params: LedgerSignTypedDataParams, + ): Promise; + + async #deviceActionMessage( + ...[action, params]: + | [IFrameMessageAction.LedgerUnlock, GetPublicKeyParams] + | [IFrameMessageAction.LedgerSignTransaction, LedgerSignTransactionParams] + | [IFrameMessageAction.LedgerSignPersonalMessage, LedgerSignMessageParams] + | [IFrameMessageAction.LedgerSignTypedData, LedgerSignTypedDataParams] ) { return new Promise((resolve, reject) => { this.#sendMessage( From a4f256e65dbee03fef51789b19adc494450b9f36 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Tue, 9 May 2023 13:11:07 +0100 Subject: [PATCH 21/22] event listener refactor --- src/ledger-iframe-bridge.test.ts | 4 +-- src/ledger-iframe-bridge.ts | 47 +++++++++++++++++++------------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/src/ledger-iframe-bridge.test.ts b/src/ledger-iframe-bridge.test.ts index 46d0ae89..161eaf69 100644 --- a/src/ledger-iframe-bridge.test.ts +++ b/src/ledger-iframe-bridge.test.ts @@ -89,11 +89,9 @@ describe('LedgerIframeBridge', function () { const addEventListenerSpy = jest.spyOn(global.window, 'addEventListener'); - const bridgeUrl = 'bridgeUrl'; - await bridge.init(bridgeUrl); + await bridge.init('bridgeUrl'); expect(addEventListenerSpy).toHaveBeenCalledTimes(1); - expect(bridge.bridgeUrl).toBe(bridgeUrl); expect(bridge.iframeLoaded).toBe(false); await simulateIFrameLoad(bridge.iframe); diff --git a/src/ledger-iframe-bridge.ts b/src/ledger-iframe-bridge.ts index bd4afbe0..a5ffe5f8 100644 --- a/src/ledger-iframe-bridge.ts +++ b/src/ledger-iframe-bridge.ts @@ -78,7 +78,10 @@ export class LedgerIframeBridge implements LedgerBridge { iframeLoaded = false; - bridgeUrl = ''; + eventListener?: (eventMessage: { + origin: string; + data: IFrameMessageResponse; + }) => void; isDeviceConnected = false; @@ -96,15 +99,17 @@ export class LedgerIframeBridge implements LedgerBridge { }; async init(bridgeUrl: string) { - this.bridgeUrl = bridgeUrl; + this.#setupIframe(bridgeUrl); - this.#setupIframe(); + this.eventListener = this.#eventListener.bind(this, bridgeUrl); - window.addEventListener('message', this.#eventListener.bind(this)); + window.addEventListener('message', this.eventListener); } async destroy() { - window.removeEventListener('message', this.#eventListener.bind(this)); + if (this.eventListener) { + window.removeEventListener('message', this.eventListener); + } } async attemptMakeApp(): Promise { @@ -228,9 +233,9 @@ export class LedgerIframeBridge implements LedgerBridge { }); } - #setupIframe() { + #setupIframe(bridgeUrl: string) { this.iframe = document.createElement('iframe'); - this.iframe.src = this.bridgeUrl; + this.iframe.src = bridgeUrl; this.iframe.allow = `hid 'src'`; this.iframe.onload = async () => { // If the ledger live preference was set before the iframe is loaded, @@ -252,28 +257,32 @@ export class LedgerIframeBridge implements LedgerBridge { document.head.appendChild(this.iframe); } - #getOrigin() { - const tmp = this.bridgeUrl.split('/'); + #getOrigin(bridgeUrl: string) { + const tmp = bridgeUrl.split('/'); tmp.splice(-1, 1); return tmp.join('/'); } - #eventListener(params: { - origin: string; - data: IFrameMessageResponse; - }) { - if (params.origin !== this.#getOrigin()) { + #eventListener( + bridgeUrl: string, + eventMessage: { + origin: string; + data: IFrameMessageResponse; + }, + ) { + if (eventMessage.origin !== this.#getOrigin(bridgeUrl)) { return; } - if (params.data) { - const messageCallback = this.messageCallbacks[params.data.messageId]; + if (eventMessage.data) { + const messageCallback = + this.messageCallbacks[eventMessage.data.messageId]; if (messageCallback) { - messageCallback(params.data); + messageCallback(eventMessage.data); } else if ( - params.data.action === IFrameMessageAction.LedgerConnectionChange + eventMessage.data.action === IFrameMessageAction.LedgerConnectionChange ) { - this.isDeviceConnected = params.data.payload.connected; + this.isDeviceConnected = eventMessage.data.payload.connected; } } } From c9e7d0d4236ef4c5dbecfb10fa9aadc01a5b8241 Mon Sep 17 00:00:00 2001 From: Bernardo Garces Chapero Date: Thu, 11 May 2023 08:56:56 +0100 Subject: [PATCH 22/22] bump coverage --- jest.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jest.config.js b/jest.config.js index 8ae22185..178632c7 100644 --- a/jest.config.js +++ b/jest.config.js @@ -41,7 +41,7 @@ module.exports = { // An object that configures minimum threshold enforcement for coverage results coverageThreshold: { global: { - branches: 65.09, + branches: 65.42, functions: 88.57, lines: 81.57, statements: 81.49,