You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
126 lines
3.4 KiB
126 lines
3.4 KiB
import { EthereumRpcError } from 'eth-rpc-errors';
|
|
import SafeEventEmitter from '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;
|
|
|