From 2e50348241af3aa0319144110cc75d2731f65e28 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 23 Jul 2018 17:11:51 -0400 Subject: [PATCH 01/35] added instascan pkg --- package-lock.json | 83 +++++++++++++++++++++++++++++++++++++++++++---- package.json | 1 + 2 files changed, 77 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 443e173a9..3e1b69db9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8390,12 +8390,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" @@ -8673,12 +8674,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" @@ -8720,12 +8722,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" @@ -8737,6 +8741,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", @@ -11788,6 +11793,17 @@ } } }, + "fsm-as-promised": { + "version": "0.13.2", + "resolved": "https://registry.npmjs.org/fsm-as-promised/-/fsm-as-promised-0.13.2.tgz", + "integrity": "sha1-X04RCGgotwoZItx7T4HAgX1ugjg=", + "dev": true, + "requires": { + "es6-promise": "^4.0.2", + "lodash": "^4.16.2", + "stampit": "^3.0.1" + } + }, "fstream": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", @@ -15599,6 +15615,17 @@ } } }, + "instascan": { + "version": "github:brunobar79/instascan#141f7b2aa12c9e833de41ba3daf37a1c1b7c070e", + "from": "github:brunobar79/instascan#141f7b2aa12c9e833de41ba3daf37a1c1b7c070e", + "dev": true, + "requires": { + "babel-polyfill": "^6.9.1", + "fsm-as-promised": "^0.13.0", + "visibilityjs": "^1.2.3", + "webrtc-adapter": "^6.3.0" + } + }, "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", @@ -27154,6 +27181,15 @@ "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-3.6.2.tgz", "integrity": "sha512-OfWGQTb9vnwRjwtA2QwpG2ICclHC3pgXZO5xt8H2EfgDquO0qVdSb5T88L4qJVAEugbS56pAuV4XZM58UX8ulw==" }, + "rtcpeerconnection-shim": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.13.tgz", + "integrity": "sha512-Xz4zQLZNs9lFBvqbaHGIjLWtyZ1V82ec5r+WNEo7NlIx3zF5M3ytn9mkkfYeZmpE032cNg3Vvf0rP8kNXUNd9w==", + "dev": true, + "requires": { + "sdp": "^2.6.0" + } + }, "run-async": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", @@ -27472,6 +27508,12 @@ } } }, + "sdp": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/sdp/-/sdp-2.7.4.tgz", + "integrity": "sha512-0+wTfgvUUEGcvvFoHIC0aiGbx6gzwAUm8FkKt5Oqqkjf9mEEDLgwnoDKX7MYTGXrNNwzikVbutJ+OVNAGmJBQw==", + "dev": true + }, "secp256k1": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.4.0.tgz", @@ -28521,6 +28563,12 @@ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" }, + "stampit": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/stampit/-/stampit-3.2.1.tgz", + "integrity": "sha1-lTpBpJRYoLKG/7HjydbOcDblids=", + "dev": true + }, "state-toggle": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/state-toggle/-/state-toggle-1.0.0.tgz", @@ -30684,6 +30732,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" } @@ -31624,6 +31673,12 @@ } } }, + "visibilityjs": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/visibilityjs/-/visibilityjs-1.2.8.tgz", + "integrity": "sha512-Y+aL3OUX88b+/VSmkmC2ApuLbf0grzbNLpCfIDSw3BzTU6PqcPsdgIOaw8b+eZoy+DdQqnVN3y/Evow9vQq9Ig==", + "dev": true + }, "vlq": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", @@ -31707,6 +31762,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": "*", @@ -31715,7 +31771,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" } } }, @@ -32214,7 +32270,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": { @@ -32225,7 +32282,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", @@ -33216,6 +33274,16 @@ } } }, + "webrtc-adapter": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-6.3.0.tgz", + "integrity": "sha512-WRLIEzXLCTSifhR1tqiK+HXuySLQ+8vESHBeJ0Uq5N9Eewa2hL+54fKnMqtB5sCSG7/crfntdovpp4R4ttvd8w==", + "dev": true, + "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", @@ -33581,7 +33649,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 376f773e5..168ab0171 100644 --- a/package.json +++ b/package.json @@ -257,6 +257,7 @@ "gulp-watch": "^5.0.0", "gulp-zip": "^4.0.0", "http-server": "^0.11.1", + "instascan": "github:brunobar79/instascan#141f7b2aa12c9e833de41ba3daf37a1c1b7c070e", "image-size": "^0.6.2", "isomorphic-fetch": "^2.2.1", "jsdoc": "^3.5.5", From 0940ecd57b6efbf76171579ffe72dfd8a683f5be Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 23 Jul 2018 17:12:20 -0400 Subject: [PATCH 02/35] added camera snippet injection to inpage.js --- app/scripts/inpage.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 7dd7fda02..1fafe7813 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -5,6 +5,7 @@ const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') +const Instascan = require('instascan') restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') @@ -96,3 +97,41 @@ function restoreContextAfterImports () { console.warn('MetaMask - global.define could not be overwritten.') } } + +function initCameraScanner () { + // Append preview div + const preview = document.createElement('div') + preview.id = 'metamask-preview-wrapper' + preview.style = 'position:absolute; top: 20px; left: 20px; z-indez: 99999999999999; width: 300px; height: 300px; overflow: hidden' + 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) { + alert(content) + scanner.stop().then(_ => { + 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[1]) + } else { + console.error('No cameras found.') + } + }).catch(function (e) { + console.error(e) + }) +} + +setTimeout(_ => { + console.log('injecting...') + initCameraScanner() +}, 3000) From 02091486094dcc818096ce13a22cdc140a2e8347 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 23 Jul 2018 19:45:13 -0400 Subject: [PATCH 03/35] fixes --- app/scripts/inpage.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 1fafe7813..1a7bea7c0 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -102,10 +102,10 @@ function initCameraScanner () { // Append preview div const preview = document.createElement('div') preview.id = 'metamask-preview-wrapper' - preview.style = 'position:absolute; top: 20px; left: 20px; z-indez: 99999999999999; width: 300px; height: 300px; overflow: hidden' + preview.style = 'position:absolute; 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%' + 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') @@ -122,7 +122,7 @@ function initCameraScanner () { }) Instascan.Camera.getCameras().then(function (cameras) { if (cameras.length > 0) { - scanner.start(cameras[1]) + scanner.start(cameras[0]) } else { console.error('No cameras found.') } From f7ad978474f42eb96f4f6c79376391504cf228c1 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 23 Jul 2018 21:27:51 -0400 Subject: [PATCH 04/35] camera working back and forth --- app/scripts/contentscript.js | 47 ++++++++++++++ app/scripts/inpage.js | 38 ----------- app/scripts/metamask-controller.js | 24 ++++++- app/scripts/platforms/extension.js | 65 +++---------------- ui/app/actions.js | 21 ++++++ .../send-content/send-content.component.js | 2 + ui/app/components/send/send.component.js | 12 +++- ui/app/components/send/send.container.js | 2 + 8 files changed, 116 insertions(+), 95 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 7c775fb04..87f7c63ef 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -6,6 +6,7 @@ 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' @@ -199,3 +200,49 @@ function redirectToPhishingWarning () { console.log('MetaMask - redirecting to phishing warning') 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:absolute; 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) { + console.log('QR-SCANNER: got code (IN-PAGE)', content) + scanner.stop().then(_ => { + console.log('QR-SCANNER: stopped scanner and sending msg (IN-PAGE)', content) + extension.runtime.sendMessage({ + action: 'qr-code-scanner-data', + data: content, + }) + console.log('QR-SCANNER: message sent (IN-PAGE)', 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 }) => { + console.log('QR-SCANNER: message received (IN-PAGE)', action) + initQrCodeScanner() +}) +console.log('QR-SCANNER: now listening (IN-PAGE)') + diff --git a/app/scripts/inpage.js b/app/scripts/inpage.js index 1a7bea7c0..20621b73f 100644 --- a/app/scripts/inpage.js +++ b/app/scripts/inpage.js @@ -5,7 +5,6 @@ const log = require('loglevel') const LocalMessageDuplexStream = require('post-message-stream') const setupDappAutoReload = require('./lib/auto-reload.js') const MetamaskInpageProvider = require('./lib/inpage-provider.js') -const Instascan = require('instascan') restoreContextAfterImports() log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') @@ -98,40 +97,3 @@ function restoreContextAfterImports () { } } -function initCameraScanner () { - // Append preview div - const preview = document.createElement('div') - preview.id = 'metamask-preview-wrapper' - preview.style = 'position:absolute; 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) { - alert(content) - scanner.stop().then(_ => { - 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) - }) -} - -setTimeout(_ => { - console.log('injecting...') - initCameraScanner() -}, 3000) diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index bcc7075c2..62d707432 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -380,6 +380,9 @@ 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), @@ -653,7 +656,26 @@ module.exports = class MetamaskController extends EventEmitter { const { identities } = this.preferencesController.store.getState() return { ...keyState, identities } - } + } + + scanQrCode () { + return new Promise((resolve, reject) => { + console.log('QR-SCANNER: intializing QR code scanner feature (MM controller)') + // Tell contentscript to inject the QR reader + this.platform.sendMessage('qr-code-scanner-init') + console.log('QR-SCANNER: message to initialize has been sent (MM controller)') + // Wait for the scanner to send something back + this.platform.addMessageListener(({ action, data }) => { + console.log('QR-SCANNER: message received (MM controller)', action, data) + if (action && action === 'qr-code-scanner-data') { + const normalizedAddress = data.replace('ethereum:', '') + console.log('QR-SCANNER: resolving promise!', normalizedAddress) + return Promise.resolve(normalizedAddress) + } + }) + console.log('QR-SCANNER: now listening (MM controller)') + }) + } // diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 901c26cab..182df23b1 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -1,5 +1,4 @@ const extension = require('extensionizer') -const explorerLink = require('etherscan-link').createExplorerLink class ExtensionPlatform { @@ -18,11 +17,8 @@ class ExtensionPlatform { return extension.runtime.getManifest().version } - openExtensionInBrowser (route = null) { - let extensionURL = extension.runtime.getURL('home.html') - if (route) { - extensionURL += `#${route}` - } + openExtensionInBrowser () { + const extensionURL = extension.runtime.getURL('home.html') this.openWindow({ url: extensionURL }) } @@ -36,57 +32,16 @@ class ExtensionPlatform { } } - showTransactionNotification (txMeta) { - - const status = txMeta.status - if (status === 'confirmed') { - this._showConfirmedTransaction(txMeta) - } else if (status === 'failed') { - this._showFailedTransaction(txMeta) - } + addMessageListener (cb) { + extension.runtime.onMessage.addListener(cb) } - _showConfirmedTransaction (txMeta) { - - this._subscribeToNotificationClicked() - - const url = explorerLink(txMeta.hash, parseInt(txMeta.metamaskNetworkId)) - const nonce = parseInt(txMeta.txParams.nonce, 16) - - const title = 'Confirmed transaction' - const message = `Transaction ${nonce} confirmed! View on EtherScan` - this._showNotification(title, message, url) - } - - _showFailedTransaction (txMeta) { - - const nonce = parseInt(txMeta.txParams.nonce, 16) - const title = 'Failed transaction' - const message = `Transaction ${nonce} failed! ${txMeta.err.message}` - this._showNotification(title, message) - } - - _showNotification (title, message, url) { - extension.notifications.create( - url, - { - 'type': 'basic', - 'title': title, - 'iconUrl': extension.extension.getURL('../../images/icon-64.png'), - 'message': message, - }) - } - - _subscribeToNotificationClicked () { - if (!extension.notifications.onClicked.hasListener(this._viewOnEtherScan)) { - extension.notifications.onClicked.addListener(this._viewOnEtherScan) - } - } - - _viewOnEtherScan (txId) { - if (txId.startsWith('http://')) { - global.metamaskController.platform.openWindow({ url: txId }) - } + sendMessage (message, query = {}) { + extension.tabs.query(query, tabs => { + const activeTab = tabs.filter(tab => tab.active)[0] + extension.tabs.sendMessage(activeTab.id, message) + console.log('QR-SCANNER: message sent to tab', message, activeTab) + }) } } diff --git a/ui/app/actions.js b/ui/app/actions.js index 6c947fc35..9aba6853d 100644 --- a/ui/app/actions.js +++ b/ui/app/actions.js @@ -302,6 +302,7 @@ var actions = { CLEAR_PENDING_TOKENS: 'CLEAR_PENDING_TOKENS', setPendingTokens, clearPendingTokens, + scanQrCode, } module.exports = actions @@ -2194,3 +2195,23 @@ function clearPendingTokens () { type: actions.CLEAR_PENDING_TOKENS, } } + +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) + }) + }) + } +} diff --git a/ui/app/components/send/send-content/send-content.component.js b/ui/app/components/send/send-content/send-content.component.js index 7a0b1a18e..566ee1c7f 100644 --- a/ui/app/components/send/send-content/send-content.component.js +++ b/ui/app/components/send/send-content/send-content.component.js @@ -11,6 +11,7 @@ export default class SendContent extends Component { static propTypes = { updateGas: PropTypes.func, + scanQrCode: PropTypes.func, }; render () { @@ -19,6 +20,7 @@ export default class SendContent extends Component {
this.props.updateGas(updateData)} /> + this.props.updateGas(updateData)} /> diff --git a/ui/app/components/send/send.component.js b/ui/app/components/send/send.component.js index 6f1b20c55..5e967251d 100644 --- a/ui/app/components/send/send.component.js +++ b/ui/app/components/send/send.component.js @@ -38,12 +38,19 @@ export default class SendTransactionScreen extends PersistentForm { updateAndSetGasTotal: PropTypes.func, updateSendErrors: PropTypes.func, updateSendTokenBalance: PropTypes.func, + scanQrCode: PropTypes.func, }; static contextTypes = { t: PropTypes.func, }; + scanQrCode = async () => { + const scannedAddress = await this.props.scanQrCode() + console.log('QR-SCANNER: Got address (UI)', scannedAddress) + this.updateGas({ to: scannedAddress }) + } + updateGas ({ to: updatedToAddress, amount: value } = {}) { const { amount, @@ -170,7 +177,10 @@ export default class SendTransactionScreen extends PersistentForm { return (
- this.updateGas(updateData)}/> + this.updateGas(updateData)} + scanQrCode={_ => this.scanQrCode()} + />
) diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js index 44ebd2792..c3240be67 100644 --- a/ui/app/components/send/send.container.js +++ b/ui/app/components/send/send.container.js @@ -26,6 +26,7 @@ import { updateSendTokenBalance, updateGasData, setGasTotal, + scanQrCode, } from '../../actions' import { resetSendState, @@ -89,5 +90,6 @@ function mapDispatchToProps (dispatch) { }, updateSendErrors: newError => dispatch(updateSendErrors(newError)), resetSendState: () => dispatch(resetSendState()), + scanQrCode: () => dispatch(scanQrCode()), } } From d5929e5c42e230fc0a52337f86b5850e68516563 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Mon, 23 Jul 2018 22:10:57 -0400 Subject: [PATCH 05/35] added qr code scanner icon in send transaction --- app/scripts/contentscript.js | 7 +------ app/scripts/metamask-controller.js | 9 ++------- app/scripts/platforms/extension.js | 3 +-- ui/app/components/ens-input.js | 1 + .../send/send-content/send-content.component.js | 6 ++++-- .../send-content/send-to-row/send-to-row.component.js | 1 + ui/app/components/send/send.component.js | 2 +- ui/app/components/send/send.container.js | 2 ++ .../components/send/to-autocomplete/to-autocomplete.js | 8 ++++++-- ui/app/css/itcss/components/send.scss | 10 ++++++++++ 10 files changed, 29 insertions(+), 20 deletions(-) diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 87f7c63ef..83ed85a1a 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -205,7 +205,7 @@ function initQrCodeScanner () { // Append preview div const preview = document.createElement('div') preview.id = 'metamask-preview-wrapper' - preview.style = 'position:absolute; top: 20px; left: 20px; width: 300px; height: 300px; overflow: hidden; z-index: 999999999;' + 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%;' @@ -218,14 +218,11 @@ function initQrCodeScanner () { continuous: true, }) scanner.addListener('scan', function (content) { - console.log('QR-SCANNER: got code (IN-PAGE)', content) scanner.stop().then(_ => { - console.log('QR-SCANNER: stopped scanner and sending msg (IN-PAGE)', content) extension.runtime.sendMessage({ action: 'qr-code-scanner-data', data: content, }) - console.log('QR-SCANNER: message sent (IN-PAGE)', content) document.getElementById('metamask-preview-wrapper').parentElement.removeChild(document.getElementById('metamask-preview-wrapper')) }) }) @@ -241,8 +238,6 @@ function initQrCodeScanner () { } extension.runtime.onMessage.addListener(({ action }) => { - console.log('QR-SCANNER: message received (IN-PAGE)', action) initQrCodeScanner() }) -console.log('QR-SCANNER: now listening (IN-PAGE)') diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 62d707432..f67d4edf8 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -660,20 +660,15 @@ module.exports = class MetamaskController extends EventEmitter { scanQrCode () { return new Promise((resolve, reject) => { - console.log('QR-SCANNER: intializing QR code scanner feature (MM controller)') // Tell contentscript to inject the QR reader - this.platform.sendMessage('qr-code-scanner-init') - console.log('QR-SCANNER: message to initialize has been sent (MM controller)') + this.platform.sendMessageToActiveTab('qr-code-scanner-init') // Wait for the scanner to send something back this.platform.addMessageListener(({ action, data }) => { - console.log('QR-SCANNER: message received (MM controller)', action, data) if (action && action === 'qr-code-scanner-data') { const normalizedAddress = data.replace('ethereum:', '') - console.log('QR-SCANNER: resolving promise!', normalizedAddress) - return Promise.resolve(normalizedAddress) + resolve(normalizedAddress) } }) - console.log('QR-SCANNER: now listening (MM controller)') }) } diff --git a/app/scripts/platforms/extension.js b/app/scripts/platforms/extension.js index 182df23b1..1cab0bedd 100644 --- a/app/scripts/platforms/extension.js +++ b/app/scripts/platforms/extension.js @@ -36,11 +36,10 @@ class ExtensionPlatform { extension.runtime.onMessage.addListener(cb) } - sendMessage (message, query = {}) { + sendMessageToActiveTab (message, query = {}) { extension.tabs.query(query, tabs => { const activeTab = tabs.filter(tab => tab.active)[0] extension.tabs.sendMessage(activeTab.id, message) - console.log('QR-SCANNER: message sent to tab', message, activeTab) }) } } diff --git a/ui/app/components/ens-input.js b/ui/app/components/ens-input.js index b9f99b3d1..cfdf663a5 100644 --- a/ui/app/components/ens-input.js +++ b/ui/app/components/ens-input.js @@ -54,6 +54,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/send/send-content/send-content.component.js b/ui/app/components/send/send-content/send-content.component.js index 566ee1c7f..60f97ab32 100644 --- a/ui/app/components/send/send-content/send-content.component.js +++ b/ui/app/components/send/send-content/send-content.component.js @@ -19,8 +19,10 @@ export default class SendContent extends Component {
- this.props.updateGas(updateData)} /> - + this.props.updateGas(updateData)} + scanQrCode={ _ => this.props.scanQrCode()} + /> this.props.updateGas(updateData)} /> diff --git a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js b/ui/app/components/send/send-content/send-to-row/send-to-row.component.js index 892ad5d67..321d1cfac 100644 --- a/ui/app/components/send/send-content/send-to-row/send-to-row.component.js +++ b/ui/app/components/send/send-content/send-to-row/send-to-row.component.js @@ -51,6 +51,7 @@ export default class SendToRow extends Component { showError={inError} > this.props.scanQrCode()} accounts={toAccounts} closeDropdown={() => closeToDropdown()} dropdownOpen={toDropdownOpen} diff --git a/ui/app/components/send/send.component.js b/ui/app/components/send/send.component.js index 5e967251d..6c439cd21 100644 --- a/ui/app/components/send/send.component.js +++ b/ui/app/components/send/send.component.js @@ -47,7 +47,7 @@ export default class SendTransactionScreen extends PersistentForm { scanQrCode = async () => { const scannedAddress = await this.props.scanQrCode() - console.log('QR-SCANNER: Got address (UI)', scannedAddress) + this.props.updateSendTo(scannedAddress) this.updateGas({ to: scannedAddress }) } diff --git a/ui/app/components/send/send.container.js b/ui/app/components/send/send.container.js index c3240be67..417941601 100644 --- a/ui/app/components/send/send.container.js +++ b/ui/app/components/send/send.container.js @@ -23,6 +23,7 @@ import { getTokenBalance, } from './send.selectors' import { + updateSendTo, updateSendTokenBalance, updateGasData, setGasTotal, @@ -91,5 +92,6 @@ function mapDispatchToProps (dispatch) { updateSendErrors: newError => dispatch(updateSendErrors(newError)), resetSendState: () => dispatch(resetSendState()), scanQrCode: () => dispatch(scanQrCode()), + updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)), } } diff --git a/ui/app/components/send/to-autocomplete/to-autocomplete.js b/ui/app/components/send/to-autocomplete/to-autocomplete.js index 80cfa7a85..2b8923dd1 100644 --- a/ui/app/components/send/to-autocomplete/to-autocomplete.js +++ b/ui/app/components/send/to-autocomplete/to-autocomplete.js @@ -94,11 +94,12 @@ ToAutoComplete.prototype.render = function () { dropdownOpen, onChange, inError, + qrScanner, } = this.props return h('div.send-v2__to-autocomplete', {}, [ - h('input.send-v2__to-autocomplete__input', { + h(`input.send-v2__to-autocomplete__input${qrScanner?'.with-qr':''}`, { placeholder: this.context.t('recipientAddress'), className: inError ? `send-v2__error-border` : '', value: to, @@ -108,7 +109,10 @@ ToAutoComplete.prototype.render = function () { borderColor: inError ? 'red' : null, }, }), - + qrScanner && h(`i.fa.fa-qrcode.fa-lg.send-v2__to-autocomplete__qr-code`, { + style: { color: '#33333' }, + onClick: () => this.props.scanQrCode(), + }), !to && h(`i.fa.fa-caret-down.fa-lg.send-v2__to-autocomplete__down-caret`, { style: { color: '#dedede' }, onClick: () => this.handleInputEvent(), diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index e9c872ea7..e9f22a14e 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -626,6 +626,16 @@ top: 18px; right: 12px; } + + &__qr-code { + position: absolute; + top: 21px; + left: 13px; + } + + &__input.with-qr { + padding-left: 40px; + } } &__to-autocomplete, &__memo-text-area, &__hex-data { From 74fd6d1d1227d7a9e49623b73ee85985d79a1e46 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 24 Jul 2018 20:32:20 -0400 Subject: [PATCH 06/35] working without injection --- app/manifest.json | 3 +- app/scripts/contentscript.js | 41 ----- app/scripts/metamask-controller.js | 18 --- app/scripts/platforms/extension.js | 6 - ui/app/actions.js | 41 +++-- ui/app/app.js | 7 + ui/app/components/qr-scanner/index.js | 2 + .../qr-scanner/qr-scanner.component.js | 152 ++++++++++++++++++ ui/app/components/send/send.component.js | 20 ++- ui/app/components/send/send.container.js | 2 + ui/app/components/send/send.selectors.js | 5 + ui/app/reducers/app.js | 20 ++- ui/app/selectors.js | 1 + 13 files changed, 231 insertions(+), 87 deletions(-) create mode 100644 ui/app/components/qr-scanner/index.js create mode 100644 ui/app/components/qr-scanner/qr-scanner.component.js 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 ( +
+
+

+ Scan QR code +

+
+
+
+ {this.state.msg} +
+
+
this.props.hideQrScanner() } + /> +
+ ) + } +} + +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 { this.updateGas(updateData)} - scanQrCode={_ => this.scanQrCode()} + scanQrCode={_ => this.props.scanQrCode()} />
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 } + From eeb902dbf0d77a487903c386c11fca5bd9114300 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 24 Jul 2018 21:13:17 -0400 Subject: [PATCH 07/35] decent UI --- .../qr-scanner/qr-scanner.component.js | 88 ++++++++++++------- 1 file changed, 55 insertions(+), 33 deletions(-) diff --git a/ui/app/components/qr-scanner/qr-scanner.component.js b/ui/app/components/qr-scanner/qr-scanner.component.js index cc07e53a2..4cc296441 100644 --- a/ui/app/components/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/qr-scanner/qr-scanner.component.js @@ -3,6 +3,7 @@ import PropTypes from 'prop-types' import {connect} from 'react-redux' import { hideQrScanner, qrCodeDetected} from '../../actions' import Instascan from 'instascan' +import Spinner from '../spinner' class QrScanner extends Component { static propTypes = { @@ -13,11 +14,49 @@ class QrScanner extends Component { constructor (props) { super(props) this.state = { - msg: 'Place the QR code in front of your camera so we can read it...', + ready: false, + msg: 'Accesing your camera...', } this.scanning = false } + componentDidUpdate () { + if (this.props.visible && this.camera && !this.scanning) { + this.scanner = new Instascan.Scanner({ + video: this.camera, + backgroundScan: false, + continuous: true, + }) + this.scanner.addListener('scan', (content) => { + this.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() + this.setState({ ready: false }) + } else { + this.setState({msg: 'Error: We couldn\'t identify that QR code'}) + } + this.scanning = false + }) + }) + Instascan.Camera.getCameras().then((cameras) => { + if (cameras.length > 0) { + this.scanner.start(cameras[0]) + this.setState({ ready: true }) + this.setState({ msg: 'Place the QR code in front of your camera so we can read it...'}) + console.log('QR-SCANNER: started scanning with camera', cameras[0]) + } else { + this.setState({ msg: 'No camera found :('}) + } + }).catch(function (e) { + console.error(e) + }) + this.scanning = true + } + } + parseContent (content) { let type = 'unknown' let values = {} @@ -32,37 +71,13 @@ class QrScanner extends Component { 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 - } + + stopAndClose = () => { + this.scanner.stop().then(_ => { + this.scanning = false + this.props.hideQrScanner() + this.setState({ ready: false }) + }) } render () { @@ -92,6 +107,8 @@ class QrScanner extends Component {

Scan QR code

@@ -101,17 +118,22 @@ class QrScanner extends Component { overflow: 'hidden', width: '100%', height: '275px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', }}>
{this.state.msg} @@ -132,7 +154,7 @@ class QrScanner extends Component { animationName: 'anim_171532470906313', animationTimingFunction: 'ease-out', }} - onClick={_ => this.props.hideQrScanner() } + onClick={this.stopAndClose} />
) From 4759915856440028186df56d4646565dc5c4a5d6 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Tue, 24 Jul 2018 22:34:15 -0400 Subject: [PATCH 08/35] fix spinner and qr icon --- ui/app/components/qr-scanner/qr-scanner.component.js | 7 +++++-- ui/app/css/itcss/components/send.scss | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/ui/app/components/qr-scanner/qr-scanner.component.js b/ui/app/components/qr-scanner/qr-scanner.component.js index 4cc296441..fd3361cf2 100644 --- a/ui/app/components/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/qr-scanner/qr-scanner.component.js @@ -44,8 +44,11 @@ class QrScanner extends Component { Instascan.Camera.getCameras().then((cameras) => { if (cameras.length > 0) { this.scanner.start(cameras[0]) - this.setState({ ready: true }) - this.setState({ msg: 'Place the QR code in front of your camera so we can read it...'}) + setTimeout(_ => { + this.setState({ + ready: true, + msg: 'Place the QR code in front of your camera so we can read it...'}) + }, 2000) console.log('QR-SCANNER: started scanning with camera', cameras[0]) } else { this.setState({ msg: 'No camera found :('}) diff --git a/ui/app/css/itcss/components/send.scss b/ui/app/css/itcss/components/send.scss index e9f22a14e..ad6199f02 100644 --- a/ui/app/css/itcss/components/send.scss +++ b/ui/app/css/itcss/components/send.scss @@ -631,6 +631,7 @@ position: absolute; top: 21px; left: 13px; + cursor: pointer; } &__input.with-qr { From 6cd4bc9f4ecb2c4a066da0aceb36d1a24bbe33e2 Mon Sep 17 00:00:00 2001 From: brunobar79 Date: Thu, 26 Jul 2018 20:24:39 -0400 Subject: [PATCH 09/35] working without permission issues --- app/manifest.json | 3 +- package-lock.json | 17 +++- package.json | 8 +- .../qr-scanner/qr-scanner.component.js | 80 ++++++++++--------- 4 files changed, 64 insertions(+), 44 deletions(-) diff --git a/app/manifest.json b/app/manifest.json index 6933652e6..52256c5b7 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -76,6 +76,5 @@ "ids": [ "*" ] - }, - "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'" + } } diff --git a/package-lock.json b/package-lock.json index 3e1b69db9..0fd733fa7 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", @@ -30207,8 +30216,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", @@ -30668,6 +30676,11 @@ "resolved": "https://registry.npmjs.org/try-require/-/try-require-1.2.1.tgz", "integrity": "sha1-NEiaLKwMCcHMEO2RugEVlNQzO+I=" }, + "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", diff --git a/package.json b/package.json index 168ab0171..1ab582428 100644 --- a/package.json +++ b/package.json @@ -65,6 +65,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", @@ -94,6 +95,7 @@ "eslint-plugin-react": "^7.4.0", "eth-bin-to-ops": "^1.0.1", "eth-contract-metadata": "github:MetaMask/eth-contract-metadata#master", + "eth-ens-namehash": "^2.0.8", "eth-hd-keyring": "^2.0.0", "eth-json-rpc-filters": "^1.2.6", "eth-json-rpc-infura": "^3.0.0", @@ -142,6 +144,7 @@ "metamascara": "^2.0.0", "metamask-logo": "^2.1.4", "mkdirp": "^0.5.1", + "multihashes": "^0.4.12", "multiplex": "^6.7.0", "number-to-bn": "^1.7.0", "obj-multiplex": "^1.0.0", @@ -195,9 +198,7 @@ "web3": "^0.20.1", "web3-provider-engine": "^14.0.5", "web3-stream-provider": "^3.0.1", - "xtend": "^4.0.1", - "multihashes": "^0.4.12", - "eth-ens-namehash": "^2.0.8" + "xtend": "^4.0.1" }, "devDependencies": { "@sentry/cli": "^1.30.3", @@ -257,7 +258,6 @@ "gulp-watch": "^5.0.0", "gulp-zip": "^4.0.0", "http-server": "^0.11.1", - "instascan": "github:brunobar79/instascan#141f7b2aa12c9e833de41ba3daf37a1c1b7c070e", "image-size": "^0.6.2", "isomorphic-fetch": "^2.2.1", "jsdoc": "^3.5.5", diff --git a/ui/app/components/qr-scanner/qr-scanner.component.js b/ui/app/components/qr-scanner/qr-scanner.component.js index fd3361cf2..e0b2c40d6 100644 --- a/ui/app/components/qr-scanner/qr-scanner.component.js +++ b/ui/app/components/qr-scanner/qr-scanner.component.js @@ -2,8 +2,8 @@ import React, { Component } from 'react' import PropTypes from 'prop-types' import {connect} from 'react-redux' import { hideQrScanner, qrCodeDetected} from '../../actions' -import Instascan from 'instascan' import Spinner from '../spinner' +import { BrowserQRCodeReader } from '@zxing/library' class QrScanner extends Component { static propTypes = { @@ -18,18 +18,36 @@ class QrScanner extends Component { msg: 'Accesing your camera...', } this.scanning = false + this.codeReader = null } componentDidUpdate () { if (this.props.visible && this.camera && !this.scanning) { - this.scanner = new Instascan.Scanner({ - video: this.camera, - backgroundScan: false, - continuous: true, - }) - this.scanner.addListener('scan', (content) => { - this.scanner.stop().then(_ => { - const result = this.parseContent(content) + this.scanning = true + this.initCamera() + } + } + + initCamera () { + console.log('QR-SCANNER: initCamera ') + this.codeReader = new BrowserQRCodeReader() + this.codeReader.getVideoInputDevices() + .then(videoInputDevices => { + console.log('QR-SCANNER: getVideoInputDevices ', videoInputDevices) + setTimeout(_ => { + this.setState({ + ready: true, + msg: 'Place the QR code in front of your camera so we can read it...'}) + console.log('QR-SCANNER: this.state.ready = true') + }, 2000) + + console.log('QR-SCANNER: started scanning...') + this.codeReader.decodeFromInputVideoDevice(videoInputDevices[0].deviceId, 'video') + .then(content => { + console.log('QR-SCANNER: content found!', content) + this.codeReader.reset() + console.log('QR-SCANNER: stopped scanning...') + const result = this.parseContent(content.text) if (result.type !== 'unknown') { console.log('QR-SCANNER: CODE DETECTED', result) this.props.qrCodeDetected(result) @@ -37,27 +55,15 @@ class QrScanner extends Component { this.setState({ ready: false }) } else { this.setState({msg: 'Error: We couldn\'t identify that QR code'}) + console.log('QR-SCANNER: Unknown code') } - this.scanning = false }) + .catch(err => { + console.log('QR-SCANNER: decodeFromInputVideoDevice threw an exception: ', err) + }) + }).catch(err => { + console.log('QR-SCANNER: getVideoInputDevices threw an exception: ', err) }) - Instascan.Camera.getCameras().then((cameras) => { - if (cameras.length > 0) { - this.scanner.start(cameras[0]) - setTimeout(_ => { - this.setState({ - ready: true, - msg: 'Place the QR code in front of your camera so we can read it...'}) - }, 2000) - console.log('QR-SCANNER: started scanning with camera', cameras[0]) - } else { - this.setState({ msg: 'No camera found :('}) - } - }).catch(function (e) { - console.error(e) - }) - this.scanning = true - } } parseContent (content) { @@ -76,11 +82,11 @@ class QrScanner extends Component { stopAndClose = () => { - this.scanner.stop().then(_ => { - this.scanning = false - this.props.hideQrScanner() - this.setState({ ready: false }) - }) + console.log('QR-SCANNER: stopping scanner...') + this.codeReader.reset() + this.scanning = false + this.props.hideQrScanner() + this.setState({ ready: false }) } render () { @@ -126,11 +132,13 @@ class QrScanner extends Component { justifyContent: 'center', }}>