Merged master into dev

This commit is contained in:
Dan Finlay 2016-12-19 13:35:43 -08:00
commit 77d2deb176
25 changed files with 626 additions and 54 deletions

3
.gitignore vendored
View File

@ -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

View File

@ -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

View File

@ -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",

View File

@ -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,
},
}

View File

@ -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) {

View File

@ -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)
}
}

View 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)
}
}

View 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
View 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

View 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": {}
}

View 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
View 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

View File

@ -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",

View File

@ -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

View 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)
})
})
})
})

View File

@ -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
//

View File

@ -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) {

View File

@ -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

View File

@ -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', {

View File

@ -133,7 +133,7 @@ function currentProviderDisplay (metamaskState) {
case 'testnet':
title = 'Current Network'
value = 'Morden Test Network'
value = 'Ropsten Test Network'
break
default:

View File

@ -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
View 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)
}

View File

@ -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,

View File

@ -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)