aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/_locales/en/messages.json6
-rw-r--r--package-lock.json29
-rw-r--r--package.json1
-rw-r--r--ui/app/actions.js26
-rw-r--r--ui/app/components/modals/qr-scanner/index.scss5
-rw-r--r--ui/app/components/modals/qr-scanner/qr-scanner.component.js108
-rw-r--r--ui/app/components/modals/qr-scanner/qr-scanner.container.js9
-rw-r--r--ui/app/components/send/send.component.js5
-rw-r--r--ui/app/components/send/send.container.js8
-rw-r--r--ui/lib/webcam-utils.js38
10 files changed, 179 insertions, 56 deletions
diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json
index 60e873371..ec96f5b08 100644
--- a/app/_locales/en/messages.json
+++ b/app/_locales/en/messages.json
@@ -656,6 +656,9 @@
"notStarted": {
"message": "Not Started"
},
+ "noWebcamFound": {
+ "message": "We couldn't find any webcam available on your computer. Make sure the device is connected and configured correctly."
+ },
"oldUI": {
"message": "Old UI"
},
@@ -1098,6 +1101,9 @@
"unknownQrCode": {
"message": "Error: We couldn't identify that QR code"
},
+ "unknownCameraError": {
+ "message": "Ooops! Something went wrong while trying to access you camera. Please try again..."
+ },
"unlock": {
"message": "Unlock"
},
diff --git a/package-lock.json b/package-lock.json
index 3a4007048..3b99e2487 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7147,6 +7147,11 @@
}
}
},
+ "detectrtc": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/detectrtc/-/detectrtc-1.3.6.tgz",
+ "integrity": "sha1-2rwDU5gaPadzLelpBxwItt3dW1k="
+ },
"di": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz",
@@ -8406,12 +8411,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.0.1.tgz",
"integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==",
"requires": {
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@@ -8689,12 +8695,13 @@
"resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": {
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@@ -8736,12 +8743,14 @@
"integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==",
"dev": true,
"requires": {
+ "ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1"
},
"dependencies": {
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
- "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
+ "from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
+ "dev": true,
"requires": {
"bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0"
@@ -8753,6 +8762,7 @@
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
+ "dev": true,
"requires": {
"bn.js": "^4.11.0",
"create-hash": "^1.1.2",
@@ -30717,6 +30727,7 @@
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
+ "dev": true,
"requires": {
"is-typedarray": "^1.0.0"
}
@@ -31740,6 +31751,7 @@
"resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz",
"integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=",
"requires": {
+ "bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
"crypto-js": "^3.1.4",
"utf8": "^2.1.1",
"xhr2": "*",
@@ -31748,7 +31760,7 @@
"dependencies": {
"bignumber.js": {
"version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
- "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934"
+ "from": "git+https://github.com/frozeman/bignumber.js-nolookahead.git"
}
}
},
@@ -32247,7 +32259,8 @@
"dev": true,
"requires": {
"underscore": "1.8.3",
- "web3-core-helpers": "1.0.0-beta.34"
+ "web3-core-helpers": "1.0.0-beta.34",
+ "websocket": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2"
},
"dependencies": {
"underscore": {
@@ -32258,7 +32271,8 @@
},
"websocket": {
"version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
- "from": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2",
+ "from": "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible",
+ "dev": true,
"requires": {
"debug": "^2.2.0",
"nan": "^2.3.3",
@@ -33623,7 +33637,8 @@
"yaeti": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
- "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc="
+ "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=",
+ "dev": true
},
"yallist": {
"version": "2.1.2",
diff --git a/package.json b/package.json
index 55f99dca8..a39534983 100644
--- a/package.json
+++ b/package.json
@@ -98,6 +98,7 @@
"debounce-stream": "^2.0.0",
"deep-extend": "^0.5.1",
"detect-node": "^2.0.3",
+ "detectrtc": "^1.3.6",
"disc": "^1.3.2",
"dnode": "^1.2.2",
"end-of-stream": "^1.1.0",
diff --git a/ui/app/actions.js b/ui/app/actions.js
index 0eb47edba..3540f61a5 100644
--- a/ui/app/actions.js
+++ b/ui/app/actions.js
@@ -12,6 +12,7 @@ const { fetchLocale } = require('../i18n-helper')
const log = require('loglevel')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums')
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
+const WebcamUtils = require('../lib/webcam-utils')
var actions = {
_setBackgroundConnection: _setBackgroundConnection,
@@ -127,7 +128,8 @@ var actions = {
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
SET_CURRENT_FIAT: 'SET_CURRENT_FIAT',
- setCurrentCurrency: setCurrentCurrency,
+ showQrScanner,
+ setCurrentCurrency,
setCurrentAccountTab,
// account detail screen
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE',
@@ -723,6 +725,28 @@ function showInfoPage () {
}
}
+function showQrScanner (ROUTE) {
+ return (dispatch, getState) => {
+ return WebcamUtils.checkStatus()
+ .then(status => {
+ if (!status.environmentReady) {
+ // We need to switch to fullscreen mode to ask for permission
+ global.platform.openExtensionInBrowser(`${ROUTE}`, `scan=true`)
+ } else {
+ dispatch(actions.showModal({
+ name: 'QR_SCANNER',
+ }))
+ }
+ }).catch(e => {
+ dispatch(actions.showModal({
+ name: 'QR_SCANNER',
+ error: true,
+ errorType: e.type,
+ }))
+ })
+ }
+}
+
function setCurrentCurrency (currencyCode) {
return (dispatch) => {
dispatch(actions.showLoadingIndication())
diff --git a/ui/app/components/modals/qr-scanner/index.scss b/ui/app/components/modals/qr-scanner/index.scss
index 314e94069..df65cfbbb 100644
--- a/ui/app/components/modals/qr-scanner/index.scss
+++ b/ui/app/components/modals/qr-scanner/index.scss
@@ -38,5 +38,10 @@
font-size: 14px;
padding: 15px;
}
+
+ &__status.error {
+ padding: 60px 45px 80px;
+ font-size: 16px;
+ }
}
diff --git a/ui/app/components/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/modals/qr-scanner/qr-scanner.component.js
index 395008fca..29ce45184 100644
--- a/ui/app/components/modals/qr-scanner/qr-scanner.component.js
+++ b/ui/app/components/modals/qr-scanner/qr-scanner.component.js
@@ -3,16 +3,14 @@ import PropTypes from 'prop-types'
import { BrowserQRCodeReader } from '@zxing/library'
import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars
import Spinner from '../../spinner'
-const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums')
-const { getEnvironmentType } = require('../../../../../app/scripts/lib/util')
-const {
- SEND_ROUTE,
-} = require('../../../routes')
+import WebcamUtils from '../../../../lib/webcam-utils'
export default class QrScanner extends Component {
static propTypes = {
hideModal: PropTypes.func.isRequired,
qrCodeDetected: PropTypes.func,
+ error: PropTypes.bool,
+ errorType: PropTypes.string,
}
static contextTypes = {
@@ -21,46 +19,65 @@ export default class QrScanner extends Component {
constructor (props, context) {
super(props)
+
+ let initialMsg = context.t('accessingYourCamera')
+ if (props.error) {
+ if (props.errorType === 'NO_WEBCAM_FOUND') {
+ initialMsg = context.t('noWebcamFound')
+ } else {
+ initialMsg = context.t('unknownCameraError')
+ }
+ }
+
this.state = {
ready: false,
- msg: context.t('accessingYourCamera'),
+ msg: initialMsg,
}
- this.scanning = false
this.codeReader = null
+ this.permissionChecker = null
this.notAllowed = false
}
componentDidMount () {
+ this.initCamera()
+ }
- if (!this.scanning) {
- this.scanning = true
-
- this.initCamera()
+ async checkPermisisions () {
+ const { permissions } = await WebcamUtils.checkStatus()
+ if (permissions) {
+ clearTimeout(this.permissionChecker)
+ // Let the video stream load first...
+ setTimeout(_ => {
+ this.setState({
+ ready: true,
+ msg: this.context.t('scanInstructions'),
+ })
+ }, 2000)
+
+ } else {
+ // Keep checking for permissions
+ this.permissionChecker = setTimeout(_ => {
+ console.log('[QR-SCANNER]: time to check again!')
+ this.checkPermisisions()
+ }, 1000)
}
}
componentWillUnmount () {
- this.codeReader.reset()
+ clearTimeout(this.permissionChecker)
+ if (this.codeReader) {
+ this.codeReader.reset()
+ }
}
initCamera () {
-
this.codeReader = new BrowserQRCodeReader()
this.codeReader.getVideoInputDevices()
.then(videoInputDevices => {
-
- setTimeout(_ => {
- if (!this.notAllowed) {
- this.setState({
- ready: true,
- msg: this.context.t('scanInstructions')})
- }
- }, 2000)
-
-
- this.codeReader.decodeFromInputVideoDevice(videoInputDevices[0].deviceId, 'video')
+ clearTimeout(this.permissionChecker)
+ this.checkPermisisions()
+ this.codeReader.decodeFromInputVideoDevice(undefined, 'video')
.then(content => {
-
const result = this.parseContent(content.text)
if (result.type !== 'unknown') {
this.props.qrCodeDetected(result)
@@ -70,18 +87,14 @@ export default class QrScanner extends Component {
}
})
.catch(err => {
- this.notAllowed = true
if (err && err.name === 'NotAllowedError') {
- if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
- global.platform.openExtensionInBrowser(`${SEND_ROUTE}`, `scan=true`)
- } else {
- this.setState({msg: this.context.t('youNeedToAllowCameraAccess')})
- }
+ this.setState({msg: this.context.t('youNeedToAllowCameraAccess')})
+ clearTimeout(this.permissionChecker)
+ this.checkPermisisions()
}
- console.error('QR-SCANNER: decodeFromInputVideoDevice threw an exception: ', err)
})
}).catch(err => {
- console.error('QR-SCANNER: getVideoInputDevices threw an exception: ', err)
+ console.error('[QR-SCANNER]: getVideoInputDevices threw an exception: ', err)
})
}
@@ -103,31 +116,36 @@ export default class QrScanner extends Component {
stopAndClose = () => {
this.codeReader.reset()
- this.scanning = false
this.setState({ ready: false })
this.props.hideModal()
}
+ renderVideo () {
+ return (
+ <div className={'qr-scanner__content__video-wrapper'}>
+ <video
+ id="video"
+ style={{
+ display: this.state.ready ? 'block' : 'none',
+ }}
+ />
+ { !this.state.ready ? <Spinner color={'#F7C06C'} /> : null}
+ </div>
+ )
+ }
+
render () {
const { t } = this.context
return (
<div className="qr-scanner">
<div className="qr-scanner__title">
- { `${t('scanQrCode')}?` }
+ { `${t('scanQrCode')}` }
</div>
<div className="qr-scanner__content">
- <div className={'qr-scanner__content__video-wrapper'}>
- <video
- id="video"
- style={{
- display: this.state.ready ? 'block' : 'none',
- }}
- />
- { !this.state.ready ? <Spinner color={'#F7C06C'} /> : null}
- </div>
+ { !this.props.error ? this.renderVideo() : null}
</div>
- <div className="qr-scanner__status">
+ <div className={`qr-scanner__status ${this.props.error ? 'error' : ''}`}>
{this.state.msg}
</div>
</div>
diff --git a/ui/app/components/modals/qr-scanner/qr-scanner.container.js b/ui/app/components/modals/qr-scanner/qr-scanner.container.js
index 198d5ff81..d50abe0ae 100644
--- a/ui/app/components/modals/qr-scanner/qr-scanner.container.js
+++ b/ui/app/components/modals/qr-scanner/qr-scanner.container.js
@@ -3,6 +3,13 @@ import QrScanner from './qr-scanner.component'
const { hideModal, qrCodeDetected } = require('../../../actions')
+const mapStateToProps = state => {
+ return {
+ error: state.appState.modal.modalState.props.error,
+ errorType: state.appState.modal.modalState.props.errorType,
+ }
+}
+
const mapDispatchToProps = dispatch => {
return {
hideModal: () => dispatch(hideModal()),
@@ -10,4 +17,4 @@ const mapDispatchToProps = dispatch => {
}
}
-export default connect(null, mapDispatchToProps)(QrScanner)
+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 8305a288e..80b50fad4 100644
--- a/ui/app/components/send/send.component.js
+++ b/ui/app/components/send/send.component.js
@@ -179,6 +179,11 @@ export default class SendTransactionScreen extends PersistentForm {
// Show QR Scanner modal if ?scan=true
if (window.location.search === '?scan=true') {
this.props.scanQrCode()
+
+ // Clear the queryString param after showing the modal
+ const cleanUrl = location.href.split('?')[0]
+ history.pushState({}, null, `${cleanUrl}`)
+ window.location.hash = '#send'
}
}
diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js
index 1fb771974..25abb706c 100644
--- a/ui/app/components/send/send.container.js
+++ b/ui/app/components/send/send.container.js
@@ -28,7 +28,7 @@ import {
updateSendTokenBalance,
updateGasData,
setGasTotal,
- showModal,
+ showQrScanner,
} from '../../actions'
import {
resetSendState,
@@ -38,6 +38,10 @@ import {
calcGasTotal,
} from './send.utils.js'
+import {
+ SEND_ROUTE,
+} from '../../routes'
+
module.exports = compose(
withRouter,
connect(mapStateToProps, mapDispatchToProps)
@@ -93,7 +97,7 @@ function mapDispatchToProps (dispatch) {
},
updateSendErrors: newError => dispatch(updateSendErrors(newError)),
resetSendState: () => dispatch(resetSendState()),
- scanQrCode: () => dispatch(showModal({ name: 'QR_SCANNER' })),
+ scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
}
}
diff --git a/ui/lib/webcam-utils.js b/ui/lib/webcam-utils.js
new file mode 100644
index 000000000..e4261dfbc
--- /dev/null
+++ b/ui/lib/webcam-utils.js
@@ -0,0 +1,38 @@
+'use strict'
+
+import DetectRTC from 'detectrtc'
+const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums')
+const { getEnvironmentType } = require('../../app/scripts/lib/util')
+
+class WebcamUtils {
+
+ static checkStatus () {
+ return new Promise((resolve, reject) => {
+ const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP
+ const isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1
+ const isBrave = !!window.chrome.ipcRenderer
+ const isFirefoxOrBrave = isFirefox || isBrave
+ try {
+ reject({type: 'NO_WEBCAM_FOUND'})
+ // DetectRTC.load(_ => {
+ // if (DetectRTC.hasWebcam) {
+ // let environmentReady = true
+ // if ((isFirefoxOrBrave && isPopup) || (isPopup && !DetectRTC.isWebsiteHasWebcamPermissions)) {
+ // environmentReady = false
+ // }
+ // resolve({
+ // permissions: DetectRTC.isWebsiteHasWebcamPermissions,
+ // environmentReady,
+ // })
+ // } else {
+ // reject({type: 'NO_WEBCAM_FOUND'})
+ // }
+ // })
+ } catch (e) {
+ reject({type: 'UNKNOWN_ERROR'})
+ }
+ })
+ }
+}
+
+module.exports = WebcamUtils