mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +01:00
Merged master into dev
This commit is contained in:
commit
77d2deb176
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
dist
|
||||
npm-debug.log
|
||||
node_modules
|
||||
temp
|
||||
.tmp
|
||||
@ -13,4 +14,4 @@ app/.DS_Store
|
||||
development/bundle.js
|
||||
builds.zip
|
||||
test/integration/bundle.js
|
||||
npm-debug.log
|
||||
development/states.js
|
||||
|
11
CHANGELOG.md
11
CHANGELOG.md
@ -2,6 +2,17 @@
|
||||
|
||||
## Current Master
|
||||
|
||||
- Temporarily disable extension reload detection causing infinite reload bug.
|
||||
|
||||
## 2.14.0 2016-12-16
|
||||
|
||||
- Removed Morden testnet provider from provider menu.
|
||||
- Add support for notices.
|
||||
- Fix broken reload detection.
|
||||
- Fix transaction forever cached-as-pending bug.
|
||||
|
||||
## 2.13.11 2016-11-23
|
||||
|
||||
- Add support for synchronous RPC method "eth_uninstallFilter".
|
||||
|
||||
## 2.13.10 2016-11-22
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "MetaMask",
|
||||
"short_name": "Metamask",
|
||||
"version": "2.13.10",
|
||||
"version": "2.14.0",
|
||||
"manifest_version": 2,
|
||||
"author": "https://metamask.io",
|
||||
"description": "Ethereum Browser Extension",
|
||||
|
@ -1,6 +1,5 @@
|
||||
const MAINET_RPC_URL = 'https://mainnet.infura.io/metamask'
|
||||
const TESTNET_RPC_URL = 'https://ropsten.infura.io/metamask'
|
||||
const MORDEN_RPC_URL = 'https://morden.infura.io/metamask'
|
||||
const DEFAULT_RPC_URL = TESTNET_RPC_URL
|
||||
|
||||
global.METAMASK_DEBUG = 'GULP_METAMASK_DEBUG'
|
||||
@ -11,6 +10,6 @@ module.exports = {
|
||||
default: DEFAULT_RPC_URL,
|
||||
mainnet: MAINET_RPC_URL,
|
||||
testnet: TESTNET_RPC_URL,
|
||||
morden: MORDEN_RPC_URL,
|
||||
morden: TESTNET_RPC_URL,
|
||||
},
|
||||
}
|
||||
|
@ -2,8 +2,8 @@
|
||||
cleanContextForImports()
|
||||
require('web3/dist/web3.min.js')
|
||||
const LocalMessageDuplexStream = require('post-message-stream')
|
||||
const PingStream = require('ping-pong-stream/ping')
|
||||
const endOfStream = require('end-of-stream')
|
||||
// const PingStream = require('ping-pong-stream/ping')
|
||||
// const endOfStream = require('end-of-stream')
|
||||
const setupDappAutoReload = require('./lib/auto-reload.js')
|
||||
const MetamaskInpageProvider = require('./lib/inpage-provider.js')
|
||||
restoreContextAfterImports()
|
||||
@ -40,13 +40,15 @@ reloadStream.once('data', triggerReload)
|
||||
|
||||
// setup ping timeout autoreload
|
||||
// LocalMessageDuplexStream does not self-close, so reload if pingStream fails
|
||||
var pingChannel = inpageProvider.multiStream.createStream('pingpong')
|
||||
var pingStream = new PingStream({ objectMode: true })
|
||||
// var pingChannel = inpageProvider.multiStream.createStream('pingpong')
|
||||
// var pingStream = new PingStream({ objectMode: true })
|
||||
// wait for first successful reponse
|
||||
metamaskStream.once('_data', function () {
|
||||
pingStream.pipe(pingChannel).pipe(pingStream)
|
||||
})
|
||||
endOfStream(pingStream, triggerReload)
|
||||
|
||||
// disable pingStream until https://github.com/MetaMask/metamask-plugin/issues/746 is resolved more gracefully
|
||||
// metamaskStream.once('data', function(){
|
||||
// pingStream.pipe(pingChannel).pipe(pingStream)
|
||||
// })
|
||||
// endOfStream(pingStream, triggerReload)
|
||||
|
||||
// set web3 defaultAcount
|
||||
inpageProvider.publicConfigStore.subscribe(function (state) {
|
||||
|
@ -2,6 +2,7 @@ const extend = require('xtend')
|
||||
const EthStore = require('eth-store')
|
||||
const MetaMaskProvider = require('web3-provider-engine/zero.js')
|
||||
const KeyringController = require('./keyring-controller')
|
||||
const NoticeController = require('./notice-controller')
|
||||
const messageManager = require('./lib/message-manager')
|
||||
const HostStore = require('./lib/remote-store.js').HostStore
|
||||
const Web3 = require('web3')
|
||||
@ -22,6 +23,13 @@ module.exports = class MetamaskController {
|
||||
configManager: this.configManager,
|
||||
getNetwork: this.getStateNetwork.bind(this),
|
||||
})
|
||||
// notices
|
||||
this.noticeController = new NoticeController({
|
||||
configManager: this.configManager,
|
||||
})
|
||||
this.noticeController.updateNoticesList()
|
||||
// to be uncommented when retrieving notices from a remote server.
|
||||
// this.noticeController.startPolling()
|
||||
this.provider = this.initializeProvider(opts)
|
||||
this.ethStore = new EthStore(this.provider)
|
||||
this.keyringController.setStore(this.ethStore)
|
||||
@ -43,12 +51,14 @@ module.exports = class MetamaskController {
|
||||
this.state,
|
||||
this.ethStore.getState(),
|
||||
this.configManager.getConfig(),
|
||||
this.keyringController.getState()
|
||||
this.keyringController.getState(),
|
||||
this.noticeController.getState()
|
||||
)
|
||||
}
|
||||
|
||||
getApi () {
|
||||
const keyringController = this.keyringController
|
||||
const noticeController = this.noticeController
|
||||
|
||||
return {
|
||||
getState: (cb) => { cb(null, this.getState()) },
|
||||
@ -85,6 +95,9 @@ module.exports = class MetamaskController {
|
||||
buyEth: this.buyEth.bind(this),
|
||||
// shapeshift
|
||||
createShapeShiftTx: this.createShapeShiftTx.bind(this),
|
||||
// notices
|
||||
checkNotices: noticeController.updateNoticesList.bind(noticeController),
|
||||
markNoticeRead: noticeController.markNoticeRead.bind(noticeController),
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,7 +281,7 @@ module.exports = class MetamaskController {
|
||||
setTOSHash (hash) {
|
||||
try {
|
||||
this.configManager.setTOSHash(hash)
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
console.error('Error in setting terms of service hash.')
|
||||
}
|
||||
}
|
||||
@ -280,17 +293,19 @@ module.exports = class MetamaskController {
|
||||
this.resetDisclaimer()
|
||||
this.setTOSHash(global.TOS_HASH)
|
||||
}
|
||||
} catch (e) {
|
||||
} catch (err) {
|
||||
console.error('Error in checking TOS change.')
|
||||
}
|
||||
}
|
||||
|
||||
// disclaimer
|
||||
|
||||
agreeToDisclaimer (cb) {
|
||||
try {
|
||||
this.configManager.setConfirmedDisclaimer(true)
|
||||
cb()
|
||||
} catch (e) {
|
||||
cb(e)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,8 +328,8 @@ module.exports = class MetamaskController {
|
||||
conversionDate: this.configManager.getConversionDate(),
|
||||
}
|
||||
cb(data)
|
||||
} catch (e) {
|
||||
cb(null, e)
|
||||
} catch (err) {
|
||||
cb(null, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -387,8 +402,8 @@ module.exports = class MetamaskController {
|
||||
try {
|
||||
this.configManager.setGasMultiplier(gasMultiplier)
|
||||
cb()
|
||||
} catch (e) {
|
||||
cb(e)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
|
96
app/scripts/notice-controller.js
Normal file
96
app/scripts/notice-controller.js
Normal file
@ -0,0 +1,96 @@
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const hardCodedNotices = require('../../development/notices.json')
|
||||
|
||||
module.exports = class NoticeController extends EventEmitter {
|
||||
|
||||
constructor (opts) {
|
||||
super()
|
||||
this.configManager = opts.configManager
|
||||
this.noticePoller = null
|
||||
}
|
||||
|
||||
getState() {
|
||||
var lastUnreadNotice = this.getLatestUnreadNotice()
|
||||
|
||||
return {
|
||||
lastUnreadNotice: lastUnreadNotice,
|
||||
noActiveNotices: !lastUnreadNotice,
|
||||
}
|
||||
}
|
||||
|
||||
getNoticesList() {
|
||||
var data = this.configManager.getData()
|
||||
if ('noticesList' in data) {
|
||||
return data.noticesList
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
setNoticesList(list) {
|
||||
var data = this.configManager.getData()
|
||||
data.noticesList = list
|
||||
this.configManager.setData(data)
|
||||
return Promise.resolve(true)
|
||||
}
|
||||
|
||||
markNoticeRead(notice, cb) {
|
||||
cb = cb || function(err){ if (err) throw err }
|
||||
try {
|
||||
var notices = this.getNoticesList()
|
||||
var id = notice.id
|
||||
notices[id].read = true
|
||||
this.setNoticesList(notices)
|
||||
let latestNotice = this.getLatestUnreadNotice()
|
||||
cb(null, latestNotice)
|
||||
} catch (err) {
|
||||
cb(err)
|
||||
}
|
||||
}
|
||||
|
||||
updateNoticesList() {
|
||||
return this._retrieveNoticeData().then((newNotices) => {
|
||||
var oldNotices = this.getNoticesList()
|
||||
var combinedNotices = this._mergeNotices(oldNotices, newNotices)
|
||||
return Promise.resolve(this.setNoticesList(combinedNotices))
|
||||
})
|
||||
}
|
||||
|
||||
getLatestUnreadNotice() {
|
||||
var notices = this.getNoticesList()
|
||||
var filteredNotices = notices.filter((notice) => {
|
||||
return notice.read === false
|
||||
})
|
||||
return filteredNotices[filteredNotices.length - 1]
|
||||
}
|
||||
|
||||
startPolling () {
|
||||
if (this.noticePoller) {
|
||||
clearInterval(this.noticePoller)
|
||||
}
|
||||
this.noticePoller = setInterval(() => {
|
||||
this.noticeController.updateNoticesList()
|
||||
}, 300000)
|
||||
}
|
||||
|
||||
_mergeNotices(oldNotices, newNotices) {
|
||||
var noticeMap = this._mapNoticeIds(oldNotices)
|
||||
newNotices.forEach((notice) => {
|
||||
if (noticeMap.indexOf(notice.id) === -1) {
|
||||
oldNotices.push(notice)
|
||||
}
|
||||
})
|
||||
return oldNotices
|
||||
}
|
||||
|
||||
_mapNoticeIds(notices) {
|
||||
return notices.map((notice) => notice.id)
|
||||
}
|
||||
|
||||
_retrieveNoticeData() {
|
||||
// Placeholder for the API.
|
||||
return Promise.resolve(hardCodedNotices)
|
||||
}
|
||||
|
||||
|
||||
}
|
36
development/notice-generator.js
Normal file
36
development/notice-generator.js
Normal file
@ -0,0 +1,36 @@
|
||||
var fsp = require('fs-promise')
|
||||
var path = require('path')
|
||||
var prompt = require('prompt')
|
||||
var open = require('open')
|
||||
var extend = require('extend')
|
||||
var notices = require('./notices.json')
|
||||
|
||||
var id = 0
|
||||
var date = new Date().toDateString()
|
||||
|
||||
var notice = {
|
||||
read: false,
|
||||
date: date,
|
||||
}
|
||||
|
||||
fsp.readdir('notices')
|
||||
.then((files) => {
|
||||
files.forEach(file => { id ++ })
|
||||
Promise.resolve()
|
||||
}).then(() => {
|
||||
fsp.writeFile(`notices/notice_${id}.md`,'Message goes here. Please write out your notice and save before proceeding at the command line.')
|
||||
.then(() => {
|
||||
open(`notices/notice_${id}.md`)
|
||||
prompt.start()
|
||||
prompt.get(['title'], (err, result) => {
|
||||
notice.title = result.title
|
||||
fsp.readFile(`notices/notice_${id}.md`)
|
||||
.then((body) => {
|
||||
notice.body = body.toString()
|
||||
notice.id = id
|
||||
notices.push(notice)
|
||||
return fsp.writeFile(`development/notices.json`, JSON.stringify(notices))
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
1
development/notices.json
Normal file
1
development/notices.json
Normal file
@ -0,0 +1 @@
|
||||
[{"read":false,"date":"Fri Dec 16 2016","title":"Ending Morden Support","body":"Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.\n\nUsers will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).\n\nPlease use the new Ropsten Network as your new default test network.\n\nYou can fund your Ropsten account using the buy button on your account page.\n\nBest wishes!\nThe MetaMask Team\n\n","id":0}]
|
File diff suppressed because one or more lines are too long
62
development/states/notice.json
Normal file
62
development/states/notice.json
Normal file
@ -0,0 +1,62 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": true,
|
||||
"isUnlocked": true,
|
||||
"identities": {
|
||||
"0x24a1d059462456aa332d6da9117aa7f91a46f2ac": {
|
||||
"address": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac",
|
||||
"name": "Account 1"
|
||||
}
|
||||
},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 8.3533002,
|
||||
"conversionDate": 1481671082,
|
||||
"noActiveNotices": false,
|
||||
"lastUnreadNotice": {
|
||||
"read": false,
|
||||
"date": "Tue Dec 13 2016",
|
||||
"title": "MultiVault Support",
|
||||
"body": "# Multi\n# Line\n## Support\nLorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi tincidunt dapibus justo a auctor. Sed luctus metus non mi laoreet, sit amet placerat nibh ultricies. Cras fringilla, urna sit amet sodales porttitor, lacus risus lacinia lorem, non euismod magna felis id ex. Nam iaculis, ante nec imperdiet suscipit, nisi quam fringilla nisl, sed fringilla turpis lectus et nibh. Pellentesque sed neque pretium nulla elementum lacinia eu eget felis. Nulla facilisi. Pellentesque id mi tempor, tempus sapien id, ultricies nibh. Integer faucibus elit non orci dapibus porttitor. Pellentesque rutrum hendrerit sapien ut lacinia. Nunc elementum eget arcu eu volutpat. Integer ullamcorper aliquam metus, eu malesuada tellus vestibulum a.\n",
|
||||
"id": 0
|
||||
},
|
||||
"network": "3",
|
||||
"accounts": {
|
||||
"0x24a1d059462456aa332d6da9117aa7f91a46f2ac": {
|
||||
"code": "0x",
|
||||
"nonce": "0x0",
|
||||
"balance": "0x0",
|
||||
"address": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac"
|
||||
}
|
||||
},
|
||||
"transactions": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"selectedAccount": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac",
|
||||
"seedWords": null,
|
||||
"isDisclaimerConfirmed": true,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"keyringTypes": [
|
||||
"Simple Key Pair",
|
||||
"HD Key Tree"
|
||||
]
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accountDetail",
|
||||
"detailView": null,
|
||||
"context": "0x24a1d059462456aa332d6da9117aa7f91a46f2ac"
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
40
development/states/terms-and-conditions.json
Normal file
40
development/states/terms-and-conditions.json
Normal file
@ -0,0 +1,40 @@
|
||||
{
|
||||
"metamask": {
|
||||
"isInitialized": false,
|
||||
"isUnlocked": false,
|
||||
"rpcTarget": "https://rawtestrpc.metamask.io/",
|
||||
"identities": {},
|
||||
"unconfTxs": {},
|
||||
"currentFiat": "USD",
|
||||
"conversionRate": 8.18703468,
|
||||
"conversionDate": 1481755832,
|
||||
"network": "3",
|
||||
"accounts": {},
|
||||
"transactions": [],
|
||||
"provider": {
|
||||
"type": "testnet"
|
||||
},
|
||||
"isDisclaimerConfirmed": false,
|
||||
"unconfMsgs": {},
|
||||
"messages": [],
|
||||
"shapeShiftTxList": [],
|
||||
"keyringTypes": [
|
||||
"Simple Key Pair",
|
||||
"HD Key Tree"
|
||||
]
|
||||
},
|
||||
"appState": {
|
||||
"menuOpen": false,
|
||||
"currentView": {
|
||||
"name": "accounts",
|
||||
"detailView": null
|
||||
},
|
||||
"accountDetail": {
|
||||
"subview": "transactions"
|
||||
},
|
||||
"transForward": true,
|
||||
"isLoading": false,
|
||||
"warning": null
|
||||
},
|
||||
"identities": {}
|
||||
}
|
12
notices/notice_0.md
Normal file
12
notices/notice_0.md
Normal file
@ -0,0 +1,12 @@
|
||||
Due to [recent events](https://blog.ethereum.org/2016/11/20/from-morden-to-ropsten/), MetaMask is now deprecating support for the Morden Test Network.
|
||||
|
||||
Users will still be able to access Morden through a locally hosted node, but we will no longer be providing hosted access to this network through [Infura](http://infura.io/).
|
||||
|
||||
Please use the new Ropsten Network as your new default test network.
|
||||
|
||||
You can fund your Ropsten account using the buy button on your account page.
|
||||
|
||||
Best wishes!
|
||||
|
||||
The MetaMask Team
|
||||
|
15
package.json
15
package.json
@ -12,12 +12,14 @@
|
||||
"test": "npm run fastTest && npm run ci && npm run lint",
|
||||
"fastTest": "METAMASK_ENV=test mocha --require test/helper.js --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
|
||||
"watch": "mocha watch --compilers js:babel-register --recursive \"test/unit/**/*.js\"",
|
||||
"ui": "node development/genStates.js && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"genStates": "node development/genStates.js",
|
||||
"ui": "npm run genStates && beefy ui-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"mock": "beefy mock-dev.js:bundle.js --live --open --index=./development/index.html --cwd ./",
|
||||
"buildMock": "browserify ./mock-dev.js -o ./development/bundle.js",
|
||||
"buildMock": "npm run genStates && browserify ./mock-dev.js -o ./development/bundle.js",
|
||||
"testem": "npm run buildMock && testem",
|
||||
"ci": "npm run buildMock && npm run buildCiUnits && testem ci -P 2",
|
||||
"announce": "node development/announcer.js"
|
||||
"announce": "node development/announcer.js",
|
||||
"generateNotice": "node development/notice-generator.js"
|
||||
},
|
||||
"browserify": {
|
||||
"transform": [
|
||||
@ -50,6 +52,8 @@
|
||||
"ethereumjs-util": "^4.4.0",
|
||||
"ethereumjs-wallet": "^0.6.0",
|
||||
"express": "^4.14.0",
|
||||
"extension-link-enabler": "^1.0.0",
|
||||
"extensionizer": "^1.0.0",
|
||||
"gulp-eslint": "^2.0.0",
|
||||
"hat": "0.0.3",
|
||||
"identicon.js": "^1.2.1",
|
||||
@ -87,7 +91,7 @@
|
||||
"through2": "^2.0.1",
|
||||
"vreme": "^3.0.2",
|
||||
"web3": "0.17.0-beta",
|
||||
"web3-provider-engine": "^8.1.5",
|
||||
"web3-provider-engine": "^8.1.14",
|
||||
"web3-stream-provider": "^2.0.6",
|
||||
"xtend": "^4.0.1"
|
||||
},
|
||||
@ -101,6 +105,7 @@
|
||||
"chai": "^3.5.0",
|
||||
"deep-freeze-strict": "^1.1.1",
|
||||
"del": "^2.2.0",
|
||||
"fs-promise": "^1.0.0",
|
||||
"gulp": "github:gulpjs/gulp#4.0",
|
||||
"gulp-brfs": "^0.1.0",
|
||||
"gulp-if": "^2.0.1",
|
||||
@ -120,6 +125,8 @@
|
||||
"mocha-jsdom": "^1.1.0",
|
||||
"mocha-sinon": "^1.1.5",
|
||||
"nock": "^8.0.0",
|
||||
"open": "0.0.5",
|
||||
"prompt": "^1.0.0",
|
||||
"qs": "^6.2.0",
|
||||
"qunit": "^0.9.1",
|
||||
"sinon": "^1.17.3",
|
||||
|
@ -1,12 +1,12 @@
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
var configManagerGen = require('../lib/mock-config-manager')
|
||||
var configManager
|
||||
const rp = require('request-promise')
|
||||
const nock = require('nock')
|
||||
var configManagerGen = require('../lib/mock-config-manager')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
|
||||
describe('config-manager', function() {
|
||||
var configManager
|
||||
|
||||
beforeEach(function() {
|
||||
window.localStorage = {} // Hacking localStorage support into JSDom
|
||||
|
115
test/unit/notice-controller-test.js
Normal file
115
test/unit/notice-controller-test.js
Normal file
@ -0,0 +1,115 @@
|
||||
const assert = require('assert')
|
||||
const extend = require('xtend')
|
||||
const rp = require('request-promise')
|
||||
const nock = require('nock')
|
||||
const configManagerGen = require('../lib/mock-config-manager')
|
||||
const NoticeController = require('../../app/scripts/notice-controller')
|
||||
const STORAGE_KEY = 'metamask-persistance-key'
|
||||
// Hacking localStorage support into JSDom
|
||||
window.localStorage = {}
|
||||
|
||||
describe('notice-controller', function() {
|
||||
var noticeController
|
||||
|
||||
beforeEach(function() {
|
||||
let configManager = configManagerGen()
|
||||
noticeController = new NoticeController({
|
||||
configManager: configManager,
|
||||
})
|
||||
})
|
||||
|
||||
describe('notices', function() {
|
||||
describe('#getNoticesList', function() {
|
||||
it('should return an empty array when new', function() {
|
||||
var testList = [{
|
||||
id:0,
|
||||
read:false,
|
||||
title:"Futuristic Notice"
|
||||
}]
|
||||
var result = noticeController.getNoticesList()
|
||||
assert.equal(result.length, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#setNoticesList', function() {
|
||||
it('should set data appropriately', function () {
|
||||
var testList = [{
|
||||
id:0,
|
||||
read:false,
|
||||
title:"Futuristic Notice"
|
||||
}]
|
||||
noticeController.setNoticesList(testList)
|
||||
var testListId = noticeController.getNoticesList()[0].id
|
||||
assert.equal(testListId, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#updateNoticeslist', function() {
|
||||
it('should integrate the latest changes from the source', function() {
|
||||
var testList = [{
|
||||
id:55,
|
||||
read:false,
|
||||
title:"Futuristic Notice"
|
||||
}]
|
||||
noticeController.setNoticesList(testList)
|
||||
noticeController.updateNoticesList().then(() => {
|
||||
var newList = noticeController.getNoticesList()
|
||||
assert.ok(newList[0].id === 55)
|
||||
assert.ok(newList[1])
|
||||
})
|
||||
})
|
||||
it('should not overwrite any existing fields', function () {
|
||||
var testList = [{
|
||||
id:0,
|
||||
read:false,
|
||||
title:"Futuristic Notice"
|
||||
}]
|
||||
noticeController.setNoticesList(testList)
|
||||
noticeController.updateNoticesList().then(() => {
|
||||
var newList = noticeController.getNoticesList()
|
||||
assert.equal(newList[0].id, 0)
|
||||
assert.equal(newList[0].title, "Futuristic Notice")
|
||||
assert.equal(newList.length, 1)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('#markNoticeRead', function () {
|
||||
it('should mark a notice as read', function () {
|
||||
var testList = [{
|
||||
id:0,
|
||||
read:false,
|
||||
title:"Futuristic Notice"
|
||||
}]
|
||||
noticeController.setNoticesList(testList)
|
||||
noticeController.markNoticeRead(testList[0])
|
||||
var newList = noticeController.getNoticesList()
|
||||
assert.ok(newList[0].read)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getLatestUnreadNotice', function () {
|
||||
it('should retrieve the latest unread notice', function () {
|
||||
var testList = [
|
||||
{id:0,read:true,title:"Past Notice"},
|
||||
{id:1,read:false,title:"Current Notice"},
|
||||
{id:2,read:false,title:"Future Notice"},
|
||||
]
|
||||
noticeController.setNoticesList(testList)
|
||||
var latestUnread = noticeController.getLatestUnreadNotice()
|
||||
assert.equal(latestUnread.id, 2)
|
||||
})
|
||||
it('should return undefined if no unread notices exist.', function () {
|
||||
var testList = [
|
||||
{id:0,read:true,title:"Past Notice"},
|
||||
{id:1,read:true,title:"Current Notice"},
|
||||
{id:2,read:true,title:"Future Notice"},
|
||||
]
|
||||
noticeController.setNoticesList(testList)
|
||||
var latestUnread = noticeController.getLatestUnreadNotice()
|
||||
assert.ok(!latestUnread)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
})
|
@ -13,6 +13,13 @@ var actions = {
|
||||
// remote state
|
||||
UPDATE_METAMASK_STATE: 'UPDATE_METAMASK_STATE',
|
||||
updateMetamaskState: updateMetamaskState,
|
||||
// notices
|
||||
MARK_NOTICE_READ: 'MARK_NOTICE_READ',
|
||||
markNoticeRead: markNoticeRead,
|
||||
SHOW_NOTICE: 'SHOW_NOTICE',
|
||||
showNotice: showNotice,
|
||||
CLEAR_NOTICES: 'CLEAR_NOTICES',
|
||||
clearNotices: clearNotices,
|
||||
// intialize screen
|
||||
AGREE_TO_DISCLAIMER: 'AGREE_TO_DISCLAIMER',
|
||||
agreeToDisclaimer: agreeToDisclaimer,
|
||||
@ -539,6 +546,43 @@ function goBackToInitView () {
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// notice
|
||||
//
|
||||
|
||||
function markNoticeRead (notice) {
|
||||
return (dispatch) => {
|
||||
dispatch(this.showLoadingIndication())
|
||||
background.markNoticeRead(notice, (err, notice) => {
|
||||
dispatch(this.hideLoadingIndication())
|
||||
if (err) {
|
||||
return dispatch(actions.showWarning(err))
|
||||
}
|
||||
if (notice) {
|
||||
return dispatch(actions.showNotice(notice))
|
||||
} else {
|
||||
dispatch(this.clearNotices())
|
||||
return {
|
||||
type: actions.SHOW_ACCOUNTS_PAGE,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function showNotice (notice) {
|
||||
return {
|
||||
type: actions.SHOW_NOTICE,
|
||||
value: notice,
|
||||
}
|
||||
}
|
||||
|
||||
function clearNotices () {
|
||||
return {
|
||||
type: actions.CLEAR_NOTICES,
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// config
|
||||
//
|
||||
|
@ -15,6 +15,8 @@ const AccountsScreen = require('./accounts')
|
||||
const AccountDetailScreen = require('./account-detail')
|
||||
const SendTransactionScreen = require('./send')
|
||||
const ConfirmTxScreen = require('./conf-tx')
|
||||
// notice
|
||||
const NoticeScreen = require('./notice')
|
||||
// other views
|
||||
const ConfigScreen = require('./config')
|
||||
const InfoScreen = require('./info')
|
||||
@ -40,6 +42,7 @@ function mapStateToProps (state) {
|
||||
// state from plugin
|
||||
isLoading: state.appState.isLoading,
|
||||
isDisclaimerConfirmed: state.metamask.isDisclaimerConfirmed,
|
||||
noActiveNotices: state.metamask.noActiveNotices,
|
||||
isInitialized: state.metamask.isInitialized,
|
||||
isUnlocked: state.metamask.isUnlocked,
|
||||
currentView: state.appState.currentView,
|
||||
@ -240,15 +243,6 @@ App.prototype.renderNetworkDropdown = function () {
|
||||
provider: props.provider,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Morden Test Network',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
action: () => props.dispatch(actions.setProviderType('morden')),
|
||||
icon: h('.menu-icon.red-dot'),
|
||||
activeNetworkRender: props.network,
|
||||
provider: props.provider,
|
||||
}),
|
||||
|
||||
h(DropMenuItem, {
|
||||
label: 'Localhost 8545',
|
||||
closeMenu: () => this.setState({ isNetworkMenuOpen: false }),
|
||||
@ -372,6 +366,10 @@ App.prototype.renderPrimary = function () {
|
||||
}
|
||||
}
|
||||
|
||||
if (!props.noActiveNotices) {
|
||||
return h(NoticeScreen, {key: 'NoticeScreen'})
|
||||
}
|
||||
|
||||
// show current view
|
||||
switch (props.currentView.name) {
|
||||
|
||||
|
@ -44,9 +44,6 @@ DropMenuItem.prototype.activeNetworkRender = function () {
|
||||
case 'Ropsten Test Network':
|
||||
if (provider.type === 'testnet') return h('.check', '✓')
|
||||
break
|
||||
case 'Morden Test Network':
|
||||
if (provider.type === 'morden') return h('.check', '✓')
|
||||
break
|
||||
case 'Localhost 8545':
|
||||
if (activeNetwork === 'http://localhost:8545') return h('.check', '✓')
|
||||
break
|
||||
|
@ -40,9 +40,6 @@ Network.prototype.render = function () {
|
||||
} else if (parseInt(networkNumber) === 3) {
|
||||
hoverText = 'Ropsten Test Network'
|
||||
iconName = 'ropsten-test-network'
|
||||
} else if (parseInt(networkNumber) === 2) {
|
||||
hoverText = 'Morden Test Network'
|
||||
iconName = 'morden-test-network'
|
||||
} else {
|
||||
hoverText = 'Unknown Private Network'
|
||||
iconName = 'unknown-private-network'
|
||||
@ -77,15 +74,6 @@ Network.prototype.render = function () {
|
||||
}},
|
||||
'Ropsten Test Net'),
|
||||
])
|
||||
case 'morden-test-network':
|
||||
return h('.network-indicator', [
|
||||
h('.menu-icon.red-dot'),
|
||||
h('.network-name', {
|
||||
style: {
|
||||
color: '#ff6666',
|
||||
}},
|
||||
'Morden Test Net'),
|
||||
])
|
||||
default:
|
||||
return h('.network-indicator', [
|
||||
h('i.fa.fa-question-circle.fa-lg', {
|
||||
|
@ -133,7 +133,7 @@ function currentProviderDisplay (metamaskState) {
|
||||
|
||||
case 'testnet':
|
||||
title = 'Current Network'
|
||||
value = 'Morden Test Network'
|
||||
value = 'Ropsten Test Network'
|
||||
break
|
||||
|
||||
default:
|
||||
|
@ -6,6 +6,8 @@ const actions = require('../actions')
|
||||
const ReactMarkdown = require('react-markdown')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const linker = require('extension-link-enabler')
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
const disclaimer = fs.readFileSync(path.join(__dirname, '..', '..', '..', 'USER_AGREEMENT.md')).toString()
|
||||
module.exports = connect(mapStateToProps)(DisclaimerScreen)
|
||||
|
||||
@ -98,3 +100,13 @@ DisclaimerScreen.prototype.render = function () {
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
DisclaimerScreen.prototype.componentDidMount = function () {
|
||||
var node = findDOMNode(this)
|
||||
linker.setupListener(node)
|
||||
}
|
||||
|
||||
DisclaimerScreen.prototype.componentWillUnmount = function () {
|
||||
var node = findDOMNode(this)
|
||||
linker.teardownListener(node)
|
||||
}
|
||||
|
118
ui/app/notice.js
Normal file
118
ui/app/notice.js
Normal file
@ -0,0 +1,118 @@
|
||||
const inherits = require('util').inherits
|
||||
const Component = require('react').Component
|
||||
const h = require('react-hyperscript')
|
||||
const ReactMarkdown = require('react-markdown')
|
||||
const connect = require('react-redux').connect
|
||||
const actions = require('./actions')
|
||||
const linker = require('extension-link-enabler')
|
||||
const findDOMNode = require('react-dom').findDOMNode
|
||||
|
||||
module.exports = connect(mapStateToProps)(Notice)
|
||||
|
||||
function mapStateToProps (state) {
|
||||
return {
|
||||
lastUnreadNotice: state.metamask.lastUnreadNotice,
|
||||
}
|
||||
}
|
||||
|
||||
inherits(Notice, Component)
|
||||
function Notice () {
|
||||
Component.call(this)
|
||||
}
|
||||
|
||||
Notice.prototype.render = function () {
|
||||
const props = this.props
|
||||
const title = props.lastUnreadNotice.title
|
||||
const date = props.lastUnreadNotice.date
|
||||
|
||||
return (
|
||||
h('.flex-column.flex-center.flex-grow', [
|
||||
h('h3.flex-center.text-transform-uppercacse.terms-header', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
textAlign: 'center',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
title,
|
||||
]),
|
||||
|
||||
h('h5.flex-center.text-transform-uppercacse.terms-header', {
|
||||
style: {
|
||||
background: '#EBEBEB',
|
||||
color: '#AEAEAE',
|
||||
marginBottom: 24,
|
||||
width: '100%',
|
||||
fontSize: '20px',
|
||||
textAlign: 'center',
|
||||
padding: 6,
|
||||
},
|
||||
}, [
|
||||
date,
|
||||
]),
|
||||
|
||||
h('style', `
|
||||
|
||||
.markdown {
|
||||
overflow-x: hidden;
|
||||
}
|
||||
.markdown h1, .markdown h2, .markdown h3 {
|
||||
margin: 10px 0;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.markdown strong {
|
||||
font-weight: bold;
|
||||
}
|
||||
.markdown em {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.markdown a {
|
||||
color: #df6b0e;
|
||||
}
|
||||
|
||||
`),
|
||||
|
||||
h('div.markdown', {
|
||||
style: {
|
||||
background: 'rgb(235, 235, 235)',
|
||||
height: '310px',
|
||||
padding: '6px',
|
||||
width: '90%',
|
||||
overflowY: 'scroll',
|
||||
scroll: 'auto',
|
||||
},
|
||||
}, [
|
||||
h(ReactMarkdown, {
|
||||
source: props.lastUnreadNotice.body,
|
||||
skipHtml: true,
|
||||
}),
|
||||
]),
|
||||
|
||||
h('button', {
|
||||
onClick: () => props.dispatch(actions.markNoticeRead(props.lastUnreadNotice)),
|
||||
style: {
|
||||
marginTop: '18px',
|
||||
},
|
||||
}, 'Continue'),
|
||||
])
|
||||
)
|
||||
}
|
||||
|
||||
Notice.prototype.componentDidMount = function () {
|
||||
var node = findDOMNode(this)
|
||||
linker.setupListener(node)
|
||||
}
|
||||
|
||||
Notice.prototype.componentWillUnmount = function () {
|
||||
var node = findDOMNode(this)
|
||||
linker.teardownListener(node)
|
||||
}
|
@ -248,6 +248,12 @@ function reduceApp (state, action) {
|
||||
forgottenPassword: false,
|
||||
})
|
||||
|
||||
case actions.SHOW_NOTICE:
|
||||
return extend(appState, {
|
||||
transForward: true,
|
||||
isLoading: false,
|
||||
})
|
||||
|
||||
case actions.REVEAL_ACCOUNT:
|
||||
return extend(appState, {
|
||||
scrollToBottom: true,
|
||||
|
@ -16,6 +16,8 @@ function reduceMetamask (state, action) {
|
||||
currentFiat: 'USD',
|
||||
conversionRate: 0,
|
||||
conversionDate: 'N/A',
|
||||
noActiveNotices: true,
|
||||
lastUnreadNotice: undefined,
|
||||
}, state.metamask)
|
||||
|
||||
switch (action.type) {
|
||||
@ -25,6 +27,17 @@ function reduceMetamask (state, action) {
|
||||
delete newState.seedWords
|
||||
return newState
|
||||
|
||||
case actions.SHOW_NOTICE:
|
||||
return extend(metamaskState, {
|
||||
noActiveNotices: false,
|
||||
lastUnreadNotice: action.value,
|
||||
})
|
||||
|
||||
case actions.CLEAR_NOTICES:
|
||||
return extend(metamaskState, {
|
||||
noActiveNotices: true,
|
||||
})
|
||||
|
||||
case actions.UPDATE_METAMASK_STATE:
|
||||
return extend(metamaskState, action.value)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user