Merge pull request #2068 from MetaMask/export-as-file

Allow exporting seed words and private keys as files
feature/default_network_editable
Dan Finlay 7 years ago committed by GitHub
commit 2478fd85c2
  1. 3
      CHANGELOG.md
  2. 28
      ui/app/components/account-export.js
  3. 10
      ui/app/keychains/hd/create-vault-complete.js
  4. 16
      ui/app/util.js

@ -2,6 +2,9 @@
## Current Master ## Current Master
- Add ability to export private keys as a file.
- Add ability to export seed words as a file.
## 3.10.0 2017-9-11 ## 3.10.0 2017-9-11
- Readded loose keyring label back into the account list. - Readded loose keyring label back into the account list.

@ -1,6 +1,7 @@
const Component = require('react').Component const Component = require('react').Component
const h = require('react-hyperscript') const h = require('react-hyperscript')
const inherits = require('util').inherits const inherits = require('util').inherits
const exportAsFile = require('../util').exportAsFile
const copyToClipboard = require('copy-to-clipboard') const copyToClipboard = require('copy-to-clipboard')
const actions = require('../actions') const actions = require('../actions')
const ethUtil = require('ethereumjs-util') const ethUtil = require('ethereumjs-util')
@ -20,20 +21,21 @@ function mapStateToProps (state) {
} }
ExportAccountView.prototype.render = function () { ExportAccountView.prototype.render = function () {
var state = this.props const state = this.props
var accountDetail = state.accountDetail const accountDetail = state.accountDetail
const nickname = state.identities[state.address].name
if (!accountDetail) return h('div') if (!accountDetail) return h('div')
var accountExport = accountDetail.accountExport const accountExport = accountDetail.accountExport
var notExporting = accountExport === 'none' const notExporting = accountExport === 'none'
var exportRequested = accountExport === 'requested' const exportRequested = accountExport === 'requested'
var accountExported = accountExport === 'completed' const accountExported = accountExport === 'completed'
if (notExporting) return h('div') if (notExporting) return h('div')
if (exportRequested) { if (exportRequested) {
var warning = `Export private keys at your own risk.` const warning = `Export private keys at your own risk.`
return ( return (
h('div', { h('div', {
style: { style: {
@ -89,6 +91,8 @@ ExportAccountView.prototype.render = function () {
} }
if (accountExported) { if (accountExported) {
const plainKey = ethUtil.stripHexPrefix(accountDetail.privateKey)
return h('div.privateKey', { return h('div.privateKey', {
style: { style: {
margin: '0 20px', margin: '0 20px',
@ -105,10 +109,16 @@ ExportAccountView.prototype.render = function () {
onClick: function (event) { onClick: function (event) {
copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey)) copyToClipboard(ethUtil.stripHexPrefix(accountDetail.privateKey))
}, },
}, ethUtil.stripHexPrefix(accountDetail.privateKey)), }, plainKey),
h('button', { h('button', {
onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)), onClick: () => this.props.dispatch(actions.backToAccountDetail(this.props.address)),
}, 'Done'), }, 'Done'),
h('button', {
style: {
marginLeft: '10px',
},
onClick: () => exportAsFile(`MetaMask ${nickname} Private Key`, plainKey),
}, 'Save as File'),
]) ])
} }
} }
@ -117,6 +127,6 @@ ExportAccountView.prototype.onExportKeyPress = function (event) {
if (event.key !== 'Enter') return if (event.key !== 'Enter') return
event.preventDefault() event.preventDefault()
var input = document.getElementById('exportAccount').value const input = document.getElementById('exportAccount').value
this.props.dispatch(actions.exportAccount(input, this.props.address)) this.props.dispatch(actions.exportAccount(input, this.props.address))
} }

@ -3,6 +3,7 @@ const Component = require('react').Component
const connect = require('react-redux').connect const connect = require('react-redux').connect
const h = require('react-hyperscript') const h = require('react-hyperscript')
const actions = require('../../actions') const actions = require('../../actions')
const exportAsFile = require('../../util').exportAsFile
module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen) module.exports = connect(mapStateToProps)(CreateVaultCompleteScreen)
@ -65,8 +66,17 @@ CreateVaultCompleteScreen.prototype.render = function () {
style: { style: {
margin: '24px', margin: '24px',
fontSize: '0.9em', fontSize: '0.9em',
marginBottom: '10px',
}, },
}, 'I\'ve copied it somewhere safe'), }, 'I\'ve copied it somewhere safe'),
h('button.primary', {
onClick: () => exportAsFile(`MetaMask Seed Words`, seed),
style: {
margin: '10px',
fontSize: '0.9em',
},
}, 'Save Seed Words As File'),
]) ])
) )
} }

@ -36,6 +36,7 @@ module.exports = {
valueTable: valueTable, valueTable: valueTable,
bnTable: bnTable, bnTable: bnTable,
isHex: isHex, isHex: isHex,
exportAsFile: exportAsFile,
} }
function valuesFor (obj) { function valuesFor (obj) {
@ -215,3 +216,18 @@ function readableDate (ms) {
function isHex (str) { function isHex (str) {
return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/)) return Boolean(str.match(/^(0x)?[0-9a-fA-F]+$/))
} }
function exportAsFile (filename, data) {
// source: https://stackoverflow.com/a/33542499 by Ludovic Feltz
const blob = new Blob([data], {type: 'text/csv'})
if (window.navigator.msSaveOrOpenBlob) {
window.navigator.msSaveBlob(blob, filename)
} else {
const elem = window.document.createElement('a')
elem.href = window.URL.createObjectURL(blob)
elem.download = filename
document.body.appendChild(elem)
elem.click()
document.body.removeChild(elem)
}
}

Loading…
Cancel
Save