Skip to content

Commit

Permalink
implement new EIP 1193 API
Browse files Browse the repository at this point in the history
send -> request
sendBatch -> batchRequest
add warnings for deprecated events
convert getExperimentalApi to class method
add log files to gitignore
bind all private methods
  • Loading branch information
rekmarks committed Apr 20, 2020
1 parent 9dde074 commit 9b345e2
Show file tree
Hide file tree
Showing 3 changed files with 139 additions and 43 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
node_modules/
*.log
161 changes: 124 additions & 37 deletions src/MetamaskInpageProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,25 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {

this.setMaxListeners(maxEventListeners)

// private state, kept here in part for use in the _metamask proxy
// private state
this._state = {
sentWarnings: {
// methods
enable: false,
experimentalMethods: false,
isConnected: false,
send: false,
sendAsync: false,
// events
events: {
chainIdChanged: false,
close: false,
networkChanged: false,
notification: false,
},
// misc
// TODO:deprecation:remove
autoReload: false,
sendSync: false,
},
isConnected: undefined,
accounts: undefined,
Expand All @@ -71,9 +80,11 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
// bind functions (to prevent e.g. [email protected] from making unbound calls)
this._handleAccountsChanged = this._handleAccountsChanged.bind(this)
this._handleDisconnect = this._handleDisconnect.bind(this)
this._rpcRequest = this._rpcRequest.bind(this)
this._legacySend = this._legacySend.bind(this)
this._rpcRequest = this._rpcRequest.bind(this)
this._warnOfDeprecation = this._warnOfDeprecation.bind(this)
this.enable = this.enable.bind(this)
this.request = this.request.bind(this)
this.send = this.send.bind(this)
this.sendAsync = this.sendAsync.bind(this)

Expand Down Expand Up @@ -140,7 +151,7 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
this._state.isConnected = true
})

// connect to async provider
// setup RPC connection

const jsonRpcConnection = createJsonRpcStream()
pump(
Expand All @@ -162,10 +173,16 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
if (payload.method === 'wallet_accountsChanged') {
this._handleAccountsChanged(payload.result)
} else if (EMITTED_NOTIFICATIONS.includes(payload.method)) {
this.emit('notification', payload)
this.emit('notification', payload) // deprecated
this.emit('message', {
type: payload.method,
data: payload.params,
})
}
})

// miscellanea

// send website metadata
if (shouldSendMetadata) {
const domContentLoadedHandler = () => {
Expand All @@ -176,7 +193,7 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
}

// indicate that we've connected, for EIP-1193 compliance
setTimeout(() => this.emit('connect'))
setTimeout(() => this.emit('connect', { chainId: this.chainId }))

// TODO:deprecation:remove
this._web3Ref = undefined
Expand All @@ -202,16 +219,62 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
// Public Methods
//====================

// TODO:deprecation deprecate this method in favor of 'request'
/**
* Backwards compatibility; ethereum.request() with JSON-RPC request object
* and a callback.
* Submits an RPC request to MetaMask for the given method, with the given params.
* Resolves with the result of the method call, or rejects on error.
*
* @param {Object} payload - The RPC request object.
* @param {Function} callback - The callback function.
* @param {Object} args - The RPC request arguments.
* @param {string} args.method - The RPC method name.
* @param {unknown} [args.params] - The parameters for the RPC method.
* @returns {Promise<unknown>} A Promise that resolves with the result of the RPC method,
* or rejects if an error is encountered.
*/
sendAsync (payload, cb) {
this._rpcRequest(payload, cb)
async request (args) {

if (typeof args !== 'object' || Array.isArray(args)) {
throw ethErrors.rpc.invalidRequest({
message: `Expected a single, non-array, object argument.`,
data: args,
})
}

const { method, params } = args

if (typeof method !== 'string' || !method) {
throw ethErrors.rpc.invalidRequest({
message: `'args.method' must be a non-empty string`,
data: args,
})
}

return new Promise((resolve, reject) => {
this._rpcRequest(
{ method, params },
getRpcPromiseCallback(resolve, reject),
)
})
}

/**
* We override the following event methods so that we can warn consumers
* about deprecated events:
* on, once
*/

/**
* @inheritdoc
*/
on (eventName, listener) {
this._warnOfDeprecation(eventName)
return super.on(eventName, listener)
}

/**
* @inheritdoc
*/
once (eventName, listener) {
this._warnOfDeprecation(eventName)
return super.once(eventName, listener)
}

//====================
Expand Down Expand Up @@ -261,11 +324,15 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
_handleDisconnect (streamName, err) {

logStreamDisconnectWarning.bind(this)(streamName, err)

const disconnectError = {
code: 1011,
reason: messages.errors.disconnected(),
}

if (this._state.isConnected) {
this.emit('close', {
code: 1011,
reason: 'MetaMask background communication error.',
})
this.emit('disconnect', disconnectError)
this.emit('close', disconnectError) // deprecated
}
this._state.isConnected = false
}
Expand Down Expand Up @@ -321,9 +388,22 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
}

/**
* Gets experimental _metamask API as Proxy.
* Warns of deprecation for the given event, if applicable.
*/
_warnOfDeprecation (eventName) {
if (this._state.sentWarnings.events[eventName] === false) {
console.warn(messages.warnings.events[eventName])
this._state.sentWarnings.events[eventName] = true
}
}

/**
* Constructor helper.
* Gets experimental _metamask API as Proxy, so that we can warn consumers
* about its experiment nature.
*/
_getExperimentalApi () {

return new Proxy(
{

Expand All @@ -342,12 +422,11 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
},

/**
* Make a batch request.
* Make a batch RPC request.
*/
// eslint-disable-next-line require-await
sendBatch: async (requests) => {
batchRequest: async (requests) => {

// basic input validation
if (!Array.isArray(requests)) {
throw ethErrors.rpc.invalidRequest({
message: 'Batch requests must be made with an array of request objects.',
Expand All @@ -356,20 +435,16 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
}

return new Promise((resolve, reject) => {
try {
this._rpcRequest(
requests,
getRpcPromiseCallback(resolve, reject),
)
} catch (error) {
reject(error)
}
this._rpcRequest(
requests,
getRpcPromiseCallback(resolve, reject),
)
})
},

// TODO:deprecation:remove isEnabled, isApproved
/**
* DEPRECATED. Scheduled for removal.
* DEPRECATED. To be removed.
* Synchronously determines if this domain is currently enabled, with a potential false negative if called to soon
*
* @returns {boolean} - returns true if this domain is currently enabled
Expand All @@ -379,7 +454,7 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
},

/**
* DEPRECATED. Scheduled for removal.
* DEPRECATED. To be removed.
* Asynchronously determines if this domain is currently enabled
*
* @returns {Promise<boolean>} - Promise resolving to true if this domain is currently enabled
Expand Down Expand Up @@ -488,15 +563,27 @@ module.exports = class MetamaskInpageProvider extends SafeEventEmitter {
}

/**
* TODO:deprecation:remove
* Internal backwards compatibility method.
* DEPRECATED
* Backwards compatibility; ethereum.request() with JSON-RPC request object
* and a callback.
*
* @param {Object} payload - The RPC request object.
* @param {Function} callback - The callback function.
*/
_legacySend (payload) {
sendAsync (payload, cb) {

if (!this._state.sentWarnings.sendSync) {
log.warn(messages.warnings.sendSyncDeprecation)
this._state.sentWarnings.sendSync = true
if (!this._state.sentWarnings.sendAsync) {
log.warn(messages.warnings.sendAsyncDeprecation)
this._state.sentWarnings.sendAsync = true
}
this._rpcRequest(payload, cb)
}

/**
* DEPRECATED
* Internal backwards compatibility method.
*/
_legacySend (payload) {

let result
switch (payload.method) {
Expand Down
20 changes: 14 additions & 6 deletions src/messages.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9b345e2

Please sign in to comment.