mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-12-22 09:23:21 +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)
|
||||
function connectRemote (remotePort) {
|
||||
var isMetaMaskInternalProcess = (remotePort.name === 'popup')
|
||||
var isMetaMaskInternalProcess = remotePort.name === 'popup' || remotePort.name === 'notification'
|
||||
var portStream = new PortStream(remotePort)
|
||||
if (isMetaMaskInternalProcess) {
|
||||
// communication with popup
|
||||
|
@ -18,142 +18,24 @@ const notifications = {
|
||||
module.exports = 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) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
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,
|
||||
})
|
||||
showNotification()
|
||||
}
|
||||
|
||||
function createTxNotification (state) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
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),
|
||||
}))
|
||||
})
|
||||
showNotification()
|
||||
}
|
||||
|
||||
function createMsgNotification (state) {
|
||||
// guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236
|
||||
if (!extension.notifications) return console.error('Chrome notifications API missing...')
|
||||
showNotification()
|
||||
}
|
||||
|
||||
renderMsgNotificationSVG(state, function (err, notificationSvgSource) {
|
||||
if (err) throw err
|
||||
|
||||
showNotification(extend(state, {
|
||||
title: 'New Unsigned Message',
|
||||
imageUrl: toSvgUri(notificationSvgSource),
|
||||
}))
|
||||
function showNotification() {
|
||||
chrome.windows.create({
|
||||
url:"notification.html",
|
||||
type:"panel",
|
||||
width:360,
|
||||
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',
|
||||
'background',
|
||||
'popup',
|
||||
'notification',
|
||||
]
|
||||
|
||||
jsFiles.forEach((jsFile) => {
|
||||
@ -115,9 +116,9 @@ jsFiles.forEach((jsFile) => {
|
||||
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
|
||||
|
||||
|
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