mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-12 20:57:12 +01:00
commit
9268c4ed54
@ -2,6 +2,12 @@
|
||||
|
||||
## Current Develop Branch
|
||||
|
||||
## 4.9.3 Wed Aug 15 2018
|
||||
|
||||
- (#4897)[https://github.com/MetaMask/metamask-extension/pull/4897]: QR code scan for recipient addresses.
|
||||
- (#4961)[https://github.com/MetaMask/metamask-extension/pull/4961]: Add a download seed phrase link.
|
||||
- (#5060)[https://github.com/MetaMask/metamask-extension/pull/5060]: Fix bug where gas was not updating properly.
|
||||
|
||||
## 4.9.2 Mon Aug 09 2018
|
||||
|
||||
- [#5020](https://github.com/MetaMask/metamask-extension/pull/5020): Fix bug in migration #28 ( moving tokens to specific accounts )
|
||||
|
@ -84,7 +84,7 @@
|
||||
"message": "Auf Coinbase kaufen"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase ist die weltweit bekannteste Art und Weise um bitcoin, ethereum und litecoin zu kaufen und verkaufen."
|
||||
"message": "Coinbase ist die weltweit bekannteste Art und Weise um Bitcoin, Ethereum und Litecoin zu kaufen und verkaufen."
|
||||
},
|
||||
"ok": {
|
||||
"message": "Ok"
|
||||
@ -828,7 +828,7 @@
|
||||
"message": "Willkommen zur neuen Oberfläche (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "Du verwendest nun die neue Metamask Oberfläche. Schau dich um, teste die neuen Features wie z.B. das Senden von Token und lass es uns wissen falls du irgendwelche Probleme hast."
|
||||
"message": "Du verwendest nun die neue MetaMask Oberfläche. Schau dich um, teste die neuen Features wie z.B. das Senden von Token und lass es uns wissen falls du irgendwelche Probleme hast."
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "Nicht genehmigt"
|
||||
|
@ -2,6 +2,9 @@
|
||||
"accept": {
|
||||
"message": "Accept"
|
||||
},
|
||||
"accessingYourCamera": {
|
||||
"message": "Accesing your camera..."
|
||||
},
|
||||
"account": {
|
||||
"message": "Account"
|
||||
},
|
||||
@ -96,7 +99,7 @@
|
||||
"message": "Buy on Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase is the world’s most popular way to buy and sell bitcoin, ethereum, and litecoin."
|
||||
"message": "Coinbase is the world’s most popular way to buy and sell Bitcoin, Ethereum, and Litecoin."
|
||||
},
|
||||
"bytes": {
|
||||
"message": "Bytes"
|
||||
@ -117,7 +120,7 @@
|
||||
"message": "Close"
|
||||
},
|
||||
"chromeRequiredForTrezor":{
|
||||
"message": "You need to use Metamask on Google Chrome in order to connect to your TREZOR device."
|
||||
"message": "You need to use MetaMask on Google Chrome in order to connect to your TREZOR device."
|
||||
},
|
||||
"confirm": {
|
||||
"message": "Confirm"
|
||||
@ -147,7 +150,7 @@
|
||||
"message": "Connect to Trezor"
|
||||
},
|
||||
"connectToTrezorHelp": {
|
||||
"message": "Metamask is able to access your TREZOR ethereum accounts. First make sure your device is connected and unlocked."
|
||||
"message": "MetaMask is able to access your TREZOR Ethereum accounts. First make sure your device is connected and unlocked."
|
||||
},
|
||||
"connectToTrezorTrouble": {
|
||||
"message": "If you are having trouble, please make sure you are using the latest version of the TREZOR firmware."
|
||||
@ -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."
|
||||
},
|
||||
@ -1069,7 +1087,7 @@
|
||||
"message": "Welcome to the New UI (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "You are now using the new Metamask UI."
|
||||
"message": "You are now using the new MetaMask UI."
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "Unapproved"
|
||||
@ -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"
|
||||
},
|
||||
|
@ -75,7 +75,7 @@
|
||||
"message": "Pedir prestado con Dharma (Beta)"
|
||||
},
|
||||
"builtInCalifornia": {
|
||||
"message": "Metamask fue diseñado y construido en California"
|
||||
"message": "MetaMask fue diseñado y construido en California"
|
||||
},
|
||||
"buy": {
|
||||
"message": "Comprar"
|
||||
@ -874,7 +874,7 @@
|
||||
"message": "Advertencia"
|
||||
},
|
||||
"welcomeBeta": {
|
||||
"message": "Bienvenido a Metamask Beta"
|
||||
"message": "Bienvenido a MetaMask Beta"
|
||||
},
|
||||
"whatsThis": {
|
||||
"message": "¿Qué es esto?"
|
||||
|
@ -63,7 +63,7 @@
|
||||
"message": "Acheter sur Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase est le moyen le plus populaire au monde d'acheter et de vendre du bitcoin, de l'ethereum et du litecoin."
|
||||
"message": "Coinbase est le moyen le plus populaire au monde d'acheter et de vendre du Bitcoin, de l'Ethereum et du Litecoin."
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Annuler"
|
||||
@ -570,7 +570,7 @@
|
||||
"message": "Bienvenue dans la nouvelle interface utilisateur (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "Vous utilisez maintenant la nouvelle interface utilisateur Metamask. Jetez un coup d'oeil, essayez de nouvelles fonctionnalités comme l'envoi de jetons, et faites-nous savoir si vous avez des problèmes."
|
||||
"message": "Vous utilisez maintenant la nouvelle interface utilisateur MetaMask. Jetez un coup d'oeil, essayez de nouvelles fonctionnalités comme l'envoi de jetons, et faites-nous savoir si vous avez des problèmes."
|
||||
},
|
||||
"unavailable": {
|
||||
"message": "Indisponible"
|
||||
|
@ -81,7 +81,7 @@
|
||||
"message": "Compra su Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere bitcoin, ethereum e litecoin."
|
||||
"message": "Coinbase è il servizio più popolare al mondo per comprare e vendere Bitcoin, Ethereum e Litecoin."
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Cancella"
|
||||
@ -178,7 +178,7 @@
|
||||
"message": "La rete predefinita per transazioni in Ether è la Rete Ethereum Principale."
|
||||
},
|
||||
"denExplainer": {
|
||||
"message": "Il DEN è il tuo archivio crittato con password dentro Metamask."
|
||||
"message": "Il DEN è il tuo archivio crittato con password dentro MetaMask."
|
||||
},
|
||||
"deposit": {
|
||||
"message": "Deposita"
|
||||
|
@ -81,7 +81,7 @@
|
||||
"message": "Koop op Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase is 's werelds populairste manier om bitcoin, ethereum en litecoin te kopen en verkopen."
|
||||
"message": "Coinbase is 's werelds populairste manier om Bitcoin, Ethereum en Litecoin te kopen en verkopen."
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Annuleer"
|
||||
@ -435,7 +435,7 @@
|
||||
"message": "back-up woorden hebben alleen kleine letters"
|
||||
},
|
||||
"mainnet": {
|
||||
"message": "belangrijkste ethereum-netwerk"
|
||||
"message": "belangrijkste Ethereum-netwerk"
|
||||
},
|
||||
"message": {
|
||||
"message": "Bericht"
|
||||
@ -762,7 +762,7 @@
|
||||
"message": "Welkom bij de nieuwe gebruikersinterface (bèta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "U gebruikt nu de nieuwe gebruikersinterface van Metamask. Kijk rond, probeer nieuwe functies uit zoals het verzenden van tokens en laat ons weten of u problemen ondervindt."
|
||||
"message": "U gebruikt nu de nieuwe gebruikersinterface van MetaMask. Kijk rond, probeer nieuwe functies uit zoals het verzenden van tokens en laat ons weten of u problemen ondervindt."
|
||||
},
|
||||
"unavailable": {
|
||||
"message": "Niet beschikbaar"
|
||||
|
@ -63,7 +63,7 @@
|
||||
"message": "Bumili sa Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng bitcoin, ethereum, at litecoin sa buong mundo."
|
||||
"message": "Ang Coinbase ang pinakasikat na paraan upang bumili at magbenta ng Bitcoin, Ethereum, at Litecoin sa buong mundo."
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Kanselahin"
|
||||
|
@ -81,7 +81,7 @@
|
||||
"message": "Comprar no Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase é a forma mais conhecida para comprar e vender bitcoin, ethereum, e litecoin."
|
||||
"message": "Coinbase é a forma mais conhecida para comprar e vender Bitcoin, Ethereum, e Litecoin."
|
||||
},
|
||||
"cancel": {
|
||||
"message": "Cancelar"
|
||||
|
@ -84,7 +84,7 @@
|
||||
"message": "Купить на Coinbase"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Биржа Coinbase – это наиболее популярный способ купить или продать bitcoin, ethereum и litecoin."
|
||||
"message": "Биржа Coinbase – это наиболее популярный способ купить или продать Bitcoin, Ethereum и Litecoin."
|
||||
},
|
||||
"ok": {
|
||||
"message": "ОК"
|
||||
|
@ -762,7 +762,7 @@
|
||||
"message": "ยินดีต้อนรับสู่หน้าตาใหม่ (เบต้า)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "ขณะนี้คุณใช้งาน Metamask หน้าตาใหม่แล้ว ลองใช้ความสามรถใหม่ ๆ เช่นการส่งโทเค็นและหากพบปัญหากรุณาแจ้งให้เราทราบ"
|
||||
"message": "ขณะนี้คุณใช้งาน MetaMask หน้าตาใหม่แล้ว ลองใช้ความสามรถใหม่ ๆ เช่นการส่งโทเค็นและหากพบปัญหากรุณาแจ้งให้เราทราบ"
|
||||
},
|
||||
"unavailable": {
|
||||
"message": "ใช้งานไม่ได้"
|
||||
|
@ -84,7 +84,7 @@
|
||||
"message": "Coinbase'de satın al"
|
||||
},
|
||||
"buyCoinbaseExplainer": {
|
||||
"message": "Coinbase bitcoin, ethereum, and litecoin alıp satmanın dünyadaki en popüler yolu"
|
||||
"message": "Coinbase Bitcoin, Ethereum, and Litecoin alıp satmanın dünyadaki en popüler yolu"
|
||||
},
|
||||
"ok": {
|
||||
"message": "Tamam"
|
||||
@ -852,7 +852,7 @@
|
||||
"message": "Yeni UI (Beta)'ya hoşgeldiniz"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "Şu anda yeni Metamask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin"
|
||||
"message": "Şu anda yeni MetaMask UI kullanmaktasınız. Gözatın, jeton gönderme gibi yeni özellikleri deneyin ve herhangi bir sorunlar karşılaşırsanız bize haber verin"
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "Onaylanmadı"
|
||||
|
@ -570,7 +570,7 @@
|
||||
"message": "Chào mừng bạn đến với giao diện mới (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "Bạn đang sử dụng giao diện mới của Metamask. Chúng tôi khuyến khích bạn thử nghiệm và khám phá các tính năng mới như gửi token, và nếu bạn có gặp phải vấn đề gì khó khăn, xin hãy liên hệ ngay để chúng tôi có thể giúp đỡ bạn."
|
||||
"message": "Bạn đang sử dụng giao diện mới của MetaMask. Chúng tôi khuyến khích bạn thử nghiệm và khám phá các tính năng mới như gửi token, và nếu bạn có gặp phải vấn đề gì khó khăn, xin hãy liên hệ ngay để chúng tôi có thể giúp đỡ bạn."
|
||||
},
|
||||
"unavailable": {
|
||||
"message": "Không có sẵn"
|
||||
|
@ -879,7 +879,7 @@
|
||||
"message": "欢迎使用新版界面 (Beta)"
|
||||
},
|
||||
"uiWelcomeMessage": {
|
||||
"message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||
"message": "你现在正在使用新的 MetaMask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||
},
|
||||
"unapproved": {
|
||||
"message": "未批准"
|
||||
|
@ -362,7 +362,7 @@
|
||||
"message": "你想怎麼存入 Ether?"
|
||||
},
|
||||
"holdEther": {
|
||||
"message": "Metamask 讓您能保存 ether 和代幣, 並成為您接觸分散式應用程式的途徑."
|
||||
"message": "MetaMask 讓您能保存 ether 和代幣, 並成為您接觸分散式應用程式的途徑."
|
||||
},
|
||||
"import": {
|
||||
"message": "導入",
|
||||
|
18
app/images/webcam.svg
Normal file
18
app/images/webcam.svg
Normal file
@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg width="53px" height="53px" viewBox="0 0 53 53" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<!-- Generator: Sketch 50.2 (55047) - http://www.bohemiancoding.com/sketch -->
|
||||
<title>webcam</title>
|
||||
<desc>Created with Sketch.</desc>
|
||||
<defs></defs>
|
||||
<g id="QR-Code-Scan" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
|
||||
<g id="Group-4-Copy" transform="translate(-482.000000, -218.000000)">
|
||||
<g id="webcam" transform="translate(482.000000, 218.000000)">
|
||||
<circle id="Oval" fill="#D5ECFA" cx="26.5" cy="26.5" r="26.5"></circle>
|
||||
<g id="Group" transform="translate(14.000000, 19.000000)" fill="#259DE5">
|
||||
<rect id="Rectangle" x="0" y="0" width="18" height="16"></rect>
|
||||
<polygon id="Triangle" points="19 6.57142857 26 3 26 13 19 9.42857143"></polygon>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1020 B |
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "__MSG_appName__",
|
||||
"short_name": "__MSG_appName__",
|
||||
"version": "4.9.2",
|
||||
"version": "4.9.3",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "__MSG_appDescription__",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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}`
|
||||
}
|
||||
|
@ -340,6 +340,19 @@
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.backup-phrase__tips-text--link {
|
||||
color: #2f9ae0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.backup-phrase__tips-text--link:hover {
|
||||
color: #2f9ae0;
|
||||
}
|
||||
|
||||
.backup-phrase__tips-text--strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 768px) {
|
||||
.backup-phrase__content-wrapper {
|
||||
flex-direction: column;
|
||||
|
@ -5,6 +5,7 @@ import classnames from 'classnames'
|
||||
import { withRouter } from 'react-router-dom'
|
||||
import { compose } from 'recompose'
|
||||
import Identicon from '../../../../ui/app/components/identicon'
|
||||
import {exportAsFile} from '../../../../ui/app/util'
|
||||
import Breadcrumbs from './breadcrumbs'
|
||||
import LoadingScreen from './loading-screen'
|
||||
import { DEFAULT_ROUTE, INITIALIZE_CONFIRM_SEED_ROUTE } from '../../../../ui/app/routes'
|
||||
@ -65,6 +66,12 @@ class BackupPhraseScreen extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
exportSeedWords = () => {
|
||||
const { seedWords } = this.props
|
||||
|
||||
exportAsFile('MetaMask Secret Backup Phrase', seedWords, 'text/plain')
|
||||
}
|
||||
|
||||
renderSecretWordsContainer () {
|
||||
const { isShowingSecret } = this.state
|
||||
|
||||
@ -111,7 +118,7 @@ class BackupPhraseScreen extends Component {
|
||||
<div className="backup-phrase__tips">
|
||||
<div className="backup-phrase__tips-text">Tips:</div>
|
||||
<div className="backup-phrase__tips-text">
|
||||
Store this phrase in a password manager like 1password.
|
||||
Store this phrase in a password manager like 1Password.
|
||||
</div>
|
||||
<div className="backup-phrase__tips-text">
|
||||
Write this phrase on a piece of paper and store in a secure location. If you want even more security, write it down on multiple pieces of paper and store each in 2 - 3 different locations.
|
||||
@ -119,6 +126,13 @@ class BackupPhraseScreen extends Component {
|
||||
<div className="backup-phrase__tips-text">
|
||||
Memorize this phrase.
|
||||
</div>
|
||||
<div className="backup-phrase__tips-text">
|
||||
<strong>
|
||||
<a className="backup-phrase__tips-text--link backup-phrase__tips-text--strong" onClick={this.exportSeedWords}>
|
||||
Download this Secret Backup Phrase
|
||||
</a>
|
||||
</strong> and keep it stored safely on an external encrypted hard drive or storage medium.
|
||||
</div>
|
||||
</div>
|
||||
<div className="backup-phrase__next-button">
|
||||
<button
|
||||
|
66
package-lock.json
generated
66
package-lock.json
generated
@ -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",
|
||||
|
@ -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": {
|
||||
|
@ -38,18 +38,20 @@ const transferTokens = document.getElementById('transferTokens')
|
||||
const approveTokens = document.getElementById('approveTokens')
|
||||
|
||||
deployButton.addEventListener('click', async function (event) {
|
||||
document.getElementById('contractStatus').innerHTML = 'Deploying'
|
||||
|
||||
var piggybank = await piggybankContract.new(
|
||||
{
|
||||
from: web3.eth.accounts[0],
|
||||
data: '0x608060405234801561001057600080fd5b5033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506000808190555061023b806100686000396000f300608060405260043610610057576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680632e1a7d4d1461005c5780638da5cb5b1461009d578063d0e30db0146100f4575b600080fd5b34801561006857600080fd5b5061008760048036038101908080359060200190929190505050610112565b6040518082815260200191505060405180910390f35b3480156100a957600080fd5b506100b26101d0565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100fc6101f6565b6040518082815260200191505060405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614151561017057600080fd5b8160008082825403925050819055503373ffffffffffffffffffffffffffffffffffffffff166108fc839081150290604051600060405180830381858888f193505050501580156101c5573d6000803e3d6000fd5b506000549050919050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60003460008082825401925050819055506000549050905600a165627a7a72305820f237db3ec816a52589d82512117bc85bc08d3537683ffeff9059108caf3e5d400029',
|
||||
gas: '4700000',
|
||||
}, function (e, contract) {
|
||||
console.log(e, contract)
|
||||
if (e) {
|
||||
throw e
|
||||
}
|
||||
if (typeof contract.address !== 'undefined') {
|
||||
console.log('Contract mined! address: ' + contract.address + ' transactionHash: ' + contract.transactionHash)
|
||||
|
||||
console.log(`contract`, contract)
|
||||
|
||||
document.getElementById('contractStatus').innerHTML = 'Deployed'
|
||||
|
||||
depositButton.addEventListener('click', function (event) {
|
||||
|
@ -11,7 +11,7 @@
|
||||
<button id="withdrawButton">Withdraw</button>
|
||||
</div>
|
||||
<div id="contractStatus" style="display: flex; font-size: 1rem;">
|
||||
Not yet deployed
|
||||
Not clicked
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; flex-flow: column;">
|
||||
|
@ -89,7 +89,14 @@ describe('Using MetaMask with an existing account', function () {
|
||||
await driver.wait(until.stalenessOf(overlay))
|
||||
} catch (e) {}
|
||||
|
||||
const button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
let button
|
||||
try {
|
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
} catch (e) {
|
||||
await loadExtension(driver, extensionId)
|
||||
await delay(largeDelayMs)
|
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
}
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
|
@ -122,12 +122,14 @@ async function closeAllWindowHandlesExcept (driver, exceptions, windowHandles) {
|
||||
}
|
||||
|
||||
async function assertElementNotPresent (webdriver, driver, by) {
|
||||
let dataTab
|
||||
try {
|
||||
const dataTab = await findElement(driver, by, 4000)
|
||||
dataTab = await findElement(driver, by, 4000)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
assert(err instanceof webdriver.error.NoSuchElementError || err instanceof webdriver.error.TimeoutError)
|
||||
}
|
||||
if (dataTab) {
|
||||
assert(false, 'Data tab should not be present')
|
||||
}
|
||||
} catch (err) {
|
||||
assert(err instanceof webdriver.error.NoSuchElementError)
|
||||
}
|
||||
}
|
||||
|
@ -88,7 +88,14 @@ describe('MetaMask', function () {
|
||||
await driver.wait(until.stalenessOf(overlay))
|
||||
} catch (e) {}
|
||||
|
||||
const button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
let button
|
||||
try {
|
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
} catch (e) {
|
||||
await loadExtension(driver, extensionId)
|
||||
await delay(largeDelayMs)
|
||||
button = await findElement(driver, By.xpath("//button[contains(text(), 'Try it now')]"))
|
||||
}
|
||||
await button.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
@ -345,8 +352,8 @@ describe('MetaMask', function () {
|
||||
const passwordInputs = await driver.findElements(By.css('input'))
|
||||
await delay(regularDelayMs)
|
||||
|
||||
passwordInputs[0].sendKeys('correct horse battery staple')
|
||||
passwordInputs[1].sendKeys('correct horse battery staple')
|
||||
await passwordInputs[0].sendKeys('correct horse battery staple')
|
||||
await passwordInputs[1].sendKeys('correct horse battery staple')
|
||||
await driver.findElement(By.css('.first-time-flow__button')).click()
|
||||
await delay(regularDelayMs)
|
||||
})
|
||||
@ -438,7 +445,7 @@ describe('MetaMask', function () {
|
||||
await driver.switchTo().window(windowHandles[2])
|
||||
await delay(regularDelayMs)
|
||||
|
||||
assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`))
|
||||
await assertElementNotPresent(webdriver, driver, By.xpath(`//li[contains(text(), 'Data')]`))
|
||||
|
||||
const confirmButton = await findElement(driver, By.xpath(`//button[contains(text(), 'Confirm')]`), 10000)
|
||||
await confirmButton.click()
|
||||
@ -453,6 +460,11 @@ describe('MetaMask', function () {
|
||||
const transactions = await findElements(driver, By.css('.tx-list-item'))
|
||||
assert.equal(transactions.length, 2)
|
||||
|
||||
await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`))
|
||||
|
||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
||||
|
||||
const txValues = await findElement(driver, By.css('.tx-list-value'))
|
||||
await driver.wait(until.elementTextMatches(txValues, /3\sETH/), 10000)
|
||||
})
|
||||
@ -503,6 +515,8 @@ describe('MetaMask', function () {
|
||||
await confirmButton.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
await findElement(driver, By.xpath(`//span[contains(text(), 'Submitted')]`))
|
||||
|
||||
const txStatuses = await findElements(driver, By.css('.tx-list-status'))
|
||||
await driver.wait(until.elementTextMatches(txStatuses[0], /Confirmed/))
|
||||
|
||||
@ -515,15 +529,15 @@ describe('MetaMask', function () {
|
||||
await driver.switchTo().window(dapp)
|
||||
await delay(regularDelayMs)
|
||||
|
||||
let contractStatus = await driver.findElement(By.css('#contractStatus'))
|
||||
await driver.wait(until.elementTextMatches(contractStatus, /Deployed/))
|
||||
let contractStatus = await findElement(driver, By.css('#contractStatus'))
|
||||
await driver.wait(until.elementTextMatches(contractStatus, /Deployed/), 15000)
|
||||
|
||||
const depositButton = await findElement(driver, By.css('#depositButton'))
|
||||
await depositButton.click()
|
||||
await delay(largeDelayMs)
|
||||
|
||||
contractStatus = await driver.findElement(By.css('#contractStatus'))
|
||||
await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/))
|
||||
contractStatus = await findElement(driver, By.css('#contractStatus'))
|
||||
await driver.wait(until.elementTextMatches(contractStatus, /Deposit\sinitiated/), 10000)
|
||||
|
||||
await driver.switchTo().window(extension)
|
||||
await delay(largeDelayMs)
|
||||
@ -539,8 +553,8 @@ describe('MetaMask', function () {
|
||||
await configureGas.click()
|
||||
await delay(regularDelayMs)
|
||||
|
||||
const gasModal = await driver.findElement(By.css('span .modal'))
|
||||
await driver.wait(until.elementLocated(By.css('.customize-gas__title')))
|
||||
const gasModal = await findElement(driver, By.css('span .modal'))
|
||||
await driver.wait(until.elementLocated(By.css('.customize-gas__title')), 10000)
|
||||
|
||||
const [gasPriceInput, gasLimitInput] = await findElements(driver, By.css('.customize-gas-input'))
|
||||
await gasPriceInput.clear()
|
||||
|
@ -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 {
|
||||
|
@ -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' },
|
||||
|
@ -1,5 +1,7 @@
|
||||
@import './customize-gas/index';
|
||||
|
||||
@import './qr-scanner/index';
|
||||
|
||||
.modal-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -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: [],
|
||||
|
2
ui/app/components/modals/qr-scanner/index.js
Normal file
2
ui/app/components/modals/qr-scanner/index.js
Normal file
@ -0,0 +1,2 @@
|
||||
import QrScanner from './qr-scanner.container'
|
||||
module.exports = QrScanner
|
83
ui/app/components/modals/qr-scanner/index.scss
Normal file
83
ui/app/components/modals/qr-scanner/index.scss
Normal file
@ -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;
|
||||
}
|
||||
}
|
||||
|
216
ui/app/components/modals/qr-scanner/qr-scanner.component.js
Normal file
216
ui/app/components/modals/qr-scanner/qr-scanner.component.js
Normal file
@ -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 (
|
||||
<div className={'qr-scanner__content__video-wrapper'}>
|
||||
<video
|
||||
id="video"
|
||||
style={{
|
||||
display: this.state.ready ? 'block' : 'none',
|
||||
}}
|
||||
/>
|
||||
{ !this.state.ready ? <Spinner color={'#F7C06C'} /> : null}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
renderErrorModal () {
|
||||
let title, msg
|
||||
|
||||
if (this.props.error) {
|
||||
if (this.props.errorType === 'NO_WEBCAM_FOUND') {
|
||||
title = this.context.t('noWebcamFoundTitle')
|
||||
msg = this.context.t('noWebcamFound')
|
||||
} else {
|
||||
title = this.context.t('unknownCameraErrorTitle')
|
||||
msg = this.context.t('unknownCameraError')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="qr-scanner">
|
||||
<div className="qr-scanner__close" onClick={this.stopAndClose}></div>
|
||||
|
||||
<div className="qr-scanner__image">
|
||||
<img src={'images/webcam.svg'} width={70} height={70} />
|
||||
</div>
|
||||
<div className="qr-scanner__title">
|
||||
{ title }
|
||||
</div>
|
||||
<div className={'qr-scanner__error'}>
|
||||
{msg}
|
||||
</div>
|
||||
<PageContainerFooter
|
||||
onCancel={this.stopAndClose}
|
||||
onSubmit={this.tryAgain}
|
||||
cancelText={this.context.t('cancel')}
|
||||
submitText={this.context.t('tryAgain')}
|
||||
submitButtonType="confirm"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
render () {
|
||||
const { t } = this.context
|
||||
|
||||
if (this.props.error) {
|
||||
return this.renderErrorModal()
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="qr-scanner">
|
||||
<div className="qr-scanner__close" onClick={this.stopAndClose}></div>
|
||||
<div className="qr-scanner__title">
|
||||
{ `${t('scanQrCode')}` }
|
||||
</div>
|
||||
<div className="qr-scanner__content">
|
||||
{ this.renderVideo() }
|
||||
</div>
|
||||
<div className={'qr-scanner__status'}>
|
||||
{this.state.msg}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
24
ui/app/components/modals/qr-scanner/qr-scanner.container.js
Normal file
24
ui/app/components/modals/qr-scanner/qr-scanner.container.js
Normal file
@ -0,0 +1,24 @@
|
||||
import { connect } from 'react-redux'
|
||||
import QrScanner from './qr-scanner.component'
|
||||
|
||||
const { hideModal, qrCodeDetected, showQrScanner } = require('../../../actions')
|
||||
import {
|
||||
SEND_ROUTE,
|
||||
} from '../../../routes'
|
||||
|
||||
const mapStateToProps = state => {
|
||||
return {
|
||||
error: state.appState.modal.modalState.props.error,
|
||||
errorType: state.appState.modal.modalState.props.errorType,
|
||||
}
|
||||
}
|
||||
|
||||
const mapDispatchToProps = dispatch => {
|
||||
return {
|
||||
hideModal: () => dispatch(hideModal()),
|
||||
qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
|
||||
scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(QrScanner)
|
@ -11,6 +11,7 @@ export default class SendContent extends Component {
|
||||
|
||||
static propTypes = {
|
||||
updateGas: PropTypes.func,
|
||||
scanQrCode: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
@ -18,7 +19,10 @@ export default class SendContent extends Component {
|
||||
<PageContainerContent>
|
||||
<div className="send-v2__form">
|
||||
<SendFromRow />
|
||||
<SendToRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||
<SendToRow
|
||||
updateGas={(updateData) => this.props.updateGas(updateData)}
|
||||
scanQrCode={ _ => this.props.scanQrCode()}
|
||||
/>
|
||||
<SendAmountRow updateGas={(updateData) => this.props.updateGas(updateData)} />
|
||||
<SendGasRow />
|
||||
<SendHexDataRow />
|
||||
|
@ -17,6 +17,7 @@ export default class SendToRow extends Component {
|
||||
updateGas: PropTypes.func,
|
||||
updateSendTo: PropTypes.func,
|
||||
updateSendToError: PropTypes.func,
|
||||
scanQrCode: PropTypes.func,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
@ -51,6 +52,7 @@ export default class SendToRow extends Component {
|
||||
showError={inError}
|
||||
>
|
||||
<EnsInput
|
||||
scanQrCode={_ => this.props.scanQrCode()}
|
||||
accounts={toAccounts}
|
||||
closeDropdown={() => closeToDropdown()}
|
||||
dropdownOpen={toDropdownOpen}
|
||||
|
@ -38,12 +38,30 @@ export default class SendTransactionScreen extends PersistentForm {
|
||||
updateAndSetGasTotal: PropTypes.func,
|
||||
updateSendErrors: PropTypes.func,
|
||||
updateSendTokenBalance: PropTypes.func,
|
||||
scanQrCode: PropTypes.func,
|
||||
qrCodeDetected: PropTypes.func,
|
||||
qrCodeData: PropTypes.object,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
t: PropTypes.func,
|
||||
};
|
||||
|
||||
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 })
|
||||
// Clean up QR code data after handling
|
||||
this.props.qrCodeDetected(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updateGas ({ to: updatedToAddress, amount: value } = {}) {
|
||||
const {
|
||||
amount,
|
||||
@ -158,6 +176,16 @@ export default class SendTransactionScreen extends PersistentForm {
|
||||
address,
|
||||
})
|
||||
this.updateGas()
|
||||
|
||||
// Show QR Scanner modal if ?scan=true
|
||||
if (window.location.search === '?scan=true') {
|
||||
this.props.scanQrCode()
|
||||
|
||||
// Clear the queryString param after showing the modal
|
||||
const cleanUrl = location.href.split('?')[0]
|
||||
history.pushState({}, null, `${cleanUrl}`)
|
||||
window.location.hash = '#send'
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
@ -170,7 +198,10 @@ export default class SendTransactionScreen extends PersistentForm {
|
||||
return (
|
||||
<div className="page-container">
|
||||
<SendHeader history={history}/>
|
||||
<SendContent updateGas={(updateData) => this.updateGas(updateData)}/>
|
||||
<SendContent
|
||||
updateGas={(updateData) => this.updateGas(updateData)}
|
||||
scanQrCode={_ => this.props.scanQrCode()}
|
||||
/>
|
||||
<SendFooter history={history}/>
|
||||
</div>
|
||||
)
|
||||
|
@ -21,11 +21,15 @@ import {
|
||||
getSendFromObject,
|
||||
getSendTo,
|
||||
getTokenBalance,
|
||||
getQrCodeData,
|
||||
} from './send.selectors'
|
||||
import {
|
||||
updateSendTo,
|
||||
updateSendTokenBalance,
|
||||
updateGasData,
|
||||
setGasTotal,
|
||||
showQrScanner,
|
||||
qrCodeDetected,
|
||||
} from '../../actions'
|
||||
import {
|
||||
resetSendState,
|
||||
@ -35,6 +39,10 @@ import {
|
||||
calcGasTotal,
|
||||
} from './send.utils.js'
|
||||
|
||||
import {
|
||||
SEND_ROUTE,
|
||||
} from '../../routes'
|
||||
|
||||
module.exports = compose(
|
||||
withRouter,
|
||||
connect(mapStateToProps, mapDispatchToProps)
|
||||
@ -60,6 +68,7 @@ function mapStateToProps (state) {
|
||||
tokenBalance: getTokenBalance(state),
|
||||
tokenContract: getSelectedTokenContract(state),
|
||||
tokenToFiatRate: getSelectedTokenToFiatRate(state),
|
||||
qrCodeData: getQrCodeData(state),
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,5 +98,8 @@ function mapDispatchToProps (dispatch) {
|
||||
},
|
||||
updateSendErrors: newError => dispatch(updateSendErrors(newError)),
|
||||
resetSendState: () => dispatch(resetSendState()),
|
||||
scanQrCode: () => dispatch(showQrScanner(SEND_ROUTE)),
|
||||
qrCodeDetected: (data) => dispatch(qrCodeDetected(data)),
|
||||
updateSendTo: (to, nickname) => dispatch(updateSendTo(to, nickname)),
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ proxyquire('../send.container.js', {
|
||||
getSendEditingTransactionId: (s) => `mockEditingTransactionId:${s}`,
|
||||
getSendFromObject: (s) => `mockFrom:${s}`,
|
||||
getTokenBalance: (s) => `mockTokenBalance:${s}`,
|
||||
getQrCodeData: (s) => `mockQrCodeData:${s}`,
|
||||
},
|
||||
'../../actions': actionSpies,
|
||||
'../../ducks/send.duck': duckActionSpies,
|
||||
@ -76,6 +77,7 @@ describe('send container', () => {
|
||||
tokenBalance: 'mockTokenBalance:mockState',
|
||||
tokenContract: 'mockTokenContract:mockState',
|
||||
tokenToFiatRate: 'mockTokenToFiatRate:mockState',
|
||||
qrCodeData: 'mockQrCodeData:mockState',
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -4,6 +4,7 @@ const h = require('react-hyperscript')
|
||||
const inherits = require('util').inherits
|
||||
const AccountListItem = require('../account-list-item/account-list-item.component').default
|
||||
const connect = require('react-redux').connect
|
||||
const Tooltip = require('../../tooltip')
|
||||
|
||||
ToAutoComplete.contextTypes = {
|
||||
t: PropTypes.func,
|
||||
@ -94,11 +95,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 +110,13 @@ ToAutoComplete.prototype.render = function () {
|
||||
borderColor: inError ? 'red' : null,
|
||||
},
|
||||
}),
|
||||
|
||||
qrScanner && h(Tooltip, {
|
||||
title: this.context.t('scanQrCode'),
|
||||
position: 'bottom',
|
||||
}, 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(),
|
||||
|
@ -46,7 +46,7 @@ const decToBigNumberViaString = n => R.pipe(String, toBigNumber['dec'])
|
||||
// Setter Maps
|
||||
const toBigNumber = {
|
||||
hex: n => new BigNumber(stripHexPrefix(n), 16),
|
||||
dec: n => new BigNumber(n, 10),
|
||||
dec: n => new BigNumber(String(n), 10),
|
||||
BN: n => new BigNumber(n.toString(16), 16),
|
||||
}
|
||||
const toNormalizedDenomination = {
|
||||
@ -154,7 +154,7 @@ const subtractCurrencies = (a, b, options = {}) => {
|
||||
bBase,
|
||||
...conversionOptions
|
||||
} = options
|
||||
const value = (new BigNumber(a, aBase)).minus(b, bBase)
|
||||
const value = (new BigNumber(String(a), aBase)).minus(b, bBase)
|
||||
|
||||
return converter({
|
||||
value,
|
||||
|
@ -626,6 +626,23 @@
|
||||
top: 18px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
&__qr-code {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
right: 33px;
|
||||
cursor: pointer;
|
||||
padding: 8px 5px 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
&__qr-code:hover {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
|
||||
&__input.with-qr {
|
||||
padding-right: 65px;
|
||||
}
|
||||
}
|
||||
|
||||
&__to-autocomplete, &__memo-text-area, &__hex-data {
|
||||
|
@ -141,7 +141,7 @@ export function hasUnconfirmedTransactions (state) {
|
||||
|
||||
export function roundExponential (value) {
|
||||
const PRECISION = 4
|
||||
const bigNumberValue = new BigNumber(value)
|
||||
const bigNumberValue = new BigNumber(String(value))
|
||||
|
||||
// In JS, numbers with exponentials greater than 20 get displayed as an exponential.
|
||||
return bigNumberValue.e > 20 ? Number(bigNumberValue.toPrecision(PRECISION)) : value
|
||||
|
@ -51,6 +51,7 @@ function reduceApp (state, action) {
|
||||
sidebarOpen: false,
|
||||
alertOpen: false,
|
||||
alertMessage: null,
|
||||
qrCodeData: null,
|
||||
networkDropdownOpen: false,
|
||||
currentView: seedWords ? seedConfView : defaultView,
|
||||
accountDetail: {
|
||||
@ -91,7 +92,7 @@ function reduceApp (state, action) {
|
||||
sidebarOpen: false,
|
||||
})
|
||||
|
||||
// sidebar methods
|
||||
// alert methods
|
||||
case actions.ALERT_OPEN:
|
||||
return extend(appState, {
|
||||
alertOpen: true,
|
||||
@ -104,6 +105,13 @@ function reduceApp (state, action) {
|
||||
alertMessage: null,
|
||||
})
|
||||
|
||||
// qr scanner methods
|
||||
case actions.QR_CODE_DETECTED:
|
||||
return extend(appState, {
|
||||
qrCodeData: action.value,
|
||||
})
|
||||
|
||||
|
||||
// modal methods:
|
||||
case actions.MODAL_OPEN:
|
||||
const { name, ...modalProps } = action.payload
|
||||
|
@ -44,7 +44,7 @@ async function getSymbolAndDecimals (tokenAddress, existingTokens = []) {
|
||||
|
||||
function calcTokenAmount (value, decimals) {
|
||||
const multiplier = Math.pow(10, Number(decimals || 0))
|
||||
return new BigNumber(value).div(multiplier).toNumber()
|
||||
return new BigNumber(String(value)).div(multiplier).toNumber()
|
||||
}
|
||||
|
||||
|
||||
|
@ -271,9 +271,9 @@ function getContractAtAddress (tokenAddress) {
|
||||
return global.eth.contract(abi).at(tokenAddress)
|
||||
}
|
||||
|
||||
function exportAsFile (filename, data) {
|
||||
function exportAsFile (filename, data, type = 'text/csv') {
|
||||
// source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
|
||||
const blob = new Blob([data], {type: 'text/csv'})
|
||||
const blob = new Blob([data], {type})
|
||||
if (window.navigator.msSaveOrOpenBlob) {
|
||||
window.navigator.msSaveBlob(blob, filename)
|
||||
} else {
|
||||
|
36
ui/lib/webcam-utils.js
Normal file
36
ui/lib/webcam-utils.js
Normal file
@ -0,0 +1,36 @@
|
||||
'use strict'
|
||||
|
||||
import DetectRTC from 'detectrtc'
|
||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums')
|
||||
const { getEnvironmentType, getPlatform } = require('../../app/scripts/lib/util')
|
||||
const { PLATFORM_BRAVE, PLATFORM_FIREFOX } = require('../../app/scripts/lib/enums')
|
||||
|
||||
class WebcamUtils {
|
||||
|
||||
static checkStatus () {
|
||||
return new Promise((resolve, reject) => {
|
||||
const isPopup = getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP
|
||||
const isFirefoxOrBrave = getPlatform() === (PLATFORM_FIREFOX || PLATFORM_BRAVE)
|
||||
try {
|
||||
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…
Reference in New Issue
Block a user