mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-10-22 19:26:13 +02:00
851ba66cdd
Implement generation of markdown for notice files. Create npm command. Enhance notice generation. Add test files to test multiple notices. Add basic markdown support to notices. Interval checks for updates. Add extensionizer and linker Add terms and conditions state file Add link support to disclaimer. Changelog addition.
431 lines
12 KiB
JavaScript
431 lines
12 KiB
JavaScript
const extend = require('xtend')
|
|
const EthStore = require('eth-store')
|
|
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
|
const IdentityStore = require('./lib/idStore')
|
|
const NoticeController = require('./notice-controller')
|
|
const messageManager = require('./lib/message-manager')
|
|
const HostStore = require('./lib/remote-store.js').HostStore
|
|
const Web3 = require('web3')
|
|
const ConfigManager = require('./lib/config-manager')
|
|
const extension = require('./lib/extension')
|
|
|
|
module.exports = class MetamaskController {
|
|
|
|
constructor (opts) {
|
|
this.opts = opts
|
|
this.listeners = []
|
|
this.configManager = new ConfigManager(opts)
|
|
this.idStore = new IdentityStore({
|
|
configManager: this.configManager,
|
|
})
|
|
this.noticeController = new NoticeController({
|
|
configManager: this.configManager,
|
|
})
|
|
this.noticeController = new NoticeController({
|
|
configManager: this.configManager,
|
|
})
|
|
this.provider = this.initializeProvider(opts)
|
|
this.ethStore = new EthStore(this.provider)
|
|
this.idStore.setStore(this.ethStore)
|
|
this.messageManager = messageManager
|
|
this.publicConfigStore = this.initPublicConfigStore()
|
|
|
|
var currentFiat = this.configManager.getCurrentFiat() || 'USD'
|
|
this.configManager.setCurrentFiat(currentFiat)
|
|
this.configManager.updateConversionRate()
|
|
|
|
this.checkNotices()
|
|
this.checkTOSChange()
|
|
|
|
this.scheduleConversionInterval()
|
|
this.scheduleNoticeCheck()
|
|
}
|
|
|
|
getState () {
|
|
return extend(
|
|
this.ethStore.getState(),
|
|
this.idStore.getState(),
|
|
this.configManager.getConfig(),
|
|
this.noticeController.getState()
|
|
)
|
|
}
|
|
|
|
getApi () {
|
|
const idStore = this.idStore
|
|
|
|
return {
|
|
getState: (cb) => { cb(null, this.getState()) },
|
|
setRpcTarget: this.setRpcTarget.bind(this),
|
|
setProviderType: this.setProviderType.bind(this),
|
|
useEtherscanProvider: this.useEtherscanProvider.bind(this),
|
|
agreeToDisclaimer: this.agreeToDisclaimer.bind(this),
|
|
resetDisclaimer: this.resetDisclaimer.bind(this),
|
|
setCurrentFiat: this.setCurrentFiat.bind(this),
|
|
agreeToEthWarning: this.agreeToEthWarning.bind(this),
|
|
setTOSHash: this.setTOSHash.bind(this),
|
|
checkTOSChange: this.checkTOSChange.bind(this),
|
|
checkNotices: this.checkNotices.bind(this),
|
|
setGasMultiplier: this.setGasMultiplier.bind(this),
|
|
|
|
// forward directly to idStore
|
|
createNewVault: idStore.createNewVault.bind(idStore),
|
|
recoverFromSeed: idStore.recoverFromSeed.bind(idStore),
|
|
submitPassword: idStore.submitPassword.bind(idStore),
|
|
setSelectedAddress: idStore.setSelectedAddress.bind(idStore),
|
|
approveTransaction: idStore.approveTransaction.bind(idStore),
|
|
cancelTransaction: idStore.cancelTransaction.bind(idStore),
|
|
signMessage: idStore.signMessage.bind(idStore),
|
|
cancelMessage: idStore.cancelMessage.bind(idStore),
|
|
setLocked: idStore.setLocked.bind(idStore),
|
|
clearSeedWordCache: idStore.clearSeedWordCache.bind(idStore),
|
|
exportAccount: idStore.exportAccount.bind(idStore),
|
|
revealAccount: idStore.revealAccount.bind(idStore),
|
|
saveAccountLabel: idStore.saveAccountLabel.bind(idStore),
|
|
tryPassword: idStore.tryPassword.bind(idStore),
|
|
recoverSeed: idStore.recoverSeed.bind(idStore),
|
|
// coinbase
|
|
buyEth: this.buyEth.bind(this),
|
|
// shapeshift
|
|
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
|
// notices
|
|
markNoticeRead: this.markNoticeRead.bind(this),
|
|
}
|
|
}
|
|
|
|
setupProviderConnection (stream, originDomain) {
|
|
stream.on('data', this.onRpcRequest.bind(this, stream, originDomain))
|
|
}
|
|
|
|
onRpcRequest (stream, originDomain, request) {
|
|
|
|
/* Commented out for Parity compliance
|
|
* Parity does not permit additional keys, like `origin`,
|
|
* and Infura is not currently filtering this key out.
|
|
var payloads = Array.isArray(request) ? request : [request]
|
|
payloads.forEach(function (payload) {
|
|
// Append origin to rpc payload
|
|
payload.origin = originDomain
|
|
// Append origin to signature request
|
|
if (payload.method === 'eth_sendTransaction') {
|
|
payload.params[0].origin = originDomain
|
|
} else if (payload.method === 'eth_sign') {
|
|
payload.params.push({ origin: originDomain })
|
|
}
|
|
})
|
|
*/
|
|
|
|
// handle rpc request
|
|
this.provider.sendAsync(request, function onPayloadHandled (err, response) {
|
|
logger(err, request, response)
|
|
if (response) {
|
|
try {
|
|
stream.write(response)
|
|
} catch (err) {
|
|
logger(err)
|
|
}
|
|
}
|
|
})
|
|
|
|
function logger (err, request, response) {
|
|
if (err) return console.error(err)
|
|
if (!request.isMetamaskInternal) {
|
|
if (global.METAMASK_DEBUG) {
|
|
console.log(`RPC (${originDomain}):`, request, '->', response)
|
|
}
|
|
if (response.error) {
|
|
console.error('Error in RPC response:\n', response.error)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sendUpdate () {
|
|
this.listeners.forEach((remote) => {
|
|
remote.sendUpdate(this.getState())
|
|
})
|
|
}
|
|
|
|
initializeProvider (opts) {
|
|
const idStore = this.idStore
|
|
|
|
var providerOpts = {
|
|
rpcUrl: this.configManager.getCurrentRpcAddress(),
|
|
// account mgmt
|
|
getAccounts: (cb) => {
|
|
var selectedAddress = idStore.getSelectedAddress()
|
|
var result = selectedAddress ? [selectedAddress] : []
|
|
cb(null, result)
|
|
},
|
|
// tx signing
|
|
approveTransaction: this.newUnsignedTransaction.bind(this),
|
|
signTransaction: (...args) => {
|
|
idStore.signTransaction(...args)
|
|
this.sendUpdate()
|
|
},
|
|
|
|
// msg signing
|
|
approveMessage: this.newUnsignedMessage.bind(this),
|
|
signMessage: (...args) => {
|
|
idStore.signMessage(...args)
|
|
this.sendUpdate()
|
|
},
|
|
}
|
|
|
|
var provider = MetaMaskProvider(providerOpts)
|
|
var web3 = new Web3(provider)
|
|
idStore.web3 = web3
|
|
idStore.getNetwork()
|
|
|
|
provider.on('block', this.processBlock.bind(this))
|
|
provider.on('error', idStore.getNetwork.bind(idStore))
|
|
|
|
return provider
|
|
}
|
|
|
|
initPublicConfigStore () {
|
|
// get init state
|
|
var initPublicState = extend(
|
|
idStoreToPublic(this.idStore.getState()),
|
|
configToPublic(this.configManager.getConfig())
|
|
)
|
|
|
|
var publicConfigStore = new HostStore(initPublicState)
|
|
|
|
// subscribe to changes
|
|
this.configManager.subscribe(function (state) {
|
|
storeSetFromObj(publicConfigStore, configToPublic(state))
|
|
})
|
|
this.idStore.on('update', function (state) {
|
|
storeSetFromObj(publicConfigStore, idStoreToPublic(state))
|
|
})
|
|
|
|
// idStore substate
|
|
function idStoreToPublic (state) {
|
|
return {
|
|
selectedAddress: state.selectedAddress,
|
|
}
|
|
}
|
|
// config substate
|
|
function configToPublic (state) {
|
|
return {
|
|
provider: state.provider,
|
|
selectedAddress: state.selectedAccount,
|
|
}
|
|
}
|
|
// dump obj into store
|
|
function storeSetFromObj (store, obj) {
|
|
Object.keys(obj).forEach(function (key) {
|
|
store.set(key, obj[key])
|
|
})
|
|
}
|
|
|
|
return publicConfigStore
|
|
}
|
|
|
|
newUnsignedTransaction (txParams, onTxDoneCb) {
|
|
const idStore = this.idStore
|
|
let err = this.enforceTxValidations(txParams)
|
|
if (err) return onTxDoneCb(err)
|
|
idStore.addUnconfirmedTransaction(txParams, onTxDoneCb, (err, txData) => {
|
|
if (err) return onTxDoneCb(err)
|
|
this.sendUpdate()
|
|
this.opts.showUnconfirmedTx(txParams, txData, onTxDoneCb)
|
|
})
|
|
}
|
|
|
|
enforceTxValidations (txParams) {
|
|
if (('value' in txParams) && txParams.value.indexOf('-') === 0) {
|
|
const msg = `Invalid transaction value of ${txParams.value} not a positive number.`
|
|
return new Error(msg)
|
|
}
|
|
}
|
|
|
|
newUnsignedMessage (msgParams, cb) {
|
|
var state = this.idStore.getState()
|
|
if (!state.isUnlocked) {
|
|
this.idStore.addUnconfirmedMessage(msgParams, cb)
|
|
this.opts.unlockAccountMessage()
|
|
} else {
|
|
this.addUnconfirmedMessage(msgParams, cb)
|
|
this.sendUpdate()
|
|
}
|
|
}
|
|
|
|
addUnconfirmedMessage (msgParams, cb) {
|
|
const idStore = this.idStore
|
|
const msgId = idStore.addUnconfirmedMessage(msgParams, cb)
|
|
this.opts.showUnconfirmedMessage(msgParams, msgId)
|
|
}
|
|
|
|
setupPublicConfig (stream) {
|
|
var storeStream = this.publicConfigStore.createStream()
|
|
stream.pipe(storeStream).pipe(stream)
|
|
}
|
|
|
|
// Log blocks
|
|
processBlock (block) {
|
|
if (global.METAMASK_DEBUG) {
|
|
console.log(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`)
|
|
}
|
|
this.verifyNetwork()
|
|
}
|
|
|
|
verifyNetwork () {
|
|
// Check network when restoring connectivity:
|
|
if (this.idStore._currentState.network === 'loading') {
|
|
this.idStore.getNetwork()
|
|
}
|
|
}
|
|
|
|
// config
|
|
//
|
|
|
|
setTOSHash (hash) {
|
|
try {
|
|
this.configManager.setTOSHash(hash)
|
|
} catch (e) {
|
|
console.error('Error in setting terms of service hash.')
|
|
}
|
|
}
|
|
|
|
checkTOSChange () {
|
|
try {
|
|
const storedHash = this.configManager.getTOSHash() || 0
|
|
if (storedHash !== global.TOS_HASH) {
|
|
this.resetDisclaimer()
|
|
this.setTOSHash(global.TOS_HASH)
|
|
}
|
|
} catch (e) {
|
|
console.error('Error in checking TOS change.')
|
|
}
|
|
|
|
}
|
|
|
|
// notice
|
|
|
|
markNoticeRead (notice, cb) {
|
|
try {
|
|
this.configManager.markNoticeRead(notice)
|
|
cb(null, this.configManager.getLatestUnreadNotice())
|
|
} catch (e) {
|
|
cb(e)
|
|
}
|
|
}
|
|
|
|
checkNotices () {
|
|
try {
|
|
this.configManager.updateNoticesList()
|
|
} catch (e) {
|
|
console.error('Error in checking notices.')
|
|
}
|
|
}
|
|
|
|
scheduleNoticeCheck () {
|
|
if (this.noticeCheck) {
|
|
clearInterval(this.noticeCheck)
|
|
}
|
|
this.noticeCheck = setInterval(() => {
|
|
this.configManager.updateNoticesList()
|
|
}, 300000)
|
|
}
|
|
|
|
// disclaimer
|
|
|
|
agreeToDisclaimer (cb) {
|
|
try {
|
|
this.configManager.setConfirmed(true)
|
|
cb()
|
|
} catch (e) {
|
|
cb(e)
|
|
}
|
|
}
|
|
|
|
resetDisclaimer () {
|
|
try {
|
|
this.configManager.setConfirmed(false)
|
|
} catch (e) {
|
|
console.error(e)
|
|
}
|
|
}
|
|
|
|
setCurrentFiat (fiat, cb) {
|
|
try {
|
|
this.configManager.setCurrentFiat(fiat)
|
|
this.configManager.updateConversionRate()
|
|
this.scheduleConversionInterval()
|
|
const data = {
|
|
conversionRate: this.configManager.getConversionRate(),
|
|
currentFiat: this.configManager.getCurrentFiat(),
|
|
conversionDate: this.configManager.getConversionDate(),
|
|
}
|
|
cb(data)
|
|
} catch (e) {
|
|
cb(null, e)
|
|
}
|
|
}
|
|
|
|
scheduleConversionInterval () {
|
|
if (this.conversionInterval) {
|
|
clearInterval(this.conversionInterval)
|
|
}
|
|
this.conversionInterval = setInterval(() => {
|
|
this.configManager.updateConversionRate()
|
|
}, 300000)
|
|
}
|
|
|
|
agreeToEthWarning (cb) {
|
|
try {
|
|
this.configManager.setShouldntShowWarning()
|
|
cb()
|
|
} catch (e) {
|
|
cb(e)
|
|
}
|
|
}
|
|
|
|
// called from popup
|
|
setRpcTarget (rpcTarget) {
|
|
this.configManager.setRpcTarget(rpcTarget)
|
|
extension.runtime.reload()
|
|
this.idStore.getNetwork()
|
|
}
|
|
|
|
setProviderType (type) {
|
|
this.configManager.setProviderType(type)
|
|
extension.runtime.reload()
|
|
this.idStore.getNetwork()
|
|
}
|
|
|
|
useEtherscanProvider () {
|
|
this.configManager.useEtherscanProvider()
|
|
extension.runtime.reload()
|
|
}
|
|
|
|
buyEth (address, amount) {
|
|
if (!amount) amount = '5'
|
|
|
|
var network = this.idStore._currentState.network
|
|
var url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH`
|
|
|
|
if (network === '3') {
|
|
url = 'https://faucet.metamask.io/'
|
|
}
|
|
|
|
extension.tabs.create({
|
|
url,
|
|
})
|
|
}
|
|
|
|
createShapeShiftTx (depositAddress, depositType) {
|
|
this.configManager.createShapeShiftTx(depositAddress, depositType)
|
|
}
|
|
|
|
setGasMultiplier (gasMultiplier, cb) {
|
|
try {
|
|
this.configManager.setGasMultiplier(gasMultiplier)
|
|
cb()
|
|
} catch (e) {
|
|
cb(e)
|
|
}
|
|
}
|
|
}
|