summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gui/package-lock.json38
-rw-r--r--gui/package.json3
-rw-r--r--gui/src/main/jsonrpc-client.ts450
-rw-r--r--gui/test/jsonrpc-transport.spec.ts122
4 files changed, 0 insertions, 613 deletions
diff --git a/gui/package-lock.json b/gui/package-lock.json
index aaffe0d2b0..96c793cf13 100644
--- a/gui/package-lock.json
+++ b/gui/package-lock.json
@@ -726,26 +726,6 @@
"integrity": "sha512-hkgzYF+qnIl8uTO8rmUSVSfQ8BIfMXC4yJAF4n8BE758YsKBZvFC4NumnAegj7KmylP0liEZNpb9RRGFMbFejA==",
"dev": true
},
- "@types/stream-chain": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/@types/stream-chain/-/stream-chain-2.0.0.tgz",
- "integrity": "sha512-O3IRJcZi4YddlS8jgasH87l+rdNmad9uPAMmMZCfRVhumbWMX6lkBWnIqr9kokO5sx8LHp8peQ1ELhMZHbR0Gg==",
- "dev": true,
- "requires": {
- "@types/node": "*"
- }
- },
- "@types/stream-json": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/@types/stream-json/-/stream-json-1.0.0.tgz",
- "integrity": "sha512-W9B6R5GPbTq72Oz/oJZ8qSl3+6g2xbBK0et+pK/LwGpoSWsHh2AVEYLuQUBUzg+ps4xZGdYJP5nqsW17DFbxzQ==",
- "dev": true,
- "requires": {
- "@types/events": "*",
- "@types/node": "*",
- "@types/stream-chain": "*"
- }
- },
"@types/styled-components": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@types/styled-components/-/styled-components-5.1.0.tgz",
@@ -8052,11 +8032,6 @@
"graceful-fs": "^4.1.6"
}
},
- "jsonrpc-lite": {
- "version": "2.0.7",
- "resolved": "https://registry.npmjs.org/jsonrpc-lite/-/jsonrpc-lite-2.0.7.tgz",
- "integrity": "sha512-BzDgvW9iZzVS0hgWaoM1RhBg4eRDqy+JjJtD2+23MbWd+H30ld8qWkUW1LtnHliL4QlYwrYur8ZZkVpiyPkrYQ=="
- },
"jsx-ast-utils": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz",
@@ -11834,11 +11809,6 @@
"integrity": "sha1-+vUbnrdKrvOzrPStX2Gr8ky3uT4=",
"dev": true
},
- "stream-chain": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.1.0.tgz",
- "integrity": "sha512-PAUXdRGm0G8P0+/+JEd3O9kfmB9kwmr2nKIc5zhcsHn0KdBByD5PJ2po21iDzc+TZsOSEbU8j4JbAevJsZkLyQ=="
- },
"stream-combiner": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz",
@@ -11854,14 +11824,6 @@
"integrity": "sha512-b/qaq/GlBK5xaq1yrK9/zFcyRSTNxmcZwFLGSTG0mXgZl/4Z6GgiyYOXOvY7N3eEvFRAG1bkDRz5EPGSvPYQlw==",
"dev": true
},
- "stream-json": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/stream-json/-/stream-json-1.3.0.tgz",
- "integrity": "sha512-3qLDv/xnwmleb5kssgmKbGPqcLou2tbFIgj3CM3fy5PxKaAmeCv103Zp4LKIALdE30zMHTJn09yVwxq773btaw==",
- "requires": {
- "stream-chain": "^2.1.0"
- }
- },
"stream-shift": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.0.tgz",
diff --git a/gui/package.json b/gui/package.json
index 68aae86602..3e4a1aa25f 100644
--- a/gui/package.json
+++ b/gui/package.json
@@ -20,7 +20,6 @@
"gettext-parser": "^4.0.3",
"google-protobuf": "^4.0.0-rc.2",
"history": "^4.6.1",
- "jsonrpc-lite": "^2.0.7",
"linux-app-list": "^1.0.1",
"mkdirp": "^1.0.3",
"moment": "^2.24.0",
@@ -34,7 +33,6 @@
"reactxp": "^2.0.0",
"redux": "^4.0.5",
"sprintf-js": "^1.1.2",
- "stream-json": "^1.3.0",
"styled-components": "^5.1.0",
"uuid": "^3.0.1",
"validated": "^2.0.1"
@@ -62,7 +60,6 @@
"@types/react-simple-maps": "^0.12.1",
"@types/sinon": "^7.0.5",
"@types/sprintf-js": "^1.1.2",
- "@types/stream-json": "^1.0.0",
"@types/styled-components": "^5.1.0",
"@types/topojson-specification": "^1.0.1",
"@types/uuid": "^3.4.4",
diff --git a/gui/src/main/jsonrpc-client.ts b/gui/src/main/jsonrpc-client.ts
deleted file mode 100644
index e7e8721804..0000000000
--- a/gui/src/main/jsonrpc-client.ts
+++ /dev/null
@@ -1,450 +0,0 @@
-import assert from 'assert';
-import log from 'electron-log';
-import { EventEmitter } from 'events';
-import jsonrpc from 'jsonrpc-lite';
-import * as net from 'net';
-import StreamValues from 'stream-json/streamers/StreamValues';
-import * as uuid from 'uuid';
-
-/* eslint-disable @typescript-eslint/no-explicit-any */
-
-export interface IUnansweredRequest {
- resolve: (value: any) => void;
- reject: (value: any) => void;
- timerId: NodeJS.Timeout;
- message: object;
-}
-
-export interface IJsonRpcErrorResponse {
- type: 'error';
- payload: {
- id: string;
- error: {
- code: number;
- message: string;
- };
- };
-}
-export interface IJsonRpcNotification {
- type: 'notification';
- payload: {
- method: string;
- params: {
- subscription: string;
- result: any;
- };
- };
-}
-export interface IJsonRpcSuccess {
- type: 'success';
- payload: {
- id: string;
- result: any;
- };
-}
-export type JsonRpcMessage = IJsonRpcErrorResponse | IJsonRpcNotification | IJsonRpcSuccess;
-
-export class RemoteError extends Error {
- constructor(private codeValue: number, private detailsValue: string) {
- super(`Remote JSON-RPC error ${codeValue}: ${detailsValue}`);
- }
-
- get code(): number {
- return this.codeValue;
- }
-
- get details(): string {
- return this.detailsValue;
- }
-}
-
-export class TimeOutError extends Error {
- constructor(private jsonRpcMessageValue: object) {
- super('Request timed out');
- }
-
- get jsonRpcMessage(): object {
- return this.jsonRpcMessageValue;
- }
-}
-
-export class SubscriptionError extends Error {
- constructor(message: string, private replyValue: any) {
- super(`${message}: ${JSON.stringify(replyValue)}`);
- }
-
- get reply(): any {
- return this.replyValue;
- }
-}
-
-export class WebSocketError extends Error {
- get code(): number {
- return this.codeValue;
- }
-
- private static reason(code: number): string {
- switch (code) {
- case 1006:
- return 'Abnormal closure';
- case 1011:
- return 'Internal error';
- case 1012:
- return 'Service restart';
- case 1014:
- return 'Bad gateway';
- default:
- return `Unknown (${code})`;
- }
- }
- constructor(private codeValue: number) {
- super(WebSocketError.reason(codeValue));
- }
-}
-
-export class TransportError extends Error {}
-
-const DEFAULT_TIMEOUT_MILLIS = 5000;
-
-export default class JsonRpcClient<T> extends EventEmitter {
- private unansweredRequests: Map<string, IUnansweredRequest> = new Map();
- private subscriptions: Map<string | number, (value: any) => void> = new Map();
- private transport: ITransport<T>;
-
- constructor(transport: ITransport<T>) {
- super();
-
- this.transport = transport;
- }
-
- /// Connect websocket
- public connect(connectionParams: T): Promise<void> {
- return new Promise((resolve, reject) => {
- this.disconnect();
-
- log.info('Connecting to transport with params', connectionParams);
-
- // A flag used to determine if Promise was resolved.
- let isPromiseResolved = false;
-
- const transport = this.transport;
-
- transport.onOpen = () => {
- log.info('Transport is connected');
- this.emit('open');
-
- // Resolve the Promise
- resolve();
- isPromiseResolved = true;
- };
-
- transport.onMessage = (obj) => {
- this.onMessage(obj);
- };
-
- transport.onClose = (error?: Error) => {
- // Remove all subscriptions since they are connection based
- this.subscriptions.clear();
-
- this.emit('close', error);
-
- // Prevent rejecting a previously resolved Promise.
- if (!isPromiseResolved) {
- reject(error);
- }
- };
- transport.connect(connectionParams);
-
- this.transport = transport;
- });
- }
-
- public disconnect() {
- if (this.transport) {
- this.transport.close();
- }
- }
-
- public async subscribe(event: string, listener: (value: any) => void): Promise<string | number> {
- log.silly(`Adding a listener for ${event}`);
-
- try {
- const subscriptionId = await this.send(`${event}_subscribe`);
-
- if (typeof subscriptionId === 'string' || typeof subscriptionId === 'number') {
- this.subscriptions.set(subscriptionId, listener);
- } else {
- throw new SubscriptionError(
- 'The subscription id was not a string or a number',
- subscriptionId,
- );
- }
-
- return subscriptionId;
- } catch (e) {
- log.error(`Failed adding listener to ${event}: ${e.message}`);
- throw e;
- }
- }
-
- public async unsubscribe(event: string, subscriptionId: string | number): Promise<void> {
- log.silly(`Removing a listener for ${event}`);
-
- try {
- if (this.subscriptions.has(subscriptionId)) {
- await this.send(`${event}_unsubscribe`, [subscriptionId]);
- }
- } catch (e) {
- log.error(`Failed removing listener to ${event}: ${e.message}`);
- throw e;
- } finally {
- this.subscriptions.delete(subscriptionId);
- }
- }
-
- public send(action: string, data?: any, timeout: number = DEFAULT_TIMEOUT_MILLIS): Promise<any> {
- return new Promise((resolve, reject) => {
- const transport = this.transport;
- if (!transport) {
- reject(new Error('RPC client transport is not connected.'));
- return;
- }
-
- const id = uuid.v4();
- const payload = this.prepareParams(data);
- const timerId = global.setTimeout(() => this.onTimeout(id), timeout);
- const message = jsonrpc.request(id, action, payload);
- this.unansweredRequests.set(id, {
- resolve,
- reject,
- timerId,
- message,
- });
-
- try {
- log.silly('Sending message', id, action);
- transport.send(JSON.stringify(message));
- } catch (error) {
- log.error(`Failed sending RPC message "${action}": ${error.message}`);
-
- // clean up on error
- this.unansweredRequests.delete(id);
- clearTimeout(timerId);
-
- throw error;
- }
- });
- }
-
- private prepareParams(data?: any): any[] | object {
- // JSONRPC only accepts arrays and objects as params, but
- // this isn't very nice to use, so this method wraps other
- // types in an array. The choice of array is based on try-and-error
-
- if (data === undefined) {
- return [];
- } else if (data === null) {
- return [null];
- } else if (Array.isArray(data) || typeof data === 'object') {
- return data;
- } else {
- return [data];
- }
- }
-
- private onTimeout(requestId: string) {
- const request = this.unansweredRequests.get(requestId);
-
- this.unansweredRequests.delete(requestId);
-
- if (request) {
- log.warn(`Request ${requestId} timed out: `, request.message);
- request.reject(new TimeOutError(request.message));
- } else {
- log.warn(`Request ${requestId} timed out but it seems to already have been answered`);
- }
- }
-
- private onMessage(obj: object) {
- let message: ReturnType<typeof jsonrpc.parseObject>;
- try {
- message = jsonrpc.parseObject(obj);
- } catch (error) {
- log.error(`Failed to parse JSON-RPC message: ${error} for object`);
- return;
- }
-
- if (message.type === 'notification') {
- this.onNotification(message as IJsonRpcNotification);
- } else {
- this.onReply(message as IJsonRpcErrorResponse | IJsonRpcSuccess);
- }
- }
-
- private onNotification(message: IJsonRpcNotification) {
- const subscriptionId = message.payload.params.subscription;
- const listener = this.subscriptions.get(subscriptionId);
-
- if (listener) {
- log.silly(`Got notification for ${message.payload.method}`);
- listener(message.payload.params.result);
- } else {
- log.warn(`Got notification for ${message.payload.method} but no one is listening for it`);
- }
- }
-
- private onReply(message: IJsonRpcErrorResponse | IJsonRpcSuccess) {
- const id = message.payload.id;
- const request = this.unansweredRequests.get(id);
- this.unansweredRequests.delete(id);
-
- if (request) {
- log.silly('Got answer to', id, message.type);
-
- clearTimeout(request.timerId);
-
- if (message.type === 'error') {
- const error = message.payload.error;
- request.reject(new RemoteError(error.code, error.message));
- } else {
- const reply = message.payload.result;
- request.resolve(reply);
- }
- } else {
- log.warn(`Got reply to ${id} but no one was waiting for it`);
- }
- }
-}
-
-export interface ITransport<T> {
- onOpen: () => void;
- onMessage: (data: object) => void;
- onClose: (error?: Error) => void;
- close(): void;
- send(message: string): void;
- connect(params: T): void;
-}
-
-// Given the correct parameters, this transport supports named pipes/unix
-// domain sockets, and also TCP/UDP sockets
-export class SocketTransport implements ITransport<{ path: string }> {
- private connection?: net.Socket;
- private jsonStream?: NodeJS.ReadWriteStream;
- private socketReady = false;
- private lastError?: Error;
- public onMessage = (_message: object) => {
- // no-op
- };
- public onClose = (_error?: Error) => {
- // no-op
- };
- public onOpen = () => {
- // no-op
- };
-
- public connect(options: { path: string }) {
- assert(!this.connection, 'Make sure to close the existing socket');
-
- const jsonStream = StreamValues.withParser()
- .on('data', this.onJsonStreamData)
- .once('error', this.onJsonStreamError);
-
- const connection = new net.Socket()
- .once('ready', this.onSocketReady)
- .once('error', this.onSocketError)
- .once('close', this.onSocketClose);
-
- this.connection = connection;
- this.jsonStream = jsonStream;
- this.socketReady = false;
- this.lastError = undefined;
-
- log.debug('Connect socket');
-
- connection.pipe(jsonStream);
- connection.connect(options);
- }
-
- public close() {
- if (this.connection) {
- log.debug('Close socket');
-
- this.cleanupConnection(true);
-
- this.onClose();
- }
- }
-
- public send(msg: string) {
- if (this.socketReady && this.connection) {
- this.connection.write(msg);
- } else {
- throw new TransportError('Socket not connected');
- }
- }
-
- private onSocketReady = () => {
- this.socketReady = true;
-
- log.debug('Socket is ready');
-
- this.onOpen();
- };
-
- private onSocketError = (error: Error) => {
- this.lastError = error;
-
- log.error('Socket error: ', error);
- };
-
- private onSocketClose = (hadError: boolean) => {
- this.cleanupConnection(false);
-
- if (hadError) {
- log.debug(`Socket was closed due to an error: `, this.lastError);
-
- this.onClose(this.lastError);
- } else {
- log.debug(`Socket was closed by peer`);
-
- this.onClose(new TransportError('Socket was closed by peer'));
- }
- };
-
- private onJsonStreamData = (data: { key: number; value: any }) => {
- this.onMessage(data.value);
- };
-
- private onJsonStreamError = (error: Error) => {
- log.error('Socket JSON stream error: ', error);
-
- if (this.connection) {
- // This will destroy the socket and emit "error" and "close" events
- this.connection.destroy(error);
- }
- };
-
- private cleanupConnection(shouldClose: boolean) {
- // closing socket is not synchronous, so remove all of the event handlers first
- this.connection!.removeListener('ready', this.onSocketReady)
- .removeListener('error', this.onSocketError)
- .removeListener('close', this.onSocketClose);
-
- this.jsonStream!.removeListener('data', this.onJsonStreamData).removeListener(
- 'error',
- this.onJsonStreamError,
- );
-
- if (shouldClose) {
- try {
- this.connection!.end();
- } catch (error) {
- log.error('Failed to close the socket: ', error);
- }
- }
-
- this.connection = undefined;
- this.jsonStream = undefined;
- this.socketReady = false;
- }
-}
diff --git a/gui/test/jsonrpc-transport.spec.ts b/gui/test/jsonrpc-transport.spec.ts
deleted file mode 100644
index 26d812b793..0000000000
--- a/gui/test/jsonrpc-transport.spec.ts
+++ /dev/null
@@ -1,122 +0,0 @@
-import { expect } from 'chai';
-import { it, describe, beforeEach } from 'mocha';
-import jsonrpc from 'jsonrpc-lite';
-import JsonRpcClient, { ITransport, TimeOutError } from '../src/main/jsonrpc-client';
-
-describe('JSON RPC transport', () => {
- let client: JsonRpcClient<string>, transport: MockTransport;
-
- beforeEach(() => {
- transport = new MockTransport();
- client = new JsonRpcClient(transport);
- return client.connect('');
- });
-
- it('should reject failed jsonrpc requests', async () => {
- transport.onServerMessage = (msg) => {
- const parsedMessage = jsonrpc.parseObject(msg);
-
- if (parsedMessage.type === 'request' && parsedMessage.payload.method === 'invalid-method') {
- transport.reply(
- JSON.stringify(
- jsonrpc.error(
- parsedMessage.payload.id,
- new jsonrpc.JsonRpcError('Method not found', -32601),
- ),
- ),
- );
- }
- };
-
- const sendPromise = client.send('invalid-method');
-
- return expect(sendPromise).to.eventually.be.rejectedWith('Method not found');
- });
-
- it('should route reply to correct promise', async () => {
- transport.onServerMessage = (msg) => {
- const parsedMessage = jsonrpc.parseObject(msg);
-
- if (parsedMessage.type === 'request' && parsedMessage.payload.method === 'a message') {
- transport.reply(JSON.stringify(jsonrpc.success(parsedMessage.payload.id, 'a reply')));
- }
- };
-
- const decoyPromise = client.send('a decoy', [], 100);
- const messagePromise = client.send('a message', [], 100);
-
- return Promise.all([
- expect(messagePromise).to.eventually.be.equal('a reply'),
- expect(decoyPromise).to.eventually.be.rejectedWith(TimeOutError),
- ]);
- });
-
- it('should timeout if no response is returned', async () => {
- const sendPromise = client.send('timeout-message', {}, 1);
-
- return expect(sendPromise).to.eventually.be.rejectedWith(TimeOutError, 'Request timed out');
- });
-
- it('should route notifications', async () => {
- transport.onServerMessage = (msg) => {
- const parsedMessage = jsonrpc.parseObject(msg);
-
- if (parsedMessage.type === 'request' && parsedMessage.payload.method === 'event_subscribe') {
- transport.reply(JSON.stringify(jsonrpc.success(parsedMessage.payload.id, 1)));
- }
- };
-
- const eventPromiseHelper = (() => {
- let borrowedResolve: ((param: any) => void) | undefined = undefined;
- const promise = new Promise((resolve) => (borrowedResolve = resolve));
- /* Flow does not understand that the body of Promise runs immediately.
- see https://github.com/facebook/flow/issues/6711 */
- if (!borrowedResolve) {
- throw new Error();
- }
- return {
- resolve: borrowedResolve,
- promise,
- };
- })();
-
- await client.subscribe('event', eventPromiseHelper.resolve);
-
- transport.reply(
- JSON.stringify(jsonrpc.notification('event', { subscription: 1, result: 'beacon' })),
- );
-
- return expect(eventPromiseHelper.promise).to.eventually.be.equal('beacon');
- });
-});
-
-class MockTransport implements ITransport<string> {
- public onOpen = () => {
- // no-op
- };
- public onMessage = (_message: object) => {
- // no-op
- };
- public onServerMessage = (_message: object) => {
- // no-op
- };
- public onClose = (_error?: Error) => {
- // no-op
- };
-
- public close() {
- this.onClose();
- }
-
- public send(msg: string) {
- this.onServerMessage(JSON.parse(msg));
- }
-
- public reply(msg: string) {
- this.onMessage(JSON.parse(msg));
- }
-
- public connect(_params: string) {
- this.onOpen();
- }
-}