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 |
||||
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 TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask' |
||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL |
||||
const ROPSTEN_RPC_URL = 'https://ropsten.infura.io/metamask' |
||||
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' |
||||
const RINKEBY_RPC_URL = 'https://rinkeby.infura.io/metamask' |
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
||||
|
||||
module.exports = { |
||||
network: { |
||||
default: DEFAULT_RPC_URL, |
||||
mainnet: MAINET_RPC_URL, |
||||
testnet: TESTNET_RPC_URL, |
||||
morden: TESTNET_RPC_URL, |
||||
ropsten: ROPSTEN_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