mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-22 18:00:18 +01:00
Set up MVP for popup-based notifications.
This commit is contained in:
parent
f1986d7a37
commit
5479509618
11
app/notification.html
Normal file
11
app/notification.html
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>MetaMask Notification</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app-content"></div>
|
||||||
|
<script src="./scripts/notification.js" type="text/javascript" charset="utf-8"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -69,7 +69,7 @@ function showUnconfirmedTx (txParams, txData, onTxDoneCb) {
|
|||||||
|
|
||||||
extension.runtime.onConnect.addListener(connectRemote)
|
extension.runtime.onConnect.addListener(connectRemote)
|
||||||
function connectRemote (remotePort) {
|
function connectRemote (remotePort) {
|
||||||
var isMetaMaskInternalProcess = (remotePort.name === 'popup')
|
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
||||||
var portStream = new PortStream(remotePort)
|
var portStream = new PortStream(remotePort)
|
||||||
if (isMetaMaskInternalProcess) {
|
if (isMetaMaskInternalProcess) {
|
||||||
// communication with popup
|
// communication with popup
|
||||||
|
@ -18,142 +18,24 @@ const notifications = {
|
|||||||
module.exports = notifications
|
module.exports = notifications
|
||||||
window.METAMASK_NOTIFIER = notifications
|
window.METAMASK_NOTIFIER = notifications
|
||||||
|
|
||||||
setupListeners()
|
|
||||||
|
|
||||||
function setupListeners () {
|
|
||||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
|
||||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
|
||||||
|
|
||||||
// notification button press
|
|
||||||
extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) {
|
|
||||||
var handlers = notificationHandlers[notificationId]
|
|
||||||
if (buttonIndex === 0) {
|
|
||||||
handlers.confirm()
|
|
||||||
} else {
|
|
||||||
handlers.cancel()
|
|
||||||
}
|
|
||||||
extension.notifications.clear(notificationId)
|
|
||||||
})
|
|
||||||
|
|
||||||
// notification teardown
|
|
||||||
extension.notifications.onClosed.addListener(function (notificationId) {
|
|
||||||
delete notificationHandlers[notificationId]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// creation helper
|
|
||||||
function createUnlockRequestNotification (opts) {
|
function createUnlockRequestNotification (opts) {
|
||||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
showNotification()
|
||||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
|
||||||
var message = 'An Ethereum app has requested a signature. Please unlock your account.'
|
|
||||||
|
|
||||||
var id = createId()
|
|
||||||
extension.notifications.create(id, {
|
|
||||||
type: 'basic',
|
|
||||||
iconUrl: '/images/icon-128.png',
|
|
||||||
title: opts.title,
|
|
||||||
message: message,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTxNotification (state) {
|
function createTxNotification (state) {
|
||||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
showNotification()
|
||||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
|
||||||
|
|
||||||
renderTxNotificationSVG(state, function (err, notificationSvgSource) {
|
|
||||||
if (err) throw err
|
|
||||||
|
|
||||||
showNotification(extend(state, {
|
|
||||||
title: 'New Unsigned Transaction',
|
|
||||||
imageUrl: toSvgUri(notificationSvgSource),
|
|
||||||
}))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function createMsgNotification (state) {
|
function createMsgNotification (state) {
|
||||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
showNotification()
|
||||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
}
|
||||||
|
|
||||||
renderMsgNotificationSVG(state, function (err, notificationSvgSource) {
|
function showNotification() {
|
||||||
if (err) throw err
|
chrome.windows.create({
|
||||||
|
url:"notification.html",
|
||||||
showNotification(extend(state, {
|
type:"panel",
|
||||||
title: 'New Unsigned Message',
|
width:360,
|
||||||
imageUrl: toSvgUri(notificationSvgSource),
|
height:500,
|
||||||
}))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function showNotification (state) {
|
|
||||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
|
||||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
|
||||||
|
|
||||||
var id = createId()
|
|
||||||
extension.notifications.create(id, {
|
|
||||||
type: 'image',
|
|
||||||
requireInteraction: true,
|
|
||||||
iconUrl: '/images/icon-128.png',
|
|
||||||
imageUrl: state.imageUrl,
|
|
||||||
title: state.title,
|
|
||||||
message: '',
|
|
||||||
buttons: [{
|
|
||||||
title: 'Approve',
|
|
||||||
}, {
|
|
||||||
title: 'Reject',
|
|
||||||
}],
|
|
||||||
})
|
|
||||||
notificationHandlers[id] = {
|
|
||||||
confirm: state.onConfirm,
|
|
||||||
cancel: state.onCancel,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderTxNotificationSVG (state, cb) {
|
|
||||||
var content = h(PendingTxDetails, state)
|
|
||||||
renderNotificationSVG(content, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderMsgNotificationSVG (state, cb) {
|
|
||||||
var content = h(PendingMsgDetails, state)
|
|
||||||
renderNotificationSVG(content, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderNotificationSVG (content, cb) {
|
|
||||||
var container = document.createElement('div')
|
|
||||||
var confirmView = h('div.app-primary', {
|
|
||||||
style: {
|
|
||||||
width: '360px',
|
|
||||||
height: '240px',
|
|
||||||
padding: '16px',
|
|
||||||
// background: '#F7F7F7',
|
|
||||||
background: 'white',
|
|
||||||
},
|
|
||||||
}, [
|
|
||||||
h('style', MetaMaskUiCss()),
|
|
||||||
content,
|
|
||||||
])
|
|
||||||
|
|
||||||
render(confirmView, container, function ready() {
|
|
||||||
var rootElement = findDOMNode(this)
|
|
||||||
var viewSource = rootElement.outerHTML
|
|
||||||
unmountComponentAtNode(container)
|
|
||||||
var svgSource = svgWrapper(viewSource)
|
|
||||||
// insert content into svg wrapper
|
|
||||||
cb(null, svgSource)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
function svgWrapper (content) {
|
|
||||||
var wrapperSource = `
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="360" height="240">
|
|
||||||
<foreignObject x="0" y="0" width="100%" height="100%">
|
|
||||||
<body xmlns="http://www.w3.org/1999/xhtml" height="100%">{{content}}</body>
|
|
||||||
</foreignObject>
|
|
||||||
</svg>
|
|
||||||
`
|
|
||||||
return wrapperSource.split('{{content}}').join(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSvgUri (content) {
|
|
||||||
return 'data:image/svg+xml;utf8,' + encodeURIComponent(content)
|
|
||||||
}
|
|
||||||
|
85
app/scripts/notification.js
Normal file
85
app/scripts/notification.js
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
const url = require('url')
|
||||||
|
const EventEmitter = require('events').EventEmitter
|
||||||
|
const async = require('async')
|
||||||
|
const Dnode = require('dnode')
|
||||||
|
const Web3 = require('web3')
|
||||||
|
const MetaMaskNotification = require('../../ui/notification')
|
||||||
|
const MetaMaskUiCss = require('../../ui/css')
|
||||||
|
const injectCss = require('inject-css')
|
||||||
|
const PortStream = require('./lib/port-stream.js')
|
||||||
|
const StreamProvider = require('web3-stream-provider')
|
||||||
|
const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex
|
||||||
|
const extension = require('./lib/extension')
|
||||||
|
|
||||||
|
// setup app
|
||||||
|
var css = MetaMaskUiCss()
|
||||||
|
injectCss(css)
|
||||||
|
|
||||||
|
async.parallel({
|
||||||
|
currentDomain: getCurrentDomain,
|
||||||
|
accountManager: connectToAccountManager,
|
||||||
|
}, setupApp)
|
||||||
|
|
||||||
|
function connectToAccountManager (cb) {
|
||||||
|
// setup communication with background
|
||||||
|
var pluginPort = extension.runtime.connect({name: 'notification'})
|
||||||
|
var portStream = new PortStream(pluginPort)
|
||||||
|
// setup multiplexing
|
||||||
|
var mx = setupMultiplex(portStream)
|
||||||
|
// connect features
|
||||||
|
setupControllerConnection(mx.createStream('controller'), cb)
|
||||||
|
setupWeb3Connection(mx.createStream('provider'))
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWeb3Connection (stream) {
|
||||||
|
var remoteProvider = new StreamProvider()
|
||||||
|
remoteProvider.pipe(stream).pipe(remoteProvider)
|
||||||
|
stream.on('error', console.error.bind(console))
|
||||||
|
remoteProvider.on('error', console.error.bind(console))
|
||||||
|
global.web3 = new Web3(remoteProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupControllerConnection (stream, cb) {
|
||||||
|
var eventEmitter = new EventEmitter()
|
||||||
|
var background = Dnode({
|
||||||
|
sendUpdate: function (state) {
|
||||||
|
eventEmitter.emit('update', state)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
stream.pipe(background).pipe(stream)
|
||||||
|
background.once('remote', function (accountManager) {
|
||||||
|
// setup push events
|
||||||
|
accountManager.on = eventEmitter.on.bind(eventEmitter)
|
||||||
|
cb(null, accountManager)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCurrentDomain (cb) {
|
||||||
|
const unknown = '<unknown>'
|
||||||
|
if (!extension.tabs) return cb(null, unknown)
|
||||||
|
extension.tabs.query({active: true, currentWindow: true}, function (results) {
|
||||||
|
var activeTab = results[0]
|
||||||
|
var currentUrl = activeTab && activeTab.url
|
||||||
|
var currentDomain = url.parse(currentUrl).host
|
||||||
|
if (!currentUrl) {
|
||||||
|
return cb(null, unknown)
|
||||||
|
}
|
||||||
|
cb(null, currentDomain)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupApp (err, opts) {
|
||||||
|
if (err) {
|
||||||
|
alert(err.stack)
|
||||||
|
throw err
|
||||||
|
}
|
||||||
|
|
||||||
|
var container = document.getElementById('app-content')
|
||||||
|
|
||||||
|
MetaMaskNotification({
|
||||||
|
container: container,
|
||||||
|
accountManager: opts.accountManager,
|
||||||
|
currentDomain: opts.currentDomain,
|
||||||
|
networkVersion: opts.networkVersion,
|
||||||
|
})
|
||||||
|
}
|
@ -108,6 +108,7 @@ const jsFiles = [
|
|||||||
'contentscript',
|
'contentscript',
|
||||||
'background',
|
'background',
|
||||||
'popup',
|
'popup',
|
||||||
|
'notification',
|
||||||
]
|
]
|
||||||
|
|
||||||
jsFiles.forEach((jsFile) => {
|
jsFiles.forEach((jsFile) => {
|
||||||
@ -115,9 +116,9 @@ jsFiles.forEach((jsFile) => {
|
|||||||
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, filename: `${jsFile}.js` }))
|
gulp.task(`build:js:${jsFile}`, bundleTask({ watch: false, filename: `${jsFile}.js` }))
|
||||||
})
|
})
|
||||||
|
|
||||||
gulp.task('dev:js', gulp.parallel('dev:js:inpage','dev:js:contentscript','dev:js:background','dev:js:popup'))
|
gulp.task('dev:js', gulp.parallel('dev:js:inpage','dev:js:contentscript','dev:js:background','dev:js:popup', 'dev:js:notification'))
|
||||||
|
|
||||||
gulp.task('build:js', gulp.parallel('build:js:inpage','build:js:contentscript','build:js:background','build:js:popup'))
|
gulp.task('build:js', gulp.parallel('build:js:inpage','build:js:contentscript','build:js:background','build:js:popup', 'dev:js:notification'))
|
||||||
|
|
||||||
// clean dist
|
// clean dist
|
||||||
|
|
||||||
|
52
ui/notification.js
Normal file
52
ui/notification.js
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
const render = require('react-dom').render
|
||||||
|
const h = require('react-hyperscript')
|
||||||
|
const Root = require('./app/root')
|
||||||
|
const actions = require('./app/actions')
|
||||||
|
const configureStore = require('./app/store')
|
||||||
|
|
||||||
|
module.exports = launchApp
|
||||||
|
|
||||||
|
function launchApp (opts) {
|
||||||
|
var accountManager = opts.accountManager
|
||||||
|
actions._setAccountManager(accountManager)
|
||||||
|
|
||||||
|
// check if we are unlocked first
|
||||||
|
accountManager.getState(function (err, metamaskState) {
|
||||||
|
if (err) throw err
|
||||||
|
startApp(metamaskState, accountManager, opts)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function startApp (metamaskState, accountManager, opts) {
|
||||||
|
// parse opts
|
||||||
|
var store = configureStore({
|
||||||
|
|
||||||
|
// metamaskState represents the cross-tab state
|
||||||
|
metamask: metamaskState,
|
||||||
|
|
||||||
|
// appState represents the current tab's popup state
|
||||||
|
appState: {
|
||||||
|
currentDomain: opts.currentDomain,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Which blockchain we are using:
|
||||||
|
networkVersion: opts.networkVersion,
|
||||||
|
})
|
||||||
|
|
||||||
|
// if unconfirmed txs, start on txConf page
|
||||||
|
if (Object.keys(metamaskState.unconfTxs || {}).length) {
|
||||||
|
store.dispatch(actions.showConfTxPage())
|
||||||
|
}
|
||||||
|
|
||||||
|
accountManager.on('update', function (metamaskState) {
|
||||||
|
store.dispatch(actions.updateMetamaskState(metamaskState))
|
||||||
|
})
|
||||||
|
|
||||||
|
// start app
|
||||||
|
render(
|
||||||
|
h(Root, {
|
||||||
|
// inject initial state
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
), opts.container)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user