diff options
Implement some cross-browser practices (#473)
* Add mozilla plugin key to manifest
* Move all chrome references into platform-checking module
Addresses #453
* Add chrome global back to linter blacklist
* Add tests
-rw-r--r-- | .eslintignore | 1 | ||||
-rw-r--r-- | .eslintrc | 1 | ||||
-rw-r--r-- | app/manifest.json | 5 | ||||
-rw-r--r-- | app/scripts/background.js | 7 | ||||
-rw-r--r-- | app/scripts/chromereload.js | 3 | ||||
-rw-r--r-- | app/scripts/contentscript.js | 7 | ||||
-rw-r--r-- | app/scripts/lib/extension-instance.js | 38 | ||||
-rw-r--r-- | app/scripts/lib/extension.js | 14 | ||||
-rw-r--r-- | app/scripts/lib/notifications.js | 31 | ||||
-rw-r--r-- | app/scripts/metamask-controller.js | 7 | ||||
-rw-r--r-- | app/scripts/popup.js | 11 | ||||
-rw-r--r-- | test/unit/extension-test.js | 39 | ||||
-rw-r--r-- | ui/app/components/transaction-list-item.js | 3 | ||||
-rw-r--r-- | ui/app/info.js | 7 |
14 files changed, 139 insertions, 35 deletions
diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 000000000..df49525be --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +app/scripts/lib/extension-instance.js @@ -23,7 +23,6 @@ ], "globals": { - "chrome": true, "document": false, "navigator": false, "web3": true, diff --git a/app/manifest.json b/app/manifest.json index 2c5629f7a..4cca79c72 100644 --- a/app/manifest.json +++ b/app/manifest.json @@ -37,6 +37,11 @@ "all_frames": false } ], + "applications": { + "gecko": { + "id": "MOZILLA_EXTENSION_ID" + } + }, "permissions": [ "notifications", "storage", diff --git a/app/scripts/background.js b/app/scripts/background.js index 801dc95cf..34c994ab7 100644 --- a/app/scripts/background.js +++ b/app/scripts/background.js @@ -9,6 +9,7 @@ const createMsgNotification = require('./lib/notifications.js').createMsgNotific const messageManager = require('./lib/message-manager') const setupMultiplex = require('./lib/stream-utils.js').setupMultiplex const MetamaskController = require('./metamask-controller') +const extension = require('./lib/extension') const STORAGE_KEY = 'metamask-config' @@ -65,7 +66,7 @@ function showUnconfirmedTx (txParams, txData, onTxDoneCb) { // connect to other contexts // -chrome.runtime.onConnect.addListener(connectRemote) +extension.runtime.onConnect.addListener(connectRemote) function connectRemote (remotePort) { var isMetaMaskInternalProcess = (remotePort.name === 'popup') var portStream = new PortStream(remotePort) @@ -133,8 +134,8 @@ function updateBadge (state) { if (count) { label = String(count) } - chrome.browserAction.setBadgeText({ text: label }) - chrome.browserAction.setBadgeBackgroundColor({ color: '#506F8B' }) + extension.browserAction.setBadgeText({ text: label }) + extension.browserAction.setBadgeBackgroundColor({ color: '#506F8B' }) } function loadData () { diff --git a/app/scripts/chromereload.js b/app/scripts/chromereload.js index 283a131f1..cd85a8114 100644 --- a/app/scripts/chromereload.js +++ b/app/scripts/chromereload.js @@ -25,11 +25,12 @@ // if (e.data) { // var data = JSON.parse(e.data); // if (data && data.command === 'reload') { -// chrome.runtime.reload(); +// extension.runtime.reload(); // } // } // }; +const extension = require('./lib/extension') window.LiveReloadOptions = { host: 'localhost' }; (function e (t, n, r) { function s (o, u) { if (!n[o]) { if (!t[o]) { var a = typeof require === 'function' && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw f.code = 'MODULE_NOT_FOUND', f } var l = n[o] = {exports: {}}; t[o][0].call(l.exports, function (e) { var n = t[o][1][e]; return s(n ? n : e) }, l, l.exports, e, t, n, r) } return n[o].exports } var i = typeof require === 'function' && require; for (var o = 0; o < r.length; o++)s(r[o]); return s })({1: [function (require, module, exports) { diff --git a/app/scripts/contentscript.js b/app/scripts/contentscript.js index 60b37284e..0ffe93e3c 100644 --- a/app/scripts/contentscript.js +++ b/app/scripts/contentscript.js @@ -1,6 +1,7 @@ const LocalMessageDuplexStream = require('./lib/local-message-stream.js') const PortStream = require('./lib/port-stream.js') const ObjectMultiplex = require('./lib/obj-multiplex') +const extension = require('./lib/extension') if (shouldInjectWeb3()) { setupInjection() @@ -10,7 +11,7 @@ if (shouldInjectWeb3()) { function setupInjection(){ // inject in-page script var scriptTag = document.createElement('script') - scriptTag.src = chrome.extension.getURL('scripts/inpage.js') + scriptTag.src = extension.extension.getURL('scripts/inpage.js') scriptTag.onload = function () { this.parentNode.removeChild(this) } var container = document.head || document.documentElement // append as first child @@ -25,7 +26,7 @@ function setupStreams(){ target: 'inpage', }) pageStream.on('error', console.error.bind(console)) - var pluginPort = chrome.runtime.connect({name: 'contentscript'}) + var pluginPort = extension.runtime.connect({name: 'contentscript'}) var pluginStream = new PortStream(pluginPort) pluginStream.on('error', console.error.bind(console)) @@ -49,4 +50,4 @@ function setupStreams(){ function shouldInjectWeb3(){ var shouldInject = (window.location.href.indexOf('.pdf') === -1) return shouldInject -}
\ No newline at end of file +} diff --git a/app/scripts/lib/extension-instance.js b/app/scripts/lib/extension-instance.js new file mode 100644 index 000000000..eeab6c6d0 --- /dev/null +++ b/app/scripts/lib/extension-instance.js @@ -0,0 +1,38 @@ +const apis = [ + 'alarms', + 'bookmarks', + 'browserAction', + 'commands', + 'contextMenus', + 'cookies', + 'downloads', + 'events', + 'extension', + 'extensionTypes', + 'history', + 'i18n', + 'idle', + 'notifications', + 'pageAction', + 'runtime', + 'storage', + 'tabs', + 'webNavigation', + 'webRequest', + 'windows', +] + +function Extension () { + const _this = this + let global = window + + if (window.chrome) { + global = window.chrome + } + + apis.forEach(function (api) { + _this[api] = global[api] + }) +} + +module.exports = Extension diff --git a/app/scripts/lib/extension.js b/app/scripts/lib/extension.js new file mode 100644 index 000000000..4b670490f --- /dev/null +++ b/app/scripts/lib/extension.js @@ -0,0 +1,14 @@ +/* Extension.js + * + * A module for unifying browser differences in the WebExtension API. + * + * Initially implemented because Chrome hides all of their WebExtension API + * behind a global `chrome` variable, but we'd like to start grooming + * the code-base for cross-browser extension support. + * + * You can read more about the WebExtension API here: + * https://developer.mozilla.org/en-US/Add-ons/WebExtensions + */ + +const Extension = require('./extension-instance') +module.exports = new Extension() diff --git a/app/scripts/lib/notifications.js b/app/scripts/lib/notifications.js index b6590b0e5..6c1601df1 100644 --- a/app/scripts/lib/notifications.js +++ b/app/scripts/lib/notifications.js @@ -7,6 +7,7 @@ const h = require('react-hyperscript') const PendingTxDetails = require('../../../ui/app/components/pending-tx-details') const PendingMsgDetails = require('../../../ui/app/components/pending-msg-details') const MetaMaskUiCss = require('../../../ui/css') +const extension = require('./extension') var notificationHandlers = {} const notifications = { @@ -20,34 +21,34 @@ window.METAMASK_NOTIFIER = notifications setupListeners() function setupListeners () { - // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 - if (!chrome.notifications) return console.error('Chrome notifications API missing...') + // 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 - chrome.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) { + extension.notifications.onButtonClicked.addListener(function (notificationId, buttonIndex) { var handlers = notificationHandlers[notificationId] if (buttonIndex === 0) { handlers.confirm() } else { handlers.cancel() } - chrome.notifications.clear(notificationId) + extension.notifications.clear(notificationId) }) // notification teardown - chrome.notifications.onClosed.addListener(function (notificationId) { + extension.notifications.onClosed.addListener(function (notificationId) { delete notificationHandlers[notificationId] }) } // creation helper function createUnlockRequestNotification (opts) { - // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 - if (!chrome.notifications) return console.error('Chrome notifications API missing...') + // 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() - chrome.notifications.create(id, { + extension.notifications.create(id, { type: 'basic', iconUrl: '/images/icon-128.png', title: opts.title, @@ -56,8 +57,8 @@ function createUnlockRequestNotification (opts) { } function createTxNotification (state) { - // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 - if (!chrome.notifications) return console.error('Chrome notifications API missing...') + // 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 @@ -70,8 +71,8 @@ function createTxNotification (state) { } function createMsgNotification (state) { - // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 - if (!chrome.notifications) return console.error('Chrome notifications API missing...') + // guard for extension bug https://github.com/MetaMask/metamask-plugin/issues/236 + if (!extension.notifications) return console.error('Chrome notifications API missing...') renderMsgNotificationSVG(state, function (err, notificationSvgSource) { if (err) throw err @@ -84,11 +85,11 @@ function createMsgNotification (state) { } function showNotification (state) { - // guard for chrome bug https://github.com/MetaMask/metamask-plugin/issues/236 - if (!chrome.notifications) return console.error('Chrome notifications API missing...') + // 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() - chrome.notifications.create(id, { + extension.notifications.create(id, { type: 'image', requireInteraction: true, iconUrl: '/images/icon-128.png', diff --git a/app/scripts/metamask-controller.js b/app/scripts/metamask-controller.js index 63970799d..17af4cc29 100644 --- a/app/scripts/metamask-controller.js +++ b/app/scripts/metamask-controller.js @@ -6,6 +6,7 @@ const messageManager = require('./lib/message-manager') const HostStore = require('./lib/remote-store.js').HostStore const Web3 = require('web3') const ConfigManager = require('./lib/config-manager') +const extension = require('./lib/extension') module.exports = class MetamaskController { @@ -239,19 +240,19 @@ module.exports = class MetamaskController { // called from popup setRpcTarget (rpcTarget) { this.configManager.setRpcTarget(rpcTarget) - chrome.runtime.reload() + extension.runtime.reload() this.idStore.getNetwork() } setProviderType (type) { this.configManager.setProviderType(type) - chrome.runtime.reload() + extension.runtime.reload() this.idStore.getNetwork() } useEtherscanProvider () { this.configManager.useEtherscanProvider() - chrome.runtime.reload() + extension.runtime.reload() } } diff --git a/app/scripts/popup.js b/app/scripts/popup.js index 2e5b98896..20be15df7 100644 --- a/app/scripts/popup.js +++ b/app/scripts/popup.js @@ -9,6 +9,7 @@ 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() @@ -21,7 +22,7 @@ async.parallel({ function connectToAccountManager (cb) { // setup communication with background - var pluginPort = chrome.runtime.connect({name: 'popup'}) + var pluginPort = extension.runtime.connect({name: 'popup'}) var portStream = new PortStream(pluginPort) // setup multiplexing var mx = setupMultiplex(portStream) @@ -55,8 +56,8 @@ function setupControllerConnection (stream, cb) { function getCurrentDomain (cb) { const unknown = '<unknown>' - if (!chrome.tabs) return cb(null, unknown) - chrome.tabs.query({active: true, currentWindow: true}, function (results) { + 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 @@ -68,9 +69,9 @@ function getCurrentDomain (cb) { } function clearNotifications(){ - chrome.notifications.getAll(function (object) { + extension.notifications.getAll(function (object) { for (let notification in object){ - chrome.notifications.clear(notification) + extension.notifications.clear(notification) } }) } diff --git a/test/unit/extension-test.js b/test/unit/extension-test.js new file mode 100644 index 000000000..0a03a3b97 --- /dev/null +++ b/test/unit/extension-test.js @@ -0,0 +1,39 @@ +var assert = require('assert') +var sinon = require('sinon') +const ethUtil = require('ethereumjs-util') + +var path = require('path') +var Extension = require(path.join(__dirname, '..', '..', 'app', 'scripts', 'lib', 'extension-instance.js')) + +describe('extension', function() { + + describe('with chrome global', function() { + let extension + + beforeEach(function() { + window.chrome = { + alarms: 'foo' + } + extension = new Extension() + }) + + it('should use the chrome global apis', function() { + assert.equal(extension.alarms, 'foo') + }) + }) + + describe('without chrome global', function() { + let extension + + beforeEach(function() { + window.chrome = undefined + window.alarms = 'foo' + extension = new Extension() + }) + + it('should use the global apis', function() { + assert.equal(extension.alarms, 'foo') + }) + }) + +}) diff --git a/ui/app/components/transaction-list-item.js b/ui/app/components/transaction-list-item.js index 78867fca4..796ba61ae 100644 --- a/ui/app/components/transaction-list-item.js +++ b/ui/app/components/transaction-list-item.js @@ -7,6 +7,7 @@ const addressSummary = require('../util').addressSummary const explorerLink = require('../../lib/explorer-link') const CopyButton = require('./copyButton') const vreme = new (require('vreme')) +const extension = require('../../../app/scripts/lib/extension') const TransactionIcon = require('./transaction-list-item-icon') @@ -49,7 +50,7 @@ TransactionListItem.prototype.render = function () { if (!transaction.hash || !isLinkable) return var url = explorerLink(transaction.hash, parseInt(network)) - chrome.tabs.create({ url }) + extension.tabs.create({ url }) }, style: { padding: '20px 0', diff --git a/ui/app/info.js b/ui/app/info.js index d97998fd7..4e540bd03 100644 --- a/ui/app/info.js +++ b/ui/app/info.js @@ -3,6 +3,7 @@ const Component = require('react').Component const h = require('react-hyperscript') const connect = require('react-redux').connect const actions = require('./actions') +const extension = require('../../app/scripts/lib/extension') module.exports = connect(mapStateToProps)(InfoScreen) @@ -19,7 +20,7 @@ InfoScreen.prototype.render = function () { var state = this.props var manifest try { - manifest = chrome.runtime.getManifest() + manifest = extension.runtime.getManifest() } catch (e) { manifest = { version: '2.0.0' } } @@ -105,7 +106,7 @@ InfoScreen.prototype.render = function () { h('a.info', { target: '_blank', style: { width: '85vw' }, - onClick () { chrome.tabs.create({url: 'mailto:help@metamask.io?subject=Feedback'}) }, + onClick () { extension.tabs.create({url: 'mailto:help@metamask.io?subject=Feedback'}) }, }, 'Email us any questions or comments!'), ]), @@ -124,5 +125,5 @@ InfoScreen.prototype.render = function () { } InfoScreen.prototype.navigateTo = function (url) { - chrome.tabs.create({ url }) + extension.tabs.create({ url }) } |