mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-23 09:52:26 +01:00
Merge branch 'develop' of github.com:MetaMask/metamask-extension into scsaba-transaction-history-timestamps
This commit is contained in:
commit
44f31f9a7e
@ -18,10 +18,15 @@ workflows:
|
|||||||
- test-deps:
|
- test-deps:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- test-e2e:
|
- test-e2e-chrome:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
- prep-build
|
- prep-build
|
||||||
|
- test-e2e-firefox:
|
||||||
|
requires:
|
||||||
|
- prep-deps-npm
|
||||||
|
- prep-deps-firefox
|
||||||
|
- prep-build
|
||||||
- test-unit:
|
- test-unit:
|
||||||
requires:
|
requires:
|
||||||
- prep-deps-npm
|
- prep-deps-npm
|
||||||
@ -48,7 +53,8 @@ workflows:
|
|||||||
- test-lint
|
- test-lint
|
||||||
- test-deps
|
- test-deps
|
||||||
- test-unit
|
- test-unit
|
||||||
- test-e2e
|
- test-e2e-chrome
|
||||||
|
- test-e2e-firefox
|
||||||
- test-integration-mascara-chrome
|
- test-integration-mascara-chrome
|
||||||
- test-integration-mascara-firefox
|
- test-integration-mascara-firefox
|
||||||
- test-integration-flat-chrome
|
- test-integration-flat-chrome
|
||||||
@ -160,7 +166,7 @@ jobs:
|
|||||||
name: Test
|
name: Test
|
||||||
command: npx nsp check
|
command: npx nsp check
|
||||||
|
|
||||||
test-e2e:
|
test-e2e-chrome:
|
||||||
docker:
|
docker:
|
||||||
- image: circleci/node:8-browsers
|
- image: circleci/node:8-browsers
|
||||||
steps:
|
steps:
|
||||||
@ -171,7 +177,34 @@ jobs:
|
|||||||
key: build-cache-{{ .Revision }}
|
key: build-cache-{{ .Revision }}
|
||||||
- run:
|
- run:
|
||||||
name: Test
|
name: Test
|
||||||
command: npm run test:e2e
|
command: npm run test:e2e:chrome
|
||||||
|
- store_artifacts:
|
||||||
|
path: test-artifacts
|
||||||
|
destination: test-artifacts
|
||||||
|
|
||||||
|
test-e2e-firefox:
|
||||||
|
environment:
|
||||||
|
browsers: '["Firefox"]'
|
||||||
|
docker:
|
||||||
|
- image: circleci/node:8-browsers
|
||||||
|
steps:
|
||||||
|
- checkout
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-firefox-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: Install firefox
|
||||||
|
command: >
|
||||||
|
sudo rm -r /opt/firefox
|
||||||
|
&& sudo mv firefox /opt/firefox58
|
||||||
|
&& sudo mv /usr/bin/firefox /usr/bin/firefox-old
|
||||||
|
&& sudo ln -s /opt/firefox58/firefox /usr/bin/firefox
|
||||||
|
- restore_cache:
|
||||||
|
key: dependency-cache-{{ .Revision }}
|
||||||
|
- restore_cache:
|
||||||
|
key: build-cache-{{ .Revision }}
|
||||||
|
- run:
|
||||||
|
name: test:e2e:firefox
|
||||||
|
command: npm run test:e2e:firefox
|
||||||
- store_artifacts:
|
- store_artifacts:
|
||||||
path: test-artifacts
|
path: test-artifacts
|
||||||
destination: test-artifacts
|
destination: test-artifacts
|
||||||
@ -335,3 +368,4 @@ jobs:
|
|||||||
- run:
|
- run:
|
||||||
name: All Tests Passed
|
name: All Tests Passed
|
||||||
command: echo 'weew - everything passed!'
|
command: echo 'weew - everything passed!'
|
||||||
|
|
5
.nsprc
5
.nsprc
@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"exceptions": ["https://nodesecurity.io/advisories/566"]
|
"exceptions": [
|
||||||
|
"https://nodesecurity.io/advisories/566",
|
||||||
|
"https://nodesecurity.io/advisories/157"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -393,6 +393,9 @@
|
|||||||
"message": "Imported",
|
"message": "Imported",
|
||||||
"description": "status showing that an account has been fully loaded into the keyring"
|
"description": "status showing that an account has been fully loaded into the keyring"
|
||||||
},
|
},
|
||||||
|
"importUsingSeed": {
|
||||||
|
"message": "Import using account seed phrase"
|
||||||
|
},
|
||||||
"infoHelp": {
|
"infoHelp": {
|
||||||
"message": "Info & Help"
|
"message": "Info & Help"
|
||||||
},
|
},
|
||||||
@ -632,7 +635,7 @@
|
|||||||
"message": "Reset Account"
|
"message": "Reset Account"
|
||||||
},
|
},
|
||||||
"restoreFromSeed": {
|
"restoreFromSeed": {
|
||||||
"message": "Restore from seed phrase"
|
"message": "Restore account?"
|
||||||
},
|
},
|
||||||
"restoreVault": {
|
"restoreVault": {
|
||||||
"message": "Restore Vault"
|
"message": "Restore Vault"
|
||||||
@ -721,7 +724,7 @@
|
|||||||
"message": "New Password (min 8 chars)"
|
"message": "New Password (min 8 chars)"
|
||||||
},
|
},
|
||||||
"seedPhraseReq": {
|
"seedPhraseReq": {
|
||||||
"message": "seed phrases are 12 words long"
|
"message": "Seed phrases are 12 words long"
|
||||||
},
|
},
|
||||||
"select": {
|
"select": {
|
||||||
"message": "Select"
|
"message": "Select"
|
||||||
@ -896,6 +899,9 @@
|
|||||||
"unknownNetworkId": {
|
"unknownNetworkId": {
|
||||||
"message": "Unknown network ID"
|
"message": "Unknown network ID"
|
||||||
},
|
},
|
||||||
|
"unlockMessage": {
|
||||||
|
"message": "The decentralized web awaits"
|
||||||
|
},
|
||||||
"uriErrorMsg": {
|
"uriErrorMsg": {
|
||||||
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
||||||
},
|
},
|
||||||
@ -924,6 +930,9 @@
|
|||||||
"warning": {
|
"warning": {
|
||||||
"message": "Warning"
|
"message": "Warning"
|
||||||
},
|
},
|
||||||
|
"welcomeBack": {
|
||||||
|
"message": "Welcome Back!"
|
||||||
|
},
|
||||||
"welcomeBeta": {
|
"welcomeBeta": {
|
||||||
"message": "Welcome to MetaMask Beta"
|
"message": "Welcome to MetaMask Beta"
|
||||||
},
|
},
|
||||||
|
@ -14,9 +14,15 @@
|
|||||||
"address": {
|
"address": {
|
||||||
"message": "地址"
|
"message": "地址"
|
||||||
},
|
},
|
||||||
|
"addCustomToken": {
|
||||||
|
"message": "添加自定义代币"
|
||||||
|
},
|
||||||
"addToken": {
|
"addToken": {
|
||||||
"message": "添加代币"
|
"message": "添加代币"
|
||||||
},
|
},
|
||||||
|
"addTokens": {
|
||||||
|
"message": "添加代币"
|
||||||
|
},
|
||||||
"amount": {
|
"amount": {
|
||||||
"message": "数量"
|
"message": "数量"
|
||||||
},
|
},
|
||||||
@ -31,9 +37,15 @@
|
|||||||
"message": "MetaMask",
|
"message": "MetaMask",
|
||||||
"description": "The name of the application"
|
"description": "The name of the application"
|
||||||
},
|
},
|
||||||
|
"approved": {
|
||||||
|
"message": "批准"
|
||||||
|
},
|
||||||
"attemptingConnect": {
|
"attemptingConnect": {
|
||||||
"message": "正在尝试连接区块链。"
|
"message": "正在尝试连接区块链。"
|
||||||
},
|
},
|
||||||
|
"attributions": {
|
||||||
|
"message": "来源"
|
||||||
|
},
|
||||||
"available": {
|
"available": {
|
||||||
"message": "可用"
|
"message": "可用"
|
||||||
},
|
},
|
||||||
@ -43,6 +55,9 @@
|
|||||||
"balance": {
|
"balance": {
|
||||||
"message": "余额:"
|
"message": "余额:"
|
||||||
},
|
},
|
||||||
|
"balances": {
|
||||||
|
"message": "代币余额"
|
||||||
|
},
|
||||||
"balanceIsInsufficientGas": {
|
"balanceIsInsufficientGas": {
|
||||||
"message": "当前余额不足以支付 Gas"
|
"message": "当前余额不足以支付 Gas"
|
||||||
},
|
},
|
||||||
@ -53,9 +68,15 @@
|
|||||||
"message": "必须大于等于 $1 并且小于等于 $2 。",
|
"message": "必须大于等于 $1 并且小于等于 $2 。",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
},
|
},
|
||||||
|
"blockiesIdenticon": {
|
||||||
|
"message": "使用区块Identicon"
|
||||||
|
},
|
||||||
"borrowDharma": {
|
"borrowDharma": {
|
||||||
"message": "Borrow With Dharma (Beta)"
|
"message": "Borrow With Dharma (Beta)"
|
||||||
},
|
},
|
||||||
|
"builtInCalifornia": {
|
||||||
|
"message": "MetaMask在加利福尼亚设计和制造。"
|
||||||
|
},
|
||||||
"buy": {
|
"buy": {
|
||||||
"message": "购买"
|
"message": "购买"
|
||||||
},
|
},
|
||||||
@ -65,15 +86,27 @@
|
|||||||
"buyCoinbaseExplainer": {
|
"buyCoinbaseExplainer": {
|
||||||
"message": "Coinbase 是世界上最流行的买卖比特币,以太币和莱特币的交易所。"
|
"message": "Coinbase 是世界上最流行的买卖比特币,以太币和莱特币的交易所。"
|
||||||
},
|
},
|
||||||
|
"ok": {
|
||||||
|
"message": "确认"
|
||||||
|
},
|
||||||
"cancel": {
|
"cancel": {
|
||||||
"message": "取消"
|
"message": "取消"
|
||||||
},
|
},
|
||||||
|
"classicInterface": {
|
||||||
|
"message": "使用经典接口"
|
||||||
|
},
|
||||||
"clickCopy": {
|
"clickCopy": {
|
||||||
"message": "点击复制"
|
"message": "点击复制"
|
||||||
},
|
},
|
||||||
|
"close": {
|
||||||
|
"message": "关闭"
|
||||||
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"message": "确认"
|
"message": "确认"
|
||||||
},
|
},
|
||||||
|
"confirmed": {
|
||||||
|
"message": "确认"
|
||||||
|
},
|
||||||
"confirmContract": {
|
"confirmContract": {
|
||||||
"message": "确认合约"
|
"message": "确认合约"
|
||||||
},
|
},
|
||||||
@ -83,6 +116,9 @@
|
|||||||
"confirmTransaction": {
|
"confirmTransaction": {
|
||||||
"message": "确认交易"
|
"message": "确认交易"
|
||||||
},
|
},
|
||||||
|
"continue": {
|
||||||
|
"message": "继续"
|
||||||
|
},
|
||||||
"continueToCoinbase": {
|
"continueToCoinbase": {
|
||||||
"message": "继续访问 Coinbase"
|
"message": "继续访问 Coinbase"
|
||||||
},
|
},
|
||||||
@ -99,7 +135,10 @@
|
|||||||
"message": "已复制到剪贴板"
|
"message": "已复制到剪贴板"
|
||||||
},
|
},
|
||||||
"copiedExclamation": {
|
"copiedExclamation": {
|
||||||
"message": "已复制!"
|
"message": "已复制"
|
||||||
|
},
|
||||||
|
"copiedSafe": {
|
||||||
|
"message": "我已将它复制保存到某个安全的地方"
|
||||||
},
|
},
|
||||||
"copy": {
|
"copy": {
|
||||||
"message": "复制"
|
"message": "复制"
|
||||||
@ -126,15 +165,30 @@
|
|||||||
"message": "加密",
|
"message": "加密",
|
||||||
"description": "Exchange type (cryptocurrencies)"
|
"description": "Exchange type (cryptocurrencies)"
|
||||||
},
|
},
|
||||||
|
"currentConversion": {
|
||||||
|
"message": "当前汇率"
|
||||||
|
},
|
||||||
|
"currentNetwork": {
|
||||||
|
"message": "当前网络"
|
||||||
|
},
|
||||||
"customGas": {
|
"customGas": {
|
||||||
"message": "自定义 Gas"
|
"message": "自定义 Gas"
|
||||||
},
|
},
|
||||||
|
"customToken": {
|
||||||
|
"message": "自定义代币"
|
||||||
|
},
|
||||||
"customize": {
|
"customize": {
|
||||||
"message": "自定义"
|
"message": "自定义"
|
||||||
},
|
},
|
||||||
"customRPC": {
|
"customRPC": {
|
||||||
"message": "自定义 RPC"
|
"message": "自定义 RPC"
|
||||||
},
|
},
|
||||||
|
"decimalsMustZerotoTen": {
|
||||||
|
"message": "小数位最小为0并且不超过36位."
|
||||||
|
},
|
||||||
|
"decimal": {
|
||||||
|
"message": "精确小数点"
|
||||||
|
},
|
||||||
"defaultNetwork": {
|
"defaultNetwork": {
|
||||||
"message": "默认以太坊交易网络为主网。"
|
"message": "默认以太坊交易网络为主网。"
|
||||||
},
|
},
|
||||||
@ -184,18 +238,39 @@
|
|||||||
"done": {
|
"done": {
|
||||||
"message": "完成"
|
"message": "完成"
|
||||||
},
|
},
|
||||||
|
"downloadStateLogs": {
|
||||||
|
"message": "下载日志"
|
||||||
|
},
|
||||||
|
"dropped": {
|
||||||
|
"message": "丢弃"
|
||||||
|
},
|
||||||
"edit": {
|
"edit": {
|
||||||
"message": "编辑"
|
"message": "编辑"
|
||||||
},
|
},
|
||||||
"editAccountName": {
|
"editAccountName": {
|
||||||
"message": "编辑账户名称"
|
"message": "编辑账户名称"
|
||||||
},
|
},
|
||||||
|
"emailUs": {
|
||||||
|
"message": "联系我们"
|
||||||
|
},
|
||||||
"encryptNewDen": {
|
"encryptNewDen": {
|
||||||
"message": "加密你的新 DEN"
|
"message": "加密你的新 DEN"
|
||||||
},
|
},
|
||||||
"enterPassword": {
|
"enterPassword": {
|
||||||
"message": "请输入密码"
|
"message": "请输入密码"
|
||||||
},
|
},
|
||||||
|
"enterPasswordConfirm": {
|
||||||
|
"message": "请输入密码以确认"
|
||||||
|
},
|
||||||
|
"enterPasswordContinue": {
|
||||||
|
"message": "请输入密码以继续"
|
||||||
|
},
|
||||||
|
"passwordNotLongEnough": {
|
||||||
|
"message": "密码长度不足"
|
||||||
|
},
|
||||||
|
"passwordsDontMatch": {
|
||||||
|
"message": "密码不匹配"
|
||||||
|
},
|
||||||
"etherscanView": {
|
"etherscanView": {
|
||||||
"message": "在 Etherscan 上查看账户"
|
"message": "在 Etherscan 上查看账户"
|
||||||
},
|
},
|
||||||
@ -219,9 +294,15 @@
|
|||||||
"message": "文件导入失败? 点击这里!",
|
"message": "文件导入失败? 点击这里!",
|
||||||
"description": "Helps user import their account from a JSON file"
|
"description": "Helps user import their account from a JSON file"
|
||||||
},
|
},
|
||||||
|
"followTwitter": {
|
||||||
|
"message": "关注我们的Twitter"
|
||||||
|
},
|
||||||
"from": {
|
"from": {
|
||||||
"message": "来自"
|
"message": "来自"
|
||||||
},
|
},
|
||||||
|
"fromToSame": {
|
||||||
|
"message": "发送和接受地址不能相同"
|
||||||
|
},
|
||||||
"fromShapeShift": {
|
"fromShapeShift": {
|
||||||
"message": "来自 ShapeShift"
|
"message": "来自 ShapeShift"
|
||||||
},
|
},
|
||||||
@ -244,6 +325,9 @@
|
|||||||
"gasLimitTooLow": {
|
"gasLimitTooLow": {
|
||||||
"message": "Gas Limit 至少要 21000"
|
"message": "Gas Limit 至少要 21000"
|
||||||
},
|
},
|
||||||
|
"generatingSeed": {
|
||||||
|
"message": "生成密钥中..."
|
||||||
|
},
|
||||||
"gasPrice": {
|
"gasPrice": {
|
||||||
"message": "Gas Price (GWEI)"
|
"message": "Gas Price (GWEI)"
|
||||||
},
|
},
|
||||||
@ -253,6 +337,9 @@
|
|||||||
"gasPriceRequired": {
|
"gasPriceRequired": {
|
||||||
"message": "Gas Price 必填"
|
"message": "Gas Price 必填"
|
||||||
},
|
},
|
||||||
|
"generatingTransaction": {
|
||||||
|
"message": "生成 交易"
|
||||||
|
},
|
||||||
"getEther": {
|
"getEther": {
|
||||||
"message": "获取 Ether"
|
"message": "获取 Ether"
|
||||||
},
|
},
|
||||||
@ -268,6 +355,9 @@
|
|||||||
"message": "这里",
|
"message": "这里",
|
||||||
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
|
"description": "as in -click here- for more information (goes with troubleTokenBalances)"
|
||||||
},
|
},
|
||||||
|
"hereList": {
|
||||||
|
"message": "Here's a list!!!!"
|
||||||
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"message": "隐藏"
|
"message": "隐藏"
|
||||||
},
|
},
|
||||||
@ -280,6 +370,9 @@
|
|||||||
"howToDeposit": {
|
"howToDeposit": {
|
||||||
"message": "你想怎样转入 Ether?"
|
"message": "你想怎样转入 Ether?"
|
||||||
},
|
},
|
||||||
|
"holdEther": {
|
||||||
|
"message": "它允许你保存ether和代币,并作为你使用Dapp的桥梁."
|
||||||
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"message": "导入",
|
"message": "导入",
|
||||||
"description": "Button to import an account from a selected file"
|
"description": "Button to import an account from a selected file"
|
||||||
@ -287,6 +380,9 @@
|
|||||||
"importAccount": {
|
"importAccount": {
|
||||||
"message": "导入账户"
|
"message": "导入账户"
|
||||||
},
|
},
|
||||||
|
"importAccountMsg": {
|
||||||
|
"message":" Imported accounts will not be associated with your originally created MetaMask account seedphrase. Learn more about imported accounts "
|
||||||
|
},
|
||||||
"importAnAccount": {
|
"importAnAccount": {
|
||||||
"message": "导入一个账户"
|
"message": "导入一个账户"
|
||||||
},
|
},
|
||||||
@ -294,46 +390,82 @@
|
|||||||
"message": "导入存在的 DEN"
|
"message": "导入存在的 DEN"
|
||||||
},
|
},
|
||||||
"imported": {
|
"imported": {
|
||||||
"message": "已导入私钥",
|
"message": "已导入",
|
||||||
"description": "status showing that an account has been fully loaded into the keyring"
|
"description": "status showing that an account has been fully loaded into the keyring"
|
||||||
},
|
},
|
||||||
"infoHelp": {
|
"infoHelp": {
|
||||||
"message": "信息 & 帮助"
|
"message": "信息 & 帮助"
|
||||||
},
|
},
|
||||||
|
"insufficientFunds": {
|
||||||
|
"message": "余额不足."
|
||||||
|
},
|
||||||
|
"insufficientTokens": {
|
||||||
|
"message": "代币余额不足."
|
||||||
|
},
|
||||||
"invalidAddress": {
|
"invalidAddress": {
|
||||||
"message": "错误的地址"
|
"message": "无效地址"
|
||||||
|
},
|
||||||
|
"invalidAddressRecipient": {
|
||||||
|
"message": "收款地址不合法"
|
||||||
},
|
},
|
||||||
"invalidGasParams": {
|
"invalidGasParams": {
|
||||||
"message": "错误的 Gas 参数"
|
"message": "无效 Gas 参数"
|
||||||
},
|
},
|
||||||
"invalidInput": {
|
"invalidInput": {
|
||||||
"message": "错误的输入。"
|
"message": "无效输入."
|
||||||
},
|
},
|
||||||
"invalidRequest": {
|
"invalidRequest": {
|
||||||
"message": "无效请求"
|
"message": "无效请求"
|
||||||
},
|
},
|
||||||
|
"invalidRPC": {
|
||||||
|
"message": "无效 RPC URI"
|
||||||
|
},
|
||||||
|
"jsonFail": {
|
||||||
|
"message": "Something went wrong. Please make sure your JSON file is properly formatted."
|
||||||
|
},
|
||||||
"jsonFile": {
|
"jsonFile": {
|
||||||
"message": "JSON 文件",
|
"message": "JSON 文件",
|
||||||
"description": "format for importing an account"
|
"description": "format for importing an account"
|
||||||
},
|
},
|
||||||
|
"keepTrackTokens": {
|
||||||
|
"message": "Keep track of the tokens you’ve bought with your MetaMask account."
|
||||||
|
},
|
||||||
"kovan": {
|
"kovan": {
|
||||||
"message": "Kovan 测试网络"
|
"message": "Kovan 测试网络"
|
||||||
},
|
},
|
||||||
|
"knowledgeDataBase": {
|
||||||
|
"message": "浏览我们的知识库"
|
||||||
|
},
|
||||||
|
"max": {
|
||||||
|
"message": "最大"
|
||||||
|
},
|
||||||
|
"learnMore": {
|
||||||
|
"message": "查看更多."
|
||||||
|
},
|
||||||
"lessThanMax": {
|
"lessThanMax": {
|
||||||
"message": "必须小于等于 $1.",
|
"message": "必须小于或等于 $1.",
|
||||||
"description": "helper for inputting hex as decimal input"
|
"description": "helper for inputting hex as decimal input"
|
||||||
},
|
},
|
||||||
|
"likeToAddTokens": {
|
||||||
|
"message": "你想添加这些代币吗?"
|
||||||
|
},
|
||||||
|
"links": {
|
||||||
|
"message": "链接"
|
||||||
|
},
|
||||||
"limit": {
|
"limit": {
|
||||||
"message": "限定"
|
"message": "限制"
|
||||||
},
|
},
|
||||||
"loading": {
|
"loading": {
|
||||||
"message": "加载..."
|
"message": "加载中..."
|
||||||
},
|
},
|
||||||
"loadingTokens": {
|
"loadingTokens": {
|
||||||
"message": "加载代币..."
|
"message": "加载代币中..."
|
||||||
},
|
},
|
||||||
"localhost": {
|
"localhost": {
|
||||||
"message": "本地主机 8545"
|
"message": "Localhost 8545"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"message": "登录"
|
||||||
},
|
},
|
||||||
"logout": {
|
"logout": {
|
||||||
"message": "登出"
|
"message": "登出"
|
||||||
@ -341,17 +473,29 @@
|
|||||||
"loose": {
|
"loose": {
|
||||||
"message": "疏松"
|
"message": "疏松"
|
||||||
},
|
},
|
||||||
|
"loweCaseWords": {
|
||||||
|
"message": "助记词只有小写字符"
|
||||||
|
},
|
||||||
"mainnet": {
|
"mainnet": {
|
||||||
"message": "以太坊主网络"
|
"message": "以太坊主网络"
|
||||||
},
|
},
|
||||||
"message": {
|
"message": {
|
||||||
"message": "消息"
|
"message": "消息"
|
||||||
},
|
},
|
||||||
|
"metamaskDescription": {
|
||||||
|
"message": "MetaMask is a secure identity vault for Ethereum."
|
||||||
|
},
|
||||||
|
"metamaskSeedWords": {
|
||||||
|
"message": "MetaMask 助记词"
|
||||||
|
},
|
||||||
"min": {
|
"min": {
|
||||||
"message": "最小"
|
"message": "最小"
|
||||||
},
|
},
|
||||||
"myAccounts": {
|
"myAccounts": {
|
||||||
"message": "我的账户"
|
"message": "My Accounts"
|
||||||
|
},
|
||||||
|
"mustSelectOne": {
|
||||||
|
"message": "至少选择一种代币."
|
||||||
},
|
},
|
||||||
"needEtherInWallet": {
|
"needEtherInWallet": {
|
||||||
"message": "使用 MetaMask 与 DAPP 交互,需要你的钱包里有 Ether。"
|
"message": "使用 MetaMask 与 DAPP 交互,需要你的钱包里有 Ether。"
|
||||||
@ -361,9 +505,12 @@
|
|||||||
"description": "User is important an account and needs to add a file to continue"
|
"description": "User is important an account and needs to add a file to continue"
|
||||||
},
|
},
|
||||||
"needImportPassword": {
|
"needImportPassword": {
|
||||||
"message": "必须为已选择的文件输入密码。",
|
"message": "必须为已选择的文件输入密码。",
|
||||||
"description": "Password and file needed to import an account"
|
"description": "Password and file needed to import an account"
|
||||||
},
|
},
|
||||||
|
"negativeETH": {
|
||||||
|
"message": "Can not send negative amounts of ETH."
|
||||||
|
},
|
||||||
"networks": {
|
"networks": {
|
||||||
"message": "网络"
|
"message": "网络"
|
||||||
},
|
},
|
||||||
@ -383,8 +530,11 @@
|
|||||||
"newRecipient": {
|
"newRecipient": {
|
||||||
"message": "新收款人"
|
"message": "新收款人"
|
||||||
},
|
},
|
||||||
|
"newRPC": {
|
||||||
|
"message": "新 RPC URL"
|
||||||
|
},
|
||||||
"next": {
|
"next": {
|
||||||
"message": "下一个"
|
"message": "下一步"
|
||||||
},
|
},
|
||||||
"noAddressForName": {
|
"noAddressForName": {
|
||||||
"message": "此 ENS 名字还没有指定地址。"
|
"message": "此 ENS 名字还没有指定地址。"
|
||||||
@ -405,12 +555,18 @@
|
|||||||
"message": "旧版界面"
|
"message": "旧版界面"
|
||||||
},
|
},
|
||||||
"oldUIMessage": {
|
"oldUIMessage": {
|
||||||
"message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
|
"message": "你已经切换到旧版界面。 你可以通过右上方下拉菜单中的选项切换回新的用户界面。"
|
||||||
},
|
},
|
||||||
"or": {
|
"or": {
|
||||||
"message": "或",
|
"message": "或",
|
||||||
"description": "choice between creating or importing a new account"
|
"description": "choice between creating or importing a new account"
|
||||||
},
|
},
|
||||||
|
"password": {
|
||||||
|
"message": "密码"
|
||||||
|
},
|
||||||
|
"passwordCorrect": {
|
||||||
|
"message": "Please make sure your password is correct."
|
||||||
|
},
|
||||||
"passwordMismatch": {
|
"passwordMismatch": {
|
||||||
"message": "密码不匹配",
|
"message": "密码不匹配",
|
||||||
"description": "in password creation process, the two new password fields did not match"
|
"description": "in password creation process, the two new password fields did not match"
|
||||||
@ -426,15 +582,24 @@
|
|||||||
"pasteSeed": {
|
"pasteSeed": {
|
||||||
"message": "请粘贴你的助记词!"
|
"message": "请粘贴你的助记词!"
|
||||||
},
|
},
|
||||||
|
"personalAddressDetected": {
|
||||||
|
"message": "检测到个人地址。请输入代币合约地址。"
|
||||||
|
},
|
||||||
"pleaseReviewTransaction": {
|
"pleaseReviewTransaction": {
|
||||||
"message": "请检查你的交易。"
|
"message": "请检查你的交易。"
|
||||||
},
|
},
|
||||||
|
"popularTokens": {
|
||||||
|
"message": "常用代币"
|
||||||
|
},
|
||||||
|
"privacyMsg": {
|
||||||
|
"message": "隐私政策"
|
||||||
|
},
|
||||||
"privateKey": {
|
"privateKey": {
|
||||||
"message": "私钥",
|
"message": "私钥",
|
||||||
"description": "select this type of file to use to import an account"
|
"description": "select this type of file to use to import an account"
|
||||||
},
|
},
|
||||||
"privateKeyWarning": {
|
"privateKeyWarning": {
|
||||||
"message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
|
"message": "注意:永远不要公开这个私钥。任何拥有你的私钥的人都可以窃取你帐户中的任何资产。"
|
||||||
},
|
},
|
||||||
"privateNetwork": {
|
"privateNetwork": {
|
||||||
"message": "私有网络"
|
"message": "私有网络"
|
||||||
@ -443,11 +608,14 @@
|
|||||||
"message": "显示二维码"
|
"message": "显示二维码"
|
||||||
},
|
},
|
||||||
"readdToken": {
|
"readdToken": {
|
||||||
"message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
|
"message": "之后你还可以通过帐户选项菜单中的“添加代币”来添加此代币。"
|
||||||
},
|
},
|
||||||
"readMore": {
|
"readMore": {
|
||||||
"message": "了解更多。"
|
"message": "了解更多。"
|
||||||
},
|
},
|
||||||
|
"readMore2": {
|
||||||
|
"message": "了解更多。"
|
||||||
|
},
|
||||||
"receive": {
|
"receive": {
|
||||||
"message": "接收"
|
"message": "接收"
|
||||||
},
|
},
|
||||||
@ -460,12 +628,39 @@
|
|||||||
"rejected": {
|
"rejected": {
|
||||||
"message": "拒绝"
|
"message": "拒绝"
|
||||||
},
|
},
|
||||||
|
"resetAccount": {
|
||||||
|
"message": "重设账户"
|
||||||
|
},
|
||||||
|
"restoreFromSeed": {
|
||||||
|
"message": "从助记词还原"
|
||||||
|
},
|
||||||
|
"restoreVault": {
|
||||||
|
"message": "还原保险柜"
|
||||||
|
},
|
||||||
"required": {
|
"required": {
|
||||||
"message": "必填"
|
"message": "必填"
|
||||||
},
|
},
|
||||||
"retryWithMoreGas": {
|
"retryWithMoreGas": {
|
||||||
"message": "使用更高的 Gas Price 重试"
|
"message": "使用更高的 Gas Price 重试"
|
||||||
},
|
},
|
||||||
|
"walletSeed": {
|
||||||
|
"message": "钱包助记词"
|
||||||
|
},
|
||||||
|
"revealSeedWords": {
|
||||||
|
"message": "显示助记词"
|
||||||
|
},
|
||||||
|
"revealSeedWordsTitle": {
|
||||||
|
"message": "助记词"
|
||||||
|
},
|
||||||
|
"revealSeedWordsDescription": {
|
||||||
|
"message": "如果您更换浏览器或计算机,则需要使用此助记词访问您的帐户。请将它们保存在安全秘密的地方。"
|
||||||
|
},
|
||||||
|
"revealSeedWordsWarningTitle": {
|
||||||
|
"message": "不要对任何人展示助记词!"
|
||||||
|
},
|
||||||
|
"revealSeedWordsWarning": {
|
||||||
|
"message": "助记词可以用来窃取您的所有帐户."
|
||||||
|
},
|
||||||
"revert": {
|
"revert": {
|
||||||
"message": "还原"
|
"message": "还原"
|
||||||
},
|
},
|
||||||
@ -475,6 +670,24 @@
|
|||||||
"ropsten": {
|
"ropsten": {
|
||||||
"message": "Ropsten 测试网络"
|
"message": "Ropsten 测试网络"
|
||||||
},
|
},
|
||||||
|
"currentRpc": {
|
||||||
|
"message": "当前 RPC"
|
||||||
|
},
|
||||||
|
"connectingToMainnet": {
|
||||||
|
"message": "正在连接到以太坊主网"
|
||||||
|
},
|
||||||
|
"connectingToRopsten": {
|
||||||
|
"message": "正在连接到Ropsten测试网络"
|
||||||
|
},
|
||||||
|
"connectingToKovan": {
|
||||||
|
"message": "正在连接到Kovan测试网络"
|
||||||
|
},
|
||||||
|
"connectingToRinkeby": {
|
||||||
|
"message": "正在连接到Rinkeby测试网络"
|
||||||
|
},
|
||||||
|
"connectingToUnknown": {
|
||||||
|
"message": "正在连接到未知网络"
|
||||||
|
},
|
||||||
"sampleAccountName": {
|
"sampleAccountName": {
|
||||||
"message": "例如:我的账户",
|
"message": "例如:我的账户",
|
||||||
"description": "Help user understand concept of adding a human-readable name to their account"
|
"description": "Help user understand concept of adding a human-readable name to their account"
|
||||||
@ -482,25 +695,70 @@
|
|||||||
"save": {
|
"save": {
|
||||||
"message": "保存"
|
"message": "保存"
|
||||||
},
|
},
|
||||||
|
"reprice_title": {
|
||||||
|
"message": "重新出价交易"
|
||||||
|
},
|
||||||
|
"reprice_subtitle": {
|
||||||
|
"message": "提高 GAS 价格尝试覆盖并加速交易"
|
||||||
|
},
|
||||||
|
"saveAsCsvFile": {
|
||||||
|
"message": "另存为CSV文件"
|
||||||
|
},
|
||||||
"saveAsFile": {
|
"saveAsFile": {
|
||||||
"message": "保存文件",
|
"message": "保存文件",
|
||||||
"description": "Account export process"
|
"description": "Account export process"
|
||||||
},
|
},
|
||||||
|
"saveSeedAsFile": {
|
||||||
|
"message": "保存助记词为文件"
|
||||||
|
},
|
||||||
|
"search": {
|
||||||
|
"message": "搜索"
|
||||||
|
},
|
||||||
|
"secretPhrase": {
|
||||||
|
"message": "输入12位助记词以恢复金库."
|
||||||
|
},
|
||||||
|
"newPassword8Chars": {
|
||||||
|
"message": "新密码(至少8位)"
|
||||||
|
},
|
||||||
|
"seedPhraseReq": {
|
||||||
|
"message": "助记词为12个单词"
|
||||||
|
},
|
||||||
|
"select": {
|
||||||
|
"message": "选择"
|
||||||
|
},
|
||||||
|
"selectCurrency": {
|
||||||
|
"message": "选择货币"
|
||||||
|
},
|
||||||
"selectService": {
|
"selectService": {
|
||||||
"message": "选择服务"
|
"message": "选择服务"
|
||||||
},
|
},
|
||||||
|
"selectType": {
|
||||||
|
"message": "选择类型"
|
||||||
|
},
|
||||||
"send": {
|
"send": {
|
||||||
"message": "发送"
|
"message": "发送"
|
||||||
},
|
},
|
||||||
|
"sendETH": {
|
||||||
|
"message": "发送 ETH"
|
||||||
|
},
|
||||||
"sendTokens": {
|
"sendTokens": {
|
||||||
"message": "发送代币"
|
"message": "发送 代币"
|
||||||
|
},
|
||||||
|
"onlySendToEtherAddress": {
|
||||||
|
"message": "只发送 ETH 给一个以太坊地址"
|
||||||
|
},
|
||||||
|
"searchTokens": {
|
||||||
|
"message": "搜索代币"
|
||||||
},
|
},
|
||||||
"sendTokensAnywhere": {
|
"sendTokensAnywhere": {
|
||||||
"message": "发送代币给拥有以太坊账户的任何人"
|
"message": "将代币发送给拥有以太坊地址的任何人"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"message": "设置"
|
"message": "设置"
|
||||||
},
|
},
|
||||||
|
"info": {
|
||||||
|
"message": "信息"
|
||||||
|
},
|
||||||
"shapeshiftBuy": {
|
"shapeshiftBuy": {
|
||||||
"message": "使用 Shapeshift 购买"
|
"message": "使用 Shapeshift 购买"
|
||||||
},
|
},
|
||||||
@ -513,6 +771,9 @@
|
|||||||
"sign": {
|
"sign": {
|
||||||
"message": "签名"
|
"message": "签名"
|
||||||
},
|
},
|
||||||
|
"signed": {
|
||||||
|
"message": "已签名"
|
||||||
|
},
|
||||||
"signMessage": {
|
"signMessage": {
|
||||||
"message": "签署消息"
|
"message": "签署消息"
|
||||||
},
|
},
|
||||||
@ -525,15 +786,39 @@
|
|||||||
"sigRequested": {
|
"sigRequested": {
|
||||||
"message": "签名已请求"
|
"message": "签名已请求"
|
||||||
},
|
},
|
||||||
|
"spaceBetween": {
|
||||||
|
"message": "单词之间只能有一个空格"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"message": "状态"
|
"message": "状态"
|
||||||
},
|
},
|
||||||
|
"stateLogs": {
|
||||||
|
"message": "状态日志"
|
||||||
|
},
|
||||||
|
"stateLogsDescription": {
|
||||||
|
"message": "状态日志包含您的账户地址和已发送的交易。"
|
||||||
|
},
|
||||||
|
"stateLogError": {
|
||||||
|
"message": "检索状态日志时出错。"
|
||||||
|
},
|
||||||
"submit": {
|
"submit": {
|
||||||
"message": "提交"
|
"message": "提交"
|
||||||
},
|
},
|
||||||
|
"submitted": {
|
||||||
|
"message": "已提交"
|
||||||
|
},
|
||||||
|
"supportCenter": {
|
||||||
|
"message": "访问我们的支持中心"
|
||||||
|
},
|
||||||
|
"symbolBetweenZeroTen": {
|
||||||
|
"message": "符号应该有0-10个字符."
|
||||||
|
},
|
||||||
"takesTooLong": {
|
"takesTooLong": {
|
||||||
"message": "花费太长时间?"
|
"message": "花费太长时间?"
|
||||||
},
|
},
|
||||||
|
"terms": {
|
||||||
|
"message": "使用条款"
|
||||||
|
},
|
||||||
"testFaucet": {
|
"testFaucet": {
|
||||||
"message": "测试水管"
|
"message": "测试水管"
|
||||||
},
|
},
|
||||||
@ -544,33 +829,60 @@
|
|||||||
"message": "$1 ETH 通过 ShapeShift",
|
"message": "$1 ETH 通过 ShapeShift",
|
||||||
"description": "system will fill in deposit type in start of message"
|
"description": "system will fill in deposit type in start of message"
|
||||||
},
|
},
|
||||||
|
"tokenAddress": {
|
||||||
|
"message": "代币地址"
|
||||||
|
},
|
||||||
|
"tokenAlreadyAdded": {
|
||||||
|
"message": "代币已经被添加."
|
||||||
|
},
|
||||||
"tokenBalance": {
|
"tokenBalance": {
|
||||||
"message": "代币余额:"
|
"message": "代币余额:"
|
||||||
},
|
},
|
||||||
|
"tokenSelection": {
|
||||||
|
"message": "搜索代币或从我们的常用代币列表中进行选择"
|
||||||
|
},
|
||||||
|
"tokenSymbol": {
|
||||||
|
"message": "代币符号"
|
||||||
|
},
|
||||||
|
"tokenWarning1": {
|
||||||
|
"message": "Keep track of the tokens you’ve bought with your MetaMask account. If you bought tokens using a different account, those tokens will not appear here."
|
||||||
|
},
|
||||||
"total": {
|
"total": {
|
||||||
"message": "总量"
|
"message": "总量"
|
||||||
},
|
},
|
||||||
|
"transactions": {
|
||||||
|
"message": "交易"
|
||||||
|
},
|
||||||
|
"transactionError": {
|
||||||
|
"message": "交易出错. 合约代码执行异常."
|
||||||
|
},
|
||||||
"transactionMemo": {
|
"transactionMemo": {
|
||||||
"message": "交易备注 (可选)"
|
"message": "交易备注(可选)"
|
||||||
},
|
},
|
||||||
"transactionNumber": {
|
"transactionNumber": {
|
||||||
"message": "交易号"
|
"message": "交易 number"
|
||||||
},
|
},
|
||||||
"transfers": {
|
"transfers": {
|
||||||
"message": "Transfers"
|
"message": "交易"
|
||||||
},
|
},
|
||||||
"troubleTokenBalances": {
|
"troubleTokenBalances": {
|
||||||
"message": "无法加载代币余额。你可以再这里查看 ",
|
"message": "我们无法加载您的代币余额。你可以查看它们",
|
||||||
"description": "Followed by a link (here) to view token balances"
|
"description": "Followed by a link (here) to view token balances"
|
||||||
},
|
},
|
||||||
|
"twelveWords": {
|
||||||
|
"message": "这12个单词是恢复MetaMask帐户的唯一方法。.\n将它们存放在安全和秘密的地方。."
|
||||||
|
},
|
||||||
"typePassword": {
|
"typePassword": {
|
||||||
"message": "请输入密码"
|
"message": "输入你的密码"
|
||||||
},
|
},
|
||||||
"uiWelcome": {
|
"uiWelcome": {
|
||||||
"message": "欢迎使用新版界面 (Beta)"
|
"message": "欢迎使用新版界面 (Beta)"
|
||||||
},
|
},
|
||||||
"uiWelcomeMessage": {
|
"uiWelcomeMessage": {
|
||||||
"message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
"message": "你现在正在使用新的 Metamask 界面。 尝试发送代币等新功能,有任何问题请告知我们。"
|
||||||
|
},
|
||||||
|
"unapproved": {
|
||||||
|
"message": "未批准"
|
||||||
},
|
},
|
||||||
"unavailable": {
|
"unavailable": {
|
||||||
"message": "不可用"
|
"message": "不可用"
|
||||||
@ -582,7 +894,10 @@
|
|||||||
"message": "未知私有网络"
|
"message": "未知私有网络"
|
||||||
},
|
},
|
||||||
"unknownNetworkId": {
|
"unknownNetworkId": {
|
||||||
"message": "未知网络 ID"
|
"message": "未知网络ID"
|
||||||
|
},
|
||||||
|
"uriErrorMsg": {
|
||||||
|
"message": "URIs require the appropriate HTTP/HTTPS prefix."
|
||||||
},
|
},
|
||||||
"usaOnly": {
|
"usaOnly": {
|
||||||
"message": "只限于美国",
|
"message": "只限于美国",
|
||||||
@ -591,12 +906,27 @@
|
|||||||
"usedByClients": {
|
"usedByClients": {
|
||||||
"message": "可用于各种不同的客户端"
|
"message": "可用于各种不同的客户端"
|
||||||
},
|
},
|
||||||
|
"useOldUI": {
|
||||||
|
"message": "使用旧版 UI"
|
||||||
|
},
|
||||||
|
"validFileImport": {
|
||||||
|
"message": "您必须选择一个有效的文件进行导入."
|
||||||
|
},
|
||||||
|
"vaultCreated": {
|
||||||
|
"message": "已创建保险库"
|
||||||
|
},
|
||||||
"viewAccount": {
|
"viewAccount": {
|
||||||
"message": "查看账户"
|
"message": "查看账户"
|
||||||
},
|
},
|
||||||
|
"visitWebSite": {
|
||||||
|
"message": "访问我们的网站"
|
||||||
|
},
|
||||||
"warning": {
|
"warning": {
|
||||||
"message": "警告"
|
"message": "警告"
|
||||||
},
|
},
|
||||||
|
"welcomeBeta": {
|
||||||
|
"message": "欢迎使用 MetaMask 测试版"
|
||||||
|
},
|
||||||
"whatsThis": {
|
"whatsThis": {
|
||||||
"message": "这是什么?"
|
"message": "这是什么?"
|
||||||
},
|
},
|
||||||
@ -605,5 +935,8 @@
|
|||||||
},
|
},
|
||||||
"youSign": {
|
"youSign": {
|
||||||
"message": "正在签名"
|
"message": "正在签名"
|
||||||
|
},
|
||||||
|
"yourPrivateSeedPhrase": {
|
||||||
|
"message": "你的私有助记词"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,20 +13,6 @@ const RINKEBY_DISPLAY_NAME = 'Rinkeby'
|
|||||||
const KOVAN_DISPLAY_NAME = 'Kovan'
|
const KOVAN_DISPLAY_NAME = 'Kovan'
|
||||||
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
const MAINNET_DISPLAY_NAME = 'Main Ethereum Network'
|
||||||
|
|
||||||
const MAINNET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
|
||||||
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask'
|
|
||||||
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask'
|
|
||||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask'
|
|
||||||
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
|
||||||
|
|
||||||
const MAINNET_RPC_URL_BETA = 'https://mainnet.infura.io/metamask2'
|
|
||||||
const ROPSTEN_RPC_URL_BETA = 'https://ropsten.infura.io/metamask2'
|
|
||||||
const KOVAN_RPC_URL_BETA = 'https://kovan.infura.io/metamask2'
|
|
||||||
const RINKEBY_RPC_URL_BETA = 'https://rinkeby.infura.io/metamask2'
|
|
||||||
|
|
||||||
const DEFAULT_NETWORK = 'rinkeby'
|
|
||||||
const OLD_UI_NETWORK_TYPE = 'network'
|
|
||||||
const BETA_UI_NETWORK_TYPE = 'networkBeta'
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
ROPSTEN,
|
ROPSTEN,
|
||||||
@ -41,16 +27,4 @@ module.exports = {
|
|||||||
RINKEBY_DISPLAY_NAME,
|
RINKEBY_DISPLAY_NAME,
|
||||||
KOVAN_DISPLAY_NAME,
|
KOVAN_DISPLAY_NAME,
|
||||||
MAINNET_DISPLAY_NAME,
|
MAINNET_DISPLAY_NAME,
|
||||||
MAINNET_RPC_URL,
|
|
||||||
ROPSTEN_RPC_URL,
|
|
||||||
KOVAN_RPC_URL,
|
|
||||||
RINKEBY_RPC_URL,
|
|
||||||
LOCALHOST_RPC_URL,
|
|
||||||
MAINNET_RPC_URL_BETA,
|
|
||||||
ROPSTEN_RPC_URL_BETA,
|
|
||||||
KOVAN_RPC_URL_BETA,
|
|
||||||
RINKEBY_RPC_URL_BETA,
|
|
||||||
DEFAULT_NETWORK,
|
|
||||||
OLD_UI_NETWORK_TYPE,
|
|
||||||
BETA_UI_NETWORK_TYPE,
|
|
||||||
}
|
}
|
||||||
|
@ -14,52 +14,40 @@ const {
|
|||||||
RINKEBY,
|
RINKEBY,
|
||||||
KOVAN,
|
KOVAN,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
OLD_UI_NETWORK_TYPE,
|
LOCALHOST,
|
||||||
DEFAULT_NETWORK,
|
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
const { getNetworkEndpoints } = require('./util')
|
const LOCALHOST_RPC_URL = 'http://localhost:8545'
|
||||||
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
const INFURA_PROVIDER_TYPES = [ROPSTEN, RINKEBY, KOVAN, MAINNET]
|
||||||
|
|
||||||
|
const env = process.env.METAMASK_ENV
|
||||||
|
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
||||||
|
const testMode = (METAMASK_DEBUG || env === 'test')
|
||||||
|
|
||||||
|
const defaultProviderConfig = {
|
||||||
|
type: testMode ? RINKEBY : MAINNET,
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = class NetworkController extends EventEmitter {
|
module.exports = class NetworkController extends EventEmitter {
|
||||||
|
|
||||||
constructor (config) {
|
constructor (opts = {}) {
|
||||||
super()
|
super()
|
||||||
|
|
||||||
this._networkEndpointVersion = OLD_UI_NETWORK_TYPE
|
// parse options
|
||||||
this._networkEndpoints = getNetworkEndpoints(OLD_UI_NETWORK_TYPE)
|
const providerConfig = opts.provider || defaultProviderConfig
|
||||||
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
|
// create stores
|
||||||
|
this.providerStore = new ObservableStore(providerConfig)
|
||||||
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider)
|
|
||||||
this.networkStore = new ObservableStore('loading')
|
this.networkStore = new ObservableStore('loading')
|
||||||
this.providerStore = new ObservableStore(config.provider)
|
|
||||||
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore })
|
||||||
|
// create event emitter proxy
|
||||||
this._proxy = createEventEmitterProxy()
|
this._proxy = createEventEmitterProxy()
|
||||||
|
|
||||||
this.on('networkDidChange', this.lookupNetwork)
|
this.on('networkDidChange', this.lookupNetwork)
|
||||||
}
|
}
|
||||||
|
|
||||||
async setNetworkEndpoints (version) {
|
|
||||||
if (version === this._networkEndpointVersion) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this._networkEndpointVersion = version
|
|
||||||
this._networkEndpoints = getNetworkEndpoints(version)
|
|
||||||
this._defaultRpc = this._networkEndpoints[DEFAULT_NETWORK]
|
|
||||||
const { type } = this.getProviderConfig()
|
|
||||||
|
|
||||||
return this.setProviderType(type, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
initializeProvider (_providerParams) {
|
initializeProvider (_providerParams) {
|
||||||
this._baseProviderParams = _providerParams
|
this._baseProviderParams = _providerParams
|
||||||
const { type, rpcTarget } = this.providerStore.getState()
|
const { type, rpcTarget } = this.providerStore.getState()
|
||||||
// map rpcTarget to rpcUrl
|
this._configureProvider({ type, rpcTarget })
|
||||||
const opts = {
|
|
||||||
type,
|
|
||||||
rpcUrl: rpcTarget,
|
|
||||||
}
|
|
||||||
this._configureProvider(opts)
|
|
||||||
this._proxy.on('block', this._logBlock.bind(this))
|
this._proxy.on('block', this._logBlock.bind(this))
|
||||||
this._proxy.on('error', this.verifyNetwork.bind(this))
|
this._proxy.on('error', this.verifyNetwork.bind(this))
|
||||||
this.ethQuery = new EthQuery(this._proxy)
|
this.ethQuery = new EthQuery(this._proxy)
|
||||||
@ -96,45 +84,27 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
setRpcTarget (rpcUrl) {
|
setRpcTarget (rpcTarget) {
|
||||||
this.providerStore.updateState({
|
const providerConfig = {
|
||||||
type: 'rpc',
|
type: 'rpc',
|
||||||
rpcTarget: rpcUrl,
|
rpcTarget,
|
||||||
})
|
|
||||||
this._switchNetwork({ rpcUrl })
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentRpcAddress () {
|
|
||||||
const provider = this.getProviderConfig()
|
|
||||||
if (!provider) return null
|
|
||||||
return this.getRpcAddressForType(provider.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
async setProviderType (type, forceUpdate = false) {
|
|
||||||
assert(type !== 'rpc', `NetworkController.setProviderType - cannot connect by type "rpc"`)
|
|
||||||
// skip if type already matches
|
|
||||||
if (type === this.getProviderConfig().type && !forceUpdate) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
this.providerStore.updateState(providerConfig)
|
||||||
|
this._switchNetwork(providerConfig)
|
||||||
|
}
|
||||||
|
|
||||||
const rpcTarget = this.getRpcAddressForType(type)
|
async setProviderType (type) {
|
||||||
assert(rpcTarget, `NetworkController - unknown rpc address for type "${type}"`)
|
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
|
||||||
this.providerStore.updateState({ type, rpcTarget })
|
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
|
||||||
this._switchNetwork({ type })
|
const providerConfig = { type }
|
||||||
|
this.providerStore.updateState(providerConfig)
|
||||||
|
this._switchNetwork(providerConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
getProviderConfig () {
|
getProviderConfig () {
|
||||||
return this.providerStore.getState()
|
return this.providerStore.getState()
|
||||||
}
|
}
|
||||||
|
|
||||||
getRpcAddressForType (type, provider = this.getProviderConfig()) {
|
|
||||||
if (this._networkEndpoints[type]) {
|
|
||||||
return this._networkEndpoints[type]
|
|
||||||
}
|
|
||||||
|
|
||||||
return provider && provider.rpcTarget ? provider.rpcTarget : this._defaultRpc
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// Private
|
// Private
|
||||||
//
|
//
|
||||||
@ -146,32 +116,27 @@ module.exports = class NetworkController extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_configureProvider (opts) {
|
_configureProvider (opts) {
|
||||||
// type-based rpc endpoints
|
const { type, rpcTarget } = opts
|
||||||
const { type } = opts
|
// infura type-based endpoints
|
||||||
if (type) {
|
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
||||||
// type-based infura rpc endpoints
|
if (isInfura) {
|
||||||
const isInfura = INFURA_PROVIDER_TYPES.includes(type)
|
this._configureInfuraProvider(opts)
|
||||||
opts.rpcUrl = this.getRpcAddressForType(type)
|
// other type-based rpc endpoints
|
||||||
if (isInfura) {
|
} else if (type === LOCALHOST) {
|
||||||
this._configureInfuraProvider(opts)
|
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
|
||||||
// other type-based rpc endpoints
|
|
||||||
} else {
|
|
||||||
this._configureStandardProvider(opts)
|
|
||||||
}
|
|
||||||
// url-based rpc endpoints
|
// url-based rpc endpoints
|
||||||
|
} else if (type === 'rpc'){
|
||||||
|
this._configureStandardProvider({ rpcUrl: rpcTarget })
|
||||||
} else {
|
} else {
|
||||||
this._configureStandardProvider(opts)
|
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_configureInfuraProvider (opts) {
|
_configureInfuraProvider ({ type }) {
|
||||||
log.info('_configureInfuraProvider', opts)
|
log.info('_configureInfuraProvider', type)
|
||||||
const infuraProvider = createInfuraProvider({
|
const infuraProvider = createInfuraProvider({ network: type })
|
||||||
network: opts.type,
|
|
||||||
})
|
|
||||||
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
const infuraSubprovider = new SubproviderFromProvider(infuraProvider)
|
||||||
const providerParams = extend(this._baseProviderParams, {
|
const providerParams = extend(this._baseProviderParams, {
|
||||||
rpcUrl: opts.rpcUrl,
|
|
||||||
engineParams: {
|
engineParams: {
|
||||||
pollingInterval: 8000,
|
pollingInterval: 8000,
|
||||||
blockTrackerProvider: infuraProvider,
|
blockTrackerProvider: infuraProvider,
|
||||||
|
@ -3,7 +3,6 @@ const {
|
|||||||
RINKEBY,
|
RINKEBY,
|
||||||
KOVAN,
|
KOVAN,
|
||||||
MAINNET,
|
MAINNET,
|
||||||
LOCALHOST,
|
|
||||||
ROPSTEN_CODE,
|
ROPSTEN_CODE,
|
||||||
RINKEYBY_CODE,
|
RINKEYBY_CODE,
|
||||||
KOVAN_CODE,
|
KOVAN_CODE,
|
||||||
@ -11,17 +10,6 @@ const {
|
|||||||
RINKEBY_DISPLAY_NAME,
|
RINKEBY_DISPLAY_NAME,
|
||||||
KOVAN_DISPLAY_NAME,
|
KOVAN_DISPLAY_NAME,
|
||||||
MAINNET_DISPLAY_NAME,
|
MAINNET_DISPLAY_NAME,
|
||||||
MAINNET_RPC_URL,
|
|
||||||
ROPSTEN_RPC_URL,
|
|
||||||
KOVAN_RPC_URL,
|
|
||||||
RINKEBY_RPC_URL,
|
|
||||||
LOCALHOST_RPC_URL,
|
|
||||||
MAINNET_RPC_URL_BETA,
|
|
||||||
ROPSTEN_RPC_URL_BETA,
|
|
||||||
KOVAN_RPC_URL_BETA,
|
|
||||||
RINKEBY_RPC_URL_BETA,
|
|
||||||
OLD_UI_NETWORK_TYPE,
|
|
||||||
BETA_UI_NETWORK_TYPE,
|
|
||||||
} = require('./enums')
|
} = require('./enums')
|
||||||
|
|
||||||
const networkToNameMap = {
|
const networkToNameMap = {
|
||||||
@ -34,32 +22,8 @@ const networkToNameMap = {
|
|||||||
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
|
[KOVAN_CODE]: KOVAN_DISPLAY_NAME,
|
||||||
}
|
}
|
||||||
|
|
||||||
const networkEndpointsMap = {
|
|
||||||
[OLD_UI_NETWORK_TYPE]: {
|
|
||||||
[LOCALHOST]: LOCALHOST_RPC_URL,
|
|
||||||
[MAINNET]: MAINNET_RPC_URL,
|
|
||||||
[ROPSTEN]: ROPSTEN_RPC_URL,
|
|
||||||
[KOVAN]: KOVAN_RPC_URL,
|
|
||||||
[RINKEBY]: RINKEBY_RPC_URL,
|
|
||||||
},
|
|
||||||
[BETA_UI_NETWORK_TYPE]: {
|
|
||||||
[LOCALHOST]: LOCALHOST_RPC_URL,
|
|
||||||
[MAINNET]: MAINNET_RPC_URL_BETA,
|
|
||||||
[ROPSTEN]: ROPSTEN_RPC_URL_BETA,
|
|
||||||
[KOVAN]: KOVAN_RPC_URL_BETA,
|
|
||||||
[RINKEBY]: RINKEBY_RPC_URL_BETA,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const getNetworkDisplayName = key => networkToNameMap[key]
|
const getNetworkDisplayName = key => networkToNameMap[key]
|
||||||
|
|
||||||
const getNetworkEndpoints = (networkType = OLD_UI_NETWORK_TYPE) => {
|
|
||||||
return {
|
|
||||||
...networkEndpointsMap[networkType],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getNetworkDisplayName,
|
getNetworkDisplayName,
|
||||||
getNetworkEndpoints,
|
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
// test and development environment variables
|
|
||||||
const env = process.env.METAMASK_ENV
|
|
||||||
const METAMASK_DEBUG = process.env.METAMASK_DEBUG
|
|
||||||
const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {Object} FirstTimeState
|
* @typedef {Object} FirstTimeState
|
||||||
@ -14,11 +10,6 @@ const { DEFAULT_NETWORK, MAINNET } = require('./controllers/network/enums')
|
|||||||
*/
|
*/
|
||||||
const initialState = {
|
const initialState = {
|
||||||
config: {},
|
config: {},
|
||||||
NetworkController: {
|
|
||||||
provider: {
|
|
||||||
type: (METAMASK_DEBUG || env === 'test') ? DEFAULT_NETWORK : MAINNET,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = initialState
|
module.exports = initialState
|
||||||
|
@ -25,7 +25,7 @@ function setupRaven(opts) {
|
|||||||
const report = opts.data
|
const report = opts.data
|
||||||
try {
|
try {
|
||||||
// handle error-like non-error exceptions
|
// handle error-like non-error exceptions
|
||||||
nonErrorException(report)
|
rewriteErrorLikeExceptions(report)
|
||||||
// simplify certain complex error messages (e.g. Ethjs)
|
// simplify certain complex error messages (e.g. Ethjs)
|
||||||
simplifyErrorMessages(report)
|
simplifyErrorMessages(report)
|
||||||
// modify report urls
|
// modify report urls
|
||||||
@ -42,27 +42,35 @@ function setupRaven(opts) {
|
|||||||
return Raven
|
return Raven
|
||||||
}
|
}
|
||||||
|
|
||||||
function nonErrorException(report) {
|
function rewriteErrorLikeExceptions(report) {
|
||||||
// handle errors that lost their error-ness in serialization
|
// handle errors that lost their error-ness in serialization (e.g. dnode)
|
||||||
if (report.message.includes('Non-Error exception captured with keys: message')) {
|
rewriteErrorMessages(report, (errorMessage) => {
|
||||||
if (!(report.extra && report.extra.__serialized__)) return
|
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
|
||||||
report.message = `Non-Error Exception: ${report.extra.__serialized__.message}`
|
if (!(report.extra && report.extra.__serialized__ && report.extra.__serialized__.message)) return errorMessage
|
||||||
}
|
return `Non-Error Exception: ${report.extra.__serialized__.message}`
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function simplifyErrorMessages(report) {
|
function simplifyErrorMessages(report) {
|
||||||
|
rewriteErrorMessages(report, (errorMessage) => {
|
||||||
|
// simplify ethjs error messages
|
||||||
|
errorMessage = extractEthjsErrorMessage(errorMessage)
|
||||||
|
// simplify 'Transaction Failed: known transaction'
|
||||||
|
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
|
||||||
|
// cut the hash from the error message
|
||||||
|
errorMessage = 'Transaction Failed: known transaction'
|
||||||
|
}
|
||||||
|
return errorMessage
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteErrorMessages(report, rewriteFn) {
|
||||||
|
// rewrite top level message
|
||||||
|
report.message = rewriteFn(report.message)
|
||||||
|
// rewrite each exception message
|
||||||
if (report.exception && report.exception.values) {
|
if (report.exception && report.exception.values) {
|
||||||
report.exception.values.forEach(item => {
|
report.exception.values.forEach(item => {
|
||||||
let errorMessage = item.value
|
item.value = rewriteFn(item.value)
|
||||||
// simplify ethjs error messages
|
|
||||||
errorMessage = extractEthjsErrorMessage(errorMessage)
|
|
||||||
// simplify 'Transaction Failed: known transaction'
|
|
||||||
if (errorMessage.indexOf('Transaction Failed: known transaction') === 0) {
|
|
||||||
// cut the hash from the error message
|
|
||||||
errorMessage = 'Transaction Failed: known transaction'
|
|
||||||
}
|
|
||||||
// finalize
|
|
||||||
item.value = errorMessage
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -355,7 +355,6 @@ module.exports = class MetamaskController extends EventEmitter {
|
|||||||
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
submitPassword: nodeify(keyringController.submitPassword, keyringController),
|
||||||
|
|
||||||
// network management
|
// network management
|
||||||
setNetworkEndpoints: nodeify(networkController.setNetworkEndpoints, networkController),
|
|
||||||
setProviderType: nodeify(networkController.setProviderType, networkController),
|
setProviderType: nodeify(networkController.setProviderType, networkController),
|
||||||
setCustomRpc: nodeify(this.setCustomRpc, this),
|
setCustomRpc: nodeify(this.setCustomRpc, this),
|
||||||
|
|
||||||
|
@ -13,8 +13,13 @@ import {
|
|||||||
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
|
INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE,
|
||||||
INITIALIZE_NOTICE_ROUTE,
|
INITIALIZE_NOTICE_ROUTE,
|
||||||
} from '../../../../ui/app/routes'
|
} from '../../../../ui/app/routes'
|
||||||
|
import TextField from '../../../../ui/app/components/text-field'
|
||||||
|
|
||||||
class CreatePasswordScreen extends Component {
|
class CreatePasswordScreen extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
isLoading: PropTypes.bool.isRequired,
|
isLoading: PropTypes.bool.isRequired,
|
||||||
createAccount: PropTypes.func.isRequired,
|
createAccount: PropTypes.func.isRequired,
|
||||||
@ -27,6 +32,8 @@ class CreatePasswordScreen extends Component {
|
|||||||
state = {
|
state = {
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
|
passwordError: null,
|
||||||
|
confirmPasswordError: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@ -69,82 +76,37 @@ class CreatePasswordScreen extends Component {
|
|||||||
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
|
.then(() => history.push(INITIALIZE_UNIQUE_IMAGE_ROUTE))
|
||||||
}
|
}
|
||||||
|
|
||||||
renderFields () {
|
handlePasswordChange (password) {
|
||||||
const { isMascara, history } = this.props
|
const { confirmPassword } = this.state
|
||||||
|
let confirmPasswordError = null
|
||||||
|
let passwordError = null
|
||||||
|
|
||||||
return (
|
if (password && password.length < 8) {
|
||||||
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
passwordError = this.context.t('passwordNotLongEnough')
|
||||||
<div className={classnames({
|
}
|
||||||
'first-view-main': !isMascara,
|
|
||||||
'first-view-main__mascara': isMascara,
|
if (confirmPassword && password !== confirmPassword) {
|
||||||
})}>
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
{isMascara && <div className="mascara-info first-view-phone-invisible">
|
}
|
||||||
<Mascot
|
|
||||||
animationEventEmitter={this.animationEventEmitter}
|
this.setState({ password, passwordError, confirmPasswordError })
|
||||||
width="225"
|
}
|
||||||
height="225"
|
|
||||||
/>
|
handleConfirmPasswordChange (confirmPassword) {
|
||||||
<div className="info">
|
const { password } = this.state
|
||||||
MetaMask is a secure identity vault for Ethereum.
|
let confirmPasswordError = null
|
||||||
</div>
|
|
||||||
<div className="info">
|
if (password !== confirmPassword) {
|
||||||
It allows you to hold ether & tokens, and interact with decentralized applications.
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
</div>
|
}
|
||||||
</div>}
|
|
||||||
<div className="create-password">
|
this.setState({ confirmPassword, confirmPasswordError })
|
||||||
<div className="create-password__title">
|
|
||||||
Create Password
|
|
||||||
</div>
|
|
||||||
<input
|
|
||||||
className="first-time-flow__input"
|
|
||||||
type="password"
|
|
||||||
placeholder="New Password (min 8 characters)"
|
|
||||||
onChange={e => this.setState({password: e.target.value})}
|
|
||||||
/>
|
|
||||||
<input
|
|
||||||
className="first-time-flow__input create-password__confirm-input"
|
|
||||||
type="password"
|
|
||||||
placeholder="Confirm Password"
|
|
||||||
onChange={e => this.setState({confirmPassword: e.target.value})}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className="first-time-flow__button"
|
|
||||||
disabled={!this.isValid()}
|
|
||||||
onClick={this.createAccount}
|
|
||||||
>
|
|
||||||
Create
|
|
||||||
</button>
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
className="first-time-flow__link create-password__import-link"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import with seed phrase
|
|
||||||
</a>
|
|
||||||
{ /* }
|
|
||||||
<a
|
|
||||||
href=""
|
|
||||||
className="first-time-flow__link create-password__import-link"
|
|
||||||
onClick={e => {
|
|
||||||
e.preventDefault()
|
|
||||||
history.push(INITIALIZE_IMPORT_ACCOUNT_ROUTE)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Import an account
|
|
||||||
</a>
|
|
||||||
{ */ }
|
|
||||||
<Breadcrumbs total={3} currentIndex={0} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { history, isMascara } = this.props
|
const { history, isMascara } = this.props
|
||||||
|
const { passwordError, confirmPasswordError } = this.state
|
||||||
|
const { t } = this.context
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
<div className={classnames({ 'first-view-main-wrapper': !isMascara })}>
|
||||||
@ -169,17 +131,30 @@ class CreatePasswordScreen extends Component {
|
|||||||
<div className="create-password__title">
|
<div className="create-password__title">
|
||||||
Create Password
|
Create Password
|
||||||
</div>
|
</div>
|
||||||
<input
|
<TextField
|
||||||
|
id="create-password"
|
||||||
|
label={t('newPassword')}
|
||||||
|
type="password"
|
||||||
className="first-time-flow__input"
|
className="first-time-flow__input"
|
||||||
type="password"
|
value={this.state.password}
|
||||||
placeholder="New Password (min 8 characters)"
|
onChange={event => this.handlePasswordChange(event.target.value)}
|
||||||
onChange={e => this.setState({password: e.target.value})}
|
error={passwordError}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="new-password"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<input
|
<TextField
|
||||||
className="first-time-flow__input create-password__confirm-input"
|
id="confirm-password"
|
||||||
|
label={t('confirmPassword')}
|
||||||
type="password"
|
type="password"
|
||||||
placeholder="Confirm Password"
|
className="first-time-flow__input"
|
||||||
onChange={e => this.setState({confirmPassword: e.target.value})}
|
value={this.state.confirmPassword}
|
||||||
|
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
|
||||||
|
error={confirmPasswordError}
|
||||||
|
autoComplete="confirm-password"
|
||||||
|
margin="normal"
|
||||||
|
fullWidth
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
className="first-time-flow__button"
|
className="first-time-flow__button"
|
||||||
|
@ -1,29 +1,33 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types'
|
import PropTypes from 'prop-types'
|
||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import classnames from 'classnames'
|
|
||||||
import {
|
import {
|
||||||
createNewVaultAndRestore,
|
createNewVaultAndRestore,
|
||||||
hideWarning,
|
|
||||||
displayWarning,
|
|
||||||
unMarkPasswordForgotten,
|
unMarkPasswordForgotten,
|
||||||
} from '../../../../ui/app/actions'
|
} from '../../../../ui/app/actions'
|
||||||
import { DEFAULT_ROUTE, INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
|
import { INITIALIZE_NOTICE_ROUTE } from '../../../../ui/app/routes'
|
||||||
|
import TextField from '../../../../ui/app/components/text-field'
|
||||||
|
|
||||||
class ImportSeedPhraseScreen extends Component {
|
class ImportSeedPhraseScreen extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
warning: PropTypes.string,
|
warning: PropTypes.string,
|
||||||
createNewVaultAndRestore: PropTypes.func.isRequired,
|
createNewVaultAndRestore: PropTypes.func.isRequired,
|
||||||
hideWarning: PropTypes.func.isRequired,
|
|
||||||
displayWarning: PropTypes.func,
|
|
||||||
leaveImportSeedScreenState: PropTypes.func,
|
leaveImportSeedScreenState: PropTypes.func,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
|
isLoading: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
state = {
|
state = {
|
||||||
seedPhrase: '',
|
seedPhrase: '',
|
||||||
password: '',
|
password: '',
|
||||||
confirmPassword: '',
|
confirmPassword: '',
|
||||||
|
seedPhraseError: null,
|
||||||
|
passwordError: null,
|
||||||
|
confirmPasswordError: null,
|
||||||
}
|
}
|
||||||
|
|
||||||
parseSeedPhrase = (seedPhrase) => {
|
parseSeedPhrase = (seedPhrase) => {
|
||||||
@ -32,39 +36,47 @@ class ImportSeedPhraseScreen extends Component {
|
|||||||
.join(' ')
|
.join(' ')
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange = ({ seedPhrase, password, confirmPassword }) => {
|
handleSeedPhraseChange (seedPhrase) {
|
||||||
const {
|
let seedPhraseError = null
|
||||||
password: prevPassword,
|
|
||||||
confirmPassword: prevConfirmPassword,
|
|
||||||
} = this.state
|
|
||||||
const { displayWarning, hideWarning } = this.props
|
|
||||||
|
|
||||||
let warning = null
|
|
||||||
|
|
||||||
if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
if (seedPhrase && this.parseSeedPhrase(seedPhrase).split(' ').length !== 12) {
|
||||||
warning = 'Seed Phrases are 12 words long'
|
seedPhraseError = this.context.t('seedPhraseReq')
|
||||||
} else if (password && password.length < 8) {
|
|
||||||
warning = 'Passwords require a mimimum length of 8'
|
|
||||||
} else if ((password || prevPassword) !== (confirmPassword || prevConfirmPassword)) {
|
|
||||||
warning = 'Confirmed password does not match'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (warning) {
|
this.setState({ seedPhrase, seedPhraseError })
|
||||||
displayWarning(warning)
|
}
|
||||||
} else {
|
|
||||||
hideWarning()
|
handlePasswordChange (password) {
|
||||||
|
const { confirmPassword } = this.state
|
||||||
|
let confirmPasswordError = null
|
||||||
|
let passwordError = null
|
||||||
|
|
||||||
|
if (password && password.length < 8) {
|
||||||
|
passwordError = this.context.t('passwordNotLongEnough')
|
||||||
}
|
}
|
||||||
|
|
||||||
seedPhrase && this.setState({ seedPhrase })
|
if (confirmPassword && password !== confirmPassword) {
|
||||||
password && this.setState({ password })
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
confirmPassword && this.setState({ confirmPassword })
|
}
|
||||||
|
|
||||||
|
this.setState({ password, passwordError, confirmPasswordError })
|
||||||
|
}
|
||||||
|
|
||||||
|
handleConfirmPasswordChange (confirmPassword) {
|
||||||
|
const { password } = this.state
|
||||||
|
let confirmPasswordError = null
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
confirmPasswordError = this.context.t('passwordsDontMatch')
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ confirmPassword, confirmPasswordError })
|
||||||
}
|
}
|
||||||
|
|
||||||
onClick = () => {
|
onClick = () => {
|
||||||
const { password, seedPhrase } = this.state
|
const { password, seedPhrase } = this.state
|
||||||
const {
|
const {
|
||||||
createNewVaultAndRestore,
|
createNewVaultAndRestore,
|
||||||
displayWarning,
|
|
||||||
leaveImportSeedScreenState,
|
leaveImportSeedScreenState,
|
||||||
history,
|
history,
|
||||||
} = this.props
|
} = this.props
|
||||||
@ -74,10 +86,23 @@ class ImportSeedPhraseScreen extends Component {
|
|||||||
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
|
.then(() => history.push(INITIALIZE_NOTICE_ROUTE))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hasError () {
|
||||||
|
const { passwordError, confirmPasswordError, seedPhraseError } = this.state
|
||||||
|
return passwordError || confirmPasswordError || seedPhraseError
|
||||||
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { seedPhrase, password, confirmPassword } = this.state
|
const {
|
||||||
const { warning, isLoading } = this.props
|
seedPhrase,
|
||||||
const importDisabled = warning || !seedPhrase || !password || !confirmPassword || isLoading
|
password,
|
||||||
|
confirmPassword,
|
||||||
|
seedPhraseError,
|
||||||
|
passwordError,
|
||||||
|
confirmPasswordError,
|
||||||
|
} = this.state
|
||||||
|
const { t } = this.context
|
||||||
|
const { isLoading } = this.props
|
||||||
|
const disabled = !seedPhrase || !password || !confirmPassword || isLoading || this.hasError()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="first-view-main-wrapper">
|
<div className="first-view-main-wrapper">
|
||||||
@ -103,45 +128,40 @@ class ImportSeedPhraseScreen extends Component {
|
|||||||
<label className="import-account__input-label">Wallet Seed</label>
|
<label className="import-account__input-label">Wallet Seed</label>
|
||||||
<textarea
|
<textarea
|
||||||
className="import-account__secret-phrase"
|
className="import-account__secret-phrase"
|
||||||
onChange={e => this.onChange({seedPhrase: e.target.value})}
|
onChange={e => this.handleSeedPhraseChange(e.target.value)}
|
||||||
value={this.state.seedPhrase}
|
value={this.state.seedPhrase}
|
||||||
placeholder="Separate each word with a single space"
|
placeholder="Separate each word with a single space"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<span
|
<span className="error">
|
||||||
className="error"
|
{ seedPhraseError }
|
||||||
>
|
|
||||||
{this.props.warning}
|
|
||||||
</span>
|
</span>
|
||||||
<div className="import-account__input-wrapper">
|
<TextField
|
||||||
<label className="import-account__input-label">New Password</label>
|
id="password"
|
||||||
<input
|
label={t('newPassword')}
|
||||||
className="first-time-flow__input"
|
type="password"
|
||||||
type="password"
|
className="first-time-flow__input"
|
||||||
placeholder="New Password (min 8 characters)"
|
value={this.state.password}
|
||||||
onChange={e => this.onChange({password: e.target.value})}
|
onChange={event => this.handlePasswordChange(event.target.value)}
|
||||||
/>
|
error={passwordError}
|
||||||
</div>
|
autoComplete="new-password"
|
||||||
<div className="import-account__input-wrapper">
|
margin="normal"
|
||||||
<label
|
/>
|
||||||
className={classnames('import-account__input-label', {
|
<TextField
|
||||||
'import-account__input-label__disabled': password.length < 8,
|
id="confirm-password"
|
||||||
})}
|
label={t('confirmPassword')}
|
||||||
>Confirm Password</label>
|
type="password"
|
||||||
<input
|
className="first-time-flow__input"
|
||||||
className={classnames('first-time-flow__input', {
|
value={this.state.confirmPassword}
|
||||||
'first-time-flow__input__disabled': password.length < 8,
|
onChange={event => this.handleConfirmPasswordChange(event.target.value)}
|
||||||
})}
|
error={confirmPasswordError}
|
||||||
type="password"
|
autoComplete="confirm-password"
|
||||||
placeholder="Confirm Password"
|
margin="normal"
|
||||||
onChange={e => this.onChange({confirmPassword: e.target.value})}
|
/>
|
||||||
disabled={password.length < 8}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<button
|
<button
|
||||||
className="first-time-flow__button"
|
className="first-time-flow__button"
|
||||||
onClick={() => !importDisabled && this.onClick()}
|
onClick={() => !disabled && this.onClick()}
|
||||||
disabled={importDisabled}
|
disabled={disabled}
|
||||||
>
|
>
|
||||||
Import
|
Import
|
||||||
</button>
|
</button>
|
||||||
@ -159,7 +179,5 @@ export default connect(
|
|||||||
dispatch(unMarkPasswordForgotten())
|
dispatch(unMarkPasswordForgotten())
|
||||||
},
|
},
|
||||||
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
|
createNewVaultAndRestore: (pw, seed) => dispatch(createNewVaultAndRestore(pw, seed)),
|
||||||
displayWarning: (warning) => dispatch(displayWarning(warning)),
|
|
||||||
hideWarning: () => dispatch(hideWarning()),
|
|
||||||
})
|
})
|
||||||
)(ImportSeedPhraseScreen)
|
)(ImportSeedPhraseScreen)
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
background: #f7861c;
|
background: #f7861c;
|
||||||
|
flex: 0 0 auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.alpha-warning,
|
.alpha-warning,
|
||||||
@ -173,10 +174,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.first-time-flow__input {
|
.first-time-flow__input {
|
||||||
width: initial !important;
|
width: 100%;
|
||||||
font-size: 14px !important;
|
|
||||||
line-height: 18px !important;
|
|
||||||
padding: 12px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tou__body {
|
.tou__body {
|
||||||
@ -247,7 +245,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.create-password__confirm-input {
|
.create-password__confirm-input {
|
||||||
margin-top: 15px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.create-password__import-link {
|
.create-password__import-link {
|
||||||
@ -519,10 +517,6 @@ button.backup-phrase__confirm-seed-option:hover {
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-time-flow__input--error {
|
|
||||||
border: 1px solid #FF001F !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.import-account__input-error-message {
|
.import-account__input-error-message {
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
width: 422px;
|
width: 422px;
|
||||||
@ -543,7 +537,13 @@ button.backup-phrase__confirm-seed-option:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.import-account__input {
|
.import-account__input {
|
||||||
width: 325px !important;
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 575px) {
|
||||||
|
.import-account__input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.import-account__file-input {
|
.import-account__file-input {
|
||||||
@ -680,20 +680,6 @@ button.backup-phrase__confirm-seed-option:hover {
|
|||||||
|
|
||||||
.first-time-flow__input {
|
.first-time-flow__input {
|
||||||
width: 350px;
|
width: 350px;
|
||||||
font-size: 18px;
|
|
||||||
line-height: 24px;
|
|
||||||
padding: 15px;
|
|
||||||
border: 1px solid #CDCDCD;
|
|
||||||
background-color: #FFFFFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-time-flow__input__disabled {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first-time-flow__input::placeholder {
|
|
||||||
color: #9B9B9B;
|
|
||||||
font-weight: 200;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.first-time-flow__button {
|
.first-time-flow__button {
|
||||||
|
@ -3,6 +3,8 @@ import PropTypes from 'prop-types'
|
|||||||
import {connect} from 'react-redux'
|
import {connect} from 'react-redux'
|
||||||
import { withRouter, Switch, Route } from 'react-router-dom'
|
import { withRouter, Switch, Route } from 'react-router-dom'
|
||||||
import { compose } from 'recompose'
|
import { compose } from 'recompose'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
|
||||||
import CreatePasswordScreen from './create-password-screen'
|
import CreatePasswordScreen from './create-password-screen'
|
||||||
import UniqueImageScreen from './unique-image-screen'
|
import UniqueImageScreen from './unique-image-screen'
|
||||||
import NoticeScreen from './notice-screen'
|
import NoticeScreen from './notice-screen'
|
||||||
@ -33,6 +35,7 @@ class FirstTimeFlow extends Component {
|
|||||||
isUnlocked: PropTypes.bool,
|
isUnlocked: PropTypes.bool,
|
||||||
history: PropTypes.object,
|
history: PropTypes.object,
|
||||||
welcomeScreenSeen: PropTypes.bool,
|
welcomeScreenSeen: PropTypes.bool,
|
||||||
|
isPopup: PropTypes.bool,
|
||||||
};
|
};
|
||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
@ -41,23 +44,44 @@ class FirstTimeFlow extends Component {
|
|||||||
noActiveNotices: false,
|
noActiveNotices: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
render () {
|
renderAppBar () {
|
||||||
|
const { welcomeScreenSeen } = this.props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="first-time-flow">
|
<div className="alpha-warning__container">
|
||||||
<Switch>
|
<h2 className={classnames({
|
||||||
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
|
'alpha-warning': welcomeScreenSeen,
|
||||||
<Route
|
'alpha-warning-welcome-screen': !welcomeScreenSeen,
|
||||||
exact
|
})}
|
||||||
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
|
>
|
||||||
component={ImportSeedPhraseScreen}
|
Please be aware that this version is still under development
|
||||||
/>
|
</h2>
|
||||||
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
|
</div>
|
||||||
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
|
)
|
||||||
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
|
}
|
||||||
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
|
|
||||||
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
|
render () {
|
||||||
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
|
const { isPopup } = this.props
|
||||||
</Switch>
|
|
||||||
|
return (
|
||||||
|
<div className="flex-column flex-grow">
|
||||||
|
{ !isPopup && this.renderAppBar() }
|
||||||
|
<div className="first-time-flow">
|
||||||
|
<Switch>
|
||||||
|
<Route exact path={INITIALIZE_IMPORT_ACCOUNT_ROUTE} component={ImportAccountScreen} />
|
||||||
|
<Route
|
||||||
|
exact
|
||||||
|
path={INITIALIZE_IMPORT_WITH_SEED_PHRASE_ROUTE}
|
||||||
|
component={ImportSeedPhraseScreen}
|
||||||
|
/>
|
||||||
|
<Route exact path={INITIALIZE_UNIQUE_IMAGE_ROUTE} component={UniqueImageScreen} />
|
||||||
|
<Route exact path={INITIALIZE_NOTICE_ROUTE} component={NoticeScreen} />
|
||||||
|
<Route exact path={INITIALIZE_BACKUP_PHRASE_ROUTE} component={BackupPhraseScreen} />
|
||||||
|
<Route exact path={INITIALIZE_CONFIRM_SEED_ROUTE} component={ConfirmSeed} />
|
||||||
|
<Route exact path={INITIALIZE_CREATE_PASSWORD_ROUTE} component={CreatePasswordScreen} />
|
||||||
|
<Route exact path={INITIALIZE_ROUTE} component={WelcomeScreen} />
|
||||||
|
</Switch>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -73,6 +97,7 @@ const mapStateToProps = ({ metamask }) => {
|
|||||||
isMascara,
|
isMascara,
|
||||||
isUnlocked,
|
isUnlocked,
|
||||||
welcomeScreenSeen,
|
welcomeScreenSeen,
|
||||||
|
isPopup,
|
||||||
} = metamask
|
} = metamask
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -84,6 +109,7 @@ const mapStateToProps = ({ metamask }) => {
|
|||||||
forgottenPassword,
|
forgottenPassword,
|
||||||
isUnlocked,
|
isUnlocked,
|
||||||
welcomeScreenSeen,
|
welcomeScreenSeen,
|
||||||
|
isPopup,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,7 +35,6 @@ const HDCreateVaultComplete = require('./keychains/hd/create-vault-complete')
|
|||||||
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
const HDRestoreVaultScreen = require('./keychains/hd/restore-vault')
|
||||||
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
const RevealSeedConfirmation = require('./keychains/hd/recover-seed/confirmation')
|
||||||
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
const AccountDropdowns = require('./components/account-dropdowns').AccountDropdowns
|
||||||
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(App)
|
module.exports = connect(mapStateToProps)(App)
|
||||||
|
|
||||||
@ -409,7 +408,6 @@ App.prototype.renderDropdown = function () {
|
|||||||
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
closeMenu: () => this.setState({ isMainMenuOpen: !isOpen }),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
this.props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
}, 'Try Beta!'),
|
}, 'Try Beta!'),
|
||||||
])
|
])
|
||||||
@ -472,7 +470,6 @@ App.prototype.renderPrimary = function () {
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
global.platform.openExtensionInBrowser()
|
global.platform.openExtensionInBrowser()
|
||||||
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
props.dispatch(actions.setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => props.dispatch(actions.setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
style: {
|
style: {
|
||||||
fontSize: '0.8em',
|
fontSize: '0.8em',
|
||||||
|
2998
package-lock.json
generated
2998
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@ -13,8 +13,10 @@
|
|||||||
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
"test:single": "cross-env METAMASK_ENV=test mocha --require test/helper.js",
|
||||||
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
"test:integration": "npm run test:integration:build && npm run test:flat && npm run test:mascara",
|
||||||
"test:integration:build": "gulp build:scss",
|
"test:integration:build": "gulp build:scss",
|
||||||
"test:e2e": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run'",
|
"test:e2e:chrome": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:chrome'",
|
||||||
"test:e2e:run": "mocha test/e2e/metamask.spec --recursive",
|
"test:e2e:firefox": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:e2e:run:firefox'",
|
||||||
|
"test:e2e:run:chrome": "SELENIUM_BROWSER=chrome mocha test/e2e/chrome/metamask.spec --bail --recursive",
|
||||||
|
"test:e2e:run:firefox": "SELENIUM_BROWSER=firefox mocha test/e2e/firefox/metamask.spec --bail --recursive",
|
||||||
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
"test:screens": "shell-parallel -s 'npm run ganache:start' -x 'sleep 3 && npm run test:screens:run'",
|
||||||
"test:screens:run": "node test/screens/new-ui.js",
|
"test:screens:run": "node test/screens/new-ui.js",
|
||||||
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
"test:coverage": "nyc npm run test:unit && npm run test:coveralls-upload",
|
||||||
@ -81,7 +83,7 @@
|
|||||||
"currency-formatter": "^1.4.2",
|
"currency-formatter": "^1.4.2",
|
||||||
"debounce": "^1.0.0",
|
"debounce": "^1.0.0",
|
||||||
"debounce-stream": "^2.0.0",
|
"debounce-stream": "^2.0.0",
|
||||||
"deep-extend": "^0.5.0",
|
"deep-extend": "^0.5.1",
|
||||||
"detect-node": "^2.0.3",
|
"detect-node": "^2.0.3",
|
||||||
"disc": "^1.3.2",
|
"disc": "^1.3.2",
|
||||||
"dnode": "^1.2.2",
|
"dnode": "^1.2.2",
|
||||||
@ -134,6 +136,7 @@
|
|||||||
"lodash.shuffle": "^4.2.0",
|
"lodash.shuffle": "^4.2.0",
|
||||||
"lodash.uniqby": "^4.7.0",
|
"lodash.uniqby": "^4.7.0",
|
||||||
"loglevel": "^1.4.1",
|
"loglevel": "^1.4.1",
|
||||||
|
"material-ui": "1.0.0-beta.44",
|
||||||
"metamascara": "^2.0.0",
|
"metamascara": "^2.0.0",
|
||||||
"metamask-logo": "^2.1.4",
|
"metamask-logo": "^2.1.4",
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
@ -228,6 +231,7 @@
|
|||||||
"fs-promise": "^2.0.3",
|
"fs-promise": "^2.0.3",
|
||||||
"ganache-cli": "^6.1.0",
|
"ganache-cli": "^6.1.0",
|
||||||
"ganache-core": "^2.1.0",
|
"ganache-core": "^2.1.0",
|
||||||
|
"geckodriver": "^1.11.0",
|
||||||
"gifencoder": "^1.1.0",
|
"gifencoder": "^1.1.0",
|
||||||
"gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed",
|
"gulp": "github:gulpjs/gulp#6d71a658c61edb3090221579d8f97dbe086ba2ed",
|
||||||
"gulp-babel": "^7.0.0",
|
"gulp-babel": "^7.0.0",
|
||||||
|
314
test/e2e/chrome/metamask.spec.js
Normal file
314
test/e2e/chrome/metamask.spec.js
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const mkdirp = require('mkdirp')
|
||||||
|
const path = require('path')
|
||||||
|
const assert = require('assert')
|
||||||
|
const pify = require('pify')
|
||||||
|
const webdriver = require('selenium-webdriver')
|
||||||
|
const until = require('selenium-webdriver/lib/until')
|
||||||
|
const By = webdriver.By
|
||||||
|
const { delay, buildChromeWebDriver } = require('../func')
|
||||||
|
|
||||||
|
describe('Metamask popup page', function () {
|
||||||
|
let driver, accountAddress, tokenAddress, extensionId
|
||||||
|
|
||||||
|
this.timeout(0)
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
const extPath = path.resolve('dist/chrome')
|
||||||
|
driver = buildChromeWebDriver(extPath)
|
||||||
|
await driver.get('chrome://extensions')
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
if (this.currentTest.state === 'failed') {
|
||||||
|
await verboseReportOnFailure(this.currentTest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await driver.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Setup', function () {
|
||||||
|
|
||||||
|
it('switches to Chrome extensions list', async function () {
|
||||||
|
const tabs = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(tabs[0])
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`selects MetaMask's extension id and opens it in the current tab`, async function () {
|
||||||
|
extensionId = await getExtensionId()
|
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets provider type to localhost', async function () {
|
||||||
|
await driver.wait(until.elementLocated(By.css('#app-content')), 300)
|
||||||
|
await setProviderType('localhost')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Account Creation', () => {
|
||||||
|
|
||||||
|
it('matches MetaMask title', async () => {
|
||||||
|
const title = await driver.getTitle()
|
||||||
|
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows privacy notice', async () => {
|
||||||
|
await driver.wait(async () => {
|
||||||
|
const privacyHeader = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText()
|
||||||
|
assert.equal(privacyHeader, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||||
|
return privacyHeader === 'PRIVACY NOTICE'
|
||||||
|
}, 300)
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('show terms of use', async () => {
|
||||||
|
await driver.wait(async () => {
|
||||||
|
const terms = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > h3')).getText()
|
||||||
|
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
||||||
|
return terms === 'TERMS OF USE'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks if the TOU button is disabled', async () => {
|
||||||
|
const button = await driver.findElement(By.css('button')).isEnabled()
|
||||||
|
assert.equal(button, false, 'disabled continue button')
|
||||||
|
const element = await driver.findElement(By.linkText('Attributions'))
|
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||||
|
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
||||||
|
const buttonEnabled = await button.isEnabled()
|
||||||
|
assert.equal(buttonEnabled, true, 'enabled continue button')
|
||||||
|
await button.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts password with length of eight', async () => {
|
||||||
|
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||||
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||||
|
const button = await driver.findElements(By.css('button'))
|
||||||
|
|
||||||
|
await passwordBox.sendKeys('123456789')
|
||||||
|
await passwordBoxConfirm.sendKeys('123456789')
|
||||||
|
await button[0].click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows value was created and seed phrase', async () => {
|
||||||
|
await delay(300)
|
||||||
|
const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
||||||
|
assert.equal(seedPhrase.split(' ').length, 12)
|
||||||
|
const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)'))
|
||||||
|
assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`)
|
||||||
|
await continueAfterSeedPhrase.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows account address', async function () {
|
||||||
|
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs out of the vault', async () => {
|
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||||
|
await delay(500)
|
||||||
|
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||||
|
assert.equal(await logoutButton.getText(), 'Log Out')
|
||||||
|
await logoutButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts account password after lock', async () => {
|
||||||
|
await delay(500)
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows QR code option', async () => {
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks QR code address is the same as account details address', async () => {
|
||||||
|
const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
||||||
|
assert.equal(accountAddress.toLowerCase(), QRaccountAddress)
|
||||||
|
await driver.findElement(By.css('.fa-arrow-left')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Import Ganache seed phrase', function () {
|
||||||
|
it('logs out', async function () {
|
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||||
|
await delay(200)
|
||||||
|
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||||
|
assert.equal(await logOut.getText(), 'Log Out')
|
||||||
|
await logOut.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('restores from seed phrase', async function () {
|
||||||
|
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p'))
|
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase')
|
||||||
|
await restoreSeedLink.click()
|
||||||
|
await delay(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds seed phrase', async function () {
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||||
|
const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea'))
|
||||||
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance renders', async function () {
|
||||||
|
await delay(200)
|
||||||
|
const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'))
|
||||||
|
assert.equal(await balance.getText(), '100.000')
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends transaction', async function () {
|
||||||
|
const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)'))
|
||||||
|
assert.equal(await sendButton.getText(), 'SEND')
|
||||||
|
await sendButton.click()
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds recipient address and amount', async function () {
|
||||||
|
const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText()
|
||||||
|
assert.equal(sendTranscationScreen, 'SEND TRANSACTION')
|
||||||
|
const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input'))
|
||||||
|
const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input'))
|
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
|
await inputAmmount.sendKeys('10')
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirms transaction', async function () {
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () {
|
||||||
|
const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'))
|
||||||
|
assert.equal(await tranasactionAmount.getText(), '10.0')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Token Factory', function () {
|
||||||
|
|
||||||
|
it('navigates to token factory', async function () {
|
||||||
|
await driver.get('http://tokenfactory.surge.sh/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates to create token contract link', async function () {
|
||||||
|
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a'))
|
||||||
|
await createToken.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds input for token', async function () {
|
||||||
|
const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input'))
|
||||||
|
const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input'))
|
||||||
|
const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input'))
|
||||||
|
const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input'))
|
||||||
|
const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button'))
|
||||||
|
|
||||||
|
await totalSupply.sendKeys('100')
|
||||||
|
await tokenName.sendKeys('Test')
|
||||||
|
await tokenDecimal.sendKeys('0')
|
||||||
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
await createToken.click()
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirms transaction in MetaMask popup', async function () {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||||
|
const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input'))
|
||||||
|
await metamaskSubmit.click()
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('switches back to Token Factory to grab the token contract address', async function () {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[0])
|
||||||
|
const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)'))
|
||||||
|
tokenAddress = await tokenContactAddress.getText()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates back to MetaMask popup in the tab', async function () {
|
||||||
|
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
||||||
|
await delay(700)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add Token', function () {
|
||||||
|
it('switches to the add token screen', async function () {
|
||||||
|
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer'))
|
||||||
|
assert.equal(await tokensTab.getText(), 'TOKENS')
|
||||||
|
await tokensTab.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates to the add token screen', async function () {
|
||||||
|
const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button'))
|
||||||
|
assert.equal(await addTokenButton.getText(), 'ADD TOKEN')
|
||||||
|
await addTokenButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks add token screen rendered', async function () {
|
||||||
|
const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2'))
|
||||||
|
assert.equal(await addTokenScreen.getText(), 'ADD TOKEN')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds token parameters', async function () {
|
||||||
|
const tokenContractAddress = await driver.findElement(By.css('#token-address'))
|
||||||
|
await tokenContractAddress.sendKeys(tokenAddress)
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click()
|
||||||
|
await delay(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks the token balance', async function () {
|
||||||
|
const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3'))
|
||||||
|
assert.equal(await tokenBalance.getText(), '100 TST')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function getExtensionId () {
|
||||||
|
const extension = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("extensions-item:nth-child(2)").getAttribute("id")')
|
||||||
|
return extension
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setProviderType (type) {
|
||||||
|
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verboseReportOnFailure (test) {
|
||||||
|
const artifactDir = `./test-artifacts/chrome/${test.title}`
|
||||||
|
const filepathBase = `${artifactDir}/test-failure`
|
||||||
|
await pify(mkdirp)(artifactDir)
|
||||||
|
// capture screenshot
|
||||||
|
const screenshot = await driver.takeScreenshot()
|
||||||
|
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||||
|
// capture dom source
|
||||||
|
const htmlSource = await driver.getPageSource()
|
||||||
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
323
test/e2e/firefox/metamask.spec.js
Normal file
323
test/e2e/firefox/metamask.spec.js
Normal file
@ -0,0 +1,323 @@
|
|||||||
|
const fs = require('fs')
|
||||||
|
const mkdirp = require('mkdirp')
|
||||||
|
const path = require('path')
|
||||||
|
const assert = require('assert')
|
||||||
|
const pify = require('pify')
|
||||||
|
const webdriver = require('selenium-webdriver')
|
||||||
|
const Command = require('selenium-webdriver/lib/command').Command
|
||||||
|
const By = webdriver.By
|
||||||
|
const { delay, buildFirefoxWebdriver } = require('../func')
|
||||||
|
|
||||||
|
describe('', function () {
|
||||||
|
let driver, accountAddress, tokenAddress, extensionId
|
||||||
|
|
||||||
|
this.timeout(0)
|
||||||
|
|
||||||
|
before(async function () {
|
||||||
|
const extPath = path.resolve('dist/firefox')
|
||||||
|
driver = buildFirefoxWebdriver()
|
||||||
|
installWebExt(driver, extPath)
|
||||||
|
await delay(700)
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(async function () {
|
||||||
|
if (this.currentTest.state === 'failed') {
|
||||||
|
await verboseReportOnFailure(this.currentTest)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
after(async function () {
|
||||||
|
await driver.quit()
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Setup', function () {
|
||||||
|
|
||||||
|
it('switches to Firefox addon list', async function () {
|
||||||
|
await driver.get('about:debugging#addons')
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it(`selects MetaMask's extension id and opens it in the current tab`, async function () {
|
||||||
|
const tabs = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(tabs[0])
|
||||||
|
extensionId = await driver.findElement(By.css('dd.addon-target-info-content:nth-child(6) > span:nth-child(1)')).getText()
|
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sets provider type to localhost', async function () {
|
||||||
|
await setProviderType('localhost')
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Account Creation', () => {
|
||||||
|
|
||||||
|
it('matches MetaMask title', async () => {
|
||||||
|
const title = await driver.getTitle()
|
||||||
|
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows privacy notice', async () => {
|
||||||
|
await delay(300)
|
||||||
|
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
|
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
||||||
|
await driver.findElement(By.css('button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('show terms of use', async () => {
|
||||||
|
await delay(300)
|
||||||
|
const terms = await driver.findElement(By.css('.terms-header')).getText()
|
||||||
|
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks if the TOU button is disabled', async () => {
|
||||||
|
const button = await driver.findElement(By.css('button')).isEnabled()
|
||||||
|
assert.equal(button, false, 'disabled continue button')
|
||||||
|
const element = await driver.findElement(By.linkText('Attributions'))
|
||||||
|
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('allows the button to be clicked when scrolled to the bottom of TOU', async () => {
|
||||||
|
const button = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-center.flex-grow > button'))
|
||||||
|
await delay(300)
|
||||||
|
const buttonEnabled = await button.isEnabled()
|
||||||
|
assert.equal(buttonEnabled, true, 'enabled continue button')
|
||||||
|
await delay(200)
|
||||||
|
await button.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts password with length of eight', async () => {
|
||||||
|
const passwordBox = await driver.findElement(By.id('password-box'))
|
||||||
|
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
||||||
|
const button = await driver.findElements(By.css('button'))
|
||||||
|
|
||||||
|
await passwordBox.sendKeys('123456789')
|
||||||
|
await passwordBoxConfirm.sendKeys('123456789')
|
||||||
|
await button[0].click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows value was created and seed phrase', async () => {
|
||||||
|
await delay(300)
|
||||||
|
const seedPhrase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
||||||
|
assert.equal(seedPhrase.split(' ').length, 12)
|
||||||
|
const continueAfterSeedPhrase = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > button:nth-child(4)'))
|
||||||
|
assert.equal(await continueAfterSeedPhrase.getText(), `I'VE COPIED IT SOMEWHERE SAFE`)
|
||||||
|
await continueAfterSeedPhrase.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows account address', async function () {
|
||||||
|
accountAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > div:nth-child(1) > flex-column > div.flex-row > div')).getText()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('logs out of the vault', async () => {
|
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||||
|
await delay(500)
|
||||||
|
const logoutButton = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||||
|
assert.equal(await logoutButton.getText(), 'Log Out')
|
||||||
|
await logoutButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('accepts account password after lock', async () => {
|
||||||
|
await delay(500)
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys(webdriver.Key.ENTER)
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('shows QR code option', async () => {
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks QR code address is the same as account details address', async () => {
|
||||||
|
const QRaccountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
||||||
|
assert.equal(accountAddress.toLowerCase(), QRaccountAddress)
|
||||||
|
await driver.findElement(By.css('.fa-arrow-left')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Import Ganache seed phrase', function () {
|
||||||
|
it('logs out', async function () {
|
||||||
|
await driver.findElement(By.css('.sandwich-expando')).click()
|
||||||
|
await delay(200)
|
||||||
|
const logOut = await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)'))
|
||||||
|
assert.equal(await logOut.getText(), 'Log Out')
|
||||||
|
await logOut.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('restores from seed phrase', async function () {
|
||||||
|
const restoreSeedLink = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div.flex-row.flex-center.flex-grow > p'))
|
||||||
|
assert.equal(await restoreSeedLink.getText(), 'Restore from seed phrase')
|
||||||
|
await restoreSeedLink.click()
|
||||||
|
await delay(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds seed phrase', async function () {
|
||||||
|
const testSeedPhrase = 'phrase upgrade clock rough situate wedding elder clever doctor stamp excess tent'
|
||||||
|
const seedTextArea = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > textarea'))
|
||||||
|
await seedTextArea.sendKeys(testSeedPhrase)
|
||||||
|
|
||||||
|
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.id('password-box-confirm')).sendKeys('123456789')
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > div > button:nth-child(2)')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('balance renders', async function () {
|
||||||
|
await delay(200)
|
||||||
|
const balance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > div.ether-balance.ether-balance-amount > div > div > div:nth-child(1) > div:nth-child(1)'))
|
||||||
|
assert.equal(await balance.getText(), '100.000')
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends transaction', async function () {
|
||||||
|
const sendButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div.flex-row > button:nth-child(4)'))
|
||||||
|
assert.equal(await sendButton.getText(), 'SEND')
|
||||||
|
await sendButton.click()
|
||||||
|
await delay(200)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds recipient address and amount', async function () {
|
||||||
|
const sendTranscationScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > h3:nth-child(2)')).getText()
|
||||||
|
assert.equal(sendTranscationScreen, 'SEND TRANSACTION')
|
||||||
|
const inputAddress = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(3) > div > input'))
|
||||||
|
const inputAmmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > input'))
|
||||||
|
await inputAddress.sendKeys('0x2f318C334780961FB129D2a6c30D0763d9a5C970')
|
||||||
|
await inputAmmount.sendKeys('10')
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section:nth-child(4) > button')).click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('confirms transaction', async function () {
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input')).click()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('finds the transaction in the transactions list', async function () {
|
||||||
|
const tranasactionAmount = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > section > div > div > div > div.ether-balance.ether-balance-amount > div > div > div > div:nth-child(1)'))
|
||||||
|
assert.equal(await tranasactionAmount.getText(), '10.0')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Token Factory', function () {
|
||||||
|
|
||||||
|
it('navigates to token factory', async function () {
|
||||||
|
await driver.get('http://tokenfactory.surge.sh/')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates to create token contract link', async function () {
|
||||||
|
const createToken = await driver.findElement(By.css('#bs-example-navbar-collapse-1 > ul > li:nth-child(3) > a'))
|
||||||
|
await createToken.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds input for token', async function () {
|
||||||
|
const totalSupply = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(5) > input'))
|
||||||
|
const tokenName = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(6) > input'))
|
||||||
|
const tokenDecimal = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(7) > input'))
|
||||||
|
const tokenSymbol = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > div:nth-child(8) > input'))
|
||||||
|
const createToken = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > div > button'))
|
||||||
|
|
||||||
|
await totalSupply.sendKeys('100')
|
||||||
|
await tokenName.sendKeys('Test')
|
||||||
|
await tokenDecimal.sendKeys('0')
|
||||||
|
await tokenSymbol.sendKeys('TST')
|
||||||
|
await createToken.click()
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
// There is an issue with blank confirmation window, but the button is still there and the driver is able to clicked (?.?)
|
||||||
|
it('confirms transaction in MetaMask popup', async function () {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[windowHandles.length - 1])
|
||||||
|
const metamaskSubmit = await driver.findElement(By.css('#pending-tx-form > div.flex-row.flex-space-around.conf-buttons > input'))
|
||||||
|
await metamaskSubmit.click()
|
||||||
|
await delay(1000)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('switches back to Token Factory to grab the token contract address', async function () {
|
||||||
|
const windowHandles = await driver.getAllWindowHandles()
|
||||||
|
await driver.switchTo().window(windowHandles[0])
|
||||||
|
const tokenContactAddress = await driver.findElement(By.css('#main > div > div > div > div:nth-child(2) > span:nth-child(3)'))
|
||||||
|
tokenAddress = await tokenContactAddress.getText()
|
||||||
|
await delay(500)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates back to MetaMask popup in the tab', async function () {
|
||||||
|
await driver.get(`moz-extension://${extensionId}/popup.html`)
|
||||||
|
await delay(700)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Add Token', function () {
|
||||||
|
it('switches to the add token screen', async function () {
|
||||||
|
const tokensTab = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div > div.inactiveForm.pointer'))
|
||||||
|
assert.equal(await tokensTab.getText(), 'TOKENS')
|
||||||
|
await tokensTab.click()
|
||||||
|
await delay(300)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('navigates to the add token screen', async function () {
|
||||||
|
const addTokenButton = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > section > div.full-flex-height > div > button'))
|
||||||
|
assert.equal(await addTokenButton.getText(), 'ADD TOKEN')
|
||||||
|
await addTokenButton.click()
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks add token screen rendered', async function () {
|
||||||
|
const addTokenScreen = await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.section-title.flex-row.flex-center > h2'))
|
||||||
|
assert.equal(await addTokenScreen.getText(), 'ADD TOKEN')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('adds token parameters', async function () {
|
||||||
|
const tokenContractAddress = await driver.findElement(By.css('#token-address'))
|
||||||
|
await tokenContractAddress.sendKeys(tokenAddress)
|
||||||
|
await delay(300)
|
||||||
|
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div.flex-column.flex-justify-center.flex-grow.select-none > div > button')).click()
|
||||||
|
await delay(100)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('checks the token balance', async function () {
|
||||||
|
const tokenBalance = await driver.findElement(By.css('#app-content > div > div.app-primary.from-left > div > section > div.full-flex-height > ol > li:nth-child(2) > h3'))
|
||||||
|
assert.equal(await tokenBalance.getText(), '100 TST')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
async function setProviderType(type) {
|
||||||
|
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function verboseReportOnFailure(test) {
|
||||||
|
const artifactDir = `./test-artifacts/firefox/${test.title}`
|
||||||
|
const filepathBase = `${artifactDir}/test-failure`
|
||||||
|
await pify(mkdirp)(artifactDir)
|
||||||
|
// capture screenshot
|
||||||
|
const screenshot = await driver.takeScreenshot()
|
||||||
|
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
||||||
|
// capture dom source
|
||||||
|
const htmlSource = await driver.getPageSource()
|
||||||
|
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
async function installWebExt (driver, extension) {
|
||||||
|
const cmd = await new Command('moz-install-web-ext')
|
||||||
|
.setParameter('path', path.resolve(extension))
|
||||||
|
.setParameter('temporary', true)
|
||||||
|
|
||||||
|
await driver.getExecutor()
|
||||||
|
.defineCommand(cmd.getName(), 'POST', '/session/:sessionId/moz/addon/install')
|
||||||
|
|
||||||
|
return await driver.schedule(cmd, 'installWebExt(' + extension + ')')
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,5 @@
|
|||||||
require('chromedriver')
|
require('chromedriver')
|
||||||
|
require('geckodriver')
|
||||||
const webdriver = require('selenium-webdriver')
|
const webdriver = require('selenium-webdriver')
|
||||||
|
|
||||||
exports.delay = function delay (time) {
|
exports.delay = function delay (time) {
|
||||||
@ -6,13 +7,16 @@ exports.delay = function delay (time) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
exports.buildWebDriver = function buildWebDriver (extPath) {
|
exports.buildChromeWebDriver = function buildChromeWebDriver (extPath) {
|
||||||
return new webdriver.Builder()
|
return new webdriver.Builder()
|
||||||
.withCapabilities({
|
.withCapabilities({
|
||||||
chromeOptions: {
|
chromeOptions: {
|
||||||
args: [`load-extension=${extPath}`],
|
args: [`load-extension=${extPath}`],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
.forBrowser('chrome')
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.buildFirefoxWebdriver = function buildFirefoxWebdriver (extPath) {
|
||||||
|
return new webdriver.Builder().build()
|
||||||
|
}
|
||||||
|
@ -1,145 +0,0 @@
|
|||||||
const fs = require('fs')
|
|
||||||
const mkdirp = require('mkdirp')
|
|
||||||
const path = require('path')
|
|
||||||
const assert = require('assert')
|
|
||||||
const pify = require('pify')
|
|
||||||
const webdriver = require('selenium-webdriver')
|
|
||||||
const By = webdriver.By
|
|
||||||
const { delay, buildWebDriver } = require('./func')
|
|
||||||
|
|
||||||
describe('Metamask popup page', function () {
|
|
||||||
let driver
|
|
||||||
this.seedPhase
|
|
||||||
this.accountAddress
|
|
||||||
this.timeout(0)
|
|
||||||
|
|
||||||
before(async function () {
|
|
||||||
const extPath = path.resolve('dist/chrome')
|
|
||||||
driver = buildWebDriver(extPath)
|
|
||||||
await driver.get('chrome://extensions-frame')
|
|
||||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper'))
|
|
||||||
const extensionId = await elems[1].getAttribute('id')
|
|
||||||
await driver.get(`chrome-extension://${extensionId}/popup.html`)
|
|
||||||
await delay(500)
|
|
||||||
})
|
|
||||||
|
|
||||||
afterEach(async function () {
|
|
||||||
if (this.currentTest.state === 'failed') {
|
|
||||||
await verboseReportOnFailure(this.currentTest)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
after(async function () {
|
|
||||||
await driver.quit()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('#onboarding', () => {
|
|
||||||
it('should open Metamask.io', async function () {
|
|
||||||
const tabs = await driver.getAllWindowHandles()
|
|
||||||
await driver.switchTo().window(tabs[0])
|
|
||||||
await delay(300)
|
|
||||||
await setProviderType('localhost')
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should match title', async () => {
|
|
||||||
const title = await driver.getTitle()
|
|
||||||
assert.equal(title, 'MetaMask', 'title matches MetaMask')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show privacy notice', async () => {
|
|
||||||
const privacy = await driver.findElement(By.css('.terms-header')).getText()
|
|
||||||
assert.equal(privacy, 'PRIVACY NOTICE', 'shows privacy notice')
|
|
||||||
driver.findElement(By.css('button')).click()
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show terms of use', async () => {
|
|
||||||
await delay(300)
|
|
||||||
const terms = await driver.findElement(By.css('.terms-header')).getText()
|
|
||||||
assert.equal(terms, 'TERMS OF USE', 'shows terms of use')
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should be unable to continue without scolling throught the terms of use', async () => {
|
|
||||||
const button = await driver.findElement(By.css('button')).isEnabled()
|
|
||||||
assert.equal(button, false, 'disabled continue button')
|
|
||||||
const element = driver.findElement(By.linkText(
|
|
||||||
'Attributions'
|
|
||||||
))
|
|
||||||
await driver.executeScript('arguments[0].scrollIntoView(true)', element)
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should be able to continue when scrolled to the bottom of terms of use', async () => {
|
|
||||||
const button = await driver.findElement(By.css('button'))
|
|
||||||
const buttonEnabled = await button.isEnabled()
|
|
||||||
await delay(500)
|
|
||||||
assert.equal(buttonEnabled, true, 'enabled continue button')
|
|
||||||
await button.click()
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should accept password with length of eight', async () => {
|
|
||||||
const passwordBox = await driver.findElement(By.id('password-box'))
|
|
||||||
const passwordBoxConfirm = await driver.findElement(By.id('password-box-confirm'))
|
|
||||||
const button = driver.findElement(By.css('button'))
|
|
||||||
|
|
||||||
passwordBox.sendKeys('123456789')
|
|
||||||
passwordBoxConfirm.sendKeys('123456789')
|
|
||||||
await delay(500)
|
|
||||||
await button.click()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show value was created and seed phrase', async () => {
|
|
||||||
await delay(700)
|
|
||||||
this.seedPhase = await driver.findElement(By.css('.twelve-word-phrase')).getText()
|
|
||||||
const continueAfterSeedPhrase = await driver.findElement(By.css('button'))
|
|
||||||
await continueAfterSeedPhrase.click()
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show lock account', async () => {
|
|
||||||
await driver.findElement(By.css('.sandwich-expando')).click()
|
|
||||||
await delay(500)
|
|
||||||
await driver.findElement(By.css('#app-content > div > div:nth-child(3) > span > div > li:nth-child(3)')).click()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should accept account password after lock', async () => {
|
|
||||||
await delay(500)
|
|
||||||
await driver.findElement(By.id('password-box')).sendKeys('123456789')
|
|
||||||
await driver.findElement(By.css('button')).click()
|
|
||||||
await delay(500)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show QR code option', async () => {
|
|
||||||
await delay(300)
|
|
||||||
await driver.findElement(By.css('.fa-ellipsis-h')).click()
|
|
||||||
await driver.findElement(By.css('#app-content > div > div.app-primary.from-right > div > div > div:nth-child(1) > flex-column > div.name-label > div > span > i > div > div > li:nth-child(3)')).click()
|
|
||||||
await delay(300)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should show the account address', async () => {
|
|
||||||
this.accountAddress = await driver.findElement(By.css('.ellip-address')).getText()
|
|
||||||
await driver.findElement(By.css('.fa-arrow-left')).click()
|
|
||||||
await delay(500)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
async function setProviderType(type) {
|
|
||||||
await driver.executeScript('window.metamask.setProviderType(arguments[0])', type)
|
|
||||||
}
|
|
||||||
|
|
||||||
async function verboseReportOnFailure(test) {
|
|
||||||
const artifactDir = `./test-artifacts/${test.title}`
|
|
||||||
const filepathBase = `${artifactDir}/test-failure`
|
|
||||||
await pify(mkdirp)(artifactDir)
|
|
||||||
// capture screenshot
|
|
||||||
const screenshot = await driver.takeScreenshot()
|
|
||||||
await pify(fs.writeFile)(`${filepathBase}-screenshot.png`, screenshot, { encoding: 'base64' })
|
|
||||||
// capture dom source
|
|
||||||
const htmlSource = await driver.getPageSource()
|
|
||||||
await pify(fs.writeFile)(`${filepathBase}-dom.html`, htmlSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
})
|
|
@ -1,5 +1,4 @@
|
|||||||
const PASSWORD = 'password123'
|
const PASSWORD = 'password123'
|
||||||
const reactTriggerChange = require('react-trigger-change')
|
|
||||||
const {
|
const {
|
||||||
timeout,
|
timeout,
|
||||||
findAsync,
|
findAsync,
|
||||||
@ -11,6 +10,11 @@ async function runFirstTimeUsageTest (assert, done) {
|
|||||||
|
|
||||||
const app = await queryAsync($, '#app-content')
|
const app = await queryAsync($, '#app-content')
|
||||||
|
|
||||||
|
// Used to set values on TextField input component
|
||||||
|
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
|
||||||
|
window.HTMLInputElement.prototype, 'value'
|
||||||
|
).set
|
||||||
|
|
||||||
await skipNotices(app)
|
await skipNotices(app)
|
||||||
|
|
||||||
const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0]
|
const welcomeButton = (await findAsync(app, '.welcome-screen__button'))[0]
|
||||||
@ -21,12 +25,14 @@ async function runFirstTimeUsageTest (assert, done) {
|
|||||||
assert.equal(title, 'Create Password', 'create password screen')
|
assert.equal(title, 'Create Password', 'create password screen')
|
||||||
|
|
||||||
// enter password
|
// enter password
|
||||||
const pwBox = (await findAsync(app, '.first-time-flow__input'))[0]
|
const pwBox = (await findAsync(app, '#create-password'))[0]
|
||||||
const confBox = (await findAsync(app, '.first-time-flow__input'))[1]
|
const confBox = (await findAsync(app, '#confirm-password'))[0]
|
||||||
pwBox.value = PASSWORD
|
|
||||||
confBox.value = PASSWORD
|
nativeInputValueSetter.call(pwBox, PASSWORD)
|
||||||
reactTriggerChange(pwBox)
|
pwBox.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
reactTriggerChange(confBox)
|
|
||||||
|
nativeInputValueSetter.call(confBox, PASSWORD)
|
||||||
|
confBox.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
|
|
||||||
// Create Password
|
// Create Password
|
||||||
const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0]
|
const createButton = (await findAsync(app, 'button.first-time-flow__button'))[0]
|
||||||
@ -71,10 +77,16 @@ async function runFirstTimeUsageTest (assert, done) {
|
|||||||
assert.ok(lock, 'Lock menu item found')
|
assert.ok(lock, 'Lock menu item found')
|
||||||
lock.click()
|
lock.click()
|
||||||
|
|
||||||
const pwBox2 = (await findAsync(app, '#password-box'))[0]
|
await timeout(1000)
|
||||||
pwBox2.value = PASSWORD
|
|
||||||
|
|
||||||
const createButton2 = (await findAsync(app, 'button.primary'))[0]
|
const pwBox2 = (await findAsync(app, '#password'))[0]
|
||||||
|
pwBox2.focus()
|
||||||
|
await timeout(1000)
|
||||||
|
|
||||||
|
nativeInputValueSetter.call(pwBox2, PASSWORD)
|
||||||
|
pwBox2.dispatchEvent(new Event('input', { bubbles: true}))
|
||||||
|
|
||||||
|
const createButton2 = (await findAsync(app, 'button[type="submit"]'))[0]
|
||||||
createButton2.click()
|
createButton2.click()
|
||||||
|
|
||||||
const detail2 = (await findAsync(app, '.wallet-view'))[0]
|
const detail2 = (await findAsync(app, '.wallet-view'))[0]
|
||||||
|
@ -21,7 +21,7 @@ async function runTxListItemsTest(assert, done) {
|
|||||||
selectState.val('tx list items')
|
selectState.val('tx list items')
|
||||||
reactTriggerChange(selectState[0])
|
reactTriggerChange(selectState[0])
|
||||||
|
|
||||||
const metamaskLogo = await queryAsync($, '.left-menu-wrapper')
|
const metamaskLogo = await queryAsync($, '.app-header__logo-container')
|
||||||
assert.ok(metamaskLogo[0], 'metamask logo present')
|
assert.ok(metamaskLogo[0], 'metamask logo present')
|
||||||
metamaskLogo[0].click()
|
metamaskLogo[0].click()
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ async function runTxListItemsTest(assert, done) {
|
|||||||
const failedTx = txListItems[4]
|
const failedTx = txListItems[4]
|
||||||
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status')
|
const failedTxRenderedStatus = await findAsync($(failedTx), '.tx-list-status')
|
||||||
assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label')
|
assert.equal(failedTxRenderedStatus[0].textContent, 'Failed', 'failedTx has correct label')
|
||||||
|
|
||||||
const shapeShiftTx = txListItems[5]
|
const shapeShiftTx = txListItems[5]
|
||||||
const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)')
|
const shapeShiftTxStatus = await findAsync($(shapeShiftTx), '.flex-column div:eq(1)')
|
||||||
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
assert.equal(shapeShiftTxStatus[0].textContent, 'No deposits received', 'shapeShiftTx has correct status')
|
||||||
|
@ -39,8 +39,7 @@ async function captureAllScreens() {
|
|||||||
const extPath = path.resolve('dist/chrome')
|
const extPath = path.resolve('dist/chrome')
|
||||||
driver = buildWebDriver(extPath)
|
driver = buildWebDriver(extPath)
|
||||||
await driver.get('chrome://extensions-frame')
|
await driver.get('chrome://extensions-frame')
|
||||||
const elems = await driver.findElements(By.css('.extension-list-item-wrapper'))
|
const extensionId = await driver.executeScript('return document.querySelector("extensions-manager").shadowRoot.querySelector("extensions-view-manager extensions-item-list").shadowRoot.querySelector("#container > div.items-container > extensions-item:nth-child(2)").getAttribute("id")')
|
||||||
const extensionId = await elems[1].getAttribute('id')
|
|
||||||
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
await driver.get(`chrome-extension://${extensionId}/home.html`)
|
||||||
await delay(500)
|
await delay(500)
|
||||||
tabs = await driver.getAllWindowHandles()
|
tabs = await driver.getAllWindowHandles()
|
||||||
@ -75,8 +74,8 @@ async function captureAllScreens() {
|
|||||||
await driver.findElement(By.css('button')).click()
|
await driver.findElement(By.css('button')).click()
|
||||||
await captureLanguageScreenShots('create password')
|
await captureLanguageScreenShots('create password')
|
||||||
|
|
||||||
const passwordBox = await driver.findElement(By.css('input[type=password]:nth-of-type(1)'))
|
const passwordBox = await driver.findElement(By.css('input#create-password'))
|
||||||
const passwordBoxConfirm = await driver.findElement(By.css('input[type=password]:nth-of-type(2)'))
|
const passwordBoxConfirm = await driver.findElement(By.css('input#confirm-password'))
|
||||||
passwordBox.sendKeys('123456789')
|
passwordBox.sendKeys('123456789')
|
||||||
passwordBoxConfirm.sendKeys('123456789')
|
passwordBoxConfirm.sendKeys('123456789')
|
||||||
await delay(500)
|
await delay(500)
|
||||||
|
@ -3,17 +3,15 @@ const nock = require('nock')
|
|||||||
const NetworkController = require('../../app/scripts/controllers/network')
|
const NetworkController = require('../../app/scripts/controllers/network')
|
||||||
const {
|
const {
|
||||||
getNetworkDisplayName,
|
getNetworkDisplayName,
|
||||||
getNetworkEndpoints,
|
|
||||||
} = require('../../app/scripts/controllers/network/util')
|
} = require('../../app/scripts/controllers/network/util')
|
||||||
|
|
||||||
const { createTestProviderTools } = require('../stub/provider')
|
const { createTestProviderTools } = require('../stub/provider')
|
||||||
const providerResultStub = {}
|
const providerResultStub = {}
|
||||||
const provider = createTestProviderTools({ scaffold: providerResultStub }).provider
|
|
||||||
|
|
||||||
describe('# Network Controller', function () {
|
describe('# Network Controller', function () {
|
||||||
let networkController
|
let networkController
|
||||||
const noop = () => {}
|
const noop = () => {}
|
||||||
const networkControllerProviderInit = {
|
const networkControllerProviderConfig = {
|
||||||
getAccounts: noop,
|
getAccounts: noop,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,11 +22,9 @@ describe('# Network Controller', function () {
|
|||||||
.post('/metamask')
|
.post('/metamask')
|
||||||
.reply(200)
|
.reply(200)
|
||||||
|
|
||||||
networkController = new NetworkController({
|
networkController = new NetworkController()
|
||||||
provider,
|
|
||||||
})
|
|
||||||
|
|
||||||
networkController.initializeProvider(networkControllerProviderInit, provider)
|
networkController.initializeProvider(networkControllerProviderConfig)
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
@ -38,7 +34,7 @@ describe('# Network Controller', function () {
|
|||||||
describe('network', function () {
|
describe('network', function () {
|
||||||
describe('#provider', function () {
|
describe('#provider', function () {
|
||||||
it('provider should be updatable without reassignment', function () {
|
it('provider should be updatable without reassignment', function () {
|
||||||
networkController.initializeProvider(networkControllerProviderInit, provider)
|
networkController.initializeProvider(networkControllerProviderConfig)
|
||||||
const proxy = networkController._proxy
|
const proxy = networkController._proxy
|
||||||
proxy.setTarget({ test: true, on: () => {} })
|
proxy.setTarget({ test: true, on: () => {} })
|
||||||
assert.ok(proxy.test)
|
assert.ok(proxy.test)
|
||||||
@ -59,12 +55,6 @@ describe('# Network Controller', function () {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#getRpcAddressForType', function () {
|
|
||||||
it('should return the right rpc address', function () {
|
|
||||||
const rpcTarget = networkController.getRpcAddressForType('mainnet')
|
|
||||||
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
describe('#setProviderType', function () {
|
describe('#setProviderType', function () {
|
||||||
it('should update provider.type', function () {
|
it('should update provider.type', function () {
|
||||||
networkController.setProviderType('mainnet')
|
networkController.setProviderType('mainnet')
|
||||||
@ -76,16 +66,11 @@ describe('# Network Controller', function () {
|
|||||||
const loading = networkController.isNetworkLoading()
|
const loading = networkController.isNetworkLoading()
|
||||||
assert.ok(loading, 'network is loading')
|
assert.ok(loading, 'network is loading')
|
||||||
})
|
})
|
||||||
it('should set the right rpcTarget', function () {
|
|
||||||
networkController.setProviderType('mainnet')
|
|
||||||
const rpcTarget = networkController.getProviderConfig().rpcTarget
|
|
||||||
assert.equal(rpcTarget, 'https://mainnet.infura.io/metamask', 'returns the right rpcAddress')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('# Network utils', () => {
|
describe('Network utils', () => {
|
||||||
it('getNetworkDisplayName should return the correct network name', () => {
|
it('getNetworkDisplayName should return the correct network name', () => {
|
||||||
const tests = [
|
const tests = [
|
||||||
{
|
{
|
||||||
@ -114,9 +99,4 @@ describe('# Network utils', () => {
|
|||||||
|
|
||||||
tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected))
|
tests.forEach(({ input, expected }) => assert.equal(getNetworkDisplayName(input), expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
it('getNetworkEndpoints should return the correct endpoints', () => {
|
|
||||||
assert.equal(getNetworkEndpoints('networkBeta').ropsten, 'https://ropsten.infura.io/metamask2')
|
|
||||||
assert.equal(getNetworkEndpoints('network').rinkeby, 'https://rinkeby.infura.io/metamask')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
@ -271,7 +271,6 @@ var actions = {
|
|||||||
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
|
SET_MOUSE_USER_STATE: 'SET_MOUSE_USER_STATE',
|
||||||
|
|
||||||
// Network
|
// Network
|
||||||
setNetworkEndpoints,
|
|
||||||
updateNetworkEndpointType,
|
updateNetworkEndpointType,
|
||||||
UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
|
UPDATE_NETWORK_ENDPOINT_TYPE: 'UPDATE_NETWORK_ENDPOINT_TYPE',
|
||||||
|
|
||||||
@ -317,6 +316,7 @@ function tryUnlockMetamask (password) {
|
|||||||
background.verifySeedPhrase(err => {
|
background.verifySeedPhrase(err => {
|
||||||
if (err) {
|
if (err) {
|
||||||
dispatch(actions.displayWarning(err.message))
|
dispatch(actions.displayWarning(err.message))
|
||||||
|
return reject(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve()
|
resolve()
|
||||||
@ -330,6 +330,7 @@ function tryUnlockMetamask (password) {
|
|||||||
.catch(err => {
|
.catch(err => {
|
||||||
dispatch(actions.unlockFailed(err.message))
|
dispatch(actions.unlockFailed(err.message))
|
||||||
dispatch(actions.hideLoadingIndication())
|
dispatch(actions.hideLoadingIndication())
|
||||||
|
return Promise.reject(err)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1922,23 +1923,6 @@ function setLocaleMessages (localeMessages) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setNetworkEndpoints (networkEndpointType) {
|
|
||||||
return dispatch => {
|
|
||||||
log.debug('background.setNetworkEndpoints')
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
background.setNetworkEndpoints(networkEndpointType, err => {
|
|
||||||
if (err) {
|
|
||||||
dispatch(actions.displayWarning(err.message))
|
|
||||||
return reject(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
dispatch(actions.updateNetworkEndpointType(networkEndpointType))
|
|
||||||
resolve(networkEndpointType)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateNetworkEndpointType (networkEndpointType) {
|
function updateNetworkEndpointType (networkEndpointType) {
|
||||||
return {
|
return {
|
||||||
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,
|
type: actions.UPDATE_NETWORK_ENDPOINT_TYPE,
|
||||||
|
119
ui/app/app.js
119
ui/app/app.js
@ -22,7 +22,7 @@ const Home = require('./components/pages/home')
|
|||||||
const Authenticated = require('./components/pages/authenticated')
|
const Authenticated = require('./components/pages/authenticated')
|
||||||
const Initialized = require('./components/pages/initialized')
|
const Initialized = require('./components/pages/initialized')
|
||||||
const Settings = require('./components/pages/settings')
|
const Settings = require('./components/pages/settings')
|
||||||
const UnlockPage = require('./components/pages/unlock')
|
const UnlockPage = require('./components/pages/unlock-page')
|
||||||
const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
|
const RestoreVaultPage = require('./components/pages/keychains/restore-vault')
|
||||||
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
|
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
|
||||||
const AddTokenPage = require('./components/pages/add-token')
|
const AddTokenPage = require('./components/pages/add-token')
|
||||||
@ -30,8 +30,6 @@ const CreateAccountPage = require('./components/pages/create-account')
|
|||||||
const NoticeScreen = require('./components/pages/notice')
|
const NoticeScreen = require('./components/pages/notice')
|
||||||
|
|
||||||
const Loading = require('./components/loading-screen')
|
const Loading = require('./components/loading-screen')
|
||||||
const NetworkIndicator = require('./components/network')
|
|
||||||
const Identicon = require('./components/identicon')
|
|
||||||
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
const ReactCSSTransitionGroup = require('react-addons-css-transition-group')
|
||||||
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
|
const NetworkDropdown = require('./components/dropdowns/network-dropdown')
|
||||||
const AccountMenu = require('./components/account-menu')
|
const AccountMenu = require('./components/account-menu')
|
||||||
@ -39,6 +37,8 @@ const AccountMenu = require('./components/account-menu')
|
|||||||
// Global Modals
|
// Global Modals
|
||||||
const Modal = require('./components/modals/index').Modal
|
const Modal = require('./components/modals/index').Modal
|
||||||
|
|
||||||
|
const AppHeader = require('./components/app-header')
|
||||||
|
|
||||||
// Routes
|
// Routes
|
||||||
const {
|
const {
|
||||||
DEFAULT_ROUTE,
|
DEFAULT_ROUTE,
|
||||||
@ -69,11 +69,11 @@ class App extends Component {
|
|||||||
return (
|
return (
|
||||||
h(Switch, [
|
h(Switch, [
|
||||||
h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
|
h(Route, { path: INITIALIZE_ROUTE, component: InitializeScreen }),
|
||||||
h(Initialized, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
|
|
||||||
h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
|
h(Initialized, { path: UNLOCK_ROUTE, exact, component: UnlockPage }),
|
||||||
h(Initialized, { path: SETTINGS_ROUTE, component: Settings }),
|
|
||||||
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
|
h(Initialized, { path: RESTORE_VAULT_ROUTE, exact, component: RestoreVaultPage }),
|
||||||
h(Initialized, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
|
h(Authenticated, { path: REVEAL_SEED_ROUTE, exact, component: RevealSeedConfirmation }),
|
||||||
|
h(Authenticated, { path: SETTINGS_ROUTE, component: Settings }),
|
||||||
|
h(Authenticated, { path: NOTICE_ROUTE, exact, component: NoticeScreen }),
|
||||||
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
|
h(Authenticated, { path: CONFIRM_TRANSACTION_ROUTE, component: ConfirmTxScreen }),
|
||||||
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
|
h(Authenticated, { path: SEND_ROUTE, exact, component: SendTransactionScreen2 }),
|
||||||
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
h(Authenticated, { path: ADD_TOKEN_ROUTE, exact, component: AddTokenPage }),
|
||||||
@ -119,8 +119,7 @@ class App extends Component {
|
|||||||
// global modal
|
// global modal
|
||||||
h(Modal, {}, []),
|
h(Modal, {}, []),
|
||||||
|
|
||||||
// app bar
|
h(AppHeader),
|
||||||
this.renderAppBar(),
|
|
||||||
|
|
||||||
// sidebar
|
// sidebar
|
||||||
this.renderSidebar(),
|
this.renderSidebar(),
|
||||||
@ -197,110 +196,6 @@ class App extends Component {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
renderAppBar () {
|
|
||||||
const {
|
|
||||||
isUnlocked,
|
|
||||||
network,
|
|
||||||
provider,
|
|
||||||
networkDropdownOpen,
|
|
||||||
showNetworkDropdown,
|
|
||||||
hideNetworkDropdown,
|
|
||||||
isInitialized,
|
|
||||||
welcomeScreenSeen,
|
|
||||||
isPopup,
|
|
||||||
betaUI,
|
|
||||||
} = this.props
|
|
||||||
|
|
||||||
if (window.METAMASK_UI_TYPE === 'notification') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = this.props
|
|
||||||
const {isMascara, isOnboarding} = props
|
|
||||||
|
|
||||||
// Do not render header if user is in mascara onboarding
|
|
||||||
if (isMascara && isOnboarding) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not render header if user is in mascara buy ether
|
|
||||||
if (isMascara && props.currentView.name === 'buyEth') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
|
|
||||||
h('.full-width', {
|
|
||||||
style: {},
|
|
||||||
}, [
|
|
||||||
|
|
||||||
(isInitialized || welcomeScreenSeen || isPopup || !betaUI) && h('.app-header.flex-row.flex-space-between', {
|
|
||||||
className: classnames({
|
|
||||||
'app-header--initialized': !isOnboarding,
|
|
||||||
}),
|
|
||||||
}, [
|
|
||||||
h('div.app-header-contents', {}, [
|
|
||||||
h('div.left-menu-wrapper', {
|
|
||||||
onClick: () => props.history.push(DEFAULT_ROUTE),
|
|
||||||
}, [
|
|
||||||
// mini logo
|
|
||||||
h('img.metafox-icon', {
|
|
||||||
height: 42,
|
|
||||||
width: 42,
|
|
||||||
src: '/images/metamask-fox.svg',
|
|
||||||
}),
|
|
||||||
|
|
||||||
// metamask name
|
|
||||||
h('.flex-row', [
|
|
||||||
h('h1', this.context.t('appName')),
|
|
||||||
h('div.beta-label', this.context.t('beta')),
|
|
||||||
]),
|
|
||||||
|
|
||||||
]),
|
|
||||||
|
|
||||||
betaUI && isInitialized && h('div.header__right-actions', [
|
|
||||||
h('div.network-component-wrapper', {
|
|
||||||
style: {},
|
|
||||||
}, [
|
|
||||||
// Network Indicator
|
|
||||||
h(NetworkIndicator, {
|
|
||||||
network,
|
|
||||||
provider,
|
|
||||||
disabled: this.props.location.pathname === CONFIRM_TRANSACTION_ROUTE,
|
|
||||||
onClick: (event) => {
|
|
||||||
event.preventDefault()
|
|
||||||
event.stopPropagation()
|
|
||||||
return networkDropdownOpen === false
|
|
||||||
? showNetworkDropdown()
|
|
||||||
: hideNetworkDropdown()
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
|
|
||||||
]),
|
|
||||||
|
|
||||||
isUnlocked && h('div.account-menu__icon', { onClick: this.props.toggleAccountMenu }, [
|
|
||||||
h(Identicon, {
|
|
||||||
address: this.props.selectedAddress,
|
|
||||||
diameter: 32,
|
|
||||||
}),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
]),
|
|
||||||
|
|
||||||
!isInitialized && !isPopup && betaUI && h('.alpha-warning__container', {}, [
|
|
||||||
h('h2', {
|
|
||||||
className: classnames({
|
|
||||||
'alpha-warning': welcomeScreenSeen,
|
|
||||||
'alpha-warning-welcome-screen': !welcomeScreenSeen,
|
|
||||||
}),
|
|
||||||
}, 'Please be aware that this version is still under development'),
|
|
||||||
]),
|
|
||||||
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
toggleMetamaskActive () {
|
toggleMetamaskActive () {
|
||||||
if (!this.props.isUnlocked) {
|
if (!this.props.isUnlocked) {
|
||||||
// currently inactive: redirect to password box
|
// currently inactive: redirect to password box
|
||||||
|
140
ui/app/components/app-header/app-header.component.js
Normal file
140
ui/app/components/app-header/app-header.component.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import classnames from 'classnames'
|
||||||
|
import { matchPath } from 'react-router-dom'
|
||||||
|
|
||||||
|
const {
|
||||||
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
|
} = require('../../../../app/scripts/lib/enums')
|
||||||
|
const { DEFAULT_ROUTE, INITIALIZE_ROUTE, CONFIRM_TRANSACTION_ROUTE } = require('../../routes')
|
||||||
|
const Identicon = require('../identicon')
|
||||||
|
const NetworkIndicator = require('../network')
|
||||||
|
|
||||||
|
class AppHeader extends Component {
|
||||||
|
static propTypes = {
|
||||||
|
history: PropTypes.object,
|
||||||
|
location: PropTypes.object,
|
||||||
|
network: PropTypes.string,
|
||||||
|
provider: PropTypes.object,
|
||||||
|
networkDropdownOpen: PropTypes.bool,
|
||||||
|
showNetworkDropdown: PropTypes.func,
|
||||||
|
hideNetworkDropdown: PropTypes.func,
|
||||||
|
toggleAccountMenu: PropTypes.func,
|
||||||
|
selectedAddress: PropTypes.string,
|
||||||
|
isUnlocked: PropTypes.bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNetworkIndicatorClick (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const { networkDropdownOpen, showNetworkDropdown, hideNetworkDropdown } = this.props
|
||||||
|
|
||||||
|
return networkDropdownOpen === false
|
||||||
|
? showNetworkDropdown()
|
||||||
|
: hideNetworkDropdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
isConfirming () {
|
||||||
|
const { location } = this.props
|
||||||
|
|
||||||
|
return Boolean(matchPath(location.pathname, {
|
||||||
|
path: CONFIRM_TRANSACTION_ROUTE, exact: false,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
renderAccountMenu () {
|
||||||
|
const { isUnlocked, toggleAccountMenu, selectedAddress } = this.props
|
||||||
|
|
||||||
|
return isUnlocked && (
|
||||||
|
<div
|
||||||
|
className={classnames('account-menu__icon', {
|
||||||
|
'account-menu__icon--disabled': this.isConfirming(),
|
||||||
|
})}
|
||||||
|
onClick={() => this.isConfirming() || toggleAccountMenu()}
|
||||||
|
>
|
||||||
|
<Identicon
|
||||||
|
address={selectedAddress}
|
||||||
|
diameter={32}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
hideAppHeader () {
|
||||||
|
const { location } = this.props
|
||||||
|
|
||||||
|
const isInitializing = Boolean(matchPath(location.pathname, {
|
||||||
|
path: INITIALIZE_ROUTE, exact: false,
|
||||||
|
}))
|
||||||
|
|
||||||
|
if (isInitializing) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_NOTIFICATION) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.METAMASK_UI_TYPE === ENVIRONMENT_TYPE_POPUP && this.isConfirming()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const {
|
||||||
|
network,
|
||||||
|
provider,
|
||||||
|
history,
|
||||||
|
location,
|
||||||
|
isUnlocked,
|
||||||
|
} = this.props
|
||||||
|
|
||||||
|
if (this.hideAppHeader()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={classnames('app-header', { 'app-header--back-drop': isUnlocked })}>
|
||||||
|
<div className="app-header__contents">
|
||||||
|
<div
|
||||||
|
className="app-header__logo-container"
|
||||||
|
onClick={() => history.push(DEFAULT_ROUTE)}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
className="app-header__metafox"
|
||||||
|
src="/images/metamask-fox.svg"
|
||||||
|
height={42}
|
||||||
|
width={42}
|
||||||
|
/>
|
||||||
|
<div className="flex-row">
|
||||||
|
<h1>{ this.context.t('appName') }</h1>
|
||||||
|
<div className="app-header__beta-label">
|
||||||
|
{ this.context.t('beta') }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="app-header__account-menu-container">
|
||||||
|
<div className="network-component-wrapper">
|
||||||
|
<NetworkIndicator
|
||||||
|
network={network}
|
||||||
|
provider={provider}
|
||||||
|
onClick={event => this.handleNetworkIndicatorClick(event)}
|
||||||
|
disabled={location.pathname === CONFIRM_TRANSACTION_ROUTE}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{ this.renderAccountMenu() }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AppHeader
|
38
ui/app/components/app-header/app-header.container.js
Normal file
38
ui/app/components/app-header/app-header.container.js
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { withRouter } from 'react-router-dom'
|
||||||
|
import { compose } from 'recompose'
|
||||||
|
|
||||||
|
import AppHeader from './app-header.component'
|
||||||
|
const actions = require('../../actions')
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const { appState, metamask } = state
|
||||||
|
const { networkDropdownOpen } = appState
|
||||||
|
const {
|
||||||
|
network,
|
||||||
|
provider,
|
||||||
|
selectedAddress,
|
||||||
|
isUnlocked,
|
||||||
|
} = metamask
|
||||||
|
|
||||||
|
return {
|
||||||
|
networkDropdownOpen,
|
||||||
|
network,
|
||||||
|
provider,
|
||||||
|
selectedAddress,
|
||||||
|
isUnlocked,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
showNetworkDropdown: () => dispatch(actions.showNetworkDropdown()),
|
||||||
|
hideNetworkDropdown: () => dispatch(actions.hideNetworkDropdown()),
|
||||||
|
toggleAccountMenu: () => dispatch(actions.toggleAccountMenu()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
)(AppHeader)
|
2
ui/app/components/app-header/index.js
Normal file
2
ui/app/components/app-header/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import AppHeader from './app-header.container'
|
||||||
|
module.exports = AppHeader
|
@ -37,7 +37,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
font-size: 14px;
|
font-size: 12px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: $curious-blue;
|
color: $curious-blue;
|
||||||
|
|
||||||
|
@ -12,7 +12,6 @@ const SimpleDropdown = require('../../dropdowns/simple-dropdown')
|
|||||||
const ToggleButton = require('react-toggle-button')
|
const ToggleButton = require('react-toggle-button')
|
||||||
const { REVEAL_SEED_ROUTE } = require('../../../routes')
|
const { REVEAL_SEED_ROUTE } = require('../../../routes')
|
||||||
const locales = require('../../../../../app/_locales/index.json')
|
const locales = require('../../../../../app/_locales/index.json')
|
||||||
const { OLD_UI_NETWORK_TYPE } = require('../../../../../app/scripts/controllers/network/enums')
|
|
||||||
|
|
||||||
const getInfuraCurrencyOptions = () => {
|
const getInfuraCurrencyOptions = () => {
|
||||||
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
|
const sortedCurrencies = infuraCurrencies.objects.sort((a, b) => {
|
||||||
@ -349,7 +348,6 @@ const mapDispatchToProps = dispatch => {
|
|||||||
updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)),
|
updateCurrentLocale: key => dispatch(actions.updateCurrentLocale(key)),
|
||||||
setFeatureFlagToBeta: () => {
|
setFeatureFlagToBeta: () => {
|
||||||
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
|
return dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
showResetAccountConfirmationModal: () => {
|
showResetAccountConfirmationModal: () => {
|
||||||
return dispatch(actions.showModal({ name: 'CONFIRM_RESET_ACCOUNT' }))
|
return dispatch(actions.showModal({ name: 'CONFIRM_RESET_ACCOUNT' }))
|
||||||
|
2
ui/app/components/pages/unlock-page/index.js
Normal file
2
ui/app/components/pages/unlock-page/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import UnlockPage from './unlock-page.container'
|
||||||
|
module.exports = UnlockPage
|
180
ui/app/components/pages/unlock-page/unlock-page.component.js
Normal file
180
ui/app/components/pages/unlock-page/unlock-page.component.js
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
import React, { Component } from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import Button from 'material-ui/Button'
|
||||||
|
import TextField from '../../text-field'
|
||||||
|
|
||||||
|
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../../app/scripts/lib/enums')
|
||||||
|
const { getEnvironmentType } = require('../../../../../app/scripts/lib/util')
|
||||||
|
const getCaretCoordinates = require('textarea-caret')
|
||||||
|
const EventEmitter = require('events').EventEmitter
|
||||||
|
const Mascot = require('../../mascot')
|
||||||
|
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../../routes')
|
||||||
|
|
||||||
|
class UnlockPage extends Component {
|
||||||
|
static contextTypes = {
|
||||||
|
t: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (props) {
|
||||||
|
super(props)
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
password: '',
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
this.animationEventEmitter = new EventEmitter()
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount () {
|
||||||
|
const { isUnlocked, history } = this.props
|
||||||
|
|
||||||
|
if (isUnlocked) {
|
||||||
|
history.push(DEFAULT_ROUTE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tryUnlockMetamask (password) {
|
||||||
|
const { tryUnlockMetamask, history } = this.props
|
||||||
|
tryUnlockMetamask(password)
|
||||||
|
.then(() => history.push(DEFAULT_ROUTE))
|
||||||
|
.catch(({ message }) => this.setState({ error: message }))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSubmit (event) {
|
||||||
|
event.preventDefault()
|
||||||
|
event.stopPropagation()
|
||||||
|
|
||||||
|
const { password } = this.state
|
||||||
|
const { tryUnlockMetamask, history } = this.props
|
||||||
|
|
||||||
|
if (password === '') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setState({ error: null })
|
||||||
|
|
||||||
|
tryUnlockMetamask(password)
|
||||||
|
.then(() => history.push(DEFAULT_ROUTE))
|
||||||
|
.catch(({ message }) => this.setState({ error: message }))
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInputChange ({ target }) {
|
||||||
|
this.setState({ password: target.value, error: null })
|
||||||
|
|
||||||
|
// tell mascot to look at page action
|
||||||
|
const element = target
|
||||||
|
const boundingRect = element.getBoundingClientRect()
|
||||||
|
const coordinates = getCaretCoordinates(element, element.selectionEnd)
|
||||||
|
this.animationEventEmitter.emit('point', {
|
||||||
|
x: boundingRect.left + coordinates.left - element.scrollLeft,
|
||||||
|
y: boundingRect.top + coordinates.top - element.scrollTop,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
renderSubmitButton () {
|
||||||
|
const style = {
|
||||||
|
backgroundColor: '#f7861c',
|
||||||
|
color: 'white',
|
||||||
|
marginTop: '20px',
|
||||||
|
height: '60px',
|
||||||
|
fontWeight: '400',
|
||||||
|
boxShadow: 'none',
|
||||||
|
borderRadius: '4px',
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
type="submit"
|
||||||
|
style={style}
|
||||||
|
disabled={!this.state.password}
|
||||||
|
fullWidth
|
||||||
|
variant="raised"
|
||||||
|
size="large"
|
||||||
|
onClick={event => this.handleSubmit(event)}
|
||||||
|
disableRipple
|
||||||
|
>
|
||||||
|
{ this.context.t('login') }
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { error } = this.state
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="unlock-page__container">
|
||||||
|
<div className="unlock-page">
|
||||||
|
<div className="unlock-page__mascot-container">
|
||||||
|
<Mascot
|
||||||
|
animationEventEmitter={this.animationEventEmitter}
|
||||||
|
width="120"
|
||||||
|
height="120"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<h1 className="unlock-page__title">
|
||||||
|
{ this.context.t('welcomeBack') }
|
||||||
|
</h1>
|
||||||
|
<div>{ this.context.t('unlockMessage') }</div>
|
||||||
|
<form
|
||||||
|
className="unlock-page__form"
|
||||||
|
onSubmit={event => this.handleSubmit(event)}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
id="password"
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
value={this.state.password}
|
||||||
|
onChange={event => this.handleInputChange(event)}
|
||||||
|
error={error}
|
||||||
|
autoFocus
|
||||||
|
autoComplete="current-password"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</form>
|
||||||
|
{ this.renderSubmitButton() }
|
||||||
|
<div className="unlock-page__links">
|
||||||
|
<div
|
||||||
|
className="unlock-page__link"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.markPasswordForgotten()
|
||||||
|
this.props.history.push(RESTORE_VAULT_ROUTE)
|
||||||
|
|
||||||
|
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
|
||||||
|
global.platform.openExtensionInBrowser()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ this.context.t('restoreFromSeed') }
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="unlock-page__link unlock-page__link--import"
|
||||||
|
onClick={() => {
|
||||||
|
this.props.markPasswordForgotten()
|
||||||
|
this.props.history.push(RESTORE_VAULT_ROUTE)
|
||||||
|
|
||||||
|
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
|
||||||
|
global.platform.openExtensionInBrowser()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{ this.context.t('importUsingSeed') }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlockPage.propTypes = {
|
||||||
|
forgotPassword: PropTypes.func,
|
||||||
|
tryUnlockMetamask: PropTypes.func,
|
||||||
|
markPasswordForgotten: PropTypes.func,
|
||||||
|
history: PropTypes.object,
|
||||||
|
isUnlocked: PropTypes.bool,
|
||||||
|
t: PropTypes.func,
|
||||||
|
useOldInterface: PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UnlockPage
|
31
ui/app/components/pages/unlock-page/unlock-page.container.js
Normal file
31
ui/app/components/pages/unlock-page/unlock-page.container.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { connect } from 'react-redux'
|
||||||
|
import { withRouter } from 'react-router-dom'
|
||||||
|
import { compose } from 'recompose'
|
||||||
|
|
||||||
|
const {
|
||||||
|
tryUnlockMetamask,
|
||||||
|
forgotPassword,
|
||||||
|
markPasswordForgotten,
|
||||||
|
} = require('../../../actions')
|
||||||
|
|
||||||
|
import UnlockPage from './unlock-page.component'
|
||||||
|
|
||||||
|
const mapStateToProps = state => {
|
||||||
|
const { metamask: { isUnlocked } } = state
|
||||||
|
return {
|
||||||
|
isUnlocked,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mapDispatchToProps = dispatch => {
|
||||||
|
return {
|
||||||
|
forgotPassword: () => dispatch(forgotPassword()),
|
||||||
|
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
|
||||||
|
markPasswordForgotten: () => dispatch(markPasswordForgotten()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default compose(
|
||||||
|
withRouter,
|
||||||
|
connect(mapStateToProps, mapDispatchToProps)
|
||||||
|
)(UnlockPage)
|
51
ui/app/components/pages/unlock-page/unlock-page.scss
Normal file
51
ui/app/components/pages/unlock-page/unlock-page.scss
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
.unlock-page {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-start;
|
||||||
|
align-items: center;
|
||||||
|
width: 357px;
|
||||||
|
padding: 30px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: $silver-chalice;
|
||||||
|
|
||||||
|
&__container {
|
||||||
|
background: $white;
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__mascot-container {
|
||||||
|
margin-top: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin-top: 5px;
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $tundora;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__form {
|
||||||
|
width: 100%;
|
||||||
|
margin: 56px 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__links {
|
||||||
|
margin-top: 25px;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&--import {
|
||||||
|
color: $ecstasy;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--use-classic {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,194 +0,0 @@
|
|||||||
const { Component } = require('react')
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const connect = require('../../metamask-connect')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const { withRouter } = require('react-router-dom')
|
|
||||||
const { compose } = require('recompose')
|
|
||||||
const {
|
|
||||||
tryUnlockMetamask,
|
|
||||||
forgotPassword,
|
|
||||||
markPasswordForgotten,
|
|
||||||
setNetworkEndpoints,
|
|
||||||
setFeatureFlag,
|
|
||||||
} = require('../../actions')
|
|
||||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../../../app/scripts/lib/enums')
|
|
||||||
const { getEnvironmentType } = require('../../../../app/scripts/lib/util')
|
|
||||||
const getCaretCoordinates = require('textarea-caret')
|
|
||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
const Mascot = require('../mascot')
|
|
||||||
const { OLD_UI_NETWORK_TYPE } = require('../../../../app/scripts/controllers/network/enums')
|
|
||||||
const { DEFAULT_ROUTE, RESTORE_VAULT_ROUTE } = require('../../routes')
|
|
||||||
|
|
||||||
class UnlockScreen extends Component {
|
|
||||||
constructor (props) {
|
|
||||||
super(props)
|
|
||||||
|
|
||||||
this.state = {
|
|
||||||
error: null,
|
|
||||||
}
|
|
||||||
|
|
||||||
this.animationEventEmitter = new EventEmitter()
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillMount () {
|
|
||||||
const { isUnlocked, history } = this.props
|
|
||||||
|
|
||||||
if (isUnlocked) {
|
|
||||||
history.push(DEFAULT_ROUTE)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount () {
|
|
||||||
const passwordBox = document.getElementById('password-box')
|
|
||||||
|
|
||||||
if (passwordBox) {
|
|
||||||
passwordBox.focus()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
tryUnlockMetamask (password) {
|
|
||||||
const { tryUnlockMetamask, history } = this.props
|
|
||||||
tryUnlockMetamask(password)
|
|
||||||
.then(() => history.push(DEFAULT_ROUTE))
|
|
||||||
.catch(({ message }) => this.setState({ error: message }))
|
|
||||||
}
|
|
||||||
|
|
||||||
onSubmit (event) {
|
|
||||||
const input = document.getElementById('password-box')
|
|
||||||
const password = input.value
|
|
||||||
this.tryUnlockMetamask(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
onKeyPress (event) {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
this.submitPassword(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
submitPassword (event) {
|
|
||||||
var element = event.target
|
|
||||||
var password = element.value
|
|
||||||
// reset input
|
|
||||||
element.value = ''
|
|
||||||
this.tryUnlockMetamask(password)
|
|
||||||
}
|
|
||||||
|
|
||||||
inputChanged (event) {
|
|
||||||
// tell mascot to look at page action
|
|
||||||
var element = event.target
|
|
||||||
var boundingRect = element.getBoundingClientRect()
|
|
||||||
var coordinates = getCaretCoordinates(element, element.selectionEnd)
|
|
||||||
this.animationEventEmitter.emit('point', {
|
|
||||||
x: boundingRect.left + coordinates.left - element.scrollLeft,
|
|
||||||
y: boundingRect.top + coordinates.top - element.scrollTop,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
render () {
|
|
||||||
const { error } = this.state
|
|
||||||
return (
|
|
||||||
h('.unlock-screen', [
|
|
||||||
|
|
||||||
h(Mascot, {
|
|
||||||
animationEventEmitter: this.animationEventEmitter,
|
|
||||||
}),
|
|
||||||
|
|
||||||
h('h1', {
|
|
||||||
style: {
|
|
||||||
fontSize: '1.4em',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
color: '#7F8082',
|
|
||||||
},
|
|
||||||
}, this.props.t('appName')),
|
|
||||||
|
|
||||||
h('input.large-input', {
|
|
||||||
type: 'password',
|
|
||||||
id: 'password-box',
|
|
||||||
placeholder: 'enter password',
|
|
||||||
style: {
|
|
||||||
background: 'white',
|
|
||||||
},
|
|
||||||
onKeyPress: this.onKeyPress.bind(this),
|
|
||||||
onInput: this.inputChanged.bind(this),
|
|
||||||
}),
|
|
||||||
|
|
||||||
h('.error', {
|
|
||||||
style: {
|
|
||||||
display: error ? 'block' : 'none',
|
|
||||||
padding: '0 20px',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
}, error),
|
|
||||||
|
|
||||||
h('button.primary.cursor-pointer', {
|
|
||||||
onClick: this.onSubmit.bind(this),
|
|
||||||
style: {
|
|
||||||
margin: 10,
|
|
||||||
},
|
|
||||||
}, this.props.t('login')),
|
|
||||||
|
|
||||||
h('p.pointer', {
|
|
||||||
onClick: () => {
|
|
||||||
this.props.markPasswordForgotten()
|
|
||||||
this.props.history.push(RESTORE_VAULT_ROUTE)
|
|
||||||
|
|
||||||
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
|
|
||||||
global.platform.openExtensionInBrowser()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fontSize: '0.8em',
|
|
||||||
color: 'rgb(247, 134, 28)',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
},
|
|
||||||
}, this.props.t('restoreFromSeed')),
|
|
||||||
|
|
||||||
h('p.pointer', {
|
|
||||||
onClick: () => {
|
|
||||||
this.props.useOldInterface()
|
|
||||||
.then(() => this.props.setNetworkEndpoints(OLD_UI_NETWORK_TYPE))
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fontSize: '0.8em',
|
|
||||||
color: '#aeaeae',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
marginTop: '32px',
|
|
||||||
},
|
|
||||||
}, this.props.t('classicInterface')),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.propTypes = {
|
|
||||||
forgotPassword: PropTypes.func,
|
|
||||||
tryUnlockMetamask: PropTypes.func,
|
|
||||||
markPasswordForgotten: PropTypes.func,
|
|
||||||
history: PropTypes.object,
|
|
||||||
isUnlocked: PropTypes.bool,
|
|
||||||
t: PropTypes.func,
|
|
||||||
useOldInterface: PropTypes.func,
|
|
||||||
setNetworkEndpoints: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapStateToProps = state => {
|
|
||||||
const { metamask: { isUnlocked } } = state
|
|
||||||
return {
|
|
||||||
isUnlocked,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = dispatch => {
|
|
||||||
return {
|
|
||||||
forgotPassword: () => dispatch(forgotPassword()),
|
|
||||||
tryUnlockMetamask: password => dispatch(tryUnlockMetamask(password)),
|
|
||||||
markPasswordForgotten: () => dispatch(markPasswordForgotten()),
|
|
||||||
useOldInterface: () => dispatch(setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL')),
|
|
||||||
setNetworkEndpoints: type => dispatch(setNetworkEndpoints(type)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = compose(
|
|
||||||
withRouter,
|
|
||||||
connect(mapStateToProps, mapDispatchToProps)
|
|
||||||
)(UnlockScreen)
|
|
@ -28,6 +28,10 @@ const currencies = require('currency-formatter/currencies')
|
|||||||
|
|
||||||
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
|
const { MIN_GAS_PRICE_HEX } = require('../send/send-constants')
|
||||||
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
|
const { SEND_ROUTE, DEFAULT_ROUTE } = require('../../routes')
|
||||||
|
const {
|
||||||
|
ENVIRONMENT_TYPE_POPUP,
|
||||||
|
ENVIRONMENT_TYPE_NOTIFICATION,
|
||||||
|
} = require('../../../../app/scripts/lib/enums')
|
||||||
|
|
||||||
ConfirmSendEther.contextTypes = {
|
ConfirmSendEther.contextTypes = {
|
||||||
t: PropTypes.func,
|
t: PropTypes.func,
|
||||||
@ -293,6 +297,14 @@ ConfirmSendEther.prototype.editTransaction = function (txMeta) {
|
|||||||
history.push(SEND_ROUTE)
|
history.push(SEND_ROUTE)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ConfirmSendEther.prototype.renderNetworkDisplay = function () {
|
||||||
|
const windowType = window.METAMASK_UI_TYPE
|
||||||
|
|
||||||
|
return (windowType === ENVIRONMENT_TYPE_NOTIFICATION || windowType === ENVIRONMENT_TYPE_POPUP)
|
||||||
|
? h(NetworkDisplay)
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
|
||||||
ConfirmSendEther.prototype.render = function () {
|
ConfirmSendEther.prototype.render = function () {
|
||||||
const {
|
const {
|
||||||
currentCurrency,
|
currentCurrency,
|
||||||
@ -358,7 +370,7 @@ ConfirmSendEther.prototype.render = function () {
|
|||||||
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
|
visibility: !txMeta.lastGasPrice ? 'initial' : 'hidden',
|
||||||
},
|
},
|
||||||
}, 'Edit'),
|
}, 'Edit'),
|
||||||
window.METAMASK_UI_TYPE === 'notification' && h(NetworkDisplay),
|
this.renderNetworkDisplay(),
|
||||||
]),
|
]),
|
||||||
h('.page-container__title', title),
|
h('.page-container__title', title),
|
||||||
h('.page-container__subtitle', subtitle),
|
h('.page-container__subtitle', subtitle),
|
||||||
|
2
ui/app/components/text-field/index.js
Normal file
2
ui/app/components/text-field/index.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
import TextField from './text-field.component'
|
||||||
|
module.exports = TextField
|
59
ui/app/components/text-field/text-field.component.js
Normal file
59
ui/app/components/text-field/text-field.component.js
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import PropTypes from 'prop-types'
|
||||||
|
import { withStyles } from 'material-ui/styles'
|
||||||
|
import { default as MaterialTextField } from 'material-ui/TextField'
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
cssLabel: {
|
||||||
|
'&$cssFocused': {
|
||||||
|
color: '#aeaeae',
|
||||||
|
},
|
||||||
|
'&$cssError': {
|
||||||
|
color: '#aeaeae',
|
||||||
|
},
|
||||||
|
fontWeight: '400',
|
||||||
|
color: '#aeaeae',
|
||||||
|
},
|
||||||
|
cssFocused: {},
|
||||||
|
cssUnderline: {
|
||||||
|
'&:after': {
|
||||||
|
backgroundColor: '#f7861c',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
cssError: {},
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextField = props => {
|
||||||
|
const { error, classes, ...textFieldProps } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MaterialTextField
|
||||||
|
error={Boolean(error)}
|
||||||
|
helperText={error}
|
||||||
|
InputLabelProps={{
|
||||||
|
FormLabelClasses: {
|
||||||
|
root: classes.cssLabel,
|
||||||
|
focused: classes.cssFocused,
|
||||||
|
error: classes.cssError,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
InputProps={{
|
||||||
|
classes: {
|
||||||
|
underline: classes.cssUnderline,
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
{...textFieldProps}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField.defaultProps = {
|
||||||
|
error: null,
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField.propTypes = {
|
||||||
|
error: PropTypes.string,
|
||||||
|
classes: PropTypes.object,
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withStyles(styles)(TextField)
|
24
ui/app/components/text-field/text-field.stories.js
Normal file
24
ui/app/components/text-field/text-field.stories.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { storiesOf } from '@storybook/react'
|
||||||
|
import TextField from './'
|
||||||
|
|
||||||
|
storiesOf('TextField', module)
|
||||||
|
.add('text', () =>
|
||||||
|
<TextField
|
||||||
|
label="Text"
|
||||||
|
type="text"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
.add('password', () =>
|
||||||
|
<TextField
|
||||||
|
label="Password"
|
||||||
|
type="password"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
.add('error', () =>
|
||||||
|
<TextField
|
||||||
|
type="text"
|
||||||
|
label="Name"
|
||||||
|
error="Invalid value"
|
||||||
|
/>
|
||||||
|
)
|
@ -102,6 +102,7 @@ WalletView.prototype.render = function () {
|
|||||||
selectedIdentity,
|
selectedIdentity,
|
||||||
keyrings,
|
keyrings,
|
||||||
showAccountDetailModal,
|
showAccountDetailModal,
|
||||||
|
sidebarOpen,
|
||||||
hideSidebar,
|
hideSidebar,
|
||||||
history,
|
history,
|
||||||
} = this.props
|
} = this.props
|
||||||
@ -182,7 +183,10 @@ WalletView.prototype.render = function () {
|
|||||||
h(TokenList),
|
h(TokenList),
|
||||||
|
|
||||||
h('button.btn-primary.wallet-view__add-token-button', {
|
h('button.btn-primary.wallet-view__add-token-button', {
|
||||||
onClick: () => history.push(ADD_TOKEN_ROUTE),
|
onClick: () => {
|
||||||
|
history.push(ADD_TOKEN_ROUTE)
|
||||||
|
sidebarOpen && hideSidebar()
|
||||||
|
},
|
||||||
}, this.context.t('addToken')),
|
}, this.context.t('addToken')),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@
|
|||||||
&__icon {
|
&__icon {
|
||||||
margin-left: 20px;
|
margin-left: 20px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
|
&--disabled {
|
||||||
|
cursor: initial;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
.app-header {
|
.app-header {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
visibility: visible;
|
|
||||||
background: $gallery;
|
background: $gallery;
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: $header-z-index;
|
z-index: $header-z-index;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
|
width: 100%;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
@media screen and (max-width: 575px) {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
width: 100%;
|
|
||||||
box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);
|
box-shadow: 0 0 0 1px rgba(0, 0, 0, .08);
|
||||||
z-index: $mobile-header-z-index;
|
z-index: $mobile-header-z-index;
|
||||||
}
|
}
|
||||||
@ -17,48 +17,71 @@
|
|||||||
@media screen and (min-width: 576px) {
|
@media screen and (min-width: 576px) {
|
||||||
height: 75px;
|
height: 75px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
|
||||||
.metafox-icon {
|
&--back-drop {
|
||||||
cursor: pointer;
|
&::after {
|
||||||
}
|
content: '';
|
||||||
}
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
.app-header--initialized {
|
height: 32px;
|
||||||
|
background: $gallery;
|
||||||
@media screen and (min-width: 576px) {
|
bottom: -32px;
|
||||||
&::after {
|
}
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
width: 100%;
|
|
||||||
height: 32px;
|
|
||||||
background: $gallery;
|
|
||||||
bottom: -32px;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
.app-header-contents {
|
&__metafox {
|
||||||
display: flex;
|
cursor: pointer;
|
||||||
justify-content: space-between;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
width: 100%;
|
|
||||||
height: 6.9vh;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
height: 100%;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 576px) {
|
&__beta-label {
|
||||||
width: 85vw;
|
font-family: Roboto;
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: .8rem;
|
||||||
|
color: $buttercup;
|
||||||
|
margin-left: 5px;
|
||||||
|
line-height: initial;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 769px) {
|
&__contents {
|
||||||
width: 80vw;
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 576px) {
|
||||||
|
width: 85vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 769px) {
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1281px) {
|
||||||
|
width: 62vw;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media screen and (min-width: 1281px) {
|
&__logo-container {
|
||||||
width: 62vw;
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__account-menu-container {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -76,20 +99,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.beta-label {
|
|
||||||
font-family: Roboto;
|
|
||||||
text-transform: uppercase;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: .8rem;
|
|
||||||
color: $buttercup;
|
|
||||||
margin-left: 5px;
|
|
||||||
line-height: initial;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h2.page-subtitle {
|
h2.page-subtitle {
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
color: #aeaeae;
|
color: #aeaeae;
|
||||||
@ -102,20 +111,3 @@ h2.page-subtitle {
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.left-menu-wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header__right-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.identicon {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
@import './unlock.scss';
|
|
||||||
|
|
||||||
@import './reveal-seed.scss';
|
@import './reveal-seed.scss';
|
||||||
|
|
||||||
|
@import '../../../../components/pages/unlock-page/unlock-page.scss';
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
.unlock-page {
|
|
||||||
box-shadow: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: rgb(247, 247, 247);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
@ -95,19 +95,6 @@ textarea.twelve-word-phrase {
|
|||||||
margin: -2px 8px 0px -8px;
|
margin: -2px 8px 0px -8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unlock-screen #metamask-mascot-container {
|
|
||||||
margin-top: 24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlock-screen h1 {
|
|
||||||
margin-top: -28px;
|
|
||||||
margin-bottom: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.unlock-screen input[type=password] {
|
|
||||||
width: 260px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sizing-input {
|
.sizing-input {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
height: 30px;
|
height: 30px;
|
||||||
@ -118,34 +105,6 @@ textarea.twelve-word-phrase {
|
|||||||
display: flex;
|
display: flex;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Webkit */
|
|
||||||
|
|
||||||
.unlock-screen input::-webkit-input-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Firefox 18- */
|
|
||||||
|
|
||||||
.unlock-screen input:-moz-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Firefox 19+ */
|
|
||||||
|
|
||||||
.unlock-screen input::-moz-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* IE */
|
|
||||||
|
|
||||||
.unlock-screen input:-ms-input-placeholder {
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* accounts */
|
/* accounts */
|
||||||
|
|
||||||
.accounts-section {
|
.accounts-section {
|
||||||
|
@ -3,8 +3,6 @@
|
|||||||
background: $white;
|
background: $white;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column nowrap;
|
flex-flow: column nowrap;
|
||||||
height: auto;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings__header {
|
.settings__header {
|
||||||
@ -29,6 +27,8 @@
|
|||||||
|
|
||||||
.settings__content {
|
.settings__content {
|
||||||
padding: 0 25px;
|
padding: 0 25px;
|
||||||
|
height: auto;
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings__content-row {
|
.settings__content-row {
|
||||||
|
@ -1,59 +1,60 @@
|
|||||||
.welcome-screen {
|
.welcome-screen {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
font-family: Roboto;
|
||||||
|
font-weight: 400;
|
||||||
|
width: 100%;
|
||||||
|
flex: 1 0 auto;
|
||||||
|
padding: 70px 0;
|
||||||
|
background: $white;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__info {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
font-family: Roboto;
|
|
||||||
font-weight: 400;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
flex: 1 0 auto;
|
height: 100%;
|
||||||
padding: 70px 0;
|
align-items: center;
|
||||||
background: $white;
|
justify-content: center;
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
&__header {
|
||||||
padding: 0;
|
font-size: 1.65em;
|
||||||
|
margin-bottom: 14px;
|
||||||
|
|
||||||
|
@media screen and (max-width: 575px) {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__info {
|
&__copy {
|
||||||
display: flex;
|
font-size: 1em;
|
||||||
flex-flow: column;
|
width: 400px;
|
||||||
width: 100%;
|
max-width: 90vw;
|
||||||
height: 100%;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
&__header {
|
|
||||||
font-size: 1.65em;
|
|
||||||
margin-bottom: 14px;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
font-size: 1.5em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__copy {
|
|
||||||
font-size: 1em;
|
|
||||||
width: 400px;
|
|
||||||
max-width: 90vw;
|
|
||||||
text-align: center;
|
|
||||||
|
|
||||||
@media screen and (max-width: 575px) {
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&__button {
|
|
||||||
height: 54px;
|
|
||||||
width: 198px;
|
|
||||||
box-shadow: 0 2px 4px 0 rgba(0,0,0,0.14);
|
|
||||||
color: #FFFFFF;
|
|
||||||
font-size: 20px;
|
|
||||||
font-weight: 500;
|
|
||||||
line-height: 26px;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
text-transform: uppercase;
|
|
||||||
margin: 35px 0 14px;
|
@media screen and (max-width: 575px) {
|
||||||
transition: 200ms ease-in-out;
|
font-size: .9em;
|
||||||
background-color: rgba(247, 134, 28, 0.9);
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
height: 54px;
|
||||||
|
width: 198px;
|
||||||
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .14);
|
||||||
|
color: #fff;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 26px;
|
||||||
|
text-align: center;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin: 35px 0 14px;
|
||||||
|
transition: 200ms ease-in-out;
|
||||||
|
background-color: rgba(247, 134, 28, .9);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -205,10 +205,8 @@ input.large-input {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
height: 100%;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
min-height: 250px;
|
flex: 1;
|
||||||
max-height: 400px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__warning-container {
|
&__warning-container {
|
||||||
@ -256,6 +254,7 @@ input.large-input {
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
background-color: $white;
|
background-color: $white;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ const getCaretCoordinates = require('textarea-caret')
|
|||||||
const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes')
|
const { RESTORE_VAULT_ROUTE, DEFAULT_ROUTE } = require('../routes')
|
||||||
const { getEnvironmentType } = require('../../../app/scripts/lib/util')
|
const { getEnvironmentType } = require('../../../app/scripts/lib/util')
|
||||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums')
|
const { ENVIRONMENT_TYPE_POPUP } = require('../../../app/scripts/lib/enums')
|
||||||
const { OLD_UI_NETWORK_TYPE } = require('../../../app/scripts/controllers/network/enums')
|
|
||||||
|
|
||||||
class InitializeMenuScreen extends Component {
|
class InitializeMenuScreen extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
@ -190,7 +189,6 @@ class InitializeMenuScreen extends Component {
|
|||||||
|
|
||||||
showOldUI () {
|
showOldUI () {
|
||||||
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
|
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ const h = require('react-hyperscript')
|
|||||||
const inherits = require('util').inherits
|
const inherits = require('util').inherits
|
||||||
const AccountAndTransactionDetails = require('./account-and-transaction-details')
|
const AccountAndTransactionDetails = require('./account-and-transaction-details')
|
||||||
const Settings = require('./components/pages/settings')
|
const Settings = require('./components/pages/settings')
|
||||||
const UnlockScreen = require('./components/pages/unlock')
|
const UnlockScreen = require('./components/pages/unlock-page')
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
module.exports = MainContainer
|
module.exports = MainContainer
|
||||||
|
@ -6,8 +6,7 @@ const { HashRouter } = require('react-router-dom')
|
|||||||
const App = require('./app')
|
const App = require('./app')
|
||||||
const OldApp = require('../../old-ui/app/app')
|
const OldApp = require('../../old-ui/app/app')
|
||||||
const { autoAddToBetaUI } = require('./selectors')
|
const { autoAddToBetaUI } = require('./selectors')
|
||||||
const { setFeatureFlag, setNetworkEndpoints } = require('./actions')
|
const { setFeatureFlag } = require('./actions')
|
||||||
const { BETA_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
|
|
||||||
const I18nProvider = require('./i18n-provider')
|
const I18nProvider = require('./i18n-provider')
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
function mapStateToProps (state) {
|
||||||
@ -24,11 +23,9 @@ function mapDispatchToProps (dispatch) {
|
|||||||
return {
|
return {
|
||||||
setFeatureFlagWithModal: () => {
|
setFeatureFlagWithModal: () => {
|
||||||
return dispatch(setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
return dispatch(setFeatureFlag('betaUI', true, 'BETA_UI_NOTIFICATION_MODAL'))
|
||||||
.then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
setFeatureFlagWithoutModal: () => {
|
setFeatureFlagWithoutModal: () => {
|
||||||
return dispatch(setFeatureFlag('betaUI', true))
|
return dispatch(setFeatureFlag('betaUI', true))
|
||||||
.then(() => dispatch(setNetworkEndpoints(BETA_UI_NETWORK_TYPE)))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
141
ui/app/unlock.js
141
ui/app/unlock.js
@ -1,141 +0,0 @@
|
|||||||
const inherits = require('util').inherits
|
|
||||||
const Component = require('react').Component
|
|
||||||
const PropTypes = require('prop-types')
|
|
||||||
const h = require('react-hyperscript')
|
|
||||||
const connect = require('react-redux').connect
|
|
||||||
const actions = require('./actions')
|
|
||||||
const getCaretCoordinates = require('textarea-caret')
|
|
||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
const { OLD_UI_NETWORK_TYPE } = require('../../app/scripts/controllers/network/enums')
|
|
||||||
const { getEnvironmentType } = require('../../app/scripts/lib/util')
|
|
||||||
const { ENVIRONMENT_TYPE_POPUP } = require('../../app/scripts/lib/enums')
|
|
||||||
|
|
||||||
const Mascot = require('./components/mascot')
|
|
||||||
|
|
||||||
UnlockScreen.contextTypes = {
|
|
||||||
t: PropTypes.func,
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = connect(mapStateToProps)(UnlockScreen)
|
|
||||||
|
|
||||||
|
|
||||||
inherits(UnlockScreen, Component)
|
|
||||||
function UnlockScreen () {
|
|
||||||
Component.call(this)
|
|
||||||
this.animationEventEmitter = new EventEmitter()
|
|
||||||
}
|
|
||||||
|
|
||||||
function mapStateToProps (state) {
|
|
||||||
return {
|
|
||||||
warning: state.appState.warning,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.prototype.render = function () {
|
|
||||||
const state = this.props
|
|
||||||
const warning = state.warning
|
|
||||||
return (
|
|
||||||
h('.unlock-screen', [
|
|
||||||
|
|
||||||
h(Mascot, {
|
|
||||||
animationEventEmitter: this.animationEventEmitter,
|
|
||||||
}),
|
|
||||||
|
|
||||||
h('h1', {
|
|
||||||
style: {
|
|
||||||
fontSize: '1.4em',
|
|
||||||
textTransform: 'uppercase',
|
|
||||||
color: '#7F8082',
|
|
||||||
},
|
|
||||||
}, this.context.t('appName')),
|
|
||||||
|
|
||||||
h('input.large-input', {
|
|
||||||
type: 'password',
|
|
||||||
id: 'password-box',
|
|
||||||
placeholder: 'enter password',
|
|
||||||
style: {
|
|
||||||
background: 'white',
|
|
||||||
},
|
|
||||||
onKeyPress: this.onKeyPress.bind(this),
|
|
||||||
onInput: this.inputChanged.bind(this),
|
|
||||||
}),
|
|
||||||
|
|
||||||
h('.error', {
|
|
||||||
style: {
|
|
||||||
display: warning ? 'block' : 'none',
|
|
||||||
padding: '0 20px',
|
|
||||||
textAlign: 'center',
|
|
||||||
},
|
|
||||||
}, warning),
|
|
||||||
|
|
||||||
h('button.primary.cursor-pointer', {
|
|
||||||
onClick: this.onSubmit.bind(this),
|
|
||||||
style: {
|
|
||||||
margin: 10,
|
|
||||||
},
|
|
||||||
}, this.context.t('login')),
|
|
||||||
|
|
||||||
h('p.pointer', {
|
|
||||||
onClick: () => {
|
|
||||||
this.props.dispatch(actions.markPasswordForgotten())
|
|
||||||
if (getEnvironmentType(window.location.href) === ENVIRONMENT_TYPE_POPUP) {
|
|
||||||
global.platform.openExtensionInBrowser()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fontSize: '0.8em',
|
|
||||||
color: 'rgb(247, 134, 28)',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
},
|
|
||||||
}, this.context.t('restoreFromSeed')),
|
|
||||||
|
|
||||||
h('p.pointer', {
|
|
||||||
onClick: () => {
|
|
||||||
this.props.dispatch(actions.setFeatureFlag('betaUI', false, 'OLD_UI_NOTIFICATION_MODAL'))
|
|
||||||
.then(() => this.props.dispatch(actions.setNetworkEndpoints(OLD_UI_NETWORK_TYPE)))
|
|
||||||
},
|
|
||||||
style: {
|
|
||||||
fontSize: '0.8em',
|
|
||||||
color: '#aeaeae',
|
|
||||||
textDecoration: 'underline',
|
|
||||||
marginTop: '32px',
|
|
||||||
},
|
|
||||||
}, this.context.t('classicInterface')),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.prototype.componentDidMount = function () {
|
|
||||||
document.getElementById('password-box').focus()
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.prototype.onSubmit = function (event) {
|
|
||||||
const input = document.getElementById('password-box')
|
|
||||||
const password = input.value
|
|
||||||
this.props.dispatch(actions.tryUnlockMetamask(password))
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.prototype.onKeyPress = function (event) {
|
|
||||||
if (event.key === 'Enter') {
|
|
||||||
this.submitPassword(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.prototype.submitPassword = function (event) {
|
|
||||||
var element = event.target
|
|
||||||
var password = element.value
|
|
||||||
// reset input
|
|
||||||
element.value = ''
|
|
||||||
this.props.dispatch(actions.tryUnlockMetamask(password))
|
|
||||||
}
|
|
||||||
|
|
||||||
UnlockScreen.prototype.inputChanged = function (event) {
|
|
||||||
// tell mascot to look at page action
|
|
||||||
var element = event.target
|
|
||||||
var boundingRect = element.getBoundingClientRect()
|
|
||||||
var coordinates = getCaretCoordinates(element, element.selectionEnd)
|
|
||||||
this.animationEventEmitter.emit('point', {
|
|
||||||
x: boundingRect.left + coordinates.left - element.scrollLeft,
|
|
||||||
y: boundingRect.top + coordinates.top - element.scrollTop,
|
|
||||||
})
|
|
||||||
}
|
|
@ -5,11 +5,6 @@ const actions = require('./app/actions')
|
|||||||
const configureStore = require('./app/store')
|
const configureStore = require('./app/store')
|
||||||
const txHelper = require('./lib/tx-helper')
|
const txHelper = require('./lib/tx-helper')
|
||||||
const { fetchLocale } = require('./i18n-helper')
|
const { fetchLocale } = require('./i18n-helper')
|
||||||
const {
|
|
||||||
OLD_UI_NETWORK_TYPE,
|
|
||||||
BETA_UI_NETWORK_TYPE,
|
|
||||||
} = require('../app/scripts/controllers/network/enums')
|
|
||||||
|
|
||||||
const log = require('loglevel')
|
const log = require('loglevel')
|
||||||
|
|
||||||
module.exports = launchMetamaskUi
|
module.exports = launchMetamaskUi
|
||||||
@ -55,10 +50,6 @@ async function startApp (metamaskState, accountManager, opts) {
|
|||||||
networkVersion: opts.networkVersion,
|
networkVersion: opts.networkVersion,
|
||||||
})
|
})
|
||||||
|
|
||||||
const useBetaUi = metamaskState.featureFlags.betaUI
|
|
||||||
const networkEndpointType = useBetaUi ? BETA_UI_NETWORK_TYPE : OLD_UI_NETWORK_TYPE
|
|
||||||
store.dispatch(actions.setNetworkEndpoints(networkEndpointType))
|
|
||||||
|
|
||||||
// if unconfirmed txs, start on txConf page
|
// if unconfirmed txs, start on txConf page
|
||||||
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
const unapprovedTxsAll = txHelper(metamaskState.unapprovedTxs, metamaskState.unapprovedMsgs, metamaskState.unapprovedPersonalMsgs, metamaskState.unapprovedTypedMessages, metamaskState.network)
|
||||||
const numberOfUnapprivedTx = unapprovedTxsAll.length
|
const numberOfUnapprivedTx = unapprovedTxsAll.length
|
||||||
|
Loading…
Reference in New Issue
Block a user