aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/manifest.json3
-rw-r--r--app/scripts/contentscript.js41
-rw-r--r--app/scripts/metamask-controller.js18
-rw-r--r--app/scripts/platforms/extension.js6
-rw-r--r--ui/app/actions.js41
-rw-r--r--ui/app/app.js7
-rw-r--r--ui/app/components/qr-scanner/index.js2
-rw-r--r--ui/app/components/qr-scanner/qr-scanner.component.js152
-rw-r--r--ui/app/components/send/send.component.js20
-rw-r--r--ui/app/components/send/send.container.js2
-rw-r--r--ui/app/components/send/send.selectors.js5
-rw-r--r--ui/app/reducers/app.js20
-rw-r--r--ui/app/selectors.js1
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
}
+