diff --git a/.eslintrc b/.eslintrc index d7cb8022e..7fab8f127 100644 --- a/.eslintrc +++ b/.eslintrc @@ -98,7 +98,7 @@ "no-obj-calls": 2, "no-octal": 2, "no-octal-escape": 2, - "no-path-concat": 2, + "no-path-concat": 1, "no-proto": 2, "no-redeclare": 2, "no-regex-spaces": 2, diff --git a/.nvmrc b/.nvmrc index 9773998bc..bb83eeea2 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -v6.0.0 +v6.3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md index d9aee6ac1..8ac0983d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## Current Master +- Fix various typos. + +## 2.7.3 2016-07-29 + +- Fix bug where changing an account would not update in a live Dapp. + +## 2.7.2 2016-07-29 + +- Add Ethereum Classic to provider menu +- Fix bug where host store would fail to receive updates. + +## 2.7.1 2016-07-27 + +- Fix bug where web3 would sometimes not be injected in time for the application. +- Fixed bug where sometimes when opening the plugin, it would not fully open until closing and re-opening. +- Got most functionality working within Firefox (still working on review process before it can be available). +- Fixed menu dropdown bug introduced in Chrome 52. + +## 2.7.0 2016-07-21 + - Added a Warning screen about storing ETH - Add buy Button! - MetaMask now throws descriptive errors when apps try to use synchronous web3 methods. diff --git a/README.md b/README.md index 34fac137c..4c348b56c 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,14 @@ ## Building locally - - Install [Node.js](https://nodejs.org/en/) version 6 or later. + - Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later. - Install local dependencies with `npm install`. - - Install gulp globally with `npm install -g gulp`. + - Install gulp globally with `npm install -g gulp-cli`. - Build the project to the `./dist/` folder with `gulp build`. - Optionally, to rebuild on file changes, run `gulp dev`. + - To package .zip files for distribution, run `gulp zip`, or run the full build & zip with `gulp dist`. + + Uncompressed builds can be found in `/dist`, compressed builds can be found in `/builds` once they're built. ## Architecture @@ -18,19 +21,6 @@ npm install ``` -### Developing with Gulp - -We're using an experimental version of `gulp-cli`, so if you have the old version of gulp, you'll need to uninstall it, `npm uninstall -g gulp`, and install this one instead: - -```bash -npm install gulpjs/gulp-cli#4.0 -g -``` - -After that, you can just: -```bash -gulp dev -``` - #### In Chrome Open `Settings` > `Extensions`. @@ -39,12 +29,26 @@ Check "Developer mode". At the top, click `Load Unpacked Extension`. -Navigate to your `metamask-plugin/dist` folder. +Navigate to your `metamask-plugin/dist/chrome` folder. Click `Select`. You now have the plugin, and can click 'inspect views: background plugin' to view its dev console. +#### In Firefox + +Go to the url `about:debugging`. + +Click the button `Load Temporary Add-On`. + +Select the file `dist/firefox/manifest.json`. + +You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console. + +If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`. + +For longer questions, use the StackOverfow tag `firefox-addons`. + ### Developing on UI Only You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI. @@ -85,7 +89,7 @@ You can run the linter by itself with `gulp lint`. 0. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`. 1. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). - 2. Zip the `dist` folder in this repository. - 3. Upload that zip file as the updated package. + 2. Run `gulp dist` (or `gulp zip` if you've already built) + 3. Upload the latest zip file from `builds/metamask-$PLATFORM-$VERSION.zip` as the updated package. [1]: http://www.nomnoml.com/#view/%5B%3Cactor%3Euser%5D%0A%0A%5Bmetamask-ui%7C%0A%20%20%20%5Btools%7C%0A%20%20%20%20%20react%0A%20%20%20%20%20redux%0A%20%20%20%20%20thunk%0A%20%20%20%20%20ethUtils%0A%20%20%20%20%20jazzicon%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20account-detail%0A%20%20%20%20%20accounts%0A%20%20%20%20%20locked-screen%0A%20%20%20%20%20restore-vault%0A%20%20%20%20%20identicon%0A%20%20%20%20%20config%0A%20%20%20%20%20info%0A%20%20%20%5D%0A%20%20%20%5Breducers%7C%0A%20%20%20%20%20app%0A%20%20%20%20%20metamask%0A%20%20%20%20%20identities%0A%20%20%20%5D%0A%20%20%20%5Bactions%7C%0A%20%20%20%20%20%5BaccountManager%5D%0A%20%20%20%5D%0A%20%20%20%5Bcomponents%5D%3A-%3E%5Bactions%5D%0A%20%20%20%5Bactions%5D%3A-%3E%5Breducers%5D%0A%20%20%20%5Breducers%5D%3A-%3E%5Bcomponents%5D%0A%5D%0A%0A%5Bweb%20dapp%7C%0A%20%20%5Bui%20code%5D%0A%20%20%5Bweb3%5D%0A%20%20%5Bmetamask-inpage%5D%0A%20%20%0A%20%20%5B%3Cactor%3Eui%20developer%5D%0A%20%20%5Bui%20developer%5D-%3E%5Bui%20code%5D%0A%20%20%5Bui%20code%5D%3C-%3E%5Bweb3%5D%0A%20%20%5Bweb3%5D%3C-%3E%5Bmetamask-inpage%5D%0A%5D%0A%0A%5Bmetamask-background%7C%0A%20%20%5Bprovider-engine%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bid%20store%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%3E%5Bhooked%20wallet%20subprovider%5D%0A%20%20%5Bhooked%20wallet%20subprovider%5D%3C-%3E%5Bid%20store%5D%0A%20%20%5Bconfig%20manager%7C%0A%20%20%20%20%5Brpc%20configuration%5D%0A%20%20%20%20%5Bencrypted%20keys%5D%0A%20%20%20%20%5Bwallet%20nicknames%5D%0A%20%20%5D%0A%20%20%0A%20%20%5Bprovider-engine%5D%3C-%5Bconfig%20manager%5D%0A%20%20%5Bid%20store%5D%3C-%3E%5Bconfig%20manager%5D%0A%5D%0A%0A%5Buser%5D%3C-%3E%5Bmetamask-ui%5D%0A%0A%5Buser%5D%3C%3A--%3A%3E%5Bweb%20dapp%5D%0A%0A%5Bmetamask-contentscript%7C%0A%20%20%5Bplugin%20restart%20detector%5D%0A%20%20%5Brpc%20passthrough%5D%0A%5D%0A%0A%5Brpc%20%7C%0A%20%20%5Bethereum%20blockchain%20%7C%0A%20%20%20%20%5Bcontracts%5D%0A%20%20%20%20%5Baccounts%5D%0A%20%20%5D%0A%5D%0A%0A%5Bweb%20dapp%5D%3C%3A--%3A%3E%5Bmetamask-contentscript%5D%0A%5Bmetamask-contentscript%5D%3C-%3E%5Bmetamask-background%5D%0A%5Bmetamask-background%5D%3C-%3E%5Bmetamask-ui%5D%0A%5Bmetamask-background%5D%3C-%3E%5Brpc%5D%0A diff --git a/app/manifest.json b/app/manifest.json index 2c5629f7a..fa71742b1 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -1,13 +1,18 @@ { - "name": "__MSG_appName__", + "name": "MetaMask", "short_name": "Metamask", - "version": "2.6.2", + "version": "2.7.3", "manifest_version": 2, - "description": "__MSG_appDescription__", + "description": "Ethereum Browser Extension", "icons": { "16": "images/icon-16.png", "128": "images/icon-128.png" }, + "applications": { + "gecko": { + "id": "webextension@metamask.io" + } + }, "default_locale": "en", "background": { "scripts": [ diff --git a/app/scripts/chromereload.js b/app/scripts/chromereload.js index cd85a8114..88333ba8a 100644 --- a/app/scripts/chromereload.js +++ b/app/scripts/chromereload.js @@ -30,7 +30,6 @@ // } // }; -const extension = require('./lib/extension') window.LiveReloadOptions = { host: 'localhost' }; (function e (t, n, r) { function s (o, u) { if (!n[o]) { if (!t[o]) { var a = typeof require === 'function' && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw f.code = 'MODULE_NOT_FOUND', f } var l = n[o] = {exports: {}}; t[o][0].call(l.exports, function (e) { var n = t[o][1][e]; return s(n ? n : e) }, l, l.exports, e, t, n, r) } return n[o].exports } var i = typeof require === 'function' && require; for (var o = 0; o < r.length; o++)s(r[o]); return s })({1: [function (require, module, exports) { diff --git a/app/scripts/config.js b/app/scripts/config.js index f26e6778d..5f6ffd936 100644 --- a/app/scripts/config.js +++ b/app/scripts/config.js @@ -1,12 +1,14 @@ const MAINET_RPC_URL = 'https://mainnet.infura.io/' const TESTNET_RPC_URL = 'https://morden.infura.io/' const DEFAULT_RPC_URL = TESTNET_RPC_URL +const CLASSIC_RPC_URL = 'https://mainnet-nf.infura.io/' module.exports = { network: { default: DEFAULT_RPC_URL, mainnet: MAINET_RPC_URL, testnet: TESTNET_RPC_URL, + classic: CLASSIC_RPC_URL, }, } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 0ffe93e3c..1eb04059d 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -3,19 +3,37 @@ const PortStream = require('./lib/port-stream.js') const ObjectMultiplex = require('./lib/obj-multiplex') const extension = require('./lib/extension') +const fs = require('fs') +const path = require('path') +const inpageText = fs.readFileSync(path.join(__dirname + '/inpage.js')).toString() + +// Eventually this streaming injection could be replaced with: +// https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Language_Bindings/Components.utils.exportFunction +// +// But for now that is only Firefox +// If we create a FireFox-only code path using that API, +// MetaMask will be much faster loading and performant on Firefox. + if (shouldInjectWeb3()) { setupInjection() setupStreams() } function setupInjection(){ - // inject in-page script - var scriptTag = document.createElement('script') - scriptTag.src = extension.extension.getURL('scripts/inpage.js') - scriptTag.onload = function () { this.parentNode.removeChild(this) } - var container = document.head || document.documentElement - // append as first child - container.insertBefore(scriptTag, container.children[0]) + try { + + // inject in-page script + var scriptTag = document.createElement('script') + scriptTag.src = extension.extension.getURL('scripts/inpage.js') + scriptTag.textContent = inpageText + scriptTag.onload = function () { this.parentNode.removeChild(this) } + var container = document.head || document.documentElement + // append as first child + container.insertBefore(scriptTag, container.children[0]) + + } catch (e) { + console.error('Metamask injection failed.', e) + } } function setupStreams(){ @@ -44,7 +62,6 @@ function setupStreams(){ pluginStream.on('close', function () { reloadStream.write({ method: 'reset' }) }) - } function shouldInjectWeb3(){ diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 8d250e555..9c37a2c6f 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -5,6 +5,7 @@ const rp = require('request-promise') const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet +const CLASSIC_RPC = MetamaskConfig.network.classic /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -145,6 +146,9 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'testnet': return TESTNET_RPC + case 'classic': + return CLASSIC_RPC + default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC } diff --git a/app/scripts/lib/extension-instance.js b/app/scripts/lib/extension-instance.js index eeab6c6d0..eb3b8a1e9 100644 --- a/app/scripts/lib/extension-instance.js +++ b/app/scripts/lib/extension-instance.js @@ -24,14 +24,27 @@ const apis = [ function Extension () { const _this = this - let global = window - - if (window.chrome) { - global = window.chrome - } apis.forEach(function (api) { - _this[api] = global[api] + + _this[api] = null + + try { + if (chrome[api]) { + _this[api] = chrome[api] + } + } catch (e) {} + + try { + if (window[api]) { + _this[api] = window[api] + } + } catch (e) {} + + try { + _this.api = browser.extension[api] + } catch (e) {} + }) } diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 613484ffb..45305ee8f 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -170,6 +170,7 @@ module.exports = class MetamaskController { function configToPublic (state) { return { provider: state.provider, + selectedAddress: state.selectedAccount, } } // dump obj into store diff --git a/gulpfile.js b/gulpfile.js index 941155ff4..aeaf3e674 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,19 +6,23 @@ 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 brfs = require('gulp-brfs') var del = require('del') var eslint = require('gulp-eslint') var fs = require('fs') var path = require('path') +var manifest = require('./app/manifest.json') // browser reload gulp.task('dev:reload', function() { livereload.listen({ port: 35729, - // basePath: './dist/' + // basePath: './dist/firefox/' }) }) @@ -27,27 +31,52 @@ gulp.task('dev:reload', function() { gulp.task('copy:locales', copyTask({ source: './app/_locales/', - destination: './dist/_locales', + destinations: [ + './dist/firefox/_locales', + './dist/chrome/_locales', + ] })) gulp.task('copy:images', copyTask({ source: './app/images/', - destination: './dist/images', + destinations: [ + './dist/firefox/images', + './dist/chrome/images', + ], })) gulp.task('copy:fonts', copyTask({ source: './app/fonts/', - destination: './dist/fonts', + destinations: [ + './dist/firefox/fonts', + './dist/chrome/fonts', + ], })) gulp.task('copy:reload', copyTask({ source: './app/scripts/', - destination: './dist/scripts', + destinations: [ + './dist/firefox/scripts', + './dist/chrome/scripts', + ], pattern: '/chromereload.js', })) gulp.task('copy:root', copyTask({ source: './app/', - destination: './dist', + destinations: [ + './dist/firefox', + './dist/chrome', + ], pattern: '/*', })) -gulp.task('copy', gulp.parallel('copy:locales','copy:images','copy:fonts','copy:reload','copy:root')) + +gulp.task('manifest:cleanup', function() { + return gulp.src('./dist/firefox/manifest.json') + .pipe(jsoneditor(function(json) { + delete json.applications + return json + })) + .pipe(gulp.dest('./dist/chrome', { overwrite: true })) +}) + +gulp.task('copy', gulp.series(gulp.parallel('copy:locales','copy:images','copy:fonts','copy:reload','copy:root'), 'manifest:cleanup')) gulp.task('copy:watch', function(){ gulp.watch(['./app/{_locales,images}/*', './app/scripts/chromereload.js', './app/*.{html,json}'], gulp.series('copy')) }) @@ -55,8 +84,8 @@ gulp.task('copy:watch', function(){ // lint js gulp.task('lint', function () { - // Ignoring node_modules, dist, and docs folders: - return gulp.src(['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/**', '!docs/**', '!app/scripts/chromereload.js']) + // Ignoring node_modules, dist/firefox, and docs folders: + return gulp.src(['app/**/*.js', 'ui/**/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js']) .pipe(eslint(fs.readFileSync(path.join(__dirname, '.eslintrc')))) // eslint.format() outputs the lint results to the console. // Alternatively use eslint.formatEach() (see Docs). @@ -74,48 +103,66 @@ gulp.task('default', ['lint'], function () { // build js -gulp.task('dev:js:inpage', bundleTask({ watch: true, filename: 'inpage.js' })) -gulp.task('dev:js:contentscript', bundleTask({ watch: true, filename: 'contentscript.js' })) -gulp.task('dev:js:background', bundleTask({ watch: true, filename: 'background.js' })) -gulp.task('dev:js:popup', bundleTask({ watch: true, filename: 'popup.js' })) -gulp.task('dev:js', gulp.parallel('dev:js:inpage','dev:js:contentscript','dev:js:background','dev:js:popup')) +const jsFiles = [ + 'inpage', + 'contentscript', + 'background', + 'popup', +] + +jsFiles.forEach((jsFile) => { + gulp.task(`dev:js:${jsFile}`, bundleTask({ watch: true, filename: `${jsFile}.js` })) + gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, filename: `${jsFile}.js` })) +}) + +gulp.task('dev:js', gulp.parallel('dev:js:inpage','dev:js:contentscript','dev:js:background','dev:js:popup')) -gulp.task('build:js:inpage', bundleTask({ watch: false, filename: 'inpage.js' })) -gulp.task('build:js:contentscript', bundleTask({ watch: false, filename: 'contentscript.js' })) -gulp.task('build:js:background', bundleTask({ watch: false, filename: 'background.js' })) -gulp.task('build:js:popup', bundleTask({ watch: false, filename: 'popup.js' })) gulp.task('build:js', gulp.parallel('build:js:inpage','build:js:contentscript','build:js:background','build:js:popup')) // clean dist gulp.task('clean', function clean() { - return del(['./dist']) + return del(['./dist/*']) }) +// zip tasks for distribution +gulp.task('zip:chrome', () => { + return gulp.src('dist/chrome/**') + .pipe(zip(`metamask-chrome-${manifest.version}.zip`)) + .pipe(gulp.dest('builds')); +}); +gulp.task('zip:firefox', () => { + return gulp.src('dist/firefox/**') + .pipe(zip(`metamask-firefox-${manifest.version}.zip`)) + .pipe(gulp.dest('builds')); +}); +gulp.task('zip', gulp.parallel('zip:chrome', 'zip:firefox')) // high level tasks gulp.task('dev', gulp.series('dev:js', 'copy', gulp.parallel('copy:watch', 'dev:reload'))) gulp.task('build', gulp.series('clean', gulp.parallel('build:js', 'copy'))) +gulp.task('dist', gulp.series('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(){ - return ( + let stream = gulp.src(source + pattern, { base: source }) + destinations.forEach(function(destination) { + stream = stream.pipe(gulp.dest(destination)) + }) + stream.pipe(livereload()) - gulp.src(source + pattern, { base: source }) - .pipe(gulp.dest(destination)) - .pipe(livereload()) - - ) + return stream } } @@ -144,13 +191,15 @@ function bundleTask(opts) { // log errors if they happen .on('error', gutil.log.bind(gutil, 'Browserify Error')) .pipe(source(opts.filename)) + .pipe(brfs()) // optional, remove if you don't need to buffer file contents .pipe(buffer()) // optional, remove if you dont want sourcemaps .pipe(sourcemaps.init({loadMaps: true})) // loads map from browserify file // Add transformation tasks to the pipeline here. .pipe(sourcemaps.write('./')) // writes .map file - .pipe(gulp.dest('./dist/scripts')) + .pipe(gulp.dest('./dist/firefox/scripts')) + .pipe(gulp.dest('./dist/chrome/scripts')) .pipe(livereload()) ) diff --git a/package.json b/package.json index ef570fb94..ded561301 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "private": true, "scripts": { "start": "gulp dev", - "test": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\" && npm run ci", + "test": "npm run fastTest && npm run ci", + "fastTest": "mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"", "ui": "node development/genStates.js && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", "mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./", @@ -86,10 +87,13 @@ "deep-freeze-strict": "^1.1.1", "del": "^2.2.0", "gulp": "github:gulpjs/gulp#4.0", + "gulp-brfs": "^0.1.0", + "gulp-json-editor": "^2.2.1", "gulp-livereload": "^3.8.1", "gulp-sourcemaps": "^1.6.0", "gulp-util": "^3.0.7", "gulp-watch": "^4.3.5", + "gulp-zip": "^3.2.0", "jsdom": "^8.1.0", "jsdom-global": "^1.7.0", "jshint-stylish": "~0.1.5", diff --git a/test/unit/extension-test.js b/test/unit/extension-test.js index 0a03a3b97..6b695e835 100644 --- a/test/unit/extension-test.js +++ b/test/unit/extension-test.js @@ -1,6 +1,8 @@ var assert = require('assert') var sinon = require('sinon') const ethUtil = require('ethereumjs-util') +GLOBAL.chrome = {} +GLOBAL.browser = {} var path = require('path') var Extension = require(path.join(__dirname, '..', '..', 'app', 'scripts', 'lib', 'extension-instance.js')) @@ -11,7 +13,7 @@ describe('extension', function() { let extension beforeEach(function() { - window.chrome = { + GLOBAL.chrome = { alarms: 'foo' } extension = new Extension() @@ -24,13 +26,20 @@ describe('extension', function() { describe('without chrome global', function() { let extension + let realWindow beforeEach(function() { - window.chrome = undefined - window.alarms = 'foo' + realWindow = window + window = GLOBAL + GLOBAL.chrome = undefined + GLOBAL.alarms = 'foo' extension = new Extension() }) + after(function() { + window = realWindow + }) + it('should use the global apis', function() { assert.equal(extension.alarms, 'foo') }) diff --git a/ui/app/app.js b/ui/app/app.js index a11d679f1..cc616fb7c 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -131,6 +131,7 @@ App.prototype.renderAppBar = function () { h(NetworkIndicator, { network: this.props.network, + provider: this.props.provider, onClick: (event) => { event.preventDefault() event.stopPropagation() @@ -203,6 +204,7 @@ App.prototype.renderNetworkDropdown = function () { style: { position: 'absolute', left: 0, + top: '36px', }, innerStyle: { background: 'white', @@ -220,6 +222,16 @@ App.prototype.renderNetworkDropdown = function () { action: () => props.dispatch(actions.setProviderType('mainnet')), icon: h('.menu-icon.diamond'), activeNetworkRender: props.network, + provider: props.provider, + }), + + h(DropMenuItem, { + label: 'Ethereum Classic Network', + closeMenu: () => this.setState({ isNetworkMenuOpen: false }), + action: () => props.dispatch(actions.setProviderType('classic')), + icon: h('.menu-icon.hollow-diamond'), + activeNetworkRender: props.network, + provider: props.provider, }), h(DropMenuItem, { @@ -237,6 +249,7 @@ App.prototype.renderNetworkDropdown = function () { icon: h('i.fa.fa-question-circle.fa-lg', { ariaHidden: true }), activeNetworkRender: props.provider.rpcTarget, }), + this.renderCustomOption(props.provider.rpcTarget), ]) } @@ -254,6 +267,7 @@ App.prototype.renderDropdown = function () { style: { position: 'absolute', right: 0, + top: '36px', }, innerStyle: { background: 'white', diff --git a/ui/app/components/drop-menu-item.js b/ui/app/components/drop-menu-item.js index f5800f799..1a0d6cbd5 100644 --- a/ui/app/components/drop-menu-item.js +++ b/ui/app/components/drop-menu-item.js @@ -32,20 +32,25 @@ DropMenuItem.prototype.render = function () { } DropMenuItem.prototype.activeNetworkRender = function () { - var activeNetwork = this.props.activeNetworkRender + let activeNetwork = this.props.activeNetworkRender + let { provider } = this.props + let providerType = provider ? provider.type : null if (activeNetwork === undefined) return switch (this.props.label) { case 'Main Ethereum Network': - if (activeNetwork === '1') return h('.check', ' ✓') + if (providerType === 'mainnet') return h('.check', '✓') + break + case 'Ethereum Classic Network': + if (providerType === 'classic') return h('.check', '✓') break case 'Morden Test Network': - if (activeNetwork === '2') return h('.check', ' ✓') + if (activeNetwork === '2') return h('.check', '✓') break case 'Localhost 8545': - if (activeNetwork === 'http://localhost:8545') return h('.check', ' ✓') + if (activeNetwork === 'http://localhost:8545') return h('.check', '✓') break default: - if (activeNetwork === 'custom') return h('.check', ' ✓') + if (activeNetwork === 'custom') return h('.check', '✓') } } diff --git a/ui/app/components/network.js b/ui/app/components/network.js index 032e71699..95901fe70 100644 --- a/ui/app/components/network.js +++ b/ui/app/components/network.js @@ -11,11 +11,18 @@ function Network () { } Network.prototype.render = function () { - const state = this.props - const networkNumber = state.network + const props = this.props + const networkNumber = props.network + let providerName + try { + providerName = props.provider.type + } catch (e) { + providerName = null + } let iconName, hoverText if (networkNumber === 'loading') { + return h('img', { title: 'Attempting to connect to blockchain.', onClick: (event) => this.props.onClick(event), @@ -25,9 +32,13 @@ Network.prototype.render = function () { }, src: 'images/loading.svg', }) - } else if (parseInt(networkNumber) === 1) { + + } else if (providerName === 'mainnet') { hoverText = 'Main Ethereum Network' iconName = 'ethereum-network' + } else if (providerName === 'classic') { + hoverText = 'Ethereum Classic Network' + iconName = 'classic-network' } else if (parseInt(networkNumber) === 2) { hoverText = 'Morden Test Network' iconName = 'morden-test-network' @@ -55,6 +66,15 @@ Network.prototype.render = function () { }}, 'Etherum Main Net'), ]) + case 'classic-network': + return h('.network-indicator', [ + h('.menu-icon.hollow-diamond'), + h('.network-name', { + style: { + color: '#039396', + }}, + 'Etherum Classic'), + ]) case 'morden-test-network': return h('.network-indicator', [ h('.menu-icon.red-dot'), diff --git a/ui/app/css/index.css b/ui/app/css/index.css index 36430b5eb..612dc9d9a 100644 --- a/ui/app/css/index.css +++ b/ui/app/css/index.css @@ -151,12 +151,14 @@ textarea.twelve-word-phrase { .network-name { position: absolute; top: 8px; + left: 60px; width: 5.2em; line-height: 9px; text-rendering: geometricPrecision; } .check { + margin-left: 7px; color: #F7861C; flex: 1 0 auto; display: flex; diff --git a/ui/app/css/lib.css b/ui/app/css/lib.css index 22b26d4f1..bcd6a4a67 100644 --- a/ui/app/css/lib.css +++ b/ui/app/css/lib.css @@ -178,6 +178,11 @@ hr.horizontal-line { background: #038789; } +.hollow-diamond { + transform: rotate(45deg); + border: 1px solid #038789; +} + .pending-dot { background: red; left: 14px; diff --git a/ui/app/eth-store-warning.js b/ui/app/eth-store-warning.js index 4cc5da81c..7fe54a309 100644 --- a/ui/app/eth-store-warning.js +++ b/ui/app/eth-store-warning.js @@ -56,14 +56,14 @@ EthStoreWarning.prototype.render = function () { }, [ h('input', { type: 'checkbox', - onChange: this.toggleShowWarning.bind(this, event), + onChange: this.toggleShowWarning.bind(this), }), h('.warning', { style: { fontSize: '11px', }, - }, 'Dont show me this message again'), + }, 'Don\'t show me this message again'), ]), h('.flex-row', { style: { @@ -80,7 +80,7 @@ EthStoreWarning.prototype.render = function () { ) } -EthStoreWarning.prototype.toggleShowWarning = function (event) { +EthStoreWarning.prototype.toggleShowWarning = function () { this.props.dispatch(actions.agreeToEthWarning()) } diff --git a/ui/app/first-time/create-vault.js b/ui/app/first-time/create-vault.js index 3dfbf0dbd..33ae62179 100644 --- a/ui/app/first-time/create-vault.js +++ b/ui/app/first-time/create-vault.js @@ -120,7 +120,7 @@ CreateVaultScreen.prototype.createNewVault = function () { return } if (password !== passwordConfirm) { - this.warning = 'passwords dont match' + this.warning = 'passwords don\'t match' this.props.dispatch(actions.displayWarning(this.warning)) return } diff --git a/ui/app/settings.js b/ui/app/settings.js index e56f4ee63..454cc95e0 100644 --- a/ui/app/settings.js +++ b/ui/app/settings.js @@ -32,7 +32,6 @@ AppSettingsPage.prototype.render = function () { htmlFor: 'settings-rpc-endpoint', }, 'RPC Endpoint:'), h('input', { - // value: '//testrpc.metamask.io', type: 'url', id: 'settings-rpc-endpoint', onKeyPress: this.onKeyPress.bind(this),