Merge remote-tracking branch 'upstream/develop' into HEAD

This commit is contained in:
brunobar79 2018-07-03 14:21:17 -04:00
commit 595447ccac
349 changed files with 43743 additions and 35634 deletions

View File

@ -30,6 +30,15 @@ workflows:
- prep-deps-npm
- prep-deps-firefox
- prep-build
- test-e2e-beta-chrome:
requires:
- prep-deps-npm
- prep-build
- test-e2e-beta-firefox:
requires:
- prep-deps-npm
- prep-deps-firefox
- prep-build
- test-unit:
requires:
- prep-deps-npm
@ -57,6 +66,8 @@ workflows:
- test-unit
- test-e2e-chrome
- test-e2e-firefox
- test-e2e-beta-chrome
- test-e2e-beta-firefox
- test-integration-mascara-chrome
- test-integration-mascara-firefox
- test-integration-flat-chrome
@ -86,7 +97,7 @@ workflows:
jobs:
prep-deps-npm:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -105,14 +116,12 @@ jobs:
prep-deps-firefox:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- run:
name: Download Firefox
command: >
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2
&& tar xjf firefox-58.0.tar.bz2
command: ./.circleci/scripts/firefox-download.sh
- save_cache:
key: dependency-cache-firefox-{{ .Revision }}
paths:
@ -120,7 +129,7 @@ jobs:
prep-build:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -139,7 +148,7 @@ jobs:
prep-docs:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -154,7 +163,7 @@ jobs:
prep-scss:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -173,7 +182,7 @@ jobs:
test-lint:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -184,7 +193,7 @@ jobs:
test-deps:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -195,7 +204,7 @@ jobs:
test-e2e-chrome:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -203,28 +212,22 @@ jobs:
- restore_cache:
key: build-cache-{{ .Revision }}
- run:
name: Test
name: test:e2e:chrome
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
- image: circleci/node:8.11.3-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
command: ./.circleci/scripts/firefox-install.sh
- restore_cache:
key: dependency-cache-{{ .Revision }}
- restore_cache:
@ -236,9 +239,46 @@ jobs:
path: test-artifacts
destination: test-artifacts
test-e2e-beta-chrome:
docker:
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
key: dependency-cache-{{ .Revision }}
- restore_cache:
key: build-cache-{{ .Revision }}
- run:
name: test:e2e:chrome:beta
command: npm run test:e2e:chrome:beta
- store_artifacts:
path: test-artifacts
destination: test-artifacts
test-e2e-beta-firefox:
docker:
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
key: dependency-cache-firefox-{{ .Revision }}
- run:
name: Install firefox
command: ./.circleci/scripts/firefox-install.sh
- restore_cache:
key: dependency-cache-{{ .Revision }}
- restore_cache:
key: build-cache-{{ .Revision }}
- run:
name: test:e2e:firefox:beta
command: npm run test:e2e:firefox:beta
- store_artifacts:
path: test-artifacts
destination: test-artifacts
job-screens:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -255,7 +295,7 @@ jobs:
job-publish-prerelease:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -282,7 +322,7 @@ jobs:
job-publish-release:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -305,7 +345,7 @@ jobs:
test-unit:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -318,18 +358,14 @@ jobs:
environment:
browsers: '["Firefox"]'
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-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
command: ./.circleci/scripts/firefox-install.sh
- restore_cache:
key: dependency-cache-{{ .Revision }}
- run:
@ -346,7 +382,7 @@ jobs:
environment:
browsers: '["Chrome"]'
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -365,18 +401,14 @@ jobs:
environment:
browsers: '["Firefox"]'
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-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
command: ./.circleci/scripts/firefox-install.sh
- restore_cache:
key: dependency-cache-{{ .Revision }}
- run:
@ -393,7 +425,7 @@ jobs:
environment:
browsers: '["Chrome"]'
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- checkout
- restore_cache:
@ -410,9 +442,8 @@ jobs:
all-tests-pass:
docker:
- image: circleci/node:8-browsers
- image: circleci/node:8.11.3-browsers
steps:
- run:
name: All Tests Passed
command: echo 'weew - everything passed!'

View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
echo "Downloading firefox..."
wget https://ftp.mozilla.org/pub/firefox/releases/58.0/linux-x86_64/en-US/firefox-58.0.tar.bz2 \
&& tar xjf firefox-58.0.tar.bz2
echo "firefox download complete"

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash
echo "Installing firefox..."
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
echo "Firefox installed."

View File

@ -1,6 +1,20 @@
node_modules/**
dist/**
builds/**
docs/**
development/bundle.js
development/states.js
app/scripts/lib/extension-instance.js
app/scripts/chromereload.js
ui/lib/blockies.js
mascara/src/app/first-time/spinner.js
mascara/test/jquery-3.1.0.min.js
test/integration/bundle.js
test/integration/jquery-3.1.0.min.js
test/integration/helpers.js
test/integration/lib/first-time.js
ui/lib/blockies.js

View File

@ -37,7 +37,9 @@
"document": false,
"navigator": false,
"web3": true,
"window": false
"window": false,
"$": false,
"QUnit": false
},
"rules": {
@ -142,6 +144,7 @@
"operator-linebreak": [1, "after", { "overrides": { "?": "ignore", ":": "ignore" } }],
"padded-blocks": "off",
"quotes": [2, "single", {"avoidEscape": true, "allowTemplateLiterals": true}],
"react/no-deprecated": 0,
"semi": [2, "never"],
"semi-spacing": [2, { "before": false, "after": true }],
"space-before-blocks": [1, "always"],
@ -158,5 +161,6 @@
"yield-star-spacing": [2, "both"],
"yoda": [2, "never"],
"prefer-const": 1,
"mocha/no-exclusive-tests": "error"
}
}

2
.nvmrc
View File

@ -1 +1 @@
v6.3.1
v8.11.3

View File

@ -2,6 +2,42 @@
## Current Master
## 4.8.0 Thur Jun 14 2018
- [#4513](https://github.com/MetaMask/metamask-extension/pull/4513): Attempting to import an empty private key will now show a clear error.
- [#4570](https://github.com/MetaMask/metamask-extension/pull/4570): Fix bug where metamask data would stop being written to disk after prolonged use.
- [#4523](https://github.com/MetaMask/metamask-extension/pull/4523): Fix bug where account reset did not work with custom RPC providers.
- [#4524](https://github.com/MetaMask/metamask-extension/pull/4524): Fix for Brave i18n getAcceptLanguages.
- [#4557](https://github.com/MetaMask/metamask-extension/pull/4557): Fix bug where nonce mutex was never released.
- [#4566](https://github.com/MetaMask/metamask-extension/pull/4566): Add phishing notice.
- [#4591](https://github.com/MetaMask/metamask-extension/pull/4591): Allow Copying Token Addresses and link to Token on Etherscan.
## 4.7.4 Tue Jun 05 2018
- Add diagnostic reporting for users with multiple HD keyrings
- Throw explicit error when selected account is unset
## 4.7.3 Mon Jun 04 2018
- Hide token now uses new modal
- Indicate the current selected account on the popup account view
- Reduce height of notice container in onboarding
- Fixes issue where old nicknames were kept around causing errors
## 4.7.2 Sun Jun 03 2018
- Fix bug preventing users from logging in. Internally accounts and identities were out of sync.
- Fix support links to point to new support system (Zendesk)
- Fix bug in migration #26 ( moving account nicknames to preferences )
- Clears account nicknames on restore from seedPhrase
## 4.7.1 Fri Jun 01 2018
- Fix bug where errors were not returned to Dapps.
## 4.7.0 Wed May 30 2018
- Fix Brave support
- Adds error messages when passwords don't match in onboarding flow.
- Adds modal notification if a retry in the process of being confirmed is dropped.
- New unlock screen design.

View File

@ -18,3 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -27,7 +27,9 @@ If you're a web dapp developer, we've got two types of guides for you:
## Building locally
- Install [Node.js](https://nodejs.org/en/) version 6.3.1 or later.
- Install local dependencies with `npm install`.
- Install dependencies:
- For node versions up to and including 9, install local dependencies with `npm install`.
- For node versions 10 and later, install [Yarn](https://yarnpkg.com/lang/en/docs/install/) and use `yarn install`.
- Install gulp globally with `npm install -g gulp-cli`.
- Build the project to the `./dist/` folder with `gulp build`.
- Optionally, to rebuild on file changes, run `gulp dev`.

View File

@ -146,6 +146,9 @@
"copy": {
"message": "Copy"
},
"copyContractAddress": {
"message": "Copy Contract Address"
},
"copyToClipboard": {
"message": "Copy to clipboard"
},
@ -253,12 +256,18 @@
"editAccountName": {
"message": "Edit Account Name"
},
"editingTransaction": {
"message": "Make changes to your transaction"
},
"emailUs": {
"message": "Email us!"
},
"encryptNewDen": {
"message": "Encrypt your new DEN"
},
"ensNameNotFound": {
"message": "ENS name not found"
},
"enterPassword": {
"message": "Enter password"
},
@ -771,6 +780,10 @@
"onlySendToEtherAddress": {
"message": "Only send ETH to an Ethereum address."
},
"onlySendTokensToAccountAddress": {
"message": "Only send $1 to an Ethereum account address.",
"description": "displays token symbol"
},
"searchTokens": {
"message": "Search Tokens"
},
@ -948,6 +961,9 @@
"viewAccount": {
"message": "View Account"
},
"viewOnEtherscan": {
"message": "View on Etherscan"
},
"visitWebSite": {
"message": "Visit our web site"
},

View File

@ -62,6 +62,9 @@
"message": " $1以上 $2以下にして下さい。",
"description": "helper for inputting hex as decimal input"
},
"blockiesIdenticon": {
"message": "Blockies Identicon を使用"
},
"borrowDharma": {
"message": "Dharmaで借りる(ベータ版)"
},
@ -95,6 +98,9 @@
"confirmTransaction": {
"message": "トランザクションの確認"
},
"continue": {
"message": "続行"
},
"continueToCoinbase": {
"message": "Coinbaseを開く"
},
@ -359,6 +365,9 @@
"likeToAddTokens": {
"message": "トークンを追加しますか?"
},
"links": {
"message": "リンク"
},
"limit": {
"message": "リミット"
},
@ -371,12 +380,18 @@
"localhost": {
"message": "Localhost 8545"
},
"login": {
"message": "ログイン"
},
"logout": {
"message": "ログアウト"
},
"loose": {
"message": "外部秘密鍵"
},
"max": {
"message": "最大"
},
"mainnet": {
"message": "Ethereumメインネットワーク"
},
@ -417,7 +432,7 @@
"message": "新規コントラクト"
},
"newPassword": {
"message": "新規パスワード(最低文字)"
"message": "新規パスワード(最低8文字)"
},
"newRecipient": {
"message": "新規受取人"
@ -453,6 +468,9 @@
"message": "または",
"description": "choice between creating or importing a new account"
},
"password": {
"message": "パスワード"
},
"passwordMismatch": {
"message": "パスワードが一致しません。",
"description": "in password creation process, the two new password fields did not match"
@ -474,6 +492,9 @@
"popularTokens": {
"message": "人気のトークン"
},
"privacyMsg": {
"message": "プライバシーポリシー"
},
"privateKey": {
"message": "秘密鍵",
"description": "select this type of file to use to import an account"
@ -546,6 +567,12 @@
"message": "ファイルとして保存",
"description": "Account export process"
},
"search": {
"message": "検索"
},
"searchResults": {
"message": "検索結果"
},
"selectService": {
"message": "サービスを選択"
},
@ -575,7 +602,7 @@
},
"info": {
"message": "情報"
},
},
"shapeshiftBuy": {
"message": "Shapeshiftで交換"
},
@ -609,6 +636,9 @@
"takesTooLong": {
"message": "送信に時間がかかりますか?"
},
"terms": {
"message": "利用規約"
},
"testFaucet": {
"message": "Faucetをテスト"
},
@ -619,6 +649,9 @@
"message": "ShapeShiftで $1をETHにする",
"description": "system will fill in deposit type in start of message"
},
"token": {
"message": "トークン"
},
"tokenAddress": {
"message": "トークンアドレス"
},
@ -690,6 +723,12 @@
"warning": {
"message": "警告"
},
"welcomeBack": {
"message": "おかえりなさい!"
},
"welcomeBeta": {
"message": "MetaMask ベータ版へようこそ!"
},
"whatsThis": {
"message": "この機能について"
},

View File

@ -1,7 +1,7 @@
{
"name": "__MSG_appName__",
"short_name": "__MSG_appName__",
"version": "4.6.1",
"version": "4.8.0",
"manifest_version": 2,
"author": "https://metamask.io",
"description": "__MSG_appDescription__",
@ -71,6 +71,8 @@
"matches": [
"https://metamask.io/*"
],
"ids": ["*"]
"ids": [
"*"
]
}
}

View File

@ -16,7 +16,18 @@ const accountImporter = {
strategies: {
'Private Key': (privateKey) => {
const stripped = ethUtil.stripHexPrefix(privateKey)
if (!privateKey) {
throw new Error('Cannot import an empty key.')
}
const prefixed = ethUtil.addHexPrefix(privateKey)
const buffer = ethUtil.toBuffer(prefixed)
if (!ethUtil.isValidPrivate(buffer)) {
throw new Error('Cannot import invalid private key.')
}
const stripped = ethUtil.stripHexPrefix(prefixed)
return stripped
},
'JSON File': (input, password) => {

View File

@ -16,6 +16,7 @@ const ExtensionPlatform = require('./platforms/extension')
const Migrator = require('./lib/migrator/')
const migrations = require('./migrations/')
const PortStream = require('./lib/port-stream.js')
const createStreamSink = require('./lib/createStreamSink')
const NotificationManager = require('./lib/notification-manager.js')
const MetamaskController = require('./metamask-controller')
const firstTimeState = require('./first-time-state')
@ -279,7 +280,7 @@ function setupController (initState, initLangCode) {
asStream(controller.store),
debounce(1000),
storeTransform(versionifyData),
storeTransform(persistData),
createStreamSink(persistData),
(error) => {
log.error('MetaMask - Persistence pipeline failed', error)
}
@ -295,7 +296,7 @@ function setupController (initState, initLangCode) {
return versionedData
}
function persistData (state) {
async function persistData (state) {
if (!state) {
throw new Error('MetaMask - updated state is missing', state)
}
@ -303,12 +304,13 @@ function setupController (initState, initLangCode) {
throw new Error('MetaMask - updated state does not have data', state)
}
if (localStore.isSupported) {
localStore.set(state)
.catch((err) => {
try {
await localStore.set(state)
} catch (err) {
// log error so we dont break the pipeline
log.error('error setting state in local store:', err)
})
}
}
return state
}
//
@ -382,7 +384,7 @@ function setupController (initState, initLangCode) {
}
// communication with page or other extension
function connectExternal(remotePort) {
function connectExternal (remotePort) {
const originDomain = urlUtil.parse(remotePort.sender.url).hostname
const portStream = new PortStream(remotePort)
controller.setupUntrustedCommunication(portStream, originDomain)

View File

@ -115,8 +115,8 @@ function logStreamDisconnectWarning (remoteLabel, err) {
* @returns {boolean} {@code true} if Web3 should be injected
*/
function shouldInjectWeb3 () {
return doctypeCheck() && suffixCheck()
&& documentElementCheck() && !blacklistedDomainCheck()
return doctypeCheck() && suffixCheck() &&
documentElementCheck() && !blacklistedDomainCheck()
}
/**
@ -176,6 +176,7 @@ function blacklistedDomainCheck () {
'webbyawards.com',
'cdn.shopify.com/s/javascripts/tricorder/xtld-read-only-frame.html',
'adyen.com',
'gravityforms.com',
]
var currentUrl = window.location.href
var currentRegex

View File

@ -60,7 +60,7 @@ class BalanceController {
* Sets up listeners and subscriptions which should trigger an update of ethBalance. These updates include:
* - when a transaction changes state to 'submitted', 'confirmed' or 'failed'
* - when the current account changes (i.e. a new account is selected)
* - when there is a block update
* - when there is a block update
*
* @private
*
@ -100,7 +100,7 @@ class BalanceController {
/**
* Gets the pending transactions (i.e. those with a 'submitted' status). These are accessed from the
* TransactionController passed to this BalanceController during construction.
* TransactionController passed to this BalanceController during construction.
*
* @private
* @returns {Promise<array>} Promises an array of transaction objects.

View File

@ -87,7 +87,7 @@ class BlacklistController {
*
* @private
* @param {object} config A config object like that found at {@link https://github.com/MetaMask/eth-phishing-detect/blob/master/src/config.json}
*
*
*/
_setupPhishingDetector (config) {
this._phishingDetector = new PhishingDetector(config)

View File

@ -18,7 +18,7 @@ class ComputedbalancesController {
/**
* Creates a new controller instance
*
* @param {ComputedBalancesOptions} [opts] Controller configuration parameters
* @param {ComputedBalancesOptions} [opts] Controller configuration parameters
*/
constructor (opts = {}) {
const { accountTracker, txController, blockTracker } = opts

View File

@ -16,9 +16,9 @@ class CurrencyController {
* currentCurrency, conversionRate and conversionDate properties
* @property {string} currentCurrency A 2-4 character shorthand that describes a specific currency, currently
* selected by the user
* @property {number} conversionRate The conversion rate from ETH to the selected currency.
* @property {number} conversionRate The conversion rate from ETH to the selected currency.
* @property {string} conversionDate The date at which the conversion rate was set. Expressed in in milliseconds
* since midnight of January 1, 1970
* since midnight of January 1, 1970
* @property {number} conversionInterval The id of the interval created by the scheduleConversionInterval method.
* Used to clear an existing interval on subsequent calls of that method.
*
@ -59,7 +59,7 @@ class CurrencyController {
/**
* A getter for the conversionRate property
*
* @returns {string} The conversion rate from ETH to the selected currency.
* @returns {string} The conversion rate from ETH to the selected currency.
*
*/
getConversionRate () {
@ -80,7 +80,7 @@ class CurrencyController {
* A getter for the conversionDate property
*
* @returns {string} The date at which the conversion rate was set. Expressed in milliseconds since midnight of
* January 1, 1970
* January 1, 1970
*
*/
getConversionDate () {

View File

@ -89,14 +89,21 @@ module.exports = class NetworkController extends EventEmitter {
type: 'rpc',
rpcTarget,
}
this.providerStore.updateState(providerConfig)
this._switchNetwork(providerConfig)
this.providerConfig = providerConfig
}
async setProviderType (type) {
assert.notEqual(type, 'rpc', `NetworkController - cannot call "setProviderType" with type 'rpc'. use "setRpcTarget"`)
assert(INFURA_PROVIDER_TYPES.includes(type) || type === LOCALHOST, `NetworkController - Unknown rpc type "${type}"`)
const providerConfig = { type }
this.providerConfig = providerConfig
}
resetConnection () {
this.providerConfig = this.getProviderConfig()
}
set providerConfig (providerConfig) {
this.providerStore.updateState(providerConfig)
this._switchNetwork(providerConfig)
}
@ -125,7 +132,7 @@ module.exports = class NetworkController extends EventEmitter {
} else if (type === LOCALHOST) {
this._configureStandardProvider({ rpcUrl: LOCALHOST_RPC_URL })
// url-based rpc endpoints
} else if (type === 'rpc'){
} else if (type === 'rpc') {
this._configureStandardProvider({ rpcUrl: rpcTarget })
} else {
throw new Error(`NetworkController - _configureProvider - unknown type "${type}"`)

View File

@ -2,6 +2,7 @@ const ObservableStore = require('obs-store')
const normalizeAddress = require('eth-sig-util').normalize
const extend = require('xtend')
class PreferencesController {
/**
@ -28,7 +29,11 @@ class PreferencesController {
featureFlags: {},
currentLocale: opts.initLangCode,
identities: {},
lostIdentities: {},
}, opts.initState)
this.diagnostics = opts.diagnostics
this.store = new ObservableStore(initState)
}
// PUBLIC METHODS
@ -63,6 +68,13 @@ class PreferencesController {
this.store.updateState({ currentLocale: key })
}
/**
* Updates identities to only include specified addresses. Removes identities
* not included in addresses array
*
* @param {string[]} addresses An array of hex addresses
*
*/
setAddresses (addresses) {
const oldIdentities = this.store.getState().identities
const identities = addresses.reduce((ids, address, index) => {
@ -73,6 +85,68 @@ class PreferencesController {
this.store.updateState({ identities })
}
/**
* Adds addresses to the identities object without removing identities
*
* @param {string[]} addresses An array of hex addresses
*
*/
addAddresses (addresses) {
const identities = this.store.getState().identities
addresses.forEach((address) => {
// skip if already exists
if (identities[address]) return
// add missing identity
const identityCount = Object.keys(identities).length
identities[address] = { name: `Account ${identityCount + 1}`, address }
})
this.store.updateState({ identities })
}
/*
* Synchronizes identity entries with known accounts.
* Removes any unknown identities, and returns the resulting selected address.
*
* @param {Array<string>} addresses known to the vault.
* @returns {Promise<string>} selectedAddress the selected address.
*/
syncAddresses (addresses) {
const { identities, lostIdentities } = this.store.getState()
const newlyLost = {}
Object.keys(identities).forEach((identity) => {
if (!addresses.includes(identity)) {
newlyLost[identity] = identities[identity]
delete identities[identity]
}
})
// Identities are no longer present.
if (Object.keys(newlyLost).length > 0) {
// Notify our servers:
if (this.diagnostics) this.diagnostics.reportOrphans(newlyLost)
// store lost accounts
for (const key in newlyLost) {
lostIdentities[key] = newlyLost[key]
}
}
this.store.updateState({ identities, lostIdentities })
this.addAddresses(addresses)
// If the selected account is no longer valid,
// select an arbitrary other account:
let selected = this.getSelectedAddress()
if (!addresses.includes(selected)) {
selected = addresses[0]
this.setSelectedAddress(selected)
}
return selected
}
/**
* Setter for the `selectedAddress` property
*
@ -111,7 +185,7 @@ class PreferencesController {
/**
* Adds a new token to the token array, or updates the token if passed an address that already exists.
* Modifies the existing tokens array from the store. All objects in the tokens array array AddedToken objects.
* @see AddedToken {@link AddedToken}
* @see AddedToken {@link AddedToken}
*
* @param {string} rawAddress Hex address of the token contract. May or may not be a checksum address.
* @param {string} symbol The symbol of the token
@ -173,6 +247,7 @@ class PreferencesController {
* @return {Promise<string>}
*/
setAccountLabel (account, label) {
if (!account) throw new Error('setAccountLabel requires a valid address, got ' + String(account))
const address = normalizeAddress(account)
const {identities} = this.store.getState()
identities[address] = identities[address] || {}
@ -197,7 +272,7 @@ class PreferencesController {
}
/**
* Setter for the `currentAccountTab` property
* Setter for the `currentAccountTab` property
*
* @param {string} currentAccountTab Specifies the new tab to be marked as current
* @returns {Promise<void>} Promise resolves with undefined
@ -215,7 +290,7 @@ class PreferencesController {
* The returned list will have a max length of 2. If the _url currently exists it the list, it will be moved to the
* end of the list. The current list is modified and returned as a promise.
*
* @param {string} _url The rpc url to add to the frequentRpcList.
* @param {string} _url The rpc url to add to the frequentRpcList.
* @returns {Promise<array>} The updated frequentRpcList.
*
*/

View File

@ -117,7 +117,7 @@ class RecentBlocksController {
*
* @returns {Promise<void>} Promises undefined
*/
async backfill() {
async backfill () {
this.blockTracker.once('block', async (block) => {
const currentBlockNumber = Number.parseInt(block.number, 16)
const blocksToFetch = Math.min(currentBlockNumber, this.historyLength)

View File

@ -10,6 +10,7 @@ const NonceTracker = require('./nonce-tracker')
const txUtils = require('./lib/util')
const cleanErrorStack = require('../../lib/cleanErrorStack')
const log = require('loglevel')
const recipientBlacklistChecker = require('./lib/recipient-blacklist-checker')
/**
Transaction Controller is an aggregate of sub-controllers and trackers
@ -157,11 +158,14 @@ class TransactionController extends EventEmitter {
let txMeta = this.txStateManager.generateTxMeta({ txParams: normalizedTxParams })
this.addTx(txMeta)
this.emit('newUnapprovedTx', txMeta)
// add default tx params
try {
// check whether recipient account is blacklisted
recipientBlacklistChecker.checkAccount(txMeta.metamaskNetworkId, normalizedTxParams.to)
// add default tx params
txMeta = await this.addTxGasDefaults(txMeta)
} catch (error) {
console.log(error)
log.warn(error)
this.txStateManager.setTxStatusFailed(txMeta.id, error)
throw error
}
@ -260,7 +264,12 @@ class TransactionController extends EventEmitter {
// must set transaction to submitted/failed before releasing lock
nonceLock.releaseLock()
} catch (err) {
this.txStateManager.setTxStatusFailed(txId, err)
// this is try-catch wrapped so that we can guarantee that the nonceLock is released
try {
this.txStateManager.setTxStatusFailed(txId, err)
} catch (err) {
log.error(err)
}
// must set transaction to submitted/failed before releasing lock
if (nonceLock) nonceLock.releaseLock()
// continue with error chain

View File

@ -0,0 +1,24 @@
const Config = require('./recipient-blacklist.js')
/** @module*/
module.exports = {
checkAccount,
}
/**
* Checks if a specified account on a specified network is blacklisted.
@param networkId {number}
@param account {string}
*/
function checkAccount (networkId, account) {
const mainnetId = 1
if (networkId !== mainnetId) {
return
}
const accountToCheck = account.toLowerCase()
if (Config.blacklist.includes(accountToCheck)) {
throw new Error('Recipient is a public account')
}
}

View File

@ -0,0 +1,17 @@
module.exports = {
'blacklist': [
// IDEX phisher
'0x9bcb0A9d99d815Bb87ee3191b1399b1Bcc46dc77',
// Ganache default seed phrases
'0x627306090abab3a6e1400e9345bc60c78a8bef57',
'0xf17f52151ebef6c7334fad080c5704d77216b732',
'0xc5fdf4076b8f3a5357c5e395ab970b5b54098fef',
'0x821aea9a577a9b44299b9c15c88cf3087f3b5544',
'0x0d1d4e623d10f9fba5db95830f7d3839406c6af2',
'0x2932b7a2355d6fecc4b5c0b6bd44cc31df247a2e',
'0x2191ef87e392377ec08e7c08eb105ef5448eced5',
'0x0f4f2ac550a1b4e2280d04c21cea7ebd822934b5',
'0x6330a553fc93768f612722bb8c2ec78ac90b3bbc',
'0x5aeda56215b167893e80b4fe645ba6d5bab767de',
],
}

View File

@ -49,29 +49,35 @@ class NonceTracker {
await this._globalMutexFree()
// await lock free, then take lock
const releaseLock = await this._takeMutex(address)
// evaluate multiple nextNonce strategies
const nonceDetails = {}
const networkNonceResult = await this._getNetworkNextNonce(address)
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
const nextNetworkNonce = networkNonceResult.nonce
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
try {
// evaluate multiple nextNonce strategies
const nonceDetails = {}
const networkNonceResult = await this._getNetworkNextNonce(address)
const highestLocallyConfirmed = this._getHighestLocallyConfirmed(address)
const nextNetworkNonce = networkNonceResult.nonce
const highestSuggested = Math.max(nextNetworkNonce, highestLocallyConfirmed)
const pendingTxs = this.getPendingTransactions(address)
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
const pendingTxs = this.getPendingTransactions(address)
const localNonceResult = this._getHighestContinuousFrom(pendingTxs, highestSuggested) || 0
nonceDetails.params = {
highestLocallyConfirmed,
highestSuggested,
nextNetworkNonce,
nonceDetails.params = {
highestLocallyConfirmed,
highestSuggested,
nextNetworkNonce,
}
nonceDetails.local = localNonceResult
nonceDetails.network = networkNonceResult
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
// return nonce and release cb
return { nextNonce, nonceDetails, releaseLock }
} catch (err) {
// release lock if we encounter an error
releaseLock()
throw err
}
nonceDetails.local = localNonceResult
nonceDetails.network = networkNonceResult
const nextNonce = Math.max(networkNonceResult.nonce, localNonceResult.nonce)
assert(Number.isInteger(nextNonce), `nonce-tracker - nextNonce is not an integer - got: (${typeof nextNonce}) "${nextNonce}"`)
// return nonce and release cb
return { nextNonce, nonceDetails, releaseLock }
}
async _getCurrentBlock () {
@ -85,8 +91,8 @@ class NonceTracker {
async _globalMutexFree () {
const globalMutex = this._lookupMutex('global')
const release = await globalMutex.acquire()
release()
const releaseLock = await globalMutex.acquire()
releaseLock()
}
async _takeMutex (lockId) {

View File

@ -196,14 +196,14 @@ class PendingTransactionTracker extends EventEmitter {
async _checkPendingTxs () {
const signedTxList = this.getPendingTransactions()
// in order to keep the nonceTracker accurate we block it while updating pending transactions
const nonceGlobalLock = await this.nonceTracker.getGlobalLock()
const { releaseLock } = await this.nonceTracker.getGlobalLock()
try {
await Promise.all(signedTxList.map((txMeta) => this._checkPendingTx(txMeta)))
} catch (err) {
log.error('PendingTransactionWatcher - Error updating pending transactions')
log.error(err)
}
nonceGlobalLock.releaseLock()
releaseLock()
}
/**

View File

@ -126,4 +126,4 @@ class TxGasUtil {
}
}
module.exports = TxGasUtil
module.exports = TxGasUtil

View File

@ -0,0 +1,17 @@
const MessageManager = require('./lib/message-manager')
const PersonalMessageManager = require('./lib/personal-message-manager')
const TypedMessageManager = require('./lib/typed-message-manager')
class UserActionController {
constructor (opts = {}) {
this.messageManager = new MessageManager()
this.personalMessageManager = new PersonalMessageManager()
this.typedMessageManager = new TypedMessageManager()
}
}
module.exports = UserActionController

View File

@ -38,9 +38,30 @@ web3.setProvider = function () {
log.debug('MetaMask - overrode web3.setProvider')
}
log.debug('MetaMask - injected web3')
// export global web3, with usage-detection
setupDappAutoReload(web3, inpageProvider.publicConfigStore)
// export global web3, with usage-detection and deprecation warning
/* TODO: Uncomment this area once auto-reload.js has been deprecated:
let hasBeenWarned = false
global.web3 = new Proxy(web3, {
get: (_web3, key) => {
// show warning once on web3 access
if (!hasBeenWarned && key !== 'currentProvider') {
console.warn('MetaMask: web3 will be deprecated in the near future in favor of the ethereumProvider \nhttps://github.com/MetaMask/faq/blob/master/detecting_metamask.md#web3-deprecation')
hasBeenWarned = true
}
// return value normally
return _web3[key]
},
set: (_web3, key, value) => {
// set value normally
_web3[key] = value
},
})
*/
// set web3 defaultAccount
inpageProvider.publicConfigStore.subscribe(function (state) {
web3.eth.defaultAccount = state.selectedAddress

View File

@ -3,7 +3,7 @@
* @param {Error} err - error
* @returns {Error} Error with clean stack trace.
*/
function cleanErrorStack(err){
function cleanErrorStack (err) {
var name = err.name
name = (name === undefined) ? 'Error' : String(name)

View File

@ -59,8 +59,9 @@ function createErrorMiddleware ({ override = true } = {}) {
if (!error) { return done() }
sanitizeRPCError(error)
log.error(`MetaMask - RPC Error: ${error.message}`, error)
done()
})
}
}
module.exports = createErrorMiddleware
module.exports = createErrorMiddleware

View File

@ -0,0 +1,24 @@
const WritableStream = require('readable-stream').Writable
const promiseToCallback = require('promise-to-callback')
module.exports = createStreamSink
function createStreamSink (asyncWriteFn, _opts) {
return new AsyncWritableStream(asyncWriteFn, _opts)
}
class AsyncWritableStream extends WritableStream {
constructor (asyncWriteFn, _opts) {
const opts = Object.assign({ objectMode: true }, _opts)
super(opts)
this._asyncWriteFn = asyncWriteFn
}
// write from incomming stream to state
_write (chunk, encoding, callback) {
promiseToCallback(this._asyncWriteFn(chunk, encoding))(callback)
}
}

View File

@ -0,0 +1,71 @@
class DiagnosticsReporter {
constructor ({ firstTimeInfo, version }) {
this.firstTimeInfo = firstTimeInfo
this.version = version
}
async reportOrphans (orphans) {
try {
return await this.submit({
accounts: Object.keys(orphans),
metadata: {
type: 'orphans',
},
})
} catch (err) {
console.error('DiagnosticsReporter - "reportOrphans" encountered an error:')
console.error(err)
}
}
async reportMultipleKeyrings (rawKeyrings) {
try {
const keyrings = await Promise.all(rawKeyrings.map(async (keyring, index) => {
return {
index,
type: keyring.type,
accounts: await keyring.getAccounts(),
}
}))
return await this.submit({
accounts: [],
metadata: {
type: 'keyrings',
keyrings,
},
})
} catch (err) {
console.error('DiagnosticsReporter - "reportMultipleKeyrings" encountered an error:')
console.error(err)
}
}
async submit (message) {
try {
// add metadata
message.metadata.version = this.version
message.metadata.firstTimeInfo = this.firstTimeInfo
return await postData(message)
} catch (err) {
console.error('DiagnosticsReporter - "submit" encountered an error:')
throw err
}
}
}
function postData (data) {
const uri = 'https://diagnostics.metamask.io/v1/orphanedAccounts'
return fetch(uri, {
body: JSON.stringify(data), // must match 'Content-Type' header
credentials: 'same-origin', // include, same-origin, *omit
headers: {
'content-type': 'application/json',
},
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, cors, *same-origin
})
}
module.exports = DiagnosticsReporter

View File

@ -10,13 +10,13 @@ module.exports = extractEthjsErrorMessage
*
* @param {string} errorMessage The error message to parse
* @returns {string} Returns an error message, either the same as was passed, or the ending message portion of an isEthjsRpcError
*
*
* @example
* // returns 'Transaction Failed: replacement transaction underpriced'
* extractEthjsErrorMessage(`Error: [ethjs-rpc] rpc error with payload {"id":3947817945380,"jsonrpc":"2.0","params":["0xf8eb8208708477359400830398539406012c8cf97bead5deae237070f9587f8e7a266d80b8843d7d3f5a0000000000000000000000000000000000000000000000000000000000081d1a000000000000000000000000000000000000000000000000001ff973cafa800000000000000000000000000000000000000000000000000000038d7ea4c68000000000000000000000000000000000000000000000000000000000000003f48025a04c32a9b630e0d9e7ff361562d850c86b7a884908135956a7e4a336fa0300d19ca06830776423f25218e8d19b267161db526e66895567147015b1f3fc47aef9a3c7"],"method":"eth_sendRawTransaction"} Error: replacement transaction underpriced`)
*
*/
function extractEthjsErrorMessage(errorMessage) {
function extractEthjsErrorMessage (errorMessage) {
const isEthjsRpcError = errorMessage.includes(ethJsRpcSlug)
if (isEthjsRpcError) {
const payloadAndError = errorMessage.slice(ethJsRpcSlug.length)

View File

@ -2,8 +2,7 @@ const extension = require('extensionizer')
const promisify = require('pify')
const allLocales = require('../../_locales/index.json')
const isSupported = extension.i18n && extension.i18n.getAcceptLanguages
const getPreferredLocales = isSupported ? promisify(
const getPreferredLocales = extension.i18n ? promisify(
extension.i18n.getAcceptLanguages,
{ errorFirst: false }
) : async () => []
@ -18,7 +17,21 @@ const existingLocaleCodes = allLocales.map(locale => locale.code.toLowerCase().r
*
*/
async function getFirstPreferredLangCode () {
const userPreferredLocaleCodes = await getPreferredLocales()
let userPreferredLocaleCodes
try {
userPreferredLocaleCodes = await getPreferredLocales()
} catch (e) {
// Brave currently throws when calling getAcceptLanguages, so this handles that.
userPreferredLocaleCodes = []
}
// safeguard for Brave Browser until they implement chrome.i18n.getAcceptLanguages
// https://github.com/MetaMask/metamask-extension/issues/4270
if (!userPreferredLocaleCodes) {
userPreferredLocaleCodes = []
}
const firstPreferredLangCode = userPreferredLocaleCodes
.map(code => code.toLowerCase())
.find(code => existingLocaleCodes.includes(code))
@ -26,3 +39,4 @@ async function getFirstPreferredLangCode () {
}
module.exports = getFirstPreferredLangCode

View File

@ -18,12 +18,12 @@ module.exports = getObjStructure
* Creates an object that represents the structure of the given object. It replaces all values with the result of their
* type.
*
* @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class.
* @param {object} obj The object for which a 'structure' will be returned. Usually a plain object and not a class.
* @returns {object} The "mapped" version of a deep clone of the passed object, with each non-object property value
* replaced with the javascript type of that value.
*
*/
function getObjStructure(obj) {
function getObjStructure (obj) {
const structure = clone(obj)
return deepMap(structure, (value) => {
return value === null ? 'null' : typeof value
@ -38,7 +38,7 @@ function getObjStructure(obj) {
* @param {Function} visit The modifier to apply to each non-object property value
* @returns {object} The modified object
*/
function deepMap(target = {}, visit) {
function deepMap (target = {}, visit) {
Object.entries(target).forEach(([key, value]) => {
if (typeof value === 'object' && value !== null) {
target[key] = deepMap(value, visit)

View File

@ -8,7 +8,7 @@ module.exports = class ExtensionStore {
/**
* @constructor
*/
constructor() {
constructor () {
this.isSupported = !!(extension.storage.local)
if (!this.isSupported) {
log.error('Storage local API not available.')
@ -19,7 +19,7 @@ module.exports = class ExtensionStore {
* Returns all of the keys currently saved
* @return {Promise<*>}
*/
async get() {
async get () {
if (!this.isSupported) return undefined
const result = await this._get()
// extension.storage.local always returns an obj
@ -36,7 +36,7 @@ module.exports = class ExtensionStore {
* @param {object} state - The state to set
* @return {Promise<void>}
*/
async set(state) {
async set (state) {
return this._set(state)
}
@ -45,7 +45,7 @@ module.exports = class ExtensionStore {
* @private
* @return {object} the key-value map from local storage
*/
_get() {
_get () {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.get(null, (/** @type {any} */ result) => {
@ -65,7 +65,7 @@ module.exports = class ExtensionStore {
* @return {Promise<void>}
* @private
*/
_set(obj) {
_set (obj) {
const local = extension.storage.local
return new Promise((resolve, reject) => {
local.set(obj, () => {
@ -85,6 +85,6 @@ module.exports = class ExtensionStore {
* @param {object} obj - The object to check
* @returns {boolean}
*/
function isEmpty(obj) {
function isEmpty (obj) {
return Object.keys(obj).length === 0
}

View File

@ -26,13 +26,15 @@ class NotificationManager {
// bring focus to existing chrome popup
extension.windows.update(popup.id, { focused: true })
} else {
const cb = (currentPopup) => { this._popupId = currentPopup.id }
// create new notification popup
extension.windows.create({
const creation = extension.windows.create({
url: 'notification.html',
type: 'popup',
width,
height,
})
}, cb)
creation && creation.then && creation.then(cb)
}
})
}
@ -84,7 +86,7 @@ class NotificationManager {
}
/**
* Given an array of windows, returns the first that has a 'popup' type, or null if no such window exists.
* Given an array of windows, returns the 'popup' that has been opened by MetaMask, or null if no such window exists.
*
* @private
* @param {array} windows An array of objects containing data about the open MetaMask extension windows.
@ -93,7 +95,7 @@ class NotificationManager {
_getPopupIn (windows) {
return windows ? windows.find((win) => {
// Returns notification popup
return (win && win.type === 'popup')
return (win && win.type === 'popup' && win.id === this._popupId)
}) : null
}

View File

@ -58,7 +58,7 @@ PortDuplexStream.prototype._read = noop
/**
* Called internally when data should be written to
* this writable stream.
*
*
* @private
* @param {*} msg Arbitrary object to write
* @param {string} encoding Encoding to use when writing payload

View File

@ -7,7 +7,7 @@ module.exports = reportFailedTxToSentry
// for sending to sentry
//
function reportFailedTxToSentry({ raven, txMeta }) {
function reportFailedTxToSentry ({ raven, txMeta }) {
const errorMessage = 'Transaction Failed: ' + extractEthjsErrorMessage(txMeta.err.message)
raven.captureMessage(errorMessage, {
// "extra" key is required by Sentry

View File

@ -4,7 +4,7 @@ module.exports = setupMetamaskMeshMetrics
/**
* Injects an iframe into the current document for testing
*/
function setupMetamaskMeshMetrics() {
function setupMetamaskMeshMetrics () {
const testingContainer = document.createElement('iframe')
testingContainer.src = 'https://metamask.github.io/mesh-testing/'
console.log('Injecting MetaMask Mesh testing client')

View File

@ -7,7 +7,7 @@ const DEV = 'https://f59f3dd640d2429d9d0e2445a87ea8e1@sentry.io/273496'
module.exports = setupRaven
// Setup raven / sentry remote error reporting
function setupRaven(opts) {
function setupRaven (opts) {
const { release } = opts
let ravenTarget
@ -21,7 +21,7 @@ function setupRaven(opts) {
const client = Raven.config(ravenTarget, {
release,
transport: function(opts) {
transport: function (opts) {
const report = opts.data
try {
// handle error-like non-error exceptions
@ -42,7 +42,7 @@ function setupRaven(opts) {
return Raven
}
function rewriteErrorLikeExceptions(report) {
function rewriteErrorLikeExceptions (report) {
// handle errors that lost their error-ness in serialization (e.g. dnode)
rewriteErrorMessages(report, (errorMessage) => {
if (!errorMessage.includes('Non-Error exception captured with keys:')) return errorMessage
@ -51,7 +51,7 @@ function rewriteErrorLikeExceptions(report) {
})
}
function simplifyErrorMessages(report) {
function simplifyErrorMessages (report) {
rewriteErrorMessages(report, (errorMessage) => {
// simplify ethjs error messages
errorMessage = extractEthjsErrorMessage(errorMessage)
@ -64,9 +64,9 @@ function simplifyErrorMessages(report) {
})
}
function rewriteErrorMessages(report, rewriteFn) {
function rewriteErrorMessages (report, rewriteFn) {
// rewrite top level message
report.message = rewriteFn(report.message)
if (report.message) report.message = rewriteFn(report.message)
// rewrite each exception message
if (report.exception && report.exception.values) {
report.exception.values.forEach(item => {
@ -75,7 +75,7 @@ function rewriteErrorMessages(report, rewriteFn) {
}
}
function rewriteReportUrls(report) {
function rewriteReportUrls (report) {
// update request url
report.request.url = toMetamaskUrl(report.request.url)
// update exception stack trace
@ -88,7 +88,7 @@ function rewriteReportUrls(report) {
}
}
function toMetamaskUrl(origUrl) {
function toMetamaskUrl (origUrl) {
const filePath = origUrl.split(location.origin)[1]
if (!filePath) return origUrl
const metamaskUrl = `metamask${filePath}`

View File

@ -139,6 +139,8 @@ module.exports = class MetamaskController extends EventEmitter {
const address = addresses[0]
this.preferencesController.setSelectedAddress(address)
}
// ensure preferences + identities controller know about all addresses
this.preferencesController.addAddresses(addresses)
this.accountTracker.syncWithAddresses(addresses)
})
@ -179,9 +181,6 @@ module.exports = class MetamaskController extends EventEmitter {
version,
firstVersion: initState.firstTimeInfo.version,
})
this.noticeController.updateNoticesList()
// to be uncommented when retrieving notices from a remote server.
// this.noticeController.startPolling()
this.shapeshiftController = new ShapeShiftController({
initState: initState.ShapeShiftController,
@ -354,7 +353,7 @@ module.exports = class MetamaskController extends EventEmitter {
importAccountWithStrategy: nodeify(this.importAccountWithStrategy, this),
// vault management
submitPassword: nodeify(keyringController.submitPassword, keyringController),
submitPassword: nodeify(this.submitPassword, this),
// network management
setProviderType: nodeify(networkController.setProviderType, networkController),
@ -384,6 +383,8 @@ module.exports = class MetamaskController extends EventEmitter {
updateAndApproveTransaction: nodeify(txController.updateAndApproveTransaction, txController),
retryTransaction: nodeify(this.retryTransaction, this),
getFilteredTxList: nodeify(txController.getFilteredTxList, txController),
isNonceTaken: nodeify(txController.isNonceTaken, txController),
estimateGas: nodeify(this.estimateGas, this),
// messageManager
signMessage: nodeify(this.signMessage, this),
@ -404,7 +405,6 @@ module.exports = class MetamaskController extends EventEmitter {
}
//=============================================================================
// VAULT / KEYRING RELATED METHODS
//=============================================================================
@ -424,28 +424,24 @@ module.exports = class MetamaskController extends EventEmitter {
* @returns {Object} vault
*/
async createNewVaultAndKeychain (password) {
const release = await this.createVaultMutex.acquire()
let vault
const releaseLock = await this.createVaultMutex.acquire()
try {
let vault
const accounts = await this.keyringController.getAccounts()
if (accounts.length > 0) {
vault = await this.keyringController.fullUpdate()
} else {
vault = await this.keyringController.createNewVaultAndKeychain(password)
const accounts = await this.keyringController.getAccounts()
this.preferencesController.setAddresses(accounts)
this.selectFirstIdentity()
}
release()
releaseLock()
return vault
} catch (err) {
release()
releaseLock()
throw err
}
return vault
}
/**
@ -454,20 +450,46 @@ module.exports = class MetamaskController extends EventEmitter {
* @param {} seed
*/
async createNewVaultAndRestore (password, seed) {
const release = await this.createVaultMutex.acquire()
const releaseLock = await this.createVaultMutex.acquire()
try {
// clear known identities
this.preferencesController.setAddresses([])
// create new vault
const vault = await this.keyringController.createNewVaultAndRestore(password, seed)
// set new identities
const accounts = await this.keyringController.getAccounts()
this.preferencesController.setAddresses(accounts)
this.selectFirstIdentity()
release()
releaseLock()
return vault
} catch (err) {
release()
releaseLock()
throw err
}
}
/*
* Submits the user's password and attempts to unlock the vault.
* Also synchronizes the preferencesController, to ensure its schema
* is up to date with known accounts once the vault is decrypted.
*
* @param {string} password - The user's password
* @returns {Promise<object>} - The keyringController update.
*/
async submitPassword (password) {
await this.keyringController.submitPassword(password)
const accounts = await this.keyringController.getAccounts()
// verify keyrings
const nonSimpleKeyrings = this.keyringController.keyrings.filter(keyring => keyring.type !== 'Simple Key Pair')
if (nonSimpleKeyrings.length > 1 && this.diagnostics) {
await this.diagnostics.reportMultipleKeyrings(nonSimpleKeyrings)
}
await this.preferencesController.syncAddresses(accounts)
return this.keyringController.fullUpdate()
}
/**
* @type Identity
* @property {string} name - The account nickname.
@ -592,10 +614,7 @@ module.exports = class MetamaskController extends EventEmitter {
async resetAccount () {
const selectedAddress = this.preferencesController.getSelectedAddress()
this.txController.wipeTransactions(selectedAddress)
const networkController = this.networkController
const oldType = networkController.getProviderConfig().type
await networkController.setProviderType(oldType, true)
this.networkController.resetConnection()
return selectedAddress
}
@ -922,6 +941,18 @@ module.exports = class MetamaskController extends EventEmitter {
return state
}
estimateGas (estimateGasParams) {
return new Promise((resolve, reject) => {
return this.txController.txGasUtil.query.estimateGas(estimateGasParams, (err, res) => {
if (err) {
return reject(err)
}
return resolve(res)
})
})
}
//=============================================================================
// PASSWORD MANAGEMENT
//=============================================================================
@ -930,7 +961,7 @@ module.exports = class MetamaskController extends EventEmitter {
* Allows a user to begin the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
markPasswordForgotten(cb) {
markPasswordForgotten (cb) {
this.configManager.setPasswordForgotten(true)
this.sendUpdate()
cb()
@ -940,7 +971,7 @@ module.exports = class MetamaskController extends EventEmitter {
* Allows a user to end the seed phrase recovery process.
* @param {Function} cb - A callback function called when complete.
*/
unMarkPasswordForgotten(cb) {
unMarkPasswordForgotten (cb) {
this.configManager.setPasswordForgotten(false)
this.sendUpdate()
cb()

View File

@ -28,7 +28,7 @@ module.exports = {
function transformState (state) {
const newState = state
const { config } = newState
if ( config && config.provider ) {
if (config && config.provider) {
if (config.provider.type === 'testnet') {
newState.config.provider.type = 'ropsten'
}

View File

@ -35,10 +35,10 @@ function transformState (state) {
if (transactions.length <= 40) return newState
let reverseTxList = transactions.reverse()
const reverseTxList = transactions.reverse()
let stripping = true
while (reverseTxList.length > 40 && stripping) {
let txIndex = reverseTxList.findIndex((txMeta) => {
const txIndex = reverseTxList.findIndex((txMeta) => {
return (txMeta.status === 'failed' ||
txMeta.status === 'rejected' ||
txMeta.status === 'confirmed' ||

View File

@ -27,7 +27,7 @@ module.exports = {
function transformState (state) {
if (!state.KeyringController || !state.PreferencesController) {
return
return state
}
if (!state.KeyringController.walletNicknames) {

View File

@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter
const semver = require('semver')
const extend = require('xtend')
const ObservableStore = require('obs-store')
const hardCodedNotices = require('../../notices/notices.json')
const hardCodedNotices = require('../../notices/notices.js')
const uniqBy = require('lodash.uniqby')
module.exports = class NoticeController extends EventEmitter {
@ -16,8 +16,12 @@ module.exports = class NoticeController extends EventEmitter {
noticesList: [],
}, opts.initState)
this.store = new ObservableStore(initState)
// setup memStore
this.memStore = new ObservableStore({})
this.store.subscribe(() => this._updateMemstore())
this._updateMemstore()
// pull in latest notices
this.updateNoticesList()
}
getNoticesList () {
@ -29,9 +33,9 @@ module.exports = class NoticeController extends EventEmitter {
return notices.filter((notice) => notice.read === false)
}
getLatestUnreadNotice () {
getNextUnreadNotice () {
const unreadNotices = this.getUnreadNotices()
return unreadNotices[unreadNotices.length - 1]
return unreadNotices[0]
}
async setNoticesList (noticesList) {
@ -47,7 +51,7 @@ module.exports = class NoticeController extends EventEmitter {
notices[index].read = true
notices[index].body = ''
this.setNoticesList(notices)
const latestNotice = this.getLatestUnreadNotice()
const latestNotice = this.getNextUnreadNotice()
cb(null, latestNotice)
} catch (err) {
cb(err)
@ -64,15 +68,6 @@ module.exports = class NoticeController extends EventEmitter {
return result
}
startPolling () {
if (this.noticePoller) {
clearInterval(this.noticePoller)
}
this.noticePoller = setInterval(() => {
this.noticeController.updateNoticesList()
}, 300000)
}
_mergeNotices (oldNotices, newNotices) {
return uniqBy(oldNotices.concat(newNotices), 'id')
}
@ -91,19 +86,15 @@ module.exports = class NoticeController extends EventEmitter {
})
}
_mapNoticeIds (notices) {
return notices.map((notice) => notice.id)
}
async _retrieveNoticeData () {
// Placeholder for the API.
// Placeholder for remote notice API.
return hardCodedNotices
}
_updateMemstore () {
const lastUnreadNotice = this.getLatestUnreadNotice()
const noActiveNotices = !lastUnreadNotice
this.memStore.updateState({ lastUnreadNotice, noActiveNotices })
const nextUnreadNotice = this.getNextUnreadNotice()
const noActiveNotices = !nextUnreadNotice
this.memStore.updateState({ nextUnreadNotice, noActiveNotices })
}
}

View File

@ -12,7 +12,7 @@ module.exports = initializePopup
/**
* Asynchronously initializes the MetaMask popup UI
*
* @param {{ container: Element, connectionStream: * }} config Popup configuration object
* @param {{ container: Element, connectionStream: * }} config Popup configuration object
* @param {Function} cb Called when initialization is complete
*/
function initializePopup ({ container, connectionStream }, cb) {

View File

@ -14,7 +14,7 @@ const log = require('loglevel')
start().catch(log.error)
async function start() {
async function start () {
// create platform global
global.platform = new ExtensionPlatform()

View File

@ -7,6 +7,6 @@ var changelog = fs.readFileSync(path.join(__dirname, '..', 'CHANGELOG.md')).toSt
var log = changelog.split(version)[1].split('##')[0].trim()
let msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}`
const msg = `*MetaMask ${version}* now published! It should auto-update soon!\n${log}`
console.log(msg)

View File

@ -1,5 +1,5 @@
module.exports = {
"confirm sig requests": {
'confirm sig requests': {
signMessage: (msgData, cb) => {
const stateUpdate = {
unapprovedMsgs: {},

View File

@ -1,17 +1,14 @@
const beefy = require('beefy')
const http = require('http')
const fs = require('fs')
const path = require('path')
const port = 8124
const handler = beefy({
entries: {'mocker.js': 'bundle.js'}
, cwd: __dirname
, live: true
, open: true
, quiet: false
, bundlerFlags: ['-t', 'brfs']
entries: {'mocker.js': 'bundle.js'},
cwd: __dirname,
live: true,
open: true,
quiet: false,
bundlerFlags: ['-t', 'brfs'],
})

View File

@ -4,7 +4,7 @@ const VERSION = require('../dist/chrome/manifest.json').version
start().catch(console.error)
async function start() {
async function start () {
const GITHUB_COMMENT_TOKEN = process.env.GITHUB_COMMENT_TOKEN
const CIRCLE_PULL_REQUEST = process.env.CIRCLE_PULL_REQUEST
@ -20,7 +20,7 @@ async function start() {
}
const CIRCLE_PR_NUMBER = CIRCLE_PULL_REQUEST.split('/').pop()
const SHORT_SHA1 = CIRCLE_SHA1.slice(0,7)
const SHORT_SHA1 = CIRCLE_SHA1.slice(0, 7)
const BUILD_LINK_BASE = `https://${CIRCLE_BUILD_NUM}-42009758-gh.circle-artifacts.com/0`
const MASCARA = `${BUILD_LINK_BASE}/builds/mascara/home.html`

View File

@ -12,7 +12,6 @@
* To use, run `npm run mock`.
*/
const extend = require('xtend')
const render = require('react-dom').render
const h = require('react-hyperscript')
const Root = require('../ui/app/root')
@ -24,7 +23,6 @@ const Selector = require('./selector')
const MetamaskController = require('../app/scripts/metamask-controller')
const firstTimeState = require('../app/scripts/first-time-state')
const ExtensionPlatform = require('../app/scripts/platforms/extension')
const extension = require('./mockExtension')
const noop = function () {}
const log = require('loglevel')
@ -81,14 +79,14 @@ const controller = new MetamaskController({
initState: firstTimeState,
})
global.metamaskController = controller
global.platform = new ExtensionPlatform
global.platform = new ExtensionPlatform()
//
// User Interface
//
actions._setBackgroundConnection(controller.getApi())
actions.update = function(stateName) {
actions.update = function (stateName) {
selectedView = stateName
updateQueryParams(stateName)
const newState = states[selectedView]
@ -98,7 +96,7 @@ actions.update = function(stateName) {
}
}
function modifyBackgroundConnection(backgroundConnectionModifier) {
function modifyBackgroundConnection (backgroundConnectionModifier) {
const modifiedBackgroundConnection = Object.assign({}, controller.getApi(), backgroundConnectionModifier)
actions._setBackgroundConnection(modifiedBackgroundConnection)
}
@ -112,7 +110,7 @@ var store = configureStore(firstState)
// start app
startApp()
function startApp(){
function startApp () {
const body = document.body
const container = document.createElement('div')
container.id = 'test-container'

View File

@ -39,6 +39,6 @@ extension.runtime.reload = noop
extension.tabs.create = noop
extension.runtime.getManifest = function () {
return {
version: 'development'
version: 'development',
}
}
}

View File

@ -11,7 +11,7 @@ const bumpType = normalizeType(process.argv[2])
start().catch(console.error)
async function start() {
async function start () {
const changeBuffer = await readFile(changelogPath)
const changelog = changeBuffer.toString()

View File

@ -11,7 +11,7 @@ function NewComponent () {
NewComponent.prototype.render = function () {
const props = this.props
let {
const {
states,
selectedKey,
actions,
@ -28,7 +28,7 @@ NewComponent.prototype.render = function () {
margin: '20px 20px 0px',
},
value: selected,
onChange:(event) => {
onChange: (event) => {
const selectedKey = event.target.value
const backgroundConnectionModifier = backGroundConnectionModifiers[selectedKey]
modifyBackgroundConnection(backgroundConnectionModifier || {})

View File

@ -5,7 +5,7 @@ const VERSION = require('../dist/chrome/manifest.json').version
start().catch(console.error)
async function start(){
async function start () {
const authWorked = await checkIfAuthWorks()
if (!authWorked) {
console.log(`Sentry auth failed...`)
@ -31,21 +31,21 @@ async function start(){
console.log('all done!')
}
async function checkIfAuthWorks() {
async function checkIfAuthWorks () {
const itWorked = await doesNotFail(async () => {
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' list`)
})
return itWorked
}
async function checkIfVersionExists() {
async function checkIfVersionExists () {
const versionAlreadyExists = await doesNotFail(async () => {
await exec(`sentry-cli releases --org 'metamask' --project 'metamask' info ${VERSION}`)
})
return versionAlreadyExists
}
async function doesNotFail(asyncFn) {
async function doesNotFail (asyncFn) {
try {
await asyncFn()
return true

View File

@ -1,6 +1,6 @@
const fs = require('fs')
const { SourceMapConsumer } = require('source-map')
const path = require('path')
//
// Utility to help check if sourcemaps are working
//
@ -11,9 +11,11 @@ const { SourceMapConsumer } = require('source-map')
start()
async function start() {
const rawBuild = fs.readFileSync(__dirname + '/../dist/chrome/inpage.js', 'utf8')
const rawSourceMap = fs.readFileSync(__dirname + '/../dist/sourcemaps/inpage.js.map', 'utf8')
async function start () {
const rawBuild = fs.readFileSync(path.join(__dirname, '/../dist/chrome/', 'inpage.js')
, 'utf8')
const rawSourceMap = fs.readFileSync(path.join(__dirname, '/../dist/sourcemaps/', 'inpage.js.map'), 'utf8')
const consumer = await new SourceMapConsumer(rawSourceMap)
console.log('hasContentsOfAllSources:', consumer.hasContentsOfAllSources(), '\n')
@ -34,7 +36,7 @@ async function start() {
if (result.source === 'node_modules/web3/dist/web3.min.js') return // minified mess
const sourceContent = consumer.sourceContentFor(result.source)
const sourceLines = sourceContent.split('\n')
const line = sourceLines[result.line-1]
const line = sourceLines[result.line - 1]
console.log(`\n========================== ${result.source} ====================================\n`)
console.log(line)
console.log(`\n==============================================================================\n`)
@ -42,8 +44,9 @@ async function start() {
})
}
function indicesOf(substring, string) {
var a=[],i=-1;
while((i=string.indexOf(substring,i+1)) >= 0) a.push(i);
return a;
function indicesOf (substring, string) {
var a = []
var i = -1
while ((i = string.indexOf(substring, i + 1)) >= 0) a.push(i)
return a
}

View File

@ -75,9 +75,9 @@
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{

View File

@ -52,7 +52,7 @@
"conversionRate": 12.7200827,
"conversionDate": 1487363041,
"noActiveNotices": true,
"lastUnreadNotice": {
"nextUnreadNotice": {
"read": true,
"date": "Thu Feb 09 2017",
"title": "Terms of Use",

View File

@ -151,5 +151,10 @@
"scrollToBottom": false,
"forgottenPassword": null
},
"identities": {}
"identities": {},
"send": {
"fromDropdownOpen": false,
"toDropdownOpen": false,
"errors": {}
}
}

View File

@ -115,9 +115,9 @@
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{

View File

@ -76,9 +76,9 @@
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{

View File

@ -12,7 +12,7 @@
"conversionRate": 12.7527416,
"conversionDate": 1487624341,
"noActiveNotices": false,
"lastUnreadNotice": {
"nextUnreadNotice": {
"read": false,
"date": "Thu Feb 09 2017",
"title": "Terms of Use",

View File

@ -13,7 +13,7 @@
"conversionRate": 8.3533002,
"conversionDate": 1481671082,
"noActiveNotices": false,
"lastUnreadNotice": {
"nextUnreadNotice": {
"read": false,
"date": "Tue Dec 13 2016",
"title": "MultiVault Support",

View File

@ -94,9 +94,9 @@
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
@ -151,5 +151,10 @@
"scrollToBottom": false,
"forgottenPassword": null
},
"identities": {}
"identities": {},
"send": {
"fromDropdownOpen": false,
"toDropdownOpen": false,
"errors": {}
}
}

View File

@ -76,9 +76,9 @@
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
@ -130,5 +130,10 @@
"scrollToBottom": false,
"forgottenPassword": null
},
"identities": {}
"identities": {},
"send": {
"fromDropdownOpen": false,
"toDropdownOpen": false,
"errors": {}
}
}

View File

@ -83,9 +83,9 @@
{
"type": "HD Key Tree",
"accounts": [
"fdea65c8e26263f6d9a1b5de9555d2931a33b825",
"c5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"2f8d4a878cfa04a6e60d46362f5644deab66572d"
"0xfdea65c8e26263f6d9a1b5de9555d2931a33b825",
"0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb",
"0x2f8d4a878cfa04a6e60d46362f5644deab66572d"
]
},
{
@ -124,5 +124,10 @@
"scrollToBottom": false,
"forgottenPassword": null
},
"identities": {}
"identities": {},
"send": {
"fromDropdownOpen": false,
"toDropdownOpen": false,
"errors": {}
}
}

View File

@ -29,9 +29,8 @@ log.setDefaultLevel(1)
// Query String
const qs = require('qs')
let queryString = qs.parse(window.location.href.split('#')[1])
const queryString = qs.parse(window.location.href.split('#')[1])
let selectedView = queryString.view || 'first time'
const firstState = states[selectedView]
updateQueryParams(selectedView)
// CSS
@ -39,15 +38,15 @@ const MetaMaskUiCss = require('../ui/css')
const injectCss = require('inject-css')
function updateQueryParams(newView) {
function updateQueryParams (newView) {
queryString.view = newView
const params = qs.stringify(queryString)
window.location.href = window.location.href.split('#')[0] + `#${params}`
}
const actions = {
_setBackgroundConnection(){},
update: function(stateName) {
_setBackgroundConnection () {},
update: function (stateName) {
selectedView = stateName
updateQueryParams(stateName)
const newState = states[selectedView]
@ -67,7 +66,7 @@ var store = configureStore(states[selectedView])
// start app
startApp()
function startApp(){
function startApp () {
const body = document.body
const container = document.createElement('div')
container.id = 'test-container'

View File

@ -1,4 +1,4 @@
////////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
//
// Locale verification script
//
@ -8,7 +8,7 @@
//
// will check the given locale against the strings in english
//
////////////////////////////////////////////////////////////////////////////////
// //////////////////////////////////////////////////////////////////////////////
const fs = require('fs')
const path = require('path')
@ -20,7 +20,7 @@ const specifiedLocale = process.argv[2]
if (specifiedLocale) {
console.log(`Verifying selected locale "${specifiedLocale}":\n\n`)
const locale = localeIndex.find(localeMeta => localeMeta.code === specifiedLocale)
verifyLocale({ localeMeta })
verifyLocale({ locale })
} else {
console.log('Verifying all locales:\n\n')
localeIndex.forEach(localeMeta => {
@ -30,16 +30,16 @@ if (specifiedLocale) {
}
function verifyLocale({ localeMeta }) {
function verifyLocale ({ localeMeta }) {
const localeCode = localeMeta.code
const localeName = localeMeta.name
let targetLocale, englishLocale
try {
const localeFilePath = path.join(process.cwd(), 'app', '_locales', localeCode, 'messages.json')
targetLocale = JSON.parse(fs.readFileSync(localeFilePath, 'utf8'));
targetLocale = JSON.parse(fs.readFileSync(localeFilePath, 'utf8'))
} catch (e) {
if (e.code == 'ENOENT') {
if (e.code === 'ENOENT') {
console.log('Locale file not found')
} else {
console.log(`Error opening your locale ("${localeCode}") file: `, e)
@ -49,9 +49,9 @@ function verifyLocale({ localeMeta }) {
try {
const englishFilePath = path.join(process.cwd(), 'app', '_locales', 'en', 'messages.json')
englishLocale = JSON.parse(fs.readFileSync(englishFilePath, 'utf8'));
englishLocale = JSON.parse(fs.readFileSync(englishFilePath, 'utf8'))
} catch (e) {
if(e.code == 'ENOENT') {
if (e.code === 'ENOENT') {
console.log('English File not found')
} else {
console.log('Error opening english locale file: ', e)
@ -71,7 +71,7 @@ function verifyLocale({ localeMeta }) {
if (extraItems.length) {
console.log('\nMissing from english locale:')
extraItems.forEach(function(key) {
extraItems.forEach(function (key) {
console.log(` - [ ] ${key}`)
})
} else {
@ -80,7 +80,7 @@ function verifyLocale({ localeMeta }) {
if (missingItems.length) {
console.log(`\nMissing:`)
missingItems.forEach(function(key) {
missingItems.forEach(function (key) {
console.log(` - [ ] ${key}`)
})
} else {
@ -92,6 +92,6 @@ function verifyLocale({ localeMeta }) {
}
}
function compareLocalesForMissingItems({ base, subject }) {
function compareLocalesForMissingItems ({ base, subject }) {
return Object.keys(base).filter((key) => !subject[key])
}

View File

@ -1,6 +1,6 @@
const clone = require('clone')
async function versionBump(bumpType, changelog, oldManifest) {
async function versionBump (bumpType, changelog, oldManifest) {
const manifest = clone(oldManifest)
const newVersion = newVersionFrom(manifest, bumpType)
@ -19,13 +19,13 @@ async function versionBump(bumpType, changelog, oldManifest) {
return {
version: newVersion,
manifest: manifest,
changelog: logLines.join('\n')
changelog: logLines.join('\n'),
}
}
function newVersionFrom (manifest, bumpType) {
const string = manifest.version
let segments = string.split('.').map((str) => parseInt(str))
const segments = string.split('.').map((str) => parseInt(str))
switch (bumpType) {
case 'major':
@ -45,8 +45,4 @@ function newVersionFrom (manifest, bumpType) {
return segments.map(String).join('.')
}
function bumpManifest (manifest, bumpType) {
}
module.exports = versionBump

View File

@ -2,18 +2,32 @@
When publishing a new version of MetaMask, we follow this procedure:
## Preparation
We try to ensure certain criteria are met before deploying:
- Deploy early in the week, to give time for emergency responses to unforeseen bugs.
- Deploy early in the day, for the same reason.
- Make sure at least one member of the support team is "on duty" to watch for new user issues coming through the support system.
- Roll out incrementally when possible, to a small number of users first, and gradually to more users.
## Incrementing Version & Changelog
Version can be automatically incremented [using our bump script](./bumping-version.md).
npm run version:bump $BUMP_TYPE` where `$BUMP_TYPE` is one of `major`, `minor`, or `patch`.
## Building
While we develop on the main `develop` branch, our production version is maintained on the `master` branch.
With each pull request, the @MetaMaskBot will comment with a build of that new pull request, so after bumping the version on `develop`, open a pull request against `master`, and once the pull request is reviewed and merged, you can download those builds for publication.
## Publishing
1. `npm run dist` to generate the latest build.
2. Publish to chrome store.
- Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
3. Publish to firefox addon marketplace.
4. Post on Github releases page.
5. `npm run announce`, post that announcement in our public places.
1. Publish to chrome store.
2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2).
3. Publish to [firefox addon marketplace](http://addons.mozilla.org/en-us/firefox/addon/ether-metamask).
4. Publish to [Opera store](https://addons.opera.com/en/extensions/details/metamask/).
5. Post on [Github releases](https://github.com/MetaMask/metamask-extension/releases) page.
6. Run the `npm run announce` script, and post that announcement in our public places.

241
gentests.js Normal file
View File

@ -0,0 +1,241 @@
const fs = require('fs')
const path = require('path')
const async = require('async')
const promisify = require('pify')
// start(/\.selectors.js/, generateSelectorTest).catch(console.error)
// start(/\.utils.js/, generateUtilTest).catch(console.error)
startContainer(/\.container.js/, generateContainerTest).catch(console.error)
async function getAllFileNames (dirName) {
const allNames = (await promisify(fs.readdir)(dirName))
const fileNames = allNames.filter(name => name.match(/^.+\./))
const dirNames = allNames.filter(name => name.match(/^[^.]+$/))
const fullPathDirNames = dirNames.map(d => `${dirName}/${d}`)
const subNameArrays = await promisify(async.map)(fullPathDirNames, getAllFileNames)
let subNames = []
subNameArrays.forEach(subNameArray => { subNames = [...subNames, ...subNameArray] })
return [
...fileNames.map(name => dirName + '/' + name),
...subNames,
]
}
/*
async function start (fileRegEx, testGenerator) {
const fileNames = await getAllFileNames('./ui/app')
const sFiles = fileNames.filter(name => name.match(fileRegEx))
let sFileMethodNames
let testFilePath
async.each(sFiles, async (sFile, cb) => {
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
sFileMethodNames = Object.keys(require(__dirname + '/' + sFile))
testFilePath = sPath.replace('.', '-').replace('.', '.test.')
await promisify(fs.writeFile)(
`${__dirname}/${sRootPath}tests/${testFilePath}`,
testGenerator(sPath, sFileMethodNames),
'utf8'
)
}, (err) => {
console.log(err)
})
}
*/
async function startContainer (fileRegEx, testGenerator) {
const fileNames = await getAllFileNames('./ui/app')
const sFiles = fileNames.filter(name => name.match(fileRegEx))
async.each(sFiles, async (sFile, cb) => {
console.log(`sFile`, sFile)
const [, sRootPath, sPath] = sFile.match(/^(.+\/)([^/]+)$/)
const testFilePath = sPath.replace('.', '-').replace('.', '.test.')
await promisify(fs.readFile)(
path.join(__dirname, sFile),
'utf8',
async (err, result) => {
if (err) {
console.log('Error: ', err)
} else {
console.log(`result`, result.length)
const returnObjectStrings = result
.match(/return\s(\{[\s\S]+?})\n}/g)
.map(str => {
return str
.slice(0, str.length - 1)
.slice(7)
.replace(/\n/g, '')
.replace(/\s\s+/g, ' ')
})
const mapStateToPropsAssertionObject = returnObjectStrings[0]
.replace(/\w+:\s\w+\([\w,\s]+\),/g, str => {
const strKey = str.match(/^\w+/)[0]
return strKey + ': \'mock' + str.match(/^\w+/)[0].replace(/^./, c => c.toUpperCase()) + ':mockState\',\n'
})
.replace(/{\s\w.+/, firstLinePair => `{\n ${firstLinePair.slice(2)}`)
.replace(/\w+:.+,/g, s => ` ${s}`)
.replace(/}/g, s => ` ${s}`)
let mapDispatchToPropsMethodNames
if (returnObjectStrings[1]) {
mapDispatchToPropsMethodNames = returnObjectStrings[1].match(/\s\w+:\s/g).map(str => str.match(/\w+/)[0])
}
const proxyquireObject = ('{\n ' + result
.match(/import\s{[\s\S]+?}\sfrom\s.+/g)
.map(s => s.replace(/\n/g, ''))
.map((s, i) => {
const proxyKeys = s.match(/{.+}/)[0].match(/\w+/g)
return '\'' + s.match(/'(.+)'/)[1] + '\': { ' + (proxyKeys.length > 1
? '\n ' + proxyKeys.join(': () => {},\n ') + ': () => {},\n '
: proxyKeys[0] + ': () => {},') + ' }'
})
.join(',\n ') + '\n}')
.replace('{ connect: () => {}, },', `{
connect: (ms, md) => {
mapStateToProps = ms
mapDispatchToProps = md
return () => ({})
},
},`)
// console.log(`proxyquireObject`, proxyquireObject);
// console.log(`mapStateToPropsAssertionObject`, mapStateToPropsAssertionObject);
// console.log(`mapDispatchToPropsMethodNames`, mapDispatchToPropsMethodNames);
const containerTest = generateContainerTest(sPath, {
mapStateToPropsAssertionObject,
mapDispatchToPropsMethodNames,
proxyquireObject,
})
// console.log(`containerTest`, `${__dirname}/${sRootPath}tests/${testFilePath}`, containerTest);
console.log('----')
console.log(`sRootPath`, sRootPath)
console.log(`testFilePath`, testFilePath)
await promisify(fs.writeFile)(
`${__dirname}/${sRootPath}tests/${testFilePath}`,
containerTest,
'utf8'
)
}
}
)
}, (err) => {
console.log('123', err)
})
}
/*
function generateMethodList (methodArray) {
return methodArray.map(n => ' ' + n).join(',\n') + ','
}
function generateMethodDescribeBlock (methodName, index) {
const describeBlock =
`${index ? ' ' : ''}describe('${methodName}()', () => {
it('should', () => {
const state = {}
assert.equal(${methodName}(state), )
})
})`
return describeBlock
}
*/
function generateDispatchMethodDescribeBlock (methodName, index) {
const describeBlock =
`${index ? ' ' : ''}describe('${methodName}()', () => {
it('should dispatch an action', () => {
mapDispatchToPropsObject.${methodName}()
assert(dispatchSpy.calledOnce)
})
})`
return describeBlock
}
/*
function generateMethodDescribeBlocks (methodArray) {
return methodArray
.map((methodName, index) => generateMethodDescribeBlock(methodName, index))
.join('\n\n')
}
*/
function generateDispatchMethodDescribeBlocks (methodArray) {
return methodArray
.map((methodName, index) => generateDispatchMethodDescribeBlock(methodName, index))
.join('\n\n')
}
/*
function generateSelectorTest (name, methodArray) {
return `import assert from 'assert'
import {
${generateMethodList(methodArray)}
} from '../${name}'
describe('${name.match(/^[^.]+/)} selectors', () => {
${generateMethodDescribeBlocks(methodArray)}
})`
}
function generateUtilTest (name, methodArray) {
return `import assert from 'assert'
import {
${generateMethodList(methodArray)}
} from '../${name}'
describe('${name.match(/^[^.]+/)} utils', () => {
${generateMethodDescribeBlocks(methodArray)}
})`
}
*/
function generateContainerTest (sPath, {
mapStateToPropsAssertionObject,
mapDispatchToPropsMethodNames,
proxyquireObject,
}) {
return `import assert from 'assert'
import proxyquire from 'proxyquire'
import sinon from 'sinon'
let mapStateToProps
let mapDispatchToProps
proxyquire('../${sPath}', ${proxyquireObject})
describe('${sPath.match(/^[^.]+/)} container', () => {
describe('mapStateToProps()', () => {
it('should map the correct properties to props', () => {
assert.deepEqual(mapStateToProps('mockState'), ${mapStateToPropsAssertionObject})
})
})
describe('mapDispatchToProps()', () => {
let dispatchSpy
let mapDispatchToPropsObject
beforeEach(() => {
dispatchSpy = sinon.spy()
mapDispatchToPropsObject = mapDispatchToProps(dispatchSpy)
})
${mapDispatchToPropsMethodNames ? generateDispatchMethodDescribeBlocks(mapDispatchToPropsMethodNames) : 'delete'}
})
})`
}

View File

@ -13,27 +13,21 @@ const zip = require('gulp-zip')
const assign = require('lodash.assign')
const livereload = require('gulp-livereload')
const del = require('del')
const eslint = require('gulp-eslint')
const fs = require('fs')
const path = require('path')
const manifest = require('./app/manifest.json')
const replace = require('gulp-replace')
const mkdirp = require('mkdirp')
const asyncEach = require('async/each')
const exec = require('child_process').exec
const sass = require('gulp-sass')
const autoprefixer = require('gulp-autoprefixer')
const gulpStylelint = require('gulp-stylelint')
const stylefmt = require('gulp-stylefmt')
const uglify = require('gulp-uglify-es').default
const babel = require('gulp-babel')
const debug = require('gulp-debug')
const pify = require('pify')
const gulpMultiProcess = require('gulp-multi-process')
const endOfStream = pify(require('end-of-stream'))
function gulpParallel (...args) {
return function spawnGulpChildProcess(cb) {
return function spawnGulpChildProcess (cb) {
return gulpMultiProcess(args, cb, true)
}
}
@ -48,12 +42,12 @@ const commonPlatforms = [
// browser webapp
'mascara',
// browser extensions
...browserPlatforms
...browserPlatforms,
]
// browser reload
gulp.task('dev:reload', function() {
gulp.task('dev:reload', function () {
livereload.listen({
port: 35729,
})
@ -108,7 +102,7 @@ createCopyTasks('html:mascara', {
destinations: [`./dist/mascara/`],
})
function createCopyTasks(label, opts) {
function createCopyTasks (label, opts) {
if (!opts.devOnly) {
const copyTaskName = `copy:${label}`
copyTask(copyTaskName, opts)
@ -119,7 +113,7 @@ function createCopyTasks(label, opts) {
copyDevTaskNames.push(copyDevTaskName)
}
function copyTask(taskName, opts){
function copyTask (taskName, opts) {
const source = opts.source
const destination = opts.destination
const destinations = opts.destinations || [destination]
@ -137,12 +131,12 @@ function copyTask(taskName, opts){
return performCopy()
})
function performCopy() {
function performCopy () {
// stream from source
let stream = gulp.src(source + pattern, { base: source })
// copy to destinations
destinations.forEach(function(destination) {
destinations.forEach(function (destination) {
stream = stream.pipe(gulp.dest(destination))
})
@ -152,40 +146,40 @@ function copyTask(taskName, opts){
// manifest tinkering
gulp.task('manifest:chrome', function() {
gulp.task('manifest:chrome', function () {
return gulp.src('./dist/chrome/manifest.json')
.pipe(jsoneditor(function(json) {
.pipe(jsoneditor(function (json) {
delete json.applications
return json
}))
.pipe(gulp.dest('./dist/chrome', { overwrite: true }))
})
gulp.task('manifest:opera', function() {
gulp.task('manifest:opera', function () {
return gulp.src('./dist/opera/manifest.json')
.pipe(jsoneditor(function(json) {
.pipe(jsoneditor(function (json) {
json.permissions = [
"storage",
"tabs",
"clipboardWrite",
"clipboardRead",
"http://localhost:8545/"
'storage',
'tabs',
'clipboardWrite',
'clipboardRead',
'http://localhost:8545/',
]
return json
}))
.pipe(gulp.dest('./dist/opera', { overwrite: true }))
})
gulp.task('manifest:production', function() {
gulp.task('manifest:production', function () {
return gulp.src([
'./dist/firefox/manifest.json',
'./dist/chrome/manifest.json',
'./dist/edge/manifest.json',
'./dist/opera/manifest.json',
],{base: './dist/'})
], {base: './dist/'})
// Exclude chromereload script in production:
.pipe(jsoneditor(function(json) {
.pipe(jsoneditor(function (json) {
json.background.scripts = json.background.scripts.filter((script) => {
return !script.includes('chromereload')
})
@ -212,29 +206,6 @@ gulp.task('dev:copy',
)
)
// lint js
const lintTargets = ['app/**/*.json', 'app/**/*.js', '!app/scripts/vendor/**/*.js', 'ui/**/*.js', 'old-ui/**/*.js', 'mascara/src/*.js', 'mascara/server/*.js', '!node_modules/**', '!dist/firefox/**', '!docs/**', '!app/scripts/chromereload.js', '!mascara/test/jquery-3.1.0.min.js']
gulp.task('lint', function () {
// Ignoring node_modules, dist/firefox, and docs folders:
return gulp.src(lintTargets)
.pipe(eslint(fs.readFileSync(path.join(__dirname, '.eslintrc'))))
// eslint.format() outputs the lint results to the console.
// Alternatively use eslint.formatEach() (see Docs).
.pipe(eslint.format())
// To have the process exit with an error code (1) on
// lint error, return the stream and pipe to failAfterError last.
.pipe(eslint.failAfterError())
});
gulp.task('lint:fix', function () {
return gulp.src(lintTargets)
.pipe(eslint(Object.assign(fs.readFileSync(path.join(__dirname, '.eslintrc')), {fix: true})))
.pipe(eslint.format())
.pipe(eslint.failAfterError())
});
// scss compilation and autoprefixing tasks
gulp.task('build:scss', createScssBuildTask({
@ -250,7 +221,7 @@ gulp.task('dev:scss', createScssBuildTask({
pattern: 'ui/app/**/*.scss',
}))
function createScssBuildTask({ src, dest, devMode, pattern }) {
function createScssBuildTask ({ src, dest, devMode, pattern }) {
return function () {
if (devMode) {
watch(pattern, async (event) => {
@ -262,7 +233,7 @@ function createScssBuildTask({ src, dest, devMode, pattern }) {
return buildScss()
}
function buildScss() {
function buildScss () {
return gulp.src(src)
.pipe(sourcemaps.init())
.pipe(sass().on('error', sass.logError))
@ -272,22 +243,22 @@ function createScssBuildTask({ src, dest, devMode, pattern }) {
}
}
gulp.task('lint-scss', function() {
gulp.task('lint-scss', function () {
return gulp
.src('ui/app/css/itcss/**/*.scss')
.pipe(gulpStylelint({
reporters: [
{ formatter: 'string', console: true }
{ formatter: 'string', console: true },
],
fix: true,
}));
});
}))
})
gulp.task('fmt-scss', function () {
return gulp.src('ui/app/css/itcss/**/*.scss')
.pipe(stylefmt())
.pipe(gulp.dest('ui/app/css/itcss'));
});
.pipe(gulp.dest('ui/app/css/itcss'))
})
// build js
@ -300,11 +271,11 @@ const buildJsFiles = [
// bundle tasks
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'dev:extension:js', devMode: true })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
createTasksForBuildJsExtension({ buildJsFiles, taskPrefix: 'build:extension:js' })
createTasksForBuildJsMascara({ taskPrefix: 'build:mascara:js' })
createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true })
function createTasksForBuildJsExtension({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) {
function createTasksForBuildJsExtension ({ buildJsFiles, taskPrefix, devMode, bundleTaskOpts = {} }) {
// inpage must be built before all other scripts:
const rootDir = './app/scripts'
const nonInpageFiles = buildJsFiles.filter(file => file !== 'inpage')
@ -322,7 +293,7 @@ function createTasksForBuildJsExtension({ buildJsFiles, taskPrefix, devMode, bun
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1, buildPhase2 })
}
function createTasksForBuildJsMascara({ taskPrefix, devMode, bundleTaskOpts = {} }) {
function createTasksForBuildJsMascara ({ taskPrefix, devMode, bundleTaskOpts = {} }) {
// inpage must be built before all other scripts:
const rootDir = './mascara/src/'
const buildPhase1 = ['ui', 'proxy', 'background', 'metamascara']
@ -338,7 +309,7 @@ function createTasksForBuildJsMascara({ taskPrefix, devMode, bundleTaskOpts = {}
createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 })
}
function createTasksForBuildJs({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
function createTasksForBuildJs ({ rootDir, taskPrefix, bundleTaskOpts, destinations, buildPhase1 = [], buildPhase2 = [] }) {
// bundle task for each file
const jsFiles = [].concat(buildPhase1, buildPhase2)
jsFiles.forEach((jsFile) => {
@ -367,7 +338,7 @@ gulp.task('disc', gulp.parallel(buildJsFiles.map(jsFile => `disc:${jsFile}`)))
// clean dist
gulp.task('clean', function clean() {
gulp.task('clean', function clean () {
return del(['./dist/*'])
})
@ -460,7 +431,7 @@ gulp.task('dist',
// task generators
function zipTask(target) {
function zipTask (target) {
return () => {
return gulp.src(`dist/${target}/**`)
.pipe(zip(`metamask-${target}-${manifest.version}.zip`))
@ -468,7 +439,7 @@ function zipTask(target) {
}
}
function generateBundler(opts, performBundle) {
function generateBundler (opts, performBundle) {
const browserifyOpts = assign({}, watchify.args, {
entries: [opts.filepath],
plugin: 'browserify-derequire',
@ -497,7 +468,7 @@ function generateBundler(opts, performBundle) {
return bundler
}
function discTask(opts) {
function discTask (opts) {
opts = Object.assign({
buildWithFullPaths: true,
}, opts)
@ -508,7 +479,7 @@ function discTask(opts) {
return performBundle
function performBundle(){
function performBundle () {
// start "disc" build
const discDir = path.join(__dirname, 'disc')
mkdirp.sync(discDir)
@ -523,14 +494,14 @@ function discTask(opts) {
}
function bundleTask(opts) {
function bundleTask (opts) {
const bundler = generateBundler(opts, performBundle)
// output build logs to terminal
bundler.on('log', gutil.log)
return performBundle
function performBundle(){
function performBundle () {
let buildStream = bundler.bundle()
// handle errors
@ -562,7 +533,7 @@ function bundleTask(opts) {
buildStream = buildStream
.pipe(uglify({
mangle: {
reserved: [ 'MetamaskInpageProvider' ]
reserved: [ 'MetamaskInpageProvider' ],
},
}))
}

View File

@ -3,7 +3,7 @@ const EthQuery = require('ethjs-query')
window.addEventListener('load', loadProvider)
window.addEventListener('message', console.warn)
async function loadProvider() {
async function loadProvider () {
const ethereumProvider = window.metamask.createDefaultProvider({ host: 'http://localhost:9001' })
const ethQuery = new EthQuery(ethereumProvider)
const accounts = await ethQuery.accounts()
@ -13,7 +13,7 @@ async function loadProvider() {
}
function logToDom(message, context){
function logToDom (message, context) {
document.getElementById(context).innerText = message
console.log(message)
}
@ -35,4 +35,4 @@ function setupButtons (ethQuery) {
})
logToDom(txHash, 'cb-value')
})
}
}

View File

@ -1,8 +1,8 @@
const express = require('express')
const path = require('path')
const createMetamascaraServer = require('../server/')
const createBundle = require('../server/util').createBundle
const serveBundle = require('../server/util').serveBundle
//
// Iframe Server
//
@ -23,7 +23,7 @@ const dappServer = express()
// serve dapp bundle
serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js')))
dappServer.use(express.static(__dirname + '/app/'))
dappServer.use(express.static(path.join(__dirname, '/app/')))
// start the server
const dappPort = '9002'

View File

@ -8,7 +8,7 @@ export default class Breadcrumbs extends Component {
currentIndex: PropTypes.number,
};
render() {
render () {
const {total, currentIndex} = this.props
return (
<div className="breadcrumbs">
@ -20,7 +20,7 @@ export default class Breadcrumbs extends Component {
/>
))}
</div>
);
)
}
}

View File

@ -54,7 +54,7 @@ class BuyEtherScreen extends Component {
return (
<div
className='buy-ether__do-it-later'
className="buy-ether__do-it-later"
onClick={() => showAccountDetail(address)}
>
Do it later
@ -64,17 +64,17 @@ class BuyEtherScreen extends Component {
renderCoinbaseLogo () {
return (
<svg width='140px' height='49px' viewBox='0 0 579 126' version='1.1'>
<g id='Page-1' stroke='none' strokeWidth={1} fill='none' fillRule='evenodd'>
<g id='Imported-Layers' fill='#0081C9'>
<path d='M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873' id='Fill-1' />
<path d='M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z' id='Fill-2' />
<path d='M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z' id='Fill-3' />
<path d='M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137' id='Fill-4' />
<path d='M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z' id='Fill-5' />
<path d='M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z' id='Fill-6' />
<path d='M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873' id='Fill-7' />
<path d='M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z' id='Fill-8' />
<svg width="140px" height="49px" viewBox="0 0 579 126" version="1.1">
<g id="Page-1" stroke="none" strokeWidth={1} fill="none" fillRule="evenodd">
<g id="Imported-Layers" fill="#0081C9">
<path d="M37.752,125.873 C18.824,125.873 0.369,112.307 0.369,81.549 C0.369,50.79 18.824,37.382 37.752,37.382 C47.059,37.382 54.315,39.749 59.52,43.219 L53.841,55.68 C50.371,53.156 45.166,51.579 39.961,51.579 C28.604,51.579 18.193,60.57 18.193,81.391 C18.193,102.212 28.919,111.361 39.961,111.361 C45.166,111.361 50.371,109.783 53.841,107.26 L59.52,120.036 C54.157,123.664 47.059,125.873 37.752,125.873" id="Fill-1" />
<path d="M102.898,125.873 C78.765,125.873 65.515,106.786 65.515,81.549 C65.515,56.311 78.765,37.382 102.898,37.382 C127.032,37.382 140.282,56.311 140.282,81.549 C140.282,106.786 127.032,125.873 102.898,125.873 L102.898,125.873 Z M102.898,51.105 C89.491,51.105 82.866,63.093 82.866,81.391 C82.866,99.688 89.491,111.834 102.898,111.834 C116.306,111.834 122.931,99.688 122.931,81.391 C122.931,63.093 116.306,51.105 102.898,51.105 L102.898,51.105 Z" id="Fill-2" />
<path d="M163.468,23.659 C157.79,23.659 153.215,19.243 153.215,13.88 C153.215,8.517 157.79,4.1 163.468,4.1 C169.146,4.1 173.721,8.517 173.721,13.88 C173.721,19.243 169.146,23.659 163.468,23.659 L163.468,23.659 Z M154.793,39.118 L172.144,39.118 L172.144,124.138 L154.793,124.138 L154.793,39.118 Z" id="Fill-3" />
<path d="M240.443,124.137 L240.443,67.352 C240.443,57.415 234.449,51.263 222.619,51.263 C216.31,51.263 210.473,52.367 207.003,53.787 L207.003,124.137 L189.81,124.137 L189.81,43.376 C198.328,39.906 209.212,37.382 222.461,37.382 C246.28,37.382 257.794,47.793 257.794,65.775 L257.794,124.137 L240.443,124.137" id="Fill-4" />
<path d="M303.536,125.873 C292.494,125.873 281.611,123.191 274.986,119.879 L274.986,0.314 L292.179,0.314 L292.179,41.326 C296.28,39.433 302.905,37.856 308.741,37.856 C330.667,37.856 345.494,53.629 345.494,79.656 C345.494,111.676 328.931,125.873 303.536,125.873 L303.536,125.873 Z M305.744,51.263 C301.012,51.263 295.491,52.367 292.179,54.103 L292.179,109.941 C294.703,111.045 299.593,112.149 304.482,112.149 C318.205,112.149 328.301,102.685 328.301,80.918 C328.301,62.305 319.467,51.263 305.744,51.263 L305.744,51.263 Z" id="Fill-5" />
<path d="M392.341,125.873 C367.892,125.873 355.589,115.935 355.589,99.215 C355.589,75.555 380.826,71.296 406.537,69.876 L406.537,64.513 C406.537,53.787 399.439,50.001 388.555,50.001 C380.511,50.001 370.731,52.525 365.053,55.207 L360.636,43.376 C367.419,40.379 378.933,37.382 390.29,37.382 C410.638,37.382 422.942,45.269 422.942,66.248 L422.942,119.879 C416.79,123.191 404.329,125.873 392.341,125.873 L392.341,125.873 Z M406.537,81.391 C389.186,82.337 371.835,83.757 371.835,98.9 C371.835,107.89 378.776,113.411 391.868,113.411 C397.389,113.411 403.856,112.465 406.537,111.203 L406.537,81.391 L406.537,81.391 Z" id="Fill-6" />
<path d="M461.743,125.873 C451.806,125.873 441.395,123.191 435.244,119.879 L441.08,106.629 C445.496,109.31 454.803,112.149 461.27,112.149 C470.576,112.149 476.728,107.575 476.728,100.477 C476.728,92.748 470.261,89.751 461.586,86.596 C450.228,82.337 437.452,77.132 437.452,61.201 C437.452,47.162 448.336,37.382 467.264,37.382 C477.517,37.382 486.035,39.906 492.029,43.376 L486.665,55.364 C482.88,52.998 475.309,50.317 469.157,50.317 C460.166,50.317 455.118,55.049 455.118,61.201 C455.118,68.93 461.428,71.611 469.788,74.766 C481.618,79.183 494.71,84.072 494.71,100.635 C494.71,115.935 483.038,125.873 461.743,125.873" id="Fill-7" />
<path d="M578.625,81.233 L522.155,89.12 C523.89,104.42 533.828,112.149 548.182,112.149 C556.699,112.149 565.848,110.099 571.684,106.944 L576.732,119.879 C570.107,123.349 558.75,125.873 547.078,125.873 C520.262,125.873 505.277,108.679 505.277,81.549 C505.277,55.522 519.789,37.382 543.607,37.382 C565.69,37.382 578.782,51.894 578.782,74.766 C578.782,76.816 578.782,79.025 578.625,81.233 L578.625,81.233 Z M543.292,50.001 C530.042,50.001 521.367,60.097 521.051,77.763 L562.22,72.084 C562.062,57.257 554.649,50.001 543.292,50.001 L543.292,50.001 Z" id="Fill-8" />
</g>
</g>
</svg>
@ -85,13 +85,13 @@ class BuyEtherScreen extends Component {
const {goToCoinbase, address} = this.props
return (
<div className='buy-ether__action-content-wrapper'>
<div className="buy-ether__action-content-wrapper">
<div>{this.renderCoinbaseLogo()}</div>
<div className='buy-ether__body-text'>Coinbase is the worlds most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
<a className='first-time-flow__link buy-ether__faq-link'>What is Ethereum?</a>
<div className='buy-ether__buttons'>
<div className="buy-ether__body-text">Coinbase is the worlds most popular way to buy and sell bitcoin, ethereum, and litecoin.</div>
<a className="first-time-flow__link buy-ether__faq-link">What is Ethereum?</a>
<div className="buy-ether__buttons">
<button
className='first-time-flow__button'
className="first-time-flow__button"
onClick={() => goToCoinbase(address)}
>
Buy
@ -114,23 +114,23 @@ class BuyEtherScreen extends Component {
return this.renderCoinbaseForm()
case OPTION_VALUES.SHAPESHIFT:
return (
<div className='buy-ether__action-content-wrapper'>
<div className='shapeshift-logo' />
<div className='buy-ether__body-text'>
<div className="buy-ether__action-content-wrapper">
<div className="shapeshift-logo" />
<div className="buy-ether__body-text">
Trade any leading blockchain asset for any other. Protection by Design. No Account Needed.
</div>
<ShapeShiftForm btnClass='first-time-flow__button' />
<ShapeShiftForm btnClass="first-time-flow__button" />
</div>
)
case OPTION_VALUES.QR_CODE:
return (
<div className='buy-ether__action-content-wrapper'>
<div className="buy-ether__action-content-wrapper">
<div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
<div className='buy-ether__body-text'>Deposit Ether directly into your account.</div>
<div className='buy-ether__small-body-text'>(This is the account address that MetaMask created for you to recieve funds.)</div>
<div className='buy-ether__buttons'>
<div className="buy-ether__body-text">Deposit Ether directly into your account.</div>
<div className="buy-ether__small-body-text">(This is the account address that MetaMask created for you to recieve funds.)</div>
<div className="buy-ether__buttons">
<button
className='first-time-flow__button'
className="first-time-flow__button"
onClick={this.copyToClipboard}
disabled={justCopied}
>
@ -149,19 +149,19 @@ class BuyEtherScreen extends Component {
const { selectedOption } = this.state
return (
<div className='buy-ether'>
<div className="buy-ether">
<Identicon address={this.props.address} diameter={70} />
<div className='buy-ether__title'>Deposit Ether</div>
<div className='buy-ether__body-text'>
<div className="buy-ether__title">Deposit Ether</div>
<div className="buy-ether__body-text">
MetaMask works best if you have Ether in your account to pay for transaction gas fees and more. To get Ether, choose from one of these methods.
</div>
<div className='buy-ether__content-wrapper'>
<div className='buy-ether__content-headline-wrapper'>
<div className='buy-ether__content-headline'>Deposit Options</div>
<div className="buy-ether__content-wrapper">
<div className="buy-ether__content-headline-wrapper">
<div className="buy-ether__content-headline">Deposit Options</div>
{this.renderSkip()}
</div>
<div className='buy-ether__content'>
<div className='buy-ether__side-panel'>
<div className="buy-ether__content">
<div className="buy-ether__side-panel">
{OPTIONS.map(({ name, value }) => (
<div
key={value}
@ -170,16 +170,16 @@ class BuyEtherScreen extends Component {
})}
onClick={() => this.setState({ selectedOption: value })}
>
<div className='buy-ether__side-panel-item-name'>{name}</div>
<div className="buy-ether__side-panel-item-name">{name}</div>
{value === selectedOption && (
<svg viewBox='0 0 574 1024' id='si-ant-right' width='15px' height='15px'>
<path d='M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z' />
<svg viewBox="0 0 574 1024" id="si-ant-right" width="15px" height="15px">
<path d="M10 9Q0 19 0 32t10 23l482 457L10 969Q0 979 0 992t10 23q10 9 24 9t24-9l506-480q10-10 10-23t-10-23L58 9Q48 0 34 0T10 9z" />
</svg>
)}
</div>
))}
</div>
<div className='buy-ether__action-content'>
<div className="buy-ether__action-content">
{this.renderContent()}
</div>
</div>

View File

@ -123,10 +123,6 @@
width: calc(100vw - 80px);
}
.unique-image {
width: auto;
}
.create-password__title,
.unique-image__title,
.tou__title,
@ -148,7 +144,7 @@
height: 100%;
flex-direction: column;
align-items: center;
justify-content: space-evenly;
justify-content: flex-start;
margin-top: 12px;
}
@ -181,7 +177,6 @@
margin: 0 !important;
padding: 16px 20px !important;
height: 30vh !important;
width: calc(100% - 48px) !important;
}
.backup-phrase__content-wrapper {
@ -280,6 +275,12 @@
width: 335px;
}
@media only screen and (max-width: 575px) {
.unique-image__body-text {
width: initial;
}
}
.unique-image__body-text +
.unique-image__body-text,
.backup-phrase__body-text +
@ -294,7 +295,7 @@
border-radius: 8px;
background-color: #FFFFFF;
margin: 0 142px 0 0;
height: 334px;
height: 200px;
overflow-y: auto;
color: #757575;
font-family: Roboto;
@ -679,7 +680,7 @@ button.backup-phrase__confirm-seed-option:hover {
}
.first-time-flow__input {
width: 350px;
max-width: 350px;
}
.first-time-flow__button {

View File

@ -2,7 +2,7 @@ import React from 'react'
import PropTypes from 'prop-types'
import Spinner from './spinner'
export default function LoadingScreen({ className = '', loadingMessage }) {
export default function LoadingScreen ({ className = '', loadingMessage }) {
return (
<div className={`${className} loading-screen`}>
<Spinner color="#1B344D" />

View File

@ -14,7 +14,7 @@ import LoadingScreen from './loading-screen'
class NoticeScreen extends Component {
static propTypes = {
address: PropTypes.string.isRequired,
lastUnreadNotice: PropTypes.shape({
nextUnreadNotice: PropTypes.shape({
title: PropTypes.string,
date: PropTypes.string,
body: PropTypes.string,
@ -31,7 +31,7 @@ class NoticeScreen extends Component {
};
static defaultProps = {
lastUnreadNotice: {},
nextUnreadNotice: {},
};
state = {
@ -47,8 +47,8 @@ class NoticeScreen extends Component {
}
acceptTerms = () => {
const { markNoticeRead, lastUnreadNotice, history } = this.props
markNoticeRead(lastUnreadNotice)
const { markNoticeRead, nextUnreadNotice, history } = this.props
markNoticeRead(nextUnreadNotice)
.then(hasActiveNotices => {
if (!hasActiveNotices) {
history.push(INITIALIZE_BACKUP_PHRASE_ROUTE)
@ -72,7 +72,7 @@ class NoticeScreen extends Component {
render () {
const {
address,
lastUnreadNotice: { title, body },
nextUnreadNotice: { title, body },
isLoading,
} = this.props
const { atBottom } = this.state
@ -113,12 +113,12 @@ class NoticeScreen extends Component {
}
const mapStateToProps = ({ metamask, appState }) => {
const { selectedAddress, lastUnreadNotice, noActiveNotices } = metamask
const { selectedAddress, nextUnreadNotice, noActiveNotices } = metamask
const { isLoading } = appState
return {
address: selectedAddress,
lastUnreadNotice,
nextUnreadNotice,
noActiveNotices,
isLoading,
}

View File

@ -79,11 +79,11 @@ export class ShapeShiftForm extends Component {
renderMetadata (label, value) {
return (
<div className='shapeshift-form__metadata-wrapper'>
<div className='shapeshift-form__metadata-label'>
<div className="shapeshift-form__metadata-wrapper">
<div className="shapeshift-form__metadata-label">
{label}:
</div>
<div className='shapeshift-form__metadata-value'>
<div className="shapeshift-form__metadata-value">
{value}
</div>
</div>
@ -101,7 +101,7 @@ export class ShapeShiftForm extends Component {
} = tokenExchangeRates[coinPair] || {}
return (
<div className='shapeshift-form__metadata'>
<div className="shapeshift-form__metadata">
{this.renderMetadata('Status', limit ? 'Available' : 'Unavailable')}
{this.renderMetadata('Limit', limit)}
{this.renderMetadata('Exchange Rate', rate)}
@ -117,13 +117,13 @@ export class ShapeShiftForm extends Component {
qrImage.make()
return (
<div className='shapeshift-form'>
<div className='shapeshift-form__deposit-instruction'>
<div className="shapeshift-form">
<div className="shapeshift-form__deposit-instruction">
Deposit your BTC to the address bellow:
</div>
<div className='shapeshift-form__qr-code'>
<div className="shapeshift-form__qr-code">
{isLoading
? <img src='images/loading.svg' style={{ width: '60px' }} />
? <img src="images/loading.svg" style={{ width: '60px' }} />
: <div dangerouslySetInnerHTML={{ __html: qrImage.createTableTag(4) }} />
}
</div>
@ -141,14 +141,14 @@ export class ShapeShiftForm extends Component {
return showQrCode ? this.renderQrCode() : (
<div>
<div className='shapeshift-form'>
<div className='shapeshift-form__selectors'>
<div className='shapeshift-form__selector'>
<div className='shapeshift-form__selector-label'>
<div className="shapeshift-form">
<div className="shapeshift-form__selectors">
<div className="shapeshift-form__selector">
<div className="shapeshift-form__selector-label">
Deposit
</div>
<select
className='shapeshift-form__selector-input'
className="shapeshift-form__selector-input"
value={this.state.depositCoin}
onChange={this.onCoinChange}
>
@ -160,14 +160,14 @@ export class ShapeShiftForm extends Component {
</select>
</div>
<div
className='icon shapeshift-form__caret'
className="icon shapeshift-form__caret"
style={{ backgroundImage: 'url(images/caret-right.svg)'}}
/>
<div className='shapeshift-form__selector'>
<div className='shapeshift-form__selector-label'>
<div className="shapeshift-form__selector">
<div className="shapeshift-form__selector-label">
Receive
</div>
<div className='shapeshift-form__selector-input'>
<div className="shapeshift-form__selector-input">
ETH
</div>
</div>
@ -177,18 +177,18 @@ export class ShapeShiftForm extends Component {
'shapeshift-form__address-input-wrapper--error': errorMessage,
})}
>
<div className='shapeshift-form__address-input-label'>
<div className="shapeshift-form__address-input-label">
Your Refund Address
</div>
<input
type='text'
className='shapeshift-form__address-input'
type="text"
className="shapeshift-form__address-input"
onChange={e => this.setState({
refundAddress: e.target.value,
errorMessage: '',
})}
/>
<div className='shapeshift-form__address-input-error-message'>
<div className="shapeshift-form__address-input-error-message">
{errorMessage}
</div>
</div>

View File

@ -37,7 +37,7 @@ const dbController = new DbController({
start().catch(log.error)
async function start() {
async function start () {
log.debug('MetaMask initializing...')
const initState = await loadStateFromPersistence()
await setupController(initState)

View File

@ -43,7 +43,7 @@ console.log('starting service worker')
swController.startWorker()
// Setup listener for when the service worker is read
function connectApp() {
function connectApp () {
const connectionStream = SwStream({
serviceWorker: swController.getWorker(),
context: name,

View File

@ -1,6 +1,6 @@
function wait(time) {
return new Promise(function(resolve, reject) {
setTimeout(function() {
export default function wait (time) {
return new Promise(function (resolve, reject) {
setTimeout(function () {
resolve()
}, time * 3 || 1500)
})

View File

@ -1,9 +1,9 @@
var fs = require('fs')
var path = require('path')
var browserify = require('browserify');
var browserify = require('browserify')
var tests = fs.readdirSync(path.join(__dirname, 'lib'))
var bundlePath = path.join(__dirname, 'test-bundle.js')
var b = browserify();
var b = browserify()
// Remove old bundle
try {
@ -14,9 +14,9 @@ try {
var writeStream = fs.createWriteStream(bundlePath)
tests.forEach(function(fileName) {
tests.forEach(function (fileName) {
b.add(path.join(__dirname, 'lib', fileName))
})
b.bundle().pipe(writeStream);
b.bundle().pipe(writeStream)

View File

@ -1,5 +1,3 @@
const Helper = require('./util/mascara-test-helper.js')
window.addEventListener('load', () => {
window.METAMASK_SKIP_RELOAD = true
// inject app container

View File

@ -2,27 +2,29 @@ const EventEmitter = require('events')
const IDB = require('idb-global')
const KEY = 'metamask-test-config'
module.exports = class Helper extends EventEmitter {
constructor () {
super()
}
tryToCleanContext () {
this.unregister()
.then(() => this.clearDb())
.then(() => super.emit('complete'))
.catch((err) => super.emit('complete'))
.catch((err) => {
if (err) {
super.emit('complete')
}
})
}
unregister () {
return global.navigator.serviceWorker.getRegistration()
.then((registration) => {
if (registration) return registration.unregister()
if (registration) {
return registration.unregister()
.then((b) => b ? Promise.resolve() : Promise.reject())
else return Promise.resolve()
} else return Promise.resolve()
})
}
clearDb () {
return new Promise ((resolve, reject) => {
return new Promise((resolve, reject) => {
const deleteRequest = global.indexDB.deleteDatabase(KEY)
deleteRequest.addEventListener('success', resolve)
deleteRequest.addEventListener('error', reject)
@ -33,7 +35,7 @@ module.exports = class Helper extends EventEmitter {
const db = new IDB({
version: 2,
key: KEY,
initialState: state
initialState: state,
})
return db.open()
}

View File

@ -0,0 +1,6 @@
Dear MetaMask Users,
There have been several instances of high-profile legitimate websites such as BTC Manager and Games Workshop that have had their websites temporarily compromised. This involves showing a fake MetaMask window on the page asking for user's seed phrases. MetaMask will never open itself in this way and users are encouraged to report these instances immediately to either [our phishing blacklist](https://github.com/MetaMask/eth-phishing-detect/issues) or our support email at [support@metamask.io](mailto:support@metamask.io).
Please read our full article on this ongoing issue at [https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168](https://medium.com/metamask/new-phishing-strategy-becoming-common-1b1123837168).

View File

@ -1,27 +0,0 @@
var fs = require('fs')
var path = require('path')
var prompt = require('prompt')
var open = require('open')
var extend = require('extend')
var notices = require('./notices.json')
console.log('List of Notices')
console.log(`ID \t DATE \t\t\t TITLE`)
notices.forEach((notice) => {
console.log(`${(' ' + notice.id).slice(-2)} \t ${notice.date} \t ${notice.title}`)
})
prompt.get(['id'], (error, res) => {
prompt.start()
if (error) {
console.log("Exiting...")
process.exit()
}
var index = notices.findIndex((notice) => { return notice.id == res.id})
if (index === -1) {
console.log('Notice not found. Exiting...')
}
notices.splice(index, 1)
fs.unlink(`notices/archive/notice_${res.id}.md`)
fs.writeFile(`notices/notices.json`, JSON.stringify(notices))
})

View File

@ -1,33 +0,0 @@
var fsp = require('fs-promise')
var path = require('path')
var prompt = require('prompt')
var open = require('open')
var extend = require('extend')
var notices = require('./notices.json')
var id = Number(require('./notice-nonce.json'))
var date = new Date().toDateString()
var notice = {
read: false,
date: date,
}
fsp.writeFile(`notices/archive/notice_${id}.md`,'Message goes here. Please write out your notice and save before proceeding at the command line.')
.then(() => {
open(`notices/archive/notice_${id}.md`)
prompt.start()
prompt.get(['title'], (err, result) => {
notice.title = result.title
fsp.readFile(`notices/archive/notice_${id}.md`)
.then((body) => {
notice.body = body.toString()
notice.id = id
notices.push(notice)
return fsp.writeFile(`notices/notices.json`, JSON.stringify(notices))
}).then((completion) => {
id += 1
return fsp.writeFile(`notices/notice-nonce.json`, id)
})
})
})

View File

@ -1 +0,0 @@
4

35
notices/notices.js Normal file
View File

@ -0,0 +1,35 @@
// fs.readFileSync is inlined by browserify transform "brfs"
const fs = require('fs')
const path = require('path')
module.exports = [
{
id: 0,
read: false,
date: 'Thu Feb 09 2017',
title: 'Terms of Use',
body: fs.readFileSync(path.join(__dirname, '/archive', 'notice_0.md'), 'utf8'),
},
{
id: 2,
read: false,
date: 'Mon May 08 2017',
title: 'Privacy Notice',
body: fs.readFileSync(path.join(__dirname, '/archive', 'notice_2.md'), 'utf8'),
},
{
id: 3,
read: false,
date: 'Tue Nov 28 2017',
title: 'Seed Phrase Alert',
firstVersion: '<=3.12.0',
body: fs.readFileSync(path.join(__dirname, '/archive', 'notice_3.md'), 'utf8'),
},
{
id: 4,
read: false,
date: 'Wed Jun 13 2018',
title: 'Phishing Warning',
body: fs.readFileSync(path.join(__dirname, '/archive', 'notice_4.md'), 'utf8'),
},
]

File diff suppressed because one or more lines are too long

View File

@ -73,7 +73,7 @@ function mapStateToProps (state) {
network: state.metamask.network,
provider: state.metamask.provider,
forgottenPassword: state.appState.forgottenPassword,
lastUnreadNotice: state.metamask.lastUnreadNotice,
nextUnreadNotice: state.metamask.nextUnreadNotice,
lostAccounts: state.metamask.lostAccounts,
frequentRpcList: state.metamask.frequentRpcList || [],
featureFlags,
@ -461,9 +461,9 @@ App.prototype.renderPrimary = function () {
}, [
h(NoticeScreen, {
notice: props.lastUnreadNotice,
notice: props.nextUnreadNotice,
key: 'NoticeScreen',
onConfirm: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
onConfirm: () => props.dispatch(actions.markNoticeRead(props.nextUnreadNotice)),
}),
!props.isInitialized && h('.flex-row.flex-center.flex-grow', [

View File

@ -23,9 +23,10 @@ class AccountDropdowns extends Component {
renderAccounts () {
const { identities, selected, keyrings } = this.props
const accountOrder = keyrings.reduce((list, keyring) => list.concat(keyring.accounts), [])
return Object.keys(identities).map((key, index) => {
const identity = identities[key]
return accountOrder.map((address, index) => {
const identity = identities[address]
const isSelected = identity.address === selected
const simpleAddress = identity.address.substring(2).toLowerCase()

View File

@ -20,7 +20,7 @@ function EnsInput () {
EnsInput.prototype.render = function () {
const props = this.props
function onInputChange() {
function onInputChange () {
const network = this.props.network
const networkHasEnsSupport = getNetworkEnsSupport(network)
if (!networkHasEnsSupport) return

View File

@ -28,7 +28,7 @@ LoadingIndicator.prototype.render = function () {
background: 'rgba(255, 255, 255, 0.8)',
},
}, [
canBypass ? h( 'i.fa.fa-close.cursor-pointer.close-loading', {
canBypass ? h('i.fa.fa-close.cursor-pointer.close-loading', {
style: {
position: 'absolute',
top: '1px',

Some files were not shown because too many files have changed in this diff Show More