diff options
Diffstat (limited to 'app/scripts/lib')
-rw-r--r-- | app/scripts/lib/auto-reload.js | 2 | ||||
-rw-r--r-- | app/scripts/lib/config-manager.js | 101 | ||||
-rw-r--r-- | app/scripts/lib/ensnare.js | 24 | ||||
-rw-r--r-- | app/scripts/lib/extension-instance.js | 6 | ||||
-rw-r--r-- | app/scripts/lib/idStore.js | 18 | ||||
-rw-r--r-- | app/scripts/lib/inpage-provider.js | 77 | ||||
-rw-r--r-- | app/scripts/lib/is-popup-or-notification.js | 8 | ||||
-rw-r--r-- | app/scripts/lib/local-message-stream.js | 56 | ||||
-rw-r--r-- | app/scripts/lib/notifications.js | 167 |
9 files changed, 186 insertions, 273 deletions
diff --git a/app/scripts/lib/auto-reload.js b/app/scripts/lib/auto-reload.js index b45f02009..c4c8053f0 100644 --- a/app/scripts/lib/auto-reload.js +++ b/app/scripts/lib/auto-reload.js @@ -1,5 +1,5 @@ const once = require('once') -const ensnare = require('./ensnare.js') +const ensnare = require('ensnare') module.exports = setupDappAutoReload diff --git a/app/scripts/lib/config-manager.js b/app/scripts/lib/config-manager.js index 727cd46fc..715efb42e 100644 --- a/app/scripts/lib/config-manager.js +++ b/app/scripts/lib/config-manager.js @@ -1,10 +1,11 @@ const Migrator = require('pojo-migrator') const MetamaskConfig = require('../config.js') const migrations = require('./migrations') +const rp = require('request-promise') const TESTNET_RPC = MetamaskConfig.network.testnet const MAINNET_RPC = MetamaskConfig.network.mainnet -const CLASSIC_RPC = MetamaskConfig.network.classic +const txLimit = 40 /* The config-manager is a convenience object * wrapping a pojo-migrator. @@ -15,6 +16,8 @@ const CLASSIC_RPC = MetamaskConfig.network.classic */ module.exports = ConfigManager function ConfigManager (opts) { + this.txLimit = txLimit + // ConfigManager is observable and will emit updates this._subs = [] @@ -145,9 +148,6 @@ ConfigManager.prototype.getCurrentRpcAddress = function () { case 'testnet': return TESTNET_RPC - case 'classic': - return CLASSIC_RPC - default: return provider && provider.rpcTarget ? provider.rpcTarget : TESTNET_RPC } @@ -184,6 +184,9 @@ ConfigManager.prototype._saveTxList = function (txList) { ConfigManager.prototype.addTx = function (tx) { var transactions = this.getTxList() + while (transactions.length > this.txLimit - 1) { + transactions.shift() + } transactions.push(tx) this._saveTxList(transactions) } @@ -274,9 +277,61 @@ ConfigManager.prototype.getConfirmed = function () { return ('isConfirmed' in data) && data.isConfirmed } -ConfigManager.prototype.setShouldntShowWarning = function (confirmed) { +ConfigManager.prototype.setCurrentFiat = function (currency) { + var data = this.getData() + data.fiatCurrency = currency + this.setData(data) +} + +ConfigManager.prototype.getCurrentFiat = function () { + var data = this.getData() + return ('fiatCurrency' in data) && data.fiatCurrency +} + +ConfigManager.prototype.updateConversionRate = function () { + var data = this.getData() + return rp(`https://www.cryptonator.com/api/ticker/eth-${data.fiatCurrency}`) + .then((response) => { + const parsedResponse = JSON.parse(response) + this.setConversionPrice(parsedResponse.ticker.price) + this.setConversionDate(parsedResponse.timestamp) + }).catch((err) => { + console.error('Error in conversion.', err) + this.setConversionPrice(0) + this.setConversionDate('N/A') + }) + +} + +ConfigManager.prototype.setConversionPrice = function (price) { + var data = this.getData() + data.conversionRate = Number(price) + this.setData(data) +} + +ConfigManager.prototype.setConversionDate = function (datestring) { + var data = this.getData() + data.conversionDate = datestring + this.setData(data) +} + +ConfigManager.prototype.getConversionRate = function () { + var data = this.getData() + return (('conversionRate' in data) && data.conversionRate) || 0 +} + +ConfigManager.prototype.getConversionDate = function () { var data = this.getData() - data.isEthConfirmed = confirmed + return (('conversionDate' in data) && data.conversionDate) || 'N/A' +} + +ConfigManager.prototype.setShouldntShowWarning = function () { + var data = this.getData() + if (data.isEthConfirmed) { + data.isEthConfirmed = !data.isEthConfirmed + } else { + data.isEthConfirmed = true + } this.setData(data) } @@ -284,3 +339,37 @@ ConfigManager.prototype.getShouldntShowWarning = function () { var data = this.getData() return ('isEthConfirmed' in data) && data.isEthConfirmed } + +ConfigManager.prototype.getShapeShiftTxList = function () { + var data = this.getData() + var shapeShiftTxList = data.shapeShiftTxList ? data.shapeShiftTxList : [] + shapeShiftTxList.forEach((tx) => { + if (tx.response.status !== 'complete') { + var requestListner = function (request) { + tx.response = JSON.parse(this.responseText) + if (tx.response.status === 'complete') { + tx.time = new Date().getTime() + } + } + + var shapShiftReq = new XMLHttpRequest() + shapShiftReq.addEventListener('load', requestListner) + shapShiftReq.open('GET', `https://shapeshift.io/txStat/${tx.depositAddress}`, true) + shapShiftReq.send() + } + }) + this.setData(data) + return shapeShiftTxList +} + +ConfigManager.prototype.createShapeShiftTx = function (depositAddress, depositType) { + var data = this.getData() + + var shapeShiftTx = {depositAddress, depositType, key: 'shapeshift', time: new Date().getTime(), response: {}} + if (!data.shapeShiftTxList) { + data.shapeShiftTxList = [shapeShiftTx] + } else { + data.shapeShiftTxList.push(shapeShiftTx) + } + this.setData(data) +} diff --git a/app/scripts/lib/ensnare.js b/app/scripts/lib/ensnare.js deleted file mode 100644 index 6100f7c79..000000000 --- a/app/scripts/lib/ensnare.js +++ /dev/null @@ -1,24 +0,0 @@ -module.exports = ensnare - -// creates a proxy object that calls cb everytime the obj's properties/fns are accessed -function ensnare (obj, cb) { - var proxy = {} - Object.keys(obj).forEach(function (key) { - var val = obj[key] - switch (typeof val) { - case 'function': - proxy[key] = function () { - cb() - val.apply(obj, arguments) - } - return - default: - Object.defineProperty(proxy, key, { - get: function () { cb(); return obj[key] }, - set: function (val) { cb(); obj[key] = val; return val }, - }) - return - } - }) - return proxy -} diff --git a/app/scripts/lib/extension-instance.js b/app/scripts/lib/extension-instance.js index eb3b8a1e9..1098130e3 100644 --- a/app/scripts/lib/extension-instance.js +++ b/app/scripts/lib/extension-instance.js @@ -42,6 +42,12 @@ function Extension () { } catch (e) {} try { + if (browser[api]) { + _this[api] = browser[api] + } + } catch (e) {} + + try { _this.api = browser.extension[api] } catch (e) {} diff --git a/app/scripts/lib/idStore.js b/app/scripts/lib/idStore.js index c6ac55a03..0f36a520b 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) @@ -100,6 +104,10 @@ IdentityStore.prototype.getState = function () { unconfMsgs: messageManager.unconfirmedMsgs(), messages: messageManager.getMsgList(), selectedAddress: configManager.getSelectedAccount(), + shapeShiftTxList: configManager.getShapeShiftTxList(), + currentFiat: configManager.getCurrentFiat(), + conversionRate: configManager.getConversionRate(), + conversionDate: configManager.getConversionDate(), })) } @@ -153,8 +161,9 @@ IdentityStore.prototype.getNetwork = function (err) { this._currentState.network = 'loading' return this._didUpdate() } - - console.log('web3.getNetwork returned ' + network) + if (global.METAMASK_DEBUG) { + console.log('web3.getNetwork returned ' + network) + } this._currentState.network = network this._didUpdate() }) @@ -432,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) @@ -475,7 +485,9 @@ IdentityStore.prototype._restoreFromSeed = function (password, seed, derivedKey) keyStore.generateNewAddress(derivedKey, 3) configManager.setWallet(keyStore.serialize()) - console.log('restored from seed. saved to keystore') + if (global.METAMASK_DEBUG) { + console.log('restored from seed. saved to keystore') + } return keyStore } diff --git a/app/scripts/lib/inpage-provider.js b/app/scripts/lib/inpage-provider.js index e387be895..65354cd3d 100644 --- a/app/scripts/lib/inpage-provider.js +++ b/app/scripts/lib/inpage-provider.js @@ -1,9 +1,7 @@ -const HttpProvider = require('web3/lib/web3/httpprovider') const Streams = require('mississippi') const ObjectMultiplex = require('./obj-multiplex') const StreamProvider = require('web3-stream-provider') const RemoteStore = require('./remote-store.js').RemoteStore -const MetamaskConfig = require('../config.js') module.exports = MetamaskInpageProvider @@ -27,13 +25,6 @@ function MetamaskInpageProvider (connectionStream) { }) self.publicConfigStore = publicConfigStore - // connect to sync provider - self.syncProvider = createSyncProvider(publicConfigStore.get('provider')) - // subscribe to publicConfig to update the syncProvider on change - publicConfigStore.subscribe(function (state) { - self.syncProvider = createSyncProvider(state.provider) - }) - // connect to async provider var asyncProvider = new StreamProvider() Streams.pipe(asyncProvider, multiStream.createStream('provider'), asyncProvider, function (err) { @@ -42,15 +33,23 @@ function MetamaskInpageProvider (connectionStream) { }) asyncProvider.on('error', console.error.bind(console)) self.asyncProvider = asyncProvider - // overwrite own sendAsync method - self.sendAsync = asyncProvider.sendAsync.bind(asyncProvider) + // handle sendAsync requests via asyncProvider + self.sendAsync = function(payload, cb){ + // rewrite request ids + var request = jsonrpcMessageTransform(payload, (message) => { + message.id = createRandomId() + return message + }) + // forward to asyncProvider + asyncProvider.sendAsync(request, cb) + } } MetamaskInpageProvider.prototype.send = function (payload) { const self = this + let selectedAddress - - var result = null + let result = null switch (payload.method) { case 'eth_accounts': @@ -65,9 +64,10 @@ MetamaskInpageProvider.prototype.send = function (payload) { result = selectedAddress || '0x0000000000000000000000000000000000000000' break - // fallback to normal rpc + // throw not-supported Error default: - return self.syncProvider.send(payload) + 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.' + throw new Error(message) } @@ -89,35 +89,6 @@ MetamaskInpageProvider.prototype.isConnected = function () { // util -function createSyncProvider (providerConfig) { - providerConfig = providerConfig || {} - let syncProviderUrl - - if (providerConfig.rpcTarget) { - syncProviderUrl = providerConfig.rpcTarget - } else { - switch (providerConfig.type) { - case 'testnet': - syncProviderUrl = MetamaskConfig.network.testnet - break - case 'mainnet': - syncProviderUrl = MetamaskConfig.network.mainnet - break - default: - syncProviderUrl = MetamaskConfig.network.default - } - } - - 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) { // read local cache var initState = JSON.parse(localStorage[storageKey] || '{}') @@ -129,3 +100,21 @@ function remoteStoreWithLocalStorageCache (storageKey) { return store } + +function createRandomId(){ + const extraDigits = 3 + // 13 time digits + const datePart = new Date().getTime() * Math.pow(10, extraDigits) + // 3 random digits + const extraPart = Math.floor(Math.random() * Math.pow(10, extraDigits)) + // 16 digits + return datePart + extraPart +} + +function jsonrpcMessageTransform(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/local-message-stream.js b/app/scripts/lib/local-message-stream.js deleted file mode 100644 index 821e51046..000000000 --- a/app/scripts/lib/local-message-stream.js +++ /dev/null @@ -1,56 +0,0 @@ -const Duplex = require('readable-stream').Duplex -const inherits = require('util').inherits - -module.exports = LocalMessageDuplexStream - -inherits(LocalMessageDuplexStream, Duplex) - -function LocalMessageDuplexStream (opts) { - Duplex.call(this, { - objectMode: true, - }) - - // this._origin = opts.origin - this._name = opts.name - this._target = opts.target - - // console.log('LocalMessageDuplexStream ('+this._name+') - initialized...') - window.addEventListener('message', this._onMessage.bind(this), false) -} - -// private - -LocalMessageDuplexStream.prototype._onMessage = function (event) { - var msg = event.data - // console.log('LocalMessageDuplexStream ('+this._name+') - heard message...', event) - // validate message - if (event.origin !== location.origin) return // console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (event.origin !== location.origin) ') - if (typeof msg !== 'object') return // console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (typeof msg !== "object") ') - if (msg.target !== this._name) return // console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (msg.target !== this._name) ', msg.target, this._name) - if (!msg.data) return // console.log('LocalMessageDuplexStream ('+this._name+') - rejected - (!msg.data) ') - // console.log('LocalMessageDuplexStream ('+this._name+') - accepted', msg.data) - // forward message - try { - this.push(msg.data) - } catch (err) { - this.emit('error', err) - } -} - -// stream plumbing - -LocalMessageDuplexStream.prototype._read = noop - -LocalMessageDuplexStream.prototype._write = function (data, encoding, cb) { - // console.log('LocalMessageDuplexStream ('+this._name+') - sending message...') - var message = { - target: this._target, - data: data, - } - window.postMessage(message, location.origin) - cb() -} - -// util - -function noop () {} diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js index 6c1601df1..dcb946845 100644 --- a/app/scripts/lib/notifications.js +++ b/app/scripts/lib/notifications.js @@ -1,159 +1,48 @@ -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 setupListeners () { - // 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 - extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) { - var handlers = notificationHandlers[notificationId] - if (buttonIndex === 0) { - handlers.confirm() - } else { - handlers.cancel() +function show () { + getPopup((popup) => { + if (popup) { + return extension.windows.update(popup.id, { focused: true }) } - extension.notifications.clear(notificationId) - }) - - // notification teardown - extension.notifications.onClosed.addListener(function (notificationId) { - delete notificationHandlers[notificationId] - }) -} - -// 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.' - - 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 - - showNotification(extend(state, { - title: 'New Unsigned Transaction', - imageUrl: toSvgUri(notificationSvgSource), - })) - }) -} - -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) { - if (err) throw err - showNotification(extend(state, { - title: 'New Unsigned Message', - imageUrl: toSvgUri(notificationSvgSource), - })) + extension.windows.create({ + url: 'notification.html', + type: 'detached_panel', + focused: true, + width: 360, + height: 500, + }) }) } -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...') +function getPopup(cb) { - 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, + // Ignore in test environment + if (!extension.windows) { + return cb(null) } -} -function renderTxNotificationSVG (state, cb) { - var content = h(PendingTxDetails, state) - renderNotificationSVG(content, cb) -} + extension.windows.getAll({}, (windows) => { + let popup = windows.find((win) => { + return win.type === 'popup' + }) -function renderMsgNotificationSVG (state, cb) { - var content = h(PendingMsgDetails, state) - renderNotificationSVG(content, cb) -} - -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) + cb(popup) }) } -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) +function closePopup() { + getPopup((popup) => { + if (!popup) return + extension.windows.remove(popup.id, console.error) + }) } |