From ea0b419f6352a82f3724faefd5be4023ff7ee508 Mon Sep 17 00:00:00 2001 From: Erik Marks <25517051+rekmarks@users.noreply.github.com> Date: Mon, 20 Apr 2020 11:23:31 -0700 Subject: [PATCH] Add global initialization event (#31) * add global initialization event * add init function Co-authored-by: Mark Stacey --- index.js | 9 +++++- src/MetamaskInpageProvider.js | 19 ++++++++++- src/initProvider.js | 59 +++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/initProvider.js diff --git a/index.js b/index.js index cc5f77f2..aa1fd4a8 100644 --- a/index.js +++ b/index.js @@ -1 +1,8 @@ -module.exports = require('./src/MetamaskInpageProvider') +const MetamaskInpageProvider = require('./src/MetamaskInpageProvider') +const { initProvider, setGlobalProvider } = require('./src/initProvider') + +module.exports = { + MetamaskInpageProvider, + initProvider, + setGlobalProvider, +} diff --git a/src/MetamaskInpageProvider.js b/src/MetamaskInpageProvider.js index ba43842d..5a6d9d27 100644 --- a/src/MetamaskInpageProvider.js +++ b/src/MetamaskInpageProvider.js @@ -22,12 +22,29 @@ const { module.exports = class MetamaskInpageProvider extends SafeEventEmitter { - constructor (connectionStream, shouldSendMetadata = true) { + /** + * @param {Object} connectionStream - A Node.js stream + * @param {Object} opts - An options bag + * @param {number} opts.maxEventListeners - The maximum number of event listeners + * @param {boolean} opts.shouldSendMetadata - Whether the provider should send page metadata + */ + constructor ( + connectionStream, + { shouldSendMetadata = true, maxEventListeners = 100 }, + ) { + + if ( + typeof shouldSendMetadata !== 'boolean' || typeof maxEventListeners !== 'number' + ) { + throw new Error('Invalid options.') + } super() this.isMetaMask = true + this.setMaxListeners(maxEventListeners) + // private state, kept here in part for use in the _metamask proxy this._state = { sentWarnings: { diff --git a/src/initProvider.js b/src/initProvider.js new file mode 100644 index 00000000..6a0096f0 --- /dev/null +++ b/src/initProvider.js @@ -0,0 +1,59 @@ +const MetamaskInpageProvider = require('./MetamaskInpageProvider') + +/** + * Initializes a MetamaskInpageProvider and (optionally) sets it on window.ethereum. + * + * @param {Object} opts - An options bag. + * @param {Object} opts.connectionStream - A Node.js stream. + * @param {number} opts.maxEventListeners - The maximum number of event listeners. + * @param {boolean} opts.preventPropertyDeletion - Whether to wrap the provider in a proxy that prevents property deletion. + * @param {boolean} opts.shouldSendMetadata - Whether the provider should send page metadata. + * @param {boolean} opts.shouldSetOnWindow - Whether the provider should be set as window.ethereum + * @returns {MetamaskInpageProvider | Proxy} The initialized provider (whether set or not). + */ +function initProvider ({ + connectionStream, + maxEventListeners = 100, + preventPropertyDeletion = true, + shouldSendMetadata = true, + shouldSetOnWindow = true, +}) { + + if (!connectionStream) { + throw new Error('Must provide a connection stream.') + } + + let provider = new MetamaskInpageProvider( + connectionStream, { shouldSendMetadata, maxEventListeners }, + ) + + if (preventPropertyDeletion) { + // Workaround for web3@1.0 deleting the bound `sendAsync` but not the unbound + // `sendAsync` method on the prototype, causing `this` reference issues + provider = new Proxy(provider, { + deleteProperty: () => true, + }) + } + + if (shouldSetOnWindow) { + setGlobalProvider(provider) + } + + return provider +} + +/** + * Sets the given provider instance as window.ethereum and dispatches the + * 'ethereum#initialized' event on window. + * + * @param {MetamaskInpageProvider} providerInstance - The provider instance. + */ +function setGlobalProvider (providerInstance) { + window.ethereum = providerInstance + window.dispatchEvent(new Event('ethereum#initialized')) +} + +module.exports = { + initProvider, + setGlobalProvider, +}