diff --git a/app/manifest.json b/app/manifest.json index 52256c5b7..6933652e6 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -76,5 +76,6 @@ "ids": [ "*" ] - } + }, + "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" } diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 83ed85a1a..72de16f31 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -6,7 +6,6 @@ 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' @@ -201,43 +200,3 @@ function redirectToPhishingWarning () { 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:fixed; 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) { - scanner.stop().then(_ => { - extension.runtime.sendMessage({ - action: 'qr-code-scanner-data', - data: 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 }) => { - initQrCodeScanner() -}) - diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index f67d4edf8..c6be4b9d2 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -380,9 +380,6 @@ 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), @@ -658,21 +655,6 @@ module.exports = class MetamaskController extends EventEmitter { return { ...keyState, identities } } - scanQrCode () { - return new Promise((resolve, reject) => { - // Tell contentscript to inject the QR reader - this.platform.sendMessageToActiveTab('qr-code-scanner-init') - // Wait for the scanner to send something back - this.platform.addMessageListener(({ action, data }) => { - if (action && action === 'qr-code-scanner-data') { - const normalizedAddress = data.replace('ethereum:', '') - resolve(normalizedAddress) - } - }) - }) - } - - // // Account Management // diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 1cab0bedd..452a51bd8 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -36,12 +36,6 @@ class ExtensionPlatform { extension.runtime.onMessage.addListener(cb) } - sendMessageToActiveTab (message, query = {}) { - extension.tabs.query(query, tabs => { - const activeTab = tabs.filter(tab => tab.active)[0] - extension.tabs.sendMessage(activeTab.id, message) - }) - } } module.exports = ExtensionPlatform diff --git a/ui/app/actions.js b/ui/app/actions.js index 9aba6853d..dd0e78b6a 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -31,6 +31,12 @@ var actions = { ALERT_CLOSE: 'UI_ALERT_CLOSE', showAlert: showAlert, hideAlert: hideAlert, + QR_SCANNER_OPEN: 'UI_QR_SCANNER_OPEN', + QR_SCANNER_CLOSE: 'UI_QR_SCANNER_CLOSE', + QR_CODE_DETECTED: 'UI_QR_CODE_DETECTED', + showQrScanner, + hideQrScanner, + qrCodeDetected, // network dropdown open NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN', NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE', @@ -1752,6 +1758,25 @@ function hideAlert () { } } +function showQrScanner () { + return { + type: actions.QR_SCANNER_OPEN, + } +} + +function qrCodeDetected (qrCodeData) { + return { + type: actions.QR_CODE_DETECTED, + value: qrCodeData, + } +} + +function hideQrScanner () { + return { + type: actions.QR_SCANNER_CLOSE, + } +} + function showLoadingIndication (message) { return { @@ -2197,21 +2222,7 @@ function clearPendingTokens () { } 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) - }) - }) + dispatch(actions.showQrScanner()) } } diff --git a/ui/app/app.js b/ui/app/app.js index dbb6146d1..9363d21d1 100644 --- a/ui/app/app.js +++ b/ui/app/app.js @@ -39,6 +39,8 @@ const Modal = require('./components/modals/index').Modal // Global Alert const Alert = require('./components/alert') +const QrScanner = require('./components/qr-scanner') + const AppHeader = require('./components/app-header') import UnlockPage from './components/pages/unlock-page' @@ -132,6 +134,8 @@ class App extends Component { // global alert h(Alert, {visible: this.props.alertOpen, msg: alertMessage}), + h(QrScanner, {visible: this.props.qrScannerOpen}), + h(AppHeader), // sidebar @@ -270,6 +274,7 @@ App.propTypes = { currentView: PropTypes.object, sidebarOpen: PropTypes.bool, alertOpen: PropTypes.bool, + qrScannerOpen: PropTypes.bool, hideSidebar: PropTypes.func, isMascara: PropTypes.bool, isOnboarding: PropTypes.bool, @@ -306,6 +311,7 @@ function mapStateToProps (state) { networkDropdownOpen, sidebarOpen, alertOpen, + qrScannerOpen, alertMessage, isLoading, loadingMessage, @@ -333,6 +339,7 @@ function mapStateToProps (state) { networkDropdownOpen, sidebarOpen, alertOpen, + qrScannerOpen, alertMessage, isLoading, loadingMessage, diff --git a/ui/app/components/qr-scanner/index.js b/ui/app/components/qr-scanner/index.js new file mode 100644 index 000000000..f459f6702 --- /dev/null +++ b/ui/app/components/qr-scanner/index.js @@ -0,0 +1,2 @@ +import QrScanner from './qr-scanner.component' +module.exports = QrScanner diff --git a/ui/app/components/qr-scanner/qr-scanner.component.js b/ui/app/components/qr-scanner/qr-scanner.component.js new file mode 100644 index 000000000..cc07e53a2 --- /dev/null +++ b/ui/app/components/qr-scanner/qr-scanner.component.js @@ -0,0 +1,152 @@ +import React, { Component } from 'react' +import PropTypes from 'prop-types' +import {connect} from 'react-redux' +import { hideQrScanner, qrCodeDetected} from '../../actions' +import Instascan from 'instascan' + +class QrScanner extends Component { + static propTypes = { + visible: PropTypes.bool, + hideQrScanner: PropTypes.func, + qrCodeDetected: PropTypes.func, + } + constructor (props) { + super(props) + this.state = { + msg: 'Place the QR code in front of your camera so we can read it...', + } + this.scanning = false + } + + parseContent (content) { + let type = 'unknown' + let values = {} + + // Here we could add more cases + // To parse other codes (transactions for ex.) + + if (content.split('ethereum:').length > 1) { + type = 'address' + values = {'address': content.split('ethereum:')[1] } + } + return {type, values} + } + + componentDidUpdate () { + if (this.props.visible && this.camera && !this.scanning) { + const scanner = new Instascan.Scanner({ + video: this.camera, + backgroundScan: false, + continuous: true, + }) + scanner.addListener('scan', (content) => { + scanner.stop().then(_ => { + const result = this.parseContent(content) + if (result.type !== 'unknown') { + console.log('QR-SCANNER: CODE DETECTED', result) + this.props.qrCodeDetected(result) + this.props.hideQrScanner() + } else { + this.setState({msg: 'Error: We couldn\'t identify that QR code'}) + } + }) + }) + Instascan.Camera.getCameras().then((cameras) => { + if (cameras.length > 0) { + scanner.start(cameras[0]) + console.log('QR-SCANNER: started scanning with camera', cameras[0]) + } else { + console.log('QR-SCANNER: no cameras found') + } + }).catch(function (e) { + console.error(e) + }) + this.scanning = true + } + } + + render () { + const { visible } = this.props + + if (!visible) { + return null + } + + return ( +