1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-12-23 09:52:26 +01:00

Merge branch 'master' into buyForm

This commit is contained in:
Frankie 2016-08-10 13:48:34 -07:00
commit ba1edc429b
53 changed files with 855 additions and 130 deletions

1
.eslintignore Normal file
View File

@ -0,0 +1 @@
app/scripts/lib/extension-instance.js

View File

@ -23,7 +23,6 @@
],
"globals": {
"chrome": true,
"document": false,
"navigator": false,
"web3": true,
@ -99,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,

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ package
builds/
notes.txt
app/.DS_Store
development/bundle.js

2
.nvmrc
View File

@ -1 +1 @@
v6.0.0
v6.3.1

View File

@ -2,6 +2,36 @@
## 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.
- Removed firefox-specific line in manifest.
## 2.6.2 2016-07-20
- Fixed bug that would prevent the plugin from reopening on the first try after receiving a new transaction while locked.
- Fixed bug that would render 0 ETH as a non-exact amount.
## 2.6.1 2016-07-13
- Fix tool tips on Eth balance to show the 6 decimals

View File

@ -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,26 +21,7 @@
npm install
```
### Developing on UI Only
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI.
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension.
### 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
#### In Chrome
Open `Settings` > `Extensions`.
@ -45,13 +29,41 @@ 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.
### Developing the UI
#### 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.
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension.
### Developing on UI with Mocked Background Process
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations.
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work.
You can reset the mock ui at any time with the `Reset` button at the top of the screen.
### Developing on Dependencies
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies:
@ -77,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

View File

@ -1,13 +1,18 @@
{
"name": "__MSG_appName__",
"name": "MetaMask",
"short_name": "Metamask",
"version": "2.6.1",
"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": [

View File

@ -9,6 +9,7 @@ const createMsgNotification = require('./lib/notifications.js').createMsgNotific
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const MetamaskController = require('./metamask-controller')
const extension = require('./lib/extension')
const STORAGE_KEY = 'metamask-config'
@ -65,7 +66,7 @@ function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
// connect to other contexts
//
chrome.runtime.onConnect.addListener(connectRemote)
extension.runtime.onConnect.addListener(connectRemote)
function connectRemote (remotePort) {
var isMetaMaskInternalProcess = (remotePort.name === 'popup')
var portStream = new PortStream(remotePort)
@ -133,8 +134,8 @@ function updateBadge (state) {
if (count) {
label = String(count)
}
chrome.browserAction.setBadgeText({ text: label })
chrome.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
extension.browserAction.setBadgeText({ text: label })
extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' })
}
function loadData () {

View File

@ -25,7 +25,7 @@
// if (e.data) {
// var data = JSON.parse(e.data);
// if (data && data.command === 'reload') {
// chrome.runtime.reload();
// extension.runtime.reload();
// }
// }
// };

View File

@ -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,
},
}

View File

@ -1,6 +1,18 @@
const LocalMessageDuplexStream = require('./lib/local-message-stream.js')
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()
@ -8,13 +20,20 @@ if (shouldInjectWeb3()) {
}
function setupInjection(){
// inject in-page script
var scriptTag = document.createElement('script')
scriptTag.src = chrome.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(){
@ -25,7 +44,7 @@ function setupStreams(){
target: 'inpage',
})
pageStream.on('error', console.error.bind(console))
var pluginPort = chrome.runtime.connect({name: 'contentscript'})
var pluginPort = extension.runtime.connect({name: 'contentscript'})
var pluginStream = new PortStream(pluginPort)
pluginStream.on('error', console.error.bind(console))
@ -43,10 +62,9 @@ function setupStreams(){
pluginStream.on('close', function () {
reloadStream.write({ method: 'reset' })
})
}
function shouldInjectWeb3(){
var shouldInject = (window.location.href.indexOf('.pdf') === -1)
return shouldInject
}
}

View File

@ -53,9 +53,17 @@ var __define
function cleanContextForImports () {
__define = global.define
delete global.define
try {
delete global.define
} catch (_) {
console.warn('MetaMask - global.define could not be deleted.')
}
}
function restoreContextAfterImports () {
global.define = __define
try {
global.define = __define
} catch (_) {
console.warn('MetaMask - global.define could not be overwritten.')
}
}

View File

@ -4,6 +4,7 @@ const migrations = require('./migrations')
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.
@ -144,6 +145,9 @@ ConfigManager.prototype.getCurrentRpcAddress = function () {
case 'testnet':
return TESTNET_RPC
case 'classic':
return CLASSIC_RPC
default:
return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC
}
@ -270,3 +274,17 @@ ConfigManager.prototype.getConfirmed = function () {
return ('isConfirmed' in data) && data.isConfirmed
}
ConfigManager.prototype.setShouldntShowWarning = function () {
var data = this.getData()
if (data.isEthConfirmed) {
data.isEthConfirmed = !data.isEthConfirmed
} else {
data.isEthConfirmed = true
}
this.setData(data)
}
ConfigManager.prototype.getShouldntShowWarning = function () {
var data = this.getData()
return ('isEthConfirmed' in data) && data.isEthConfirmed
}

View File

@ -0,0 +1,51 @@
const apis = [
'alarms',
'bookmarks',
'browserAction',
'commands',
'contextMenus',
'cookies',
'downloads',
'events',
'extension',
'extensionTypes',
'history',
'i18n',
'idle',
'notifications',
'pageAction',
'runtime',
'storage',
'tabs',
'webNavigation',
'webRequest',
'windows',
]
function Extension () {
const _this = this
apis.forEach(function (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) {}
})
}
module.exports = Extension

View File

@ -0,0 +1,14 @@
/* Extension.js
*
* A module for unifying browser differences in the WebExtension API.
*
* Initially implemented because Chrome hides all of their WebExtension API
* behind a global `chrome` variable, but we'd like to start grooming
* the code-base for cross-browser extension support.
*
* You can read more about the WebExtension API here:
* https://developer.mozilla.org/en-US/Add-ons/WebExtensions
*/
const Extension = require('./extension-instance')
module.exports = new Extension()

View File

@ -94,6 +94,7 @@ IdentityStore.prototype.getState = function () {
isUnlocked: this._isUnlocked(),
seedWords: seedWords,
isConfirmed: configManager.getConfirmed(),
isEthConfirmed: configManager.getShouldntShowWarning(),
unconfTxs: configManager.unconfirmedTxs(),
transactions: configManager.getTxList(),
unconfMsgs: messageManager.unconfirmedMsgs(),
@ -199,7 +200,7 @@ IdentityStore.prototype.addUnconfirmedTransaction = function (txParams, onTxDone
time: time,
status: 'unconfirmed',
}
console.log('addUnconfirmedTransaction:', txData)
// keep the onTxDoneCb around for after approval/denial (requires user interaction)

View File

@ -107,7 +107,15 @@ function createSyncProvider (providerConfig) {
syncProviderUrl = MetamaskConfig.network.default
}
}
return new HttpProvider(syncProviderUrl)
const provider = new HttpProvider(syncProviderUrl)
// Stubbing out the send method to throw on sync methods:
provider.send = function() {
var message = 'The MetaMask Web3 object does not support synchronous methods. See https://github.com/MetaMask/faq#all-async---think-of-metamask-as-a-light-client for details.'
throw new Error(message)
}
return provider
}
function remoteStoreWithLocalStorageCache (storageKey) {

View File

@ -7,6 +7,7 @@ const h = require('react-hyperscript')
const PendingTxDetails = require('../../../ui/app/components/pending-tx-details')
const PendingMsgDetails = require('../../../ui/app/components/pending-msg-details')
const MetaMaskUiCss = require('../../../ui/css')
const extension = require('./extension')
var notificationHandlers = {}
const notifications = {
@ -20,34 +21,34 @@ window.METAMASK_NOTIFIER = notifications
setupListeners()
function setupListeners () {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
// notification button press
chrome.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
var handlers = notificationHandlers[notificationId]
if (buttonIndex === 0) {
handlers.confirm()
} else {
handlers.cancel()
}
chrome.notifications.clear(notificationId)
extension.notifications.clear(notificationId)
})
// notification teardown
chrome.notifications.onClosed.addListener(function (notificationId) {
extension.notifications.onClosed.addListener(function (notificationId) {
delete notificationHandlers[notificationId]
})
}
// creation helper
function createUnlockRequestNotification (opts) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
var message = 'An Ethereum app has requested a signature. Please unlock your account.'
var id = createId()
chrome.notifications.create(id, {
extension.notifications.create(id, {
type: 'basic',
iconUrl: '/images/icon-128.png',
title: opts.title,
@ -56,8 +57,8 @@ function createUnlockRequestNotification (opts) {
}
function createTxNotification (state) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
renderTxNotificationSVG(state, function (err, notificationSvgSource) {
if (err) throw err
@ -70,8 +71,8 @@ function createTxNotification (state) {
}
function createMsgNotification (state) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
renderMsgNotificationSVG(state, function (err, notificationSvgSource) {
if (err) throw err
@ -84,11 +85,11 @@ function createMsgNotification (state) {
}
function showNotification (state) {
// guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!chrome.notifications) return console.error('Chrome notifications API missing...')
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
if (!extension.notifications) return console.error('Chrome notifications API missing...')
var id = createId()
chrome.notifications.create(id, {
extension.notifications.create(id, {
type: 'image',
requireInteraction: true,
iconUrl: '/images/icon-128.png',

View File

@ -243,7 +243,7 @@ module.exports = class MetamaskController {
agreeToEthWarning (cb) {
try {
this.configManager.setShouldntShowWarning(true)
this.configManager.setShouldntShowWarning()
cb()
} catch (e) {
cb(e)

View File

@ -9,6 +9,7 @@ const injectCss = require('inject-css')
const PortStream = require('./lib/port-stream.js')
const StreamProvider = require('web3-stream-provider')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const extension = require('./lib/extension')
// setup app
var css = MetaMaskUiCss()
@ -21,7 +22,7 @@ async.parallel({
function connectToAccountManager (cb) {
// setup communication with background
var pluginPort = chrome.runtime.connect({name: 'popup'})
var pluginPort = extension.runtime.connect({name: 'popup'})
var portStream = new PortStream(pluginPort)
// setup multiplexing
var mx = setupMultiplex(portStream)
@ -55,8 +56,8 @@ function setupControllerConnection (stream, cb) {
function getCurrentDomain (cb) {
const unknown = '<unknown>'
if (!chrome.tabs) return cb(null, unknown)
chrome.tabs.query({active: true, currentWindow: true}, function (results) {
if (!extension.tabs) return cb(null, unknown)
extension.tabs.query({active: true, currentWindow: true}, function (results) {
var activeTab = results[0]
var currentUrl = activeTab && activeTab.url
var currentDomain = url.parse(currentUrl).host
@ -68,9 +69,9 @@ function getCurrentDomain (cb) {
}
function clearNotifications(){
chrome.notifications.getAll(function (object) {
extension.notifications.getAll(function (object) {
for (let notification in object){
chrome.notifications.clear(notification)
extension.notifications.clear(notification)
}
})
}

View File

@ -1,3 +1,6 @@
machine:
node:
version: 6.0.0
dependencies:
pre:
- "npm i -g testem"

View File

@ -0,0 +1,39 @@
/* MockExtension
*
* A module for importing the global extension polyfiller
* and stubbing out all the extension methods with appropriate mocks.
*/
const extension = require('../app/scripts/lib/extension')
const noop = function () {}
const apis = [
'alarms',
'bookmarks',
'browserAction',
'commands',
'contextMenus',
'cookies',
'downloads',
'events',
'extension',
'extensionTypes',
'history',
'i18n',
'idle',
'notifications',
'pageAction',
'runtime',
'storage',
'tabs',
'webNavigation',
'webRequest',
'windows',
]
apis.forEach(function (api) {
extension[api] = {}
})
extension.runtime.reload = noop
extension.tabs.create = noop

File diff suppressed because one or more lines are too long

View File

@ -1,14 +1,17 @@
{
"metamask": {
"accounts": {},
"transactions": [],
"identities": {},
"network": "2",
"isInitialized": false,
"isUnlocked": false,
"currentDomain": "example.com",
"rpcTarget": "https://rawtestrpc.metamask.io/",
"identities": {},
"unconfTxs": {},
"accounts": {},
"transactions": [],
"network": "2",
"seedWords": null,
"isConfirmed": false,
"unconfTxs": {},
"isEthConfirmed": false,
"unconfMsgs": {},
"messages": [],
"provider": {
@ -16,6 +19,17 @@
}
},
"appState": {
"currentDomain": "extensions"
}
"menuOpen": false,
"currentView": {
"name": "EthStoreWarning"
},
"accountDetail": {
"subview": "transactions"
},
"currentDomain": "127.0.0.1:9966",
"transForward": true,
"isLoading": false,
"warning": null
},
"identities": {}
}

15
docs/state_dump.md Normal file
View File

@ -0,0 +1,15 @@
# How to take a State Dump
Sometimes a UI bug is hard to reproduce, but we'd like to rapidly develop against the application state that caused the bug.
In this case, a MetaMask developer will sometimes ask a user with a bug to perform a "state dump", so we can use some internal tools to reproduce and fix the bug.
To take a state dump, follow these steps:
1. Get the MetaMask popup to the point where it shows the bug (the developer will probably specify exactly where).
2. Right click on the extension popup UI, and in the menu, click "Inspect". This will open the developer tools.
3. In case it isn't already selected, click the "Console" tab in the new Developer Tools window.
4. In the console, type this command exactly: `logState()`. This should print a bunch of JSON text into your console.
5. Copy that printed JSON text
6. *Optional*: Annonymize that text if you'd like (you may change all instances of an account address to another valid account address, for example) We may automate the anonymization in the future.
7. Send that JSON text to the developer, ideally pasting it in the issue regarding the bug.

View File

@ -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())
)

157
mock-dev.js Normal file
View File

@ -0,0 +1,157 @@
/* MOCK DEV
*
* This is a utility module.
* It initializes a minimalist browserifiable project
* that contains the Metamask UI, with a local background process.
*
* Includes a state reset button for restoring to initial state.
*
* This is a convenient way to develop and test the plugin
* without having to re-open the plugin or even re-build it.
*
* To use, run `npm run mock`.
*/
const extend = require('xtend')
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('./ui/app/root')
const configureStore = require('./ui/app/store')
const actions = require('./ui/app/actions')
const states = require('./development/states')
const MetamaskController = require('./app/scripts/metamask-controller')
const extension = require('./development/mockExtension')
// Query String
const qs = require('qs')
let queryString = qs.parse(window.location.href.split('#')[1])
let selectedView = queryString.view || 'terms'
const firstState = states[selectedView]
updateQueryParams(selectedView)
// CSS
const MetaMaskUiCss = require('./ui/css')
const injectCss = require('inject-css')
function updateQueryParams(newView) {
queryString.view = newView
const params = qs.stringify(queryString)
window.location.href = window.location.href.split('#')[0] + `#${params}`
}
const noop = function () {}
const controller = new MetamaskController({
// User confirmation callbacks:
showUnconfirmedMessage: noop,
unlockAccountMessage: noop,
showUnconfirmedTx: noop,
// Persistence Methods:
setData,
loadData,
})
// Stub out localStorage for non-browser environments
if (!window.localStorage) {
window.localStorage = {}
}
const STORAGE_KEY = 'metamask-config'
function loadData () {
var oldData = getOldStyleData()
var newData
try {
newData = JSON.parse(window.localStorage[STORAGE_KEY])
} catch (e) {}
var data = extend({
meta: {
version: 0,
},
data: {
config: {
provider: {
type: 'testnet',
},
},
},
}, oldData || null, newData || null)
return data
}
function setData (data) {
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
}
function getOldStyleData () {
var config, wallet, seedWords
var result = {
meta: { version: 0 },
data: {},
}
try {
config = JSON.parse(window.localStorage['config'])
result.data.config = config
} catch (e) {}
try {
wallet = JSON.parse(window.localStorage['lightwallet'])
result.data.wallet = wallet
} catch (e) {}
try {
seedWords = window.localStorage['seedWords']
result.data.seedWords = seedWords
} catch (e) {}
return result
}
actions._setAccountManager(controller.getApi())
actions.update = function(stateName) {
selectedView = stateName
updateQueryParams(stateName)
const newState = states[selectedView]
return {
type: 'GLOBAL_FORCE_UPDATE',
value: newState,
}
}
var css = MetaMaskUiCss()
injectCss(css)
const container = document.querySelector('#app-content')
// parse opts
var store = configureStore(firstState)
// start app
render(
h('.super-dev-container', [
h('button', {
onClick: (ev) => {
ev.preventDefault()
store.dispatch(actions.update('terms'))
},
style: {
margin: '19px 19px 0px 19px',
},
}, 'Reset State'),
h('.mock-app-root', {
style: {
height: '500px',
width: '360px',
boxShadow: 'grey 0px 2px 9px',
margin: '20px',
},
}, [
h(Root, {
store: store,
}),
]),
]
), container)

View File

@ -5,9 +5,14 @@
"private": true,
"scripts": {
"start": "gulp dev",
"test": "mocha --require test/helper.js --compilers js:babel-register --recursive",
"watch": "mocha watch --compilers js:babel-register --recursive",
"ui": "node development/genStates.js && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./"
"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 ./",
"buildMock": "browserify ./mock-dev.js -o ./development/bundle.js",
"testem": "npm run buildMock && testem",
"ci": "npm run buildMock && testem ci -P 2"
},
"browserify": {
"transform": [
@ -82,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",
@ -95,8 +103,10 @@
"mocha-jsdom": "^1.1.0",
"mocha-sinon": "^1.1.5",
"qs": "^6.2.0",
"qunit": "^0.9.1",
"sinon": "^1.17.3",
"tape": "^4.5.1",
"testem": "^1.10.3",
"uglifyify": "^3.0.1",
"vinyl-buffer": "^1.0.0",
"vinyl-source-stream": "^1.1.0",

View File

@ -0,0 +1,7 @@
function wait() {
return new Promise(function(resolve, reject) {
setTimeout(function() {
resolve()
}, 500)
})
}

View File

@ -0,0 +1,22 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>QUnit Example</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.0.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script>
<script src="./jquery-3.1.0.min.js"></script>
<script src="helpers.js"></script>
<script src="tests.js"></script>
<script src="/testem.js"></script>
<iframe src="/development/index.html" height="500px" width="360px">
<p>Your browser does not support iframes</p>
</iframe>
</body>
</html>

4
test/integration/jquery-3.1.0.min.js vendored Normal file

File diff suppressed because one or more lines are too long

21
test/integration/tests.js Normal file
View File

@ -0,0 +1,21 @@
QUnit.test('agree to terms', function (assert) {
var done = assert.async()
// Select the mock app root
var app = $('iframe').contents().find('#app-content .mock-app-root')
// Agree to terms
app.find('button').click()
// Wait for view to transition:
wait().then(function() {
var title = app.find('h1').text()
assert.equal(title, 'MetaMask', 'title screen')
var buttons = app.find('button')
assert.equal(buttons.length, 2, 'two buttons: create and restore')
done()
})
})

View File

@ -0,0 +1,48 @@
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'))
describe('extension', function() {
describe('with chrome global', function() {
let extension
beforeEach(function() {
GLOBAL.chrome = {
alarms: 'foo'
}
extension = new Extension()
})
it('should use the chrome global apis', function() {
assert.equal(extension.alarms, 'foo')
})
})
describe('without chrome global', function() {
let extension
let realWindow
beforeEach(function() {
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')
})
})
})

9
testem.yml Normal file
View File

@ -0,0 +1,9 @@
launch_in_dev:
- Chrome
- Firefox
launch_in_ci:
- Chrome
- Firefox
framework:
- qunit
test_page: "test/integration/index.html"

View File

@ -18,7 +18,7 @@
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('./ui/app/root')
const configureStore = require('./development/mockStore')
const configureStore = require('./development/uiStore')
const states = require('./development/states')
const Selector = require('./development/selector')

View File

@ -172,19 +172,27 @@ AccountDetailScreen.prototype.render = function () {
}),
h('button', {
<<<<<<< HEAD
onClick: this.buyButtonDeligator.bind(this),
=======
onClick: () => props.dispatch(actions.buyEth(selected)),
>>>>>>> master
style: {
marginBottom: '20px',
marginRight: '8px',
position: 'absolute',
left: '219px',
},
<<<<<<< HEAD
}, props.accountDetail.subview === 'buyForm' ? [h('i.fa.fa-arrow-left', {
style: {
width: '22.641px',
height: '14px',
},
})] : 'BUY'),
=======
}, 'BUY'),
>>>>>>> master
h('button', {
onClick: () => props.dispatch(actions.showSendPage()),

View File

@ -37,7 +37,6 @@ AccountsScreen.prototype.render = function () {
var actions = {
onSelect: this.onSelect.bind(this),
onShowDetail: this.onShowDetail.bind(this),
revealAccount: this.onRevealAccount.bind(this),
goHome: this.goHome.bind(this),
}
return (
@ -88,7 +87,7 @@ AccountsScreen.prototype.render = function () {
h('div.footer.hover-white.pointer', {
key: 'reveal-account-bar',
onClick: () => {
actions.revealAccount()
this.onRevealAccount()
},
style: {
display: 'flex',

View File

@ -131,7 +131,6 @@ var actions = {
showSubLoadingIndication: showSubLoadingIndication,
HIDE_SUB_LOADING_INDICATION: 'HIDE_SUB_LOADING_INDICATION',
hideSubLoadingIndication: hideSubLoadingIndication,
}
module.exports = actions
@ -625,6 +624,7 @@ function buyEth (address, amount) {
})
}
}
<<<<<<< HEAD
function buyEthSubview () {
return {

View File

@ -267,6 +267,7 @@ App.prototype.renderDropdown = function () {
style: {
position: 'absolute',
right: 0,
top: '36px',
},
innerStyle: {
background: 'white',

View File

@ -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', '✓')
}
}

View File

@ -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'),

View File

@ -28,7 +28,8 @@ PTXP.render = function () {
var txParams = txData.txParams || {}
var address = txParams.from || props.selectedAddress
var identity = props.identities[address] || { address: address }
var balance = props.accounts[address].balance
var account = props.accounts[address]
var balance = account ? account.balance : '0x0'
var gasCost = new BN(ethUtil.stripHexPrefix(txParams.gas || txData.estimatedGas), 16)
var gasPrice = new BN(ethUtil.stripHexPrefix(txParams.gasPrice || '0x4a817c800'), 16)

View File

@ -7,6 +7,7 @@ const addressSummary = require('../util').addressSummary
const explorerLink = require('../../lib/explorer-link')
const CopyButton = require('./copyButton')
const vreme = new (require('vreme'))
const extension = require('../../../app/scripts/lib/extension')
const TransactionIcon = require('./transaction-list-item-icon')
@ -49,7 +50,7 @@ TransactionListItem.prototype.render = function () {
if (!transaction.hash || !isLinkable) return
var url = explorerLink(transaction.hash, parseInt(network))
chrome.tabs.create({ url })
extension.tabs.create({ url })
},
style: {
padding: '20px 0',

View File

@ -178,6 +178,11 @@ hr.horizontal-line {
background: #038789;
}
.hollow-diamond {
transform: rotate(45deg);
border: 1px solid #038789;
}
.pending-dot {
background: red;
left: 14px;

View File

@ -0,0 +1,89 @@
const connect = require('react-redux').connect
const Component = require('react').Component
const h = require('react-hyperscript')
const inherits = require('util').inherits
const actions = require('./actions')
module.exports = connect(mapStateToProps)(EthStoreWarning)
inherits(EthStoreWarning, Component)
function EthStoreWarning () {
Component.call(this)
}
function mapStateToProps (state) {
return {
selectedAccount: state.metamask.selectedAccount,
}
}
EthStoreWarning.prototype.render = function () {
return (
h('.flex-column', {
key: 'ethWarning',
style: {
paddingTop: '25px',
marginRight: '30px',
marginLeft: '30px',
alignItems: 'center',
},
}, [
h('.warning', {
style: {
margin: '10px 10px 10px 10px',
},
},
`MetaMask is currently in beta -
exercise caution while handling
and storing your ether.
`),
h('i.fa.fa-exclamation-triangle.fa-4', {
style: {
fontSize: '152px',
color: '#AEAEAE',
textAlign: 'center',
},
}),
h('.flex-row', {
style: {
marginTop: '25px',
marginBottom: '10px',
},
}, [
h('input', {
type: 'checkbox',
onChange: this.toggleShowWarning.bind(this),
}),
h('.warning', {
style: {
fontSize: '11px',
},
}, 'Don\'t show me this message again'),
]),
h('.flex-row', {
style: {
width: '100%',
justifyContent: 'space-around',
},
}, [
h('button', {
onClick: this.toAccounts.bind(this),
},
'Continue to MetaMask'),
]),
])
)
}
EthStoreWarning.prototype.toggleShowWarning = function () {
this.props.dispatch(actions.agreeToEthWarning())
}
EthStoreWarning.prototype.toAccounts = function () {
this.props.dispatch(actions.showAccountDetail(this.props.account))
}

View File

@ -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
}

View File

@ -3,6 +3,7 @@ const Component = require('react').Component
const h = require('react-hyperscript')
const connect = require('react-redux').connect
const actions = require('./actions')
const extension = require('../../app/scripts/lib/extension')
module.exports = connect(mapStateToProps)(InfoScreen)
@ -19,7 +20,7 @@ InfoScreen.prototype.render = function () {
var state = this.props
var manifest
try {
manifest = chrome.runtime.getManifest()
manifest = extension.runtime.getManifest()
} catch (e) {
manifest = { version: '2.0.0' }
}
@ -105,7 +106,7 @@ InfoScreen.prototype.render = function () {
h('a.info', {
target: '_blank',
style: { width: '85vw' },
onClick () { chrome.tabs.create({url: 'mailto:help@metamask.io?subject=Feedback'}) },
onClick () { extension.tabs.create({url: 'mailto:help@metamask.io?subject=Feedback'}) },
}, 'Email us any questions or comments!'),
]),
@ -124,5 +125,5 @@ InfoScreen.prototype.render = function () {
}
InfoScreen.prototype.navigateTo = function (url) {
chrome.tabs.create({ url })
extension.tabs.create({ url })
}

View File

@ -28,10 +28,13 @@ function reduceApp (state, action) {
name: 'createVaultComplete',
seedWords,
}
var ethStoreWarning = {
name: 'EthStoreWarning',
}
var appState = extend({
menuOpen: false,
currentView: seedWords ? seedConfView : defaultView,
currentView: seedWords ? seedConfView : !state.metamask.isEthConfirmed ? ethStoreWarning : defaultView,
accountDetail: {
subview: 'transactions',
},
@ -366,6 +369,17 @@ function reduceApp (state, action) {
},
})
case actions.SHOW_ETH_WARNING:
return extend(appState, {
transForward: true,
currentView: {
name: 'accountDetail',
context: appState.currentView.context,
},
accountDetail: {
subview: 'buy-eth-warning',
},
})
default:
return appState
}
@ -390,3 +404,5 @@ function indexForPending (state, txId) {
})
return idx
}

View File

@ -10,6 +10,7 @@ function reduceMetamask (state, action) {
var metamaskState = extend({
isInitialized: false,
isUnlocked: false,
isEthConfirmed: false,
currentDomain: 'example.com',
rpcTarget: 'https://rawtestrpc.metamask.io/',
identities: {},
@ -31,6 +32,11 @@ function reduceMetamask (state, action) {
isConfirmed: true,
})
case actions.AGREE_TO_ETH_WARNING:
return extend(metamaskState, {
isEthConfirmed: !metamaskState.isEthConfirmed,
})
case actions.UNLOCK_METAMASK:
return extend(metamaskState, {
isUnlocked: true,

View File

@ -173,7 +173,7 @@ SendTransactionScreen.prototype.render = function () {
marginBottom: 16,
},
}, [
'Tranasactional Data (optional)',
'Transactional Data (optional)',
]),
// 'data' field

View File

@ -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),

View File

@ -123,7 +123,12 @@ function generateBalanceObject (formattedBalance, decimalsToKeep = 1) {
var shortBalance = shortenBalance(balance, decimalsToKeep)
if (beforeDecimal === '0' && afterDecimal.substr(0, 5) === '00000') {
balance = '<1.0e-5'
// eslint-disable-next-line eqeqeq
if (afterDecimal == 0) {
balance = '0'
} else {
balance = '<1.0e-5'
}
} else if (beforeDecimal !== '0') {
balance = `${beforeDecimal}.${afterDecimal.slice(0, decimalsToKeep)}`
}