diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 000000000..635c47a95 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,151 @@ +{ + "parserOptions": { + "ecmaVersion": 6, + "ecmaFeatures": { + "experimentalObjectRestSpread": true, + "impliedStrict": true, + "modules": true, + "blockBindings": true, + "arrowFunctions": true, + "objectLiteralShorthandMethods": true, + "objectLiteralShorthandProperties": true, + "templateStrings": true + }, + }, + + "env": { + "es6": true, + "node": true, + "browser": true + }, + + "plugins": [ + ], + + "globals": { + "chrome": true, + "document": false, + "navigator": false, + "web3": true, + "window": false + }, + + "rules": { + "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 }], + "jsx-quotes": [2, "prefer-single"], + "key-spacing": [2, { "beforeColon": false, "afterColon": true }], + "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": 1 }], + "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-vars": [2, { "vars": "all", "args": "none" }], + "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": { "?": "before", ":": "before" } }], + "padded-blocks": [2, "never"], + "quotes": [2, "single", "avoid-escape"], + "semi": [2, "never"], + "semi-spacing": [2, { "before": false, "after": true }], + "space-before-blocks": [1, "always"], + "space-before-function-paren": [1, "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", "!", ","] }], + "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": 1 + } +} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 000000000..e0ea36fee --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +6.0 diff --git a/README.md b/README.md index bab22074d..98207bcb2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Metamask Plugin [![Build Status](https://travis-ci.com/MetaMask/metamask-plugin.svg?token=3txzDGFpqQqvRCdgwTJp&branch=master)](https://travis-ci.com/MetaMask/metamask-plugin) +# Metamask Plugin [![Build Status](https://circleci.com/gh/MetaMask/metamask-plugin.svg?style=shield&circle-token=a1ddcf3cd38e29267f254c9c59d556d513e3a1fd)](https://circleci.com/gh/MetaMask/metamask-plugin) ## Architecture @@ -54,6 +54,8 @@ Then just run `npm test`. You can also test with a continuously watching process, via `npm run watch`. +You can run the linter by itself with `gulp lint`. + ### Deploying the UI You must be authorized already on the Metamask plugin. diff --git a/app/scripts/background.js b/app/scripts/background.js index 3ad95d3e9..6934e9d3e 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -1,11 +1,9 @@ const urlUtil = require('url') const Dnode = require('dnode') const eos = require('end-of-stream') -const combineStreams = require('pumpify') const extend = require('xtend') const EthStore = require('eth-store') const MetaMaskProvider = require('web3-provider-engine/zero.js') -const ObjectMultiplex = require('./lib/obj-multiplex') const PortStream = require('./lib/port-stream.js') const IdentityStore = require('./lib/idStore') const createUnlockRequestNotification = require('./lib/notifications.js').createUnlockRequestNotification @@ -22,7 +20,7 @@ const Web3 = require('web3') // chrome.runtime.onConnect.addListener(connectRemote) -function connectRemote(remotePort){ +function connectRemote (remotePort) { var isMetaMaskInternalProcess = (remotePort.name === 'popup') var portStream = new PortStream(remotePort) if (isMetaMaskInternalProcess) { @@ -35,7 +33,7 @@ function connectRemote(remotePort){ } } -function setupUntrustedCommunication(connectionStream, originDomain){ +function setupUntrustedCommunication (connectionStream, originDomain) { // setup multiplexing var mx = setupMultiplex(connectionStream) // connect features @@ -43,7 +41,7 @@ function setupUntrustedCommunication(connectionStream, originDomain){ setupPublicConfig(mx.createStream('publicConfig')) } -function setupTrustedCommunication(connectionStream, originDomain){ +function setupTrustedCommunication (connectionStream, originDomain) { // setup multiplexing var mx = setupMultiplex(connectionStream) // connect features @@ -55,13 +53,12 @@ function setupTrustedCommunication(connectionStream, originDomain){ // state and network // -var providerConfig = configManager.getProvider() var idStore = new IdentityStore() var providerOpts = { rpcUrl: configManager.getCurrentRpcAddress(), // account mgmt - getAccounts: function(cb){ + getAccounts: function (cb) { var selectedAddress = idStore.getSelectedAddress() var result = selectedAddress ? [selectedAddress] : [] cb(null, result) @@ -79,8 +76,8 @@ idStore.web3 = web3 idStore.getNetwork() // log new blocks -provider.on('block', function(block){ - console.log('BLOCK CHANGED:', '#'+block.number.toString('hex'), '0x'+block.hash.toString('hex')) +provider.on('block', function (block) { + console.log('BLOCK CHANGED:', '#' + block.number.toString('hex'), '0x' + block.hash.toString('hex')) // Check network when restoring connectivity: if (idStore._currentState.network === 'loading') { @@ -93,7 +90,7 @@ provider.on('error', idStore.getNetwork.bind(idStore)) var ethStore = new EthStore(provider) idStore.setStore(ethStore) -function getState(){ +function getState () { var state = extend( ethStore.getState(), idStore.getState(), @@ -115,56 +112,61 @@ var initPublicState = extend( var publicConfigStore = new HostStore(initPublicState) // subscribe to changes -configManager.subscribe(function(state){ +configManager.subscribe(function (state) { storeSetFromObj(publicConfigStore, configToPublic(state)) }) -idStore.on('update', function(state){ +idStore.on('update', function (state) { storeSetFromObj(publicConfigStore, idStoreToPublic(state)) }) // idStore substate -function idStoreToPublic(state){ +function idStoreToPublic (state) { return { selectedAddress: state.selectedAddress, } } // config substate -function configToPublic(state){ +function configToPublic (state) { return { provider: state.provider, } } // dump obj into store -function storeSetFromObj(store, obj){ - Object.keys(obj).forEach(function(key){ +function storeSetFromObj (store, obj) { + Object.keys(obj).forEach(function (key) { store.set(key, obj[key]) }) } - // // remote features // -function setupPublicConfig(stream){ +function setupPublicConfig (stream) { var storeStream = publicConfigStore.createStream() stream.pipe(storeStream).pipe(stream) } -function setupProviderConnection(stream, originDomain){ - - stream.on('data', function onRpcRequest(payload){ - // Append origin to rpc payload - payload.origin = originDomain - // Append origin to signature request - if (payload.method === 'eth_sendTransaction') { - payload.params[0].origin = originDomain - } else if (payload.method === 'eth_sign') { - payload.params.push({ origin: originDomain }) - } +function setupProviderConnection (stream, originDomain) { + // decorate all payloads with origin domain + stream.on('data', function onRpcRequest (request) { + var payloads = Array.isArray(request) ? request : [request] + payloads.forEach(function (payload) { + // Append origin to rpc payload + payload.origin = originDomain + // Append origin to signature request + if (payload.method === 'eth_sendTransaction') { + payload.params[0].origin = originDomain + } else if (payload.method === 'eth_sign') { + payload.params.push({ origin: originDomain }) + } + }) // handle rpc request - provider.sendAsync(payload, function onPayloadHandled(err, response){ - logger(null, payload, response) + provider.sendAsync(request, function onPayloadHandled (err, response) { + if (err) { + return logger(err) + } + logger(null, request, response) try { stream.write(response) } catch (err) { @@ -173,54 +175,52 @@ function setupProviderConnection(stream, originDomain){ }) }) - function logger(err, request, response){ + function logger (err, request, response) { if (err) return console.error(err.stack) if (!request.isMetamaskInternal) { console.log(`RPC (${originDomain}):`, request, '->', response) - if (response.error) console.error('Error in RPC response:\n'+response.error.message) + if (response.error) console.error('Error in RPC response:\n' + response.error.message) } } } -function setupControllerConnection(stream){ +function setupControllerConnection (stream) { var dnode = Dnode({ - getState: function(cb){ cb(null, getState()) }, - setRpcTarget: setRpcTarget, - setProviderType: setProviderType, + getState: function (cb) { cb(null, getState()) }, + setRpcTarget: setRpcTarget, + setProviderType: setProviderType, useEtherscanProvider: useEtherscanProvider, - agreeToDisclaimer: agreeToDisclaimer, + agreeToDisclaimer: agreeToDisclaimer, // forward directly to idStore - createNewVault: idStore.createNewVault.bind(idStore), - recoverFromSeed: idStore.recoverFromSeed.bind(idStore), - submitPassword: idStore.submitPassword.bind(idStore), + createNewVault: idStore.createNewVault.bind(idStore), + recoverFromSeed: idStore.recoverFromSeed.bind(idStore), + submitPassword: idStore.submitPassword.bind(idStore), setSelectedAddress: idStore.setSelectedAddress.bind(idStore), approveTransaction: idStore.approveTransaction.bind(idStore), - cancelTransaction: idStore.cancelTransaction.bind(idStore), - signMessage: idStore.signMessage.bind(idStore), - cancelMessage: idStore.cancelMessage.bind(idStore), - setLocked: idStore.setLocked.bind(idStore), + cancelTransaction: idStore.cancelTransaction.bind(idStore), + signMessage: idStore.signMessage.bind(idStore), + cancelMessage: idStore.cancelMessage.bind(idStore), + setLocked: idStore.setLocked.bind(idStore), clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore), - exportAccount: idStore.exportAccount.bind(idStore), - revealAccount: idStore.revealAccount.bind(idStore), - saveAccountLabel: idStore.saveAccountLabel.bind(idStore), - tryPassword: idStore.tryPassword.bind(idStore), - recoverSeed: idStore.recoverSeed.bind(idStore), + exportAccount: idStore.exportAccount.bind(idStore), + revealAccount: idStore.revealAccount.bind(idStore), + saveAccountLabel: idStore.saveAccountLabel.bind(idStore), + tryPassword: idStore.tryPassword.bind(idStore), + recoverSeed: idStore.recoverSeed.bind(idStore), }) stream.pipe(dnode).pipe(stream) - dnode.on('remote', function(remote){ - + dnode.on('remote', function (remote) { // push updates to popup ethStore.on('update', sendUpdate) idStore.on('update', sendUpdate) // teardown on disconnect - eos(stream, function unsubscribe(){ + eos(stream, function unsubscribe () { ethStore.removeListener('update', sendUpdate) }) - function sendUpdate(){ + function sendUpdate () { var state = getState() remote.sendUpdate(state) } - }) } @@ -230,7 +230,7 @@ function setupControllerConnection(stream){ idStore.on('update', updateBadge) -function updateBadge(state){ +function updateBadge (state) { var label = '' var unconfTxs = configManager.unconfirmedTxs() var unconfTxLen = Object.keys(unconfTxs).length @@ -248,7 +248,7 @@ function updateBadge(state){ // Add unconfirmed Tx + Msg // -function newUnsignedTransaction(txParams, onTxDoneCb){ +function newUnsignedTransaction (txParams, onTxDoneCb) { var state = idStore.getState() if (!state.isUnlocked) { createUnlockRequestNotification({ @@ -260,20 +260,19 @@ function newUnsignedTransaction(txParams, onTxDoneCb){ } } -function newUnsignedMessage(msgParams, cb){ +function newUnsignedMessage (msgParams, cb) { var state = idStore.getState() if (!state.isUnlocked) { createUnlockRequestNotification({ title: 'Account Unlock Request', }) - var msgId = idStore.addUnconfirmedMessage(msgParams, cb) } else { addUnconfirmedMsg(msgParams, cb) } } -function addUnconfirmedTx(txParams, onTxDoneCb){ - idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, function(err, txData){ +function addUnconfirmedTx (txParams, onTxDoneCb) { + idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, function (err, txData) { if (err) return onTxDoneCb(err) createTxNotification({ title: 'New Unsigned Transaction', @@ -284,7 +283,7 @@ function addUnconfirmedTx(txParams, onTxDoneCb){ }) } -function addUnconfirmedMsg(msgParams, cb){ +function addUnconfirmedMsg (msgParams, cb) { var msgId = idStore.addUnconfirmedMessage(msgParams, cb) createMsgNotification({ title: 'New Unsigned Message', @@ -298,7 +297,7 @@ function addUnconfirmedMsg(msgParams, cb){ // config // -function agreeToDisclaimer(cb) { +function agreeToDisclaimer (cb) { try { configManager.setConfirmed(true) cb() @@ -308,23 +307,23 @@ function agreeToDisclaimer(cb) { } // called from popup -function setRpcTarget(rpcTarget){ +function setRpcTarget (rpcTarget) { configManager.setRpcTarget(rpcTarget) chrome.runtime.reload() idStore.getNetwork() } -function setProviderType(type) { +function setProviderType (type) { configManager.setProviderType(type) chrome.runtime.reload() idStore.getNetwork() } -function useEtherscanProvider() { +function useEtherscanProvider () { configManager.useEtherscanProvider() chrome.runtime.reload() } // util -function noop(){} +function noop () {} diff --git a/app/scripts/chromereload.js b/app/scripts/chromereload.js index 24adc7022..283a131f1 100644 --- a/app/scripts/chromereload.js +++ b/app/scripts/chromereload.js @@ -32,1187 +32,1160 @@ window.LiveReloadOptions = { host: 'localhost' }; - -(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o tag"); - return; + if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { + this.console.error('LiveReload disabled because the browser does not seem to support web sockets') + return } - } - this.reloader = new Reloader(this.window, this.console, Timer); - this.connector = new Connector(this.options, this.WebSocket, Timer, { - connecting: (function(_this) { - return function() {}; - })(this), - socketConnected: (function(_this) { - return function() {}; - })(this), - connected: (function(_this) { - return function(protocol) { - var _base; - if (typeof (_base = _this.listeners).connect === "function") { - _base.connect(); - } - _this.log("LiveReload is connected to " + _this.options.host + ":" + _this.options.port + " (protocol v" + protocol + ")."); - return _this.analyze(); - }; - })(this), - error: (function(_this) { - return function(e) { - if (e instanceof ProtocolError) { - if (typeof console !== "undefined" && console !== null) { - return console.log("" + e.message + "."); - } - } else { - if (typeof console !== "undefined" && console !== null) { - return console.log("LiveReload internal error: " + e.message); + if ('LiveReloadOptions' in window) { + this.options = new Options() + _ref = window['LiveReloadOptions'] + for (k in _ref) { + if (!__hasProp.call(_ref, k)) continue + v = _ref[k] + this.options.set(k, v) + } + } else { + this.options = Options.extract(this.window.document) + if (!this.options) { + this.console.error('LiveReload disabled because it could not find its own