@prajjawal/qrl_providers
Version:
A JavaScript Ethereum provider that connects to the wallet over a stream.
296 lines • 11.6 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetaMaskInpageProvider = exports.MetaMaskInpageProviderStreamName = void 0;
const eth_rpc_errors_1 = require("eth-rpc-errors");
const siteMetadata_1 = require("./siteMetadata");
const messages_1 = __importDefault(require("./messages"));
const utils_1 = require("./utils");
const StreamProvider_1 = require("./StreamProvider");
/**
* The name of the stream consumed by {@link MetaMaskInpageProvider}.
*/
exports.MetaMaskInpageProviderStreamName = 'qrl-provider';
class MetaMaskInpageProvider extends StreamProvider_1.AbstractStreamProvider {
/**
* @param connectionStream - A Node.js duplex stream
* @param options - An options bag
* @param options.jsonRpcStreamName - The name of the internal JSON-RPC stream.
* Default: qrl-provider
* @param options.logger - The logging API to use. Default: console
* @param options.maxEventListeners - The maximum number of event
* listeners. Default: 100
* @param options.shouldSendMetadata - Whether the provider should
* send page metadata. Default: true
*/
constructor(connectionStream, { jsonRpcStreamName = exports.MetaMaskInpageProviderStreamName, logger = console, maxEventListeners, shouldSendMetadata, } = {}) {
super(connectionStream, {
jsonRpcStreamName,
logger,
maxEventListeners,
rpcMiddleware: utils_1.getDefaultExternalMiddleware(logger),
});
this._sentWarnings = {
// methods
enable: false,
experimentalMethods: false,
send: false,
// events
events: {
close: false,
data: false,
networkChanged: false,
notification: false,
},
};
// We shouldn't perform asynchronous work in the constructor, but at one
// point we started doing so, and changing this class isn't worth it at
// the time of writing.
this._initializeStateAsync();
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();
// handle JSON-RPC notifications
this._jsonRpcConnection.events.on('notification', (payload) => {
const { method } = payload;
if (utils_1.EMITTED_NOTIFICATIONS.includes(method)) {
// deprecated
// emitted here because that was the original order
this.emit('data', payload);
// deprecated
this.emit('notification', payload.params.result);
}
});
// send website metadata
if (shouldSendMetadata) {
if (document.readyState === 'complete') {
siteMetadata_1.sendSiteMetadata(this._rpcEngine, this._log);
}
else {
const domContentLoadedHandler = () => {
siteMetadata_1.sendSiteMetadata(this._rpcEngine, this._log);
window.removeEventListener('DOMContentLoaded', domContentLoadedHandler);
};
window.addEventListener('DOMContentLoaded', domContentLoadedHandler);
}
}
}
//====================
// Public Methods
//====================
/**
* Submits an RPC request per the given JSON-RPC request object.
*
* @param payload - The RPC request object.
* @param callback - The callback function.
*/
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);
}
//====================
// Private Methods
//====================
/**
* When the provider becomes disconnected, updates internal state and emits
* required events. Idempotent with respect to the isRecoverable parameter.
*
* Error codes per the CloseEvent status codes as required by EIP-1193:
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent#Status_codes
*
* @param isRecoverable - Whether the disconnection is recoverable.
* @param errorMessage - A custom error message.
* @emits BaseProvider#disconnect
*/
_handleDisconnect(isRecoverable, errorMessage) {
super._handleDisconnect(isRecoverable, errorMessage);
if (this.networkVersion && !isRecoverable) {
this.networkVersion = null;
}
}
/**
* Warns of deprecation for the given event, if applicable.
*/
_warnOfDeprecation(eventName) {
var _a;
if (((_a = this._sentWarnings) === null || _a === void 0 ? void 0 : _a.events[eventName]) === false) {
this._log.warn(messages_1.default.warnings.events[eventName]);
this._sentWarnings.events[eventName] = true;
}
}
//====================
// Deprecated Methods
//====================
/**
* Equivalent to: ethereum.request('eth_requestAccounts')
*
* @deprecated Use request({ method: 'eth_requestAccounts' }) instead.
* @returns A promise that resolves to an array of addresses.
*/
enable() {
if (!this._sentWarnings.enable) {
this._log.warn(messages_1.default.warnings.enableDeprecation);
this._sentWarnings.enable = true;
}
return new Promise((resolve, reject) => {
try {
this._rpcRequest({ method: 'eth_requestAccounts', params: [] }, utils_1.getRpcPromiseCallback(resolve, reject));
}
catch (error) {
reject(error);
}
});
}
send(methodOrPayload, callbackOrArgs) {
if (!this._sentWarnings.send) {
this._log.warn(messages_1.default.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 }, utils_1.getRpcPromiseCallback(resolve, reject, false));
}
catch (error) {
reject(error);
}
});
}
else if (methodOrPayload &&
typeof methodOrPayload === 'object' &&
typeof callbackOrArgs === 'function') {
return this._rpcRequest(methodOrPayload, callbackOrArgs);
}
return this._sendSync(methodOrPayload);
}
/**
* Internal backwards compatibility method, used in send.
*
* @deprecated
*/
_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, utils_1.NOOP);
result = true;
break;
case 'net_version':
result = this.networkVersion || null;
break;
default:
throw new Error(messages_1.default.errors.unsupportedSync(payload.method));
}
return {
id: payload.id,
jsonrpc: payload.jsonrpc,
result,
};
}
/**
* Constructor helper.
*
* Gets the experimental _metamask API as Proxy, so that we can warn consumers
* about its experimental nature.
*/
_getExperimentalApi() {
return new Proxy({
/**
* Determines if MetaMask is unlocked by the user.
*
* @returns Promise resolving to true if MetaMask is currently unlocked
*/
isUnlocked: async () => {
if (!this._state.initialized) {
await new Promise((resolve) => {
this.on('_initialized', () => resolve());
});
}
return this._state.isUnlocked;
},
/**
* Make a batch RPC request.
*/
requestBatch: async (requests) => {
if (!Array.isArray(requests)) {
throw eth_rpc_errors_1.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, utils_1.getRpcPromiseCallback(resolve, reject));
});
},
}, {
get: (obj, prop, ...args) => {
if (!this._sentWarnings.experimentalMethods) {
this._log.warn(messages_1.default.warnings.experimentalMethods);
this._sentWarnings.experimentalMethods = true;
}
return Reflect.get(obj, prop, ...args);
},
});
}
/**
* Upon receipt of a new chainId and networkVersion, emits corresponding
* events and sets relevant public state. Does nothing if neither the chainId
* nor the networkVersion are different from existing values.
*
* @emits MetamaskInpageProvider#networkChanged
* @param networkInfo - An object with network info.
* @param networkInfo.chainId - The latest chain ID.
* @param networkInfo.networkVersion - The latest network ID.
*/
_handleChainChanged({ chainId, networkVersion, } = {}) {
// This will validate the params and disconnect the provider if the
// networkVersion is 'loading'.
super._handleChainChanged({ chainId, networkVersion });
if (this._state.isConnected && networkVersion !== this.networkVersion) {
this.networkVersion = networkVersion;
if (this._state.initialized) {
this.emit('networkChanged', this.networkVersion);
}
}
}
}
exports.MetaMaskInpageProvider = MetaMaskInpageProvider;
//# sourceMappingURL=MetaMaskInpageProvider.js.map