1
0
mirror of https://github.com/kremalicious/metamask-extension.git synced 2024-11-25 03:20:23 +01:00
metamask-extension/app/scripts/lib/metaRPCClientFactory.js
Mark Stacey 74f2d27d10
Update @metamask/safe-event-emitter (#18419)
The package `safe-event-emitter` has been updated to v2. This update
includes renaming the package to be scoped under `@metamask`, and it
includes a TypeScript migration.
2023-04-03 15:57:39 -02:30

127 lines
3.4 KiB
JavaScript

import { EthereumRpcError } from 'eth-rpc-errors';
import SafeEventEmitter from '@metamask/safe-event-emitter';
import createRandomId from '../../../shared/modules/random-id';
import { TEN_SECONDS_IN_MILLISECONDS } from '../../../shared/lib/transactions-controller-utils';
class DisconnectError extends Error {}
class MetaRPCClient {
constructor(connectionStream) {
this.connectionStream = connectionStream;
this.notificationChannel = new SafeEventEmitter();
this.uncaughtErrorChannel = new SafeEventEmitter();
this.requests = new Map();
this.connectionStream.on('data', this.handleResponse.bind(this));
this.connectionStream.on('end', this.close.bind(this));
this.responseHandled = {};
this.DisconnectError = DisconnectError;
}
send(id, payload, cb) {
this.requests.set(id, cb);
this.connectionStream.write(payload);
this.responseHandled[id] = false;
if (payload.method === 'getState') {
setTimeout(() => {
if (!this.responseHandled[id] && cb) {
delete this.responseHandled[id];
return cb(new Error('No response from RPC'), null);
}
delete this.responseHandled[id];
// needed for linter to pass
return true;
}, TEN_SECONDS_IN_MILLISECONDS);
}
}
onNotification(handler) {
this.notificationChannel.addListener('notification', (data) => {
handler(data);
});
}
onUncaughtError(handler) {
this.uncaughtErrorChannel.addListener('error', (error) => {
handler(error);
});
}
close() {
this.notificationChannel.removeAllListeners();
this.uncaughtErrorChannel.removeAllListeners();
// fail all unfinished requests
for (const [id, handler] of this.requests) {
if (!this.responseHandled[id]) {
this.responseHandled[id] = true;
handler(new DisconnectError('disconnected'));
}
}
}
handleResponse(data) {
const { id, result, error, method, params } = data;
const isNotification = id === undefined && error === undefined;
const cb = this.requests.get(id);
this.responseHandled[id] = true;
if (method && params && !isNotification) {
// dont handle server-side to client-side requests
return;
}
if (method && params && isNotification) {
// handle servier-side to client-side notification
this.notificationChannel.emit('notification', data);
return;
}
if (error) {
const e = new EthereumRpcError(error.code, error.message, error.data);
// preserve the stack from serializeError
e.stack = error.stack;
if (cb) {
this.requests.delete(id);
cb(e);
return;
}
this.uncaughtErrorChannel.emit('error', e);
return;
}
if (!cb) {
// not found in request list
return;
}
this.requests.delete(id);
cb(null, result);
}
}
const metaRPCClientFactory = (connectionStream) => {
const metaRPCClient = new MetaRPCClient(connectionStream);
return new Proxy(metaRPCClient, {
get: (object, property) => {
if (object[property]) {
return object[property];
}
return (...p) => {
const cb = p[p.length - 1];
const params = p.slice(0, -1);
const id = createRandomId();
const payload = {
jsonrpc: '2.0',
method: property,
params,
id,
};
object.send(id, payload, cb);
};
},
});
};
export default metaRPCClientFactory;