diff --git a/app/_locales/en/messages.json b/app/_locales/en/messages.json index 1b0183c92..62ec4ce37 100644 --- a/app/_locales/en/messages.json +++ b/app/_locales/en/messages.json @@ -2,6 +2,9 @@ "accept": { "message": "Accept" }, + "accessingYourCamera": { + "message": "Accesing your camera..." + }, "account": { "message": "Account" }, @@ -656,6 +659,12 @@ "notStarted": { "message": "Not Started" }, + "noWebcamFoundTitle": { + "message": "Webcam not found" + }, + "noWebcamFound": { + "message": "Your computer's webcam was not found. Please try again." + }, "oldUI": { "message": "Old UI" }, @@ -940,6 +949,12 @@ "info": { "message": "Info" }, + "scanInstructions": { + "message": "Place the QR code in front of your camera" + }, + "scanQrCode": { + "message": "Scan QR Code" + }, "shapeshiftBuy": { "message": "Buy with Shapeshift" }, @@ -1059,6 +1074,9 @@ "message": "We had trouble loading your token balances. You can view them ", "description": "Followed by a link (here) to view token balances" }, + "tryAgain": { + "message": "Try again" + }, "twelveWords": { "message": "These 12 words are the only way to restore your MetaMask accounts.\nSave them somewhere safe and secret." }, @@ -1089,6 +1107,15 @@ "unknownNetworkId": { "message": "Unknown network ID" }, + "unknownQrCode": { + "message": "Error: We couldn't identify that QR code" + }, + "unknownCameraErrorTitle": { + "message": "Ooops! Something went wrong...." + }, + "unknownCameraError": { + "message": "There was an error while trying to access you camera. Please try again..." + }, "unlock": { "message": "Unlock" }, @@ -1135,6 +1162,9 @@ "whatsThis": { "message": "What's this?" }, + "youNeedToAllowCameraAccess": { + "message": "You need to allow camera access to use this feature." + }, "yourSigRequested": { "message": "Your signature is being requested" }, diff --git a/app/images/webcam.svg b/app/images/webcam.svg new file mode 100644 index 000000000..4b9b58148 --- /dev/null +++ b/app/images/webcam.svg @@ -0,0 +1,18 @@ + + \ No newline at end of file diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index b7496f318..e0a2b0061 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -198,6 +198,6 @@ function blacklistedDomainCheck () { */ function redirectToPhishingWarning () { console.log('MetaMask - routing to Phishing Warning component') - let extensionURL = extension.runtime.getURL('phishing.html') + const extensionURL = extension.runtime.getURL('phishing.html') window.location.href = extensionURL } diff --git a/app/scripts/lib/enums.js b/app/scripts/lib/enums.js index 0a3afca47..c6d57a1bc 100644 --- a/app/scripts/lib/enums.js +++ b/app/scripts/lib/enums.js @@ -2,8 +2,19 @@ const ENVIRONMENT_TYPE_POPUP = 'popup' const ENVIRONMENT_TYPE_NOTIFICATION = 'notification' const ENVIRONMENT_TYPE_FULLSCREEN = 'fullscreen' +const PLATFORM_BRAVE = 'Brave' +const PLATFORM_CHROME = 'Chrome' +const PLATFORM_EDGE = 'Edge' +const PLATFORM_FIREFOX = 'Firefox' +const PLATFORM_OPERA = 'Opera' + module.exports = { ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN, + PLATFORM_BRAVE, + PLATFORM_CHROME, + PLATFORM_EDGE, + PLATFORM_FIREFOX, + PLATFORM_OPERA, } diff --git a/app/scripts/lib/util.js b/app/scripts/lib/util.js index 51e9036cc..d7423f2ad 100644 --- a/app/scripts/lib/util.js +++ b/app/scripts/lib/util.js @@ -5,6 +5,11 @@ const { ENVIRONMENT_TYPE_POPUP, ENVIRONMENT_TYPE_NOTIFICATION, ENVIRONMENT_TYPE_FULLSCREEN, + PLATFORM_FIREFOX, + PLATFORM_OPERA, + PLATFORM_CHROME, + PLATFORM_EDGE, + PLATFORM_BRAVE, } = require('./enums') /** @@ -37,6 +42,29 @@ const getEnvironmentType = (url = window.location.href) => { } } +/** + * Returns the platform (browser) where the extension is running. + * + * @returns {string} the platform ENUM + * + */ +const getPlatform = _ => { + const ua = navigator.userAgent + if (ua.search('Firefox') !== -1) { + return PLATFORM_FIREFOX + } else { + if (window && window.chrome && window.chrome.ipcRenderer) { + return PLATFORM_BRAVE + } else if (ua.search('Edge') !== -1) { + return PLATFORM_EDGE + } else if (ua.search('OPR') !== -1) { + return PLATFORM_OPERA + } else { + return PLATFORM_CHROME + } + } +} + /** * Checks whether a given balance of ETH, represented as a hex string, is sufficient to pay a value plus a gas fee * @@ -100,6 +128,7 @@ function BnMultiplyByFraction (targetBN, numerator, denominator) { } module.exports = { + getPlatform, getStack, getEnvironmentType, sufficientBalance, diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 0803164e8..71b162dd0 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -24,8 +24,13 @@ class ExtensionPlatform { return extension.runtime.getManifest().version } - openExtensionInBrowser (route = null) { + openExtensionInBrowser (route = null, queryString = null) { let extensionURL = extension.runtime.getURL('home.html') + + if (queryString) { + extensionURL += `?${queryString}` + } + if (route) { extensionURL += `#${route}` } diff --git a/old-ui/app/components/transaction-list-item.js b/old-ui/app/components/transaction-list-item.js index 015ef6ba6..6ecf7d193 100644 --- a/old-ui/app/components/transaction-list-item.js +++ b/old-ui/app/components/transaction-list-item.js @@ -40,7 +40,7 @@ TransactionListItem.prototype.showRetryButton = function () { const currentNonce = txParams.nonce const currentNonceTxs = transactions.filter(tx => tx.txParams.nonce === currentNonce) const currentNonceSubmittedTxs = currentNonceTxs.filter(tx => tx.status === 'submitted') - const currentSubmittedTxs = transactions.filter(tx => tx.status === 'submitted') + const currentSubmittedTxs = transactions.filter(tx => tx.status === 'submitted') const lastSubmittedTxWithCurrentNonce = currentNonceSubmittedTxs[0] const currentTxIsLatestWithNonce = lastSubmittedTxWithCurrentNonce && lastSubmittedTxWithCurrentNonce.id === transaction.id diff --git a/package-lock.json b/package-lock.json index 99bb67f65..6bee47cd6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1623,6 +1623,15 @@ "@types/react": "*" } }, + "@zxing/library": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@zxing/library/-/library-0.7.0.tgz", + "integrity": "sha512-VJ1cJaCWVF8MspnuyaZKGKlrSQLqQ5usgSap8uuCAvWGQ6W6OwN1NeGvnjhT+9hmnwkHK8XjaflvzaDBC7nKnw==", + "requires": { + "text-encoding": "^0.6.4", + "ts-custom-error": "^2.2.1" + } + }, "JSONStream": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", @@ -4624,9 +4633,9 @@ }, "dependencies": { "electron-to-chromium": { - "version": "1.3.52", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.52.tgz", - "integrity": "sha1-0tnxJwuko7lnuDHEDvcftNmrXOA=" + "version": "1.3.55", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.55.tgz", + "integrity": "sha1-8VDhCyC3fZ1Br8yjEu/gw7Gn/c4=" } } }, @@ -4954,9 +4963,9 @@ "integrity": "sha1-MN/YMAnVcE8C3/s3clBo7RKjZrs=" }, "caniuse-lite": { - "version": "1.0.30000865", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz", - "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==" + "version": "1.0.30000874", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000874.tgz", + "integrity": "sha512-29nr1EPiHwrJTAHHsEmTt2h+55L8j2GNFdAcYPlRy2NX6iFz7ZZiepVI7kP/QqlnHLq3KvfWpbmGa0d063U09w==" }, "capture-stack-trace": { "version": "1.0.0", @@ -7045,6 +7054,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", @@ -8210,7 +8224,7 @@ "dependencies": { "async-eventemitter": { "version": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", - "from": "github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", + "from": "async-eventemitter@github:ahultgren/async-eventemitter#fa06e39e56786ba541c180061dbf2c0a5bbf951c", "requires": { "async": "^2.4.0" } @@ -8258,7 +8272,7 @@ } }, "eth-contract-metadata": { - "version": "github:MetaMask/eth-contract-metadata#f1a59032297fc00b9804de4674667f2955158757", + "version": "github:MetaMask/eth-contract-metadata#2da362052a312dc6c72a7eec116abf6284664f50", "from": "github:MetaMask/eth-contract-metadata#master" }, "eth-ens-namehash": { @@ -13227,9 +13241,9 @@ "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==" }, "node-sass": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.2.tgz", - "integrity": "sha512-LdxoJLZutx0aQXHtWIYwJKMj+9pTjneTcLWJgzf2XbGu0q5pRNqW5QvFCEdm3mc5rJOdru/mzln5d0EZLacf6g==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.9.1.tgz", + "integrity": "sha512-m6H1I6cHXsHsJ7BIWdnJsz9S9gVMyh+/H2cOTXgl2/2WqyyWlBcl4PHJcqrXo5RZVCfCUFqOtjPN0+0XbVHR5Q==", "requires": { "async-foreach": "^0.1.3", "chalk": "^1.1.1", @@ -26891,6 +26905,14 @@ "nearley": "^2.7.10" } }, + "rtcpeerconnection-shim": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.13.tgz", + "integrity": "sha512-Xz4zQLZNs9lFBvqbaHGIjLWtyZ1V82ec5r+WNEo7NlIx3zF5M3ytn9mkkfYeZmpE032cNg3Vvf0rP8kNXUNd9w==", + "requires": { + "sdp": "^2.6.0" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -27209,6 +27231,11 @@ } } }, + "sdp": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.8.0.tgz", + "integrity": "sha512-wRSES07rAwKWAR7aev9UuClT7kdf9ZTdeUK5gTgHue9vlhs19Fbm3ccNEGJO4y2IitH4/JzS4sdzyPl6H2KQLw==" + }, "secp256k1": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.4.0.tgz", @@ -29897,8 +29924,7 @@ "text-encoding": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true + "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=" }, "text-table": { "version": "0.2.0", @@ -30345,6 +30371,11 @@ } } }, + "ts-custom-error": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-2.2.1.tgz", + "integrity": "sha512-lHKZtU+PXkVuap6nlFZybIAFLUO8B3jbCs1VynBL8AUSAHfeG6HpztcBTDRp5I+fN5820N9kGg+eTIvr+le2yg==" + }, "tslib": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", @@ -32926,6 +32957,15 @@ } } }, + "webrtc-adapter": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-6.3.2.tgz", + "integrity": "sha512-7pFMXpZCka7ScIQyk8Wo+fOr3OlKLtGd6YHqkHVT74zerpY2Siyds8sxsmkE0bNqsi/J1b0vDzN7WpB34dQzAA==", + "requires": { + "rtcpeerconnection-shim": "^1.2.10", + "sdp": "^2.7.0" + } + }, "websocket": { "version": "1.0.26", "resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.26.tgz", diff --git a/package.json b/package.json index 16aa143ba..29295a65b 100644 --- a/package.json +++ b/package.json @@ -75,6 +75,7 @@ }, "dependencies": { "@material-ui/core": "1.0.0", + "@zxing/library": "^0.7.0", "abi-decoder": "^1.0.9", "asmcrypto.js": "0.22.0", "async": "^2.5.0", @@ -97,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", @@ -206,6 +208,7 @@ "web3": "^0.20.1", "web3-provider-engine": "^14.0.5", "web3-stream-provider": "^3.0.1", + "webrtc-adapter": "^6.3.0", "xtend": "^4.0.1" }, "devDependencies": { diff --git a/ui/app/actions.js b/ui/app/actions.js index 9edb3511a..bd5d25327 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, @@ -33,6 +34,8 @@ var actions = { ALERT_CLOSE: 'UI_ALERT_CLOSE', showAlert: showAlert, hideAlert: hideAlert, + QR_CODE_DETECTED: 'UI_QR_CODE_DETECTED', + qrCodeDetected, // network dropdown open NETWORK_DROPDOWN_OPEN: 'UI_NETWORK_DROPDOWN_OPEN', NETWORK_DROPDOWN_CLOSE: 'UI_NETWORK_DROPDOWN_CLOSE', @@ -125,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 +727,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()) @@ -1809,6 +1835,17 @@ function hideAlert () { } } +/** + * This action will receive two types of values via qrCodeData + * an object with the following structure {type, values} + * or null (used to clear the previous value) + */ +function qrCodeDetected (qrCodeData) { + return { + type: actions.QR_CODE_DETECTED, + value: qrCodeData, + } +} function showLoadingIndication (message) { return { diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index b9f99b3d1..f538fd555 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -27,6 +27,7 @@ function EnsInput () { } EnsInput.prototype.onChange = function (recipient) { + const network = this.props.network const networkHasEnsSupport = getNetworkEnsSupport(network) @@ -54,6 +55,7 @@ EnsInput.prototype.render = function () { const opts = extend(props, { list: 'addresses', onChange: this.onChange.bind(this), + qrScanner: true, }) return h('div', { style: { width: '100%', position: 'relative' }, diff --git a/ui/app/components/modals/index.scss b/ui/app/components/modals/index.scss index e198cca44..0acccf172 100644 --- a/ui/app/components/modals/index.scss +++ b/ui/app/components/modals/index.scss @@ -1,5 +1,7 @@ @import './customize-gas/index'; +@import './qr-scanner/index'; + .modal-container { width: 100%; height: 100%; diff --git a/ui/app/components/modals/modal.js b/ui/app/components/modals/modal.js index f59825ed1..5dda50e52 100644 --- a/ui/app/components/modals/modal.js +++ b/ui/app/components/modals/modal.js @@ -21,6 +21,7 @@ const CustomizeGasModal = require('../customize-gas-modal') const NotifcationModal = require('./notification-modal') const ConfirmResetAccount = require('./confirm-reset-account') const ConfirmRemoveAccount = require('./confirm-remove-account') +const QRScanner = require('./qr-scanner') const TransactionConfirmed = require('./transaction-confirmed') const WelcomeBeta = require('./welcome-beta') const Notification = require('./notification') @@ -346,6 +347,18 @@ const MODALS = { borderRadius: '8px', }, }, + QR_SCANNER: { + contents: h(QRScanner), + mobileModalStyle: { + ...modalContainerMobileStyle, + }, + laptopModalStyle: { + ...modalContainerLaptopStyle, + }, + contentStyle: { + borderRadius: '8px', + }, + }, DEFAULT: { contents: [], diff --git a/ui/app/components/modals/qr-scanner/index.js b/ui/app/components/modals/qr-scanner/index.js new file mode 100644 index 000000000..470dee1f4 --- /dev/null +++ b/ui/app/components/modals/qr-scanner/index.js @@ -0,0 +1,2 @@ +import QrScanner from './qr-scanner.container' +module.exports = QrScanner diff --git a/ui/app/components/modals/qr-scanner/index.scss b/ui/app/components/modals/qr-scanner/index.scss new file mode 100644 index 000000000..6fa81d51f --- /dev/null +++ b/ui/app/components/modals/qr-scanner/index.scss @@ -0,0 +1,83 @@ +.qr-scanner { + width: 100%; + height: 100%; + background-color: #fff; + display: flex; + flex-flow: column; + border-radius: 8px; + + &__title { + font-size: 1.5rem; + font-weight: 500; + padding: 16px 0; + text-align: center; + } + + &__content { + padding-left: 20px; + padding-right: 20px; + + &__video-wrapper { + overflow: hidden; + width: 100%; + height: 275px; + display: flex; + align-items: center; + justify-content: center; + + video { + transform: scaleX(-1); + width: auto; + height: 275px; + } + } + } + + &__status { + text-align: center; + font-size: 14px; + padding: 15px; + } + + &__image { + font-size: 1.5rem; + font-weight: 500; + padding: 16px 0 0; + text-align: center; + } + + &__error { + text-align: center; + font-size: 16px; + padding: 15px; + } + + &__footer { + padding: 20px; + flex-direction: row; + display: flex; + + button { + margin-right: 15px; + } + + button:last-of-type { + margin-right: 0; + background-color: #009eec; + border: none; + color: #fff; + } + } + + &__close::after { + content: '\00D7'; + font-size: 35px; + color: #9b9b9b; + position: absolute; + top: 4px; + right: 20px; + cursor: pointer; + font-weight: 300; + } +} + diff --git a/ui/app/components/modals/qr-scanner/qr-scanner.component.js b/ui/app/components/modals/qr-scanner/qr-scanner.component.js new file mode 100644 index 000000000..cb8d07d83 --- /dev/null +++ b/ui/app/components/modals/qr-scanner/qr-scanner.component.js @@ -0,0 +1,216 @@ +import React, { Component } from 'react' +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' +import WebcamUtils from '../../../../lib/webcam-utils' +import PageContainerFooter from '../../page-container/page-container-footer/page-container-footer.component' + +export default class QrScanner extends Component { + static propTypes = { + hideModal: PropTypes.func.isRequired, + qrCodeDetected: PropTypes.func, + scanQrCode: PropTypes.func, + error: PropTypes.bool, + errorType: PropTypes.string, + } + + static contextTypes = { + t: PropTypes.func, + } + + constructor (props, context) { + super(props) + + this.state = { + ready: false, + msg: context.t('accessingYourCamera'), + } + this.codeReader = null + this.permissionChecker = null + this.needsToReinit = false + + // Clear pre-existing qr code data before scanning + this.props.qrCodeDetected(null) + } + + componentDidMount () { + 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'), + }) + if (this.needsToReinit) { + this.initCamera() + this.needsToReinit = false + } + }, 2000) + } else { + // Keep checking for permissions + this.permissionChecker = setTimeout(_ => { + this.checkPermisisions() + }, 1000) + } + } + + componentWillUnmount () { + clearTimeout(this.permissionChecker) + if (this.codeReader) { + this.codeReader.reset() + } + } + + initCamera () { + this.codeReader = new BrowserQRCodeReader() + this.codeReader.getVideoInputDevices() + .then(videoInputDevices => { + 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) + this.stopAndClose() + } else { + this.setState({msg: this.context.t('unknownQrCode')}) + } + }) + .catch(err => { + if (err && err.name === 'NotAllowedError') { + this.setState({msg: this.context.t('youNeedToAllowCameraAccess')}) + clearTimeout(this.permissionChecker) + this.needsToReinit = true + this.checkPermisisions() + } + }) + }).catch(err => { + console.error('[QR-SCANNER]: getVideoInputDevices threw an exception: ', err) + }) + } + + parseContent (content) { + let type = 'unknown' + let values = {} + + // Here we could add more cases + // To parse other type of links + // For ex. EIP-681 (https://eips.ethereum.org/EIPS/eip-681) + + + // Ethereum address links - fox ex. ethereum:0x.....1111 + if (content.split('ethereum:').length > 1) { + + type = 'address' + values = {'address': content.split('ethereum:')[1] } + + // Regular ethereum addresses - fox ex. 0x.....1111 + } else if (content.substring(0, 2).toLowerCase() === '0x') { + + type = 'address' + values = {'address': content } + + } + return {type, values} + } + + + stopAndClose = () => { + if (this.codeReader) { + this.codeReader.reset() + } + this.setState({ ready: false }) + this.props.hideModal() + } + + tryAgain = () => { + // close the modal + this.stopAndClose() + // wait for the animation and try again + setTimeout(_ => { + this.props.scanQrCode() + }, 1000) + } + + renderVideo () { + return ( +