1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-22 18:00:18 +01:00

EIP-1193: standard provider API (#6170)

* EIP-1193: Implement new provider API

* EIP-1193: Updated implementation

* Remove test file

* Fix tests

* Update ping check

* Update logic

* PR feedback
This commit is contained in:
Paul Bouchon 2019-02-19 19:42:08 -05:00 committed by Dan Finlay
parent 1eebe54c64
commit 2f7d449427
9 changed files with 138 additions and 23 deletions

View File

@ -158,7 +158,7 @@ function listenForProviderRequest () {
window.postMessage({ type: 'ethereumproviderlegacy', selectedAddress }, '*') window.postMessage({ type: 'ethereumproviderlegacy', selectedAddress }, '*')
break break
case 'reject-provider-request': case 'reject-provider-request':
window.postMessage({ type: 'ethereumprovider', error: 'User rejected provider access' }, '*') window.postMessage({ type: 'ethereumprovider', error: 'User denied account authorization' }, '*')
break break
case 'answer-is-approved': case 'answer-is-approved':
window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*') window.postMessage({ type: 'ethereumisapproved', isApproved, caching }, '*')
@ -170,6 +170,11 @@ function listenForProviderRequest () {
isEnabled = false isEnabled = false
window.postMessage({ type: 'metamasksetlocked' }, '*') window.postMessage({ type: 'metamasksetlocked' }, '*')
break break
case 'ethereum-ping-success':
window.postMessage({ type: 'ethereumpingsuccess' }, '*')
break
case 'ethereum-ping-error':
window.postMessage({ type: 'ethereumpingerror' }, '*')
} }
}) })
} }

View File

@ -0,0 +1,19 @@
const BlockTracker = require('eth-block-tracker')
/**
* Creates a block tracker that sends platform events on success and failure
*/
module.exports = function createBlockTracker (args, platform) {
const blockTracker = new BlockTracker(args)
blockTracker.on('latest', () => {
if (platform && platform.sendMessage) {
platform.sendMessage({ action: 'ethereum-ping-success' })
}
})
blockTracker.on('error', () => {
if (platform && platform.sendMessage) {
platform.sendMessage({ action: 'ethereum-ping-error' })
}
})
return blockTracker
}

View File

@ -7,14 +7,14 @@ const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const createInfuraMiddleware = require('eth-json-rpc-infura') const createInfuraMiddleware = require('eth-json-rpc-infura')
const BlockTracker = require('eth-block-tracker') const createBlockTracker = require('./createBlockTracker')
module.exports = createInfuraClient module.exports = createInfuraClient
function createInfuraClient ({ network }) { function createInfuraClient ({ network, platform }) {
const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' }) const infuraMiddleware = createInfuraMiddleware({ network, maxAttempts: 5, source: 'metamask' })
const infuraProvider = providerFromMiddleware(infuraMiddleware) const infuraProvider = providerFromMiddleware(infuraMiddleware)
const blockTracker = new BlockTracker({ provider: infuraProvider }) const blockTracker = createBlockTracker({ provider: infuraProvider }, platform)
const networkMiddleware = mergeMiddleware([ const networkMiddleware = mergeMiddleware([
createNetworkAndChainIdMiddleware({ network }), createNetworkAndChainIdMiddleware({ network }),

View File

@ -5,14 +5,14 @@ const createBlockCacheMiddleware = require('eth-json-rpc-middleware/block-cache'
const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache') const createInflightMiddleware = require('eth-json-rpc-middleware/inflight-cache')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const BlockTracker = require('eth-block-tracker') const createBlockTracker = require('./createBlockTracker')
module.exports = createJsonRpcClient module.exports = createJsonRpcClient
function createJsonRpcClient ({ rpcUrl }) { function createJsonRpcClient ({ rpcUrl, platform }) {
const fetchMiddleware = createFetchMiddleware({ rpcUrl }) const fetchMiddleware = createFetchMiddleware({ rpcUrl })
const blockProvider = providerFromMiddleware(fetchMiddleware) const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = new BlockTracker({ provider: blockProvider }) const blockTracker = createBlockTracker({ provider: blockProvider }, platform)
const networkMiddleware = mergeMiddleware([ const networkMiddleware = mergeMiddleware([
createBlockRefRewriteMiddleware({ blockTracker }), createBlockRefRewriteMiddleware({ blockTracker }),

View File

@ -3,14 +3,14 @@ const createFetchMiddleware = require('eth-json-rpc-middleware/fetch')
const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite') const createBlockRefRewriteMiddleware = require('eth-json-rpc-middleware/block-ref-rewrite')
const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector') const createBlockTrackerInspectorMiddleware = require('eth-json-rpc-middleware/block-tracker-inspector')
const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware') const providerFromMiddleware = require('eth-json-rpc-middleware/providerFromMiddleware')
const BlockTracker = require('eth-block-tracker') const createBlockTracker = require('./createBlockTracker')
module.exports = createLocalhostClient module.exports = createLocalhostClient
function createLocalhostClient () { function createLocalhostClient ({ platform }) {
const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' }) const fetchMiddleware = createFetchMiddleware({ rpcUrl: 'http://localhost:8545/' })
const blockProvider = providerFromMiddleware(fetchMiddleware) const blockProvider = providerFromMiddleware(fetchMiddleware)
const blockTracker = new BlockTracker({ provider: blockProvider, pollingInterval: 1000 }) const blockTracker = createBlockTracker({ provider: blockProvider, pollingInterval: 1000 }, platform)
const networkMiddleware = mergeMiddleware([ const networkMiddleware = mergeMiddleware([
createBlockRefRewriteMiddleware({ blockTracker }), createBlockRefRewriteMiddleware({ blockTracker }),

View File

@ -37,8 +37,9 @@ const defaultNetworkConfig = {
module.exports = class NetworkController extends EventEmitter { module.exports = class NetworkController extends EventEmitter {
constructor (opts = {}) { constructor (opts = {}, platform) {
super() super()
this.platform = platform
// parse options // parse options
const providerConfig = opts.provider || defaultProviderConfig const providerConfig = opts.provider || defaultProviderConfig
@ -180,7 +181,7 @@ module.exports = class NetworkController extends EventEmitter {
_configureInfuraProvider ({ type }) { _configureInfuraProvider ({ type }) {
log.info('NetworkController - configureInfuraProvider', type) log.info('NetworkController - configureInfuraProvider', type)
const networkClient = createInfuraClient({ network: type }) const networkClient = createInfuraClient({ network: type, platform: this.platform })
this._setNetworkClient(networkClient) this._setNetworkClient(networkClient)
// setup networkConfig // setup networkConfig
var settings = { var settings = {
@ -191,13 +192,13 @@ module.exports = class NetworkController extends EventEmitter {
_configureLocalhostProvider () { _configureLocalhostProvider () {
log.info('NetworkController - configureLocalhostProvider') log.info('NetworkController - configureLocalhostProvider')
const networkClient = createLocalhostClient() const networkClient = createLocalhostClient({ platform: this.platform })
this._setNetworkClient(networkClient) this._setNetworkClient(networkClient)
} }
_configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) { _configureStandardProvider ({ rpcUrl, chainId, ticker, nickname }) {
log.info('NetworkController - configureStandardProvider', rpcUrl) log.info('NetworkController - configureStandardProvider', rpcUrl)
const networkClient = createJsonRpcClient({ rpcUrl }) const networkClient = createJsonRpcClient({ rpcUrl, platform: this.platform })
// hack to add a 'rpc' network with chainId // hack to add a 'rpc' network with chainId
networks.networkList['rpc'] = { networks.networkList['rpc'] = {
chainId: chainId, chainId: chainId,

View File

@ -0,0 +1,92 @@
class StandardProvider {
_isConnected
_provider
constructor (provider) {
this._provider = provider
this._onMessage('ethereumpingerror', this._onClose.bind(this))
this._onMessage('ethereumpingsuccess', this._onConnect.bind(this))
window.addEventListener('load', () => {
this._subscribe()
this._ping()
})
}
_onMessage (type, handler) {
window.addEventListener('message', function ({ data }) {
if (!data || data.type !== type) return
handler.apply(this, arguments)
})
}
_onClose () {
if (this._isConnected === undefined || this._isConnected) {
this._provider.emit('close', {
code: 1011,
reason: 'Network connection error',
})
}
this._isConnected = false
}
_onConnect () {
!this._isConnected && this._provider.emit('connect')
this._isConnected = true
}
async _ping () {
try {
await this.send('net_version')
window.postMessage({ type: 'ethereumpingsuccess' }, '*')
} catch (error) {
window.postMessage({ type: 'ethereumpingerror' }, '*')
}
}
_subscribe () {
this._provider.on('data', (error, { method, params }) => {
if (!error && method === 'eth_subscription') {
this._provider.emit('notification', params.result)
}
})
}
/**
* Initiate an RPC method call
*
* @param {string} method - RPC method name to call
* @param {string[]} params - Array of RPC method parameters
* @returns {Promise<*>} Promise resolving to the result if successful
*/
send (method, params = []) {
if (method === 'eth_requestAccounts') return this._provider.enable()
return new Promise((resolve, reject) => {
try {
this._provider.sendAsync({ method, params, beta: true }, (error, response) => {
error = error || response.error
error ? reject(error) : resolve(response)
})
} catch (error) {
reject(error)
}
})
}
}
/**
* Converts a legacy provider into an EIP-1193-compliant standard provider
* @param {Object} provider - Legacy provider to convert
* @returns {Object} Standard provider
*/
export default function createStandardProvider (provider) {
const standardProvider = new StandardProvider(provider)
const sendLegacy = provider.send
provider.send = (methodOrPayload, callbackOrArgs) => {
if (typeof methodOrPayload === 'string' && !callbackOrArgs || Array.isArray(callbackOrArgs)) {
return standardProvider.send(methodOrPayload, callbackOrArgs)
}
return sendLegacy.call(provider, methodOrPayload, callbackOrArgs)
}
return provider
}

View File

@ -5,6 +5,7 @@ const log = require('loglevel')
const LocalMessageDuplexStream = require('post-message-stream') const LocalMessageDuplexStream = require('post-message-stream')
const setupDappAutoReload = require('./lib/auto-reload.js') const setupDappAutoReload = require('./lib/auto-reload.js')
const MetamaskInpageProvider = require('metamask-inpage-provider') const MetamaskInpageProvider = require('metamask-inpage-provider')
const createStandardProvider = require('./createStandardProvider').default
let isEnabled = false let isEnabled = false
let warned = false let warned = false
@ -16,12 +17,6 @@ restoreContextAfterImports()
log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn') log.setDefaultLevel(process.env.METAMASK_DEBUG ? 'debug' : 'warn')
console.warn('ATTENTION: In an effort to improve user privacy, MetaMask ' +
'stopped exposing user accounts to dapps if "privacy mode" is enabled on ' +
'November 2nd, 2018. Dapps should now call provider.enable() in order to view and use ' +
'accounts. Please see https://bit.ly/2QQHXvF for complete information and up-to-date ' +
'example code.')
/** /**
* Adds a postMessage listener for a specific message type * Adds a postMessage listener for a specific message type
* *
@ -70,7 +65,10 @@ inpageProvider.enable = function ({ force } = {}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
providerHandle = ({ data: { error, selectedAddress } }) => { providerHandle = ({ data: { error, selectedAddress } }) => {
if (typeof error !== 'undefined') { if (typeof error !== 'undefined') {
reject(error) reject({
message: error,
code: 4001,
})
} else { } else {
window.removeEventListener('message', providerHandle) window.removeEventListener('message', providerHandle)
setTimeout(() => { setTimeout(() => {
@ -155,7 +153,7 @@ const proxiedInpageProvider = new Proxy(inpageProvider, {
deleteProperty: () => true, deleteProperty: () => true,
}) })
window.ethereum = proxiedInpageProvider window.ethereum = createStandardProvider(proxiedInpageProvider)
// detect eth_requestAccounts and pipe to enable for now // detect eth_requestAccounts and pipe to enable for now
function detectAccountRequest (method) { function detectAccountRequest (method) {

View File

@ -86,7 +86,7 @@ module.exports = class MetamaskController extends EventEmitter {
this.createVaultMutex = new Mutex() this.createVaultMutex = new Mutex()
// network store // network store
this.networkController = new NetworkController(initState.NetworkController) this.networkController = new NetworkController(initState.NetworkController, this.platform)
// preferences controller // preferences controller
this.preferencesController = new PreferencesController({ this.preferencesController = new PreferencesController({