added crossbrowser support and error handling

feature/default_network_editable
brunobar79 6 years ago
parent edb154749d
commit 710b4e294f
  1. 6
      app/_locales/en/messages.json
  2. 29
      package-lock.json
  3. 1
      package.json
  4. 26
      ui/app/actions.js
  5. 5
      ui/app/components/modals/qr-scanner/index.scss
  6. 98
      ui/app/components/modals/qr-scanner/qr-scanner.component.js
  7. 9
      ui/app/components/modals/qr-scanner/qr-scanner.container.js
  8. 5
      ui/app/components/send/send.component.js
  9. 8
      ui/app/components/send/send.container.js
  10. 38
      ui/lib/webcam-utils.js

@ -656,6 +656,9 @@
"notStarted": { "notStarted": {
"message": "Not Started" "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": { "oldUI": {
"message": "Old UI" "message": "Old UI"
}, },
@ -1098,6 +1101,9 @@
"unknownQrCode": { "unknownQrCode": {
"message": "Error: We couldn't identify that QR code" "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": { "unlock": {
"message": "Unlock" "message": "Unlock"
}, },

29
package-lock.json generated

@ -7147,6 +7147,11 @@
} }
} }
}, },
"detectrtc": {
"version": "1.3.6",
"resolved": "https://registry.npmjs.org/detectrtc/-/detectrtc-1.3.6.tgz",
"integrity": "sha1-2rwDU5gaPadzLelpBxwItt3dW1k="
},
"di": { "di": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", "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", "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-2.0.1.tgz",
"integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==",
"requires": { "requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1" "ethereumjs-util": "^5.1.1"
}, },
"dependencies": { "dependencies": {
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "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": { "requires": {
"bn.js": "^4.10.0", "bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.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", "resolved": "https://registry.npmjs.org/eth-sig-util/-/eth-sig-util-1.4.2.tgz",
"integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=", "integrity": "sha1-jZWCAsftuq6Dlwf7pvCf8ydgYhA=",
"requires": { "requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1" "ethereumjs-util": "^5.1.1"
}, },
"dependencies": { "dependencies": {
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "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": { "requires": {
"bn.js": "^4.10.0", "bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0" "ethereumjs-util": "^5.0.0"
@ -8736,12 +8743,14 @@
"integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==", "integrity": "sha512-lxHZOQspexk3DaGj4RBbWy4C/qNOWRnxpaJzNnYD3WEmC8shcJ4tHs7Xv878rzvILfJnSFSCCiKQhng1m80oBQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"ethereumjs-abi": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7",
"ethereumjs-util": "^5.1.1" "ethereumjs-util": "^5.1.1"
}, },
"dependencies": { "dependencies": {
"ethereumjs-abi": { "ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#00ba8463a7f7a67fcad737ff9c2ebd95643427f7", "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": { "requires": {
"bn.js": "^4.10.0", "bn.js": "^4.10.0",
"ethereumjs-util": "^5.0.0" "ethereumjs-util": "^5.0.0"
@ -8753,6 +8762,7 @@
"version": "5.2.0", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz", "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz",
"integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==", "integrity": "sha512-CJAKdI0wgMbQFLlLRtZKGcy/L6pzVRgelIZqRqNbuVFM3K9VEnyfbcvz0ncWMRNCe4kaHWjwRYQcYMucmwsnWA==",
"dev": true,
"requires": { "requires": {
"bn.js": "^4.11.0", "bn.js": "^4.11.0",
"create-hash": "^1.1.2", "create-hash": "^1.1.2",
@ -30717,6 +30727,7 @@
"version": "3.1.5", "version": "3.1.5",
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
"dev": true,
"requires": { "requires": {
"is-typedarray": "^1.0.0" "is-typedarray": "^1.0.0"
} }
@ -31740,6 +31751,7 @@
"resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz", "resolved": "https://registry.npmjs.org/web3/-/web3-0.20.3.tgz",
"integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=", "integrity": "sha1-yqRDc9yIFayHZ73ba6cwc5ZMqos=",
"requires": { "requires": {
"bignumber.js": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934",
"crypto-js": "^3.1.4", "crypto-js": "^3.1.4",
"utf8": "^2.1.1", "utf8": "^2.1.1",
"xhr2": "*", "xhr2": "*",
@ -31748,7 +31760,7 @@
"dependencies": { "dependencies": {
"bignumber.js": { "bignumber.js": {
"version": "git+https://github.com/frozeman/bignumber.js-nolookahead.git#57692b3ecfc98bbdd6b3a516cb2353652ea49934", "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, "dev": true,
"requires": { "requires": {
"underscore": "1.8.3", "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": { "dependencies": {
"underscore": { "underscore": {
@ -32258,7 +32271,8 @@
}, },
"websocket": { "websocket": {
"version": "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2", "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": { "requires": {
"debug": "^2.2.0", "debug": "^2.2.0",
"nan": "^2.3.3", "nan": "^2.3.3",
@ -33623,7 +33637,8 @@
"yaeti": { "yaeti": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz", "resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
"integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=" "integrity": "sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc=",
"dev": true
}, },
"yallist": { "yallist": {
"version": "2.1.2", "version": "2.1.2",

@ -98,6 +98,7 @@
"debounce-stream": "^2.0.0", "debounce-stream": "^2.0.0",
"deep-extend": "^0.5.1", "deep-extend": "^0.5.1",
"detect-node": "^2.0.3", "detect-node": "^2.0.3",
"detectrtc": "^1.3.6",
"disc": "^1.3.2", "disc": "^1.3.2",
"dnode": "^1.2.2", "dnode": "^1.2.2",
"end-of-stream": "^1.1.0", "end-of-stream": "^1.1.0",

@ -12,6 +12,7 @@ const { fetchLocale } = require('../i18n-helper')
const log = require('loglevel') const log = require('loglevel')
const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums') const { ENVIRONMENT_TYPE_NOTIFICATION } = require('../../app/scripts/lib/enums')
const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util') const { hasUnconfirmedTransactions } = require('./helpers/confirm-transaction/util')
const WebcamUtils = require('../lib/webcam-utils')
var actions = { var actions = {
_setBackgroundConnection: _setBackgroundConnection, _setBackgroundConnection: _setBackgroundConnection,
@ -127,7 +128,8 @@ var actions = {
SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE', SHOW_CONF_TX_PAGE: 'SHOW_CONF_TX_PAGE',
SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE', SHOW_CONF_MSG_PAGE: 'SHOW_CONF_MSG_PAGE',
SET_CURRENT_FIAT: 'SET_CURRENT_FIAT', SET_CURRENT_FIAT: 'SET_CURRENT_FIAT',
setCurrentCurrency: setCurrentCurrency, showQrScanner,
setCurrentCurrency,
setCurrentAccountTab, setCurrentAccountTab,
// account detail screen // account detail screen
SHOW_SEND_PAGE: 'SHOW_SEND_PAGE', 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) { function setCurrentCurrency (currencyCode) {
return (dispatch) => { return (dispatch) => {
dispatch(actions.showLoadingIndication()) dispatch(actions.showLoadingIndication())

@ -38,5 +38,10 @@
font-size: 14px; font-size: 14px;
padding: 15px; padding: 15px;
} }
&__status.error {
padding: 60px 45px 80px;
font-size: 16px;
}
} }

@ -3,16 +3,14 @@ import PropTypes from 'prop-types'
import { BrowserQRCodeReader } from '@zxing/library' import { BrowserQRCodeReader } from '@zxing/library'
import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars import adapter from 'webrtc-adapter' // eslint-disable-line import/no-nodejs-modules, no-unused-vars
import Spinner from '../../spinner' import Spinner from '../../spinner'
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums') import WebcamUtils from '../../../../lib/webcam-utils'
const { getEnvironmentType } = require('../../../../../app/scripts/lib/util')
const {
SEND_ROUTE,
} = require('../../../routes')
export default class QrScanner extends Component { export default class QrScanner extends Component {
static propTypes = { static propTypes = {
hideModal: PropTypes.func.isRequired, hideModal: PropTypes.func.isRequired,
qrCodeDetected: PropTypes.func, qrCodeDetected: PropTypes.func,
error: PropTypes.bool,
errorType: PropTypes.string,
} }
static contextTypes = { static contextTypes = {
@ -21,46 +19,65 @@ export default class QrScanner extends Component {
constructor (props, context) { constructor (props, context) {
super(props) 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 = { this.state = {
ready: false, ready: false,
msg: context.t('accessingYourCamera'), msg: initialMsg,
} }
this.scanning = false
this.codeReader = null this.codeReader = null
this.permissionChecker = null
this.notAllowed = false this.notAllowed = false
} }
componentDidMount () { componentDidMount () {
this.initCamera()
}
if (!this.scanning) { async checkPermisisions () {
this.scanning = true 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)
this.initCamera() } else {
// Keep checking for permissions
this.permissionChecker = setTimeout(_ => {
console.log('[QR-SCANNER]: time to check again!')
this.checkPermisisions()
}, 1000)
} }
} }
componentWillUnmount () { componentWillUnmount () {
clearTimeout(this.permissionChecker)
if (this.codeReader) {
this.codeReader.reset() this.codeReader.reset()
} }
}
initCamera () { initCamera () {
this.codeReader = new BrowserQRCodeReader() this.codeReader = new BrowserQRCodeReader()
this.codeReader.getVideoInputDevices() this.codeReader.getVideoInputDevices()
.then(videoInputDevices => { .then(videoInputDevices => {
clearTimeout(this.permissionChecker)
setTimeout(_ => { this.checkPermisisions()
if (!this.notAllowed) { this.codeReader.decodeFromInputVideoDevice(undefined, 'video')
this.setState({
ready: true,
msg: this.context.t('scanInstructions')})
}
}, 2000)
this.codeReader.decodeFromInputVideoDevice(videoInputDevices[0].deviceId, 'video')
.then(content => { .then(content => {
const result = this.parseContent(content.text) const result = this.parseContent(content.text)
if (result.type !== 'unknown') { if (result.type !== 'unknown') {
this.props.qrCodeDetected(result) this.props.qrCodeDetected(result)
@ -70,18 +87,14 @@ export default class QrScanner extends Component {
} }
}) })
.catch(err => { .catch(err => {
this.notAllowed = true
if (err && err.name === 'NotAllowedError') { 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 => { }).catch(err => {
console.error('QR-SCANNER: getVideoInputDevices threw an exception: ', err) console.error('[QR-SCANNER]: getVideoInputDevices threw an exception: ', err)
}) })
} }
@ -103,20 +116,12 @@ export default class QrScanner extends Component {
stopAndClose = () => { stopAndClose = () => {
this.codeReader.reset() this.codeReader.reset()
this.scanning = false
this.setState({ ready: false }) this.setState({ ready: false })
this.props.hideModal() this.props.hideModal()
} }
render () { renderVideo () {
const { t } = this.context
return ( return (
<div className="qr-scanner">
<div className="qr-scanner__title">
{ `${t('scanQrCode')}?` }
</div>
<div className="qr-scanner__content">
<div className={'qr-scanner__content__video-wrapper'}> <div className={'qr-scanner__content__video-wrapper'}>
<video <video
id="video" id="video"
@ -126,8 +131,21 @@ export default class QrScanner extends Component {
/> />
{ !this.state.ready ? <Spinner color={'#F7C06C'} /> : null} { !this.state.ready ? <Spinner color={'#F7C06C'} /> : null}
</div> </div>
)
}
render () {
const { t } = this.context
return (
<div className="qr-scanner">
<div className="qr-scanner__title">
{ `${t('scanQrCode')}` }
</div>
<div className="qr-scanner__content">
{ !this.props.error ? this.renderVideo() : null}
</div> </div>
<div className="qr-scanner__status"> <div className={`qr-scanner__status ${this.props.error ? 'error' : ''}`}>
{this.state.msg} {this.state.msg}
</div> </div>
</div> </div>

@ -3,6 +3,13 @@ import QrScanner from './qr-scanner.component'
const { hideModal, qrCodeDetected } = require('../../../actions') 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 => { const mapDispatchToProps = dispatch => {
return { return {
hideModal: () => dispatch(hideModal()), hideModal: () => dispatch(hideModal()),
@ -10,4 +17,4 @@ const mapDispatchToProps = dispatch => {
} }
} }
export default connect(null, mapDispatchToProps)(QrScanner) export default connect(mapStateToProps, mapDispatchToProps)(QrScanner)

@ -179,6 +179,11 @@ export default class SendTransactionScreen extends PersistentForm {
// Show QR Scanner modal if ?scan=true // Show QR Scanner modal if ?scan=true
if (window.location.search === '?scan=true') { if (window.location.search === '?scan=true') {
this.props.scanQrCode() 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'
} }
} }

@ -28,7 +28,7 @@ import {
updateSendTokenBalance, updateSendTokenBalance,
updateGasData, updateGasData,
setGasTotal, setGasTotal,
showModal, showQrScanner,
} from '../../actions' } from '../../actions'
import { import {
resetSendState, resetSendState,
@ -38,6 +38,10 @@ import {
calcGasTotal, calcGasTotal,
} from './send.utils.js' } from './send.utils.js'
import {
SEND_ROUTE,
} from '../../routes'
module.exports = compose( module.exports = compose(
withRouter, withRouter,
connect(mapStateToProps, mapDispatchToProps) connect(mapStateToProps, mapDispatchToProps)
@ -93,7 +97,7 @@ function mapDispatchToProps (dispatch) {
}, },
updateSendErrors: newError => dispatch(updateSendErrors(newError)), updateSendErrors: newError => dispatch(updateSendErrors(newError)),
resetSendState: () => dispatch(resetSendState()), resetSendState: () => dispatch(resetSendState()),
scanQrCode: () => dispatch(showModal({ name: 'QR_SCANNER' })), scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
} }
} }

@ -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
Loading…
Cancel
Save