commit
a741cc4fc4
@ -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 |
@ -1,18 +1,27 @@ |
|||||||
dist |
|
||||||
npm-debug.log |
npm-debug.log |
||||||
node_modules |
node_modules |
||||||
temp |
package-lock.json |
||||||
.tmp |
|
||||||
.sass-cache |
|
||||||
app/bower_components |
app/bower_components |
||||||
test/bower_components |
test/bower_components |
||||||
package |
package |
||||||
|
|
||||||
|
temp |
||||||
|
.tmp |
||||||
|
.sass-cache |
||||||
.DS_Store |
.DS_Store |
||||||
|
app/.DS_Store |
||||||
|
|
||||||
|
dist |
||||||
builds/ |
builds/ |
||||||
disc/ |
disc/ |
||||||
notes.txt |
|
||||||
app/.DS_Store |
|
||||||
development/bundle.js |
|
||||||
builds.zip |
builds.zip |
||||||
test/integration/bundle.js |
|
||||||
|
development/bundle.js |
||||||
development/states.js |
development/states.js |
||||||
|
test/integration/bundle.js |
||||||
|
test/background.js |
||||||
|
test/bundle.js |
||||||
|
test/test-bundle.js |
||||||
|
|
||||||
|
notes.txt |
File diff suppressed because one or more lines are too long
@ -1,16 +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 KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' |
const KOVAN_RPC_URL = 'https://kovan.infura.io/metamask' |
||||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL |
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, |
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) |
||||||
|
} |
||||||
|
} |
@ -1,11 +1,15 @@ |
|||||||
|
// test and development environment variables
|
||||||
|
const env = process.env.METAMASK_ENV |
||||||
|
const METAMASK_DEBUG = 'GULP_METAMASK_DEBUG' |
||||||
|
|
||||||
//
|
//
|
||||||
// The default state of MetaMask
|
// The default state of MetaMask
|
||||||
//
|
//
|
||||||
|
|
||||||
module.exports = { |
module.exports = { |
||||||
config: { |
config: {}, |
||||||
|
NetworkController: { |
||||||
provider: { |
provider: { |
||||||
type: 'testnet', |
type: (METAMASK_DEBUG || env === 'test') ? 'rinkeby' : 'mainnet', |
||||||
}, |
}, |
||||||
}, |
}, |
||||||
} |
} |
@ -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,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,20 +1,33 @@ |
|||||||
start the dual servers (dapp + mascara) |
start the dual servers (dapp + mascara) |
||||||
``` |
``` |
||||||
node server.js |
npm run mascara |
||||||
``` |
``` |
||||||
|
|
||||||
## First time use: |
### First time use: |
||||||
|
|
||||||
- navigate to: http://localhost:9001/popup/popup.html |
- navigate to: http://localhost:9001 |
||||||
- Create an Account |
- Create an Account |
||||||
- go back to http://localhost:9002/ |
- go back to http://localhost:9002 |
||||||
- open devTools |
- open devTools |
||||||
- click Sync Tx |
- click Sync Tx |
||||||
|
|
||||||
### Todos |
### Tests: |
||||||
|
|
||||||
|
``` |
||||||
|
npm run testMascara |
||||||
|
``` |
||||||
|
|
||||||
|
Test will run in browser, you will have to have these browsers installed: |
||||||
|
|
||||||
|
- Chrome |
||||||
|
- Firefox |
||||||
|
- Opera |
||||||
|
|
||||||
- [ ] Figure out user flows and UI redesign |
|
||||||
- [ ] Figure out FireFox |
|
||||||
Standing problems: |
|
||||||
- [ ] IndexDb |
|
||||||
|
|
||||||
|
### Deploy: |
||||||
|
|
||||||
|
Will build and deploy mascara via docker |
||||||
|
|
||||||
|
``` |
||||||
|
docker-compose build && docker-compose stop && docker-compose up -d && docker-compose logs --tail 200 -f |
||||||
|
``` |
@ -1,88 +0,0 @@ |
|||||||
const EventEmitter = require('events') |
|
||||||
module.exports = class IndexDbController extends EventEmitter { |
|
||||||
|
|
||||||
constructor (opts) { |
|
||||||
super() |
|
||||||
this.migrations = opts.migrations |
|
||||||
this.key = opts.key |
|
||||||
this.dbObject = global.indexedDB |
|
||||||
this.IDBTransaction = global.IDBTransaction || global.webkitIDBTransaction || global.msIDBTransaction || {READ_WRITE: "readwrite"}; // This line should only be needed if it is needed to support the object's constants for older browsers
|
|
||||||
this.IDBKeyRange = global.IDBKeyRange || global.webkitIDBKeyRange || global.msIDBKeyRange; |
|
||||||
this.version = opts.version |
|
||||||
this.logging = opts.logging |
|
||||||
this.initialState = opts.initialState |
|
||||||
if (this.logging) this.on('log', logger) |
|
||||||
} |
|
||||||
|
|
||||||
// Opens the database connection and returns a promise
|
|
||||||
open (version = this.version) { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
const dbOpenRequest = this.dbObject.open(this.key, version) |
|
||||||
dbOpenRequest.onerror = (event) => { |
|
||||||
return reject(event) |
|
||||||
} |
|
||||||
dbOpenRequest.onsuccess = (event) => { |
|
||||||
this.db = dbOpenRequest.result |
|
||||||
this.emit('success') |
|
||||||
resolve(this.db) |
|
||||||
} |
|
||||||
dbOpenRequest.onupgradeneeded = (event) => { |
|
||||||
this.db = event.target.result |
|
||||||
this.db.createObjectStore('dataStore') |
|
||||||
} |
|
||||||
}) |
|
||||||
.then((openRequest) => { |
|
||||||
return this.get('dataStore') |
|
||||||
}) |
|
||||||
.then((data) => { |
|
||||||
if (!data) { |
|
||||||
return this._add('dataStore', this.initialState) |
|
||||||
.then(() => this.get('dataStore')) |
|
||||||
.then((versionedData) => Promise.resolve(versionedData)) |
|
||||||
} |
|
||||||
return Promise.resolve(data) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
requestObjectStore (key, type = 'readonly') { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
const dbReadWrite = this.db.transaction(key, type) |
|
||||||
const dataStore = dbReadWrite.objectStore(key) |
|
||||||
resolve(dataStore) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
get (key = 'dataStore') { |
|
||||||
return this.requestObjectStore(key) |
|
||||||
.then((dataObject)=> { |
|
||||||
return new Promise((resolve, reject) => { |
|
||||||
const getRequest = dataObject.get(key) |
|
||||||
getRequest.onsuccess = (event) => resolve(event.currentTarget.result) |
|
||||||
getRequest.onerror = (event) => reject(event) |
|
||||||
}) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
put (state) { |
|
||||||
return this.requestObjectStore('dataStore', 'readwrite') |
|
||||||
.then((dataObject)=> { |
|
||||||
const putRequest = dataObject.put(state, 'dataStore') |
|
||||||
putRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) |
|
||||||
putRequest.onerror = (event) => Promise.reject(event) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
_add (key, objStore, cb = logger) { |
|
||||||
return this.requestObjectStore(key, 'readwrite') |
|
||||||
.then((dataObject)=> { |
|
||||||
const addRequest = dataObject.add(objStore, key) |
|
||||||
addRequest.onsuccess = (event) => Promise.resolve(event.currentTarget.result) |
|
||||||
addRequest.onerror = (event) => Promise.reject(event) |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
} |
|
||||||
|
|
||||||
function logger (err, ress) { |
|
||||||
err ? console.error(`Logger says: ${err}`) : console.dir(`Logger says: ${ress}`) |
|
||||||
} |
|
@ -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() |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,5 @@ |
|||||||
|
const Helper = require('./util/mascara-test-helper.js') |
||||||
|
|
||||||
|
window.addEventListener('load', () => { |
||||||
|
require('../src/ui.js') |
||||||
|
}) |
@ -1 +0,0 @@ |
|||||||
MetaMask now lists a new network on our dropdown list: Kovan, a [Proof of Authority](https://github.com/paritytech/parity/wiki/Proof-of-Authority-Chains) testchain managed by several blockchain organizations such as Digix, Etherscan, and Parity. It is designed to be a more stable and reliable testnet alternative to Ropsten and was created in response to recent attacks that slowed down the Ropsten network. You can read more about Kovan [here](https://medium.com/@Digix/announcing-kovan-a-stable-ethereum-public-testnet-10ac7cb6c85f#.6o8sz8cct) and [here](https://medium.com/@Digix/letter-from-the-ceo-some-context-regarding-kovan-7b5121adb901#.kfv7zhw83). As with Ropsten, the default remote node to connect to Kovan is managed by Infura. |
|
@ -0,0 +1,8 @@ |
|||||||
|
MetaMask is beta software. |
||||||
|
|
||||||
|
When you log in to MetaMask, your current account is visible to every new site you visit. |
||||||
|
|
||||||
|
For your privacy, for now, please sign out of MetaMask when you're done using a site. |
||||||
|
|
||||||
|
Also, by default, you will be signed in to a test network. To use real Ether, you must connect to the main network manually in the top left network menu. |
||||||
|
|
@ -1 +1 @@ |
|||||||
2 |
3 |
File diff suppressed because one or more lines are too long
@ -1 +1,14 @@ |
|||||||
{"version":0,"data":{"wallet":"{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}","config":{"provider":{"type":"etherscan"}}},"meta":{"version":0}} |
{ |
||||||
|
"version": 0, |
||||||
|
"data": { |
||||||
|
"wallet": "{\"encSeed\":{\"encStr\":\"rT1C1jjkFRfmrwefscFcwZohl4f+HfIFlBZ9AM4ZD8atJmfKDIQCVK11NYDKYv8ZMIY03f3t8MuoZvfzBL8IJsWnZUhpzVTNNiARQJD2WpGA19eNBzgZm4vd0GwkIUruUDeJXu0iv2j9wU8hOQUqPbOePPy2Am5ro97iuvMAroRTnEKD60qFVg==\",\"nonce\":\"YUY2mwNq2v3FV0Fi94QnSiKFOLYfDR95\"},\"ksData\":{\"m/44'/60'/0'/0\":{\"info\":{\"curve\":\"secp256k1\",\"purpose\":\"sign\"},\"encHdPathPriv\":{\"encStr\":\"Iyi7ft4JQ9UtwrSXRT6ZIHPtZqJhe99rh0uWhNc6QLan6GanY2ZQeU0tt76CBealEWJyrJReSxGQdqDmSDYjpjH3m4JO5l0DfPLPseCqzXV/W+dzM0ubJ8lztLwpwi0L+vULNMqCx4dQtoNbNBq1QZUnjtpm6O8mWpScspboww==\",\"nonce\":\"Z7RqtjNjC6FrLUj5wVW1+HkjOW6Hib6K\"},\"hdIndex\":3,\"encPrivKeys\":{\"edb81c10122f34040cc4bef719a272fbbb1cf897\":{\"key\":\"8ab81tKBd4+CLAbzvS7SBFRTd6VWXBs86uBE43lgcmBu2U7UB22xdH64Q2hUf9eB\",\"nonce\":\"aGUEqI033FY39zKjWmZSI6PQrCLvkiRP\"},\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\":{\"key\":\"+i3wmf4b+B898QtlOBfL0Ixirjg59/LLPX61vQ2L0xRPjXzNog0O4Wn15RemM5mY\",\"nonce\":\"imKrlkuoC5uuFkzJBbuDBluGCPJXNTKm\"},\"2340695474656e3124b8eba1172fbfb00eeac8f8\":{\"key\":\"pi+H9D8LYKsdCQKrfaJtsGFjE+X9s74xN675tsoIKrbPXhtpxMLOIQVtSqYveF62\",\"nonce\":\"49g80wDTovHwbguVVYf2FsYbp7Db5OAR\"}},\"addresses\":[\"edb81c10122f34040cc4bef719a272fbbb1cf897\",\"8bd7d5c000cf05284e98356370dc5ccaa3dbfc38\",\"2340695474656e3124b8eba1172fbfb00eeac8f8\"]}},\"version\":2}", |
||||||
|
"config": { |
||||||
|
"provider": { |
||||||
|
"type": "etherscan" |
||||||
|
} |
||||||
|
} |
||||||
|
}, |
||||||
|
"meta": { |
||||||
|
"version": 0 |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,18 @@ |
|||||||
|
const createStore = require('redux').createStore |
||||||
|
const applyMiddleware = require('redux').applyMiddleware |
||||||
|
const thunkMiddleware = require('redux-thunk') |
||||||
|
const createLogger = require('redux-logger') |
||||||
|
const rootReducer = function () {} |
||||||
|
|
||||||
|
module.exports = configureStore |
||||||
|
|
||||||
|
const loggerMiddleware = createLogger() |
||||||
|
|
||||||
|
const createStoreWithMiddleware = applyMiddleware( |
||||||
|
thunkMiddleware, |
||||||
|
loggerMiddleware |
||||||
|
)(createStore) |
||||||
|
|
||||||
|
function configureStore (initialState) { |
||||||
|
return createStoreWithMiddleware(rootReducer, initialState) |
||||||
|
} |
@ -0,0 +1,51 @@ |
|||||||
|
var assert = require('assert') |
||||||
|
|
||||||
|
const additions = require('react-testutils-additions') |
||||||
|
const h = require('react-hyperscript') |
||||||
|
const ReactTestUtils = require('react-addons-test-utils') |
||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
const BN = ethUtil.BN |
||||||
|
|
||||||
|
var BnInput = require('../../../ui/app/components/bn-as-decimal-input') |
||||||
|
|
||||||
|
describe('BnInput', function () { |
||||||
|
it('can tolerate a gas decimal number at a high precision', function (done) { |
||||||
|
const renderer = ReactTestUtils.createRenderer() |
||||||
|
|
||||||
|
let valueStr = '20' |
||||||
|
while (valueStr.length < 20) { |
||||||
|
valueStr += '0' |
||||||
|
} |
||||||
|
const value = new BN(valueStr, 10) |
||||||
|
|
||||||
|
const inputStr = '2.3' |
||||||
|
|
||||||
|
let targetStr = '23' |
||||||
|
while (targetStr.length < 19) { |
||||||
|
targetStr += '0' |
||||||
|
} |
||||||
|
const target = new BN(targetStr, 10) |
||||||
|
|
||||||
|
const precision = 18 // ether precision
|
||||||
|
const scale = 18 |
||||||
|
|
||||||
|
const props = { |
||||||
|
value, |
||||||
|
scale, |
||||||
|
precision, |
||||||
|
onChange: (newBn) => { |
||||||
|
assert.equal(newBn.toString(), target.toString(), 'should tolerate increase') |
||||||
|
done() |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const inputComponent = h(BnInput, props) |
||||||
|
const component = additions.renderIntoDocument(inputComponent) |
||||||
|
renderer.render(inputComponent) |
||||||
|
const input = additions.find(component, 'input.hex-input')[0] |
||||||
|
ReactTestUtils.Simulate.change(input, { preventDefault () {}, target: { |
||||||
|
value: inputStr, |
||||||
|
checkValidity () { return true } }, |
||||||
|
}) |
||||||
|
}) |
||||||
|
}) |
@ -0,0 +1,77 @@ |
|||||||
|
const assert = require('assert') |
||||||
|
const additions = require('react-testutils-additions') |
||||||
|
const h = require('react-hyperscript') |
||||||
|
const PendingTx = require('../../../ui/app/components/pending-tx') |
||||||
|
const ReactTestUtils = require('react-addons-test-utils') |
||||||
|
const ethUtil = require('ethereumjs-util') |
||||||
|
|
||||||
|
describe('PendingTx', function () { |
||||||
|
const identities = { |
||||||
|
'0xfdea65c8e26263f6d9a1b5de9555d2931a33b826': { |
||||||
|
name: 'Main Account 1', |
||||||
|
balance: '0x00000000000000056bc75e2d63100000', |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const gasPrice = '0x4A817C800' // 20 Gwei
|
||||||
|
const txData = { |
||||||
|
'id': 5021615666270214, |
||||||
|
'time': 1494458763011, |
||||||
|
'status': 'unapproved', |
||||||
|
'metamaskNetworkId': '1494442339676', |
||||||
|
'txParams': { |
||||||
|
'from': '0xfdea65c8e26263f6d9a1b5de9555d2931a33b826', |
||||||
|
'to': '0xc5b8dbac4c1d3f152cdeb400e2313f309c410acb', |
||||||
|
'value': '0xde0b6b3a7640000', |
||||||
|
gasPrice, |
||||||
|
'gas': '0x7b0c'}, |
||||||
|
'gasLimitSpecified': false, |
||||||
|
'estimatedGas': '0x5208', |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
it('should use updated values when edited.', function (done) { |
||||||
|
const renderer = ReactTestUtils.createRenderer() |
||||||
|
const newGasPrice = '0x77359400' |
||||||
|
|
||||||
|
const props = { |
||||||
|
identities, |
||||||
|
accounts: identities, |
||||||
|
txData, |
||||||
|
sendTransaction: (txMeta, event) => { |
||||||
|
// Assert changes:
|
||||||
|
const result = ethUtil.addHexPrefix(txMeta.txParams.gasPrice) |
||||||
|
assert.notEqual(result, gasPrice, 'gas price should change') |
||||||
|
assert.equal(result, newGasPrice, 'gas price assigned.') |
||||||
|
done() |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
const pendingTxComponent = h(PendingTx, props) |
||||||
|
const component = additions.renderIntoDocument(pendingTxComponent) |
||||||
|
renderer.render(pendingTxComponent) |
||||||
|
const result = renderer.getRenderOutput() |
||||||
|
assert.equal(result.type, 'div', 'should create a div') |
||||||
|
|
||||||
|
try { |
||||||
|
const input = additions.find(component, '.cell.row input[type="number"]')[1] |
||||||
|
ReactTestUtils.Simulate.change(input, { |
||||||
|
target: { |
||||||
|
value: 2, |
||||||
|
checkValidity () { return true }, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
const form = additions.find(component, 'form')[0] |
||||||
|
form.checkValidity = () => true |
||||||
|
form.getFormEl = () => { return { checkValidity () { return true } } } |
||||||
|
ReactTestUtils.Simulate.submit(form, { preventDefault () {}, target: { checkValidity () { |
||||||
|
return true |
||||||
|
} } }) |
||||||
|
} catch (e) { |
||||||
|
console.log('WHAAAA') |
||||||
|
console.error(e) |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue