From 51530c252621c9cf7acf08dcf9b9103c85242320 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Fri, 7 Feb 2020 16:43:19 -0800 Subject: [PATCH] Convert inpage provider to Class (#17) * convert to class * move eslint-config to dev deps * 4.1.0 --- .eslintignore | 1 - .eslintrc | 146 --------- .eslintrc.js | 30 ++ index.js | 718 ++++++++++++++++++++++---------------------- package.json | 7 +- src/siteMetadata.js | 6 +- src/utils.js | 9 +- test/utils.js | 8 +- yarn.lock | 41 ++- 9 files changed, 439 insertions(+), 527 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc create mode 100644 .eslintrc.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 3c3629e6..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index ede0931b..00000000 --- a/.eslintrc +++ /dev/null @@ -1,146 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2018 - }, - - "env": { - "es6": true, - "node": true, - "browser": true - }, - - "plugins": [ - "json" - ], - - "globals": { - "document": false, - "navigator": false, - "web3": true, - "window": false, - "$": false, - "QUnit": false - }, - - "rules": { - "no-restricted-globals": ["error", "event"], - "accessor-pairs": 2, - "arrow-spacing": [2, { "before": true, "after": true }], - "block-spacing": [2, "always"], - "brace-style": [2, "1tbs", { "allowSingleLine": true }], - "camelcase": [2, { "properties": "never" }], - "comma-dangle": [2, "always-multiline"], - "comma-spacing": [2, { "before": false, "after": true }], - "comma-style": [2, "last"], - "constructor-super": 2, - "curly": [2, "multi-line"], - "dot-location": [2, "property"], - "eol-last": 2, - "eqeqeq": [2, "allow-null"], - "generator-star-spacing": [2, { "before": true, "after": true }], - "handle-callback-err": [2, "^(err|error)$" ], - "indent": [2, 2,{ "SwitchCase": 1, "MemberExpression": 0 }], - "jsx-quotes": [2, "prefer-double"], - "key-spacing": 2, - "keyword-spacing": [2, { "before": true, "after": true }], - "new-cap": [2, { "newIsCap": true, "capIsNew": false }], - "new-parens": 2, - "no-array-constructor": 2, - "no-caller": 2, - "no-class-assign": 2, - "no-cond-assign": 2, - "no-const-assign": 2, - "no-control-regex": 2, - "no-debugger": 2, - "no-delete-var": 2, - "no-dupe-args": 2, - "no-dupe-class-members": 2, - "no-dupe-keys": 2, - "no-duplicate-case": 2, - "no-duplicate-imports": 2, - "no-empty-character-class": 2, - "no-empty-pattern": 2, - "no-eval": 2, - "no-ex-assign": 2, - "no-extend-native": 2, - "no-extra-bind": 2, - "no-extra-boolean-cast": 2, - "no-extra-parens": [2, "functions"], - "no-fallthrough": 2, - "no-floating-decimal": 2, - "no-func-assign": 2, - "no-implied-eval": 2, - "no-inner-declarations": [2, "functions"], - "no-invalid-regexp": 2, - "no-irregular-whitespace": 2, - "no-iterator": 2, - "no-label-var": 2, - "no-labels": [2, { "allowLoop": false, "allowSwitch": false }], - "no-lone-blocks": 2, - "no-mixed-spaces-and-tabs": 2, - "no-multi-spaces": 2, - "no-multi-str": 2, - "no-multiple-empty-lines": [2, { "max": 2 }], - "no-native-reassign": 2, - "no-negated-in-lhs": 2, - "no-new": 2, - "no-new-func": 2, - "no-new-object": 2, - "no-new-require": 2, - "no-new-symbol": 2, - "no-new-wrappers": 2, - "no-obj-calls": 2, - "no-octal": 2, - "no-octal-escape": 2, - "no-path-concat": 2, - "no-proto": 2, - "no-redeclare": 2, - "no-regex-spaces": 2, - "no-return-assign": [2, "except-parens"], - "no-self-assign": 2, - "no-self-compare": 2, - "no-sequences": 2, - "no-shadow-restricted-names": 2, - "no-spaced-func": 2, - "no-sparse-arrays": 2, - "no-this-before-super": 2, - "no-throw-literal": 2, - "no-trailing-spaces": 2, - "no-undef": 2, - "no-undef-init": 2, - "no-unexpected-multiline": 2, - "no-unmodified-loop-condition": 2, - "no-unneeded-ternary": [2, { "defaultAssignment": false }], - "no-unreachable": 2, - "no-unsafe-finally": 2, - "no-unused-expressions": ["error", { "allowShortCircuit" : true, "allowTernary": true }], - "no-unused-vars": [2, { "vars": "all", "args": "all", "argsIgnorePattern": "[_]+" }], - "no-use-before-define": [2, { "functions": false }], - "no-useless-call": 2, - "no-useless-computed-key": 2, - "no-useless-constructor": 2, - "no-useless-escape": 2, - "no-whitespace-before-property": 2, - "no-with": 2, - "one-var": [2, { "initialized": "never" }], - "operator-linebreak": [2, "after", { "overrides": { "?": "ignore", ":": "ignore" } }], - "padded-blocks": "off", - "quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}], - "semi": [2, "never"], - "semi-spacing": [2, { "before": false, "after": true }], - "space-before-blocks": [2, "always"], - "space-before-function-paren": [2, "always"], - "space-in-parens": [2, "never"], - "space-infix-ops": 2, - "space-unary-ops": [2, { "words": true, "nonwords": false }], - "spaced-comment": [2, "always", { "markers": ["global", "globals", "eslint", "eslint-disable", "*package", "!", ","], "exceptions": ["=", "-"] } ], - "strict": 0, - "template-curly-spacing": [2, "never"], - "use-isnan": 2, - "valid-typeof": 2, - "wrap-iife": [2, "any"], - "yield-star-spacing": [2, "both"], - "yoda": [2, "never"], - "prefer-const": 2 - } -} diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..f93e0504 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,30 @@ +module.exports = { + env: { + browser: true, + commonjs: true, + es6: true, + }, + extends: [ + '@metamask/eslint-config', + '@metamask/eslint-config/config/nodejs', + ], + plugins: [ + 'json', + ], + globals: { + document: false, + navigator: false, + web3: true, + window: false, + $: false, + QUnit: false, + }, + parserOptions: { + ecmaVersion: 2018, + }, + rules: {}, + ignorePatterns: [ + '!.eslintrc.js', + 'node_modules/', + ], +} diff --git a/index.js b/index.js index 45e97c1d..2a2a366f 100644 --- a/index.js +++ b/index.js @@ -5,7 +5,6 @@ const createJsonRpcStream = require('json-rpc-middleware-stream') const ObservableStore = require('obs-store') const asStream = require('obs-store/lib/asStream') const ObjectMultiplex = require('obj-multiplex') -const { inherits } = require('util') const SafeEventEmitter = require('safe-event-emitter') const dequal = require('fast-deep-equal') const { ethErrors } = require('eth-json-rpc-errors') @@ -28,434 +27,441 @@ const getRpcPromiseCallback = (resolve, reject) => (error, response) => { : resolve(response.result) } -module.exports = MetamaskInpageProvider +module.exports = class MetamaskInpageProvider extends SafeEventEmitter { -inherits(MetamaskInpageProvider, SafeEventEmitter) + constructor (connectionStream, shouldSendMetadata = true) { -function MetamaskInpageProvider (connectionStream, shouldSendMetadata = true) { + super() - // super constructor - SafeEventEmitter.call(this) + this.isMetaMask = true - // private state, kept here in part for use in the _metamask proxy - this._state = { - sentWarnings: { - enable: false, - experimentalMethods: false, - isConnected: false, - sendAsync: false, - // TODO:deprecate:2020-Q1 - autoReload: false, - sendSync: false, - }, - isConnected: undefined, - accounts: undefined, - isUnlocked: undefined, - } + // private state, kept here in part for use in the _metamask proxy + this._state = { + sentWarnings: { + enable: false, + experimentalMethods: false, + isConnected: false, + sendAsync: false, + // TODO:deprecate:2020-Q1 + autoReload: false, + sendSync: false, + }, + isConnected: undefined, + accounts: undefined, + isUnlocked: undefined, + } - this._metamask = getExperimentalApi(this) + this._metamask = getExperimentalApi(this) + + // public state + this.selectedAddress = null + this.networkVersion = undefined + this.chainId = undefined + + // bind functions (to prevent e.g. web3@1.x from making unbound calls) + this._handleAccountsChanged = this._handleAccountsChanged.bind(this) + this._handleDisconnect = this._handleDisconnect.bind(this) + this._sendAsync = this._sendAsync.bind(this) + this._sendSync = this._sendSync.bind(this) + this.enable = this.enable.bind(this) + this.send = this.send.bind(this) + this.sendAsync = this.sendAsync.bind(this) + + // setup connectionStream multiplexing + const mux = this.mux = new ObjectMultiplex() + pump( + connectionStream, + mux, + connectionStream, + this._handleDisconnect.bind(this, 'MetaMask'), + ) - // public state - this.selectedAddress = null - this.networkVersion = undefined - this.chainId = undefined + // subscribe to metamask public config (one-way) + this._publicConfigStore = new ObservableStore({ storageKey: 'MetaMask-Config' }) - // setup connectionStream multiplexing - const mux = this.mux = new ObjectMultiplex() - pump( - connectionStream, - mux, - connectionStream, - this._handleDisconnect.bind(this, 'MetaMask'), - ) + // handle isUnlocked changes, and chainChanged and networkChanged events + this._publicConfigStore.subscribe(state => { - // subscribe to metamask public config (one-way) - this._publicConfigStore = new ObservableStore({ storageKey: 'MetaMask-Config' }) - - // handle isUnlocked changes, and chainChanged and networkChanged events - this._publicConfigStore.subscribe(state => { - - if ('isUnlocked' in state && state.isUnlocked !== this._state.isUnlocked) { - this._state.isUnlocked = state.isUnlocked - if (!this._state.isUnlocked) { - // accounts are never exposed when the extension is locked - this._handleAccountsChanged([]) - } else { - // this will get the exposed accounts, if any - try { - this._sendAsync( - { method: 'eth_accounts', params: [] }, - () => {}, - true, // indicating that eth_accounts _should_ update accounts - ) - } catch (_) {} + if ('isUnlocked' in state && state.isUnlocked !== this._state.isUnlocked) { + this._state.isUnlocked = state.isUnlocked + if (!this._state.isUnlocked) { + // accounts are never exposed when the extension is locked + this._handleAccountsChanged([]) + } else { + // this will get the exposed accounts, if any + try { + this._sendAsync( + { method: 'eth_accounts', params: [] }, + () => {}, + true, // indicating that eth_accounts _should_ update accounts + ) + } catch (_) {} + } } - } - // Emit chainChanged event on chain change - if ('chainId' in state && state.chainId !== this.chainId) { - this.chainId = state.chainId - this.emit('chainChanged', this.chainId) - this.emit('chainIdChanged', this.chainId) // TODO:deprecate:2020-Q1 - } + // Emit chainChanged event on chain change + if ('chainId' in state && state.chainId !== this.chainId) { + this.chainId = state.chainId + this.emit('chainChanged', this.chainId) + this.emit('chainIdChanged', this.chainId) // TODO:deprecate:2020-Q1 + } - // Emit networkChanged event on network change - if ('networkVersion' in state && state.networkVersion !== this.networkVersion) { - this.networkVersion = state.networkVersion - this.emit('networkChanged', this.networkVersion) - } - }) + // Emit networkChanged event on network change + if ('networkVersion' in state && state.networkVersion !== this.networkVersion) { + this.networkVersion = state.networkVersion + this.emit('networkChanged', this.networkVersion) + } + }) - pump( - mux.createStream('publicConfig'), - asStream(this._publicConfigStore), - // RPC requests should still work if only this stream fails - logStreamDisconnectWarning.bind(this, 'MetaMask PublicConfigStore'), - ) + pump( + mux.createStream('publicConfig'), + asStream(this._publicConfigStore), + // RPC requests should still work if only this stream fails + logStreamDisconnectWarning.bind(this, 'MetaMask PublicConfigStore'), + ) - // ignore phishing warning message (handled elsewhere) - mux.ignoreStream('phishing') + // ignore phishing warning message (handled elsewhere) + mux.ignoreStream('phishing') - // setup own event listeners + // setup own event listeners - // EIP-1193 connect - this.on('connect', () => { - this._state.isConnected = true - }) + // EIP-1193 connect + this.on('connect', () => { + this._state.isConnected = true + }) - // connect to async provider + // connect to async provider - const jsonRpcConnection = createJsonRpcStream() - pump( - jsonRpcConnection.stream, - mux.createStream('provider'), - jsonRpcConnection.stream, - this._handleDisconnect.bind(this, 'MetaMask RpcProvider'), - ) + const jsonRpcConnection = createJsonRpcStream() + pump( + jsonRpcConnection.stream, + mux.createStream('provider'), + jsonRpcConnection.stream, + this._handleDisconnect.bind(this, 'MetaMask RpcProvider'), + ) - // handle RPC requests via dapp-side rpc engine - const rpcEngine = new RpcEngine() - rpcEngine.push(createIdRemapMiddleware()) - rpcEngine.push(createErrorMiddleware()) - rpcEngine.push(jsonRpcConnection.middleware) - this._rpcEngine = rpcEngine - - // json rpc notification listener - jsonRpcConnection.events.on('notification', payload => { - if (payload.method === 'wallet_accountsChanged') { - this._handleAccountsChanged(payload.result) - } else if (payload.method === 'eth_subscription') { - // EIP 1193 subscriptions, per eth-json-rpc-filters/subscriptionManager - this.emit('notification', payload.params.result) - } - }) + // handle RPC requests via dapp-side rpc engine + const rpcEngine = new RpcEngine() + rpcEngine.push(createIdRemapMiddleware()) + rpcEngine.push(createErrorMiddleware()) + rpcEngine.push(jsonRpcConnection.middleware) + this._rpcEngine = rpcEngine + + // json rpc notification listener + jsonRpcConnection.events.on('notification', payload => { + if (payload.method === 'wallet_accountsChanged') { + this._handleAccountsChanged(payload.result) + } else if (payload.method === 'eth_subscription') { + // EIP 1193 subscriptions, per eth-json-rpc-filters/subscriptionManager + this.emit('notification', payload.params.result) + } + }) - // send website metadata - if (shouldSendMetadata) { - const domContentLoadedHandler = () => { - sendSiteMetadata(this._rpcEngine) - window.removeEventListener('DOMContentLoaded', domContentLoadedHandler) + // send website metadata + if (shouldSendMetadata) { + const domContentLoadedHandler = () => { + sendSiteMetadata(this._rpcEngine) + window.removeEventListener('DOMContentLoaded', domContentLoadedHandler) + } + window.addEventListener('DOMContentLoaded', domContentLoadedHandler) } - window.addEventListener('DOMContentLoaded', domContentLoadedHandler) - } - // indicate that we've connected, for EIP-1193 compliance - setTimeout(() => this.emit('connect')) - - // TODO:deprecate:2020-Q1 - // wait a second to attempt to send this, so that the warning can be silenced - // moved this here because there's another warning in .enable() discouraging - // the use thereof per EIP 1102 - setTimeout(() => { - if (this.autoRefreshOnNetworkChange && !this._state.sentWarnings.autoReload) { - log.warn(messages.warnings.autoReloadDeprecation) - this._state.sentWarnings.autoReload = true - } - }, 1000) -} + // indicate that we've connected, for EIP-1193 compliance + setTimeout(() => this.emit('connect')) -// TODO:deprecate:2020-Q1 -MetamaskInpageProvider.prototype._web3Ref = undefined + // TODO:deprecate:2020-Q1 + this._web3Ref = undefined -// TODO:deprecate:2020-Q1 -// give the dapps control of a refresh they can toggle this off on the window.ethereum -// this will be default true so it does not break any old apps. -MetamaskInpageProvider.prototype.autoRefreshOnNetworkChange = true + // TODO:deprecate:2020-Q1 + // give the dapps control of a refresh they can toggle this off on the window.ethereum + // this will be default true so it does not break any old apps. + this.autoRefreshOnNetworkChange = true -MetamaskInpageProvider.prototype.isMetaMask = true + // TODO:deprecate:2020-Q1 + // wait a second to attempt to send this, so that the warning can be silenced + // moved this here because there's another warning in .enable() discouraging + // the use thereof per EIP 1102 + setTimeout(() => { + if (this.autoRefreshOnNetworkChange && !this._state.sentWarnings.autoReload) { + log.warn(messages.warnings.autoReloadDeprecation) + this._state.sentWarnings.autoReload = true + } + }, 1000) + } -/** - * Deprecated. - * Returns whether the inpage provider is connected to MetaMask. - */ -MetamaskInpageProvider.prototype.isConnected = function () { + /** + * Deprecated. + * Returns whether the inpage provider is connected to MetaMask. + */ + isConnected () { - if (!this._state.sentWarnings.isConnected) { - log.warn(messages.warnings.isConnectedDeprecation) - this._state.sentWarnings.isConnected = true + if (!this._state.sentWarnings.isConnected) { + log.warn(messages.warnings.isConnectedDeprecation) + this._state.sentWarnings.isConnected = true + } + return this._state.isConnected } - return this._state.isConnected -} -/** - * Sends an RPC request to MetaMask. Resolves to the result of the method call. - * May reject with an error that must be caught by the caller. - * - * @param {(string|Object)} methodOrPayload - The method name, or the RPC request object. - * @param {Array} [params] - If given a method name, the method's parameters. - * @returns {Promise} - A promise resolving to the result of the method call. - */ -MetamaskInpageProvider.prototype.send = function (methodOrPayload, params) { + /** + * Sends an RPC request to MetaMask. Resolves to the result of the method call. + * May reject with an error that must be caught by the caller. + * + * @param {(string|Object)} methodOrPayload - The method name, or the RPC request object. + * @param {Array} [params] - If given a method name, the method's parameters. + * @returns {Promise} - A promise resolving to the result of the method call. + */ + send (methodOrPayload, params) { + + // preserve original params for later error if necessary + const _params = params + + // construct payload object + let payload + if ( + typeof methodOrPayload === 'object' && + !Array.isArray(methodOrPayload) + ) { - // preserve original params for later error if necessary - const _params = params + // TODO:deprecate:2020-Q1 + // handle send(object, callback), an alias for sendAsync(object, callback) + if (typeof params === 'function') { + return this._sendAsync(methodOrPayload, params) + } - // construct payload object - let payload - if ( - typeof methodOrPayload === 'object' && - !Array.isArray(methodOrPayload) - ) { + payload = methodOrPayload - // TODO:deprecate:2020-Q1 - // handle send(object, callback), an alias for sendAsync(object, callback) - if (typeof params === 'function') { - return this._sendAsync(methodOrPayload, params) - } + // TODO:deprecate:2020-Q1 + // backwards compatibility: "synchronous" methods + if (!params && [ + 'eth_accounts', + 'eth_coinbase', + 'eth_uninstallFilter', + 'net_version', + ].includes(payload.method)) { + return this._sendSync(payload) + } + } else if ( + typeof methodOrPayload === 'string' && + typeof params !== 'function' + ) { - payload = methodOrPayload + // wrap params in array out of kindness + // params have to be an array per EIP 1193, even though JSON RPC + // allows objects + if (params === undefined) { + params = [] + } else if (!Array.isArray(params)) { + params = [params] + } - // TODO:deprecate:2020-Q1 - // backwards compatibility: "synchronous" methods - if (!params && [ - 'eth_accounts', - 'eth_coinbase', - 'eth_uninstallFilter', - 'net_version', - ].includes(payload.method)) { - return this._sendSync(payload) - } - } else if ( - typeof methodOrPayload === 'string' && - typeof params !== 'function' - ) { - - // wrap params in array out of kindness - // params have to be an array per EIP 1193, even though JSON RPC - // allows objects - if (params === undefined) { - params = [] - } else if (!Array.isArray(params)) { - params = [params] + payload = { + method: methodOrPayload, + params, + } } - payload = { - method: methodOrPayload, - params, + // typecheck payload and payload.params + if ( + typeof payload !== 'object' || + Array.isArray(payload) || + !Array.isArray(params) + ) { + throw ethErrors.rpc.invalidRequest({ + message: messages.errors.invalidParams(), + data: [methodOrPayload, _params], + }) } - } - // typecheck payload and payload.params - if ( - typeof payload !== 'object' || - Array.isArray(payload) || - !Array.isArray(params) - ) { - throw ethErrors.rpc.invalidRequest({ - message: messages.errors.invalidParams(), - data: [methodOrPayload, _params], + return new Promise((resolve, reject) => { + try { + this._sendAsync( + payload, + getRpcPromiseCallback(resolve, reject), + ) + } catch (error) { + reject(error) + } }) } - return new Promise((resolve, reject) => { - try { - this._sendAsync( - payload, - getRpcPromiseCallback(resolve, reject), - ) - } catch (error) { - reject(error) + /** + * Deprecated. + * Equivalent to: ethereum.send('eth_requestAccounts') + * + * @returns {Promise>} - A promise that resolves to an array of addresses. + */ + enable () { + + if (!this._state.sentWarnings.enable) { + log.warn(messages.warnings.enableDeprecation) + this._state.sentWarnings.enable = true } - }) -} - -/** - * Deprecated. - * Equivalent to: ethereum.send('eth_requestAccounts') - * - * @returns {Promise>} - A promise that resolves to an array of addresses. - */ -MetamaskInpageProvider.prototype.enable = function () { - - if (!this._state.sentWarnings.enable) { - log.warn(messages.warnings.enableDeprecation) - this._state.sentWarnings.enable = true + return new Promise((resolve, reject) => { + try { + this._sendAsync( + { method: 'eth_requestAccounts', params: [] }, + getRpcPromiseCallback(resolve, reject), + ) + } catch (error) { + reject(error) + } + }) } - return new Promise((resolve, reject) => { - try { - this._sendAsync( - { method: 'eth_requestAccounts', params: [] }, - getRpcPromiseCallback(resolve, reject), - ) - } catch (error) { - reject(error) - } - }) -} - -/** - * Deprecated. - * Backwards compatibility. ethereum.send() with callback. - * - * @param {Object} payload - The RPC request object. - * @param {Function} callback - The callback function. - */ -MetamaskInpageProvider.prototype.sendAsync = function (payload, cb) { - if (!this._state.sentWarnings.sendAsync) { - log.warn(messages.warnings.sendAsyncDeprecation) - this._state.sentWarnings.sendAsync = true + /** + * Deprecated. + * Backwards compatibility. ethereum.send() with callback. + * + * @param {Object} payload - The RPC request object. + * @param {Function} callback - The callback function. + */ + sendAsync (payload, cb) { + + if (!this._state.sentWarnings.sendAsync) { + log.warn(messages.warnings.sendAsyncDeprecation) + this._state.sentWarnings.sendAsync = true + } + this._sendAsync(payload, cb) } - this._sendAsync(payload, cb) -} -/** - * TODO:deprecate:2020-Q1 - * Internal backwards compatibility method. - */ -MetamaskInpageProvider.prototype._sendSync = function (payload) { + /** + * TODO:deprecate:2020-Q1 + * Internal backwards compatibility method. + */ + _sendSync (payload) { - if (!this._state.sentWarnings.sendSync) { - log.warn(messages.warnings.sendSyncDeprecation) - this._state.sentWarnings.sendSync = true - } - - let result - switch (payload.method) { + if (!this._state.sentWarnings.sendSync) { + log.warn(messages.warnings.sendSyncDeprecation) + this._state.sentWarnings.sendSync = true + } - case 'eth_accounts': - result = this.selectedAddress ? [this.selectedAddress] : [] - break + let result + switch (payload.method) { - case 'eth_coinbase': - result = this.selectedAddress || null - break + case 'eth_accounts': + result = this.selectedAddress ? [this.selectedAddress] : [] + break - case 'eth_uninstallFilter': - this._sendAsync(payload, () => {}) - result = true - break + case 'eth_coinbase': + result = this.selectedAddress || null + break - case 'net_version': - result = this.networkVersion || null - break + case 'eth_uninstallFilter': + this._sendAsync(payload, () => {}) + result = true + break - default: - throw new Error(messages.errors.unsupportedSync(payload.method)) - } + case 'net_version': + result = this.networkVersion || null + break - // looks like a plain object, but behaves like a Promise if someone calls .then on it :evil_laugh: - return makeThenable({ - id: payload.id, - jsonrpc: payload.jsonrpc, - result, - }, 'result') -} + default: + throw new Error(messages.errors.unsupportedSync(payload.method)) + } -/** - * Internal RPC method. Forwards requests to background via the RPC engine. - * Also remap ids inbound and outbound. - * - * @param {Object} payload - The RPC request object. - * @param {Function} userCallback - The caller's callback. - * @param {boolean} isInternal - Whether the request is internal. - */ -MetamaskInpageProvider.prototype._sendAsync = function (payload, userCallback, isInternal = false) { + // looks like a plain object, but behaves like a Promise if someone calls .then on it :evil_laugh: + return makeThenable({ + id: payload.id, + jsonrpc: payload.jsonrpc, + result, + }, 'result') + } - let cb = userCallback + /** + * Internal RPC method. Forwards requests to background via the RPC engine. + * Also remap ids inbound and outbound. + * + * @param {Object} payload - The RPC request object. + * @param {Function} userCallback - The caller's callback. + * @param {boolean} isInternal - Whether the request is internal. + */ + _sendAsync (payload, userCallback, isInternal = false) { - if (!Array.isArray(payload)) { + let cb = userCallback - if (!payload.jsonrpc) { - payload.jsonrpc = '2.0' - } + if (!Array.isArray(payload)) { - if ( - payload.method === 'eth_accounts' || - payload.method === 'eth_requestAccounts' - ) { + if (!payload.jsonrpc) { + payload.jsonrpc = '2.0' + } - // handle accounts changing - cb = (err, res) => { - this._handleAccountsChanged( - res.result || [], - payload.method === 'eth_accounts', - isInternal, - ) - userCallback(err, res) + if ( + payload.method === 'eth_accounts' || + payload.method === 'eth_requestAccounts' + ) { + + // handle accounts changing + cb = (err, res) => { + this._handleAccountsChanged( + res.result || [], + payload.method === 'eth_accounts', + isInternal, + ) + userCallback(err, res) + } } } - } - this._rpcEngine.handle(payload, cb) -} - -/** - * Called when connection is lost to critical streams. - */ -MetamaskInpageProvider.prototype._handleDisconnect = function (streamName, err) { - - logStreamDisconnectWarning.bind(this)(streamName, err) - if (this._state.isConnected) { - this.emit('close', { - code: 1011, - reason: 'MetaMask background communication error.', - }) + this._rpcEngine.handle(payload, cb) } - this._state.isConnected = false -} -/** - * Called when accounts may have changed. - */ -MetamaskInpageProvider.prototype._handleAccountsChanged = function (accounts, isEthAccounts = false, isInternal = false) { - - // defensive programming - if (!Array.isArray(accounts)) { - log.error( - 'MetaMask: Received non-array accounts parameter. Please report this bug.', - accounts, - ) - accounts = [] + /** + * Called when connection is lost to critical streams. + */ + _handleDisconnect (streamName, err) { + + logStreamDisconnectWarning.bind(this)(streamName, err) + if (this._state.isConnected) { + this.emit('close', { + code: 1011, + reason: 'MetaMask background communication error.', + }) + } + this._state.isConnected = false } - // emit accountsChanged if anything about the accounts array has changed - if (!dequal(this._state.accounts, accounts)) { + /** + * Called when accounts may have changed. + */ + _handleAccountsChanged (accounts, isEthAccounts = false, isInternal = false) { - // we should always have the correct accounts even before eth_accounts - // returns, except in cases where isInternal is true - if (isEthAccounts && !isInternal) { + // defensive programming + if (!Array.isArray(accounts)) { log.error( - `MetaMask: 'eth_accounts' unexpectedly updated accounts. Please report this bug.`, + 'MetaMask: Received non-array accounts parameter. Please report this bug.', accounts, ) + accounts = [] } - this.emit('accountsChanged', accounts) - this._state.accounts = accounts - } + // emit accountsChanged if anything about the accounts array has changed + if (!dequal(this._state.accounts, accounts)) { - // handle selectedAddress - if (this.selectedAddress !== accounts[0]) { - this.selectedAddress = accounts[0] || null - } + // we should always have the correct accounts even before eth_accounts + // returns, except in cases where isInternal is true + if (isEthAccounts && !isInternal) { + log.error( + `MetaMask: 'eth_accounts' unexpectedly updated accounts. Please report this bug.`, + accounts, + ) + } + + this.emit('accountsChanged', accounts) + this._state.accounts = accounts + } - // TODO:deprecate:2020-Q1 - // handle web3 - if (this._web3Ref) { - this._web3Ref.defaultAccount = this.selectedAddress - } else if (window.web3 && typeof window.web3.eth === 'object') { - window.web3.eth.defaultAccount = this.selectedAddress + // handle selectedAddress + if (this.selectedAddress !== accounts[0]) { + this.selectedAddress = accounts[0] || null + } + + // TODO:deprecate:2020-Q1 + // handle web3 + if (this._web3Ref) { + this._web3Ref.defaultAccount = this.selectedAddress + } else if (window.web3 && typeof window.web3.eth === 'object') { + window.web3.eth.defaultAccount = this.selectedAddress + } } } diff --git a/package.json b/package.json index bcb5ca21..4ccd4b7d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamask-inpage-provider", - "version": "4.0.4", + "version": "4.1.0", "description": "An ethereum provider that connects over a WebExtension port.", "main": "index.js", "scripts": { @@ -24,7 +24,7 @@ }, "homepage": "https://github.com/MetaMask/metamask-inpage-provider#readme", "dependencies": { - "eth-json-rpc-errors": "^2.0.0", + "eth-json-rpc-errors": "^2.0.1", "fast-deep-equal": "^2.0.1", "json-rpc-engine": "^5.1.5", "json-rpc-middleware-stream": "^2.1.1", @@ -35,7 +35,8 @@ "safe-event-emitter": "^1.0.1" }, "devDependencies": { - "eslint": "^6.6.0", + "@metamask/eslint-config": "^1.0.0", + "eslint": "^6.8.0", "eslint-plugin-json": "^2.0.1", "tape": "^4.11.0" } diff --git a/src/siteMetadata.js b/src/siteMetadata.js index 01568142..5a81e7ac 100644 --- a/src/siteMetadata.js +++ b/src/siteMetadata.js @@ -75,7 +75,7 @@ async function getSiteIcon (window) { // Search through available icons in no particular order icon = Array.from(document.querySelectorAll('head > link[rel="icon"]')) - .find((icon) => Boolean(icon.href)) + .find((icon) => Boolean(icon.href)) if (icon && await resourceExists(icon.href)) { return icon.href } @@ -89,6 +89,6 @@ async function getSiteIcon (window) { */ function resourceExists (url) { return fetch(url, { method: 'HEAD', mode: 'same-origin' }) - .then(res => res.status === 200) - .catch(_ => false) + .then(res => res.status === 200) + .catch(_ => false) } diff --git a/src/utils.js b/src/utils.js index dce5c003..350f98d3 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,4 +1,3 @@ - const log = require('loglevel') const { ethErrors, serializeError } = require('eth-json-rpc-errors') const EventEmitter = require('events') @@ -48,7 +47,9 @@ function createErrorMiddleware () { */ function logStreamDisconnectWarning (remoteLabel, err) { let warningMsg = `MetamaskInpageProvider - lost connection to ${remoteLabel}` - if (err) warningMsg += '\n' + err.stack + if (err) { + warningMsg += '\n' + err.stack + } log.warn(warningMsg) if (this instanceof EventEmitter || this instanceof SafeEventEmitter) { if (this.listenerCount('error') > 0) { @@ -71,7 +72,9 @@ function logStreamDisconnectWarning (remoteLabel, err) { function makeThenable (obj, prop) { // don't do anything to Promises - if (obj instanceof Promise) return obj + if (obj instanceof Promise) { + return obj + } const defineOpts = { configurable: true, writable: true, enumerable: false, diff --git a/test/utils.js b/test/utils.js index 86ad06f2..541423a0 100644 --- a/test/utils.js +++ b/test/utils.js @@ -61,10 +61,10 @@ test('makeThenable objects are Promise ducks', async t => { results.res2then1 = res return res }) - .then(res => { - results.res2then2 = res - return res - }) + .then(res => { + results.res2then2 = res + return res + }) results.chainRes = chainRes results.asyncRes = await func({ ...responseObject }) diff --git a/yarn.lock b/yarn.lock index 2514e207..9dd54626 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,11 @@ esutils "^2.0.2" js-tokens "^4.0.0" +"@metamask/eslint-config@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@metamask/eslint-config/-/eslint-config-1.0.0.tgz#046012398bb27f56395355c96ef07152925043b7" + integrity sha512-MmxM2sknVhIHyXCjR6LcK57OPJ30gTEX5v/jwC+qXuw4GIgUAPbxFp3AFmFRAJwty3RMjJSbRJ7YlamMq67U8w== + acorn-jsx@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" @@ -269,10 +274,10 @@ eslint-visitor-keys@^1.1.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz#e2a82cea84ff246ad6fb57f9bde5b46621459ec2" integrity sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A== -eslint@^6.6.0: - version "6.6.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.6.0.tgz#4a01a2fb48d32aacef5530ee9c5a78f11a8afd04" - integrity sha512-PpEBq7b6qY/qrOmpYQ/jTMDYfuQMELR4g4WI1M/NaSDDD/bdcMb+dj4Hgks7p41kW2caXsPsEZAEAyAgjVVC0g== +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -289,7 +294,7 @@ eslint@^6.6.0: file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" - globals "^11.7.0" + globals "^12.1.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" @@ -302,7 +307,7 @@ eslint@^6.6.0: minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.2" + optionator "^0.8.3" progress "^2.0.0" regexpp "^2.0.1" semver "^6.1.2" @@ -357,6 +362,13 @@ eth-json-rpc-errors@^2.0.0: dependencies: fast-safe-stringify "^2.0.6" +eth-json-rpc-errors@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/eth-json-rpc-errors/-/eth-json-rpc-errors-2.0.1.tgz#e7a4c4e3c76913dff26dbc021966c72b2822e0f2" + integrity sha512-ldF9fdzkdHAgTWmqh/bgHi7uH+8icAyjcEdFOBGD32zTWd7J66VDo0rBaiaQPowXitiyAcs1R23Mje1gkdIofA== + dependencies: + fast-safe-stringify "^2.0.6" + events@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" @@ -460,10 +472,12 @@ glob@^7.1.3, glob@~7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -globals@^11.7.0: - version "11.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" - integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^12.1.0: + version "12.3.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13" + integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw== + dependencies: + type-fest "^0.8.1" has-flag@^3.0.0: version "3.0.0" @@ -761,7 +775,7 @@ onetime@^5.1.0: dependencies: mimic-fn "^2.1.0" -optionator@^0.8.2: +optionator@^0.8.3: version "0.8.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== @@ -1112,6 +1126,11 @@ type-fest@^0.5.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.5.2.tgz#d6ef42a0356c6cd45f49485c3b6281fc148e48a2" integrity sha512-DWkS49EQKVX//Tbupb9TFa19c7+MK1XmzkrZUR8TAktmE/DizXoaoJV6TZ/tSIPXipqNiRI6CyAe7x69Jb6RSw== +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"