diff --git a/app/notification.html b/app/notification.html
new file mode 100644
index 000000000..927f1634c
--- /dev/null
+++ b/app/notification.html
@@ -0,0 +1,11 @@
+
+
+
+
+ MetaMask Notification
+
+
+
+
+
+
diff --git a/app/scripts/background.js b/app/scripts/background.js
index e04309e74..79f8f9fd9 100644
--- a/app/scripts/background.js
+++ b/app/scripts/background.js
@@ -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
diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js
index 6c1601df1..75fb60dd0 100644
--- a/app/scripts/lib/notifications.js
+++ b/app/scripts/lib/notifications.js
@@ -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 = `
-
- `
- return wrapperSource.split('{{content}}').join(content)
-}
-
-function toSvgUri (content) {
- return 'data:image/svg+xml;utf8,' + encodeURIComponent(content)
-}
diff --git a/app/scripts/notification.js b/app/scripts/notification.js
new file mode 100644
index 000000000..90c00b32d
--- /dev/null
+++ b/app/scripts/notification.js
@@ -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 = ''
+ 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,
+ })
+}
diff --git a/gulpfile.js b/gulpfile.js
index aeaf3e674..48f30835b 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -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
diff --git a/ui/notification.js b/ui/notification.js
new file mode 100644
index 000000000..8cf74f6ee
--- /dev/null
+++ b/ui/notification.js
@@ -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)
+}