import { Duplex } from 'stream' ; import { JsonRpcRequest , JsonRpcResponse } from 'json-rpc-engine' ; import { ethErrors } from 'eth-rpc-errors' ; import sendSiteMetadata from './siteMetadata' ; import messages from './messages' ; import { EMITTED_NOTIFICATIONS , getRpcPromiseCallback , NOOP } from './utils' ; import BaseProvider , { BaseProviderOptions , UnvalidatedJsonRpcRequest , } from './BaseProvider' ; export interface SendSyncJsonRpcRequest extends JsonRpcRequest < unknown > { method : 'eth_accounts' | 'eth_coinbase' | 'eth_uninstallFilter' | 'net_version' ; } type WarningEventName = keyof SentWarningsState [ 'events' ] ; export interface MetaMaskInpageProviderOptions extends BaseProviderOptions { /**
     * Whether the provider should send page metadata.
     */ shouldSendMetadata ? : boolean ; } interface SentWarningsState { enable : boolean ; experimentalMethods : boolean ; send : boolean ; events : { close : boolean ; data : boolean ; networkChanged : boolean ; notification : boolean ; } ; } export default class MetaMaskInpageProvider extends BaseProvider { protected _sentWarnings : SentWarningsState = { enable : false , experimentalMethods : false , send : false , events : { close : false , data : false , networkChanged : false , notification : false , } , } ; /**
     * Experimental methods can be found here.
     */ public readonly _metamask : ReturnType < MetaMaskInpageProvider [ '_getExperimentalApi' ] > ; public networkVersion : string | null ; /**
     * Indicating that this provider is a MetaMask provider.
     */ public readonly isMetaMask : true ; param connectionStream param options param options . jsonRpcStreamName param options . logger param options . maxEventListeners param options . shouldSendMetadata constructor ( connectionStream , { jsonRpcStreamName = 'metamask-provider' , logger = console , maxEventListeners = 100 , shouldSendMetadata = true , } = { } ) { super ( connectionStream , { jsonRpcStreamName , logger , maxEventListeners } ) ; this . networkVersion = null ; this . isMetaMask = true ; this . _sendSync = this . _sendSync . bind ( this ) ; this . enable = this . enable . bind ( this ) ; this . send = this . send . bind ( this ) ; this . sendAsync = this . sendAsync . bind ( this ) ; this . _warnOfDeprecation = this . _warnOfDeprecation . bind ( this ) ; this . _metamask = this . _getExperimentalApi ( ) ; this . _jsonRpcConnection . events . on ( 'notification' , ( payload ) => { const { method } = payload ; if ( EMITTED_NOTIFICATIONS . includes ( method ) ) { this . emit ( 'data' , payload ) ; this . emit ( 'notification' , payload . params . result ) ; } } ) ; if ( shouldSendMetadata ) { if ( document . readyState === 'complete' ) { sendSiteMetadata ( this . _rpcEngine , this . _log ) ; } else { const domContentLoadedHandler = ( ) => { sendSiteMetadata ( this . _rpcEngine , this . _log ) ; window . removeEventListener ( 'DOMContentLoaded' , domContentLoadedHandler ) ; } ; window . addEventListener ( 'DOMContentLoaded' , domContentLoadedHandler ) ; } } } param payload param cb sendAsync ( payload , callback ) { this . _rpcRequest ( payload , callback ) ; } /**
     * We override the following event methods so that we can warn consumers
     * about deprecated events:
     *   addListener, on, once, prependListener, prependOnceListener
     */ addListener ( eventName , listener ) { this . _warnOfDeprecation ( eventName ) ; return super . addListener ( eventName , listener ) ; } on ( eventName , listener ) { this . _warnOfDeprecation ( eventName ) ; return super . on ( eventName , listener ) ; } once ( eventName , listener ) { this . _warnOfDeprecation ( eventName ) ; return super . once ( eventName , listener ) ; } prependListener ( eventName , listener ) { this . _warnOfDeprecation ( eventName ) ; return super . prependListener ( eventName , listener ) ; } prependOnceListener ( eventName , listener ) { this . _warnOfDeprecation ( eventName ) ; return super . prependOnceListener ( eventName , listener ) ; } param isRecoverable param errorMessage emits protected _handleDisconnect ( isRecoverable , errorMessage ? ) { super . _handleDisconnect ( isRecoverable , errorMessage ) ; if ( this . networkVersion && ! isRecoverable ) { this . networkVersion = null ; } } /**
     * Warns of deprecation for the given event, if applicable.
     */ protected _warnOfDeprecation ( eventName ) { if ( this . _sentWarnings ?. events [ eventName as WarningEventName ] === false ) { this . _log . warn ( messages . warnings . events [ eventName as WarningEventName ] ) ; this . _sentWarnings . events [ eventName as WarningEventName ] = true ; } } deprecated returns enable ( ) { if ( ! this . _sentWarnings . enable ) { this . _log . warn ( messages . warnings . enableDeprecation ) ; this . _sentWarnings . enable = true ; } return new Promise < string [ ] > ( ( resolve , reject ) => { try { this . _rpcRequest ( { method : 'eth_requestAccounts' , params : [ ] } , getRpcPromiseCallback ( resolve , reject ) ) ; } catch ( error ) { reject ( error ) ; } } ) ; } deprecated param method param params returns send < T > ( method , params ? ) ; deprecated param payload param callback send < T > ( payload , callback ) ; deprecated param payload returns send < T > ( payload ) ; send ( methodOrPayload , callbackOrArgs ? ) { if ( ! this . _sentWarnings . send ) { this . _log . warn ( messages . warnings . sendDeprecation ) ; this . _sentWarnings . send = true ; } if ( typeof methodOrPayload === 'string' && ( ! callbackOrArgs || Array . isArray ( callbackOrArgs ) ) ) { return new Promise ( ( resolve , reject ) => { try { this . _rpcRequest ( { method : methodOrPayload , params : callbackOrArgs } , getRpcPromiseCallback ( resolve , reject , false ) ) ; } catch ( error ) { reject ( error ) ; } } ) ; } else if ( methodOrPayload && typeof methodOrPayload === 'object' && typeof callbackOrArgs === 'function' ) { return this . _rpcRequest ( methodOrPayload as JsonRpcRequest < unknown > , callbackOrArgs as ( ... args : unknown [ ] ) => void ) ; } return this . _sendSync ( methodOrPayload as SendSyncJsonRpcRequest ) ; } deprecated protected _sendSync ( payload ) { let result ; switch ( payload . method ) { case 'eth_accounts' : result = this . selectedAddress ? [ this . selectedAddress ] : [ ] ; break ; case 'eth_coinbase' : result = this . selectedAddress || null ; break ; case 'eth_uninstallFilter' : this . _rpcRequest ( payload , NOOP ) ; result = true ; break ; case 'net_version' : result = this . networkVersion || null ; break ; default : throw new Error ( messages . errors . unsupportedSync ( payload . method ) ) ; } return { id : payload . id , jsonrpc : payload . jsonrpc , result , } ; } /**
     * Constructor helper.
     * Gets experimental _metamask API as Proxy, so that we can warn consumers
     * about its experiment nature.
     */ protected _getExperimentalApi ( ) { return new Proxy ( { returns isUnlocked : async ( ) => { if ( ! this . _state . initialized ) { await new Promise < void > ( ( resolve ) => { this . on ( '_initialized' , ( ) => resolve ( ) ) ; } ) ; } return this . _state . isUnlocked ; } , /**
             * Make a batch RPC request.
             */ requestBatch : async ( requests ) => { if ( ! Array . isArray ( requests ) ) { throw ethErrors . rpc . invalidRequest ( { message : 'Batch requests must be made with an array of request objects.' , data : requests , } ) ; } return new Promise ( ( resolve , reject ) => { this . _rpcRequest ( requests , getRpcPromiseCallback ( resolve , reject ) ) ; } ) ; } , } , { get : ( obj , prop , ... args ) => { if ( ! this . _sentWarnings . experimentalMethods ) { this . _log . warn ( messages . warnings . experimentalMethods ) ; this . _sentWarnings . experimentalMethods = true ; } return Reflect . get ( obj , prop , ... args ) ; } , } ) ; } emits emits param networkInfo param networkInfo . chainId param networkInfo . networkVersion protected _handleChainChanged ( { chainId , networkVersion , } = { } ) { super . _handleChainChanged ( { chainId , networkVersion } ) ; if ( networkVersion && networkVersion !== 'loading' && networkVersion !== this . networkVersion ) { this . networkVersion = networkVersion ; if ( this . _state . initialized ) { this . emit ( 'networkChanged' , this . networkVersion ) ; } } } }