From f763979beded94a8bfe7717a96e36c9c65f87368 Mon Sep 17 00:00:00 2001 From: Mark Stacey Date: Fri, 22 Nov 2019 13:03:51 -0400 Subject: [PATCH] Add support for one-click onboarding (#7017) * Add support for one-click onboarding MetaMask now allows sites to register as onboarding the user, so that the user is redirected back to the initiating site after onboarding. This is accomplished through the use of the `metamask-onboarding` library and the MetaMask forwarder. At the end of onboarding, a 'snackbar'-stype component will explain to the user they are about to be moved back to the originating dapp, and it will show the origin of that dapp. This is intended to help prevent phishing attempts, as it highlights that a redirect is taking place to an untrusted third party. If the onboarding initiator tab is closed when onboarding is finished, the user is redirected to the onboarding originator as a fallback. Closes #6161 * Add onboarding button to contract test dapp The `contract-test` dapp (run with `yarn dapp`, used in e2e tests) now uses a `Connect` button instead of connecting automatically. This button also serves as an onboarding button when a MetaMask installation is not detected. * Add new static server for test dapp The `static-server` library we were using for the `contract-test` dapp didn't allow referencing files outside the server root. This should have been possible to work around using symlinks, but there was a bug that resulted in symlinks crashing the server. Instead it has been replaced with a simple static file server that will serve paths starting with `node_modules` from the project root. This will be useful in testing the onboarding library without vendoring it. * Add `@metamask/onboarding` and `@metamask/forwarder` Both libraries used to test onboarding are now included as dev dependencies, to help with testing. A few convenience scripts were added to help with this (`yarn forwarder` and `yarn dapp-forwarder`) --- app/_locales/en/messages.json | 4 + app/scripts/background.js | 1 + app/scripts/contentscript.js | 39 ++ app/scripts/controllers/onboarding.js | 46 +- app/scripts/inpage.js | 26 +- app/scripts/metamask-controller.js | 43 +- app/scripts/migrations/031.js | 2 +- development/static-server.js | 92 ++++ package.json | 13 +- test/e2e/contract-test/contract.js | 479 +++++++++++------- test/e2e/contract-test/index.html | 99 ++-- test/e2e/ethereum-on.spec.js | 9 +- test/e2e/metamask-ui.spec.js | 5 + test/e2e/run-web3.sh | 2 +- test/e2e/signature-request.spec.js | 5 + test/e2e/web3.spec.js | 5 + ui/app/components/ui/snackbar/index.js | 1 + ui/app/components/ui/snackbar/index.scss | 11 + .../ui/snackbar/snackbar.component.js | 18 + ui/app/css/itcss/components/index.scss | 1 + .../end-of-flow/end-of-flow.component.js | 45 +- .../end-of-flow/end-of-flow.container.js | 7 +- .../first-time-flow/end-of-flow/index.scss | 2 +- .../first-time-flow.selectors.js | 28 +- .../onboarding-initiator-util.js | 48 ++ .../seed-phrase/reveal-seed-phrase/index.scss | 2 +- .../reveal-seed-phrase.component.js | 32 +- .../reveal-seed-phrase.container.js | 9 +- yarn.lock | 143 +++--- 29 files changed, 862 insertions(+), 355 deletions(-) create mode 100644 development/static-server.js create mode 100644 ui/app/components/ui/snackbar/index.js create mode 100644 ui/app/components/ui/snackbar/index.scss create mode 100644 ui/app/components/ui/snackbar/snackbar.component.js create mode 100644 ui/app/pages/first-time-flow/onboarding-initiator-util.js diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 2a060b1e4..db150930b 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -507,6 +507,10 @@ "endOfFlowMessage10": { "message": "All Done" }, + "onboardingReturnNotice": { + "message": "\"$1\" will close this tab and direct back to $2", + "description": "Return the user to the site that initiated onboarding" + }, "ensRegistrationError": { "message": "Error in ENS name registration" }, diff --git a/app/scripts/background.js b/app/scripts/background.js index b26a87ba2..18bdfdfb9 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -314,6 +314,7 @@ function setupController (initState, initLangCode) { // extension.runtime.onConnect.addListener(connectRemote) extension.runtime.onConnectExternal.addListener(connectExternal) + extension.runtime.onMessage.addListener(controller.onMessage.bind(controller)) const metamaskInternalProcessHash = { [ENVIRONMENT_TYPE_POPUP]: true, diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 4a84cb64b..6ea2db740 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -4,6 +4,7 @@ const pump = require('pump') const log = require('loglevel') const Dnode = require('dnode') const querystring = require('querystring') +const { Writable } = require('readable-stream') const LocalMessageDuplexStream = require('post-message-stream') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') @@ -86,6 +87,44 @@ async function setupStreams () { (err) => logStreamDisconnectWarning('MetaMask Background Multiplex', err) ) + const onboardingStream = pageMux.createStream('onboarding') + const addCurrentTab = new Writable({ + objectMode: true, + write: (chunk, _, callback) => { + if (!chunk) { + return callback(new Error('Malformed onboarding message')) + } + + const handleSendMessageResponse = (error, success) => { + if (!error && !success) { + error = extension.runtime.lastError + } + if (error) { + log.error(`Failed to send ${chunk.type} message`, error) + return callback(error) + } + callback(null) + } + + try { + if (chunk.type === 'registerOnboarding') { + extension.runtime.sendMessage({ type: 'metamask:registerOnboarding', location: window.location.href }, handleSendMessageResponse) + } else { + throw new Error(`Unrecognized onboarding message type: '${chunk.type}'`) + } + } catch (error) { + log.error(error) + return callback(error) + } + }, + }) + + pump( + onboardingStream, + addCurrentTab, + error => console.error('MetaMask onboarding channel traffic failed', error), + ) + // forward communication across inpage-background for these channels only forwardTrafficBetweenMuxers('provider', pageMux, extensionMux) forwardTrafficBetweenMuxers('publicConfig', pageMux, extensionMux) diff --git a/app/scripts/controllers/onboarding.js b/app/scripts/controllers/onboarding.js index a29c8407a..5d00fb775 100644 --- a/app/scripts/controllers/onboarding.js +++ b/app/scripts/controllers/onboarding.js @@ -1,5 +1,6 @@ const ObservableStore = require('obs-store') const extend = require('xtend') +const log = require('loglevel') /** * @typedef {Object} InitState @@ -9,11 +10,12 @@ const extend = require('xtend') /** * @typedef {Object} OnboardingOptions * @property {InitState} initState The initial controller state + * @property {PreferencesController} preferencesController Controller for managing user perferences */ /** * Controller responsible for maintaining - * a cache of account balances in local storage + * state related to onboarding */ class OnboardingController { /** @@ -22,10 +24,28 @@ class OnboardingController { * @param {OnboardingOptions} [opts] Controller configuration parameters */ constructor (opts = {}) { - const initState = extend({ - seedPhraseBackedUp: true, - }, opts.initState) + const initialTransientState = { + onboardingTabs: {}, + } + const initState = extend( + { + seedPhraseBackedUp: true, + }, + opts.initState, + initialTransientState, + ) this.store = new ObservableStore(initState) + this.preferencesController = opts.preferencesController + this.completedOnboarding = this.preferencesController.store.getState().completedOnboarding + + this.preferencesController.store.subscribe(({ completedOnboarding }) => { + if (completedOnboarding !== this.completedOnboarding) { + this.completedOnboarding = completedOnboarding + if (completedOnboarding) { + this.store.updateState(initialTransientState) + } + } + }) } setSeedPhraseBackedUp (newSeedPhraseBackUpState) { @@ -38,6 +58,24 @@ class OnboardingController { return this.store.getState().seedPhraseBackedUp } + /** + * Registering a site as having initiated onboarding + * + * @param {string} location - The location of the site registering + * @param {string} tabId - The id of the tab registering + */ + async registerOnboarding (location, tabId) { + if (this.completedOnboarding) { + log.debug('Ignoring registerOnboarding; user already onboarded') + return + } + const onboardingTabs = Object.assign({}, this.store.getState().onboardingTabs) + if (!onboardingTabs[location] || onboardingTabs[location] !== tabId) { + log.debug(`Registering onboarding tab at location '${location}' with tabId '${tabId}'`) + onboardingTabs[location] = tabId + this.store.updateState({ onboardingTabs }) + } + } } module.exports = OnboardingController diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index ec88243a4..0f752f1af 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -37,6 +37,9 @@ const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('metamask-inpage-provider') +const ObjectMultiplex = require('obj-multiplex') +const pump = require('pump') +const promisify = require('pify') const createStandardProvider = require('./createStandardProvider').default let warned = false @@ -61,6 +64,14 @@ const inpageProvider = new MetamaskInpageProvider(metamaskStream) // set a high max listener count to avoid unnecesary warnings inpageProvider.setMaxListeners(100) +const pageMux = new ObjectMultiplex() +const onboardingStream = pageMux.createStream('onboarding') +pump( + pageMux, + metamaskStream, + error => log.error('MetaMask muxed in-page traffic failed', error) +) + let warnedOfAutoRefreshDeprecation = false // augment the provider with its enable method inpageProvider.enable = function ({ force } = {}) { @@ -134,6 +145,15 @@ inpageProvider._metamask = new Proxy({ const { isUnlocked } = await getPublicConfigWhenReady() return Boolean(isUnlocked) }, + + /** + * Registers a page as having initated onboarding. This facilitates MetaMask focusing the initiating tab after onboarding. + * + * @returns {Promise} - Promise resolving to undefined + */ + registerOnboarding: async () => { + await promisify(onboardingStream.write({ type: 'registerOnboarding' })) + }, }, { get: function (obj, prop) { !warned && console.warn('Heads up! ethereum._metamask exposes methods that have ' + @@ -178,9 +198,3 @@ setupDappAutoReload(web3, inpageProvider.publicConfigStore) inpageProvider.publicConfigStore.subscribe(function (state) { web3.eth.defaultAccount = state.selectedAddress }) - -inpageProvider.publicConfigStore.subscribe(function (state) { - if (state.onboardingcomplete) { - window.postMessage('onboardingcomplete', '*') - } -}) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 6e2f0d08e..be8424d42 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -4,10 +4,12 @@ * @license MIT */ +const assert = require('assert').strict const EventEmitter = require('events') const pump = require('pump') const Dnode = require('dnode') const pify = require('pify') +const extension = require('extensionizer') const ObservableStore = require('obs-store') const ComposableObservableStore = require('./lib/ComposableObservableStore') const createDnodeRemoteGetter = require('./lib/createDnodeRemoteGetter') @@ -177,6 +179,7 @@ module.exports = class MetamaskController extends EventEmitter { this.onboardingController = new OnboardingController({ initState: initState.OnboardingController, + preferencesController: this.preferencesController, }) // ensure accountTracker updates balances after network change @@ -390,7 +393,7 @@ module.exports = class MetamaskController extends EventEmitter { publicConfigStore.putState(publicState) } - function selectPublicState ({ isUnlocked, selectedAddress, network, completedOnboarding, provider }) { + function selectPublicState ({ isUnlocked, selectedAddress, network, provider }) { const isEnabled = checkIsEnabled() const isReady = isUnlocked && isEnabled const result = { @@ -398,7 +401,6 @@ module.exports = class MetamaskController extends EventEmitter { isEnabled, selectedAddress: isReady ? selectedAddress : null, networkVersion: network, - onboardingcomplete: completedOnboarding, chainId: selectChainId({ network, provider }), } return result @@ -1521,6 +1523,43 @@ module.exports = class MetamaskController extends EventEmitter { ) } + onMessage (message, sender, sendResponse) { + if (!message || !message.type) { + log.debug(`Ignoring invalid message: '${JSON.stringify(message)}'`) + return + } + + let handleMessage + + try { + if (message.type === 'metamask:registerOnboarding') { + assert(sender.tab, 'Missing tab from sender') + assert(sender.tab.id && sender.tab.id !== extension.tabs.TAB_ID_NONE, 'Missing tab ID from sender') + assert(message.location, 'Missing location from message') + + handleMessage = this.onboardingController.registerOnboarding(message.location, sender.tab.id) + } else { + throw new Error(`Unrecognized message type: '${message.type}'`) + } + } catch (error) { + console.error(error) + sendResponse(error) + return true + } + + if (handleMessage) { + handleMessage + .then(() => { + sendResponse(null, true) + }) + .catch((error) => { + console.error(error) + sendResponse(error) + }) + return true + } + } + /** * A method for providing our public api over a stream. * This includes a method for setting site metadata like title and image diff --git a/app/scripts/migrations/031.js b/app/scripts/migrations/031.js index 9c8cbeb09..927de98c4 100644 --- a/app/scripts/migrations/031.js +++ b/app/scripts/migrations/031.js @@ -3,7 +3,7 @@ const version = 31 const clone = require('clone') /* - * The purpose of this migration is to properly set the completedOnboarding flag baesd on the state + * The purpose of this migration is to properly set the completedOnboarding flag based on the state * of the KeyringController. */ module.exports = { diff --git a/development/static-server.js b/development/static-server.js new file mode 100644 index 000000000..d8f22cabe --- /dev/null +++ b/development/static-server.js @@ -0,0 +1,92 @@ +const fs = require('fs') +const http = require('http') +const path = require('path') + +const chalk = require('chalk') +const pify = require('pify') +const serveHandler = require('serve-handler') + +const fsStat = pify(fs.stat) +const DEFAULT_PORT = 9080 + +const onResponse = (request, response) => { + if (response.statusCode >= 400) { + console.log(chalk`{gray '-->'} {red ${response.statusCode}} ${request.url}`) + } else if (response.statusCode >= 200 && response.statusCode < 300) { + console.log(chalk`{gray '-->'} {green ${response.statusCode}} ${request.url}`) + } else { + console.log(chalk`{gray '-->'} {green.dim ${response.statusCode}} ${request.url}`) + } +} +const onRequest = (request, response) => { + console.log(chalk`{gray '<--'} {blue [${request.method}]} ${request.url}`) + response.on('finish', () => onResponse(request, response)) +} + +const startServer = ({ port, rootDirectory }) => { + const server = http.createServer((request, response) => { + if (request.url.startsWith('/node_modules/')) { + request.url = request.url.substr(14) + return serveHandler(request, response, { + directoryListing: false, + public: path.resolve('./node_modules'), + }) + } + return serveHandler(request, response, { + directoryListing: false, + public: rootDirectory, + }) + }) + + server.on('request', onRequest) + + server.listen(port, () => { + console.log(`Running at http://localhost:${port}`) + }) +} + +const parsePort = (portString) => { + const port = Number(portString) + if (!Number.isInteger(port)) { + throw new Error(`Port '${portString}' is invalid; must be an integer`) + } else if (port < 0 || port > 65535) { + throw new Error(`Port '${portString}' is out of range; must be between 0 and 65535 inclusive`) + } + return port +} + +const parseDirectoryArgument = async (pathString) => { + const resolvedPath = path.resolve(pathString) + const directoryStats = await fsStat(resolvedPath) + if (!directoryStats.isDirectory()) { + throw new Error(`Invalid path '${pathString}'; must be a directory`) + } + return resolvedPath +} + +const main = async () => { + const args = process.argv.slice(2) + + const options = { + port: process.env.port || DEFAULT_PORT, + rootDirectory: path.resolve('.'), + } + + while (args.length) { + if (/^(--port|-p)$/i.test(args[0])) { + if (args[1] === undefined) { + throw new Error('Missing port argument') + } + options.port = parsePort(args[1]) + args.splice(0, 2) + } else { + options.rootDirectory = await parseDirectoryArgument(args[0]) + args.splice(0, 1) + } + } + + startServer(options) +} + +main() + .catch(console.error) diff --git a/package.json b/package.json index 7c0723ad4..7b51c827f 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,12 @@ "start:test": "gulp dev:test", "build:test": "gulp build:test", "test": "yarn test:unit && yarn lint", - "dapp": "static-server test/e2e/contract-test --port 8080", - "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && static-server test/e2e/contract-test --port 8080'", + "dapp": "node development/static-server.js test/e2e/contract-test --port 8080", + "dapp-chain": "GANACHE_ARGS='-b 2' concurrently -k -n ganache,dapp -p '[{time}][{name}]' 'yarn ganache:start' 'sleep 5 && node development/static-server.js test/e2e/contract-test --port 8080'", + "forwarder": "node ./development/static-server.js ./node_modules/@metamask/forwarder/dist/ --port 9010", + "dapp-forwarder": "concurrently -k -n forwarder,dapp -p '[{time}][{name}]' 'yarn forwarder' 'yarn dapp'", "watch:test:unit": "nodemon --exec \"yarn test:unit\" ./test ./app ./ui", - "sendwithprivatedapp": "static-server test/e2e/send-eth-with-private-key-test --port 8080", + "sendwithprivatedapp": "node development/static-server.js test/e2e/send-eth-with-private-key-test --port 8080", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require test/setup.js --recursive \"test/unit/**/*.js\" \"ui/app/**/*.test.js\"", "test:unit:global": "mocha test/unit-global/*", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", @@ -189,6 +191,8 @@ "@babel/preset-env": "^7.5.5", "@babel/preset-react": "^7.0.0", "@babel/register": "^7.5.5", + "@metamask/forwarder": "^1.0.0", + "@metamask/onboarding": "^0.1.2", "@sentry/cli": "^1.30.3", "@storybook/addon-actions": "^5.2.6", "@storybook/addon-info": "^5.1.1", @@ -201,6 +205,7 @@ "browserify": "^16.2.3", "browserify-transform-tools": "^1.7.0", "chai": "^4.1.0", + "chalk": "^2.4.2", "chromedriver": "^2.41.0", "concurrently": "^4.1.1", "coveralls": "^3.0.0", @@ -279,12 +284,12 @@ "rimraf": "^2.6.2", "sass-loader": "^7.0.1", "selenium-webdriver": "^3.5.0", + "serve-handler": "^6.1.2", "sesify": "^4.2.1", "sesify-viz": "^3.0.5", "sinon": "^5.0.0", "source-map": "^0.7.2", "source-map-explorer": "^2.0.1", - "static-server": "^2.2.1", "style-loader": "^0.21.0", "stylelint": "^9.10.1", "stylelint-config-standard": "^18.2.0", diff --git a/test/e2e/contract-test/contract.js b/test/e2e/contract-test/contract.js index ebfea34ec..a6b5f110b 100644 --- a/test/e2e/contract-test/contract.js +++ b/test/e2e/contract-test/contract.js @@ -1,4 +1,4 @@ -/*global ethereum*/ +/*global ethereum, MetamaskOnboarding */ /* The `piggybankContract` is compiled from: @@ -30,8 +30,14 @@ The `piggybankContract` is compiled from: } */ -web3.currentProvider.enable().then(() => { - var piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) +const forwarderOrigin = 'http://localhost:9010' + +const isMetaMaskInstalled = () => { + return Boolean(window.ethereum && window.ethereum.isMetaMask) +} + +const initialize = () => { + const onboardButton = document.getElementById('connectButton') const deployButton = document.getElementById('deployButton') const depositButton = document.getElementById('depositButton') const withdrawButton = document.getElementById('withdrawButton') @@ -42,200 +48,295 @@ web3.currentProvider.enable().then(() => { const transferTokensWithoutGas = document.getElementById('transferTokensWithoutGas') const approveTokensWithoutGas = document.getElementById('approveTokensWithoutGas') const signTypedData = document.getElementById('signTypedData') + const signTypedDataResults = document.getElementById('signTypedDataResult') - deployButton.addEventListener('click', async function () { - document.getElementById('contractStatus').innerHTML = 'Deploying' - - var piggybank = await piggybankContract.new( - { - from: web3.eth.accounts[0], - data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', - gas: '4700000', - }, function (e, contract) { - if (e) { - throw e - } - if (typeof contract.address !== 'undefined') { - console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - - document.getElementById('contractStatus').innerHTML = 'Deployed' - - depositButton.addEventListener('click', function () { - document.getElementById('contractStatus').innerHTML = 'Deposit initiated' - contract.deposit({ from: web3.eth.accounts[0], value: '0x3782dace9d900000' }, function (result) { - console.log(result) - document.getElementById('contractStatus').innerHTML = 'Deposit completed' - }) - }) - - withdrawButton.addEventListener('click', function () { - contract.withdraw('0xde0b6b3a7640000', { from: web3.eth.accounts[0] }, function (result) { - console.log(result) - document.getElementById('contractStatus').innerHTML = 'Withdrawn' - }) - }) - } - }) - - console.log(piggybank) - }) - - sendButton.addEventListener('click', function () { - web3.eth.sendTransaction({ - from: web3.eth.accounts[0], - to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', - value: '0x29a2241af62c0000', - gas: 21000, - gasPrice: 20000000000, - }, (result) => { - console.log(result) - }) - }) - - - createToken.addEventListener('click', async function () { - var _initialAmount = 100 - var _tokenName = 'TST' - var _decimalUnits = 0 - var _tokenSymbol = 'TST' - var humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) - return humanstandardtokenContract.new( - _initialAmount, - _tokenName, - _decimalUnits, - _tokenSymbol, - { - from: web3.eth.accounts[0], - data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', - gas: '4700000', - gasPrice: '20000000000', - }, function (e, contract) { - console.log(e, contract) - if (typeof contract.address !== 'undefined') { - console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) - - document.getElementById('tokenAddress').innerHTML = contract.address - - transferTokens.addEventListener('click', function (event) { - console.log(`event`, event) - contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', - gas: 60000, - gasPrice: '20000000000', - }, function (result) { - console.log('result', result) - }) - }) - - approveTokens.addEventListener('click', function () { - contract.approve('0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', '70000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0x095ea7b30000000000000000000000009bc5baF874d2DA8D216aE9f137804184EE5AfEF40000000000000000000000000000000000000000000000000000000000000005', - gas: 60000, - gasPrice: '20000000000', - }, function (result) { - console.log(result) - }) - }) - - transferTokensWithoutGas.addEventListener('click', function (event) { - console.log(`event`, event) - contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', - gasPrice: '20000000000', - }, function (result) { - console.log('result', result) - }) - }) - - approveTokensWithoutGas.addEventListener('click', function () { - contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '70000', { - from: web3.eth.accounts[0], - to: contract.address, - data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', - gasPrice: '20000000000', - }, function (result) { - console.log(result) - }) - }) - } - }) - - }) - - ethereum.autoRefreshOnNetworkChange = false - + const contractStatus = document.getElementById('contractStatus') + const tokenAddress = document.getElementById('tokenAddress') const networkDiv = document.getElementById('network') const chainIdDiv = document.getElementById('chainId') const accountsDiv = document.getElementById('accounts') - ethereum.on('networkChanged', (networkId) => { - networkDiv.innerHTML = networkId - }) + let onboarding + try { + onboarding = new MetamaskOnboarding({ forwarderOrigin }) + } catch (error) { + console.error(error) + } + let accounts + let piggybankContract - ethereum.on('chainIdChanged', (chainId) => { - chainIdDiv.innerHTML = chainId - }) + const accountButtons = [ + deployButton, + depositButton, + withdrawButton, + sendButton, + createToken, + transferTokens, + approveTokens, + transferTokensWithoutGas, + approveTokensWithoutGas, + ] - ethereum.on('accountsChanged', (accounts) => { - accountsDiv.innerHTML = accounts - }) + for (const button of accountButtons) { + button.disabled = true + } - const signTypedDataResultsDiv = document.getElementById('signTypedDataResult') - signTypedData.addEventListener('click', function () { + const isMetaMaskConnected = () => accounts && accounts.length > 0 - const typedData = { - types: { - EIP712Domain: [ - { name: 'name', type: 'string' }, - { name: 'version', type: 'string' }, - { name: 'chainId', type: 'uint256' }, - { name: 'verifyingContract', type: 'address' }, - ], - Person: [ - { name: 'name', type: 'string' }, - { name: 'wallet', type: 'address' }, - ], - Mail: [ - { name: 'from', type: 'Person' }, - { name: 'to', type: 'Person' }, - { name: 'contents', type: 'string' }, - ], - }, - primaryType: 'Mail', - domain: { - name: 'Ether Mail', - version: '1', - chainId: 3, - verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', - }, - message: { - sender: { - name: 'Cow', - wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', - }, - recipient: { - name: 'Bob', - wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', - }, - contents: 'Hello, Bob!', - }, - } - web3.currentProvider.sendAsync({ - method: 'eth_signTypedData_v3', - params: [ethereum.selectedAddress, JSON.stringify(typedData)], - from: ethereum.selectedAddress, - }, function (err, result) { - if (err) { - console.log(err) - } else { - signTypedDataResultsDiv.innerHTML = result + const onClickInstall = () => { + onboardButton.innerText = 'Onboarding in progress' + onboardButton.disabled = true + onboarding.startOnboarding() + } + + const onClickConnect = async () => { + await window.ethereum.enable() + } + + const updateButtons = () => { + const accountButtonsDisabled = !isMetaMaskInstalled() || !isMetaMaskConnected() + if (accountButtonsDisabled) { + for (const button of accountButtons) { + button.disabled = true } + } else { + deployButton.disabled = false + sendButton.disabled = false + createToken.disabled = false + } + + if (!isMetaMaskInstalled()) { + onboardButton.innerText = 'Click here to install MetaMask!' + onboardButton.onclick = onClickInstall + } else if (isMetaMaskConnected()) { + onboardButton.innerText = 'Connected' + onboardButton.disabled = true + if (onboarding) { + onboarding.stopOnboarding() + } + } else { + onboardButton.innerText = 'Connect' + onboardButton.onclick = onClickConnect + } + } + + const initializeAccountButtons = () => { + piggybankContract = web3.eth.contract([{'constant': false, 'inputs': [{'name': 'withdrawAmount', 'type': 'uint256'}], 'name': 'withdraw', 'outputs': [{'name': 'remainingBal', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [], 'name': 'deposit', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': true, 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}]) + deployButton.onclick = async () => { + contractStatus.innerHTML = 'Deploying' + + const piggybank = await piggybankContract.new( + { + from: accounts[0], + data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029', + gas: '4700000', + }, (error, contract) => { + if (error) { + contractStatus.innerHTML = 'Deployment Failed' + throw error + } else if (contract.address === undefined) { + return + } + + console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) + contractStatus.innerHTML = 'Deployed' + depositButton.disabled = false + withdrawButton.disabled = false + + depositButton.onclick = () => { + contractStatus.innerHTML = 'Deposit initiated' + contract.deposit( + { + from: accounts[0], + value: '0x3782dace9d900000', + }, + (result) => { + console.log(result) + contractStatus.innerHTML = 'Deposit completed' + } + ) + } + withdrawButton.onclick = () => { + contract.withdraw( + '0xde0b6b3a7640000', + { from: accounts[0] }, + (result) => { + console.log(result) + contractStatus.innerHTML = 'Withdrawn' + } + ) + } + } + ) + console.log(piggybank) + } + + sendButton.onclick = () => { + web3.eth.sendTransaction({ + from: accounts[0], + to: '0x2f318C334780961FB129D2a6c30D0763d9a5C970', + value: '0x29a2241af62c0000', + gas: 21000, + gasPrice: 20000000000, + }, (result) => { + console.log(result) + }) + } + + createToken.onclick = async () => { + const _initialAmount = 100 + const _tokenName = 'TST' + const _decimalUnits = 0 + const _tokenSymbol = 'TST' + const humanstandardtokenContract = web3.eth.contract([{'constant': true, 'inputs': [], 'name': 'name', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'approve', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'totalSupply', 'outputs': [{'name': '', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_from', 'type': 'address'}, {'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transferFrom', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'decimals', 'outputs': [{'name': '', 'type': 'uint8'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}], 'name': 'balanceOf', 'outputs': [{'name': 'balance', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': true, 'inputs': [], 'name': 'symbol', 'outputs': [{'name': '', 'type': 'string'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_to', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}], 'name': 'transfer', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': false, 'inputs': [{'name': '_spender', 'type': 'address'}, {'name': '_value', 'type': 'uint256'}, {'name': '_extraData', 'type': 'bytes'}], 'name': 'approveAndCall', 'outputs': [{'name': 'success', 'type': 'bool'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'function'}, {'constant': true, 'inputs': [{'name': '_owner', 'type': 'address'}, {'name': '_spender', 'type': 'address'}], 'name': 'allowance', 'outputs': [{'name': 'remaining', 'type': 'uint256'}], 'payable': false, 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initialAmount', 'type': 'uint256'}, {'name': '_tokenName', 'type': 'string'}, {'name': '_decimalUnits', 'type': 'uint8'}, {'name': '_tokenSymbol', 'type': 'string'}], 'payable': false, 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'payable': false, 'stateMutability': 'nonpayable', 'type': 'fallback'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_from', 'type': 'address'}, {'indexed': true, 'name': '_to', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Transfer', 'type': 'event'}, {'anonymous': false, 'inputs': [{'indexed': true, 'name': '_owner', 'type': 'address'}, {'indexed': true, 'name': '_spender', 'type': 'address'}, {'indexed': false, 'name': '_value', 'type': 'uint256'}], 'name': 'Approval', 'type': 'event'}]) + + return humanstandardtokenContract.new( + _initialAmount, + _tokenName, + _decimalUnits, + _tokenSymbol, + { + from: accounts[0], + data: '0x60806040523480156200001157600080fd5b506040516200156638038062001566833981018060405260808110156200003757600080fd5b8101908080516401000000008111156200005057600080fd5b828101905060208101848111156200006757600080fd5b81518560018202830111640100000000821117156200008557600080fd5b50509291906020018051640100000000811115620000a257600080fd5b82810190506020810184811115620000b957600080fd5b8151856001820283011164010000000082111715620000d757600080fd5b5050929190602001805190602001909291908051906020019092919050505083838382600390805190602001906200011192919062000305565b5081600490805190602001906200012a92919062000305565b5080600560006101000a81548160ff021916908360ff1602179055505050506200016433826200016e640100000000026401000000009004565b50505050620003b4565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515620001ab57600080fd5b620001d081600254620002e36401000000000262001155179091906401000000009004565b60028190555062000237816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054620002e36401000000000262001155179091906401000000009004565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a35050565b6000808284019050838110151515620002fb57600080fd5b8091505092915050565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106200034857805160ff191683800117855562000379565b8280016001018555821562000379579182015b82811115620003785782518255916020019190600101906200035b565b5b5090506200038891906200038c565b5090565b620003b191905b80821115620003ad57600081600090555060010162000393565b5090565b90565b6111a280620003c46000396000f3fe6080604052600436106100af576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806306fdde03146100b4578063095ea7b31461014457806318160ddd146101b757806323b872dd146101e2578063313ce5671461027557806339509351146102a657806370a082311461031957806395d89b411461037e578063a457c2d71461040e578063a9059cbb14610481578063dd62ed3e146104f4575b600080fd5b3480156100c057600080fd5b506100c9610579565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101095780820151818401526020810190506100ee565b50505050905090810190601f1680156101365780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561015057600080fd5b5061019d6004803603604081101561016757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff1690602001909291908035906020019092919050505061061b565b604051808215151515815260200191505060405180910390f35b3480156101c357600080fd5b506101cc610748565b6040518082815260200191505060405180910390f35b3480156101ee57600080fd5b5061025b6004803603606081101561020557600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610752565b604051808215151515815260200191505060405180910390f35b34801561028157600080fd5b5061028a61095a565b604051808260ff1660ff16815260200191505060405180910390f35b3480156102b257600080fd5b506102ff600480360360408110156102c957600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610971565b604051808215151515815260200191505060405180910390f35b34801561032557600080fd5b506103686004803603602081101561033c57600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ba8565b6040518082815260200191505060405180910390f35b34801561038a57600080fd5b50610393610bf0565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156103d35780820151818401526020810190506103b8565b50505050905090810190601f1680156104005780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b34801561041a57600080fd5b506104676004803603604081101561043157600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610c92565b604051808215151515815260200191505060405180910390f35b34801561048d57600080fd5b506104da600480360360408110156104a457600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff16906020019092919080359060200190929190505050610ec9565b604051808215151515815260200191505060405180910390f35b34801561050057600080fd5b506105636004803603604081101561051757600080fd5b81019080803573ffffffffffffffffffffffffffffffffffffffff169060200190929190803573ffffffffffffffffffffffffffffffffffffffff169060200190929190505050610ee0565b6040518082815260200191505060405180910390f35b606060038054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156106115780601f106105e657610100808354040283529160200191610611565b820191906000526020600020905b8154815290600101906020018083116105f457829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415151561065857600080fd5b81600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925846040518082815260200191505060405180910390a36001905092915050565b6000600254905090565b60006107e382600160008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000208190555061086e848484610f89565b3373ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160008873ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a3600190509392505050565b6000600560009054906101000a900460ff16905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff16141515156109ae57600080fd5b610a3d82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b606060048054600181600116156101000203166002900480601f016020809104026020016040519081016040528092919081815260200182805460018160011615610100020316600290048015610c885780601f10610c5d57610100808354040283529160200191610c88565b820191906000526020600020905b815481529060010190602001808311610c6b57829003601f168201915b5050505050905090565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1614151515610ccf57600080fd5b610d5e82600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925600160003373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546040518082815260200191505060405180910390a36001905092915050565b6000610ed6338484610f89565b6001905092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b6000828211151515610f7857600080fd5b600082840390508091505092915050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614151515610fc557600080fd5b611016816000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054610f6790919063ffffffff16565b6000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055506110a9816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205461115590919063ffffffff16565b6000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040518082815260200191505060405180910390a3505050565b600080828401905083811015151561116c57600080fd5b809150509291505056fea165627a7a723058205fcdfea06f4d97b442bc9f444b1e92524bc66398eb4f37ed5a99f2093a8842640029000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000186a00000000000000000000000000000000000000000000000000000000000000003545354000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000035453540000000000000000000000000000000000000000000000000000000000', + gas: '4700000', + gasPrice: '20000000000', + }, (error, contract) => { + if (error) { + tokenAddress.innerHTML = 'Creation Failed' + throw error + } else if (contract.address === undefined) { + return + } + + console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash) + tokenAddress.innerHTML = contract.address + transferTokens.disabled = false + approveTokens.disabled = false + transferTokensWithoutGas.disabled = false + approveTokensWithoutGas.disabled = false + + transferTokens.onclick = (event) => { + console.log(`event`, event) + contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { + from: accounts[0], + to: contract.address, + data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', + gas: 60000, + gasPrice: '20000000000', + }, (result) => { + console.log('result', result) + }) + } + + approveTokens.onclick = () => { + contract.approve('0x9bc5baF874d2DA8D216aE9f137804184EE5AfEF4', '70000', { + from: accounts[0], + to: contract.address, + data: '0x095ea7b30000000000000000000000009bc5baF874d2DA8D216aE9f137804184EE5AfEF40000000000000000000000000000000000000000000000000000000000000005', + gas: 60000, + gasPrice: '20000000000', + }, (result) => { + console.log(result) + }) + } + + transferTokensWithoutGas.onclick = (event) => { + console.log(`event`, event) + contract.transfer('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '15000', { + from: accounts[0], + to: contract.address, + data: '0xa9059cbb0000000000000000000000002f318c334780961fb129d2a6c30d0763d9a5c9700000000000000000000000000000000000000000000000000000000000003a98', + gasPrice: '20000000000', + }, (result) => { + console.log('result', result) + }) + } + + approveTokensWithoutGas.onclick = () => { + contract.approve('0x2f318C334780961FB129D2a6c30D0763d9a5C970', '70000', { + from: accounts[0], + to: contract.address, + data: '0x095ea7b30000000000000000000000002f318C334780961FB129D2a6c30D0763d9a5C9700000000000000000000000000000000000000000000000000000000000000005', + gasPrice: '20000000000', + }, (result) => { + console.log(result) + }) + } + } + ) + } + + signTypedData.addEventListener('click', () => { + const typedData = { + types: { + EIP712Domain: [ + { name: 'name', type: 'string' }, + { name: 'version', type: 'string' }, + { name: 'chainId', type: 'uint256' }, + { name: 'verifyingContract', type: 'address' }, + ], + Person: [ + { name: 'name', type: 'string' }, + { name: 'wallet', type: 'address' }, + ], + Mail: [ + { name: 'from', type: 'Person' }, + { name: 'to', type: 'Person' }, + { name: 'contents', type: 'string' }, + ], + }, + primaryType: 'Mail', + domain: { + name: 'Ether Mail', + version: '1', + chainId: 3, + verifyingContract: '0xCcCCccccCCCCcCCCCCCcCcCccCcCCCcCcccccccC', + }, + message: { + sender: { + name: 'Cow', + wallet: '0xCD2a3d9F938E13CD947Ec05AbC7FE734Df8DD826', + }, + recipient: { + name: 'Bob', + wallet: '0xbBbBBBBbbBBBbbbBbbBbbbbBBbBbbbbBbBbbBBbB', + }, + contents: 'Hello, Bob!', + }, + } + web3.currentProvider.sendAsync({ + method: 'eth_signTypedData_v3', + params: [ethereum.selectedAddress, JSON.stringify(typedData)], + from: ethereum.selectedAddress, + }, (err, result) => { + if (err) { + console.log(err) + } else { + signTypedDataResults.innerHTML = result + } + }) }) - }) -}) + + } + + updateButtons() + if (isMetaMaskInstalled()) { + ethereum.autoRefreshOnNetworkChange = false + ethereum.on('networkChanged', (networkId) => { + networkDiv.innerHTML = networkId + }) + ethereum.on('chainIdChanged', (chainId) => { + chainIdDiv.innerHTML = chainId + }) + ethereum.on('accountsChanged', (newAccounts) => { + const connecting = Boolean((!accounts || !accounts.length) && newAccounts && newAccounts.length) + accounts = newAccounts + accountsDiv.innerHTML = accounts + if (connecting) { + initializeAccountButtons() + } + updateButtons() + }) + } +} +window.addEventListener('DOMContentLoaded', initialize) diff --git a/test/e2e/contract-test/index.html b/test/e2e/contract-test/index.html index 9689654ee..9454a67dd 100644 --- a/test/e2e/contract-test/index.html +++ b/test/e2e/contract-test/index.html @@ -1,51 +1,64 @@ + E2E Test Dapp + + -
-
Contract
-
- - - -
-
- Not clicked -
-
-
-
Send eth
-
+
+

E2E Test Dapp

+
+
+
+

Connect

+ +
+
+

Contract

+
+ + + +
+
+ Contract Status: Not clicked +
+
+
+

Send Eth

-
-
-
-
Send tokens
-
-
- - - - - -
-
-
-
Network:
-
ChainId:
-
Accounts:
-
-
-
-
Sign Typed Data
-
+ +
+

Send Tokens

+
+ Token: +
+
+ + + + + +
+
+
+

Status

+
+ Network: +
+
+ ChainId: +
+
+ Accounts: +
+
+
+

Sign Typed Data

-
Sign Typed Data Result:
-
-
- - +
Sign Typed Data Result:
+ + - - \ No newline at end of file + diff --git a/test/e2e/ethereum-on.spec.js b/test/e2e/ethereum-on.spec.js index 9c6cd4ceb..ca062ca26 100644 --- a/test/e2e/ethereum-on.spec.js +++ b/test/e2e/ethereum-on.spec.js @@ -117,6 +117,11 @@ describe('MetaMask', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() @@ -132,9 +137,9 @@ describe('MetaMask', function () { await delay(regularDelayMs) }) - it('has not set the network within the dapp', async () => { + it('has the ganache network id within the dapp', async () => { const networkDiv = await findElement(driver, By.css('#network')) - assert.equal(await networkDiv.getText(), '') + assert.equal(await networkDiv.getText(), '5777') }) it('changes the network', async () => { diff --git a/test/e2e/metamask-ui.spec.js b/test/e2e/metamask-ui.spec.js index a0efe56f9..b5a8220b0 100644 --- a/test/e2e/metamask-ui.spec.js +++ b/test/e2e/metamask-ui.spec.js @@ -436,6 +436,11 @@ describe('MetaMask', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) windowHandles = await driver.getAllWindowHandles() diff --git a/test/e2e/run-web3.sh b/test/e2e/run-web3.sh index 174370683..729333b84 100755 --- a/test/e2e/run-web3.sh +++ b/test/e2e/run-web3.sh @@ -9,5 +9,5 @@ export PATH="$PATH:./node_modules/.bin" concurrently --kill-others \ --names 'dapp,e2e' \ --prefix '[{time}][{name}]' \ - 'static-server test/web3 --port 8080' \ + 'node development/static-server.js test/web3 --port 8080' \ 'sleep 5 && mocha test/e2e/web3.spec' diff --git a/test/e2e/signature-request.spec.js b/test/e2e/signature-request.spec.js index e9490e08d..a5be61baf 100644 --- a/test/e2e/signature-request.spec.js +++ b/test/e2e/signature-request.spec.js @@ -128,6 +128,11 @@ describe('MetaMask', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) windowHandles = await driver.getAllWindowHandles() diff --git a/test/e2e/web3.spec.js b/test/e2e/web3.spec.js index 6dd081322..f576b397f 100644 --- a/test/e2e/web3.spec.js +++ b/test/e2e/web3.spec.js @@ -126,6 +126,11 @@ describe('Using MetaMask with an existing account', function () { await openNewPage(driver, 'http://127.0.0.1:8080/') await delay(regularDelayMs) + const connectButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Connect')]`)) + await connectButton.click() + + await delay(regularDelayMs) + await waitUntilXWindowHandles(driver, 3) const windowHandles = await driver.getAllWindowHandles() diff --git a/ui/app/components/ui/snackbar/index.js b/ui/app/components/ui/snackbar/index.js new file mode 100644 index 000000000..3d3e0394d --- /dev/null +++ b/ui/app/components/ui/snackbar/index.js @@ -0,0 +1 @@ +export { default } from './snackbar.component' diff --git a/ui/app/components/ui/snackbar/index.scss b/ui/app/components/ui/snackbar/index.scss new file mode 100644 index 000000000..5cfab7a9b --- /dev/null +++ b/ui/app/components/ui/snackbar/index.scss @@ -0,0 +1,11 @@ +.snackbar { + padding: .75rem 1rem; + font-size: 0.75rem; + color: $Blue-600; + min-width: 360px; + width: fit-content; + + background: $Blue-000; + border: 1px solid $Blue-200; + border-radius: 6px; +} diff --git a/ui/app/components/ui/snackbar/snackbar.component.js b/ui/app/components/ui/snackbar/snackbar.component.js new file mode 100644 index 000000000..8945341fe --- /dev/null +++ b/ui/app/components/ui/snackbar/snackbar.component.js @@ -0,0 +1,18 @@ +import React from 'react' +import PropTypes from 'prop-types' +import classnames from 'classnames' + +const Snackbar = ({ className = '', content }) => { + return ( +
+ { content } +
+ ) +} + +Snackbar.propTypes = { + className: PropTypes.string, + content: PropTypes.string.isRequired, +} + +module.exports = Snackbar diff --git a/ui/app/css/itcss/components/index.scss b/ui/app/css/itcss/components/index.scss index 0e2034670..01c913a1a 100644 --- a/ui/app/css/itcss/components/index.scss +++ b/ui/app/css/itcss/components/index.scss @@ -1,5 +1,6 @@ @import '../../../components/ui/button/buttons'; @import '../../../components/ui/dialog/dialog'; +@import '../../../components/ui/snackbar/index'; @import './footer.scss'; diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js index 8cbf4d69f..f603c827b 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.component.js @@ -1,8 +1,10 @@ import React, { PureComponent } from 'react' import PropTypes from 'prop-types' import Button from '../../../components/ui/button' +import Snackbar from '../../../components/ui/snackbar' import MetaFoxLogo from '../../../components/ui/metafox-logo' import { DEFAULT_ROUTE } from '../../../helpers/constants/routes' +import { returnToOnboardingInitiator } from '../onboarding-initiator-util' export default class EndOfFlowScreen extends PureComponent { static contextTypes = { @@ -14,11 +16,33 @@ export default class EndOfFlowScreen extends PureComponent { history: PropTypes.object, completeOnboarding: PropTypes.func, completionMetaMetricsName: PropTypes.string, + onboardingInitiator: PropTypes.exact({ + location: PropTypes.string, + tabId: PropTypes.number, + }), + } + + onComplete = async () => { + const { history, completeOnboarding, completionMetaMetricsName, onboardingInitiator } = this.props + + await completeOnboarding() + this.context.metricsEvent({ + eventOpts: { + category: 'Onboarding', + action: 'Onboarding Complete', + name: completionMetaMetricsName, + }, + }) + + if (onboardingInitiator) { + await returnToOnboardingInitiator(onboardingInitiator) + } + history.push(DEFAULT_ROUTE) } render () { const { t } = this.context - const { history, completeOnboarding, completionMetaMetricsName } = this.props + const { onboardingInitiator } = this.props return (
@@ -62,20 +86,17 @@ export default class EndOfFlowScreen extends PureComponent { + { + onboardingInitiator ? + : + null + }
) } diff --git a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js index 38313806c..2bb9ef15a 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js +++ b/ui/app/pages/first-time-flow/end-of-flow/end-of-flow.container.js @@ -1,21 +1,22 @@ import { connect } from 'react-redux' import EndOfFlow from './end-of-flow.component' import { setCompletedOnboarding } from '../../../store/actions' +import { getOnboardingInitiator } from '../first-time-flow.selectors' const firstTimeFlowTypeNameMap = { create: 'New Wallet Created', 'import': 'New Wallet Imported', } -const mapStateToProps = ({ metamask }) => { - const { firstTimeFlowType } = metamask +const mapStateToProps = (state) => { + const { metamask: { firstTimeFlowType } } = state return { completionMetaMetricsName: firstTimeFlowTypeNameMap[firstTimeFlowType], + onboardingInitiator: getOnboardingInitiator(state), } } - const mapDispatchToProps = dispatch => { return { completeOnboarding: () => dispatch(setCompletedOnboarding()), diff --git a/ui/app/pages/first-time-flow/end-of-flow/index.scss b/ui/app/pages/first-time-flow/end-of-flow/index.scss index d7eb4513b..de603fce4 100644 --- a/ui/app/pages/first-time-flow/end-of-flow/index.scss +++ b/ui/app/pages/first-time-flow/end-of-flow/index.scss @@ -50,4 +50,4 @@ font-size: 80px; margin-top: 70px; } -} \ No newline at end of file +} diff --git a/ui/app/pages/first-time-flow/first-time-flow.selectors.js b/ui/app/pages/first-time-flow/first-time-flow.selectors.js index e6cd5a84a..74cad5e12 100644 --- a/ui/app/pages/first-time-flow/first-time-flow.selectors.js +++ b/ui/app/pages/first-time-flow/first-time-flow.selectors.js @@ -4,12 +4,6 @@ import { DEFAULT_ROUTE, } from '../../helpers/constants/routes' -const selectors = { - getFirstTimeFlowTypeRoute, -} - -module.exports = selectors - function getFirstTimeFlowTypeRoute (state) { const { firstTimeFlowType } = state.metamask @@ -24,3 +18,25 @@ function getFirstTimeFlowTypeRoute (state) { return nextRoute } + +const getOnboardingInitiator = (state) => { + const { onboardingTabs } = state.metamask + + if (!onboardingTabs || Object.keys(onboardingTabs).length !== 1) { + return null + } + + const location = Object.keys(onboardingTabs)[0] + const tabId = onboardingTabs[location] + return { + location, + tabId, + } +} + +const selectors = { + getFirstTimeFlowTypeRoute, + getOnboardingInitiator, +} + +module.exports = selectors diff --git a/ui/app/pages/first-time-flow/onboarding-initiator-util.js b/ui/app/pages/first-time-flow/onboarding-initiator-util.js new file mode 100644 index 000000000..dd70085f6 --- /dev/null +++ b/ui/app/pages/first-time-flow/onboarding-initiator-util.js @@ -0,0 +1,48 @@ +import extension from 'extensionizer' +import log from 'loglevel' + +const returnToOnboardingInitiatorTab = async (onboardingInitiator) => { + const tab = await (new Promise((resolve) => { + extension.tabs.update(onboardingInitiator.tabId, { active: true }, (tab) => { + if (tab) { + resolve(tab) + } else { + // silence console message about unchecked error + if (extension.runtime.lastError) { + log.debug(extension.runtime.lastError) + } + resolve() + } + }) + })) + + if (!tab) { + // this case can happen if the tab was closed since being checked with `extension.tabs.get` + log.warn(`Setting current tab to onboarding initator has failed; falling back to redirect`) + window.location.assign(onboardingInitiator.location) + } else { + window.close() + } +} + +export const returnToOnboardingInitiator = async (onboardingInitiator) => { + const tab = await (new Promise((resolve) => { + extension.tabs.get(onboardingInitiator.tabId, (tab) => { + if (tab) { + resolve(tab) + } else { + // silence console message about unchecked error + if (extension.runtime.lastError) { + log.debug(extension.runtime.lastError) + } + resolve() + } + }) + })) + + if (tab) { + await returnToOnboardingInitiatorTab(onboardingInitiator) + } else { + window.location.assign(onboardingInitiator.location) + } +} diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss index dfe9868cf..583d92a0b 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/index.scss @@ -60,7 +60,7 @@ } button { - margin-top: 0xp; + margin-top: 0px; } &__buttons { diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js index 4144fb878..bd45a61e8 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.component.js @@ -3,8 +3,10 @@ import PropTypes from 'prop-types' import classnames from 'classnames' import LockIcon from '../../../../components/ui/lock-icon' import Button from '../../../../components/ui/button' +import Snackbar from '../../../../components/ui/snackbar' import { INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE, DEFAULT_ROUTE } from '../../../../helpers/constants/routes' import { exportAsFile } from '../../../../helpers/utils/util' +import { returnToOnboardingInitiator } from '../../onboarding-initiator-util' export default class RevealSeedPhrase extends PureComponent { static contextTypes = { @@ -17,6 +19,10 @@ export default class RevealSeedPhrase extends PureComponent { seedPhrase: PropTypes.string, setSeedPhraseBackedUp: PropTypes.func, setCompletedOnboarding: PropTypes.func, + onboardingInitiator: PropTypes.exact({ + location: PropTypes.string, + tabId: PropTypes.number, + }), } state = { @@ -27,8 +33,7 @@ export default class RevealSeedPhrase extends PureComponent { exportAsFile('MetaMask Secret Backup Phrase', this.props.seedPhrase, 'text/plain') } - handleNext = event => { - event.preventDefault() + handleNext = () => { const { isShowingSeedPhrase } = this.state const { history } = this.props @@ -47,9 +52,8 @@ export default class RevealSeedPhrase extends PureComponent { history.push(INITIALIZE_CONFIRM_SEED_PHRASE_ROUTE) } - handleSkip = event => { - event.preventDefault() - const { history, setSeedPhraseBackedUp, setCompletedOnboarding } = this.props + handleSkip = async () => { + const { history, setSeedPhraseBackedUp, setCompletedOnboarding, onboardingInitiator } = this.props this.context.metricsEvent({ eventOpts: { @@ -59,10 +63,12 @@ export default class RevealSeedPhrase extends PureComponent { }, }) - Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) - .then(() => { - history.push(DEFAULT_ROUTE) - }) + await Promise.all([setCompletedOnboarding(), setSeedPhraseBackedUp(false)]) + + if (onboardingInitiator) { + await returnToOnboardingInitiator(onboardingInitiator) + } + history.push(DEFAULT_ROUTE) } renderSecretWordsContainer () { @@ -111,6 +117,7 @@ export default class RevealSeedPhrase extends PureComponent { render () { const { t } = this.context const { isShowingSeedPhrase } = this.state + const { onboardingInitiator } = this.props return (
@@ -166,6 +173,13 @@ export default class RevealSeedPhrase extends PureComponent { { t('next') }
+ { + onboardingInitiator ? + : + null + }
) } diff --git a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js index 7ada36afc..11a26fb6d 100644 --- a/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js +++ b/ui/app/pages/first-time-flow/seed-phrase/reveal-seed-phrase/reveal-seed-phrase.container.js @@ -4,6 +4,13 @@ import { setCompletedOnboarding, setSeedPhraseBackedUp, } from '../../../../store/actions' +import { getOnboardingInitiator } from '../../first-time-flow.selectors' + +const mapStateToProps = (state) => { + return { + onboardingInitiator: getOnboardingInitiator(state), + } +} const mapDispatchToProps = dispatch => { return { @@ -12,4 +19,4 @@ const mapDispatchToProps = dispatch => { } } -export default connect(null, mapDispatchToProps)(RevealSeedPhrase) +export default connect(mapStateToProps, mapDispatchToProps)(RevealSeedPhrase) diff --git a/yarn.lock b/yarn.lock index 5e9578a16..edd2a97fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1985,6 +1985,18 @@ scroll "^2.0.3" warning "^3.0.0" +"@metamask/forwarder@^1.0.0": + version "1.0.0" + resolved "https://registry.yarnpkg.com/@metamask/forwarder/-/forwarder-1.0.0.tgz#3e321022a36561cc6e7b7c84df25f600925f4d95" + integrity sha512-ufgPndhZz0oNhRrixiR6cXH/HwtFwurWvbrU8zAZsFnf1hB4L2VB2Wey/P1wStIx+BJJQjyROvCDyPDoz4ny1A== + +"@metamask/onboarding@^0.1.2": + version "0.1.2" + resolved "https://registry.yarnpkg.com/@metamask/onboarding/-/onboarding-0.1.2.tgz#d5126cbb5e593d782645d6236c497e27bd38d3c4" + integrity sha512-+85Z5OxckGuYr5cCoMlpxASu9geJBMYvwkNWqa5qDDEYKZ8eNXHsADcVYFsvBhxFcf87dC7ty1kWljYVEfTIIA== + dependencies: + bowser "^2.5.4" + "@mrmlnc/readdir-enhanced@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz#524af240d1a360527b730475ecfa1344aa540dde" @@ -3560,11 +3572,6 @@ ansi-red@^0.1.1: dependencies: ansi-wrap "0.1.0" -ansi-regex@^0.2.0, ansi-regex@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-0.2.1.tgz#0d8e946967a3d8143f93e24e298525fc1b2235f9" - integrity sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk= - ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" @@ -3580,11 +3587,6 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== -ansi-styles@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-1.1.0.tgz#eaecbf66cd706882760b2f4691582b8f55d7a7de" - integrity sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94= - ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -4025,10 +4027,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1, assert@^1.3.0, assert@^1.4.0, assert@^1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" - integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= + version "1.5.0" + resolved "https://registry.yarnpkg.com/assert/-/assert-1.5.0.tgz#55c109aaf6e0aefdb3dc4b71240c70bf574b18eb" + integrity sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA== dependencies: + object-assign "^4.1.1" util "0.10.3" assertion-error@^1.0.1: @@ -5640,6 +5643,11 @@ bowser@^1.7.3: resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.3.tgz#6643ae4d783f31683f6d23156976b74183862162" integrity sha512-/gp96UlcFw5DbV2KQPCqTqi0Mb9gZRyDAHiDsGEH+4B/KOQjeoE5lM1PxlVX8DQDvfEfitmC1rW2Oy8fk/XBDg== +bowser@^2.5.4: + version "2.7.0" + resolved "https://registry.yarnpkg.com/bowser/-/bowser-2.7.0.tgz#96eab1fa07fab08c1ec4c75977a7c8ddf8e0fe1f" + integrity sha512-aIlMvstvu8x+34KEiOHD3AsBgdrzg6sxALYiukOWhFvGMbQI6TRP/iY0LMhUrHs56aD6P1G0Z7h45PUJaa5m9w== + boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b" @@ -6435,17 +6443,6 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.2, chalk@^2.4.1, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-0.5.1.tgz#663b3a648b68b55d04690d49167aa837858f2174" - integrity sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ= - dependencies: - ansi-styles "^1.1.0" - escape-string-regexp "^1.0.0" - has-ansi "^0.1.0" - strip-ansi "^0.3.0" - supports-color "^0.2.0" - chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -7023,7 +7020,7 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-1.0.7.tgz#419cd7fb3258b1ed838dc0953167a25e152f5b59" integrity sha512-Jrx3xsP4pPv4AwJUDWY9wOXGtwPXARej6Xd99h4TUGotmf8APuquKMpK+dnD3UgyxK7OEWaisjZz+3b5jtL6xQ== -commander@2, commander@2.11.0, commander@^2.3.0, commander@^2.5.0, commander@^2.6.0: +commander@2, commander@2.11.0, commander@^2.5.0, commander@^2.6.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== @@ -7237,6 +7234,11 @@ contains-path@^0.1.0: resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= +content-disposition@0.5.2: + version "0.5.2" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" + integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ= + content-disposition@0.5.3, content-disposition@^0.5.2: version "0.5.3" resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd" @@ -9598,7 +9600,7 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: +escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.3, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= @@ -11308,6 +11310,13 @@ fast-safe-stringify@^2.0.6, fast-safe-stringify@^2.0.7: resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz#124aa885899261f68aedb42a7c080de9da608743" integrity sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA== +fast-url-parser@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d" + integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0= + dependencies: + punycode "^1.3.2" + fast-write-atomic@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/fast-write-atomic/-/fast-write-atomic-0.2.1.tgz#7ee8ef0ce3c1f531043c09ae8e5143361ab17ede" @@ -11442,11 +11451,6 @@ file-loader@^3.0.1: loader-utils "^1.0.2" schema-utils "^1.0.0" -file-size@0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/file-size/-/file-size-0.0.5.tgz#057d43c3a3ed735da3f90d6052ab380f1e6d5e3b" - integrity sha1-BX1Dw6Ptc12j+Q1gUqs4Dx5tXjs= - file-system-cache@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/file-system-cache/-/file-system-cache-1.0.5.tgz#84259b36a2bbb8d3d6eb1021d3132ffe64cfff4f" @@ -13288,13 +13292,6 @@ har-validator@~5.1.0: ajv "^6.5.5" har-schema "^2.0.0" -has-ansi@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-0.1.0.tgz#84f265aae8c0e6a88a12d7022894b7568894c62e" - integrity sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4= - dependencies: - ansi-regex "^0.2.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" @@ -18311,11 +18308,23 @@ mime-db@^1.28.0: resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.41.0.tgz#9110408e1f6aa1b34aef51f2c9df3caddf46b6a0" integrity sha512-B5gxBI+2K431XW8C2rcc/lhppbuji67nf9v39eH8pkWoZDxnAL0PxdpH32KYRScniF8qDHBDlI+ipgg5WrCUYw== +mime-db@~1.33.0: + version "1.33.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db" + integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ== + mime-db@~1.38.0: version "1.38.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== +mime-types@2.1.18: + version "2.1.18" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8" + integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ== + dependencies: + mime-db "~1.33.0" + mime-types@^2.1.12, mime-types@^2.1.21, mime-types@~2.1.16, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.24" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.24.tgz#b6f8d0b3e951efb77dedeca194cff6d16f676f81" @@ -18335,7 +18344,7 @@ mime@1.6.0, mime@^1.6.0: resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" integrity sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg== -mime@^1.2.11, mime@^1.4.1: +mime@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" integrity sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ== @@ -19766,13 +19775,6 @@ opn@5.4.0: dependencies: is-wsl "^1.1.0" -opn@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" - integrity sha512-Jd/GpzPyHF4P2/aNOVmS3lfMSWV9J7cOhCG1s08XCEAsPkB7lp6ddiU0J7XzyQRDUh8BqJ7PchfINjR8jyofRQ== - dependencies: - is-wsl "^1.1.0" - optimist@0.6.x, optimist@^0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" @@ -20443,7 +20445,7 @@ path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= -path-is-inside@^1.0.1, path-is-inside@^1.0.2: +path-is-inside@1.0.2, path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= @@ -20480,6 +20482,11 @@ path-to-regexp@0.1.7: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45" + integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ== + path-to-regexp@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" @@ -21928,6 +21935,11 @@ randomhex@0.1.5: resolved "https://registry.yarnpkg.com/randomhex/-/randomhex-0.1.5.tgz#baceef982329091400f2a2912c6cd02f1094f585" integrity sha1-us7vmCMpCRQA8qKRLGzQLxCU9YU= +range-parser@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" + integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4= + range-parser@^1.2.0, range-parser@^1.2.1, range-parser@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" @@ -24148,6 +24160,20 @@ serve-favicon@^2.5.0: parseurl "~1.3.2" safe-buffer "5.1.1" +serve-handler@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.2.tgz#f05b0421a313fff2d257838cba00cbcc512cd2b6" + integrity sha512-RFh49wX7zJmmOVDcIjiDSJnMH+ItQEvyuYLYuDBVoA/xmQSCuj+uRmk1cmBB5QQlI3qOiWKp6p4DUGY+Z5AB2A== + dependencies: + bytes "3.0.0" + content-disposition "0.5.2" + fast-url-parser "1.1.3" + mime-types "2.1.18" + minimatch "3.0.4" + path-is-inside "1.0.2" + path-to-regexp "2.2.1" + range-parser "1.2.0" + serve-static@1.14.1: version "1.14.1" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.14.1.tgz#666e636dc4f010f7ef29970a88a674320898b2f9" @@ -25048,17 +25074,6 @@ static-module@^2.2.0: static-eval "^2.0.0" through2 "~2.0.3" -static-server@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/static-server/-/static-server-2.2.1.tgz#49e3cae2a001736b0ee9e95d21d3d843fc95efaa" - integrity sha512-j5eeW6higxYNmXMIT8iHjsdiViTpQDthg7o+SHsRtqdbxscdHqBHXwrXjHC8hL3F0Tsu34ApUpDkwzMBPBsrLw== - dependencies: - chalk "^0.5.1" - commander "^2.3.0" - file-size "0.0.5" - mime "^1.2.11" - opn "^5.2.0" - "statuses@>= 1.3.1 < 2": version "1.3.1" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.3.1.tgz#faf51b9eb74aaef3b3acf4ad5f61abf24cb7b93e" @@ -25363,13 +25378,6 @@ strip-ansi@5.2.0, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" -strip-ansi@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-0.3.0.tgz#25f48ea22ca79187f3174a4db8759347bb126220" - integrity sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA= - dependencies: - ansi-regex "^0.2.1" - strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -25639,11 +25647,6 @@ supports-color@4.4.0: dependencies: has-flag "^2.0.0" -supports-color@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-0.2.0.tgz#d92de2694eb3f67323973d7ae3d8b55b4c22190a" - integrity sha1-2S3iaU6z9nMjlz1649i1W0wiGQo= - supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7"