diff options
-rw-r--r-- | app/manifest.json | 3 | ||||
-rw-r--r-- | app/scripts/contentscript.js | 41 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 18 | ||||
-rw-r--r-- | app/scripts/platforms/extension.js | 6 | ||||
-rw-r--r-- | ui/app/actions.js | 41 | ||||
-rw-r--r-- | ui/app/app.js | 7 | ||||
-rw-r--r-- | ui/app/components/qr-scanner/index.js | 2 | ||||
-rw-r--r-- | ui/app/components/qr-scanner/qr-scanner.component.js | 152 | ||||
-rw-r--r-- | ui/app/components/send/send.component.js | 20 | ||||
-rw-r--r-- | ui/app/components/send/send.container.js | 2 | ||||
-rw-r--r-- | ui/app/components/send/send.selectors.js | 5 | ||||
-rw-r--r-- | ui/app/reducers/app.js | 20 | ||||
-rw-r--r-- | ui/app/selectors.js | 1 |
13 files changed, 231 insertions, 87 deletions
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 ( + <div className={'qr-code-modal-wrapper'}> + <div className={'qr-scanner'} + style={{ + position: 'fixed', + top: '50%', + left: '50%', + zIndex: 1050, + minWidth: '320px', + minHeight: '400px', + maxWidth: '300px', + maxHeight: '300px', + transform: 'translate(-50%, -50%)', + backgroundColor: '#ffffff', + padding: '15px', + }} + > + <h3 style={{ + textAlign: 'center', + marginBottom: '20px', + }}> + Scan QR code + </h3> + <div + className={'qr-code-video-wrapper'} + style={{ + overflow: 'hidden', + width: '100%', + height: '275px', + }}> + <video + style={{ + width: 'auto', + height: '275px', + marginLeft: '-15%', + }} + ref={(cam) => { + this.camera = cam + }} + /> + </div> + <div className={'qr-code-help'} style={{textAlign: 'center', fontSize: '12px', padding: '15px'}}> + {this.state.msg} + </div> + </div> + <div + className={'qr-code-modal-overlay'} + style={{ + position: 'fixed', + top: '0', + right: '0', + bottom: '0', + left: '0', + zIndex: '1040', + backgroundColor: 'rgba(0, 0, 0, 0.5)', + animationFillMode: 'forwards', + animationDuration: '0.3s', + animationName: 'anim_171532470906313', + animationTimingFunction: 'ease-out', + }} + onClick={_ => this.props.hideQrScanner() } + /> + </div> + ) + } +} + +function mapDispatchToProps (dispatch) { + return { + hideQrScanner: () => dispatch(hideQrScanner()), + qrCodeDetected: (data) => dispatch(qrCodeDetected(data)), + } +} +function mapStateToProps (state) { + return {} +} + +export default connect(mapStateToProps, mapDispatchToProps)(QrScanner) diff --git a/ui/app/components/send/send.component.js b/ui/app/components/send/send.component.js index 6c439cd21..fb7eef329 100644 --- a/ui/app/components/send/send.component.js +++ b/ui/app/components/send/send.component.js @@ -39,16 +39,26 @@ export default class SendTransactionScreen extends PersistentForm { updateSendErrors: PropTypes.func, updateSendTokenBalance: PropTypes.func, scanQrCode: PropTypes.func, + qrCodeData: PropTypes.object, }; static contextTypes = { t: PropTypes.func, }; - scanQrCode = async () => { - const scannedAddress = await this.props.scanQrCode() - this.props.updateSendTo(scannedAddress) - this.updateGas({ to: scannedAddress }) + componentWillReceiveProps (nextProps) { + if (nextProps.qrCodeData) { + if (nextProps.qrCodeData.type === 'address') { + const scannedAddress = nextProps.qrCodeData.values.address.toLowerCase() + const currentAddress = this.props.to && this.props.to.toLowerCase() + if (currentAddress !== scannedAddress) { + this.props.updateSendTo(scannedAddress) + this.updateGas({ to: scannedAddress }) + + // Here we should clear props.qrCodeData + } + } + } } updateGas ({ to: updatedToAddress, amount: value } = {}) { @@ -179,7 +189,7 @@ export default class SendTransactionScreen extends PersistentForm { <SendHeader history={history}/> <SendContent updateGas={(updateData) => this.updateGas(updateData)} - scanQrCode={_ => this.scanQrCode()} + scanQrCode={_ => this.props.scanQrCode()} /> <SendFooter history={history}/> </div> diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js index 417941601..67a441a9d 100644 --- a/ui/app/components/send/send.container.js +++ b/ui/app/components/send/send.container.js @@ -21,6 +21,7 @@ import { getSendFromObject, getSendTo, getTokenBalance, + getQrCodeData, } from './send.selectors' import { updateSendTo, @@ -62,6 +63,7 @@ function mapStateToProps (state) { tokenBalance: getTokenBalance(state), tokenContract: getSelectedTokenContract(state), tokenToFiatRate: getSelectedTokenToFiatRate(state), + qrCodeData: getQrCodeData(state), } } diff --git a/ui/app/components/send/send.selectors.js b/ui/app/components/send/send.selectors.js index cf07eafe1..4e81d1f9e 100644 --- a/ui/app/components/send/send.selectors.js +++ b/ui/app/components/send/send.selectors.js @@ -46,6 +46,7 @@ const selectors = { getTokenExchangeRate, getUnapprovedTxs, transactionsSelector, + getQrCodeData, } module.exports = selectors @@ -282,3 +283,7 @@ function transactionsSelector (state) { : txsToRender .sort((a, b) => b.time - a.time) } + +function getQrCodeData (state) { + return state.appState.qrCodeData +}
\ No newline at end of file diff --git a/ui/app/reducers/app.js b/ui/app/reducers/app.js index 50d8bcba7..9775638a7 100644 --- a/ui/app/reducers/app.js +++ b/ui/app/reducers/app.js @@ -50,7 +50,9 @@ function reduceApp (state, action) { }, sidebarOpen: false, alertOpen: false, + qrScannerOpen: false, alertMessage: null, + qrCodeData: null, networkDropdownOpen: false, currentView: seedWords ? seedConfView : defaultView, accountDetail: { @@ -90,7 +92,7 @@ function reduceApp (state, action) { sidebarOpen: false, }) - // sidebar methods + // alert methods case actions.ALERT_OPEN: return extend(appState, { alertOpen: true, @@ -102,6 +104,22 @@ function reduceApp (state, action) { alertOpen: false, alertMessage: null, }) + // qr scanner methods + case actions.QR_SCANNER_OPEN: + return extend(appState, { + qrScannerOpen: true, + }) + + case actions.QR_SCANNER_CLOSE: + return extend(appState, { + qrScannerOpen: false, + }) + + case actions.QR_CODE_DETECTED: + return extend(appState, { + qrCodeData: action.value, + }) + // modal methods: case actions.MODAL_OPEN: diff --git a/ui/app/selectors.js b/ui/app/selectors.js index d86462275..14d9dcd1d 100644 --- a/ui/app/selectors.js +++ b/ui/app/selectors.js @@ -194,3 +194,4 @@ function getTotalUnapprovedCount ({ metamask }) { return Object.keys(unapprovedTxs).length + unapprovedMsgCount + unapprovedPersonalMsgCount + unapprovedTypedMessagesCount } + |