diff options
34 files changed, 1029 insertions, 473 deletions
diff --git a/.circleci/config.yml b/.circleci/config.yml index ae6f44c8a..98f27030e 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -98,6 +98,32 @@ jobs: key: build-cache-{{ .Revision }} paths: - dist + - store_artifacts: + path: dist/mascara + destination: builds/mascara + - store_artifacts: + path: builds + destination: builds + - run: + name: build:announce + command: | + CIRCLE_PR_NUMBER="${CIRCLE_PR_NUMBER:-${CIRCLE_PULL_REQUEST##*/}}" + SHORT_SHA1=$(echo $CIRCLE_SHA1 | cut -c 1-7) + BUILD_LINK_BASE="https://$CIRCLE_BUILD_NUM-42009758-gh.circle-artifacts.com/0/builds" + VERSION=$(node -p 'require("./dist/chrome/manifest.json").version') + MASCARA="$BUILD_LINK_BASE/mascara/home.html" + CHROME="$BUILD_LINK_BASE/metamask-chrome-$VERSION.zip" + FIREFOX="$BUILD_LINK_BASE/metamask-firefox-$VERSION.zip" + OPERA="$BUILD_LINK_BASE/metamask-opera-$VERSION.zip" + EDGE="$BUILD_LINK_BASE/metamask-edge-$VERSION.zip" + COMMENT_MAIN="Builds ready [$SHORT_SHA1]: [mascara][mascara], [chrome][chrome], [firefox][firefox], [edge][edge], [opera][opera]" + COMMENT_LINKS="[mascara]:$MASCARA\n[chrome]:$CHROME\n[firefox]:$FIREFOX\n[opera]:$OPERA\n[edge]:$EDGE\n" + COMMENT_BODY="$COMMENT_MAIN\n\n$COMMENT_LINKS" + JSON_PAYLOAD="{\"body\":\"$COMMENT_BODY\"}" + POST_COMMENT_URI="https://api.github.com/repos/metamask/metamask-extension/issues/$CIRCLE_PR_NUMBER/comments" + echo "Announcement:\n$COMMENT_BODY" + echo "Posting to $POST_COMMENT_URI" + curl -d "$JSON_PAYLOAD" -H "Authorization: token $GITHUB_COMMENT_TOKEN" $POST_COMMENT_URI prep-scss: docker: diff --git a/app/home.html b/app/home.html index cfb4b00a0..4fad0f993 100644 --- a/app/home.html +++ b/app/home.html @@ -3,10 +3,10 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> - <title>MetaMask Plugin</title> + <title>MetaMask</title> </head> <body> <div id="app-content"></div> - <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + <script src="./ui.js" type="text/javascript" charset="utf-8"></script> </body> </html> diff --git a/app/manifest.json b/app/manifest.json index a20f9b976..1982b4820 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -27,8 +27,8 @@ "default_locale": "en", "background": { "scripts": [ - "scripts/chromereload.js", - "scripts/background.js" + "chromereload.js", + "background.js" ], "persistent": true }, @@ -48,7 +48,7 @@ "https://*/*" ], "js": [ - "scripts/contentscript.js" + "contentscript.js" ], "run_at": "document_start", "all_frames": true @@ -62,11 +62,11 @@ "https://*.infura.io/" ], "web_accessible_resources": [ - "scripts/inpage.js" + "inpage.js" ], "externally_connectable": { "matches": [ "https://metamask.io/*" ] } -}
\ No newline at end of file +} diff --git a/app/notification.html b/app/notification.html index f10cbbf41..457ba7137 100644 --- a/app/notification.html +++ b/app/notification.html @@ -11,6 +11,6 @@ </head> <body class="notification" style="height:600px;"> <div id="app-content"></div> - <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + <script src="./ui.js" type="text/javascript" charset="utf-8"></script> </body> </html> diff --git a/app/popup.html b/app/popup.html index bf09b97ca..3acfd8c55 100644 --- a/app/popup.html +++ b/app/popup.html @@ -3,10 +3,10 @@ <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1 user-scalable=no"> - <title>MetaMask Plugin</title> + <title>MetaMask</title> </head> <body style="width:357px; height:600px;"> <div id="app-content"></div> - <script src="./scripts/popup.js" type="text/javascript" charset="utf-8"></script> + <script src="./ui.js" type="text/javascript" charset="utf-8"></script> </body> </html> diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7abbc60e7..2098fae27 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -7,8 +7,8 @@ const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('./lib/port-stream.js') -const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'scripts', 'inpage.js')).toString() -const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('scripts/inpage.js') + '\n' +const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() +const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' const inpageBundle = inpageContent + inpageSuffix // Eventually this streaming injection could be replaced with: @@ -96,7 +96,7 @@ function logStreamDisconnectWarning (remoteLabel, err) { } function shouldInjectWeb3 () { - return doctypeCheck() && suffixCheck() + return doctypeCheck() && suffixCheck() && documentElementCheck() && !blacklistedDomainCheck() } diff --git a/app/scripts/controllers/transactions.js b/app/scripts/controllers/transactions.js index 7e2cc15da..a18a2d2e2 100644 --- a/app/scripts/controllers/transactions.js +++ b/app/scripts/controllers/transactions.js @@ -161,9 +161,11 @@ module.exports = class TransactionController extends EventEmitter { this.emit(`${txMeta.id}:unapproved`, txMeta) } - async newUnapprovedTransaction (txParams) { + async newUnapprovedTransaction (txParams, opts = {}) { log.debug(`MetaMaskController newUnapprovedTransaction ${JSON.stringify(txParams)}`) const initialTxMeta = await this.addUnapprovedTransaction(txParams) + initialTxMeta.origin = opts.origin + this.txStateManager.updateTx(initialTxMeta, '#newUnapprovedTransaction - adding the origin') // listen for tx completion (success, fail) return new Promise((resolve, reject) => { this.txStateManager.once(`${initialTxMeta.id}:finished`, (finishedTxMeta) => { diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 4422a5cf3..b96acc9da 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -57,7 +57,6 @@ module.exports = class MetamaskController extends EventEmitter { this.defaultMaxListeners = 20 this.sendUpdate = debounce(this.privateSendUpdate.bind(this), 200) - this.opts = opts const initState = opts.initState || {} this.recordFirstTimeInfo(initState) @@ -242,6 +241,11 @@ module.exports = class MetamaskController extends EventEmitter { static: { eth_syncing: false, web3_clientVersion: `MetaMask/v${version}`, + eth_sendTransaction: (payload, next, end) => { + const origin = payload.origin + const txParams = payload.params[0] + nodeify(this.txController.newUnapprovedTransaction, this.txController)(txParams, { origin }, end) + }, }, // account mgmt getAccounts: (cb) => { @@ -256,7 +260,6 @@ module.exports = class MetamaskController extends EventEmitter { cb(null, result) }, // tx signing - processTransaction: nodeify(async (txParams) => await this.txController.newUnapprovedTransaction(txParams), this), // old style msg signing processMessage: this.newUnsignedMessage.bind(this), // personal_sign msg signing diff --git a/app/scripts/popup.js b/app/scripts/ui.js index 13c7ac5ec..13c7ac5ec 100644 --- a/app/scripts/popup.js +++ b/app/scripts/ui.js diff --git a/gulpfile.js b/gulpfile.js index f57ea6206..1eb0a974b 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,37 +1,48 @@ -var watchify = require('watchify') -var browserify = require('browserify') -var disc = require('disc') -var gulp = require('gulp') -var source = require('vinyl-source-stream') -var buffer = require('vinyl-buffer') -var gutil = require('gulp-util') -var watch = require('gulp-watch') -var sourcemaps = require('gulp-sourcemaps') -var jsoneditor = require('gulp-json-editor') -var zip = require('gulp-zip') -var assign = require('lodash.assign') -var livereload = require('gulp-livereload') -var del = require('del') -var eslint = require('gulp-eslint') -var fs = require('fs') -var path = require('path') -var manifest = require('./app/manifest.json') -var gulpif = require('gulp-if') -var replace = require('gulp-replace') -var mkdirp = require('mkdirp') -var asyncEach = require('async/each') -var exec = require('child_process').exec -var sass = require('gulp-sass') -var autoprefixer = require('gulp-autoprefixer') -var gulpStylelint = require('gulp-stylelint') -var stylefmt = require('gulp-stylefmt') -var uglify = require('gulp-uglify-es').default -var babel = require('gulp-babel') - - -var disableDebugTools = gutil.env.disableDebugTools -var debug = gutil.env.debug - +const watchify = require('watchify') +const browserify = require('browserify') +const disc = require('disc') +const gulp = require('gulp') +const source = require('vinyl-source-stream') +const buffer = require('vinyl-buffer') +const gutil = require('gulp-util') +const watch = require('gulp-watch') +const sourcemaps = require('gulp-sourcemaps') +const jsoneditor = require('gulp-json-editor') +const zip = require('gulp-zip') +const assign = require('lodash.assign') +const livereload = require('gulp-livereload') +const del = require('del') +const eslint = require('gulp-eslint') +const fs = require('fs') +const path = require('path') +const manifest = require('./app/manifest.json') +const replace = require('gulp-replace') +const mkdirp = require('mkdirp') +const asyncEach = require('async/each') +const exec = require('child_process').exec +const sass = require('gulp-sass') +const autoprefixer = require('gulp-autoprefixer') +const gulpStylelint = require('gulp-stylelint') +const stylefmt = require('gulp-stylefmt') +const uglify = require('gulp-uglify-es').default +const babel = require('gulp-babel') +const debug = require('gulp-debug') +const pify = require('pify') +const endOfStream = pify(require('end-of-stream')) + + +const browserPlatforms = [ + 'firefox', + 'chrome', + 'edge', + 'opera', +] +const commonPlatforms = [ + // browser webapp + 'mascara', + // browser extensions + ...browserPlatforms +] // browser reload @@ -41,65 +52,98 @@ gulp.task('dev:reload', function() { }) }) +// copy universal -// copy static +const copyTaskNames = [] +const copyDevTaskNames = [] -gulp.task('copy:locales', copyTask({ +createCopyTasks('locales', { source: './app/_locales/', - destinations: [ - './dist/firefox/_locales', - './dist/chrome/_locales', - './dist/edge/_locales', - './dist/opera/_locales', - ] -})) -gulp.task('copy:images', copyTask({ + destinations: commonPlatforms.map(platform => `./dist/${platform}/_locales`), +}) +createCopyTasks('images', { source: './app/images/', - destinations: [ - './dist/firefox/images', - './dist/chrome/images', - './dist/edge/images', - './dist/opera/images', - ], -})) -gulp.task('copy:contractImages', copyTask({ + destinations: commonPlatforms.map(platform => `./dist/${platform}/images`), +}) +createCopyTasks('contractImages', { source: './node_modules/eth-contract-metadata/images/', - destinations: [ - './dist/firefox/images/contract', - './dist/chrome/images/contract', - './dist/edge/images/contract', - './dist/opera/images/contract', - ], -})) -gulp.task('copy:fonts', copyTask({ + destinations: commonPlatforms.map(platform => `./dist/${platform}/images/contract`), +}) +createCopyTasks('fonts', { source: './app/fonts/', - destinations: [ - './dist/firefox/fonts', - './dist/chrome/fonts', - './dist/edge/fonts', - './dist/opera/fonts', - ], -})) -gulp.task('copy:reload', copyTask({ + destinations: commonPlatforms.map(platform => `./dist/${platform}/fonts`), +}) +createCopyTasks('reload', { + devOnly: true, source: './app/scripts/', - destinations: [ - './dist/firefox/scripts', - './dist/chrome/scripts', - './dist/edge/scripts', - './dist/opera/scripts', - ], pattern: '/chromereload.js', -})) -gulp.task('copy:root', copyTask({ + destinations: commonPlatforms.map(platform => `./dist/${platform}`), +}) +createCopyTasks('html', { source: './app/', - destinations: [ - './dist/firefox', - './dist/chrome', - './dist/edge', - './dist/opera', - ], - pattern: '/*', -})) + pattern: '/*.html', + destinations: commonPlatforms.map(platform => `./dist/${platform}`), +}) + +// copy extension + +createCopyTasks('manifest', { + source: './app/', + pattern: '/*.json', + destinations: browserPlatforms.map(platform => `./dist/${platform}`), +}) + +// copy mascara + +createCopyTasks('html:mascara', { + source: './mascara/', + pattern: 'proxy/index.html', + destinations: [`./dist/mascara/`], +}) + +function createCopyTasks(label, opts) { + if (!opts.devOnly) { + const copyTaskName = `copy:${label}` + copyTask(copyTaskName, opts) + copyTaskNames.push(copyTaskName) + } + const copyDevTaskName = `dev:copy:${label}` + copyTask(copyDevTaskName, Object.assign({ devMode: true }, opts)) + copyDevTaskNames.push(copyDevTaskName) +} + +function copyTask(taskName, opts){ + const source = opts.source + const destination = opts.destination + const destinations = opts.destinations || [destination] + const pattern = opts.pattern || '/**/*' + const devMode = opts.devMode + + return gulp.task(taskName, function () { + if (devMode) { + watch(source + pattern, (event) => { + livereload.changed(event.path) + performCopy() + }) + } + + return performCopy() + }) + + function performCopy() { + // stream from source + let stream = gulp.src(source + pattern, { base: source }) + + // copy to destinations + destinations.forEach(function(destination) { + stream = stream.pipe(gulp.dest(destination)) + }) + + return stream + } +} + +// manifest tinkering gulp.task('manifest:chrome', function() { return gulp.src('./dist/chrome/manifest.json') @@ -134,46 +178,32 @@ gulp.task('manifest:production', function() { ],{base: './dist/'}) // Exclude chromereload script in production: - .pipe(gulpif(!debug,jsoneditor(function(json) { + .pipe(jsoneditor(function(json) { json.background.scripts = json.background.scripts.filter((script) => { return !script.includes('chromereload') }) return json - }))) + })) .pipe(gulp.dest('./dist/', { overwrite: true })) }) -const staticFiles = [ - 'locales', - 'images', - 'fonts', - 'root' -] - -var copyStrings = staticFiles.map(staticFile => `copy:${staticFile}`) -copyStrings.push('copy:contractImages') - -if (debug) { - copyStrings.push('copy:reload') -} - -gulp.task('copy', gulp.series(gulp.parallel(...copyStrings), 'manifest:production', 'manifest:chrome', 'manifest:opera')) -gulp.task('copy:watch', function(){ - gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy')) -}) - -// record deps - -gulp.task('deps', function (cb) { - exec('npm ls', (err, stdoutOutput, stderrOutput) => { - if (err) return cb(err) - const browsers = ['firefox','chrome','edge','opera'] - asyncEach(browsers, (target, done) => { - fs.writeFile(`./dist/${target}/deps.txt`, stdoutOutput, done) - }, cb) - }) -}) +gulp.task('copy', + gulp.series( + gulp.parallel(...copyTaskNames), + 'manifest:production', + 'manifest:chrome', + 'manifest:opera' + ) +) + +gulp.task('dev:copy', + gulp.series( + gulp.parallel(...copyDevTaskNames), + 'manifest:chrome', + 'manifest:opera' + ) +) // lint js @@ -196,41 +226,49 @@ gulp.task('lint:fix', function () { .pipe(eslint.failAfterError()) }); -/* -gulp.task('default', ['lint'], function () { - // This will only run if the lint task is successful... -}); -*/ +// scss compilation and autoprefixing tasks -// build js +gulp.task('build:scss', createScssBuildTask({ + src: 'ui/app/css/index.scss', + dest: 'ui/app/css/output', + devMode: false, +})) -const jsFiles = [ - 'inpage', - 'contentscript', - 'background', - 'popup', -] +gulp.task('dev:scss', createScssBuildTask({ + src: 'ui/app/css/index.scss', + dest: 'ui/app/css/output', + devMode: true, + pattern: 'ui/app/css/**/*.scss', +})) -// scss compilation and autoprefixing tasks +function createScssBuildTask({ src, dest, devMode, pattern }) { + return function () { + if (devMode) { + watch(pattern, async (event) => { + const stream = buildScss() + await endOfStream(stream) + livereload.changed(event.path) + }) + } + return buildScss() + } -gulp.task('build:scss', function () { - return gulp.src('ui/app/css/index.scss') - .pipe(sourcemaps.init()) - .pipe(sass().on('error', sass.logError)) - .pipe(sourcemaps.write()) - .pipe(autoprefixer()) - .pipe(gulp.dest('ui/app/css/output')) -}) -gulp.task('watch:scss', function() { - gulp.watch(['ui/app/css/**/*.scss'], gulp.series(['build:scss'])) -}) + function buildScss() { + return gulp.src(src) + .pipe(sourcemaps.init()) + .pipe(sass().on('error', sass.logError)) + .pipe(sourcemaps.write()) + .pipe(autoprefixer()) + .pipe(gulp.dest(dest)) + } +} gulp.task('lint-scss', function() { return gulp .src('ui/app/css/itcss/**/*.scss') .pipe(gulpStylelint({ reporters: [ - {formatter: 'string', console: true} + { formatter: 'string', console: true } ], fix: true, })); @@ -242,46 +280,84 @@ gulp.task('fmt-scss', function () { .pipe(gulp.dest('ui/app/css/itcss')); }); -// bundle tasks +// build js -var jsDevStrings = jsFiles.map(jsFile => `dev:js:${jsFile}`) -var jsBuildStrings = jsFiles.map(jsFile => `build:js:${jsFile}`) +const buildJsFiles = [ + 'inpage', + 'contentscript', + 'background', + 'ui', +] -jsFiles.forEach((jsFile) => { - gulp.task(`dev:js:${jsFile}`, bundleTask({ - watch: true, - label: jsFile, - filename: `${jsFile}.js`, - isBuild: false - })) - gulp.task(`build:js:${jsFile}`, bundleTask({ - watch: false, - label: jsFile, - filename: `${jsFile}.js`, - isBuild: true - })) -}) +// bundle tasks +createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true }) +createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' }) +createTasksForBuildJsMascara({ taskPrefix: 'build:mascara:js' }) +createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true }) + +function createTasksForBuildJsExtension({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) { + // inpage must be built before all other scripts: + const rootDir = './app/scripts' + const nonInpageFiles = buildJsFiles.filter(file => file !== 'inpage') + const buildPhase1 = ['inpage'] + const buildPhase2 = nonInpageFiles + const destinations = browserPlatforms.map(platform => `./dist/${platform}`) + bundleTaskOpts = Object.assign({ + buildSourceMaps: true, + sourceMapDir: devMode ? './' : '../sourcemaps', + minifyBuild: !devMode, + buildWithFullPaths: devMode, + watch: devMode, + devMode, + }, bundleTaskOpts) + createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 }) +} -// inpage must be built before all other scripts: -const firstDevString = jsDevStrings.shift() -gulp.task('dev:js', gulp.series(firstDevString, gulp.parallel(...jsDevStrings))) +function createTasksForBuildJsMascara({ taskPrefix, devMode, bundleTaskOpts = {} }) { + // inpage must be built before all other scripts: + const rootDir = './mascara/src/' + const buildPhase1 = ['ui', 'proxy', 'background', 'metamascara'] + const destinations = ['./dist/mascara'] + bundleTaskOpts = Object.assign({ + buildSourceMaps: true, + sourceMapDir: './', + minifyBuild: !devMode, + buildWithFullPaths: devMode, + watch: devMode, + devMode, + }, bundleTaskOpts) + createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 }) +} + +function createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) { + // bundle task for each file + const jsFiles = [].concat(buildPhase1, buildPhase2) + jsFiles.forEach((jsFile) => { + gulp.task(`${taskPrefix}:${jsFile}`, bundleTask(Object.assign({ + label: jsFile, + filename: `${jsFile}.js`, + filepath: `${rootDir}/${jsFile}.js`, + destinations, + }, bundleTaskOpts))) + }) + // compose into larger task + const subtasks = [] + subtasks.push(gulp.parallel(buildPhase1.map(file => `${taskPrefix}:${file}`))) + if (buildPhase2.length) subtasks.push(gulp.parallel(buildPhase2.map(file => `${taskPrefix}:${file}`))) -// inpage must be built before all other scripts: -const firstBuildString = jsBuildStrings.shift() -gulp.task('build:js', gulp.series(firstBuildString, gulp.parallel(...jsBuildStrings))) + gulp.task(taskPrefix, gulp.series(subtasks)) +} // disc bundle analyzer tasks -jsFiles.forEach((jsFile) => { - gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` })) +buildJsFiles.forEach((jsFile) => { + gulp.task(`disc:${jsFile}`, discTask({ label: jsFile, filename: `${jsFile}.js` })) }) -gulp.task('disc', gulp.parallel(jsFiles.map(jsFile => `disc:${jsFile}`))) - +gulp.task('disc', gulp.parallel(buildJsFiles.map(jsFile => `disc:${jsFile}`))) // clean dist - gulp.task('clean', function clean() { return del(['./dist/*']) }) @@ -293,40 +369,95 @@ gulp.task('zip:edge', zipTask('edge')) gulp.task('zip:opera', zipTask('opera')) gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox', 'zip:edge', 'zip:opera')) -// set env var for production +// set env for production gulp.task('apply-prod-environment', function(done) { - process.env.NODE_ENV = 'production' - done() + process.env.NODE_ENV = 'production' + done() }); // high level tasks -gulp.task('dev', gulp.series('build:scss', 'dev:js', 'copy', gulp.parallel('watch:scss', 'copy:watch', 'dev:reload'))) +gulp.task('dev', + gulp.series( + 'clean', + 'dev:scss', + gulp.parallel( + 'dev:extension:js', + 'dev:mascara:js', + 'dev:copy', + 'dev:reload' + ) + ) +) + +gulp.task('dev:extension', + gulp.series( + 'clean', + 'dev:scss', + gulp.parallel( + 'dev:extension:js', + 'dev:copy', + 'dev:reload' + ) + ) +) + +gulp.task('dev:mascara', + gulp.series( + 'clean', + 'dev:scss', + gulp.parallel( + 'dev:mascara:js', + 'dev:copy', + 'dev:reload' + ) + ) +) + +gulp.task('build', + gulp.series( + 'clean', + 'build:scss', + gulp.parallel( + 'build:extension:js', + 'build:mascara:js', + 'copy' + ) + ) +) + +gulp.task('build:extension', + gulp.series( + 'clean', + 'build:scss', + gulp.parallel( + 'build:extension:js', + 'copy' + ) + ) +) + +gulp.task('build:mascara', + gulp.series( + 'clean', + 'build:scss', + gulp.parallel( + 'build:mascara:js', + 'copy' + ) + ) +) -gulp.task('build', gulp.series('clean', 'build:scss', gulp.parallel('build:js', 'copy'))) -gulp.task('dist', gulp.series('apply-prod-environment', 'build', 'zip')) +gulp.task('dist', + gulp.series( + 'apply-prod-environment', + 'build', + 'zip' + ) +) // task generators -function copyTask(opts){ - var source = opts.source - var destination = opts.destination - var destinations = opts.destinations || [ destination ] - var pattern = opts.pattern || '/**/*' - - return performCopy - - function performCopy(){ - let stream = gulp.src(source + pattern, { base: source }) - destinations.forEach(function(destination) { - stream = stream.pipe(gulp.dest(destination)) - }) - stream.pipe(gulpif(debug,livereload())) - - return stream - } -} - function zipTask(target) { return () => { return gulp.src(`dist/${target}/**`) @@ -337,10 +468,10 @@ function zipTask(target) { function generateBundler(opts, performBundle) { const browserifyOpts = assign({}, watchify.args, { - entries: ['./app/scripts/'+opts.filename], + entries: [opts.filepath], plugin: 'browserify-derequire', - debug: true, - fullPaths: debug, + debug: opts.buildSourceMaps, + fullPaths: opts.buildWithFullPaths, }) let bundler = browserify(browserifyOpts) @@ -348,13 +479,21 @@ function generateBundler(opts, performBundle) { if (opts.watch) { bundler = watchify(bundler) // on any file update, re-runs the bundler - bundler.on('update', performBundle) + bundler.on('update', async (ids) => { + const stream = performBundle() + await endOfStream(stream) + livereload.changed(`${ids}`) + }) } return bundler } function discTask(opts) { + opts = Object.assign({ + buildWithFullPaths: true, + }, opts) + const bundler = generateBundler(opts, performBundle) // output build logs to terminal bundler.on('log', gutil.log) @@ -363,9 +502,9 @@ function discTask(opts) { function performBundle(){ // start "disc" build - let discDir = path.join(__dirname, 'disc') + const discDir = path.join(__dirname, 'disc') mkdirp.sync(discDir) - let discPath = path.join(discDir, `${opts.label}.html`) + const discPath = path.join(discDir, `${opts.label}.html`) return ( bundler.bundle() @@ -384,43 +523,58 @@ function bundleTask(opts) { return performBundle function performBundle(){ - return ( - - bundler.bundle() + let buildStream = bundler.bundle() + + // handle errors + buildStream.on('error', (err) => { + beep() + if (opts.watch) { + console.warn(err.stack) + } else { + throw err + } + }) - // handle errors - .on('error', (err) => { - beep() - if (opts.watch) { - console.warn(err.stack) - } else { - throw err - } - }) + // process bundles + buildStream = buildStream // convert bundle stream to gulp vinyl stream .pipe(source(opts.filename)) // inject variables into bundle - .pipe(replace('\'GULP_METAMASK_DEBUG\'', debug)) + .pipe(replace('\'GULP_METAMASK_DEBUG\'', opts.devMode)) // buffer file contents (?) .pipe(buffer()) - // sourcemaps - // loads map from browserify file - .pipe(sourcemaps.init({ loadMaps: true })) - // Minification - .pipe(gulpif(opts.isBuild, uglify({ - mangle: { reserved: [ 'MetamaskInpageProvider' ] }, - }))) - // writes .map file - .pipe(sourcemaps.write(debug ? './' : '../../sourcemaps')) - // write completed bundles - .pipe(gulp.dest('./dist/firefox/scripts')) - .pipe(gulp.dest('./dist/chrome/scripts')) - .pipe(gulp.dest('./dist/edge/scripts')) - .pipe(gulp.dest('./dist/opera/scripts')) - // finally, trigger live reload - .pipe(gulpif(debug, livereload())) - ) + + // Initialize Source Maps + if (opts.buildSourceMaps) { + buildStream = buildStream + // loads map from browserify file + .pipe(sourcemaps.init({ loadMaps: true })) + } + + // Minification + if (opts.minifyBuild) { + buildStream = buildStream + .pipe(uglify({ + mangle: { + reserved: [ 'MetamaskInpageProvider' ] + }, + })) + } + + // Finalize Source Maps (writes .map file) + if (opts.buildSourceMaps) { + buildStream = buildStream + .pipe(sourcemaps.write(opts.sourceMapDir)) + } + + // write completed bundles + opts.destinations.forEach((dest) => { + buildStream = buildStream.pipe(gulp.dest(dest)) + }) + + return buildStream + } } diff --git a/mascara/proxy/index.html b/mascara/proxy/index.html index b83fc41af..4e167db72 100644 --- a/mascara/proxy/index.html +++ b/mascara/proxy/index.html @@ -17,4 +17,4 @@ Hello! I am the MetaMask iframe. <script src="./proxy.js"></script> </body> -</html>
\ No newline at end of file +</html> diff --git a/mascara/server/index.js b/mascara/server/index.js index 6fb1287cc..a30120438 100644 --- a/mascara/server/index.js +++ b/mascara/server/index.js @@ -1,7 +1,5 @@ const path = require('path') const express = require('express') -const createBundle = require('./util').createBundle -const serveBundle = require('./util').serveBundle const compression = require('compression') module.exports = createMetamascaraServer @@ -9,27 +7,14 @@ module.exports = createMetamascaraServer function createMetamascaraServer () { - // start bundlers - const metamascaraBundle = createBundle(path.join(__dirname, '/../src/mascara.js')) - const proxyBundle = createBundle(path.join(__dirname, '/../src/proxy.js')) - const uiBundle = createBundle(path.join(__dirname, '/../src/ui.js')) - const backgroundBuild = createBundle(path.join(__dirname, '/../src/background.js')) - - // serve bundles + // setup server const server = express() server.use(compression()) - // ui window - serveBundle(server, '/ui.js', uiBundle) + // serve assets server.use(express.static(path.join(__dirname, '/../ui/'), { setHeaders: (res) => res.set('X-Frame-Options', 'DENY') })) - server.use(express.static(path.join(__dirname, '/../../dist/chrome'))) - // metamascara - serveBundle(server, '/metamascara.js', metamascaraBundle) - // proxy - serveBundle(server, '/proxy/proxy.js', proxyBundle) - server.use('/proxy/', express.static(path.join(__dirname, '/../proxy'))) - // background - serveBundle(server, '/background.js', backgroundBuild) + server.use(express.static(path.join(__dirname, '/../../dist/mascara'))) + server.use(express.static(path.join(__dirname, '/../proxy'))) return server diff --git a/mascara/src/background.js b/mascara/src/background.js index 8aa1d8fe2..40a684f3d 100644 --- a/mascara/src/background.js +++ b/mascara/src/background.js @@ -30,15 +30,19 @@ global.addEventListener('activate', function (event) { log.debug('inside:open') - -// // state persistence +// state persistence const dbController = new DbController({ key: STORAGE_KEY, }) -loadStateFromPersistence() -.then((initState) => setupController(initState)) -.then(() => log.debug('MetaMask initialization complete.')) -.catch((err) => console.error('WHILE SETTING UP:', err)) + +start().catch(log.error) + +async function start() { + log.debug('MetaMask initializing...') + const initState = await loadStateFromPersistence() + await setupController(initState) + log.debug('MetaMask initialization complete.') +} // // State and Persistence diff --git a/mascara/src/mascara.js b/mascara/src/metamascara.js index 0af6f532f..0af6f532f 100644 --- a/mascara/src/mascara.js +++ b/mascara/src/metamascara.js diff --git a/mascara/src/proxy.js b/mascara/src/proxy.js index 54c5d5cf4..80b4dc516 100644 --- a/mascara/src/proxy.js +++ b/mascara/src/proxy.js @@ -1,13 +1,13 @@ const createParentStream = require('iframe-stream').ParentStream -const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') +const SwController = require('sw-controller') const SwStream = require('sw-stream/lib/sw-stream.js') -const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 -const background = new SWcontroller({ - fileName: '/background.js', - letBeIdle: false, - wakeUpInterval: 30000, - intervalDelay, +const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 +const background = new SwController({ + fileName: './scripts/background.js', + keepAlive: true, + keepAliveInterval: 30000, + keepAliveDelay, }) const pageStream = createParentStream() diff --git a/mascara/src/ui.js b/mascara/src/ui.js index b272a2e06..f35a11fc4 100644 --- a/mascara/src/ui.js +++ b/mascara/src/ui.js @@ -1,6 +1,6 @@ const injectCss = require('inject-css') -const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') -const SwStream = require('sw-stream/lib/sw-stream.js') +const SwController = require('sw-controller') +const SwStream = require('sw-stream') const MetaMaskUiCss = require('../../ui/css') const MetamascaraPlatform = require('../../app/scripts/platforms/window') const startPopup = require('../../app/scripts/popup-core') @@ -8,27 +8,44 @@ const startPopup = require('../../app/scripts/popup-core') // create platform global global.platform = new MetamascaraPlatform() - var css = MetaMaskUiCss() injectCss(css) const container = document.getElementById('app-content') -var name = 'popup' +const name = 'popup' window.METAMASK_UI_TYPE = name window.METAMASK_PLATFORM_TYPE = 'mascara' -const intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 +const keepAliveDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 -const background = new SWcontroller({ - fileName: '/background.js', - letBeIdle: false, - intervalDelay, - wakeUpInterval: 20000, +const swController = new SwController({ + fileName: './background.js', + keepAlive: true, + keepAliveDelay, + keepAliveInterval: 20000, }) + +swController.once('updatefound', windowReload) +swController.once('ready', async () => { + try { + swController.removeListener('updatefound', windowReload) + console.log('swController ready') + await timeout(1000) + console.log('connecting to app') + await connectApp() + console.log('app connected') + } catch (err) { + console.error(err) + } +}) + +console.log('starting service worker') +swController.startWorker() + // Setup listener for when the service worker is read -const connectApp = function (readSw) { +function connectApp() { const connectionStream = SwStream({ - serviceWorker: background.controller, + serviceWorker: swController.getWorker(), context: name, }) return new Promise((resolve, reject) => { @@ -43,19 +60,6 @@ const connectApp = function (readSw) { }) }) } -background.on('ready', async (sw) => { - try { - background.removeListener('updatefound', connectApp) - await timeout(1000) - await connectApp(sw) - console.log('hello from cb ready event!') - } catch (e) { - console.error(e) - } -}) -background.on('updatefound', windowReload) - -background.startWorker() function windowReload () { if (window.METAMASK_SKIP_RELOAD) return diff --git a/mascara/ui/index.html b/mascara/ui/index.html index eac8e4898..dbc445891 100644 --- a/mascara/ui/index.html +++ b/mascara/ui/index.html @@ -7,6 +7,6 @@ </head> <body> <div id="app-content"></div> - <script src="./ui.js" type="text/javascript" charset="utf-8"></script> + <script src="./scripts/ui.js" type="text/javascript" charset="utf-8"></script> </body> -</html>
\ No newline at end of file +</html> diff --git a/old-ui/app/app.js b/old-ui/app/app.js index c79ac633a..e9ab185f3 100644 --- a/old-ui/app/app.js +++ b/old-ui/app/app.js @@ -171,7 +171,7 @@ App.prototype.renderAppBar = function () { h('img', { height: 24, width: 24, - src: '/images/icon-128.png', + src: './images/icon-128.png', }), h(NetworkIndicator, { diff --git a/package-lock.json b/package-lock.json index f0f76919a..18d49971e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3125,11 +3125,6 @@ "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" }, - "client-sw-ready-event": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/client-sw-ready-event/-/client-sw-ready-event-3.4.0.tgz", - "integrity": "sha1-/0hkYXaQVed0hXDxrvECuGdbZtA=" - }, "cliui": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", @@ -5752,7 +5747,7 @@ "ethereumjs-vm": "2.3.2", "through2": "2.0.3", "treeify": "1.1.0", - "web3-provider-engine": "13.6.0" + "web3-provider-engine": "13.8.0" } }, "ethereum-common": { @@ -8192,6 +8187,11 @@ "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", "dev": true }, + "get-own-enumerable-property-symbols": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz", + "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==" + }, "get-stdin": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-3.0.2.tgz", @@ -8628,6 +8628,52 @@ } } }, + "gulp-debug": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/gulp-debug/-/gulp-debug-3.2.0.tgz", + "integrity": "sha512-2LZzP+ydczqz1rhqq/NYxvVvYTmOa0IgBl2B1sQTdkQgku9ayOUM/KHuGPjF4QA5aO1VcG+Sskw7iCcRUqHKkA==", + "requires": { + "chalk": "2.3.2", + "fancy-log": "1.3.2", + "plur": "2.1.2", + "stringify-object": "3.2.2", + "through2": "2.0.3", + "tildify": "1.2.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "1.9.1" + } + }, + "chalk": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", + "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "requires": { + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", + "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "requires": { + "has-flag": "3.0.0" + } + } + } + }, "gulp-eslint": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-4.0.0.tgz", @@ -9204,7 +9250,7 @@ "dev": true, "requires": { "anymatch": "1.3.2", - "chokidar": "2.0.1", + "chokidar": "2.0.3", "glob-parent": "3.1.0", "gulp-util": "3.0.8", "object-assign": "4.1.1", @@ -9228,9 +9274,9 @@ "dev": true }, "braces": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.0.tgz", - "integrity": "sha512-P4O8UQRdGiMLWSizsApmXVQDBS6KCt7dSexgLKBmH5Hr1CZq7vsnscFh8oR1sP1ab1Zj0uCHCEzZeV6SfUf3rA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", + "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", "dev": true, "requires": { "arr-flatten": "1.1.0", @@ -9239,22 +9285,43 @@ "extend-shallow": "2.0.1", "fill-range": "4.0.0", "isobject": "3.0.1", + "kind-of": "6.0.2", "repeat-element": "1.1.2", "snapdragon": "0.8.1", "snapdragon-node": "2.1.1", "split-string": "3.1.0", - "to-regex": "3.0.1" + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } } }, "chokidar": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.1.tgz", - "integrity": "sha512-rv5iP8ENhpqvDWr677rAXcB+SMoPQ1urd4ch79+PhM4lQwbATdJUQK69t0lJIKNB+VXpqxt5V1gvqs59XEPKnw==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", "dev": true, "requires": { "anymatch": "2.0.0", "async-each": "1.0.1", - "braces": "2.3.0", + "braces": "2.3.1", "fsevents": "1.1.3", "glob-parent": "3.1.0", "inherits": "2.0.3", @@ -9263,7 +9330,7 @@ "normalize-path": "2.1.1", "path-is-absolute": "1.0.1", "readdirp": "2.1.0", - "upath": "1.0.0" + "upath": "1.0.4" }, "dependencies": { "anymatch": { @@ -9272,12 +9339,22 @@ "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "dev": true, "requires": { - "micromatch": "3.1.5", + "micromatch": "3.1.10", "normalize-path": "2.1.1" } } } }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + } + }, "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", @@ -9290,7 +9367,7 @@ "posix-character-classes": "0.1.1", "regex-not": "1.0.0", "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -9301,6 +9378,53 @@ "requires": { "is-descriptor": "0.1.6" } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } } } }, @@ -9317,7 +9441,27 @@ "fragment-cache": "0.2.1", "regex-not": "1.0.0", "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "to-regex": "3.0.2" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "1.0.2" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } } }, "fill-range": { @@ -9330,6 +9474,17 @@ "is-number": "3.0.0", "repeat-string": "1.6.1", "to-regex-range": "2.1.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "0.1.1" + } + } } }, "glob-parent": { @@ -9393,25 +9548,6 @@ } } }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "0.1.6", - "is-data-descriptor": "0.1.4", - "kind-of": "5.1.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -9460,24 +9596,48 @@ "dev": true }, "micromatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.5.tgz", - "integrity": "sha512-ykttrLPQrz1PUJcXjwsTUjGoPJ64StIGNE2lGVD1c9CuguJ+L7/navsE8IcDNndOoCMvYV0qc/exfVbMHkUhvA==", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { "arr-diff": "4.0.0", "array-unique": "0.3.2", - "braces": "2.3.0", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", + "braces": "2.3.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", "extglob": "2.0.4", "fragment-cache": "0.2.1", "kind-of": "6.0.2", - "nanomatch": "1.2.7", + "nanomatch": "1.2.9", "object.pick": "1.3.0", "regex-not": "1.0.0", "snapdragon": "0.8.1", - "to-regex": "3.0.1" + "to-regex": "3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, + "requires": { + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" + }, + "dependencies": { + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, + "requires": { + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" + } + } } } } @@ -10374,8 +10534,7 @@ "irregular-plurals": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/irregular-plurals/-/irregular-plurals-1.4.0.tgz", - "integrity": "sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y=", - "dev": true + "integrity": "sha1-LKmwM2UREYVUEvFr5dd8YqRYp2Y=" }, "is-absolute": { "version": "1.0.0", @@ -10603,23 +10762,25 @@ "integrity": "sha1-8mWrian0RQNO9q/xWo8AsA9VF5k=", "dev": true }, + "is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" + }, "is-odd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-1.0.0.tgz", - "integrity": "sha1-O4qTLrAos3dcObsJ6RdnrM22kIg=", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", "dev": true, "requires": { - "is-number": "3.0.0" + "is-number": "4.0.0" }, "dependencies": { "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "3.2.2" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true } } }, @@ -10695,8 +10856,7 @@ "is-regexp": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true + "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=" }, "is-relative": { "version": "1.0.0", @@ -13589,18 +13749,19 @@ "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=" }, "nanomatch": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.7.tgz", - "integrity": "sha512-/5ldsnyurvEw7wNpxLFgjVvBLMta43niEYOy0CJ4ntcYSbx6bugRUTQeFb4BR/WanEL1o3aQgHuVLHQaB6tOqg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", + "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", "dev": true, "requires": { "arr-diff": "4.0.0", "array-unique": "0.3.2", - "define-property": "1.0.0", - "extend-shallow": "2.0.1", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", "fragment-cache": "0.2.1", - "is-odd": "1.0.0", - "kind-of": "5.1.0", + "is-odd": "2.0.0", + "is-windows": "1.0.2", + "kind-of": "6.0.2", "object.pick": "1.3.0", "regex-not": "1.0.0", "snapdragon": "0.8.1", @@ -13619,10 +13780,45 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", "dev": true }, + "define-property": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, + "requires": { + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + } + }, + "extend-shallow": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, + "requires": { + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" + } + }, + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, + "requires": { + "is-plain-object": "2.0.4" + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", "dev": true } } @@ -16675,7 +16871,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/plur/-/plur-2.1.2.tgz", "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", - "dev": true, "requires": { "irregular-plurals": "1.4.0" } @@ -18334,6 +18529,15 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, + "requires": { + "ret": "0.1.15" + } + }, "samsam": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", @@ -19102,9 +19306,9 @@ } }, "solc": { - "version": "0.4.20", - "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.20.tgz", - "integrity": "sha512-LrP3Jp4FS3y8sduIR67y8Ss1riR3fggk5sMnx4OSCcU88Ro0e51+KVXyfH3NP6ghLo7COrLx/lGUaDDugCzdgA==", + "version": "0.4.21", + "resolved": "https://registry.npmjs.org/solc/-/solc-0.4.21.tgz", + "integrity": "sha512-8lJmimVjOG9AJOQRWS2ph4rSctPMsPGZ4H360HLs5iI+euUlt7iAvUxSLeFZZzwk0kas4Qta7HmlMXNU3yYwhw==", "requires": { "fs-extra": "0.30.0", "memorystream": "0.3.1", @@ -19727,6 +19931,16 @@ "safe-buffer": "5.1.1" } }, + "stringify-object": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.2.tgz", + "integrity": "sha512-O696NF21oLiDy8PhpWu8AEqoZHw++QW6mUv0UvKZe8gWSdSvMXkiLufK7OmnP27Dro4GU5kb9U7JIO0mBuCRQg==", + "requires": { + "get-own-enumerable-property-symbols": "2.0.1", + "is-obj": "1.0.1", + "is-regexp": "1.0.0" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", @@ -20333,6 +20547,27 @@ "integrity": "sha1-WPcc7jvVGbWdSyqEO2x95krAR2Q=", "dev": true }, + "sw-controller": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sw-controller/-/sw-controller-1.0.3.tgz", + "integrity": "sha512-q+rS4v7kj1MPDxFRyl8GAICw/BbQzewd5HhVDNIPLnyvKtcpxi26fVXReUeUMRl4CRL/fX56PvKKqxtKhAaMpg==", + "requires": { + "babel-preset-es2015": "6.24.1", + "babel-runtime": "6.26.0", + "babelify": "7.3.0" + }, + "dependencies": { + "babelify": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz", + "integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=", + "requires": { + "babel-core": "6.26.0", + "object-assign": "4.1.1" + } + } + } + }, "sw-stream": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/sw-stream/-/sw-stream-2.0.2.tgz", @@ -20797,6 +21032,14 @@ "dev": true, "optional": true }, + "tildify": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", + "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", + "requires": { + "os-homedir": "1.0.2" + } + }, "time-stamp": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", @@ -21244,12 +21487,6 @@ "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=", "dev": true }, - "underscore.string": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/underscore.string/-/underscore.string-2.3.3.tgz", - "integrity": "sha1-ccCL9rQosRM/N+ePo6Icgvcymw0=", - "dev": true - }, "undertaker": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.0.tgz", @@ -21410,22 +21647,10 @@ } }, "upath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.0.tgz", - "integrity": "sha1-tHBrlGHKhHOt+JEz0jVonKF/NlY=", - "dev": true, - "requires": { - "lodash": "3.10.1", - "underscore.string": "2.3.3" - }, - "dependencies": { - "lodash": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=", - "dev": true - } - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.4.tgz", + "integrity": "sha512-d4SJySNBXDaQp+DPrziv3xGS6w3d2Xt69FijJr86zMPBy23JEloMCEOUBBzuN7xCtjLCnmB9tI/z7SBCahHBOw==", + "dev": true }, "urix": { "version": "0.1.0", @@ -21713,9 +21938,9 @@ }, "dependencies": { "clone": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.3.tgz", - "integrity": "sha1-KY1+IjFmD0DAA8LtMUDezz9TCF8=", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", "dev": true }, "clone-stats": { @@ -21742,7 +21967,7 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "1.0.3", + "clone": "1.0.4", "clone-stats": "0.0.1", "replace-ext": "0.0.1" } @@ -22084,9 +22309,9 @@ } }, "web3-provider-engine": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.6.0.tgz", - "integrity": "sha512-iCsAlAeLWHxgx6EXuBm5GNg5VBqKtzmnrhEOfJBv8Cetukush7yOvo4RPjDZIynKxg9jfAlMmWqCk6wLxA6coQ==", + "version": "13.8.0", + "resolved": "https://registry.npmjs.org/web3-provider-engine/-/web3-provider-engine-13.8.0.tgz", + "integrity": "sha512-fZXhX5VWwWpoFfrfocslyg6P7cN3YWPG/ASaevNfeO80R+nzgoPUBXcWQekSGSsNDkeRTis4aMmpmofYf1TNtQ==", "requires": { "async": "2.6.0", "clone": "2.1.1", @@ -22094,7 +22319,7 @@ "eth-sig-util": "1.4.2", "ethereumjs-block": "1.7.0", "ethereumjs-tx": "1.3.3", - "ethereumjs-util": "5.1.4", + "ethereumjs-util": "5.1.5", "ethereumjs-vm": "2.3.2", "fetch-ponyfill": "4.1.0", "json-rpc-error": "2.0.0", @@ -22103,16 +22328,16 @@ "readable-stream": "2.3.3", "request": "2.83.0", "semaphore": "1.1.0", - "solc": "0.4.20", + "solc": "0.4.21", "tape": "4.8.0", "xhr": "2.4.1", "xtend": "4.0.1" }, "dependencies": { "ethereumjs-util": { - "version": "5.1.4", - "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.4.tgz", - "integrity": "sha512-wbeTc5prEzIWFSQUcEsCAZbqubtJKy6yS+oZMY1cGG6GLYzLjm4YhC2RNrWIg8hRYnclWpnZmx2zkiufQkmd3w==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.1.5.tgz", + "integrity": "sha512-xPaSEATYJpMTCGowIt0oMZwFP4R1bxd6QsWgkcDvFL0JtXsr39p32WEcD14RscCjfP41YXZPCVWA4yAg0nrJmw==", "requires": { "bn.js": "4.11.8", "create-hash": "1.1.3", diff --git a/package.json b/package.json index 6cd8f7f4e..62d703c90 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,9 @@ "public": false, "private": true, "scripts": { - "start": "npm run dev", - "dev": "gulp dev --debug", - "ui": "npm run test:flat:build:states && beefy development/ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", - "mock": "beefy development/mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", - "watch": "mocha watch --recursive \"test/unit/**/*.js\"", - "mascara": "gulp build && cross-env METAMASK_DEBUG=true node ./mascara/example/server", - "dist": "npm run dist:clear && npm install && gulp dist", - "dist:clear": "rm -rf node_modules/eth-contract-metadata && rm -rf node_modules/eth-phishing-detect", + "start": "gulp dev:extension", + "mascara": "gulp dev:mascara & cross-env METAMASK_DEBUG=true node ./mascara/example/server", + "dist": "gulp dist", "test": "npm run test:unit && npm run test:integration && npm run lint", "test:unit": "cross-env METAMASK_ENV=test mocha --exit --require babel-core/register --require test/helper.js --recursive \"test/unit/**/*.js\"", "test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js", @@ -41,6 +36,9 @@ "sentry:upload:maps": "sentry-cli releases --org 'metamask' --project 'metamask' files $RELEASE upload-sourcemaps ./dist/sourcemaps/ --url-prefix 'sourcemaps' --rewrite", "lint": "gulp lint", "lint:fix": "gulp lint:fix", + "ui": "npm run test:flat:build:states && beefy development/ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", + "mock": "beefy development/mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", + "watch": "mocha watch --recursive \"test/unit/**/*.js\"", "disc": "gulp disc --debug", "announce": "node development/announcer.js", "version:bump": "node development/run-version-bump.js", @@ -78,7 +76,6 @@ "browserify-derequire": "^0.9.4", "browserify-unibabel": "^3.0.0", "classnames": "^2.2.5", - "client-sw-ready-event": "^3.3.0", "clone": "^2.1.1", "copy-to-clipboard": "^3.0.8", "debounce": "^1.0.0", @@ -118,6 +115,7 @@ "fuse.js": "^3.2.0", "gulp": "github:gulpjs/gulp#4.0", "gulp-autoprefixer": "^5.0.0", + "gulp-debug": "^3.2.0", "gulp-eslint": "^4.0.0", "gulp-sass": "^3.1.0", "hat": "0.0.3", @@ -180,13 +178,14 @@ "semaphore": "^1.0.5", "semver": "^5.4.1", "shallow-copy": "0.0.1", - "sw-stream": "^2.0.0", + "sw-controller": "^1.0.3", + "sw-stream": "^2.0.2", "textarea-caret": "^3.0.1", "through2": "^2.0.3", "valid-url": "^1.0.9", "vreme": "^3.0.2", "web3": "^0.20.1", - "web3-provider-engine": "^13.5.6", + "web3-provider-engine": "^13.8.0", "web3-stream-provider": "^3.0.1", "xtend": "^4.0.1" }, @@ -222,7 +221,6 @@ "gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed", "gulp-babel": "^7.0.0", "gulp-eslint": "^4.0.0", - "gulp-if": "^2.0.2", "gulp-json-editor": "^2.2.1", "gulp-livereload": "^3.8.1", "gulp-replace": "^0.6.1", @@ -258,8 +256,8 @@ "react-addons-test-utils": "^15.5.1", "react-test-renderer": "^15.6.2", "react-testutils-additions": "^15.2.0", - "selenium-webdriver": "^3.5.0", "redux-test-utils": "^0.2.2", + "selenium-webdriver": "^3.5.0", "sinon": "^5.0.0", "stylelint-config-standard": "^18.2.0", "tape": "^4.5.1", diff --git a/test/e2e/metamask.spec.js b/test/e2e/metamask.spec.js index ff5bdb51b..d2ffa041e 100644 --- a/test/e2e/metamask.spec.js +++ b/test/e2e/metamask.spec.js @@ -42,7 +42,7 @@ describe('Metamask popup page', function () { it('should match title', async () => { const title = await driver.getTitle() - assert.equal(title, 'MetaMask Plugin', 'title matches MetaMask Plugin') + assert.equal(title, 'MetaMask', 'title matches MetaMask') }) it('should show privacy notice', async () => { diff --git a/ui/app/app.js b/ui/app/app.js index ea88f3039..0b7a7a1e0 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -294,7 +294,7 @@ App.prototype.renderAppBar = function () { h('img.metafox-icon', { height: 42, width: 42, - src: '/images/metamask-fox.svg', + src: './images/metamask-fox.svg', }), // metamask name diff --git a/ui/app/components/customize-gas-modal/index.js b/ui/app/components/customize-gas-modal/index.js index 825366cb2..4c693d1c3 100644 --- a/ui/app/components/customize-gas-modal/index.js +++ b/ui/app/components/customize-gas-modal/index.js @@ -65,6 +65,7 @@ function mapDispatchToProps (dispatch) { updateGasLimit: newGasLimit => dispatch(actions.updateGasLimit(newGasLimit)), updateGasTotal: newGasTotal => dispatch(actions.updateGasTotal(newGasTotal)), updateSendAmount: newAmount => dispatch(actions.updateSendAmount(newAmount)), + updateSendErrors: error => dispatch(actions.updateSendErrors(error)), } } @@ -112,6 +113,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { selectedToken, balance, updateSendAmount, + updateSendErrors, } = this.props if (maxModeOn && !selectedToken) { @@ -126,6 +128,7 @@ CustomizeGasModal.prototype.save = function (gasPrice, gasLimit, gasTotal) { updateGasPrice(ethUtil.addHexPrefix(gasPrice)) updateGasLimit(ethUtil.addHexPrefix(gasLimit)) updateGasTotal(ethUtil.addHexPrefix(gasTotal)) + updateSendErrors({ insufficientFunds: false }) hideModal() } diff --git a/ui/app/components/identicon.js b/ui/app/components/identicon.js index b803b7ceb..7cc5a4de0 100644 --- a/ui/app/components/identicon.js +++ b/ui/app/components/identicon.js @@ -47,7 +47,7 @@ IdenticonComponent.prototype.render = function () { ) : ( h('img.balance-icon', { - src: '../images/eth_logo.svg', + src: './images/eth_logo.svg', style: { height: diameter, width: diameter, diff --git a/ui/app/components/modals/deposit-ether-modal.js b/ui/app/components/modals/deposit-ether-modal.js index 8854d258f..0dc611f50 100644 --- a/ui/app/components/modals/deposit-ether-modal.js +++ b/ui/app/components/modals/deposit-ether-modal.js @@ -150,7 +150,7 @@ DepositEtherModal.prototype.render = function () { this.renderRow({ logo: h('img.deposit-ether-modal__logo', { - src: '../../../images/deposit-eth.svg', + src: './images/deposit-eth.svg', }), title: DIRECT_DEPOSIT_ROW_TITLE, text: DIRECT_DEPOSIT_ROW_TEXT, @@ -171,7 +171,7 @@ DepositEtherModal.prototype.render = function () { this.renderRow({ logo: h('div.deposit-ether-modal__logo', { style: { - backgroundImage: 'url(\'../../../images/coinbase logo.png\')', + backgroundImage: 'url(\'./images/coinbase logo.png\')', height: '40px', }, }), @@ -185,7 +185,7 @@ DepositEtherModal.prototype.render = function () { this.renderRow({ logo: h('div.deposit-ether-modal__logo', { style: { - backgroundImage: 'url(\'../../../images/shapeshift logo.png\')', + backgroundImage: 'url(\'./images/shapeshift logo.png\')', }, }), title: SHAPESHIFT_ROW_TITLE, diff --git a/ui/app/components/pending-tx/confirm-send-ether.js b/ui/app/components/pending-tx/confirm-send-ether.js index b68de4704..2474516d4 100644 --- a/ui/app/components/pending-tx/confirm-send-ether.js +++ b/ui/app/components/pending-tx/confirm-send-ether.js @@ -8,11 +8,16 @@ const clone = require('clone') const ethUtil = require('ethereumjs-util') const BN = ethUtil.BN const hexToBn = require('../../../../app/scripts/lib/hex-to-bn') +const classnames = require('classnames') const { conversionUtil, addCurrencies, multiplyCurrencies, } = require('../../conversion-util') +const { + getGasTotal, + isBalanceSufficient, +} = require('../send/send-utils') const GasFeeDisplay = require('../send/gas-fee-display-v2') const SenderToRecipient = require('../sender-to-recipient') const NetworkDisplay = require('../network-display') @@ -35,12 +40,14 @@ function mapStateToProps (state) { } = state.metamask const accounts = state.metamask.accounts const selectedAddress = state.metamask.selectedAddress || Object.keys(accounts)[0] + const { balance } = accounts[selectedAddress] return { conversionRate, identities, selectedAddress, currentCurrency, send, + balance, } } @@ -91,6 +98,7 @@ function mapDispatchToProps (dispatch) { })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) }, + updateSendErrors: error => dispatch(actions.updateSendErrors(error)), } } @@ -101,6 +109,18 @@ function ConfirmSendEther () { this.onSubmit = this.onSubmit.bind(this) } +ConfirmSendEther.prototype.componentWillMount = function () { + const { updateSendErrors } = this.props + const txMeta = this.gatherTxMeta() + const balanceIsSufficient = this.isBalanceSufficient(txMeta) + + updateSendErrors({ + insufficientFunds: balanceIsSufficient + ? false + : this.context.t('insufficientFunds'), + }) +} + ConfirmSendEther.prototype.getAmount = function () { const { conversionRate, currentCurrency } = this.props const txMeta = this.gatherTxMeta() @@ -223,7 +243,12 @@ ConfirmSendEther.prototype.render = function () { conversionRate, currentCurrency: convertedCurrency, showCustomizeGasModal, - send: { gasTotal, gasLimit: sendGasLimit, gasPrice: sendGasPrice }, + send: { + gasTotal, + gasLimit: sendGasLimit, + gasPrice: sendGasPrice, + errors, + }, } = this.props const txMeta = this.gatherTxMeta() const txParams = txMeta.txParams || {} @@ -331,7 +356,12 @@ ConfirmSendEther.prototype.render = function () { ]), h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ - h('div.confirm-screen-section-column', [ + h('div', { + className: classnames({ + 'confirm-screen-section-column--with-error': errors['insufficientFunds'], + 'confirm-screen-section-column': !errors['insufficientFunds'], + }), + }, [ h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]), h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]), ]), @@ -340,6 +370,8 @@ ConfirmSendEther.prototype.render = function () { h('div.confirm-screen-row-info', `${totalInFIAT} ${currentCurrency.toUpperCase()}`), h('div.confirm-screen-row-detail', `${totalInETH} ETH`), ]), + + this.renderErrorMessage('insufficientFunds'), ]), ]), @@ -444,16 +476,28 @@ ConfirmSendEther.prototype.render = function () { ) } +ConfirmSendEther.prototype.renderErrorMessage = function (message) { + const { send: { errors } } = this.props + + return errors[message] + ? h('div.confirm-screen-error', [ errors[message] ]) + : null +} + ConfirmSendEther.prototype.onSubmit = function (event) { event.preventDefault() + const { updateSendErrors } = this.props const txMeta = this.gatherTxMeta() const valid = this.checkValidity() + const balanceIsSufficient = this.isBalanceSufficient(txMeta) this.setState({ valid, submitting: true }) - if (valid && this.verifyGasParams()) { + if (valid && this.verifyGasParams() && balanceIsSufficient) { this.props.sendTransaction(txMeta, event) + } else if (!balanceIsSufficient) { + updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') }) } else { - this.props.dispatch(actions.displayWarning(this.context.t('invalidGasParams'))) + updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') }) this.setState({ submitting: false }) } } @@ -465,6 +509,28 @@ ConfirmSendEther.prototype.cancel = function (event, txMeta) { cancelTransaction(txMeta) } +ConfirmSendEther.prototype.isBalanceSufficient = function (txMeta) { + const { + balance, + conversionRate, + } = this.props + const { + txParams: { + gas, + gasPrice, + value: amount, + }, + } = txMeta + const gasTotal = getGasTotal(gas, gasPrice) + + return isBalanceSufficient({ + amount, + gasTotal, + balance, + conversionRate, + }) +} + ConfirmSendEther.prototype.checkValidity = function () { const form = this.getFormEl() const valid = form.checkValidity() diff --git a/ui/app/components/pending-tx/confirm-send-token.js b/ui/app/components/pending-tx/confirm-send-token.js index 7fe260a61..dd9fdc23f 100644 --- a/ui/app/components/pending-tx/confirm-send-token.js +++ b/ui/app/components/pending-tx/confirm-send-token.js @@ -18,8 +18,13 @@ const { addCurrencies, } = require('../../conversion-util') const { + getGasTotal, + isBalanceSufficient, +} = require('../send/send-utils') +const { calcTokenAmount, } = require('../../token-util') +const classnames = require('classnames') const { MIN_GAS_PRICE_HEX } = require('../send/send-constants') @@ -46,9 +51,10 @@ function mapStateToProps (state, ownProps) { identities, currentCurrency, } = state.metamask + const accounts = state.metamask.accounts const selectedAddress = getSelectedAddress(state) const tokenExchangeRate = getTokenExchangeRate(state, symbol) - + const { balance } = accounts[selectedAddress] return { conversionRate, identities, @@ -58,6 +64,7 @@ function mapStateToProps (state, ownProps) { currentCurrency: currentCurrency.toUpperCase(), send: state.metamask.send, tokenContract: getSelectedTokenContract(state), + balance, } } @@ -129,6 +136,7 @@ function mapDispatchToProps (dispatch, ownProps) { })) dispatch(actions.showModal({ name: 'CUSTOMIZE_GAS' })) }, + updateSendErrors: error => dispatch(actions.updateSendErrors(error)), } } @@ -140,12 +148,20 @@ function ConfirmSendToken () { } ConfirmSendToken.prototype.componentWillMount = function () { - const { tokenContract, selectedAddress } = this.props + const { tokenContract, selectedAddress, updateSendErrors} = this.props + const txMeta = this.gatherTxMeta() + const balanceIsSufficient = this.isBalanceSufficient(txMeta) tokenContract && tokenContract .balanceOf(selectedAddress) .then(usersToken => { }) this.props.updateTokenExchangeRate() + + updateSendErrors({ + insufficientFunds: balanceIsSufficient + ? false + : this.context.t('insufficientFunds'), + }) } ConfirmSendToken.prototype.getAmount = function () { @@ -306,7 +322,7 @@ ConfirmSendToken.prototype.renderGasFee = function () { } ConfirmSendToken.prototype.renderTotalPlusGas = function () { - const { token: { symbol }, currentCurrency } = this.props + const { token: { symbol }, currentCurrency, send: { errors } } = this.props const { fiat: fiatAmount, token: tokenAmount } = this.getAmount() const { fiat: fiatGas, token: tokenGas } = this.getGasFee() @@ -326,7 +342,12 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () { ) : ( h('section.flex-row.flex-center.confirm-screen-row.confirm-screen-total-box ', [ - h('div.confirm-screen-section-column', [ + h('div', { + className: classnames({ + 'confirm-screen-section-column--with-error': errors['insufficientFunds'], + 'confirm-screen-section-column': !errors['insufficientFunds'], + }), + }, [ h('span.confirm-screen-label', [ this.context.t('total') + ' ' ]), h('div.confirm-screen-total-box__subtitle', [ this.context.t('amountPlusGas') ]), ]), @@ -335,10 +356,20 @@ ConfirmSendToken.prototype.renderTotalPlusGas = function () { h('div.confirm-screen-row-info', `${tokenAmount} ${symbol}`), h('div.confirm-screen-row-detail', `+ ${fiatGas} ${currentCurrency} ${this.context.t('gas')}`), ]), + + this.renderErrorMessage('insufficientFunds'), ]) ) } +ConfirmSendToken.prototype.renderErrorMessage = function (message) { + const { send: { errors } } = this.props + + return errors[message] + ? h('div.confirm-screen-error', [ errors[message] ]) + : null +} + ConfirmSendToken.prototype.render = function () { const { editTransaction } = this.props const txMeta = this.gatherTxMeta() @@ -455,18 +486,44 @@ ConfirmSendToken.prototype.render = function () { ConfirmSendToken.prototype.onSubmit = function (event) { event.preventDefault() + const { updateSendErrors } = this.props const txMeta = this.gatherTxMeta() const valid = this.checkValidity() + const balanceIsSufficient = this.isBalanceSufficient(txMeta) this.setState({ valid, submitting: true }) - if (valid && this.verifyGasParams()) { + if (valid && this.verifyGasParams() && balanceIsSufficient) { this.props.sendTransaction(txMeta, event) + } else if (!balanceIsSufficient) { + updateSendErrors({ insufficientFunds: this.context.t('insufficientFunds') }) } else { - this.props.dispatch(actions.displayWarning(this.context.t('invalidGasParams'))) + updateSendErrors({ invalidGasParams: this.context.t('invalidGasParams') }) this.setState({ submitting: false }) } } +ConfirmSendToken.prototype.isBalanceSufficient = function (txMeta) { + const { + balance, + conversionRate, + } = this.props + const { + txParams: { + gas, + gasPrice, + }, + } = txMeta + const gasTotal = getGasTotal(gas, gasPrice) + + return isBalanceSufficient({ + amount: '0', + gasTotal, + balance, + conversionRate, + }) +} + + ConfirmSendToken.prototype.cancel = function (event, txMeta) { event.preventDefault() const { cancelTransaction } = this.props diff --git a/ui/app/components/send/send-utils.js b/ui/app/components/send/send-utils.js index d8211930d..71bfb2668 100644 --- a/ui/app/components/send/send-utils.js +++ b/ui/app/components/send/send-utils.js @@ -2,6 +2,7 @@ const { addCurrencies, conversionUtil, conversionGTE, + multiplyCurrencies, } = require('../../conversion-util') const { calcTokenAmount, @@ -31,7 +32,7 @@ function isBalanceSufficient ({ { value: totalAmount, fromNumericBase: 'hex', - conversionRate: amountConversionRate, + conversionRate: amountConversionRate || conversionRate, fromCurrency: primaryCurrency, }, ) @@ -62,7 +63,16 @@ function isTokenBalanceSufficient ({ return tokenBalanceIsSufficient } +function getGasTotal (gasLimit, gasPrice) { + return multiplyCurrencies(gasLimit, gasPrice, { + toNumericBase: 'hex', + multiplicandBase: 16, + multiplierBase: 16, + }) +} + module.exports = { + getGasTotal, isBalanceSufficient, isTokenBalanceSufficient, } diff --git a/ui/app/components/sender-to-recipient.js b/ui/app/components/sender-to-recipient.js index 6b9cd32ea..9cef8e401 100644 --- a/ui/app/components/sender-to-recipient.js +++ b/ui/app/components/sender-to-recipient.js @@ -46,7 +46,7 @@ class SenderToRecipient extends Component { h('img', { height: 15, width: 15, - src: '/images/arrow-right.svg', + src: './images/arrow-right.svg', }), ]), ]), diff --git a/ui/app/css/itcss/components/confirm.scss b/ui/app/css/itcss/components/confirm.scss index abe138f54..85ff14e6e 100644 --- a/ui/app/css/itcss/components/confirm.scss +++ b/ui/app/css/itcss/components/confirm.scss @@ -266,6 +266,7 @@ section .confirm-screen-account-number, .confirm-screen-total-box { background-color: $wild-sand; + position: relative; .confirm-screen-label { line-height: 21px; @@ -287,6 +288,30 @@ section .confirm-screen-account-number, } } +.confirm-screen-error { + font-size: 12px; + line-height: 12px; + color: #f00; + position: absolute; + right: 12px; + width: 80px; + text-align: right; +} + +.confirm-screen-row.confirm-screen-total-box { + .confirm-screen-section-column--with-error { + flex: 0.6; + } +} + +@media screen and (max-width: 379px) { + .confirm-screen-row.confirm-screen-total-box { + .confirm-screen-section-column--with-error { + flex: 0.4; + } + } +} + .confirm-screen-confirm-button { height: 50px; border-radius: 4px; diff --git a/ui/app/css/itcss/settings/typography.scss b/ui/app/css/itcss/settings/typography.scss index 8a56d9c6c..18c444c8a 100644 --- a/ui/app/css/itcss/settings/typography.scss +++ b/ui/app/css/itcss/settings/typography.scss @@ -1,4 +1,4 @@ -@import url('/fonts/Font_Awesome/font-awesome.min.css'); +@import url('./fonts/Font_Awesome/font-awesome.min.css'); @font-face { font-family: 'Roboto'; @@ -338,8 +338,8 @@ @font-face { font-family: 'Montserrat Regular'; - src: url('/fonts/Montserrat/Montserrat-Regular.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); + src: url('./fonts/Montserrat/Montserrat-Regular.woff') format('woff'); + src: url('./fonts/Montserrat/Montserrat-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; font-size: 'small'; @@ -347,59 +347,59 @@ @font-face { font-family: 'Montserrat Bold'; - src: url('/fonts/Montserrat/Montserrat-Bold.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); + src: url('./fonts/Montserrat/Montserrat-Bold.woff') format('woff'); + src: url('./fonts/Montserrat/Montserrat-Bold.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Montserrat Light'; - src: url('/fonts/Montserrat/Montserrat-Light.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-Light.ttf') format('truetype'); + src: url('./fonts/Montserrat/Montserrat-Light.woff') format('woff'); + src: url('./fonts/Montserrat/Montserrat-Light.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Montserrat UltraLight'; - src: url('/fonts/Montserrat/Montserrat-UltraLight.woff') format('woff'); - src: url('/fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype'); + src: url('./fonts/Montserrat/Montserrat-UltraLight.woff') format('woff'); + src: url('./fonts/Montserrat/Montserrat-UltraLight.ttf') format('truetype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'DIN OT'; - src: url('/fonts/DIN_OT/DINOT-2.otf') format('opentype'); + src: url('./fonts/DIN_OT/DINOT-2.otf') format('opentype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'DIN OT Light'; - src: url('/fonts/DIN_OT/DINOT-2.otf') format('opentype'); + src: url('./fonts/DIN_OT/DINOT-2.otf') format('opentype'); font-weight: 200; font-style: normal; } @font-face { font-family: 'DIN NEXT'; - src: url('/fonts/DIN Next/DIN Next W01 Regular.otf') format('opentype'); + src: url('./fonts/DIN Next/DIN Next W01 Regular.otf') format('opentype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'DIN NEXT Light'; - src: url('/fonts/DIN Next/DIN Next W10 Light.otf') format('opentype'); + src: url('./fonts/DIN Next/DIN Next W10 Light.otf') format('opentype'); font-weight: 400; font-style: normal; } @font-face { font-family: 'Lato'; - src: url('/fonts/Lato/Lato-Regular.ttf') format('truetype'); + src: url('./fonts/Lato/Lato-Regular.ttf') format('truetype'); font-weight: 400; font-style: normal; } diff --git a/ui/app/send-v2.js b/ui/app/send-v2.js index 910312b47..bcc5cb03d 100644 --- a/ui/app/send-v2.js +++ b/ui/app/send-v2.js @@ -27,6 +27,7 @@ const { const { isBalanceSufficient, isTokenBalanceSufficient, + getGasTotal, } = require('./components/send/send-utils') const { isValidAddress } = require('./util') @@ -132,7 +133,7 @@ SendTransactionScreen.prototype.updateGas = function () { estimateGas(estimateGasParams), ]) .then(([gasPrice, gas]) => { - const newGasTotal = this.getGasTotal(gas, gasPrice) + const newGasTotal = getGasTotal(gas, gasPrice) updateGasTotal(newGasTotal) this.setState({ gasLoadingError: false }) }) @@ -140,19 +141,11 @@ SendTransactionScreen.prototype.updateGas = function () { this.setState({ gasLoadingError: true }) }) } else { - const newGasTotal = this.getGasTotal(gasLimit, gasPrice) + const newGasTotal = getGasTotal(gasLimit, gasPrice) updateGasTotal(newGasTotal) } } -SendTransactionScreen.prototype.getGasTotal = function (gasLimit, gasPrice) { - return multiplyCurrencies(gasLimit, gasPrice, { - toNumericBase: 'hex', - multiplicandBase: 16, - multiplierBase: 16, - }) -} - SendTransactionScreen.prototype.componentDidUpdate = function (prevProps) { const { from: { balance }, diff --git a/ui/app/settings.js b/ui/app/settings.js index e91ac7340..3aa7b9c6b 100644 --- a/ui/app/settings.js +++ b/ui/app/settings.js @@ -108,11 +108,12 @@ class Settings extends Component { renderCurrentLocale () { const { updateCurrentLocale, currentLocale } = this.props const currentLocaleMeta = locales.find(locale => locale.code === currentLocale) + const currentLocaleName = currentLocaleMeta ? currentLocaleMeta.name : '' return h('div.settings__content-row', [ h('div.settings__content-item', [ h('span', 'Current Language'), - h('span.settings__content-description', `${currentLocaleMeta.name}`), + h('span.settings__content-description', `${currentLocaleName}`), ]), h('div.settings__content-item', [ h('div.settings__content-item-col', [ diff --git a/ui/i18n-helper.js b/ui/i18n-helper.js index 3ce24ddfb..db2fd2dc4 100644 --- a/ui/i18n-helper.js +++ b/ui/i18n-helper.js @@ -27,7 +27,7 @@ const getMessage = (locale, key, substitutions) => { function fetchLocale (localeName) { return new Promise((resolve, reject) => { - return fetch(`/_locales/${localeName}/messages.json`) + return fetch(`./_locales/${localeName}/messages.json`) .then(response => response.json()) .then( locale => resolve(locale), |