diff options
Diffstat (limited to 'app/scripts')
-rw-r--r-- | app/scripts/background.js | 50 | ||||
-rw-r--r-- | app/scripts/chromereload.js | 6 | ||||
-rw-r--r-- | app/scripts/config.js | 4 | ||||
-rw-r--r-- | app/scripts/contentscript.js | 8 | ||||
-rw-r--r-- | app/scripts/lib/extension-instance.js | 19 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 7 | ||||
-rw-r--r-- | app/scripts/lib/inpage-provider.js | 27 | ||||
-rw-r--r-- | app/scripts/lib/is-popup-or-notification.js | 8 | ||||
-rw-r--r-- | app/scripts/lib/notifications.js | 168 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 32 | ||||
-rw-r--r-- | app/scripts/popup.js | 24 |
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() + } +} |