commit
27220b7bcd
@ -1 +1,4 @@ |
|||||||
{ "presets": ["es2015"] } |
{ |
||||||
|
"presets": ["es2015"], |
||||||
|
"plugins": ["transform-runtime"] |
||||||
|
} |
||||||
|
@ -0,0 +1,3 @@ |
|||||||
|
node_modules |
||||||
|
builds |
||||||
|
development |
@ -1 +1,5 @@ |
|||||||
app/scripts/lib/extension-instance.js |
app/scripts/lib/extension-instance.js |
||||||
|
test/integration/bundle.js |
||||||
|
test/integration/jquery-3.1.0.min.js |
||||||
|
test/integration/helpers.js |
||||||
|
test/integration/lib/first-time.js |
@ -0,0 +1,22 @@ |
|||||||
|
FROM node:7 |
||||||
|
MAINTAINER kumavis |
||||||
|
|
||||||
|
# setup app dir |
||||||
|
RUN mkdir -p /www/ |
||||||
|
WORKDIR /www/ |
||||||
|
|
||||||
|
# install dependencies |
||||||
|
COPY ./package.json /www/package.json |
||||||
|
RUN npm install |
||||||
|
|
||||||
|
# copy over app dir |
||||||
|
COPY ./ /www/ |
||||||
|
|
||||||
|
# run tests |
||||||
|
# RUN npm test |
||||||
|
|
||||||
|
# build app |
||||||
|
RUN npm run dist |
||||||
|
|
||||||
|
# start server |
||||||
|
CMD node mascara/example/server.js |
File diff suppressed because one or more lines are too long
@ -1,14 +1,15 @@ |
|||||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' |
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask' |
||||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' |
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' |
||||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL |
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' |
||||||
|
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' |
||||||
|
|
||||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
||||||
|
|
||||||
module.exports = { |
module.exports = { |
||||||
network: { |
network: { |
||||||
default: DEFAULT_RPC_URL, |
|
||||||
mainnet: MAINET_RPC_URL, |
mainnet: MAINET_RPC_URL, |
||||||
testnet: TESTNET_RPC_URL, |
ropsten: ROPSTEN_RPC_URL, |
||||||
morden: TESTNET_RPC_URL, |
kovan: KOVAN_RPC_URL, |
||||||
|
rinkeby: RINKEBY_RPC_URL, |
||||||
}, |
}, |
||||||
} |
} |
||||||
|
@ -0,0 +1,129 @@ |
|||||||
|
const EventEmitter = require('events') |
||||||
|
const MetaMaskProvider = require('web3-provider-engine/zero.js') |
||||||
|
const ObservableStore = require('obs-store') |
||||||
|
const ComposedStore = require('obs-store/lib/composed') |
||||||
|
const extend = require('xtend') |
||||||
|
const EthQuery = require('eth-query') |
||||||
|
const RPC_ADDRESS_LIST = require('../config.js').network |
||||||
|
const DEFAULT_RPC = RPC_ADDRESS_LIST['rinkeby'] |
||||||
|
|
||||||
|
module.exports = class NetworkController extends EventEmitter { |
||||||
|
constructor (config) { |
||||||
|
super() |
||||||
|
this.networkStore = new ObservableStore('loading') |
||||||
|
config.provider.rpcTarget = this.getRpcAddressForType(config.provider.type, config.provider) |
||||||
|
this.providerStore = new ObservableStore(config.provider) |
||||||
|
this.store = new ComposedStore({ provider: this.providerStore, network: this.networkStore }) |
||||||
|
this._providerListeners = {} |
||||||
|
|
||||||
|
this.on('networkDidChange', this.lookupNetwork) |
||||||
|
this.providerStore.subscribe((state) => this.switchNetwork({rpcUrl: state.rpcTarget})) |
||||||
|
} |
||||||
|
|
||||||
|
get provider () { |
||||||
|
return this._proxy |
||||||
|
} |
||||||
|
|
||||||
|
set provider (provider) { |
||||||
|
this._provider = provider |
||||||
|
} |
||||||
|
|
||||||
|
initializeProvider (opts) { |
||||||
|
this.providerInit = opts |
||||||
|
this._provider = MetaMaskProvider(opts) |
||||||
|
this._proxy = new Proxy(this._provider, { |
||||||
|
get: (obj, name) => { |
||||||
|
if (name === 'on') return this._on.bind(this) |
||||||
|
return this._provider[name] |
||||||
|
}, |
||||||
|
set: (obj, name, value) => { |
||||||
|
this._provider[name] = value |
||||||
|
}, |
||||||
|
}) |
||||||
|
this.provider.on('block', this._logBlock.bind(this)) |
||||||
|
this.provider.on('error', this.verifyNetwork.bind(this)) |
||||||
|
this.ethQuery = new EthQuery(this.provider) |
||||||
|
this.lookupNetwork() |
||||||
|
return this.provider |
||||||
|
} |
||||||
|
|
||||||
|
switchNetwork (providerInit) { |
||||||
|
this.setNetworkState('loading') |
||||||
|
const newInit = extend(this.providerInit, providerInit) |
||||||
|
this.providerInit = newInit |
||||||
|
|
||||||
|
this._provider.removeAllListeners() |
||||||
|
this._provider.stop() |
||||||
|
this.provider = MetaMaskProvider(newInit) |
||||||
|
// apply the listners created by other controllers
|
||||||
|
Object.keys(this._providerListeners).forEach((key) => { |
||||||
|
this._providerListeners[key].forEach((handler) => this._provider.addListener(key, handler)) |
||||||
|
}) |
||||||
|
this.emit('networkDidChange') |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
verifyNetwork () { |
||||||
|
// Check network when restoring connectivity:
|
||||||
|
if (this.isNetworkLoading()) this.lookupNetwork() |
||||||
|
} |
||||||
|
|
||||||
|
getNetworkState () { |
||||||
|
return this.networkStore.getState() |
||||||
|
} |
||||||
|
|
||||||
|
setNetworkState (network) { |
||||||
|
return this.networkStore.putState(network) |
||||||
|
} |
||||||
|
|
||||||
|
isNetworkLoading () { |
||||||
|
return this.getNetworkState() === 'loading' |
||||||
|
} |
||||||
|
|
||||||
|
lookupNetwork () { |
||||||
|
this.ethQuery.sendAsync({ method: 'net_version' }, (err, network) => { |
||||||
|
if (err) return this.setNetworkState('loading') |
||||||
|
log.info('web3.getNetwork returned ' + network) |
||||||
|
this.setNetworkState(network) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
setRpcTarget (rpcUrl) { |
||||||
|
this.providerStore.updateState({ |
||||||
|
type: 'rpc', |
||||||
|
rpcTarget: rpcUrl, |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
getCurrentRpcAddress () { |
||||||
|
const provider = this.getProviderConfig() |
||||||
|
if (!provider) return null |
||||||
|
return this.getRpcAddressForType(provider.type) |
||||||
|
} |
||||||
|
|
||||||
|
setProviderType (type) { |
||||||
|
if (type === this.getProviderConfig().type) return |
||||||
|
const rpcTarget = this.getRpcAddressForType(type) |
||||||
|
this.providerStore.updateState({type, rpcTarget}) |
||||||
|
} |
||||||
|
|
||||||
|
getProviderConfig () { |
||||||
|
return this.providerStore.getState() |
||||||
|
} |
||||||
|
|
||||||
|
getRpcAddressForType (type, provider = this.getProviderConfig()) { |
||||||
|
if (RPC_ADDRESS_LIST[type]) return RPC_ADDRESS_LIST[type] |
||||||
|
return provider && provider.rpcTarget ? provider.rpcTarget : DEFAULT_RPC |
||||||
|
} |
||||||
|
|
||||||
|
_logBlock (block) { |
||||||
|
log.info(`BLOCK CHANGED: #${block.number.toString('hex')} 0x${block.hash.toString('hex')}`) |
||||||
|
this.verifyNetwork() |
||||||
|
} |
||||||
|
|
||||||
|
_on (event, handler) { |
||||||
|
if (!this._providerListeners[event]) this._providerListeners[event] = [] |
||||||
|
this._providerListeners[event].push(handler) |
||||||
|
this._provider.on(event, handler) |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
module.exports = getBuyEthUrl |
||||||
|
|
||||||
|
function getBuyEthUrl ({ network, amount, address }) { |
||||||
|
let url |
||||||
|
switch (network) { |
||||||
|
case '1': |
||||||
|
url = `https://buy.coinbase.com/?code=9ec56d01-7e81-5017-930c-513daa27bb6a&amount=${amount}&address=${address}&crypto_currency=ETH` |
||||||
|
break |
||||||
|
|
||||||
|
case '3': |
||||||
|
url = 'https://faucet.metamask.io/' |
||||||
|
break |
||||||
|
|
||||||
|
case '4': |
||||||
|
url = 'https://www.rinkeby.io/' |
||||||
|
break |
||||||
|
|
||||||
|
case '42': |
||||||
|
url = 'https://github.com/kovan-testnet/faucet' |
||||||
|
break |
||||||
|
} |
||||||
|
return url |
||||||
|
} |
@ -1,68 +0,0 @@ |
|||||||
const apis = [ |
|
||||||
'alarms', |
|
||||||
'bookmarks', |
|
||||||
'browserAction', |
|
||||||
'commands', |
|
||||||
'contextMenus', |
|
||||||
'cookies', |
|
||||||
'downloads', |
|
||||||
'events', |
|
||||||
'extension', |
|
||||||
'extensionTypes', |
|
||||||
'history', |
|
||||||
'i18n', |
|
||||||
'idle', |
|
||||||
'notifications', |
|
||||||
'pageAction', |
|
||||||
'runtime', |
|
||||||
'storage', |
|
||||||
'tabs', |
|
||||||
'webNavigation', |
|
||||||
'webRequest', |
|
||||||
'windows', |
|
||||||
] |
|
||||||
|
|
||||||
function Extension () { |
|
||||||
const _this = this |
|
||||||
|
|
||||||
apis.forEach(function (api) { |
|
||||||
|
|
||||||
_this[api] = null |
|
||||||
|
|
||||||
try { |
|
||||||
if (chrome[api]) { |
|
||||||
_this[api] = chrome[api] |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
try { |
|
||||||
if (window[api]) { |
|
||||||
_this[api] = window[api] |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
try { |
|
||||||
if (browser[api]) { |
|
||||||
_this[api] = browser[api] |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
try { |
|
||||||
_this.api = browser.extension[api] |
|
||||||
} catch (e) {} |
|
||||||
}) |
|
||||||
|
|
||||||
try { |
|
||||||
if (browser && browser.runtime) { |
|
||||||
this.runtime = browser.runtime |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
try { |
|
||||||
if (browser && browser.browserAction) { |
|
||||||
this.browserAction = browser.browserAction |
|
||||||
} |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
module.exports = Extension |
|
@ -1,14 +0,0 @@ |
|||||||
/* Extension.js |
|
||||||
* |
|
||||||
* A module for unifying browser differences in the WebExtension API. |
|
||||||
* |
|
||||||
* Initially implemented because Chrome hides all of their WebExtension API |
|
||||||
* behind a global `chrome` variable, but we'd like to start grooming |
|
||||||
* the code-base for cross-browser extension support. |
|
||||||
* |
|
||||||
* You can read more about the WebExtension API here: |
|
||||||
* https://developer.mozilla.org/en-US/Add-ons/WebExtensions
|
|
||||||
*/ |
|
||||||
|
|
||||||
const Extension = require('./extension-instance') |
|
||||||
module.exports = new Extension() |
|
@ -0,0 +1,7 @@ |
|||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
const BN = ethUtil.BN |
||||||
|
|
||||||
|
module.exports = function hexToBn (hex) { |
||||||
|
return new BN(ethUtil.stripHexPrefix(hex), 16) |
||||||
|
} |
||||||
|
|
@ -1,90 +0,0 @@ |
|||||||
/* ID Management |
|
||||||
* |
|
||||||
* This module exists to hold the decrypted credentials for the current session. |
|
||||||
* It therefore exposes sign methods, because it is able to perform these |
|
||||||
* with noa dditional authentication, because its very instantiation |
|
||||||
* means the vault is unlocked. |
|
||||||
*/ |
|
||||||
|
|
||||||
const ethUtil = require('ethereumjs-util') |
|
||||||
const Transaction = require('ethereumjs-tx') |
|
||||||
|
|
||||||
module.exports = IdManagement |
|
||||||
|
|
||||||
function IdManagement (opts) { |
|
||||||
if (!opts) opts = {} |
|
||||||
|
|
||||||
this.keyStore = opts.keyStore |
|
||||||
this.derivedKey = opts.derivedKey |
|
||||||
this.configManager = opts.configManager |
|
||||||
this.hdPathString = "m/44'/60'/0'/0" |
|
||||||
|
|
||||||
this.getAddresses = function () { |
|
||||||
return this.keyStore.getAddresses(this.hdPathString).map(function (address) { return '0x' + address }) |
|
||||||
} |
|
||||||
|
|
||||||
this.signTx = function (txParams) { |
|
||||||
|
|
||||||
// normalize values
|
|
||||||
txParams.gasPrice = ethUtil.intToHex(txParams.gasPrice) |
|
||||||
txParams.to = ethUtil.addHexPrefix(txParams.to) |
|
||||||
txParams.from = ethUtil.addHexPrefix(txParams.from.toLowerCase()) |
|
||||||
txParams.value = ethUtil.addHexPrefix(txParams.value) |
|
||||||
txParams.data = ethUtil.addHexPrefix(txParams.data) |
|
||||||
txParams.gasLimit = ethUtil.addHexPrefix(txParams.gasLimit || txParams.gas) |
|
||||||
txParams.nonce = ethUtil.addHexPrefix(txParams.nonce) |
|
||||||
var tx = new Transaction(txParams) |
|
||||||
|
|
||||||
// sign tx
|
|
||||||
var privKeyHex = this.exportPrivateKey(txParams.from) |
|
||||||
var privKey = ethUtil.toBuffer(privKeyHex) |
|
||||||
tx.sign(privKey) |
|
||||||
|
|
||||||
// Add the tx hash to the persisted meta-tx object
|
|
||||||
var txHash = ethUtil.bufferToHex(tx.hash()) |
|
||||||
var metaTx = this.configManager.getTx(txParams.metamaskId) |
|
||||||
metaTx.hash = txHash |
|
||||||
this.configManager.updateTx(metaTx) |
|
||||||
|
|
||||||
// return raw serialized tx
|
|
||||||
var rawTx = ethUtil.bufferToHex(tx.serialize()) |
|
||||||
return rawTx |
|
||||||
} |
|
||||||
|
|
||||||
this.signMsg = function (address, message) { |
|
||||||
// sign message
|
|
||||||
var privKeyHex = this.exportPrivateKey(address.toLowerCase()) |
|
||||||
var privKey = ethUtil.toBuffer(privKeyHex) |
|
||||||
var msgSig = ethUtil.ecsign(new Buffer(message.replace('0x', ''), 'hex'), privKey) |
|
||||||
var rawMsgSig = ethUtil.bufferToHex(concatSig(msgSig.v, msgSig.r, msgSig.s)) |
|
||||||
return rawMsgSig |
|
||||||
} |
|
||||||
|
|
||||||
this.getSeed = function () { |
|
||||||
return this.keyStore.getSeed(this.derivedKey) |
|
||||||
} |
|
||||||
|
|
||||||
this.exportPrivateKey = function (address) { |
|
||||||
var privKeyHex = ethUtil.addHexPrefix(this.keyStore.exportPrivateKey(address, this.derivedKey, this.hdPathString)) |
|
||||||
return privKeyHex |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function padWithZeroes (number, length) { |
|
||||||
var myString = '' + number |
|
||||||
while (myString.length < length) { |
|
||||||
myString = '0' + myString |
|
||||||
} |
|
||||||
return myString |
|
||||||
} |
|
||||||
|
|
||||||
function concatSig (v, r, s) { |
|
||||||
const rSig = ethUtil.fromSigned(r) |
|
||||||
const sSig = ethUtil.fromSigned(s) |
|
||||||
const vSig = ethUtil.bufferToInt(v) |
|
||||||
const rStr = padWithZeroes(ethUtil.toUnsigned(rSig).toString('hex'), 64) |
|
||||||
const sStr = padWithZeroes(ethUtil.toUnsigned(sSig).toString('hex'), 64) |
|
||||||
const vStr = ethUtil.stripHexPrefix(ethUtil.intToHex(vSig)) |
|
||||||
return ethUtil.addHexPrefix(rStr.concat(sStr, vStr)).toString('hex') |
|
||||||
} |
|
||||||
|
|
@ -1,80 +0,0 @@ |
|||||||
const IdentityStore = require('./idStore') |
|
||||||
const HdKeyring = require('eth-hd-keyring') |
|
||||||
const sigUtil = require('eth-sig-util') |
|
||||||
const normalize = sigUtil.normalize |
|
||||||
const denodeify = require('denodeify') |
|
||||||
|
|
||||||
module.exports = class IdentityStoreMigrator { |
|
||||||
|
|
||||||
constructor ({ configManager }) { |
|
||||||
this.configManager = configManager |
|
||||||
const hasOldVault = this.hasOldVault() |
|
||||||
if (!hasOldVault) { |
|
||||||
this.idStore = new IdentityStore({ configManager }) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
migratedVaultForPassword (password) { |
|
||||||
const hasOldVault = this.hasOldVault() |
|
||||||
const configManager = this.configManager |
|
||||||
|
|
||||||
if (!this.idStore) { |
|
||||||
this.idStore = new IdentityStore({ configManager }) |
|
||||||
} |
|
||||||
|
|
||||||
if (!hasOldVault) { |
|
||||||
return Promise.resolve(null) |
|
||||||
} |
|
||||||
|
|
||||||
const idStore = this.idStore |
|
||||||
const submitPassword = denodeify(idStore.submitPassword.bind(idStore)) |
|
||||||
|
|
||||||
return submitPassword(password) |
|
||||||
.then(() => { |
|
||||||
const serialized = this.serializeVault() |
|
||||||
return this.checkForLostAccounts(serialized) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
serializeVault () { |
|
||||||
const mnemonic = this.idStore._idmgmt.getSeed() |
|
||||||
const numberOfAccounts = this.idStore._getAddresses().length |
|
||||||
|
|
||||||
return { |
|
||||||
type: 'HD Key Tree', |
|
||||||
data: { mnemonic, numberOfAccounts }, |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
checkForLostAccounts (serialized) { |
|
||||||
const hd = new HdKeyring() |
|
||||||
return hd.deserialize(serialized.data) |
|
||||||
.then((hexAccounts) => { |
|
||||||
const newAccounts = hexAccounts.map(normalize) |
|
||||||
const oldAccounts = this.idStore._getAddresses().map(normalize) |
|
||||||
const lostAccounts = oldAccounts.reduce((result, account) => { |
|
||||||
if (newAccounts.includes(account)) { |
|
||||||
return result |
|
||||||
} else { |
|
||||||
result.push(account) |
|
||||||
return result |
|
||||||
} |
|
||||||
}, []) |
|
||||||
|
|
||||||
return { |
|
||||||
serialized, |
|
||||||
lostAccounts: lostAccounts.map((address) => { |
|
||||||
return { |
|
||||||
address, |
|
||||||
privateKey: this.idStore.exportAccount(address), |
|
||||||
} |
|
||||||
}), |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
hasOldVault () { |
|
||||||
const wallet = this.configManager.getWallet() |
|
||||||
return wallet |
|
||||||
} |
|
||||||
} |
|
@ -1,343 +0,0 @@ |
|||||||
const EventEmitter = require('events').EventEmitter |
|
||||||
const inherits = require('util').inherits |
|
||||||
const ethUtil = require('ethereumjs-util') |
|
||||||
const KeyStore = require('eth-lightwallet').keystore |
|
||||||
const clone = require('clone') |
|
||||||
const extend = require('xtend') |
|
||||||
const autoFaucet = require('./auto-faucet') |
|
||||||
const DEFAULT_RPC = 'https://testrpc.metamask.io/' |
|
||||||
const IdManagement = require('./id-management') |
|
||||||
|
|
||||||
|
|
||||||
module.exports = IdentityStore |
|
||||||
|
|
||||||
inherits(IdentityStore, EventEmitter) |
|
||||||
function IdentityStore (opts = {}) { |
|
||||||
EventEmitter.call(this) |
|
||||||
|
|
||||||
// we just use the ethStore to auto-add accounts
|
|
||||||
this._ethStore = opts.ethStore |
|
||||||
this.configManager = opts.configManager |
|
||||||
// lightwallet key store
|
|
||||||
this._keyStore = null |
|
||||||
// lightwallet wrapper
|
|
||||||
this._idmgmt = null |
|
||||||
|
|
||||||
this.hdPathString = "m/44'/60'/0'/0" |
|
||||||
|
|
||||||
this._currentState = { |
|
||||||
selectedAddress: null, |
|
||||||
identities: {}, |
|
||||||
} |
|
||||||
// not part of serilized metamask state - only kept in memory
|
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// public
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype.createNewVault = function (password, cb) { |
|
||||||
delete this._keyStore |
|
||||||
var serializedKeystore = this.configManager.getWallet() |
|
||||||
|
|
||||||
if (serializedKeystore) { |
|
||||||
this.configManager.setData({}) |
|
||||||
} |
|
||||||
|
|
||||||
this.purgeCache() |
|
||||||
this._createVault(password, null, (err) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this._autoFaucet() |
|
||||||
|
|
||||||
this.configManager.setShowSeedWords(true) |
|
||||||
var seedWords = this._idmgmt.getSeed() |
|
||||||
|
|
||||||
this._loadIdentities() |
|
||||||
|
|
||||||
cb(null, seedWords) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.recoverSeed = function (cb) { |
|
||||||
this.configManager.setShowSeedWords(true) |
|
||||||
if (!this._idmgmt) return cb(new Error('Unauthenticated. Please sign in.')) |
|
||||||
var seedWords = this._idmgmt.getSeed() |
|
||||||
cb(null, seedWords) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.recoverFromSeed = function (password, seed, cb) { |
|
||||||
this.purgeCache() |
|
||||||
|
|
||||||
this._createVault(password, seed, (err) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this._loadIdentities() |
|
||||||
cb(null, this.getState()) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setStore = function (store) { |
|
||||||
this._ethStore = store |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.clearSeedWordCache = function (cb) { |
|
||||||
const configManager = this.configManager |
|
||||||
configManager.setShowSeedWords(false) |
|
||||||
cb(null, configManager.getSelectedAccount()) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getState = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
var seedWords = this.getSeedIfUnlocked() |
|
||||||
return clone(extend(this._currentState, { |
|
||||||
isInitialized: !!configManager.getWallet() && !seedWords, |
|
||||||
isUnlocked: this._isUnlocked(), |
|
||||||
seedWords: seedWords, |
|
||||||
selectedAddress: configManager.getSelectedAccount(), |
|
||||||
})) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getSeedIfUnlocked = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
var showSeed = configManager.getShouldShowSeedWords() |
|
||||||
var idmgmt = this._idmgmt |
|
||||||
var shouldShow = showSeed && !!idmgmt |
|
||||||
var seedWords = shouldShow ? idmgmt.getSeed() : null |
|
||||||
return seedWords |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getSelectedAddress = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
return configManager.getSelectedAccount() |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setSelectedAddressSync = function (address) { |
|
||||||
const configManager = this.configManager |
|
||||||
if (!address) { |
|
||||||
var addresses = this._getAddresses() |
|
||||||
address = addresses[0] |
|
||||||
} |
|
||||||
|
|
||||||
configManager.setSelectedAccount(address) |
|
||||||
return address |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setSelectedAddress = function (address, cb) { |
|
||||||
const resultAddress = this.setSelectedAddressSync(address) |
|
||||||
if (cb) return cb(null, resultAddress) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.revealAccount = function (cb) { |
|
||||||
const derivedKey = this._idmgmt.derivedKey |
|
||||||
const keyStore = this._keyStore |
|
||||||
const configManager = this.configManager |
|
||||||
|
|
||||||
keyStore.setDefaultHdDerivationPath(this.hdPathString) |
|
||||||
keyStore.generateNewAddress(derivedKey, 1) |
|
||||||
const addresses = keyStore.getAddresses() |
|
||||||
const address = addresses[ addresses.length - 1 ] |
|
||||||
|
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address)) |
|
||||||
|
|
||||||
configManager.setWallet(keyStore.serialize()) |
|
||||||
|
|
||||||
this._loadIdentities() |
|
||||||
this._didUpdate() |
|
||||||
cb(null) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.getNetwork = function (err) { |
|
||||||
if (err) { |
|
||||||
this._currentState.network = 'loading' |
|
||||||
this._didUpdate() |
|
||||||
} |
|
||||||
|
|
||||||
this.web3.version.getNetwork((err, network) => { |
|
||||||
if (err) { |
|
||||||
this._currentState.network = 'loading' |
|
||||||
return this._didUpdate() |
|
||||||
} |
|
||||||
if (global.METAMASK_DEBUG) { |
|
||||||
console.log('web3.getNetwork returned ' + network) |
|
||||||
} |
|
||||||
this._currentState.network = network |
|
||||||
this._didUpdate() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.setLocked = function (cb) { |
|
||||||
delete this._keyStore |
|
||||||
delete this._idmgmt |
|
||||||
cb() |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.submitPassword = function (password, cb) { |
|
||||||
const configManager = this.configManager |
|
||||||
this.tryPassword(password, (err) => { |
|
||||||
if (err) return cb(err) |
|
||||||
// load identities before returning...
|
|
||||||
this._loadIdentities() |
|
||||||
cb(null, configManager.getSelectedAccount()) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.exportAccount = function (address, cb) { |
|
||||||
var privateKey = this._idmgmt.exportPrivateKey(address) |
|
||||||
if (cb) cb(null, privateKey) |
|
||||||
return privateKey |
|
||||||
} |
|
||||||
|
|
||||||
// private
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype._didUpdate = function () { |
|
||||||
this.emit('update', this.getState()) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._isUnlocked = function () { |
|
||||||
var result = Boolean(this._keyStore) && Boolean(this._idmgmt) |
|
||||||
return result |
|
||||||
} |
|
||||||
|
|
||||||
// load identities from keyStoreet
|
|
||||||
IdentityStore.prototype._loadIdentities = function () { |
|
||||||
const configManager = this.configManager |
|
||||||
if (!this._isUnlocked()) throw new Error('not unlocked') |
|
||||||
|
|
||||||
var addresses = this._getAddresses() |
|
||||||
addresses.forEach((address, i) => { |
|
||||||
// // add to ethStore
|
|
||||||
if (this._ethStore) { |
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(address)) |
|
||||||
} |
|
||||||
// add to identities
|
|
||||||
const defaultLabel = 'Account ' + (i + 1) |
|
||||||
const nickname = configManager.nicknameForWallet(address) |
|
||||||
var identity = { |
|
||||||
name: nickname || defaultLabel, |
|
||||||
address: address, |
|
||||||
mayBeFauceting: this._mayBeFauceting(i), |
|
||||||
} |
|
||||||
this._currentState.identities[address] = identity |
|
||||||
}) |
|
||||||
this._didUpdate() |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.saveAccountLabel = function (account, label, cb) { |
|
||||||
const configManager = this.configManager |
|
||||||
configManager.setNicknameForWallet(account, label) |
|
||||||
this._loadIdentities() |
|
||||||
cb(null, label) |
|
||||||
} |
|
||||||
|
|
||||||
// mayBeFauceting
|
|
||||||
// If on testnet, index 0 may be fauceting.
|
|
||||||
// The UI will have to check the balance to know.
|
|
||||||
// If there is no balance and it mayBeFauceting,
|
|
||||||
// then it is in fact fauceting.
|
|
||||||
IdentityStore.prototype._mayBeFauceting = function (i) { |
|
||||||
const configManager = this.configManager |
|
||||||
var config = configManager.getProvider() |
|
||||||
if (i === 0 && |
|
||||||
config.type === 'rpc' && |
|
||||||
config.rpcTarget === DEFAULT_RPC) { |
|
||||||
return true |
|
||||||
} |
|
||||||
return false |
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// keyStore managment - unlocking + deserialization
|
|
||||||
//
|
|
||||||
|
|
||||||
IdentityStore.prototype.tryPassword = function (password, cb) { |
|
||||||
var serializedKeystore = this.configManager.getWallet() |
|
||||||
var keyStore = KeyStore.deserialize(serializedKeystore) |
|
||||||
|
|
||||||
keyStore.keyFromPassword(password, (err, pwDerivedKey) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
const isCorrect = keyStore.isDerivedKeyCorrect(pwDerivedKey) |
|
||||||
if (!isCorrect) return cb(new Error('Lightwallet - password incorrect')) |
|
||||||
|
|
||||||
this._keyStore = keyStore |
|
||||||
this._createIdMgmt(pwDerivedKey) |
|
||||||
cb() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._createVault = function (password, seedPhrase, cb) { |
|
||||||
const opts = { |
|
||||||
password, |
|
||||||
hdPathString: this.hdPathString, |
|
||||||
} |
|
||||||
|
|
||||||
if (seedPhrase) { |
|
||||||
opts.seedPhrase = seedPhrase |
|
||||||
} |
|
||||||
|
|
||||||
KeyStore.createVault(opts, (err, keyStore) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this._keyStore = keyStore |
|
||||||
|
|
||||||
keyStore.keyFromPassword(password, (err, derivedKey) => { |
|
||||||
if (err) return cb(err) |
|
||||||
|
|
||||||
this.purgeCache() |
|
||||||
|
|
||||||
keyStore.addHdDerivationPath(this.hdPathString, derivedKey, {curve: 'secp256k1', purpose: 'sign'}) |
|
||||||
|
|
||||||
this._createFirstWallet(derivedKey) |
|
||||||
this._createIdMgmt(derivedKey) |
|
||||||
this.setSelectedAddressSync() |
|
||||||
|
|
||||||
cb() |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._createIdMgmt = function (derivedKey) { |
|
||||||
this._idmgmt = new IdManagement({ |
|
||||||
keyStore: this._keyStore, |
|
||||||
derivedKey: derivedKey, |
|
||||||
configManager: this.configManager, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype.purgeCache = function () { |
|
||||||
this._currentState.identities = {} |
|
||||||
let accounts |
|
||||||
try { |
|
||||||
accounts = Object.keys(this._ethStore._currentState.accounts) |
|
||||||
} catch (e) { |
|
||||||
accounts = [] |
|
||||||
} |
|
||||||
accounts.forEach((address) => { |
|
||||||
this._ethStore.removeAccount(address) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._createFirstWallet = function (derivedKey) { |
|
||||||
const keyStore = this._keyStore |
|
||||||
keyStore.setDefaultHdDerivationPath(this.hdPathString) |
|
||||||
keyStore.generateNewAddress(derivedKey, 1) |
|
||||||
this.configManager.setWallet(keyStore.serialize()) |
|
||||||
var addresses = keyStore.getAddresses() |
|
||||||
this._ethStore.addAccount(ethUtil.addHexPrefix(addresses[0])) |
|
||||||
} |
|
||||||
|
|
||||||
// get addresses and normalize address hexString
|
|
||||||
IdentityStore.prototype._getAddresses = function () { |
|
||||||
return this._keyStore.getAddresses(this.hdPathString).map((address) => { |
|
||||||
return ethUtil.addHexPrefix(address) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
IdentityStore.prototype._autoFaucet = function () { |
|
||||||
var addresses = this._getAddresses() |
|
||||||
autoFaucet(addresses[0]) |
|
||||||
} |
|
||||||
|
|
||||||
// util
|
|
@ -0,0 +1,71 @@ |
|||||||
|
const extension = require('extensionizer') |
||||||
|
const height = 520 |
||||||
|
const width = 360 |
||||||
|
|
||||||
|
|
||||||
|
class NotificationManager { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
showPopup () { |
||||||
|
this._getPopup((err, popup) => { |
||||||
|
if (err) throw err |
||||||
|
|
||||||
|
if (popup) { |
||||||
|
// bring focus to existing popup
|
||||||
|
extension.windows.update(popup.id, { focused: true }) |
||||||
|
} else { |
||||||
|
// create new popup
|
||||||
|
extension.windows.create({ |
||||||
|
url: 'notification.html', |
||||||
|
type: 'popup', |
||||||
|
width, |
||||||
|
height, |
||||||
|
}) |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
closePopup () { |
||||||
|
this._getPopup((err, popup) => { |
||||||
|
if (err) throw err |
||||||
|
if (!popup) return |
||||||
|
extension.windows.remove(popup.id, console.error) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// Private
|
||||||
|
//
|
||||||
|
|
||||||
|
_getPopup (cb) { |
||||||
|
this._getWindows((err, windows) => { |
||||||
|
if (err) throw err |
||||||
|
cb(null, this._getPopupIn(windows)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
_getWindows (cb) { |
||||||
|
// Ignore in test environment
|
||||||
|
if (!extension.windows) { |
||||||
|
return cb() |
||||||
|
} |
||||||
|
|
||||||
|
extension.windows.getAll({}, (windows) => { |
||||||
|
cb(null, windows) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
_getPopupIn (windows) { |
||||||
|
return windows ? windows.find((win) => { |
||||||
|
return (win && win.type === 'popup' && |
||||||
|
win.height === height && |
||||||
|
win.width === width) |
||||||
|
}) : null |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = NotificationManager |
@ -1,65 +0,0 @@ |
|||||||
const extension = require('./extension') |
|
||||||
const height = 520 |
|
||||||
const width = 360 |
|
||||||
|
|
||||||
const notifications = { |
|
||||||
show, |
|
||||||
getPopup, |
|
||||||
closePopup, |
|
||||||
} |
|
||||||
module.exports = notifications |
|
||||||
window.METAMASK_NOTIFIER = notifications |
|
||||||
|
|
||||||
function show () { |
|
||||||
getPopup((err, popup) => { |
|
||||||
if (err) throw err |
|
||||||
|
|
||||||
if (popup) { |
|
||||||
// bring focus to existing popup
|
|
||||||
extension.windows.update(popup.id, { focused: true }) |
|
||||||
} else { |
|
||||||
// create new popup
|
|
||||||
extension.windows.create({ |
|
||||||
url: 'notification.html', |
|
||||||
type: 'popup', |
|
||||||
focused: true, |
|
||||||
width, |
|
||||||
height, |
|
||||||
}) |
|
||||||
} |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getWindows (cb) { |
|
||||||
// Ignore in test environment
|
|
||||||
if (!extension.windows) { |
|
||||||
return cb() |
|
||||||
} |
|
||||||
|
|
||||||
extension.windows.getAll({}, (windows) => { |
|
||||||
cb(null, windows) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getPopup (cb) { |
|
||||||
getWindows((err, windows) => { |
|
||||||
if (err) throw err |
|
||||||
cb(null, getPopupIn(windows)) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function getPopupIn (windows) { |
|
||||||
return windows ? windows.find((win) => { |
|
||||||
return (win && win.type === 'popup' && |
|
||||||
win.height === height && |
|
||||||
win.width === width) |
|
||||||
}) : null |
|
||||||
} |
|
||||||
|
|
||||||
function closePopup () { |
|
||||||
getPopup((err, popup) => { |
|
||||||
if (err) throw err |
|
||||||
if (!popup) return |
|
||||||
extension.windows.remove(popup.id, console.error) |
|
||||||
}) |
|
||||||
} |
|
@ -0,0 +1,36 @@ |
|||||||
|
const version = 12 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration modifies our notices to delete their body after being read. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
newState.NoticeController.noticesList.forEach((notice) => { |
||||||
|
if (notice.read) { |
||||||
|
notice.body = '' |
||||||
|
} |
||||||
|
}) |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
const version = 13 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration modifies the network config from ambiguous 'testnet' to explicit 'ropsten' |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
if (newState.config.provider.type === 'testnet') { |
||||||
|
newState.config.provider.type = 'ropsten' |
||||||
|
} |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,34 @@ |
|||||||
|
const version = 14 |
||||||
|
|
||||||
|
/* |
||||||
|
|
||||||
|
This migration removes provider from config and moves it too NetworkController. |
||||||
|
|
||||||
|
*/ |
||||||
|
|
||||||
|
const clone = require('clone') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
version, |
||||||
|
|
||||||
|
migrate: function (originalVersionedData) { |
||||||
|
const versionedData = clone(originalVersionedData) |
||||||
|
versionedData.meta.version = version |
||||||
|
try { |
||||||
|
const state = versionedData.data |
||||||
|
const newState = transformState(state) |
||||||
|
versionedData.data = newState |
||||||
|
} catch (err) { |
||||||
|
console.warn(`MetaMask Migration #${version}` + err.stack) |
||||||
|
} |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
function transformState (state) { |
||||||
|
const newState = state |
||||||
|
newState.NetworkController = {} |
||||||
|
newState.NetworkController.provider = newState.config.provider |
||||||
|
delete newState.config.provider |
||||||
|
return newState |
||||||
|
} |
@ -0,0 +1,23 @@ |
|||||||
|
const extension = require('extensionizer') |
||||||
|
|
||||||
|
class ExtensionPlatform { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () { |
||||||
|
extension.runtime.reload() |
||||||
|
} |
||||||
|
|
||||||
|
openWindow ({ url }) { |
||||||
|
extension.tabs.create({ url }) |
||||||
|
} |
||||||
|
|
||||||
|
getVersion () { |
||||||
|
return extension.runtime.getManifest().version |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = ExtensionPlatform |
@ -0,0 +1,24 @@ |
|||||||
|
|
||||||
|
class SwPlatform { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () { |
||||||
|
// you cant actually do this
|
||||||
|
global.location.reload() |
||||||
|
} |
||||||
|
|
||||||
|
openWindow ({ url }) { |
||||||
|
// this doesnt actually work
|
||||||
|
global.open(url, '_blank') |
||||||
|
} |
||||||
|
|
||||||
|
getVersion () { |
||||||
|
return '<unable to read version>' |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = SwPlatform |
@ -0,0 +1,22 @@ |
|||||||
|
|
||||||
|
class WindowPlatform { |
||||||
|
|
||||||
|
//
|
||||||
|
// Public
|
||||||
|
//
|
||||||
|
|
||||||
|
reload () { |
||||||
|
global.location.reload() |
||||||
|
} |
||||||
|
|
||||||
|
openWindow ({ url }) { |
||||||
|
global.open(url, '_blank') |
||||||
|
} |
||||||
|
|
||||||
|
getVersion () { |
||||||
|
return '<unable to read version>' |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
module.exports = WindowPlatform |
@ -0,0 +1,70 @@ |
|||||||
|
{ |
||||||
|
"metamask": { |
||||||
|
"isInitialized": true, |
||||||
|
"isUnlocked": true, |
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/", |
||||||
|
"identities": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"name": "Account 1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"unapprovedTxs": {}, |
||||||
|
"noActiveNotices": true, |
||||||
|
"frequentRpcList": [], |
||||||
|
"addressBook": [], |
||||||
|
"network": "3", |
||||||
|
"accounts": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"code": "0x", |
||||||
|
"nonce": "0x0", |
||||||
|
"balance": "0x0", |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"transactions": {}, |
||||||
|
"selectedAddressTxList": [], |
||||||
|
"unapprovedMsgs": {}, |
||||||
|
"unapprovedMsgCount": 0, |
||||||
|
"unapprovedPersonalMsgs": {}, |
||||||
|
"unapprovedPersonalMsgCount": 0, |
||||||
|
"keyringTypes": [ |
||||||
|
"Simple Key Pair", |
||||||
|
"HD Key Tree" |
||||||
|
], |
||||||
|
"keyrings": [ |
||||||
|
{ |
||||||
|
"type": "HD Key Tree", |
||||||
|
"accounts": [ |
||||||
|
"07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"currentCurrency": "USD", |
||||||
|
"conversionRate": 43.35903476, |
||||||
|
"conversionDate": 1490105102, |
||||||
|
"provider": { |
||||||
|
"type": "testnet" |
||||||
|
}, |
||||||
|
"shapeShiftTxList": [], |
||||||
|
"lostAccounts": [], |
||||||
|
"seedWords": null |
||||||
|
}, |
||||||
|
"appState": { |
||||||
|
"menuOpen": false, |
||||||
|
"currentView": { |
||||||
|
"name": "accountDetail", |
||||||
|
"context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
}, |
||||||
|
"accountDetail": { |
||||||
|
"subview": "export", |
||||||
|
"accountExport": "completed", |
||||||
|
"privateKey": "549c9638ad06432568969accacad4a02f8548cc358085938071745138ec134b7" |
||||||
|
}, |
||||||
|
"transForward": true, |
||||||
|
"isLoading": false, |
||||||
|
"warning": null |
||||||
|
}, |
||||||
|
"identities": {} |
||||||
|
} |
@ -0,0 +1,69 @@ |
|||||||
|
{ |
||||||
|
"metamask": { |
||||||
|
"isInitialized": true, |
||||||
|
"isUnlocked": true, |
||||||
|
"rpcTarget": "https://rawtestrpc.metamask.io/", |
||||||
|
"identities": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"name": "Account 1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"unapprovedTxs": {}, |
||||||
|
"noActiveNotices": true, |
||||||
|
"frequentRpcList": [], |
||||||
|
"addressBook": [], |
||||||
|
"network": "3", |
||||||
|
"accounts": { |
||||||
|
"0x07284e146926a4facd0ea60598dc4f001ad620f1": { |
||||||
|
"code": "0x", |
||||||
|
"nonce": "0x0", |
||||||
|
"balance": "0x0", |
||||||
|
"address": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
} |
||||||
|
}, |
||||||
|
"transactions": {}, |
||||||
|
"selectedAddressTxList": [], |
||||||
|
"unapprovedMsgs": {}, |
||||||
|
"unapprovedMsgCount": 0, |
||||||
|
"unapprovedPersonalMsgs": {}, |
||||||
|
"unapprovedPersonalMsgCount": 0, |
||||||
|
"keyringTypes": [ |
||||||
|
"Simple Key Pair", |
||||||
|
"HD Key Tree" |
||||||
|
], |
||||||
|
"keyrings": [ |
||||||
|
{ |
||||||
|
"type": "HD Key Tree", |
||||||
|
"accounts": [ |
||||||
|
"07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
] |
||||||
|
} |
||||||
|
], |
||||||
|
"selectedAddress": "0x07284e146926a4facd0ea60598dc4f001ad620f1", |
||||||
|
"currentCurrency": "USD", |
||||||
|
"conversionRate": 43.35903476, |
||||||
|
"conversionDate": 1490105102, |
||||||
|
"provider": { |
||||||
|
"type": "testnet" |
||||||
|
}, |
||||||
|
"shapeShiftTxList": [], |
||||||
|
"lostAccounts": [], |
||||||
|
"seedWords": null |
||||||
|
}, |
||||||
|
"appState": { |
||||||
|
"menuOpen": false, |
||||||
|
"currentView": { |
||||||
|
"name": "accountDetail", |
||||||
|
"context": "0x07284e146926a4facd0ea60598dc4f001ad620f1" |
||||||
|
}, |
||||||
|
"accountDetail": { |
||||||
|
"subview": "export", |
||||||
|
"accountExport": "requested" |
||||||
|
}, |
||||||
|
"transForward": true, |
||||||
|
"isLoading": false, |
||||||
|
"warning": null |
||||||
|
}, |
||||||
|
"identities": {} |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
metamascara: |
||||||
|
build: ./ |
||||||
|
restart: always |
||||||
|
ports: |
||||||
|
- "9001" |
||||||
|
environment: |
||||||
|
MASCARA_ORIGIN: "https://zero.metamask.io" |
||||||
|
VIRTUAL_PORT: "9001" |
||||||
|
VIRTUAL_HOST: "zero.metamask.io" |
||||||
|
LETSENCRYPT_HOST: "zero.metamask.io" |
||||||
|
LETSENCRYPT_EMAIL: "admin@metamask.io" |
@ -0,0 +1,14 @@ |
|||||||
|
## Add Custom Build to Chrome |
||||||
|
|
||||||
|
Open `Settings` > `Extensions`. |
||||||
|
|
||||||
|
Check "Developer mode". |
||||||
|
|
||||||
|
At the top, click `Load Unpacked Extension`. |
||||||
|
|
||||||
|
Navigate to your `metamask-plugin/dist/chrome` folder. |
||||||
|
|
||||||
|
Click `Select`. |
||||||
|
|
||||||
|
You now have the plugin, and can click 'inspect views: background plugin' to view its dev console. |
||||||
|
|
@ -0,0 +1,14 @@ |
|||||||
|
# Add Custom Build to Firefox |
||||||
|
|
||||||
|
Go to the url `about:debugging`. |
||||||
|
|
||||||
|
Click the button `Load Temporary Add-On`. |
||||||
|
|
||||||
|
Select the file `dist/firefox/manifest.json`. |
||||||
|
|
||||||
|
You can optionally enable debugging, and click `Debug`, for a console window that logs all of Metamask's processes to a single console. |
||||||
|
|
||||||
|
If you have problems debugging, try connecting to the IRC channel `#webextensions` on `irc.mozilla.org`. |
||||||
|
|
||||||
|
For longer questions, use the StackOverfow tag `firefox-addons`. |
||||||
|
|
@ -0,0 +1,25 @@ |
|||||||
|
## Adding Custom Networks |
||||||
|
|
||||||
|
To add another network to our dropdown menu, make sure the following files are adjusted properly: |
||||||
|
|
||||||
|
``` |
||||||
|
app/scripts/config.js |
||||||
|
app/scripts/lib/buy-eth-url.js |
||||||
|
app/scripts/lib/config-manager.js |
||||||
|
ui/app/app.js |
||||||
|
ui/app/components/buy-button-subview.js |
||||||
|
ui/app/components/drop-menu-item.js |
||||||
|
ui/app/components/network.js |
||||||
|
ui/app/components/transaction-list-item.js |
||||||
|
ui/app/config.js |
||||||
|
ui/app/css/lib.css |
||||||
|
ui/lib/account-link.js |
||||||
|
ui/lib/explorer-link.js |
||||||
|
``` |
||||||
|
|
||||||
|
You will need: |
||||||
|
+ The network ID |
||||||
|
+ An RPC Endpoint url |
||||||
|
+ An explorer link |
||||||
|
+ CSS for the display icon |
||||||
|
|
@ -0,0 +1,10 @@ |
|||||||
|
### Developing on Dependencies |
||||||
|
|
||||||
|
To enjoy the live-reloading that `gulp dev` offers while working on the `web3-provider-engine` or other dependencies: |
||||||
|
|
||||||
|
1. Clone the dependency locally. |
||||||
|
2. `npm install` in its folder. |
||||||
|
3. Run `npm link` in its folder. |
||||||
|
4. Run `npm link $DEP_NAME` in this project folder. |
||||||
|
5. Next time you `npm start` it will watch the dependency for changes as well! |
||||||
|
|
@ -0,0 +1,35 @@ |
|||||||
|
### Generate Development Visualization |
||||||
|
|
||||||
|
This will generate a video of the repo commit history. |
||||||
|
|
||||||
|
Install preqs: |
||||||
|
``` |
||||||
|
brew install gource |
||||||
|
brew install ffmpeg |
||||||
|
``` |
||||||
|
|
||||||
|
From the repo dir, pipe `gource` into `ffmpeg`: |
||||||
|
``` |
||||||
|
gource \ |
||||||
|
--seconds-per-day .1 \ |
||||||
|
--user-scale 1.5 \ |
||||||
|
--default-user-image "./images/icon-512.png" \ |
||||||
|
--viewport 1280x720 \ |
||||||
|
--auto-skip-seconds .1 \ |
||||||
|
--multi-sampling \ |
||||||
|
--stop-at-end \ |
||||||
|
--highlight-users \ |
||||||
|
--hide mouse,progress \ |
||||||
|
--file-idle-time 0 \ |
||||||
|
--max-files 0 \ |
||||||
|
--background-colour 000000 \ |
||||||
|
--font-size 18 \ |
||||||
|
--date-format "%b %d, %Y" \ |
||||||
|
--highlight-dirs \ |
||||||
|
--user-friction 0.1 \ |
||||||
|
--title "MetaMask Development History" \ |
||||||
|
--output-ppm-stream - \ |
||||||
|
--output-framerate 30 \ |
||||||
|
| ffmpeg -y -r 30 -f image2pipe -vcodec ppm -i - -b 65536K metamask-dev-history.mp4 |
||||||
|
``` |
||||||
|
|
@ -0,0 +1,15 @@ |
|||||||
|
## Generating Notices |
||||||
|
|
||||||
|
To add a notice: |
||||||
|
``` |
||||||
|
npm run generateNotice |
||||||
|
``` |
||||||
|
Enter the body of your notice into the text editor that pops up, without including the body. Be sure to save the file before closing the window! |
||||||
|
Afterwards, enter the title of the notice in the command line and press enter. Afterwards, add and commit the new changes made. |
||||||
|
|
||||||
|
To delete a notice: |
||||||
|
``` |
||||||
|
npm run deleteNotice |
||||||
|
``` |
||||||
|
A list of active notices will pop up. Enter the corresponding id in the command line prompt and add and commit the new changes afterwards. |
||||||
|
|
@ -0,0 +1,19 @@ |
|||||||
|
# Publishing Guide |
||||||
|
|
||||||
|
When publishing a new version of MetaMask, we follow this procedure: |
||||||
|
|
||||||
|
## Incrementing Version & Changelog |
||||||
|
|
||||||
|
You must be authorized already on the MetaMask plugin. |
||||||
|
|
||||||
|
1. Update the version in `app/manifest.json` and the Changelog in `CHANGELOG.md`. |
||||||
|
2. Visit [the chrome developer dashboard](https://chrome.google.com/webstore/developer/dashboard?authuser=2). |
||||||
|
|
||||||
|
## Publishing |
||||||
|
|
||||||
|
1. `npm run dist` to generate the latest build. |
||||||
|
2. Publish to chrome store. |
||||||
|
3. Publish to firefox addon marketplace. |
||||||
|
4. Post on Github releases page. |
||||||
|
5. `npm run announce`, post that announcement in our public places. |
||||||
|
|
@ -0,0 +1,6 @@ |
|||||||
|
# Running UI Dev Mode |
||||||
|
|
||||||
|
You can run `npm run ui`, and your browser should open a live-reloading demo version of the plugin UI. |
||||||
|
|
||||||
|
Some actions will crash the app, so this is only for tuning aesthetics, but it allows live-reloading styles, which is a much faster feedback loop than reloading the full extension. |
||||||
|
|
@ -0,0 +1,8 @@ |
|||||||
|
### Developing on UI with Mocked Background Process |
||||||
|
|
||||||
|
You can run `npm run mock` and your browser should open a live-reloading demo version of the plugin UI, just like the `npm run ui`, except that it tries to actually perform all normal operations. |
||||||
|
|
||||||
|
It does not yet connect to a real blockchain (this could be a good test feature later, connecting to a test blockchain), so only local operations work. |
||||||
|
|
||||||
|
You can reset the mock ui at any time with the `Reset` button at the top of the screen. |
||||||
|
|
@ -1,24 +0,0 @@ |
|||||||
start the dual servers (dapp + mascara) |
|
||||||
``` |
|
||||||
node server.js |
|
||||||
``` |
|
||||||
|
|
||||||
open the example dapp at `http://localhost:9002/` |
|
||||||
|
|
||||||
*You will need to build MetaMask in order for this to work* |
|
||||||
``` |
|
||||||
gulp dev |
|
||||||
``` |
|
||||||
to build MetaMask and have it live reload if you make changes |
|
||||||
|
|
||||||
|
|
||||||
## First time use: |
|
||||||
|
|
||||||
- navigate to: http://127.0.0.1:9001/popup/popup.html |
|
||||||
- Create an Account |
|
||||||
- go back to http://localhost:9002/ |
|
||||||
- open devTools |
|
||||||
- click Sync Tx |
|
||||||
|
|
||||||
### Todos |
|
||||||
- Look into using [Service Workers](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API) |
|
@ -1,159 +0,0 @@ |
|||||||
const urlUtil = require('url') |
|
||||||
const extend = require('xtend') |
|
||||||
const Dnode = require('dnode') |
|
||||||
const eos = require('end-of-stream') |
|
||||||
const ParentStream = require('iframe-stream').ParentStream |
|
||||||
const PortStream = require('../app/scripts/lib/port-stream.js') |
|
||||||
const notification = require('../app/scripts/lib/notifications.js') |
|
||||||
const messageManager = require('../app/scripts/lib/message-manager') |
|
||||||
const setupMultiplex = require('../app/scripts/lib/stream-utils.js').setupMultiplex |
|
||||||
const MetamaskController = require('../app/scripts/metamask-controller') |
|
||||||
const extension = require('../app/scripts/lib/extension') |
|
||||||
|
|
||||||
const STORAGE_KEY = 'metamask-config' |
|
||||||
|
|
||||||
|
|
||||||
initializeZeroClient() |
|
||||||
|
|
||||||
function initializeZeroClient() { |
|
||||||
|
|
||||||
const controller = new MetamaskController({ |
|
||||||
// User confirmation callbacks:
|
|
||||||
showUnconfirmedMessage, |
|
||||||
unlockAccountMessage, |
|
||||||
showUnapprovedTx, |
|
||||||
// Persistence Methods:
|
|
||||||
setData, |
|
||||||
loadData, |
|
||||||
}) |
|
||||||
const idStore = controller.idStore |
|
||||||
|
|
||||||
function unlockAccountMessage () { |
|
||||||
console.log('notif stub - unlockAccountMessage') |
|
||||||
} |
|
||||||
|
|
||||||
function showUnconfirmedMessage (msgParams, msgId) { |
|
||||||
console.log('notif stub - showUnconfirmedMessage') |
|
||||||
} |
|
||||||
|
|
||||||
function showUnapprovedTx (txParams, txData, onTxDoneCb) { |
|
||||||
console.log('notif stub - showUnapprovedTx') |
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// connect to other contexts
|
|
||||||
//
|
|
||||||
|
|
||||||
var connectionStream = new ParentStream() |
|
||||||
|
|
||||||
connectRemote(connectionStream, getParentHref()) |
|
||||||
|
|
||||||
function connectRemote (connectionStream, originDomain) { |
|
||||||
var isMetaMaskInternalProcess = (originDomain === '127.0.0.1:9001') |
|
||||||
if (isMetaMaskInternalProcess) { |
|
||||||
// communication with popup
|
|
||||||
setupTrustedCommunication(connectionStream, 'MetaMask') |
|
||||||
} else { |
|
||||||
// communication with page
|
|
||||||
setupUntrustedCommunication(connectionStream, originDomain) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function setupUntrustedCommunication (connectionStream, originDomain) { |
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream) |
|
||||||
// connect features
|
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
|
||||||
controller.setupPublicConfig(mx.createStream('publicConfig')) |
|
||||||
} |
|
||||||
|
|
||||||
function setupTrustedCommunication (connectionStream, originDomain) { |
|
||||||
// setup multiplexing
|
|
||||||
var mx = setupMultiplex(connectionStream) |
|
||||||
// connect features
|
|
||||||
setupControllerConnection(mx.createStream('controller')) |
|
||||||
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
|
||||||
} |
|
||||||
|
|
||||||
//
|
|
||||||
// remote features
|
|
||||||
//
|
|
||||||
|
|
||||||
function setupControllerConnection (stream) { |
|
||||||
controller.stream = stream |
|
||||||
var api = controller.getApi() |
|
||||||
var dnode = Dnode(api) |
|
||||||
stream.pipe(dnode).pipe(stream) |
|
||||||
dnode.on('remote', (remote) => { |
|
||||||
// push updates to popup
|
|
||||||
controller.ethStore.on('update', controller.sendUpdate.bind(controller)) |
|
||||||
controller.listeners.push(remote) |
|
||||||
idStore.on('update', controller.sendUpdate.bind(controller)) |
|
||||||
|
|
||||||
// teardown on disconnect
|
|
||||||
eos(stream, () => { |
|
||||||
controller.ethStore.removeListener('update', controller.sendUpdate.bind(controller)) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
function loadData () { |
|
||||||
var oldData = getOldStyleData() |
|
||||||
var newData |
|
||||||
try { |
|
||||||
newData = JSON.parse(window.localStorage[STORAGE_KEY]) |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
var data = extend({ |
|
||||||
meta: { |
|
||||||
version: 0, |
|
||||||
}, |
|
||||||
data: { |
|
||||||
config: { |
|
||||||
provider: { |
|
||||||
type: 'testnet', |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, |
|
||||||
}, oldData || null, newData || null) |
|
||||||
return data |
|
||||||
} |
|
||||||
|
|
||||||
function getOldStyleData () { |
|
||||||
var config, wallet, seedWords |
|
||||||
|
|
||||||
var result = { |
|
||||||
meta: { version: 0 }, |
|
||||||
data: {}, |
|
||||||
} |
|
||||||
|
|
||||||
try { |
|
||||||
config = JSON.parse(window.localStorage['config']) |
|
||||||
result.data.config = config |
|
||||||
} catch (e) {} |
|
||||||
try { |
|
||||||
wallet = JSON.parse(window.localStorage['lightwallet']) |
|
||||||
result.data.wallet = wallet |
|
||||||
} catch (e) {} |
|
||||||
try { |
|
||||||
seedWords = window.localStorage['seedWords'] |
|
||||||
result.data.seedWords = seedWords |
|
||||||
} catch (e) {} |
|
||||||
|
|
||||||
return result |
|
||||||
} |
|
||||||
|
|
||||||
function setData (data) { |
|
||||||
window.localStorage[STORAGE_KEY] = JSON.stringify(data) |
|
||||||
} |
|
||||||
|
|
||||||
function getParentHref(){ |
|
||||||
try { |
|
||||||
var parentLocation = window.parent.location |
|
||||||
return parentLocation.hostname + ':' + parentLocation.port |
|
||||||
} catch (err) { |
|
||||||
return 'unknown' |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -1,43 +0,0 @@ |
|||||||
const Web3 = require('web3') |
|
||||||
const setupProvider = require('./lib/setup-provider.js') |
|
||||||
|
|
||||||
//
|
|
||||||
// setup web3
|
|
||||||
//
|
|
||||||
var provider = setupProvider() |
|
||||||
hijackProvider(provider) |
|
||||||
var web3 = new Web3(provider) |
|
||||||
web3.setProvider = function(){ |
|
||||||
console.log('MetaMask - overrode web3.setProvider') |
|
||||||
} |
|
||||||
console.log('metamask lib hijacked provider') |
|
||||||
|
|
||||||
//
|
|
||||||
// export web3
|
|
||||||
//
|
|
||||||
|
|
||||||
global.web3 = web3 |
|
||||||
|
|
||||||
//
|
|
||||||
// ui stuff
|
|
||||||
//
|
|
||||||
|
|
||||||
var shouldPop = false |
|
||||||
window.addEventListener('click', function(){ |
|
||||||
if (!shouldPop) return |
|
||||||
shouldPop = false |
|
||||||
window.open('http://127.0.0.1:9001/popup/popup.html', '', 'width=360 height=500') |
|
||||||
console.log('opening window...') |
|
||||||
}) |
|
||||||
|
|
||||||
|
|
||||||
function hijackProvider(provider){ |
|
||||||
var _super = provider.sendAsync.bind(provider) |
|
||||||
provider.sendAsync = function(payload, cb){ |
|
||||||
if (payload.method === 'eth_sendTransaction') { |
|
||||||
console.log('saw send') |
|
||||||
shouldPop = true |
|
||||||
} |
|
||||||
_super(payload, cb) |
|
||||||
} |
|
||||||
} |
|
@ -1,19 +0,0 @@ |
|||||||
const injectCss = require('inject-css') |
|
||||||
const MetaMaskUiCss = require('../ui/css') |
|
||||||
const startPopup = require('../app/scripts/popup-core') |
|
||||||
const setupIframe = require('./lib/setup-iframe.js') |
|
||||||
|
|
||||||
|
|
||||||
var css = MetaMaskUiCss() |
|
||||||
injectCss(css) |
|
||||||
|
|
||||||
var name = 'popup' |
|
||||||
window.METAMASK_UI_TYPE = name |
|
||||||
|
|
||||||
var iframeStream = setupIframe({ |
|
||||||
zeroClientProvider: 'http://127.0.0.1:9001', |
|
||||||
sandboxAttributes: ['allow-scripts', 'allow-popups', 'allow-same-origin'], |
|
||||||
container: document.body, |
|
||||||
}) |
|
||||||
|
|
||||||
startPopup(iframeStream) |
|
@ -1,96 +0,0 @@ |
|||||||
const express = require('express') |
|
||||||
const browserify = require('browserify') |
|
||||||
const watchify = require('watchify') |
|
||||||
const babelify = require('babelify') |
|
||||||
|
|
||||||
const zeroBundle = createBundle('./index.js') |
|
||||||
const controllerBundle = createBundle('./controller.js') |
|
||||||
const popupBundle = createBundle('./popup.js') |
|
||||||
const appBundle = createBundle('./example/index.js') |
|
||||||
|
|
||||||
//
|
|
||||||
// Iframe Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const iframeServer = express() |
|
||||||
|
|
||||||
// serve popup window
|
|
||||||
iframeServer.get('/popup/scripts/popup.js', function(req, res){ |
|
||||||
res.send(popupBundle.latest) |
|
||||||
}) |
|
||||||
iframeServer.use('/popup', express.static('../dist/chrome')) |
|
||||||
|
|
||||||
// serve controller bundle
|
|
||||||
iframeServer.get('/controller.js', function(req, res){ |
|
||||||
res.send(controllerBundle.latest) |
|
||||||
}) |
|
||||||
|
|
||||||
// serve background controller
|
|
||||||
iframeServer.use(express.static('./server')) |
|
||||||
|
|
||||||
// start the server
|
|
||||||
const mascaraPort = 9001 |
|
||||||
iframeServer.listen(mascaraPort) |
|
||||||
console.log(`Mascara service listening on port ${mascaraPort}`) |
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Dapp Server
|
|
||||||
//
|
|
||||||
|
|
||||||
const dappServer = express() |
|
||||||
|
|
||||||
// serve metamask-lib bundle
|
|
||||||
dappServer.get('/zero.js', function(req, res){ |
|
||||||
res.send(zeroBundle.latest) |
|
||||||
}) |
|
||||||
|
|
||||||
// serve dapp bundle
|
|
||||||
dappServer.get('/app.js', function(req, res){ |
|
||||||
res.send(appBundle.latest) |
|
||||||
}) |
|
||||||
|
|
||||||
// serve static
|
|
||||||
dappServer.use(express.static('./example')) |
|
||||||
|
|
||||||
// start the server
|
|
||||||
const dappPort = '9002' |
|
||||||
dappServer.listen(dappPort) |
|
||||||
console.log(`Dapp listening on port ${dappPort}`) |
|
||||||
|
|
||||||
//
|
|
||||||
// util
|
|
||||||
//
|
|
||||||
|
|
||||||
function serveBundle(entryPoint){ |
|
||||||
const bundle = createBundle(entryPoint) |
|
||||||
return function(req, res){ |
|
||||||
res.send(bundle.latest) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
function createBundle(entryPoint){ |
|
||||||
|
|
||||||
var bundleContainer = {} |
|
||||||
|
|
||||||
var bundler = browserify({ |
|
||||||
entries: [entryPoint], |
|
||||||
cache: {}, |
|
||||||
packageCache: {}, |
|
||||||
plugin: [watchify], |
|
||||||
}) |
|
||||||
|
|
||||||
bundler.on('update', bundle) |
|
||||||
bundle() |
|
||||||
|
|
||||||
return bundleContainer |
|
||||||
|
|
||||||
function bundle() { |
|
||||||
bundler.bundle(function(err, result){ |
|
||||||
if (err) throw err |
|
||||||
console.log(`Bundle updated! (${entryPoint})`) |
|
||||||
bundleContainer.latest = result.toString() |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
@ -0,0 +1,33 @@ |
|||||||
|
start the dual servers (dapp + mascara) |
||||||
|
``` |
||||||
|
npm run mascara |
||||||
|
``` |
||||||
|
|
||||||
|
### First time use: |
||||||
|
|
||||||
|
- navigate to: http://localhost:9001 |
||||||
|
- Create an Account |
||||||
|
- go back to http://localhost:9002 |
||||||
|
- open devTools |
||||||
|
- click Sync Tx |
||||||
|
|
||||||
|
### Tests: |
||||||
|
|
||||||
|
``` |
||||||
|
npm run testMascara |
||||||
|
``` |
||||||
|
|
||||||
|
Test will run in browser, you will have to have these browsers installed: |
||||||
|
|
||||||
|
- Chrome |
||||||
|
- Firefox |
||||||
|
- Opera |
||||||
|
|
||||||
|
|
||||||
|
### Deploy: |
||||||
|
|
||||||
|
Will build and deploy mascara via docker |
||||||
|
|
||||||
|
``` |
||||||
|
docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f |
||||||
|
``` |
@ -0,0 +1,31 @@ |
|||||||
|
const express = require('express') |
||||||
|
const createMetamascaraServer = require('../server/') |
||||||
|
const createBundle = require('../server/util').createBundle |
||||||
|
const serveBundle = require('../server/util').serveBundle |
||||||
|
|
||||||
|
//
|
||||||
|
// Iframe Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const mascaraServer = createMetamascaraServer() |
||||||
|
|
||||||
|
// start the server
|
||||||
|
const mascaraPort = 9001 |
||||||
|
mascaraServer.listen(mascaraPort) |
||||||
|
console.log(`Mascara service listening on port ${mascaraPort}`) |
||||||
|
|
||||||
|
|
||||||
|
//
|
||||||
|
// Dapp Server
|
||||||
|
//
|
||||||
|
|
||||||
|
const dappServer = express() |
||||||
|
|
||||||
|
// serve dapp bundle
|
||||||
|
serveBundle(dappServer, '/app.js', createBundle(require.resolve('./app.js'))) |
||||||
|
dappServer.use(express.static(__dirname + '/app/')) |
||||||
|
|
||||||
|
// start the server
|
||||||
|
const dappPort = '9002' |
||||||
|
dappServer.listen(dappPort) |
||||||
|
console.log(`Dapp listening on port ${dappPort}`) |
@ -0,0 +1,32 @@ |
|||||||
|
const express = require('express') |
||||||
|
const createBundle = require('./util').createBundle |
||||||
|
const serveBundle = require('./util').serveBundle |
||||||
|
|
||||||
|
module.exports = createMetamascaraServer |
||||||
|
|
||||||
|
|
||||||
|
function createMetamascaraServer(){ |
||||||
|
|
||||||
|
// start bundlers
|
||||||
|
const metamascaraBundle = createBundle(__dirname + '/../src/mascara.js') |
||||||
|
const proxyBundle = createBundle(__dirname + '/../src/proxy.js') |
||||||
|
const uiBundle = createBundle(__dirname + '/../src/ui.js') |
||||||
|
const backgroundBuild = createBundle(__dirname + '/../src/background.js') |
||||||
|
|
||||||
|
// serve bundles
|
||||||
|
const server = express() |
||||||
|
// ui window
|
||||||
|
serveBundle(server, '/ui.js', uiBundle) |
||||||
|
server.use(express.static(__dirname+'/../ui/')) |
||||||
|
server.use(express.static(__dirname+'/../../dist/chrome')) |
||||||
|
// metamascara
|
||||||
|
serveBundle(server, '/metamascara.js', metamascaraBundle) |
||||||
|
// proxy
|
||||||
|
serveBundle(server, '/proxy/proxy.js', proxyBundle) |
||||||
|
server.use('/proxy/', express.static(__dirname+'/../proxy')) |
||||||
|
// background
|
||||||
|
serveBundle(server, '/background.js', backgroundBuild) |
||||||
|
|
||||||
|
return server |
||||||
|
|
||||||
|
} |
@ -0,0 +1,45 @@ |
|||||||
|
const browserify = require('browserify') |
||||||
|
const watchify = require('watchify') |
||||||
|
|
||||||
|
module.exports = { |
||||||
|
serveBundle, |
||||||
|
createBundle, |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
function serveBundle(server, path, bundle){ |
||||||
|
server.get(path, function(req, res){ |
||||||
|
res.setHeader('Content-Type', 'application/javascript; charset=UTF-8') |
||||||
|
res.send(bundle.latest) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
function createBundle(entryPoint){ |
||||||
|
|
||||||
|
var bundleContainer = {} |
||||||
|
|
||||||
|
var bundler = browserify({ |
||||||
|
entries: [entryPoint], |
||||||
|
cache: {}, |
||||||
|
packageCache: {}, |
||||||
|
plugin: [watchify], |
||||||
|
}) |
||||||
|
|
||||||
|
bundler.on('update', bundle) |
||||||
|
bundle() |
||||||
|
|
||||||
|
return bundleContainer |
||||||
|
|
||||||
|
function bundle() { |
||||||
|
bundler.bundle(function(err, result){ |
||||||
|
if (err) { |
||||||
|
console.log(`Bundle failed! (${entryPoint})`) |
||||||
|
console.error(err) |
||||||
|
return |
||||||
|
} |
||||||
|
console.log(`Bundle updated! (${entryPoint})`) |
||||||
|
bundleContainer.latest = result.toString() |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,155 @@ |
|||||||
|
global.window = global |
||||||
|
const self = global |
||||||
|
const pipe = require('pump') |
||||||
|
|
||||||
|
const SwGlobalListener = require('sw-stream/lib/sw-global-listener.js') |
||||||
|
const connectionListener = new SwGlobalListener(self) |
||||||
|
const setupMultiplex = require('../../app/scripts/lib/stream-utils.js').setupMultiplex |
||||||
|
const PortStream = require('../../app/scripts/lib/port-stream.js') |
||||||
|
|
||||||
|
const DbController = require('idb-global') |
||||||
|
|
||||||
|
const SwPlatform = require('../../app/scripts/platforms/sw') |
||||||
|
const MetamaskController = require('../../app/scripts/metamask-controller') |
||||||
|
const extension = {} //require('../../app/scripts/lib/extension')
|
||||||
|
|
||||||
|
const storeTransform = require('obs-store/lib/transform') |
||||||
|
const Migrator = require('../../app/scripts/lib/migrator/') |
||||||
|
const migrations = require('../../app/scripts/migrations/') |
||||||
|
const firstTimeState = require('../../app/scripts/first-time-state') |
||||||
|
|
||||||
|
const STORAGE_KEY = 'metamask-config' |
||||||
|
// const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||||
|
const METAMASK_DEBUG = true |
||||||
|
let popupIsOpen = false |
||||||
|
let connectedClientCount = 0 |
||||||
|
|
||||||
|
const log = require('loglevel') |
||||||
|
global.log = log |
||||||
|
log.setDefaultLevel(METAMASK_DEBUG ? 'debug' : 'warn') |
||||||
|
|
||||||
|
self.addEventListener('install', function(event) { |
||||||
|
event.waitUntil(self.skipWaiting()) |
||||||
|
}) |
||||||
|
self.addEventListener('activate', function(event) { |
||||||
|
event.waitUntil(self.clients.claim()) |
||||||
|
}) |
||||||
|
|
||||||
|
console.log('inside:open') |
||||||
|
|
||||||
|
|
||||||
|
// // state persistence
|
||||||
|
let diskStore |
||||||
|
const dbController = new DbController({ |
||||||
|
key: STORAGE_KEY, |
||||||
|
}) |
||||||
|
loadStateFromPersistence() |
||||||
|
.then((initState) => setupController(initState)) |
||||||
|
.then(() => console.log('MetaMask initialization complete.')) |
||||||
|
.catch((err) => console.error('WHILE SETTING UP:', err)) |
||||||
|
|
||||||
|
// initialization flow
|
||||||
|
|
||||||
|
//
|
||||||
|
// State and Persistence
|
||||||
|
//
|
||||||
|
function loadStateFromPersistence() { |
||||||
|
// migrations
|
||||||
|
let migrator = new Migrator({ migrations }) |
||||||
|
const initialState = migrator.generateInitialState(firstTimeState) |
||||||
|
dbController.initialState = initialState |
||||||
|
return dbController.open() |
||||||
|
.then((versionedData) => migrator.migrateData(versionedData)) |
||||||
|
.then((versionedData) => { |
||||||
|
dbController.put(versionedData) |
||||||
|
return Promise.resolve(versionedData) |
||||||
|
}) |
||||||
|
.then((versionedData) => Promise.resolve(versionedData.data)) |
||||||
|
} |
||||||
|
|
||||||
|
function setupController (initState, client) { |
||||||
|
|
||||||
|
//
|
||||||
|
// MetaMask Controller
|
||||||
|
//
|
||||||
|
|
||||||
|
const platform = new SwPlatform() |
||||||
|
|
||||||
|
const controller = new MetamaskController({ |
||||||
|
// platform specific implementation
|
||||||
|
platform, |
||||||
|
// User confirmation callbacks:
|
||||||
|
showUnconfirmedMessage: noop, |
||||||
|
unlockAccountMessage: noop, |
||||||
|
showUnapprovedTx: noop, |
||||||
|
// initial state
|
||||||
|
initState, |
||||||
|
}) |
||||||
|
global.metamaskController = controller |
||||||
|
|
||||||
|
controller.store.subscribe((state) => { |
||||||
|
versionifyData(state) |
||||||
|
.then((versionedData) => dbController.put(versionedData)) |
||||||
|
.catch((err) => {console.error(err)}) |
||||||
|
}) |
||||||
|
function versionifyData(state) { |
||||||
|
return dbController.get() |
||||||
|
.then((rawData) => { |
||||||
|
return Promise.resolve({ |
||||||
|
data: state, |
||||||
|
meta: rawData.meta, |
||||||
|
})} |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
//
|
||||||
|
// connect to other contexts
|
||||||
|
//
|
||||||
|
|
||||||
|
connectionListener.on('remote', (portStream, messageEvent) => { |
||||||
|
console.log('REMOTE CONECTION FOUND***********') |
||||||
|
connectedClientCount += 1 |
||||||
|
connectRemote(portStream, messageEvent.data.context) |
||||||
|
}) |
||||||
|
|
||||||
|
function connectRemote (connectionStream, context) { |
||||||
|
var isMetaMaskInternalProcess = (context === 'popup') |
||||||
|
if (isMetaMaskInternalProcess) { |
||||||
|
// communication with popup
|
||||||
|
controller.setupTrustedCommunication(connectionStream, 'MetaMask') |
||||||
|
popupIsOpen = true |
||||||
|
} else { |
||||||
|
// communication with page
|
||||||
|
setupUntrustedCommunication(connectionStream, context) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function setupUntrustedCommunication (connectionStream, originDomain) { |
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream) |
||||||
|
// connect features
|
||||||
|
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
||||||
|
controller.setupPublicConfig(mx.createStream('publicConfig')) |
||||||
|
} |
||||||
|
|
||||||
|
function setupTrustedCommunication (connectionStream, originDomain) { |
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(connectionStream) |
||||||
|
// connect features
|
||||||
|
controller.setupProviderConnection(mx.createStream('provider'), originDomain) |
||||||
|
} |
||||||
|
//
|
||||||
|
// User Interface setup
|
||||||
|
//
|
||||||
|
return Promise.resolve() |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
function sendMessageToAllClients (message) { |
||||||
|
self.clients.matchAll().then(function(clients) { |
||||||
|
clients.forEach(function(client) { |
||||||
|
client.postMessage(message) |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
function noop () {} |
@ -0,0 +1,47 @@ |
|||||||
|
const Web3 = require('web3') |
||||||
|
const setupProvider = require('./lib/setup-provider.js') |
||||||
|
const setupDappAutoReload = require('../../app/scripts/lib/auto-reload.js') |
||||||
|
const MASCARA_ORIGIN = process.env.MASCARA_ORIGIN || 'http://localhost:9001' |
||||||
|
console.log('MASCARA_ORIGIN:', MASCARA_ORIGIN) |
||||||
|
|
||||||
|
//
|
||||||
|
// setup web3
|
||||||
|
//
|
||||||
|
|
||||||
|
const provider = setupProvider({ |
||||||
|
mascaraUrl: MASCARA_ORIGIN + '/proxy/', |
||||||
|
}) |
||||||
|
instrumentForUserInteractionTriggers(provider) |
||||||
|
|
||||||
|
const web3 = new Web3(provider) |
||||||
|
setupDappAutoReload(web3, provider.publicConfigStore) |
||||||
|
//
|
||||||
|
// ui stuff
|
||||||
|
//
|
||||||
|
|
||||||
|
let shouldPop = false |
||||||
|
window.addEventListener('click', maybeTriggerPopup) |
||||||
|
|
||||||
|
//
|
||||||
|
// util
|
||||||
|
//
|
||||||
|
|
||||||
|
function maybeTriggerPopup(){ |
||||||
|
if (!shouldPop) return |
||||||
|
shouldPop = false |
||||||
|
window.open(MASCARA_ORIGIN, '', 'width=360 height=500') |
||||||
|
console.log('opening window...') |
||||||
|
} |
||||||
|
|
||||||
|
function instrumentForUserInteractionTriggers(provider){ |
||||||
|
const _super = provider.sendAsync.bind(provider) |
||||||
|
provider.sendAsync = function(payload, cb){ |
||||||
|
if (payload.method === 'eth_sendTransaction') { |
||||||
|
console.log('saw send') |
||||||
|
shouldPop = true |
||||||
|
} |
||||||
|
_super(payload, cb) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
@ -0,0 +1,26 @@ |
|||||||
|
const ParentStream = require('iframe-stream').ParentStream |
||||||
|
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') |
||||||
|
const SwStream = require('sw-stream/lib/sw-stream.js') |
||||||
|
const SetupUntrustedComunication = ('./lib/setup-untrusted-connection.js') |
||||||
|
|
||||||
|
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 |
||||||
|
const background = new SWcontroller({ |
||||||
|
fileName: '/background.js', |
||||||
|
letBeIdle: false, |
||||||
|
wakeUpInterval: 30000, |
||||||
|
intervalDelay, |
||||||
|
}) |
||||||
|
|
||||||
|
const pageStream = new ParentStream() |
||||||
|
background.on('ready', (_) => { |
||||||
|
let swStream = SwStream({ |
||||||
|
serviceWorker: background.controller, |
||||||
|
context: 'dapp', |
||||||
|
}) |
||||||
|
pageStream.pipe(swStream).pipe(pageStream) |
||||||
|
|
||||||
|
}) |
||||||
|
background.on('updatefound', () => window.location.reload()) |
||||||
|
|
||||||
|
background.on('error', console.error) |
||||||
|
background.startWorker() |
@ -0,0 +1,56 @@ |
|||||||
|
const injectCss = require('inject-css') |
||||||
|
const SWcontroller = require('client-sw-ready-event/lib/sw-client.js') |
||||||
|
const SwStream = require('sw-stream/lib/sw-stream.js') |
||||||
|
const MetaMaskUiCss = require('../../ui/css') |
||||||
|
const setupIframe = require('./lib/setup-iframe.js') |
||||||
|
const MetamaskInpageProvider = require('../../app/scripts/lib/inpage-provider.js') |
||||||
|
const MetamascaraPlatform = require('../../app/scripts/platforms/window') |
||||||
|
const startPopup = require('../../app/scripts/popup-core') |
||||||
|
|
||||||
|
// create platform global
|
||||||
|
global.platform = new MetamascaraPlatform() |
||||||
|
|
||||||
|
|
||||||
|
var css = MetaMaskUiCss() |
||||||
|
injectCss(css) |
||||||
|
const container = document.getElementById('app-content') |
||||||
|
|
||||||
|
var name = 'popup' |
||||||
|
window.METAMASK_UI_TYPE = name |
||||||
|
|
||||||
|
let intervalDelay = Math.floor(Math.random() * (30000 - 1000)) + 1000 |
||||||
|
|
||||||
|
const background = new SWcontroller({ |
||||||
|
fileName: '/background.js', |
||||||
|
letBeIdle: false, |
||||||
|
intervalDelay, |
||||||
|
wakeUpInterval: 20000 |
||||||
|
}) |
||||||
|
// Setup listener for when the service worker is read
|
||||||
|
const connectApp = function (readSw) { |
||||||
|
let connectionStream = SwStream({ |
||||||
|
serviceWorker: background.controller, |
||||||
|
context: name, |
||||||
|
}) |
||||||
|
startPopup({container, connectionStream}, (err, store) => { |
||||||
|
if (err) return displayCriticalError(err) |
||||||
|
store.subscribe(() => { |
||||||
|
const state = store.getState() |
||||||
|
if (state.appState.shouldClose) window.close() |
||||||
|
}) |
||||||
|
}) |
||||||
|
} |
||||||
|
background.on('ready', (sw) => { |
||||||
|
background.removeListener('updatefound', connectApp) |
||||||
|
connectApp(sw) |
||||||
|
}) |
||||||
|
background.on('updatefound', () => window.location.reload()) |
||||||
|
|
||||||
|
background.startWorker() |
||||||
|
.then(() => { |
||||||
|
setTimeout(() => { |
||||||
|
const appContent = document.getElementById(`app-content`) |
||||||
|
if (!appContent.children.length) window.location.reload() |
||||||
|
}, 2000) |
||||||
|
}) |
||||||
|
console.log('hello from MetaMascara ui!') |
@ -0,0 +1,7 @@ |
|||||||
|
function wait(time) { |
||||||
|
return new Promise(function(resolve, reject) { |
||||||
|
setTimeout(function() { |
||||||
|
resolve() |
||||||
|
}, time * 3 || 1500) |
||||||
|
}) |
||||||
|
} |
@ -0,0 +1,21 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html> |
||||||
|
<head> |
||||||
|
<meta charset="utf-8"> |
||||||
|
<meta name="viewport" content="width=device-width"> |
||||||
|
<title>QUnit Example</title> |
||||||
|
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.0.0.css"> |
||||||
|
</head> |
||||||
|
<body> |
||||||
|
<div id="qunit"></div> |
||||||
|
<div id="qunit-fixture"></div> |
||||||
|
<script src="https://code.jquery.com/qunit/qunit-2.0.0.js"></script> |
||||||
|
<script src="./jquery-3.1.0.min.js"></script> |
||||||
|
<script src="./helpers.js"></script> |
||||||
|
<script src="./test-bundle.js"></script> |
||||||
|
<script src="/testem.js"></script> |
||||||
|
|
||||||
|
<div id="app-content"></div> |
||||||
|
<script src="./bundle.js"></script> |
||||||
|
</body> |
||||||
|
</html> |
@ -0,0 +1,22 @@ |
|||||||
|
var fs = require('fs') |
||||||
|
var path = require('path') |
||||||
|
var browserify = require('browserify'); |
||||||
|
var tests = fs.readdirSync(path.join(__dirname, 'lib')) |
||||||
|
var bundlePath = path.join(__dirname, 'test-bundle.js') |
||||||
|
var b = browserify(); |
||||||
|
|
||||||
|
// Remove old bundle
|
||||||
|
try { |
||||||
|
fs.unlinkSync(bundlePath) |
||||||
|
} catch (e) { |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
|
||||||
|
var writeStream = fs.createWriteStream(bundlePath) |
||||||
|
|
||||||
|
tests.forEach(function(fileName) { |
||||||
|
b.add(path.join(__dirname, 'lib', fileName)) |
||||||
|
}) |
||||||
|
|
||||||
|
b.bundle().pipe(writeStream); |
||||||
|
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,119 @@ |
|||||||
|
const PASSWORD = 'password123' |
||||||
|
|
||||||
|
QUnit.module('first time usage') |
||||||
|
|
||||||
|
QUnit.test('render init screen', function (assert) { |
||||||
|
var done = assert.async() |
||||||
|
let app |
||||||
|
|
||||||
|
wait(1000).then(function() { |
||||||
|
app = $('#app-content').contents() |
||||||
|
const recurseNotices = function () { |
||||||
|
let button = app.find('button') |
||||||
|
if (button.html() === 'Accept') { |
||||||
|
let termsPage = app.find('.markdown')[0] |
||||||
|
termsPage.scrollTop = termsPage.scrollHeight |
||||||
|
return wait().then(() => { |
||||||
|
button.click() |
||||||
|
return wait() |
||||||
|
}).then(() => { |
||||||
|
return recurseNotices() |
||||||
|
}) |
||||||
|
} else { |
||||||
|
return wait() |
||||||
|
} |
||||||
|
} |
||||||
|
return recurseNotices() |
||||||
|
}).then(function() { |
||||||
|
// Scroll through terms
|
||||||
|
var title = app.find('h1').text() |
||||||
|
assert.equal(title, 'MetaMask', 'title screen') |
||||||
|
|
||||||
|
// enter password
|
||||||
|
var pwBox = app.find('#password-box')[0] |
||||||
|
var confBox = app.find('#password-box-confirm')[0] |
||||||
|
pwBox.value = PASSWORD |
||||||
|
confBox.value = PASSWORD |
||||||
|
|
||||||
|
return wait() |
||||||
|
}).then(function() { |
||||||
|
|
||||||
|
// create vault
|
||||||
|
var createButton = app.find('button.primary')[0] |
||||||
|
createButton.click() |
||||||
|
|
||||||
|
return wait(1500) |
||||||
|
}).then(function() { |
||||||
|
|
||||||
|
var created = app.find('h3')[0] |
||||||
|
assert.equal(created.textContent, 'Vault Created', 'Vault created screen') |
||||||
|
|
||||||
|
// Agree button
|
||||||
|
var button = app.find('button')[0] |
||||||
|
assert.ok(button, 'button present') |
||||||
|
button.click() |
||||||
|
|
||||||
|
return wait(1000) |
||||||
|
}).then(function() { |
||||||
|
|
||||||
|
var detail = app.find('.account-detail-section')[0] |
||||||
|
assert.ok(detail, 'Account detail section loaded.') |
||||||
|
|
||||||
|
var sandwich = app.find('.sandwich-expando')[0] |
||||||
|
sandwich.click() |
||||||
|
|
||||||
|
return wait() |
||||||
|
}).then(function() { |
||||||
|
|
||||||
|
var sandwich = app.find('.menu-droppo')[0] |
||||||
|
var children = sandwich.children |
||||||
|
var lock = children[children.length - 2] |
||||||
|
assert.ok(lock, 'Lock menu item found') |
||||||
|
lock.click() |
||||||
|
|
||||||
|
return wait(1000) |
||||||
|
}).then(function() { |
||||||
|
|
||||||
|
var pwBox = app.find('#password-box')[0] |
||||||
|
pwBox.value = PASSWORD |
||||||
|
|
||||||
|
var createButton = app.find('button.primary')[0] |
||||||
|
createButton.click() |
||||||
|
|
||||||
|
return wait(1000) |
||||||
|
}).then(function() { |
||||||
|
|
||||||
|
var detail = app.find('.account-detail-section')[0] |
||||||
|
assert.ok(detail, 'Account detail section loaded again.') |
||||||
|
|
||||||
|
return wait() |
||||||
|
}).then(function (){ |
||||||
|
|
||||||
|
var qrButton = app.find('.fa.fa-qrcode')[0] |
||||||
|
qrButton.click() |
||||||
|
|
||||||
|
return wait(1000) |
||||||
|
}).then(function (){ |
||||||
|
|
||||||
|
var qrHeader = app.find('.qr-header')[0] |
||||||
|
var qrContainer = app.find('#qr-container')[0] |
||||||
|
assert.equal(qrHeader.textContent, 'Account 1', 'Should show account label.') |
||||||
|
assert.ok(qrContainer, 'QR Container found') |
||||||
|
|
||||||
|
return wait() |
||||||
|
}).then(function (){ |
||||||
|
|
||||||
|
var networkMenu = app.find('.network-indicator')[0] |
||||||
|
networkMenu.click() |
||||||
|
|
||||||
|
return wait() |
||||||
|
}).then(function (){ |
||||||
|
|
||||||
|
var networkMenu = app.find('.network-indicator')[0] |
||||||
|
var children = networkMenu.children |
||||||
|
children.length[3] |
||||||
|
assert.ok(children, 'All network options present') |
||||||
|
|
||||||
|
done() |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,13 @@ |
|||||||
|
launch_in_dev: |
||||||
|
- Chrome |
||||||
|
- Firefox |
||||||
|
- Opera |
||||||
|
launch_in_ci: |
||||||
|
- Chrome |
||||||
|
- Firefox |
||||||
|
- Opera |
||||||
|
framework: |
||||||
|
- qunit |
||||||
|
before_tests: "npm run mascaraCi" |
||||||
|
after_tests: "rm ./background.js ./test-bundle.js ./bundle.js" |
||||||
|
test_page: "./index.html" |
@ -0,0 +1,40 @@ |
|||||||
|
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')) |
||||||
|
} |
||||||
|
|
||||||
|
unregister () { |
||||||
|
return global.navigator.serviceWorker.getRegistration() |
||||||
|
.then((registration) => { |
||||||
|
if (registration) return registration.unregister() |
||||||
|
.then((b) => b ? Promise.resolve() : Promise.reject()) |
||||||
|
else return Promise.resolve() |
||||||
|
}) |
||||||
|
} |
||||||
|
clearDb () { |
||||||
|
return new Promise ((resolve, reject) => { |
||||||
|
const deleteRequest = global.indexDB.deleteDatabase(KEY) |
||||||
|
deleteRequest.addEventListener('success', resolve) |
||||||
|
deleteRequest.addEventListener('error', reject) |
||||||
|
}) |
||||||
|
|
||||||
|
} |
||||||
|
mockState (state) { |
||||||
|
const db = new IDB({ |
||||||
|
version: 2, |
||||||
|
key: KEY, |
||||||
|
initialState: state |
||||||
|
}) |
||||||
|
return db.open() |
||||||
|
} |
||||||
|
} |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue