Feature Flag + Mobile Sync (#5955)

feature/default_network_editable
Dan Finlay 6 years ago committed by Whymarrh Whitby
parent fdc7eb2113
commit f507f2a927
  1. 27
      app/_locales/en/messages.json
  2. 13
      app/scripts/controllers/preferences.js
  3. 58
      app/scripts/metamask-controller.js
  4. 1
      docs/README.md
  5. 10
      docs/secret-preferences.md
  6. 3
      gulpfile.js
  7. 2
      mascara/src/app/buy-ether-widget/index.js
  8. 2
      mascara/src/app/shapeshift-form/index.js
  9. 267
      package-lock.json
  10. 3
      package.json
  11. 16
      ui/app/actions.js
  12. 3
      ui/app/app.js
  13. 387
      ui/app/components/pages/mobile-sync/index.js
  14. 38
      ui/app/components/pages/settings/settings-tab/settings-tab.component.js
  15. 2
      ui/app/components/pages/settings/settings-tab/settings-tab.container.js
  16. 2
      ui/app/components/qr-code.js
  17. 2
      ui/app/components/shapeshift-form.js
  18. 2
      ui/app/routes.js

@ -794,6 +794,12 @@
"minutesShorthand": {
"message": "Min"
},
"mobileSyncTitle": {
"message": "Sync accounts with mobile"
},
"mobileSyncText": {
"message": "Please enter your password to confirm it's you!"
},
"myAccounts": {
"message": "My Accounts"
},
@ -1333,6 +1339,27 @@
"symbolBetweenZeroTwelve": {
"message": "Symbol must be between 0 and 12 characters."
},
"syncWithMobile": {
"message": "Sync with mobile"
},
"syncWithMobileTitle": {
"message": "Sync with mobile"
},
"syncWithMobileDesc": {
"message": "You can sync your accounts and information with your mobile device. Open the MetaMask mobile app, go to \"Settings\" and tap on \"Sync from Browser Extension\""
},
"syncWithMobileDescNewUsers": {
"message": "If you just open the MetaMask Mobile app for the first time, just follow the steps in your phone."
},
"syncWithMobileScanThisCode": {
"message": "Scan this code with your MetaMask mobile app"
},
"syncWithMobileBeCareful": {
"message": "Make sure nobody else is looking at your screen when you scan this code"
},
"syncWithMobileComplete": {
"message": "Your data has been synced succesfully. Enjoy the MetaMask mobile app!"
},
"takesTooLong": {
"message": "Taking too long?"
},

@ -18,7 +18,9 @@ class PreferencesController {
* @property {object} store.assetImages Contains assets objects related to assets added
* @property {boolean} store.useBlockie The users preference for blockie identicons within the UI
* @property {object} store.featureFlags A key-boolean map, where keys refer to features and booleans to whether the
* user wishes to see that feature
* user wishes to see that feature.
*
* Feature flags can be set by the global function `setPreference(feature, enabled)`, and so should not expose any sensitive behavior.
* @property {object} store.knownMethodData Contains all data methods known by the user
* @property {string} store.currentLocale The preferred language locale key
* @property {string} store.selectedAddress A hex string that matches the currently selected address in the app
@ -33,6 +35,11 @@ class PreferencesController {
tokens: [],
suggestedTokens: {},
useBlockie: false,
// WARNING: Do not use feature flags for security-sensitive things.
// Feature flag toggling is available in the global namespace
// for convenient testing of pre-release features, and should never
// perform sensitive operations.
featureFlags: {},
knownMethodData: {},
currentLocale: opts.initLangCode,
@ -52,6 +59,10 @@ class PreferencesController {
this.store = new ObservableStore(initState)
this.openPopup = opts.openPopup
this._subscribeProviderType()
global.setPreference = (key, value) => {
return this.setFeatureFlag(key, value)
}
}
// PUBLIC METHODS

@ -56,6 +56,7 @@ const EthQuery = require('eth-query')
const ethUtil = require('ethereumjs-util')
const sigUtil = require('eth-sig-util')
module.exports = class MetamaskController extends EventEmitter {
/**
@ -410,6 +411,9 @@ module.exports = class MetamaskController extends EventEmitter {
checkHardwareStatus: nodeify(this.checkHardwareStatus, this),
unlockHardwareWalletAccount: nodeify(this.unlockHardwareWalletAccount, this),
// mobile
fetchInfoToSync: nodeify(this.fetchInfoToSync, this),
// vault management
submitPassword: nodeify(this.submitPassword, this),
@ -586,6 +590,60 @@ module.exports = class MetamaskController extends EventEmitter {
})
}
/**
* Collects all the information that we want to share
* with the mobile client for syncing purposes
* @returns Promise<Object> Parts of the state that we want to syncx
*/
async fetchInfoToSync () {
// Preferences
const {
accountTokens,
currentLocale,
frequentRpcList,
identities,
selectedAddress,
tokens,
} = this.preferencesController.store.getState()
const preferences = {
accountTokens,
currentLocale,
frequentRpcList,
identities,
selectedAddress,
tokens,
}
// Accounts
const hdKeyring = this.keyringController.getKeyringsByType('HD Key Tree')[0]
const hdAccounts = await hdKeyring.getAccounts()
const accounts = {
hd: hdAccounts.filter((item, pos) => (hdAccounts.indexOf(item) === pos)).map(address => ethUtil.toChecksumAddress(address)),
simpleKeyPair: [],
ledger: [],
trezor: [],
}
// transactions
let transactions = this.txController.store.getState().transactions
// delete tx for other accounts that we're not importing
transactions = transactions.filter(tx => {
const checksummedTxFrom = ethUtil.toChecksumAddress(tx.txParams.from)
return (
accounts.hd.includes(checksummedTxFrom)
)
})
return {
accounts,
preferences,
transactions,
network: this.networkController.store.getState(),
}
}
/*
* Submits the user's password and attempts to unlock the vault.
* Also synchronizes the preferencesController, to ensure its schema

@ -18,3 +18,4 @@ To learn how to develop MetaMask-compatible applications, visit our [Developer D
- [How to manage notices that appear when the app starts up](./notices.md)
- [How to port MetaMask to a new platform](./porting_to_new_environment.md)
- [How to generate a visualization of this repository's development](./development-visualization.md)
- [How to add a feature behind a secret feature flag](./secret-preferences.md)

@ -0,0 +1,10 @@
# Secret Preferences
Sometimes we want to test a feature in the wild that may not be ready for public consumption.
One example is our "sync with mobile" feature, which didn't make sense to roll out before the mobile version was live.
To enable features like this, first open the background console, and then you can use the global method `global.setPreference(key, value)`.
For example, if the feature flag was a booelan was called `mobileSync`, you might type `setPreference('mobileSync', true)`.

@ -304,6 +304,7 @@ createTasksForBuildJsMascara({ taskPrefix: 'dev:mascara:js', devMode: true })
function createTasksForBuildJsUIDeps ({ dependenciesToBundle, filename }) {
const destinations = browserPlatforms.map(platform => `./dist/${platform}`)
const bundleTaskOpts = Object.assign({
buildSourceMaps: true,
sourceMapDir: '../sourcemaps',
@ -512,6 +513,8 @@ function generateBundler (opts, performBundle) {
bundler.transform(envify({
METAMASK_DEBUG: opts.devMode,
NODE_ENV: opts.devMode ? 'development' : 'production',
PUBNUB_SUB_KEY: process.env.PUBNUB_SUB_KEY || '',
PUBNUB_PUB_KEY: process.env.PUBNUB_PUB_KEY || '',
}), {
global: true,
})

@ -2,7 +2,7 @@ import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {connect} from 'react-redux'
import {qrcode} from 'qrcode-npm'
import {qrcode} from 'qrcode-generator'
import copyToClipboard from 'copy-to-clipboard'
import ShapeShiftForm from '../shapeshift-form'
import {buyEth, showAccountDetail} from '../../../../ui/app/actions'

@ -1,7 +1,7 @@
import React, { Component } from 'react'
import PropTypes from 'prop-types'
import classnames from 'classnames'
import {qrcode} from 'qrcode-npm'
import qrcode from 'qrcode-generator'
import {connect} from 'react-redux'
import {shapeShiftSubview, pairUpdate, buyWithShapeShift} from '../../../../ui/app/actions'
import {isValidAddress} from '../../../../ui/app/util'

267
package-lock.json generated

@ -2761,11 +2761,18 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz",
"integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==",
"dev": true,
"requires": {
"es6-promisify": "^5.0.0"
}
},
"agentkeepalive": {
"version": "3.5.2",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-3.5.2.tgz",
"integrity": "sha512-e0L/HNe6qkQ7H19kTlRRqUibEAwDK5AFk6y3PtMsuut2VAH6+Q4xZml1tNDJD7kSAyqmbG/K08K5WEJYtUrSlQ==",
"requires": {
"humanize-ms": "^1.2.1"
}
},
"airbnb-js-shims": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/airbnb-js-shims/-/airbnb-js-shims-1.4.1.tgz",
@ -6424,8 +6431,7 @@
"component-emitter": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz",
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=",
"dev": true
"integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY="
},
"component-inherit": {
"version": "0.0.3",
@ -6572,6 +6578,11 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
},
"cookiejar": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.2.tgz",
"integrity": "sha512-Mw+adcfzPxcPeI+0WlvRrr/3lGVO0bD75SxX6811cxSh1Wbxx7xZBGK1eVtDf6si8rg2lhnUjsVLMFMfbRIuwA=="
},
"copy-concurrently": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz",
@ -7504,8 +7515,7 @@
"data-uri-to-buffer": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-1.2.0.tgz",
"integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ==",
"dev": true
"integrity": "sha512-vKQ9DTQPN1FLYiiEEOQ6IBGFqvjCa5rSK3cWMy/Nespm5d/x3dGFT9UBZnkLxCwua/IXBi2TYnwTEpsOvhC4UQ=="
},
"date-format": {
"version": "1.2.0",
@ -7613,8 +7623,7 @@
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
"dev": true
"integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"deepmerge": {
"version": "0.2.10",
@ -7697,7 +7706,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/degenerator/-/degenerator-1.0.4.tgz",
"integrity": "sha1-/PSQo37OJmRk2cxDGrmMWBnO0JU=",
"dev": true,
"requires": {
"ast-types": "0.x.x",
"escodegen": "1.x.x",
@ -7707,8 +7715,7 @@
"esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
"dev": true
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
}
}
},
@ -9066,14 +9073,12 @@
"es6-promise": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
"dev": true
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ=="
},
"es6-promisify": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
"dev": true,
"requires": {
"es6-promise": "^4.0.3"
}
@ -9140,7 +9145,6 @@
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz",
"integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==",
"dev": true,
"requires": {
"esprima": "^3.1.3",
"estraverse": "^4.2.0",
@ -9152,14 +9156,12 @@
"esprima": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=",
"dev": true
"integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"optional": true
}
}
@ -9760,7 +9762,7 @@
}
},
"eth-contract-metadata": {
"version": "github:MetaMask/eth-contract-metadata#4d855fea9a5c899059134e03986be9d98e844270",
"version": "github:MetaMask/eth-contract-metadata#f6201b0c4aca8e98321b9b0b65744bc2fe8e35fa",
"from": "github:MetaMask/eth-contract-metadata#master"
},
"eth-ens-namehash": {
@ -9812,7 +9814,7 @@
}
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -9902,7 +9904,7 @@
}
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -9971,7 +9973,7 @@
"dependencies": {
"babelify": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"resolved": "http://registry.npmjs.org/babelify/-/babelify-7.3.0.tgz",
"integrity": "sha1-qlau3nBn/XvVSWZu4W3ChQh+iOU=",
"requires": {
"babel-core": "^6.0.14",
@ -10082,7 +10084,7 @@
}
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -10190,7 +10192,7 @@
}
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -10262,7 +10264,7 @@
"integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -10485,7 +10487,7 @@
}
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -10698,7 +10700,7 @@
"integrity": "sha1-L9w1dvIykDNYl26znaeDIT/5Uj8="
},
"ethereumjs-abi": {
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#2863c40e0982acfc0b7163f0285d4c56427c7799",
"version": "git+https://github.com/ethereumjs/ethereumjs-abi.git#d84a96796079c8595a0c78accd1e7709f2277215",
"from": "git+https://github.com/ethereumjs/ethereumjs-abi.git",
"requires": {
"bn.js": "^4.10.0",
@ -12098,8 +12100,7 @@
"file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"dev": true
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw=="
},
"filename-regex": {
"version": "2.0.1",
@ -12737,6 +12738,11 @@
"samsam": "1.x"
}
},
"formidable": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz",
"integrity": "sha512-Fs9VRguL0gqGHkXS5GQiMCr1VhZBxz0JnJs4JmMp/2jL18Fmbzvv7vOFRU+U8TBkHEE/CX1qDXzJplVULgsLeg=="
},
"forwarded": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz",
@ -12909,7 +12915,6 @@
"version": "0.3.10",
"resolved": "https://registry.npmjs.org/ftp/-/ftp-0.3.10.tgz",
"integrity": "sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0=",
"dev": true,
"requires": {
"readable-stream": "1.1.x",
"xregexp": "2.0.0"
@ -12918,14 +12923,12 @@
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
"dev": true
"integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
},
"readable-stream": {
"version": "1.1.14",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
"integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
"dev": true,
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.1",
@ -12936,8 +12939,7 @@
"string_decoder": {
"version": "0.10.31",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=",
"dev": true
"integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
}
}
},
@ -18906,7 +18908,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/get-uri/-/get-uri-2.0.2.tgz",
"integrity": "sha512-ZD325dMZOgerGqF/rF6vZXyFGTAay62svjQIT+X/oU2PtxYpFxvSkbsdi+oxIrsNxlZVd4y8wUDqkaExWTI/Cw==",
"dev": true,
"requires": {
"data-uri-to-buffer": "1",
"debug": "2",
@ -21564,7 +21565,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
"integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
"dev": true,
"requires": {
"agent-base": "4",
"debug": "3.1.0"
@ -21574,7 +21574,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
@ -21641,7 +21640,6 @@
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
"integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
"dev": true,
"requires": {
"agent-base": "^4.1.0",
"debug": "^3.1.0"
@ -21651,7 +21649,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
@ -21663,6 +21660,14 @@
"resolved": "https://registry.npmjs.org/human-standard-token-abi/-/human-standard-token-abi-2.0.0.tgz",
"integrity": "sha512-m1f5DiIvqaNmpgphNqx2OziyTCj4Lvmmk28uMSxGWrOc9/lMpAKH8UcMPhvb13DMNZPzxn07WYFhxOGKuPLryg=="
},
"humanize-ms": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
"integrity": "sha1-xG4xWaKT9riW2ikxbYtv6Lt5u+0=",
"requires": {
"ms": "^2.0.0"
}
},
"humanize-url": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/humanize-url/-/humanize-url-1.0.1.tgz",
@ -22146,8 +22151,7 @@
"ip": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
"dev": true
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo="
},
"ipaddr.js": {
"version": "1.5.2",
@ -24391,7 +24395,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
@ -24430,6 +24433,11 @@
"resolve": "^1.1.7"
}
},
"lil-uuid": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/lil-uuid/-/lil-uuid-0.1.1.tgz",
"integrity": "sha1-+e3PI/AOQr9D8PhD2Y2LU/M0HxY="
},
"livereload-js": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz",
@ -26428,8 +26436,7 @@
"netmask": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/netmask/-/netmask-1.0.6.tgz",
"integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU=",
"dev": true
"integrity": "sha1-ICl+idhvb2QA8lDZ9Pa0wZRfzTU="
},
"next-tick": {
"version": "1.0.0",
@ -28525,7 +28532,6 @@
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
"dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4",
@ -28538,8 +28544,7 @@
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
"dev": true
"integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
}
}
},
@ -28664,7 +28669,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz",
"integrity": "sha512-cDNAN1Ehjbf5EHkNY5qnRhGPUCp6SnpyVof5fRzN800QV1Y2OkzbH9rmjZkbBRa8igof903yOnjIl6z0SlAhxA==",
"dev": true,
"requires": {
"agent-base": "^4.2.0",
"debug": "^3.1.0",
@ -28680,7 +28684,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dev": true,
"requires": {
"ms": "2.0.0"
}
@ -28691,7 +28694,6 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz",
"integrity": "sha512-tcc38bsjuE3XZ5+4vP96OfhOugrX+JcnpUbhfuc4LuXBLQhoTthOstZeoQJBDnQUDYzYmdImKsbz0xSl1/9qeA==",
"dev": true,
"requires": {
"co": "^4.6.0",
"degenerator": "^1.0.4",
@ -30958,8 +30960,7 @@
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
"dev": true
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prepend-file": {
"version": "1.3.1",
@ -31158,11 +31159,49 @@
"ipaddr.js": "1.5.2"
}
},
"proxy-agent": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.3.1.tgz",
"integrity": "sha512-CNKuhC1jVtm8KJYFTS2ZRO71VCBx3QSA92So/e6NrY6GoJonkx3Irnk4047EsCcswczwqAekRj3s8qLRGahSKg==",
"requires": {
"agent-base": "^4.2.0",
"debug": "^3.1.0",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.1",
"lru-cache": "^4.1.2",
"pac-proxy-agent": "^2.0.1",
"proxy-from-env": "^1.0.0",
"socks-proxy-agent": "^3.0.0"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"lru-cache": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
"integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
"requires": {
"pseudomap": "^1.0.2",
"yallist": "^2.1.2"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"proxy-from-env": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz",
"integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=",
"dev": true
"integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4="
},
"proxyquire": {
"version": "2.0.1",
@ -31203,8 +31242,7 @@
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
"dev": true
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"psl": {
"version": "1.1.29",
@ -31224,6 +31262,17 @@
"randombytes": "^2.0.1"
}
},
"pubnub": {
"version": "4.21.7",
"resolved": "https://registry.npmjs.org/pubnub/-/pubnub-4.21.7.tgz",
"integrity": "sha512-TZ96GuY+gZIu9rJaqcO2cZ6tl4JPLruoUcN01sljm1CcDgzIZbOfcDSZp4NcZas4ECSqAAwo/izMMiImRRS4Yg==",
"requires": {
"agentkeepalive": "^3.5.2",
"lil-uuid": "^0.1.1",
"superagent": "^3.8.1",
"superagent-proxy": "^1.0.3"
}
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
@ -31270,10 +31319,10 @@
"integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==",
"dev": true
},
"qrcode-npm": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/qrcode-npm/-/qrcode-npm-0.0.3.tgz",
"integrity": "sha1-d+5vvvqcDyn6CdTRUggHxqYEK5o="
"qrcode-generator": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-1.4.1.tgz",
"integrity": "sha512-KOdSAyFBPf0/5Z3mra4JfSbjrDlUn2J3YH8Rm33tRGbptxP4vhogLWysvkQp8mp5ix9u80Wfr4vxHXTeR9o0Ug=="
},
"qs": {
"version": "6.5.1",
@ -33816,8 +33865,7 @@
"smart-buffer": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz",
"integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=",
"dev": true
"integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY="
},
"snapdragon": {
"version": "0.8.2",
@ -34633,7 +34681,6 @@
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz",
"integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=",
"dev": true,
"requires": {
"ip": "^1.1.4",
"smart-buffer": "^1.0.13"
@ -34643,7 +34690,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz",
"integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==",
"dev": true,
"requires": {
"agent-base": "^4.1.0",
"socks": "^1.1.10"
@ -35986,6 +36032,89 @@
}
}
},
"superagent": {
"version": "3.8.3",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.3.tgz",
"integrity": "sha512-GLQtLMCoEIK4eDv6OGtkOoSMt3D+oq0y3dsxMuYuDvaNUvuT8eFBuLmfR0iYYzHC1e8hpzC6ZsxbuP6DIalMFA==",
"requires": {
"component-emitter": "^1.2.0",
"cookiejar": "^2.1.0",
"debug": "^3.1.0",
"extend": "^3.0.0",
"form-data": "^2.3.1",
"formidable": "^1.2.0",
"methods": "^1.1.1",
"mime": "^1.4.1",
"qs": "^6.5.1",
"readable-stream": "^2.3.5"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
},
"process-nextick-args": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz",
"integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw=="
},
"readable-stream": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"requires": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"requires": {
"safe-buffer": "~5.1.0"
}
}
}
},
"superagent-proxy": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/superagent-proxy/-/superagent-proxy-1.0.3.tgz",
"integrity": "sha512-79Ujg1lRL2ICfuHUdX+H2MjIw73kB7bXsIkxLwHURz3j0XUmEEEoJ+u/wq+mKwna21Uejsm2cGR3OESA00TIjA==",
"requires": {
"debug": "^3.1.0",
"proxy-agent": "2"
},
"dependencies": {
"debug": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
"supports-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
@ -36536,8 +36665,7 @@
"thunkify": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/thunkify/-/thunkify-2.1.2.tgz",
"integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0=",
"dev": true
"integrity": "sha1-+qDp0jDFGsyVyhOjYawFyn4EVT0="
},
"tildify": {
"version": "1.2.0",
@ -36945,7 +37073,6 @@
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
"dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
@ -38451,8 +38578,7 @@
"xregexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM=",
"dev": true
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
},
"xtend": {
"version": "4.0.1",
@ -38467,8 +38593,7 @@
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
"dev": true
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
},
"yargs": {
"version": "6.6.0",

@ -173,9 +173,10 @@
"promise-filter": "^1.1.0",
"promise-to-callback": "^1.0.0",
"prop-types": "^15.6.1",
"pubnub": "^4.21.5",
"pump": "^3.0.0",
"pumpify": "^1.3.4",
"qrcode-npm": "0.0.3",
"qrcode-generator": "1.4.1",
"ramda": "^0.24.1",
"react": "^15.6.2",
"react-addons-css-transition-group": "^15.6.0",

@ -63,6 +63,7 @@ var actions = {
CREATE_NEW_VAULT_IN_PROGRESS: 'CREATE_NEW_VAULT_IN_PROGRESS',
SHOW_CREATE_VAULT: 'SHOW_CREATE_VAULT',
SHOW_RESTORE_VAULT: 'SHOW_RESTORE_VAULT',
fetchInfoToSync,
FORGOT_PASSWORD: 'FORGOT_PASSWORD',
forgotPassword: forgotPassword,
markPasswordForgotten,
@ -635,6 +636,21 @@ function requestRevealSeedWords (password) {
}
}
function fetchInfoToSync () {
return dispatch => {
log.debug(`background.fetchInfoToSync`)
return new Promise((resolve, reject) => {
background.fetchInfoToSync((err, result) => {
if (err) {
dispatch(actions.displayWarning(err.message))
return reject(err)
}
resolve(result)
})
})
}
}
function resetAccount () {
return dispatch => {
dispatch(actions.showLoadingIndication())

@ -25,6 +25,7 @@ import Lock from './components/pages/lock'
import UiMigrationAnnouncement from './components/ui-migration-annoucement'
const RestoreVaultPage = require('./components/pages/keychains/restore-vault').default
const RevealSeedConfirmation = require('./components/pages/keychains/reveal-seed')
const MobileSyncPage = require('./components/pages/mobile-sync')
const AddTokenPage = require('./components/pages/add-token')
const ConfirmAddTokenPage = require('./components/pages/confirm-add-token')
const ConfirmAddSuggestedTokenPage = require('./components/pages/confirm-add-suggested-token')
@ -55,6 +56,7 @@ import {
UNLOCK_ROUTE,
SETTINGS_ROUTE,
REVEAL_SEED_ROUTE,
MOBILE_SYNC_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,
CONFIRM_ADD_TOKEN_ROUTE,
@ -90,6 +92,7 @@ class App extends Component {
<Initialized path={UNLOCK_ROUTE} component={UnlockPage} exact />
<Initialized path={RESTORE_VAULT_ROUTE} component={RestoreVaultPage} exact />
<Authenticated path={REVEAL_SEED_ROUTE} component={RevealSeedConfirmation} exact />
<Authenticated path={MOBILE_SYNC_ROUTE} component={MobileSyncPage} exact />
<Authenticated path={SETTINGS_ROUTE} component={Settings} />
<Authenticated path={NOTICE_ROUTE} component={NoticeScreen} exact />
<Authenticated path={`${CONFIRM_TRANSACTION_ROUTE}/:id?`} component={ConfirmTransaction} />

@ -0,0 +1,387 @@
const { Component } = require('react')
const { connect } = require('react-redux')
const PropTypes = require('prop-types')
const h = require('react-hyperscript')
const classnames = require('classnames')
const PubNub = require('pubnub')
const { requestRevealSeedWords, fetchInfoToSync } = require('../../../actions')
const { DEFAULT_ROUTE } = require('../../../routes')
const actions = require('../../../actions')
const qrCode = require('qrcode-generator')
import Button from '../../button'
import LoadingScreen from '../../loading-screen'
const PASSWORD_PROMPT_SCREEN = 'PASSWORD_PROMPT_SCREEN'
const REVEAL_SEED_SCREEN = 'REVEAL_SEED_SCREEN'
class MobileSyncPage extends Component {
static propTypes = {
history: PropTypes.object,
selectedAddress: PropTypes.string,
displayWarning: PropTypes.func,
fetchInfoToSync: PropTypes.func,
requestRevealSeedWords: PropTypes.func,
}
constructor (props) {
super(props)
this.state = {
screen: PASSWORD_PROMPT_SCREEN,
password: '',
seedWords: null,
error: null,
syncing: false,
completed: false,
}
this.syncing = false
}
componentDidMount () {
const passwordBox = document.getElementById('password-box')
if (passwordBox) {
passwordBox.focus()
}
}
handleSubmit (event) {
event.preventDefault()
this.setState({ seedWords: null, error: null })
this.props.requestRevealSeedWords(this.state.password)
.then(seedWords => {
this.generateCipherKeyAndChannelName()
this.setState({ seedWords, screen: REVEAL_SEED_SCREEN })
this.initWebsockets()
})
.catch(error => this.setState({ error: error.message }))
}
generateCipherKeyAndChannelName () {
this.cipherKey = `${this.props.selectedAddress.substr(-4)}-${PubNub.generateUUID()}`
this.channelName = `mm-${PubNub.generateUUID()}`
}
initWebsockets () {
this.pubnub = new PubNub({
subscribeKey: process.env.PUBNUB_SUB_KEY,
publishKey: process.env.PUBNUB_PUB_KEY,
cipherKey: this.cipherKey,
ssl: true,
})
this.pubnubListener = this.pubnub.addListener({
message: (data) => {
const {channel, message} = data
// handle message
if (channel !== this.channelName || !message) {
return false
}
if (message.event === 'start-sync') {
this.startSyncing()
} else if (message.event === 'end-sync') {
this.disconnectWebsockets()
this.setState({syncing: false, completed: true})
}
},
})
this.pubnub.subscribe({
channels: [this.channelName],
withPresence: false,
})
}
disconnectWebsockets () {
if (this.pubnub && this.pubnubListener) {
this.pubnub.disconnect(this.pubnubListener)
}
}
// Calculating a PubNub Message Payload Size.
calculatePayloadSize (channel, message) {
return encodeURIComponent(
channel + JSON.stringify(message)
).length + 100
}
chunkString (str, size) {
const numChunks = Math.ceil(str.length / size)
const chunks = new Array(numChunks)
for (let i = 0, o = 0; i < numChunks; ++i, o += size) {
chunks[i] = str.substr(o, size)
}
return chunks
}
notifyError (errorMsg) {
return new Promise((resolve, reject) => {
this.pubnub.publish(
{
message: {
event: 'error-sync',
data: errorMsg,
},
channel: this.channelName,
sendByPost: false, // true to send via post
storeInHistory: false,
},
(status, response) => {
if (!status.error) {
resolve()
} else {
reject(response)
}
})
})
}
async startSyncing () {
if (this.syncing) return false
this.syncing = true
this.setState({syncing: true})
const { accounts, network, preferences, transactions } = await this.props.fetchInfoToSync()
const allDataStr = JSON.stringify({
accounts,
network,
preferences,
transactions,
udata: {
pwd: this.state.password,
seed: this.state.seedWords,
},
})
const chunks = this.chunkString(allDataStr, 17000)
const totalChunks = chunks.length
try {
for (let i = 0; i < totalChunks; i++) {
await this.sendMessage(chunks[i], i + 1, totalChunks)
}
} catch (e) {
this.props.displayWarning('Sync failed :(')
this.setState({syncing: false})
this.syncing = false
this.notifyError(e.toString())
}
}
sendMessage (data, pkg, count) {
return new Promise((resolve, reject) => {
this.pubnub.publish(
{
message: {
event: 'syncing-data',
data,
totalPkg: count,
currentPkg: pkg,
},
channel: this.channelName,
sendByPost: false, // true to send via post
storeInHistory: false,
},
(status, response) => {
if (!status.error) {
resolve()
} else {
reject(response)
}
}
)
})
}
componentWillUnmount () {
this.disconnectWebsockets()
}
renderWarning (text) {
return (
h('.page-container__warning-container', [
h('.page-container__warning-message', [
h('div', [text]),
]),
])
)
}
renderContent () {
const { t } = this.context
if (this.state.syncing) {
return h(LoadingScreen, {loadingMessage: 'Sync in progress'})
}
if (this.state.completed) {
return h('div.reveal-seed__content', {},
h('label.reveal-seed__label', {
style: {
width: '100%',
textAlign: 'center',
},
}, t('syncWithMobileComplete')),
)
}
return this.state.screen === PASSWORD_PROMPT_SCREEN
? h('div', {}, [
this.renderWarning(this.context.t('mobileSyncText')),
h('.reveal-seed__content', [
this.renderPasswordPromptContent(),
]),
])
: h('div', {}, [
this.renderWarning(this.context.t('syncWithMobileBeCareful')),
h('.reveal-seed__content', [ this.renderRevealSeedContent() ]),
])
}
renderPasswordPromptContent () {
const { t } = this.context
return (
h('form', {
onSubmit: event => this.handleSubmit(event),
}, [
h('label.input-label', {
htmlFor: 'password-box',
}, t('enterPasswordContinue')),
h('.input-group', [
h('input.form-control', {
type: 'password',
placeholder: t('password'),
id: 'password-box',
value: this.state.password,
onChange: event => this.setState({ password: event.target.value }),
className: classnames({ 'form-control--error': this.state.error }),
}),
]),
this.state.error && h('.reveal-seed__error', this.state.error),
])
)
}
renderRevealSeedContent () {
const qrImage = qrCode(0, 'M')
qrImage.addData(`metamask-sync:${this.channelName}|@|${this.cipherKey}`)
qrImage.make()
const { t } = this.context
return (
h('div', [
h('label.reveal-seed__label', {
style: {
width: '100%',
textAlign: 'center',
},
}, t('syncWithMobileScanThisCode')),
h('.div.qr-wrapper', {
style: {
display: 'flex',
justifyContent: 'center',
},
dangerouslySetInnerHTML: {
__html: qrImage.createTableTag(4),
},
}),
])
)
}
renderFooter () {
return this.state.screen === PASSWORD_PROMPT_SCREEN
? this.renderPasswordPromptFooter()
: this.renderRevealSeedFooter()
}
renderPasswordPromptFooter () {
return (
h('div.new-account-import-form__buttons', {style: {padding: 30}}, [
h(Button, {
type: 'default',
large: true,
className: 'new-account-create-form__button',
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('cancel')),
h(Button, {
type: 'primary',
large: true,
className: 'new-account-create-form__button',
onClick: event => this.handleSubmit(event),
disabled: this.state.password === '',
}, this.context.t('next')),
])
)
}
renderRevealSeedFooter () {
return (
h('.page-container__footer', {style: {padding: 30}}, [
h(Button, {
type: 'default',
large: true,
className: 'page-container__footer-button',
onClick: () => this.props.history.push(DEFAULT_ROUTE),
}, this.context.t('close')),
])
)
}
render () {
return (
h('.page-container', [
h('.page-container__header', [
h('.page-container__title', this.context.t('syncWithMobileTitle')),
this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDesc')) : null,
this.state.screen === PASSWORD_PROMPT_SCREEN ? h('.page-container__subtitle', this.context.t('syncWithMobileDescNewUsers')) : null,
]),
h('.page-container__content', [
this.renderContent(),
]),
this.renderFooter(),
])
)
}
}
MobileSyncPage.propTypes = {
requestRevealSeedWords: PropTypes.func,
fetchInfoToSync: PropTypes.func,
history: PropTypes.object,
}
MobileSyncPage.contextTypes = {
t: PropTypes.func,
}
const mapDispatchToProps = dispatch => {
return {
requestRevealSeedWords: password => dispatch(requestRevealSeedWords(password)),
fetchInfoToSync: () => dispatch(fetchInfoToSync()),
displayWarning: (message) => dispatch(actions.displayWarning(message || null)),
}
}
const mapStateToProps = state => {
const {
metamask: { selectedAddress },
} = state
return {
selectedAddress,
}
}
module.exports = connect(mapStateToProps, mapDispatchToProps)(MobileSyncPage)

@ -5,7 +5,7 @@ import validUrl from 'valid-url'
import { exportAsFile } from '../../../../util'
import SimpleDropdown from '../../../dropdowns/simple-dropdown'
import ToggleButton from 'react-toggle-button'
import { REVEAL_SEED_ROUTE } from '../../../../routes'
import { REVEAL_SEED_ROUTE, MOBILE_SYNC_ROUTE } from '../../../../routes'
import locales from '../../../../../../app/_locales/index.json'
import TextField from '../../../text-field'
import Button from '../../../button'
@ -46,6 +46,7 @@ export default class SettingsTab extends PureComponent {
delRpcTarget: PropTypes.func,
displayWarning: PropTypes.func,
revealSeedConfirmation: PropTypes.func,
setFeatureFlagToBeta: PropTypes.func,
showClearApprovalModal: PropTypes.func,
showResetAccountConfirmationModal: PropTypes.func,
warning: PropTypes.string,
@ -61,6 +62,7 @@ export default class SettingsTab extends PureComponent {
setUseNativeCurrencyAsPrimaryCurrencyPreference: PropTypes.func,
setAdvancedInlineGasFeatureFlag: PropTypes.func,
advancedInlineGas: PropTypes.bool,
mobileSync: PropTypes.bool,
}
state = {
@ -338,6 +340,39 @@ export default class SettingsTab extends PureComponent {
)
}
renderMobileSync () {
const { t } = this.context
const { history, mobileSync } = this.props
if (!mobileSync) {
return
}
return (
<div className="settings-page__content-row">
<div className="settings-page__content-item">
<span>{ t('syncWithMobile') }</span>
</div>
<div className="settings-page__content-item">
<div className="settings-page__content-item-col">
<Button
type="primary"
large
onClick={event => {
event.preventDefault()
history.push(MOBILE_SYNC_ROUTE)
}}
>
{ t('syncWithMobile') }
</Button>
</div>
</div>
</div>
)
}
renderResetAccount () {
const { t } = this.context
const { showResetAccountConfirmationModal } = this.props
@ -538,6 +573,7 @@ export default class SettingsTab extends PureComponent {
{ this.renderHexDataOptIn() }
{ this.renderAdvancedGasInputInline() }
{ this.renderBlockieOptIn() }
{ this.renderMobileSync() }
</div>
)
}

@ -26,6 +26,7 @@ const mapStateToProps = state => {
sendHexData,
privacyMode,
advancedInlineGas,
mobileSync,
} = {},
provider = {},
currentLocale,
@ -44,6 +45,7 @@ const mapStateToProps = state => {
privacyMode,
provider,
useNativeCurrencyAsPrimaryCurrency,
mobileSync,
}
}

@ -1,6 +1,6 @@
const Component = require('react').Component
const h = require('react-hyperscript')
const qrCode = require('qrcode-npm').qrcode
const qrCode = require('qrcode-generator')
const inherits = require('util').inherits
const connect = require('react-redux').connect
const { isHexPrefixed } = require('ethereumjs-util')

@ -4,7 +4,7 @@ const PropTypes = require('prop-types')
const Component = require('react').Component
const connect = require('react-redux').connect
const classnames = require('classnames')
const { qrcode } = require('qrcode-npm')
const qrcode = require('qrcode-generator')
const { shapeShiftSubview, pairUpdate, buyWithShapeShift } = require('../actions')
const { isValidAddress } = require('../util')
const SimpleDropdown = require('./dropdowns/simple-dropdown')

@ -4,6 +4,7 @@ const LOCK_ROUTE = '/lock'
const SETTINGS_ROUTE = '/settings'
const INFO_ROUTE = '/settings/info'
const REVEAL_SEED_ROUTE = '/seed'
const MOBILE_SYNC_ROUTE = '/mobile-sync'
const CONFIRM_SEED_ROUTE = '/confirm-seed'
const RESTORE_VAULT_ROUTE = '/restore-vault'
const ADD_TOKEN_ROUTE = '/add-token'
@ -43,6 +44,7 @@ module.exports = {
SETTINGS_ROUTE,
INFO_ROUTE,
REVEAL_SEED_ROUTE,
MOBILE_SYNC_ROUTE,
CONFIRM_SEED_ROUTE,
RESTORE_VAULT_ROUTE,
ADD_TOKEN_ROUTE,

Loading…
Cancel
Save