aboutsummaryrefslogtreecommitdiffstats
path: root/app/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'app/scripts')
-rw-r--r--app/scripts/background.js50
-rw-r--r--app/scripts/chromereload.js6
-rw-r--r--app/scripts/config.js4
-rw-r--r--app/scripts/contentscript.js8
-rw-r--r--app/scripts/lib/extension-instance.js19
-rw-r--r--app/scripts/lib/idStore.js7
-rw-r--r--app/scripts/lib/inpage-provider.js27
-rw-r--r--app/scripts/lib/is-popup-or-notification.js8
-rw-r--r--app/scripts/lib/notifications.js168
-rw-r--r--app/scripts/metamask-controller.js32
-rw-r--r--app/scripts/popup.js24
11 files changed, 152 insertions, 201 deletions
diff --git a/app/scripts/background.js b/app/scripts/background.js
index e04309e74..58228a41a 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -3,9 +3,7 @@ const extend = require('xtend')
const Dnode = require('dnode')
const eos = require('end-of-stream')
const PortStream = require('./lib/port-stream.js')
-const createUnlockRequestNotification = require('./lib/notifications.js').createUnlockRequestNotification
-const createTxNotification = require('./lib/notifications.js').createTxNotification
-const createMsgNotification = require('./lib/notifications.js').createMsgNotification
+const notification = require('./lib/notifications.js')
const messageManager = require('./lib/message-manager')
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
const MetamaskController = require('./metamask-controller')
@@ -26,50 +24,32 @@ const controller = new MetamaskController({
const idStore = controller.idStore
function unlockAccountMessage () {
- createUnlockRequestNotification({
- title: 'Account Unlock Request',
- })
+ notification.show()
}
function showUnconfirmedMessage (msgParams, msgId) {
- var controllerState = controller.getState()
-
- createMsgNotification({
- imageifyIdenticons: false,
- txData: {
- msgParams: msgParams,
- time: (new Date()).getTime(),
- },
- identities: controllerState.identities,
- accounts: controllerState.accounts,
- onConfirm: idStore.approveMessage.bind(idStore, msgId, noop),
- onCancel: idStore.cancelMessage.bind(idStore, msgId),
- })
+ notification.show()
}
function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
- var controllerState = controller.getState()
-
- createTxNotification({
- imageifyIdenticons: false,
- txData: {
- txParams: txParams,
- time: (new Date()).getTime(),
- },
- identities: controllerState.identities,
- accounts: controllerState.accounts,
- onConfirm: idStore.approveTransaction.bind(idStore, txData.id, noop),
- onCancel: idStore.cancelTransaction.bind(idStore, txData.id),
- })
+ notification.show()
}
+// On first install, open a window to MetaMask website to how-it-works.
+
+extension.runtime.onInstalled.addListener(function (details) {
+ if (details.reason === 'install') {
+ extension.tabs.create({url: 'https://metamask.io/#how-it-works'})
+ }
+})
+
//
// connect to other contexts
//
extension.runtime.onConnect.addListener(connectRemote)
function connectRemote (remotePort) {
- var isMetaMaskInternalProcess = (remotePort.name === 'popup')
+ var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
var portStream = new PortStream(remotePort)
if (isMetaMaskInternalProcess) {
// communication with popup
@@ -109,7 +89,7 @@ function setupControllerConnection (stream) {
dnode.on('remote', (remote) => {
// push updates to popup
controller.ethStore.on('update', controller.sendUpdate.bind(controller))
- controller.remote = remote
+ controller.listeners.push(remote)
idStore.on('update', controller.sendUpdate.bind(controller))
// teardown on disconnect
@@ -188,5 +168,3 @@ function getOldStyleData () {
function setData (data) {
window.localStorage[STORAGE_KEY] = JSON.stringify(data)
}
-
-function noop () {}
diff --git a/app/scripts/chromereload.js b/app/scripts/chromereload.js
index 88333ba8a..f0bae403c 100644
--- a/app/scripts/chromereload.js
+++ b/app/scripts/chromereload.js
@@ -324,13 +324,13 @@ window.LiveReloadOptions = { host: 'localhost' };
this.pluginIdentifiers = {}
this.console = this.window.console && this.window.console.log && this.window.console.error ? this.window.location.href.match(/LR-verbose/) ? this.window.console : {
log: function () {},
- error: this.window.console.error.bind(this.window.console),
+ error: console.error,
} : {
log: function () {},
error: function () {},
}
if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) {
- this.console.error('LiveReload disabled because the browser does not seem to support web sockets')
+ console.error('LiveReload disabled because the browser does not seem to support web sockets')
return
}
if ('LiveReloadOptions' in window) {
@@ -344,7 +344,7 @@ window.LiveReloadOptions = { host: 'localhost' };
} else {
this.options = Options.extract(this.window.document)
if (!this.options) {
- this.console.error('LiveReload disabled because it could not find its own <SCRIPT> tag')
+ console.error('LiveReload disabled because it could not find its own <SCRIPT> tag')
return
}
}
diff --git a/app/scripts/config.js b/app/scripts/config.js
index 04e2907d4..b7e72eb64 100644
--- a/app/scripts/config.js
+++ b/app/scripts/config.js
@@ -1,5 +1,5 @@
-const MAINET_RPC_URL = 'https://mainnet.infura.io/'
-const TESTNET_RPC_URL = 'https://morden.infura.io/'
+const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
+const TESTNET_RPC_URL = 'https://morden.infura.io/metamask'
const DEFAULT_RPC_URL = TESTNET_RPC_URL
global.METAMASK_DEBUG = false
diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js
index de2cf263b..b3a560c88 100644
--- a/app/scripts/contentscript.js
+++ b/app/scripts/contentscript.js
@@ -43,20 +43,20 @@ function setupStreams(){
name: 'contentscript',
target: 'inpage',
})
- pageStream.on('error', console.error.bind(console))
+ pageStream.on('error', console.error)
var pluginPort = extension.runtime.connect({name: 'contentscript'})
var pluginStream = new PortStream(pluginPort)
- pluginStream.on('error', console.error.bind(console))
+ pluginStream.on('error', console.error)
// forward communication plugin->inpage
pageStream.pipe(pluginStream).pipe(pageStream)
// connect contentscript->inpage reload stream
var mx = ObjectMultiplex()
- mx.on('error', console.error.bind(console))
+ mx.on('error', console.error)
mx.pipe(pageStream)
var reloadStream = mx.createStream('reload')
- reloadStream.on('error', console.error.bind(console))
+ reloadStream.on('error', console.error)
// if we lose connection with the plugin, trigger tab refresh
pluginStream.on('close', function () {
diff --git a/app/scripts/lib/extension-instance.js b/app/scripts/lib/extension-instance.js
index eb3b8a1e9..628b62e3f 100644
--- a/app/scripts/lib/extension-instance.js
+++ b/app/scripts/lib/extension-instance.js
@@ -42,10 +42,27 @@ function Extension () {
} catch (e) {}
try {
+ if (browser[api]) {
+ _this[api] = browser[api]
+ }
+ } catch (e) {}
+ try {
_this.api = browser.extension[api]
} catch (e) {}
-
})
+
+ try {
+ if (browser && browser.runtime) {
+ this.runtime = browser.runtime
+ }
+ } catch (e) {}
+
+ try {
+ if (browser && browser.browserAction) {
+ this.browserAction = browser.browserAction
+ }
+ } catch (e) {}
+
}
module.exports = Extension
diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js
index 7ac71e409..26aa02ef7 100644
--- a/app/scripts/lib/idStore.js
+++ b/app/scripts/lib/idStore.js
@@ -45,7 +45,11 @@ function IdentityStore (opts = {}) {
IdentityStore.prototype.createNewVault = function (password, entropy, cb) {
delete this._keyStore
+ var serializedKeystore = this.configManager.getWallet()
+ if (serializedKeystore) {
+ this.configManager.setData({})
+ }
this._createIdmgmt(password, null, entropy, (err) => {
if (err) return cb(err)
@@ -437,6 +441,7 @@ IdentityStore.prototype.tryPassword = function (password, cb) {
IdentityStore.prototype._createIdmgmt = function (password, seed, entropy, cb) {
const configManager = this.configManager
+
var keyStore = null
LightwalletKeyStore.deriveKeyFromPassword(password, (err, derivedKey) => {
if (err) return cb(err)
@@ -478,7 +483,7 @@ IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey)
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'})
keyStore.setDefaultHdDerivationPath(this.hdPathString)
- keyStore.generateNewAddress(derivedKey, 3)
+ keyStore.generateNewAddress(derivedKey, 1)
configManager.setWallet(keyStore.serialize())
if (global.METAMASK_DEBUG) {
console.log('restored from seed. saved to keystore')
diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js
index 65354cd3d..4f9fa1a7d 100644
--- a/app/scripts/lib/inpage-provider.js
+++ b/app/scripts/lib/inpage-provider.js
@@ -33,15 +33,29 @@ function MetamaskInpageProvider (connectionStream) {
})
asyncProvider.on('error', console.error.bind(console))
self.asyncProvider = asyncProvider
+
+ self.idMap = {}
// handle sendAsync requests via asyncProvider
self.sendAsync = function(payload, cb){
// rewrite request ids
- var request = jsonrpcMessageTransform(payload, (message) => {
- message.id = createRandomId()
+ var request = eachJsonMessage(payload, (message) => {
+ var newId = createRandomId()
+ self.idMap[newId] = message.id
+ message.id = newId
return message
})
// forward to asyncProvider
- asyncProvider.sendAsync(request, cb)
+ asyncProvider.sendAsync(request, function(err, res){
+ if (err) return cb(err)
+ // transform messages to original ids
+ eachJsonMessage(res, (message) => {
+ var oldId = self.idMap[message.id]
+ delete self.idMap[message.id]
+ message.id = oldId
+ return message
+ })
+ cb(null, res)
+ })
}
}
@@ -66,7 +80,8 @@ MetamaskInpageProvider.prototype.send = function (payload) {
// throw not-supported Error
default:
- var message = 'The MetaMask Web3 object does not support synchronous methods. See https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#all-async---think-of-metamask-as-a-light-client for details.'
+ var message = 'The MetaMask Web3 object does not support synchronous methods like ' + payload.method +
+ '. See https://github.com/MetaMask/faq/blob/master/DEVELOPERS.md#all-async---think-of-metamask-as-a-light-client for details.'
throw new Error(message)
}
@@ -111,10 +126,10 @@ function createRandomId(){
return datePart + extraPart
}
-function jsonrpcMessageTransform(payload, transformFn){
+function eachJsonMessage(payload, transformFn){
if (Array.isArray(payload)) {
return payload.map(transformFn)
} else {
return transformFn(payload)
}
-} \ No newline at end of file
+}
diff --git a/app/scripts/lib/is-popup-or-notification.js b/app/scripts/lib/is-popup-or-notification.js
new file mode 100644
index 000000000..5c38ac823
--- /dev/null
+++ b/app/scripts/lib/is-popup-or-notification.js
@@ -0,0 +1,8 @@
+module.exports = function isPopupOrNotification() {
+ const url = window.location.href
+ if (url.match(/popup.html$/)) {
+ return 'popup'
+ } else {
+ return 'notification'
+ }
+}
diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js
index 6c1601df1..4e3f7558c 100644
--- a/app/scripts/lib/notifications.js
+++ b/app/scripts/lib/notifications.js
@@ -1,159 +1,63 @@
-const createId = require('hat')
-const extend = require('xtend')
-const unmountComponentAtNode = require('react-dom').unmountComponentAtNode
-const findDOMNode = require('react-dom').findDOMNode
-const render = require('react-dom').render
-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 = {
- createUnlockRequestNotification: createUnlockRequestNotification,
- createTxNotification: createTxNotification,
- createMsgNotification: createMsgNotification,
+ show,
+ getPopup,
+ closePopup,
}
module.exports = notifications
window.METAMASK_NOTIFIER = notifications
-setupListeners()
+function show () {
+ getPopup((err, popup) => {
+ if (err) throw err
-function setupListeners () {
- // guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
- if (!extension.notifications) return console.error('Chrome notifications API missing...')
+ if (popup) {
- // notification button press
- extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
- var handlers = notificationHandlers[notificationId]
- if (buttonIndex === 0) {
- handlers.confirm()
- } else {
- handlers.cancel()
- }
- extension.notifications.clear(notificationId)
- })
+ // bring focus to existing popup
+ extension.windows.update(popup.id, { focused: true })
- // notification teardown
- extension.notifications.onClosed.addListener(function (notificationId) {
- delete notificationHandlers[notificationId]
- })
-}
+ } else {
-// creation helper
-function createUnlockRequestNotification (opts) {
- // 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.'
+ // create new popup
+ extension.windows.create({
+ url: 'notification.html',
+ type: 'popup',
+ focused: true,
+ width: 360,
+ height: 500,
+ })
- var id = createId()
- extension.notifications.create(id, {
- type: 'basic',
- iconUrl: '/images/icon-128.png',
- title: opts.title,
- message: message,
+ }
})
}
-function createTxNotification (state) {
- // 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
+function getWindows(cb) {
+ // Ignore in test environment
+ if (!extension.windows) {
+ return cb()
+ }
- showNotification(extend(state, {
- title: 'New Unsigned Transaction',
- imageUrl: toSvgUri(notificationSvgSource),
- }))
+ extension.windows.getAll({}, (windows) => {
+ cb(null, windows)
})
}
-function createMsgNotification (state) {
- // 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) {
+function getPopup(cb) {
+ getWindows((err, windows) => {
if (err) throw err
-
- showNotification(extend(state, {
- title: 'New Unsigned Message',
- imageUrl: toSvgUri(notificationSvgSource),
- }))
+ cb(null, getPopupIn(windows))
})
}
-function showNotification (state) {
- // 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()
- extension.notifications.create(id, {
- type: 'image',
- requireInteraction: true,
- iconUrl: '/images/icon-128.png',
- imageUrl: state.imageUrl,
- title: state.title,
- message: '',
- buttons: [{
- title: 'Approve',
- }, {
- title: 'Reject',
- }],
- })
- notificationHandlers[id] = {
- confirm: state.onConfirm,
- cancel: state.onCancel,
- }
-}
-
-function renderTxNotificationSVG (state, cb) {
- var content = h(PendingTxDetails, state)
- renderNotificationSVG(content, cb)
-}
-
-function renderMsgNotificationSVG (state, cb) {
- var content = h(PendingMsgDetails, state)
- renderNotificationSVG(content, cb)
+function getPopupIn(windows) {
+ return windows ? windows.find((win) => win.type === 'popup') : null
}
-function renderNotificationSVG (content, cb) {
- var container = document.createElement('div')
- var confirmView = h('div.app-primary', {
- style: {
- width: '360px',
- height: '240px',
- padding: '16px',
- // background: '#F7F7F7',
- background: 'white',
- },
- }, [
- h('style', MetaMaskUiCss()),
- content,
- ])
-
- render(confirmView, container, function ready() {
- var rootElement = findDOMNode(this)
- var viewSource = rootElement.outerHTML
- unmountComponentAtNode(container)
- var svgSource = svgWrapper(viewSource)
- // insert content into svg wrapper
- cb(null, svgSource)
+function closePopup() {
+ getPopup((err, popup) => {
+ if (err) throw err
+ if (!popup) return
+ extension.windows.remove(popup.id, console.error)
})
}
-
-function svgWrapper (content) {
- var wrapperSource = `
- <svg xmlns="http://www.w3.org/2000/svg" width="360" height="240">
- <foreignObject x="0" y="0" width="100%" height="100%">
- <body xmlns="http://www.w3.org/1999/xhtml" height="100%">{{content}}</body>
- </foreignObject>
- </svg>
- `
- return wrapperSource.split('{{content}}').join(content)
-}
-
-function toSvgUri (content) {
- return 'data:image/svg+xml;utf8,' + encodeURIComponent(content)
-}
diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js
index d53094e43..5373cf0d9 100644
--- a/app/scripts/metamask-controller.js
+++ b/app/scripts/metamask-controller.js
@@ -12,6 +12,7 @@ module.exports = class MetamaskController {
constructor (opts) {
this.opts = opts
+ this.listeners = []
this.configManager = new ConfigManager(opts)
this.idStore = new IdentityStore({
configManager: this.configManager,
@@ -112,9 +113,9 @@ module.exports = class MetamaskController {
}
sendUpdate () {
- if (this.remote) {
- this.remote.sendUpdate(this.getState())
- }
+ this.listeners.forEach((remote) => {
+ remote.sendUpdate(this.getState())
+ })
}
initializeProvider (opts) {
@@ -130,10 +131,17 @@ module.exports = class MetamaskController {
},
// tx signing
approveTransaction: this.newUnsignedTransaction.bind(this),
- signTransaction: idStore.signTransaction.bind(idStore),
+ signTransaction: (...args) => {
+ idStore.signTransaction(...args)
+ this.sendUpdate()
+ },
+
// msg signing
approveMessage: this.newUnsignedMessage.bind(this),
- signMessage: idStore.signMessage.bind(idStore),
+ signMessage: (...args) => {
+ idStore.signMessage(...args)
+ this.sendUpdate()
+ },
}
var provider = MetaMaskProvider(providerOpts)
@@ -191,8 +199,13 @@ module.exports = class MetamaskController {
const idStore = this.idStore
var state = idStore.getState()
+ let err = this.enforceTxValidations(txParams)
+ if (err) return onTxDoneCb(err)
+
// It's locked
if (!state.isUnlocked) {
+
+ // Allow the environment to define an unlock message.
this.opts.unlockAccountMessage()
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, noop)
@@ -200,11 +213,19 @@ module.exports = class MetamaskController {
} else {
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
if (err) return onTxDoneCb(err)
+ this.sendUpdate()
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
})
}
}
+ enforceTxValidations (txParams) {
+ if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
+ const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
+ return new Error(msg)
+ }
+ }
+
newUnsignedMessage (msgParams, cb) {
var state = this.idStore.getState()
if (!state.isUnlocked) {
@@ -212,6 +233,7 @@ module.exports = class MetamaskController {
this.opts.unlockAccountMessage()
} else {
this.addUnconfirmedMessage(msgParams, cb)
+ this.sendUpdate()
}
}
diff --git a/app/scripts/popup.js b/app/scripts/popup.js
index 20be15df7..096b56115 100644
--- a/app/scripts/popup.js
+++ b/app/scripts/popup.js
@@ -9,7 +9,9 @@ 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 isPopupOrNotification = require('./lib/is-popup-or-notification')
const extension = require('./lib/extension')
+const notification = require('./lib/notifications')
// setup app
var css = MetaMaskUiCss()
@@ -22,7 +24,11 @@ async.parallel({
function connectToAccountManager (cb) {
// setup communication with background
- var pluginPort = extension.runtime.connect({name: 'popup'})
+
+ var name = isPopupOrNotification()
+ closePopupIfOpen(name)
+ window.METAMASK_UI_TYPE = name
+ var pluginPort = extension.runtime.connect({ name })
var portStream = new PortStream(pluginPort)
// setup multiplexing
var mx = setupMultiplex(portStream)
@@ -68,22 +74,12 @@ function getCurrentDomain (cb) {
})
}
-function clearNotifications(){
- extension.notifications.getAll(function (object) {
- for (let notification in object){
- extension.notifications.clear(notification)
- }
- })
-}
-
function setupApp (err, opts) {
if (err) {
alert(err.stack)
throw err
}
- clearNotifications()
-
var container = document.getElementById('app-content')
MetaMaskUi({
@@ -93,3 +89,9 @@ function setupApp (err, opts) {
networkVersion: opts.networkVersion,
})
}
+
+function closePopupIfOpen(name) {
+ if (name !== 'notification') {
+ notification.closePopup()
+ }
+}