UNPKG

@metamask/providers

Version:

A JavaScript Ethereum provider that connects to the wallet over a stream

1 lines 10.2 kB
{"version":3,"file":"StreamProvider.mjs","sourceRoot":"","sources":["../src/StreamProvider.ts"],"names":[],"mappings":"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","sourcesContent":["import type { JsonRpcMiddleware } from '@metamask/json-rpc-engine';\nimport { createStreamMiddleware } from '@metamask/json-rpc-middleware-stream';\nimport type SafeEventEmitter from '@metamask/safe-event-emitter';\nimport type { Json, JsonRpcParams } from '@metamask/utils';\nimport { duplex as isDuplex } from 'is-stream';\nimport { pipeline } from 'readable-stream';\nimport type { Duplex } from 'readable-stream';\n\nimport type { BaseProviderOptions } from './BaseProvider';\nimport { BaseProvider } from './BaseProvider';\nimport messages from './messages';\nimport {\n EMITTED_NOTIFICATIONS,\n isValidChainId,\n isValidNetworkVersion,\n} from './utils';\n\nexport type StreamProviderOptions = BaseProviderOptions;\n\nexport type JsonRpcConnection = {\n events: SafeEventEmitter;\n middleware: JsonRpcMiddleware<JsonRpcParams, Json>;\n stream: Duplex;\n};\n\n/**\n * An abstract EIP-1193 provider wired to some duplex stream via a\n * `json-rpc-middleware-stream` JSON-RPC stream middleware. Implementers must\n * call {@link AbstractStreamProvider._initializeStateAsync} after instantiation\n * to initialize the provider's state.\n */\nexport abstract class AbstractStreamProvider extends BaseProvider {\n protected _jsonRpcConnection: JsonRpcConnection;\n\n /**\n * Creates a new AbstractStreamProvider instance.\n *\n * @param connectionStream - A Node.js duplex stream.\n * @param options - An options bag.\n * @param options.logger - The logging API to use. Default: `console`.\n * @param options.maxEventListeners - The maximum number of event\n * listeners. Default: 100.\n * @param options.rpcMiddleware - The RPC middleware stack to use.\n */\n constructor(\n connectionStream: Duplex,\n {\n logger = console,\n maxEventListeners = 100,\n rpcMiddleware = [],\n }: StreamProviderOptions = {},\n ) {\n super({ logger, maxEventListeners, rpcMiddleware });\n\n if (!isDuplex(connectionStream)) {\n throw new Error(messages.errors.invalidDuplexStream());\n }\n\n // Bind functions to prevent consumers from making unbound calls\n this._handleStreamDisconnect = this._handleStreamDisconnect.bind(this);\n\n // Set up RPC connection\n // Typecast: The type of `Duplex` is incompatible with the type of\n // `JsonRpcConnection`.\n this._jsonRpcConnection = createStreamMiddleware({\n retryOnMessage: 'METAMASK_EXTENSION_CONNECT_CAN_RETRY',\n }) as unknown as JsonRpcConnection;\n\n pipeline(\n connectionStream,\n this._jsonRpcConnection.stream,\n connectionStream,\n this._handleStreamDisconnect.bind(this, 'MetaMask RpcProvider'),\n );\n\n // Wire up the JsonRpcEngine to the JSON-RPC connection stream\n this._rpcEngine.push(this._jsonRpcConnection.middleware);\n\n // Handle JSON-RPC notifications\n this._jsonRpcConnection.events.on('notification', (payload) => {\n const { method, params } = payload;\n if (method === 'metamask_accountsChanged') {\n this._handleAccountsChanged(params);\n } else if (method === 'metamask_chainChanged') {\n this._handleChainChanged(params);\n } else if (EMITTED_NOTIFICATIONS.includes(method)) {\n this.emit('message', {\n type: method,\n data: params,\n });\n } else if (method === 'METAMASK_STREAM_FAILURE') {\n connectionStream.destroy(\n new Error(messages.errors.permanentlyDisconnected()),\n );\n }\n });\n }\n\n //====================\n // Private Methods\n //====================\n\n /**\n * MUST be called by child classes.\n *\n * Calls `metamask_getProviderState` and passes the result to\n * {@link BaseProvider._initializeState}. Logs an error if getting initial state\n * fails. Throws if called after initialization has completed.\n */\n protected async _initializeStateAsync() {\n let initialState: Parameters<BaseProvider['_initializeState']>[0];\n\n try {\n initialState = (await this.request({\n method: 'metamask_getProviderState',\n })) as Parameters<BaseProvider['_initializeState']>[0];\n } catch (error) {\n this._log.error(\n 'MetaMask: Failed to get initial state. Please report this bug.',\n error,\n );\n }\n this._initializeState(initialState);\n }\n\n /**\n * Called when connection is lost to critical streams. Emits an 'error' event\n * from the provider with the error message and stack if present.\n *\n * @param streamName - The name of the stream that disconnected.\n * @param error - The error that caused the disconnection.\n * @fires BaseProvider#disconnect - If the provider is not already\n * disconnected.\n */\n // eslint-disable-next-line no-restricted-syntax\n private _handleStreamDisconnect(streamName: string, error: Error | null) {\n let warningMsg = `MetaMask: Lost connection to \"${streamName}\".`;\n if (error?.stack) {\n warningMsg += `\\n${error.stack}`;\n }\n\n this._log.warn(warningMsg);\n if (this.listenerCount('error') > 0) {\n this.emit('error', warningMsg);\n }\n\n this._handleDisconnect(false, error ? error.message : undefined);\n }\n\n /**\n * Upon receipt of a new chainId and networkVersion, emits corresponding\n * events and sets relevant public state. Child classes that use the\n * `networkVersion` for other purposes must implement additional handling.\n *\n * @fires BaseProvider#chainChanged\n * @param networkInfo - An object with network info.\n * @param networkInfo.chainId - The latest chain ID.\n * @param networkInfo.networkVersion - The latest network ID.\n * @param networkInfo.isConnected - Whether the network is available.\n */\n protected _handleChainChanged({\n chainId,\n networkVersion,\n isConnected,\n }: {\n chainId?: string | undefined;\n networkVersion?: string | undefined;\n isConnected?: boolean | undefined;\n } = {}) {\n if (!isValidChainId(chainId) || !isValidNetworkVersion(networkVersion)) {\n this._log.error(messages.errors.invalidNetworkParams(), {\n chainId,\n networkVersion,\n });\n return;\n }\n\n super._handleChainChanged({ chainId, isConnected });\n\n if (!isConnected) {\n this._handleDisconnect(true);\n }\n }\n}\n\n/**\n * An EIP-1193 provider wired to some duplex stream via a\n * `json-rpc-middleware-stream` JSON-RPC stream middleware. Consumers must\n * call {@link StreamProvider.initialize} after instantiation to complete\n * initialization.\n */\nexport class StreamProvider extends AbstractStreamProvider {\n /**\n * MUST be called after instantiation to complete initialization.\n *\n * Calls `metamask_getProviderState` and passes the result to\n * {@link BaseProvider._initializeState}. Logs an error if getting initial state\n * fails. Throws if called after initialization has completed.\n */\n async initialize() {\n return this._initializeStateAsync();\n }\n}\n"]}