diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7c775fb04..87f7c63ef 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -6,6 +6,7 @@ const PongStream = require('ping-pong-stream/pong') const ObjectMultiplex = require('obj-multiplex') const extension = require('extensionizer') const PortStream = require('./lib/port-stream.js') +const Instascan = require('instascan') const inpageContent = fs.readFileSync(path.join(__dirname, '..', '..', 'dist', 'chrome', 'inpage.js')).toString() const inpageSuffix = '//# sourceURL=' + extension.extension.getURL('inpage.js') + '\n' @@ -199,3 +200,49 @@ function redirectToPhishingWarning () { console.log('MetaMask - redirecting to phishing warning') window.location.href = 'https://metamask.io/phishing.html' } + +function initQrCodeScanner () { + // Append preview div + const preview = document.createElement('div') + preview.id = 'metamask-preview-wrapper' + preview.style = 'position:absolute; top: 20px; left: 20px; width: 300px; height: 300px; overflow: hidden; z-index: 999999999;' + const previewVideo = document.createElement('video') + previewVideo.id = 'metamask-preview-video' + previewVideo.style = 'width: 100%; height: 100%; object-fit: none; margin-left: -10%; margin-top: 10%;' + preview.appendChild(previewVideo) + document.body.appendChild(preview) + console.log('injected') + const scanner = new Instascan.Scanner({ + video: document.getElementById('metamask-preview-video'), + backgroundScan: false, + continuous: true, + }) + scanner.addListener('scan', function (content) { + console.log('QR-SCANNER: got code (IN-PAGE)', content) + scanner.stop().then(_ => { + console.log('QR-SCANNER: stopped scanner and sending msg (IN-PAGE)', content) + extension.runtime.sendMessage({ + action: 'qr-code-scanner-data', + data: content, + }) + console.log('QR-SCANNER: message sent (IN-PAGE)', content) + document.getElementById('metamask-preview-wrapper').parentElement.removeChild(document.getElementById('metamask-preview-wrapper')) + }) + }) + Instascan.Camera.getCameras().then(function (cameras) { + if (cameras.length > 0) { + scanner.start(cameras[0]) + } else { + console.error('No cameras found.') + } + }).catch(function (e) { + console.error(e) + }) +} + +extension.runtime.onMessage.addListener(({ action }) => { + console.log('QR-SCANNER: message received (IN-PAGE)', action) + initQrCodeScanner() +}) +console.log('QR-SCANNER: now listening (IN-PAGE)') + diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 1a7bea7c0..20621b73f 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -5,7 +5,6 @@ const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') -const Instascan = require('instascan') restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') @@ -98,40 +97,3 @@ function restoreContextAfterImports () { } } -function initCameraScanner () { - // Append preview div - const preview = document.createElement('div') - preview.id = 'metamask-preview-wrapper' - preview.style = 'position:absolute; top: 20px; left: 20px; width: 300px; height: 300px; overflow: hidden; z-index: 999999999;' - const previewVideo = document.createElement('video') - previewVideo.id = 'metamask-preview-video' - previewVideo.style = 'width: 100%; height: 100%; object-fit: none; margin-left: -10%; margin-top: 10%;' - preview.appendChild(previewVideo) - document.body.appendChild(preview) - console.log('injected') - const scanner = new Instascan.Scanner({ - video: document.getElementById('metamask-preview-video'), - backgroundScan: false, - continuous: true, - }) - scanner.addListener('scan', function (content) { - alert(content) - scanner.stop().then(_ => { - document.getElementById('metamask-preview-wrapper').parentElement.removeChild(document.getElementById('metamask-preview-wrapper')) - }) - }) - Instascan.Camera.getCameras().then(function (cameras) { - if (cameras.length > 0) { - scanner.start(cameras[0]) - } else { - console.error('No cameras found.') - } - }).catch(function (e) { - console.error(e) - }) -} - -setTimeout(_ => { - console.log('injecting...') - initCameraScanner() -}, 3000) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bcc7075c2..62d707432 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -380,6 +380,9 @@ module.exports = class MetamaskController extends EventEmitter { // TREZOR unlockTrezorAccount: nodeify(this.unlockTrezorAccount, this), + // QR code scanner + scanQrCode: nodeify(this.scanQrCode, this), + // vault management submitPassword: nodeify(this.submitPassword, this), @@ -653,7 +656,26 @@ module.exports = class MetamaskController extends EventEmitter { const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } - } + } + + scanQrCode () { + return new Promise((resolve, reject) => { + console.log('QR-SCANNER: intializing QR code scanner feature (MM controller)') + // Tell contentscript to inject the QR reader + this.platform.sendMessage('qr-code-scanner-init') + console.log('QR-SCANNER: message to initialize has been sent (MM controller)') + // Wait for the scanner to send something back + this.platform.addMessageListener(({ action, data }) => { + console.log('QR-SCANNER: message received (MM controller)', action, data) + if (action && action === 'qr-code-scanner-data') { + const normalizedAddress = data.replace('ethereum:', '') + console.log('QR-SCANNER: resolving promise!', normalizedAddress) + return Promise.resolve(normalizedAddress) + } + }) + console.log('QR-SCANNER: now listening (MM controller)') + }) + } // diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 901c26cab..182df23b1 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -1,5 +1,4 @@ const extension = require('extensionizer') -const explorerLink = require('etherscan-link').createExplorerLink class ExtensionPlatform { @@ -18,11 +17,8 @@ class ExtensionPlatform { return extension.runtime.getManifest().version } - openExtensionInBrowser (route = null) { - let extensionURL = extension.runtime.getURL('home.html') - if (route) { - extensionURL += `#${route}` - } + openExtensionInBrowser () { + const extensionURL = extension.runtime.getURL('home.html') this.openWindow({ url: extensionURL }) } @@ -36,57 +32,16 @@ class ExtensionPlatform { } } - showTransactionNotification (txMeta) { - - const status = txMeta.status - if (status === 'confirmed') { - this._showConfirmedTransaction(txMeta) - } else if (status === 'failed') { - this._showFailedTransaction(txMeta) - } - } - - _showConfirmedTransaction (txMeta) { - - this._subscribeToNotificationClicked() - - const url = explorerLink(txMeta.hash, parseInt(txMeta.metamaskNetworkId)) - const nonce = parseInt(txMeta.txParams.nonce, 16) - - const title = 'Confirmed transaction' - const message = `Transaction ${nonce} confirmed! View on EtherScan` - this._showNotification(title, message, url) - } - - _showFailedTransaction (txMeta) { - - const nonce = parseInt(txMeta.txParams.nonce, 16) - const title = 'Failed transaction' - const message = `Transaction ${nonce} failed! ${txMeta.err.message}` - this._showNotification(title, message) - } - - _showNotification (title, message, url) { - extension.notifications.create( - url, - { - 'type': 'basic', - 'title': title, - 'iconUrl': extension.extension.getURL('../../images/icon-64.png'), - 'message': message, - }) + addMessageListener (cb) { + extension.runtime.onMessage.addListener(cb) } - _subscribeToNotificationClicked () { - if (!extension.notifications.onClicked.hasListener(this._viewOnEtherScan)) { - extension.notifications.onClicked.addListener(this._viewOnEtherScan) - } - } - - _viewOnEtherScan (txId) { - if (txId.startsWith('http://')) { - global.metamaskController.platform.openWindow({ url: txId }) - } + sendMessage (message, query = {}) { + extension.tabs.query(query, tabs => { + const activeTab = tabs.filter(tab => tab.active)[0] + extension.tabs.sendMessage(activeTab.id, message) + console.log('QR-SCANNER: message sent to tab', message, activeTab) + }) } } diff --git a/ui/app/actions.js b/ui/app/actions.js index 6c947fc35..9aba6853d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -302,6 +302,7 @@ var actions = { CLEAR_PENDING_TOKENS: 'CLEAR_PENDING_TOKENS', setPendingTokens, clearPendingTokens, + scanQrCode, } module.exports = actions @@ -2194,3 +2195,23 @@ function clearPendingTokens () { type: actions.CLEAR_PENDING_TOKENS, } } + +function scanQrCode () { + log.debug(`background.scanQrCode`) + return (dispatch, getState) => { + dispatch(actions.showLoadingIndication()) + return new Promise((resolve, reject) => { + background.scanQrCode((err, data) => { + log.debug(`background.scanQrCode resolved!`, err, data) + if (err) { + log.error(err) + dispatch(actions.displayWarning(err.message)) + return reject(err) + } + + dispatch(actions.hideLoadingIndication()) + return resolve(data) + }) + }) + } +} diff --git a/ui/app/components/send/send-content/send-content.component.js b/ui/app/components/send/send-content/send-content.component.js index 7a0b1a18e..566ee1c7f 100644 --- a/ui/app/components/send/send-content/send-content.component.js +++ b/ui/app/components/send/send-content/send-content.component.js @@ -11,6 +11,7 @@ export default class SendContent extends Component { static propTypes = { updateGas: PropTypes.func, + scanQrCode: PropTypes.func, }; render () { @@ -19,6 +20,7 @@ export default class SendContent extends Component {
this.props.updateGas(updateData)} /> + this.props.updateGas(updateData)} /> diff --git a/ui/app/components/send/send.component.js b/ui/app/components/send/send.component.js index 6f1b20c55..5e967251d 100644 --- a/ui/app/components/send/send.component.js +++ b/ui/app/components/send/send.component.js @@ -38,12 +38,19 @@ export default class SendTransactionScreen extends PersistentForm { updateAndSetGasTotal: PropTypes.func, updateSendErrors: PropTypes.func, updateSendTokenBalance: PropTypes.func, + scanQrCode: PropTypes.func, }; static contextTypes = { t: PropTypes.func, }; + scanQrCode = async () => { + const scannedAddress = await this.props.scanQrCode() + console.log('QR-SCANNER: Got address (UI)', scannedAddress) + this.updateGas({ to: scannedAddress }) + } + updateGas ({ to: updatedToAddress, amount: value } = {}) { const { amount, @@ -170,7 +177,10 @@ export default class SendTransactionScreen extends PersistentForm { return (
- this.updateGas(updateData)}/> + this.updateGas(updateData)} + scanQrCode={_ => this.scanQrCode()} + />
) diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js index 44ebd2792..c3240be67 100644 --- a/ui/app/components/send/send.container.js +++ b/ui/app/components/send/send.container.js @@ -26,6 +26,7 @@ import { updateSendTokenBalance, updateGasData, setGasTotal, + scanQrCode, } from '../../actions' import { resetSendState, @@ -89,5 +90,6 @@ function mapDispatchToProps (dispatch) { }, updateSendErrors: newError => dispatch(updateSendErrors(newError)), resetSendState: () => dispatch(resetSendState()), + scanQrCode: () => dispatch(scanQrCode()), } }