diff --git a/elements/lisk-api-client/package.json b/elements/lisk-api-client/package.json index 0953c00a364..ff0a5a92f1f 100644 --- a/elements/lisk-api-client/package.json +++ b/elements/lisk-api-client/package.json @@ -1,6 +1,6 @@ { "name": "@liskhq/lisk-api-client", - "version": "5.0.0", + "version": "5.0.1-alpha.0", "description": "An API client for the Lisk network", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", diff --git a/elements/lisk-api-client/src/ipc_channel.ts b/elements/lisk-api-client/src/ipc_channel.ts index 8fde17113ce..6f46f73d104 100644 --- a/elements/lisk-api-client/src/ipc_channel.ts +++ b/elements/lisk-api-client/src/ipc_channel.ts @@ -22,7 +22,14 @@ import * as axon from 'pm2-axon'; import { PubSocket, PullSocket, PushSocket, SubSocket, ReqSocket } from 'pm2-axon'; import { Client as RPCClient } from 'pm2-axon-rpc'; import { EventEmitter } from 'events'; -import { Channel, EventCallback, JSONRPCNotification, JSONRPCResponse } from './types'; +import { + Channel, + EventCallback, + JSONRPCNotification, + JSONRPCResponse, + JSONRPCError, +} from './types'; +import { convertRPCError } from './utils'; const CONNECTION_TIME_OUT = 2000; @@ -139,17 +146,21 @@ export class IPCChannel implements Channel { params: params ?? {}, }; return new Promise((resolve, reject) => { - this._rpcClient.call('invoke', action, (err: Error | undefined, data: JSONRPCResponse) => { - if (err) { - reject(err); - return; - } - if (data.error) { - reject(err); - return; - } - resolve(data.result); - }); + this._rpcClient.call( + 'invoke', + action, + (err: JSONRPCError | undefined, data: JSONRPCResponse) => { + if (err) { + reject(convertRPCError(err)); + return; + } + if (data.error) { + reject(convertRPCError(data.error)); + return; + } + resolve(data.result); + }, + ); }); } diff --git a/elements/lisk-api-client/src/types.ts b/elements/lisk-api-client/src/types.ts index 9f4a3c74597..3839013fc01 100644 --- a/elements/lisk-api-client/src/types.ts +++ b/elements/lisk-api-client/src/types.ts @@ -22,12 +22,18 @@ export interface JSONRPCNotification { readonly params?: T; } +export interface JSONRPCError { + code: number; + message: string; + data?: string | number | boolean | Record; +} + export interface JSONRPCResponse { readonly id: number; readonly jsonrpc: string; readonly method: never; readonly params: never; - readonly error?: { code: number; message: string; data?: string }; + readonly error?: JSONRPCError; readonly result?: T; } diff --git a/elements/lisk-api-client/src/utils.ts b/elements/lisk-api-client/src/utils.ts new file mode 100644 index 00000000000..aaa342eae79 --- /dev/null +++ b/elements/lisk-api-client/src/utils.ts @@ -0,0 +1,19 @@ +/* + * Copyright © 2020 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ + +import { JSONRPCError } from './types'; + +export const convertRPCError = (error: JSONRPCError): Error => + new Error(typeof error.data === 'string' ? error.data : error.message); diff --git a/elements/lisk-api-client/src/ws_channel.ts b/elements/lisk-api-client/src/ws_channel.ts index ea48ea996fe..708c072210d 100644 --- a/elements/lisk-api-client/src/ws_channel.ts +++ b/elements/lisk-api-client/src/ws_channel.ts @@ -16,9 +16,9 @@ import * as WebSocket from 'isomorphic-ws'; import { EventEmitter } from 'events'; import { JSONRPCMessage, JSONRPCNotification, EventCallback } from './types'; +import { convertRPCError } from './utils'; const CONNECTION_TIMEOUT = 2000; -const ACKNOWLEDGMENT_TIMEOUT = 2000; const RESPONSE_TIMEOUT = 3000; const timeout = async (ms: number, message?: string): Promise => @@ -69,34 +69,41 @@ export class WSChannel { public async connect(): Promise { this._ws = new WebSocket(this._url); + this._ws.onclose = this._handleClose.bind(this); + this._ws.onmessage = this._handleMessage.bind(this); + this._ws.addEventListener('ping', this._handlePing.bind(this)); - const connect = new Promise(resolve => { - this._ws?.on('open', () => { + const connectHandler = new Promise(resolve => { + const onOpen = () => { this.isAlive = true; + this._ws?.removeEventListener('open', onOpen); resolve(); - }); - }); + }; - const error = new Promise((_, reject) => { - this._ws?.on('error', err => { - this.isAlive = false; - reject(err); - }); + this._ws?.addEventListener('open', onOpen); }); - await Promise.race([ - connect, - error, - timeout(CONNECTION_TIMEOUT, `Could not connect in ${CONNECTION_TIMEOUT}ms`), - ]); + const errorHandler = new Promise((_, reject) => { + const onError = (error: WebSocket.ErrorEvent) => { + this.isAlive = false; + this._ws?.removeEventListener('error', onError); + reject(error.error); + }; - this._ws.on('ping', () => { - this.isAlive = true; + this._ws?.addEventListener('error', onError); }); - this._ws.on('message', data => { - this._handleMessage(data as string); - }); + try { + await Promise.race([ + connectHandler, + errorHandler, + timeout(CONNECTION_TIMEOUT, `Could not connect in ${CONNECTION_TIMEOUT}ms`), + ]); + } catch (err) { + this._ws.close(); + + throw err; + } } public async disconnect(): Promise { @@ -104,24 +111,40 @@ export class WSChannel { this._pendingRequests = {}; if (!this._ws) { - return Promise.resolve(); + return; + } + + if (this._ws.readyState === WebSocket.CLOSED) { + this.isAlive = false; + this._ws = undefined; + return; } - return new Promise(resolve => { - this._ws?.on('close', () => { - this._ws?.terminate(); + const closeHandler = new Promise(resolve => { + const onClose = () => { this.isAlive = false; - this._ws = undefined; + this._ws?.removeEventListener('close', onClose); resolve(); - }); - this._ws?.close(); + }; + + this._ws?.addEventListener('close', onClose); }); + + this._ws.close(); + await Promise.race([ + closeHandler, + timeout(CONNECTION_TIMEOUT, `Could not disconnect in ${CONNECTION_TIMEOUT}ms`), + ]); } public async invoke>( actionName: string, params?: Record, ): Promise { + if (!this.isAlive) { + throw new Error('Websocket client is not connected.'); + } + const request = { jsonrpc: '2.0', id: this._requestCounter, @@ -129,20 +152,7 @@ export class WSChannel { params: params ?? {}, }; - const send = new Promise((resolve, reject) => { - this._ws?.send(JSON.stringify(request), (err): void => { - if (err) { - return reject(err); - } - - return resolve(); - }); - }); - - await Promise.race([ - send, - timeout(ACKNOWLEDGMENT_TIMEOUT, `Request is not acknowledged in ${ACKNOWLEDGMENT_TIMEOUT}ms`), - ]); + this._ws?.send(JSON.stringify(request)); const response = defer(); this._pendingRequests[this._requestCounter] = response; @@ -159,8 +169,16 @@ export class WSChannel { this._emitter.on(eventName, cb); } - private _handleMessage(message: string): void { - const res = JSON.parse(message) as JSONRPCMessage; + private _handleClose(): void { + this.isAlive = false; + } + + private _handlePing(): void { + this.isAlive = true; + } + + private _handleMessage(event: WebSocket.MessageEvent): void { + const res = JSON.parse(event.data as string) as JSONRPCMessage; // Its an event if (messageIsNotification(res)) { @@ -172,7 +190,7 @@ export class WSChannel { if (this._pendingRequests[id]) { if (res.error) { - this._pendingRequests[id].reject(new Error(res.error.data ?? res.error.data)); + this._pendingRequests[id].reject(convertRPCError(res.error)); } else { this._pendingRequests[id].resolve(res.result); } diff --git a/elements/lisk-api-client/test/integration/ws_channel.spec.ts b/elements/lisk-api-client/test/integration/ws_channel.spec.ts index 6a95aa74fed..1e5c5957929 100644 --- a/elements/lisk-api-client/test/integration/ws_channel.spec.ts +++ b/elements/lisk-api-client/test/integration/ws_channel.spec.ts @@ -12,11 +12,24 @@ * Removal or modification of this copyright notice is prohibited. */ +import { createServer, Server } from 'http'; import * as WebSocket from 'isomorphic-ws'; import { WSChannel } from '../../src/ws_channel'; jest.unmock('isomorphic-ws'); +const closeServer = async (server: WebSocket.Server | Server): Promise => { + return new Promise((resolve, reject) => { + server.close(error => { + if (error) { + return reject(error); + } + + return resolve(); + }); + }); +}; + describe('WSChannel', () => { describe('connect', () => { it('should be connect to ws server', async () => { @@ -28,36 +41,42 @@ describe('WSChannel', () => { expect(server.clients.size).toEqual(1); expect([...server.clients][0].readyState).toEqual(WebSocket.OPEN); } finally { - server.close(); + await closeServer(server); } expect.assertions(3); }); it('should timeout if ws server not responding', async () => { - const verifyClient = (_: any, done: (result: boolean) => void) => { - // Take more time to accept connection + const http = createServer(); + const server = new WebSocket.Server({ path: '/my-path', noServer: true }); + + // https://github.com/websockets/ws/issues/377#issuecomment-462152231 + http.on('upgrade', (request, socket, head) => { setTimeout(() => { - done(true); + server.handleUpgrade(request, socket, head, ws => { + server.emit('connection', ws, request); + }); }, 3000); - }; - const server = new WebSocket.Server({ path: '/my-path', port: 65535, verifyClient }); + }); + + http.listen(65535); + const channel = new WSChannel('ws://localhost:65535/my-path'); try { await expect(channel.connect()).rejects.toThrow('Could not connect in 2000ms'); expect(server.clients.size).toEqual(0); } finally { - // TODO: Found that unless we disconnect channel, sever.close keep open handles. - await channel.disconnect(); - server.close(); + await closeServer(server); + await closeServer(http); } expect.assertions(2); }, 5000); it('should throw error if server is not running', async () => { - const channel = new WSChannel('ws://localhost:65535/my-path'); + const channel = new WSChannel('ws://localhost:65534/my-path'); - await expect(channel.connect()).rejects.toThrow('connect ECONNREFUSED 127.0.0.1:65535'); + await expect(channel.connect()).rejects.toThrow('connect ECONNREFUSED 127.0.0.1:65534'); }); }); @@ -74,7 +93,7 @@ describe('WSChannel', () => { expect(server.clients.size).toEqual(1); expect([...server.clients][0].readyState).toEqual(WebSocket.CLOSING); } finally { - server.close(); + await closeServer(server); } expect.assertions(3); }); diff --git a/elements/lisk-api-client/test/unit/__mocks__/isomorphic-ws.js b/elements/lisk-api-client/test/unit/__mocks__/isomorphic-ws.js index 780dc11089b..bb840f3f50d 100644 --- a/elements/lisk-api-client/test/unit/__mocks__/isomorphic-ws.js +++ b/elements/lisk-api-client/test/unit/__mocks__/isomorphic-ws.js @@ -17,13 +17,22 @@ const { EventEmitter } = require('events'); class WebSocket extends EventEmitter { // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility - send(message, cb) { + send(message) { const data = JSON.parse(message); - cb(); setTimeout(() => { - this.emit('message', JSON.stringify({ ...data, result: message })); + this.onmessage({ data: JSON.stringify({ ...data, result: message }) }); }, 100); } + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + addEventListener(event, cb) { + this.prependListener(event, cb); + } + + // eslint-disable-next-line @typescript-eslint/explicit-member-accessibility + removeEventListener(event, cb) { + this.removeListener(event, cb); + } } module.exports = WebSocket; diff --git a/elements/lisk-api-client/test/unit/ws_channel.spec.ts b/elements/lisk-api-client/test/unit/ws_channel.spec.ts index 417a322dcdf..c0a665ef420 100644 --- a/elements/lisk-api-client/test/unit/ws_channel.spec.ts +++ b/elements/lisk-api-client/test/unit/ws_channel.spec.ts @@ -20,24 +20,24 @@ import { WSChannel } from '../../src/ws_channel'; jest.mock('isomorphic-ws'); describe('WSChannel', () => { - let client: WSChannel; + let channel: WSChannel; // let wsMock: WSMock; const url = 'ws://localhost:8000/ws'; beforeEach(async () => { - client = new WSChannel(url); + channel = new WSChannel(url); jest.spyOn(WebSocket.prototype, 'send'); - await Promise.race([client.connect(), client['_ws']?.emit('open')]); + await Promise.race([channel.connect(), channel['_ws']?.emit('open')]); }); describe('constructor', () => { it('should set url', () => { - expect(client['_url']).toEqual(url); + expect(channel['_url']).toEqual(url); }); it('should create local emitter', () => { - expect(client['_emitter']).toBeInstanceOf(EventEmitter); + expect(channel['_emitter']).toBeInstanceOf(EventEmitter); }); }); @@ -46,7 +46,7 @@ describe('WSChannel', () => { jest.spyOn(EventEmitter.prototype, 'on'); const cb = jest.fn(); - client.subscribe('myEvent', cb); + channel.subscribe('myEvent', cb); expect(EventEmitter.prototype.on).toHaveBeenCalledWith('myEvent', cb); }); @@ -56,40 +56,37 @@ describe('WSChannel', () => { it('should send jsonrpc request to ws server', async () => { const request = { jsonrpc: '2.0', - id: client['_requestCounter'], + id: channel['_requestCounter'], method: 'myAction', params: {}, }; - await client.invoke('myAction'); + await channel.invoke('myAction'); - expect(WebSocket.prototype.send).toHaveBeenCalledWith( - JSON.stringify(request), - expect.any(Function), - ); + expect(WebSocket.prototype.send).toHaveBeenCalledWith(JSON.stringify(request)); }); it('should wait for the jsonrpc response from ws server', async () => { const request = { jsonrpc: '2.0', - id: client['_requestCounter'], + id: channel['_requestCounter'], method: 'myAction', params: {}, }; - const result = await client.invoke('myAction'); + const result = await channel.invoke('myAction'); // Mock implementation send request as response expect(result).toEqual(JSON.stringify(request)); }); it('should increment request counter', async () => { - const counter = client['_requestCounter']; + const counter = channel['_requestCounter']; - await client.invoke('myAction'); + await channel.invoke('myAction'); // Mock implementation send request as response - expect(client['_requestCounter']).toEqual(counter + 1); + expect(channel['_requestCounter']).toEqual(counter + 1); }); }); @@ -100,11 +97,11 @@ describe('WSChannel', () => { await expect( new Promise(resolve => { - client.subscribe('module1:my:Event', event => { + channel.subscribe('module1:my:Event', event => { expect(event).toEqual(eventInfo.data); resolve(); }); - client['_ws']?.emit('message', JSON.stringify(message)); + channel['_ws']?.onmessage({ data: JSON.stringify(message) } as never); }), ).resolves.toBeUndefined(); }); diff --git a/elements/lisk-client/cypress/ws_server.js b/elements/lisk-client/cypress/ws_server.js new file mode 100644 index 00000000000..8aad5c5c156 --- /dev/null +++ b/elements/lisk-client/cypress/ws_server.js @@ -0,0 +1,43 @@ +/* + * Copyright © 2020 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + */ + +const WebSocket = require('ws'); +const { createServer } = require('http'); + +const messageHandler = (socket, message) => { + const data = JSON.parse(message); + socket.send(JSON.stringify({ ...data, result: data })); +}; + +const handleConnection = socket => { + socket.on('message', message => messageHandler(socket, message)); + + setInterval(() => { + socket.send( + JSON.stringify({ jsonrpc: '2.0', method: 'myEvent', params: { eventProp: 'eventProp' } }), + ); + }, 1000); +}; +const ws = new WebSocket.Server({ path: '/ws', port: 8989 }); +ws.on('connection', handleConnection); + +ws.on('listening', () => { + // To use with "start-server-and-test" package we need an http endpoint returning 2xx code + const requestListener = function (_, res) { + res.writeHead(200); + res.end('OK!'); + }; + const http = createServer(requestListener); + http.listen(8990); +}); diff --git a/elements/lisk-client/package.json b/elements/lisk-client/package.json index c957a921ce6..3d8782db0a8 100644 --- a/elements/lisk-client/package.json +++ b/elements/lisk-client/package.json @@ -1,6 +1,6 @@ { "name": "@liskhq/lisk-client", - "version": "5.0.0", + "version": "5.0.1-alpha.0", "description": "A default set of Elements for use by clients of the Lisk network", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -24,19 +24,20 @@ "scripts": { "prestart": "./scripts/prestart.sh", "start": "./scripts/start.sh", + "start:ws_server": "node cypress/ws_server.js", "browserify": "browserify ./dist-node/index.js -o ./dist-browser/index.js -s lisk", "uglify": "terser -nm -o ./dist-browser/index.min.js ./dist-browser/index.js", "clean": "./scripts/clean.sh", "format": "prettier --write '**/*'", "lint": "eslint --ext .js,.ts .", "lint:fix": "eslint --fix --ext .js,.ts .", - "test": "jest", - "test:e2e": "npm run test:e2e:electron", + "test": "npx start-server-and-test start:ws_server http://localhost:8990 'jest'", + "test:e2e": "npx start-server-and-test start:ws_server http://localhost:8990 'npm run test:e2e:electron'", "test:e2e:electron": "npm run cypress:install && npm run cypress:patch && npx cypress run --browser electron", "cypress:patch": "bash cypress/scripts/patch_buffer_package.sh", "cypress:install": "npx cypress install && npx cypress verify", "test:coverage": "jest --coverage=true --coverage-reporters=text", - "test:ci": "jest --coverage=true --coverage-reporters=json --verbose", + "test:ci": "npx start-server-and-test start:ws_server http://localhost:8990 'jest --coverage=true --coverage-reporters=json --verbose'", "test:watch": "npm test -- --watch", "test:node": "npm run build:check", "prebuild:node": "rm -r dist-node/* || mkdir dist-node || true", @@ -49,7 +50,7 @@ "prepublishOnly": "npm run lint && npm test && npm run build && npm run build:check" }, "dependencies": { - "@liskhq/lisk-api-client": "^5.0.0", + "@liskhq/lisk-api-client": "^5.0.1-alpha.0", "@liskhq/lisk-codec": "^0.1.0", "@liskhq/lisk-cryptography": "^3.0.0", "@liskhq/lisk-passphrase": "^3.0.1", @@ -78,10 +79,12 @@ "jest-when": "2.7.2", "prettier": "2.0.5", "source-map-support": "0.5.19", + "start-server-and-test": "1.11.6", "terser": "4.7.0", "ts-jest": "26.3.0", "ts-node": "8.6.2", "tsconfig-paths": "3.9.0", - "typescript": "3.8.3" + "typescript": "3.8.3", + "ws": "7.4.0" } } diff --git a/elements/lisk-client/test/lisk-api-client/ws_client.spec.ts b/elements/lisk-client/test/lisk-api-client/ws_client.spec.ts new file mode 100644 index 00000000000..43d687e5d7e --- /dev/null +++ b/elements/lisk-client/test/lisk-api-client/ws_client.spec.ts @@ -0,0 +1,68 @@ +/* + * Copyright © 2020 Lisk Foundation + * + * See the LICENSE file at the top-level directory of this distribution + * for licensing information. + * + * Unless otherwise agreed in a custom licensing agreement with the Lisk Foundation, + * no part of this software, including this file, may be copied, modified, + * propagated, or distributed except according to the terms contained in the + * LICENSE file. + * + * Removal or modification of this copyright notice is prohibited. + * + */ + +import { apiClient } from '../../src'; + +const { createWSClient } = apiClient; + +describe('createWSClient', () => { + it('should connect to ws server', async () => { + const client = await createWSClient('ws://localhost:8989/ws'); + expect(client).not.toBeUndefined(); + + await client.disconnect(); + }); + + it('should disconnect from ws server', async () => { + const client = await createWSClient('ws://localhost:8989/ws'); + + await expect(client.disconnect()).resolves.toBeUndefined(); + }); + + describe('invoke', () => { + it('should able to invoke any action', async () => { + const client = await createWSClient('ws://localhost:8989/ws'); + + const result = await client.invoke('myAction', { prop1: 'prop1' }); + + expect(result).toEqual( + expect.objectContaining({ + jsonrpc: '2.0', + method: 'myAction', + id: expect.any(Number), + params: { prop1: 'prop1' }, + }), + ); + + await client.disconnect(); + }); + }); + + describe('subscribe', () => { + it('should able to subscribe to an event', async () => { + const client = await createWSClient('ws://localhost:8989/ws'); + + await new Promise(resolve => { + client.subscribe('myEvent', data => { + expect(data).toEqual({ eventProp: 'eventProp' }); + resolve(); + }); + }); + + expect.assertions(1); + await client.disconnect(); + }); + }); +}); diff --git a/elements/lisk-elements/package.json b/elements/lisk-elements/package.json index e8fbc607695..fac65b831fc 100644 --- a/elements/lisk-elements/package.json +++ b/elements/lisk-elements/package.json @@ -36,7 +36,7 @@ "prepublishOnly": "npm run lint && npm test && npm run build && npm run build:check" }, "dependencies": { - "@liskhq/lisk-api-client": "^5.0.0", + "@liskhq/lisk-api-client": "^5.0.1-alpha.0", "@liskhq/lisk-bft": "^0.2.0", "@liskhq/lisk-chain": "^0.2.0", "@liskhq/lisk-codec": "^0.1.0", diff --git a/framework-plugins/lisk-framework-forger-plugin/package.json b/framework-plugins/lisk-framework-forger-plugin/package.json index c03309dc07d..f6f8175311b 100644 --- a/framework-plugins/lisk-framework-forger-plugin/package.json +++ b/framework-plugins/lisk-framework-forger-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@liskhq/lisk-framework-forger-plugin", - "version": "0.1.0", + "version": "0.1.1-alpha.0", "description": "A plugin for lisk-framework that monitors configured delegates forging activity and voters information.", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -52,10 +52,10 @@ "express-rate-limit": "5.1.3", "fs-extra": "9.0.0", "ip": "1.1.5", - "lisk-framework": "^0.7.0" + "lisk-framework": "^0.7.1-alpha.0" }, "devDependencies": { - "@liskhq/lisk-api-client": "^5.0.0", + "@liskhq/lisk-api-client": "^5.0.1-alpha.0", "@liskhq/lisk-genesis": "^0.1.0", "@types/cors": "2.8.6", "@types/debug": "4.1.5", diff --git a/framework-plugins/lisk-framework-forger-plugin/test/functional/forging_info.spec.ts b/framework-plugins/lisk-framework-forger-plugin/test/functional/forging_info.spec.ts index 80c9c662714..80057652dda 100644 --- a/framework-plugins/lisk-framework-forger-plugin/test/functional/forging_info.spec.ts +++ b/framework-plugins/lisk-framework-forger-plugin/test/functional/forging_info.spec.ts @@ -12,7 +12,7 @@ * Removal or modification of this copyright notice is prohibited. */ -import { Application } from 'lisk-framework'; +import { Application, systemDirs } from 'lisk-framework'; import { createIPCClient } from '@liskhq/lisk-api-client'; import { createApplication, @@ -29,7 +29,7 @@ describe('forger:getForgingInfo action', () => { beforeAll(async () => { app = await createApplication('forging_info_spec'); await waitNBlocks(app, 1); - liskClient = await createIPCClient(`${app.config.rootPath}/${app.config.label}`); + liskClient = await createIPCClient(systemDirs(app.config.label, app.config.rootPath).dataPath); }); afterAll(async () => { diff --git a/framework-plugins/lisk-framework-forger-plugin/test/functional/voters.spec.ts b/framework-plugins/lisk-framework-forger-plugin/test/functional/voters.spec.ts index a2e6dd8de37..11f92b72eac 100644 --- a/framework-plugins/lisk-framework-forger-plugin/test/functional/voters.spec.ts +++ b/framework-plugins/lisk-framework-forger-plugin/test/functional/voters.spec.ts @@ -11,7 +11,7 @@ * * Removal or modification of this copyright notice is prohibited. */ -import { Application } from 'lisk-framework'; +import { Application, systemDirs } from 'lisk-framework'; import { createIPCClient } from '@liskhq/lisk-api-client'; import { createApplication, closeApplication, waitNBlocks, waitTill } from '../utils/application'; import { createVoteTransaction } from '../utils/transactions'; @@ -26,7 +26,7 @@ describe('forger:getVoters action', () => { app = await createApplication('forger_functional_voters'); // The test application generates a dynamic genesis block so we need to get the networkID like this networkIdentifier = app['_node'].networkIdentifier; - liskClient = await createIPCClient(`${app.config.rootPath}/${app.config.label}`); + liskClient = await createIPCClient(systemDirs(app.config.label, app.config.rootPath).dataPath); }); afterAll(async () => { diff --git a/framework-plugins/lisk-framework-http-api-plugin/package.json b/framework-plugins/lisk-framework-http-api-plugin/package.json index 6edd971d91b..10be3e002f9 100644 --- a/framework-plugins/lisk-framework-http-api-plugin/package.json +++ b/framework-plugins/lisk-framework-http-api-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@liskhq/lisk-framework-http-api-plugin", - "version": "0.1.0", + "version": "0.1.1-alpha.0", "description": "A plugin for lisk-framework that provides basic HTTP API endpoints to get running node information.", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -44,7 +44,7 @@ "express": "4.17.1", "express-rate-limit": "5.1.3", "ip": "1.1.5", - "lisk-framework": "^0.7.0" + "lisk-framework": "^0.7.1-alpha.0" }, "devDependencies": { "@liskhq/lisk-cryptography": "^3.0.0", diff --git a/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/errors.ts b/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/errors.ts index d9fdb6cbebd..5325e9db46f 100644 --- a/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/errors.ts +++ b/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/errors.ts @@ -13,6 +13,14 @@ */ import { Request, Response, NextFunction } from 'express'; +export class ErrorWithStatus extends Error { + public statusCode: number; + public constructor(message: string, statusCode: number) { + super(message); + this.statusCode = statusCode; + } +} + interface ErrorWithDetails extends Error { errors: Error[]; } @@ -24,6 +32,7 @@ export const errorMiddleware = () => ( _next: NextFunction, ): void => { let errors; + let responseCode = 500; if (Array.isArray(err)) { errors = err; @@ -38,5 +47,11 @@ export const errorMiddleware = () => ( Object.defineProperty(error, 'message', { enumerable: true }); } - res.status(500).send({ errors }); + if (err instanceof ErrorWithStatus) { + const { statusCode, ...message } = err; + errors = message; + responseCode = statusCode; + } + + res.status(responseCode).send({ errors }); }; diff --git a/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/whitelist.ts b/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/whitelist.ts index 2ac2146a26f..5bdca04ad3c 100644 --- a/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/whitelist.ts +++ b/framework-plugins/lisk-framework-http-api-plugin/src/middlewares/whitelist.ts @@ -13,6 +13,7 @@ */ import * as ip from 'ip'; import { Request, Response, NextFunction } from 'express'; +import { ErrorWithStatus } from './errors'; const defualtOption = { whiteList: [] }; @@ -49,5 +50,5 @@ export const whiteListMiddleware = ({ return; } - next(new Error('Access Denied')); + next(new ErrorWithStatus('Access Denied', 401)); }; diff --git a/framework-plugins/lisk-framework-http-api-plugin/test/functional/forging.spec.ts b/framework-plugins/lisk-framework-http-api-plugin/test/functional/forging.spec.ts index dc4d3610ad7..66c7ca035c5 100644 --- a/framework-plugins/lisk-framework-http-api-plugin/test/functional/forging.spec.ts +++ b/framework-plugins/lisk-framework-http-api-plugin/test/functional/forging.spec.ts @@ -144,13 +144,10 @@ describe('api/forging', () => { // Assert expect(status).toEqual(500); - expect(response).toEqual({ - errors: [ - { - message: 'Failed to enable forging as the node is not synced to the network.', - }, - ], - }); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].message).toEqual( + 'Failed to enable forging as the node is not synced to the network.', + ); }); it('should fail to enable forging when overwrite is false and maxHeightPreviouslyForged does not match', async () => { @@ -172,13 +169,10 @@ describe('api/forging', () => { // Assert expect(status).toEqual(500); - expect(response).toEqual({ - errors: [ - { - message: 'Failed to enable forging as the node is not synced to the network.', - }, - ], - }); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].message).toEqual( + 'Failed to enable forging as the node is not synced to the network.', + ); }); it('should respond with 400 and error message when address is not hex format', async () => { diff --git a/framework-plugins/lisk-framework-http-api-plugin/test/functional/peers.spec.ts b/framework-plugins/lisk-framework-http-api-plugin/test/functional/peers.spec.ts index 9ecf32a0b2c..17a2e7cb5ed 100644 --- a/framework-plugins/lisk-framework-http-api-plugin/test/functional/peers.spec.ts +++ b/framework-plugins/lisk-framework-http-api-plugin/test/functional/peers.spec.ts @@ -72,13 +72,8 @@ describe('Peers endpoint', () => { const { response, status } = await callNetwork(axios.get(getURL('/api/peers'))); // Assert expect(status).toBe(500); - expect(response).toEqual({ - errors: [ - { - message: 'test', - }, - ], - }); + expect(response.errors).toHaveLength(1); + expect(response.errors[0].message).toEqual('test'); }); }); }); diff --git a/framework-plugins/lisk-framework-monitor-plugin/package.json b/framework-plugins/lisk-framework-monitor-plugin/package.json index ef41ee3f7d6..accd97adeaf 100644 --- a/framework-plugins/lisk-framework-monitor-plugin/package.json +++ b/framework-plugins/lisk-framework-monitor-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@liskhq/lisk-framework-monitor-plugin", - "version": "0.1.0", + "version": "0.1.1-alpha.0", "description": "A plugin for lisk-framework that provides network statistics of the running node", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -47,7 +47,7 @@ "express": "4.17.1", "express-rate-limit": "5.1.3", "ip": "1.1.5", - "lisk-framework": "^0.7.0" + "lisk-framework": "^0.7.1-alpha.0" }, "devDependencies": { "@types/cors": "2.8.6", diff --git a/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/errors.ts b/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/errors.ts index d9fdb6cbebd..5325e9db46f 100644 --- a/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/errors.ts +++ b/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/errors.ts @@ -13,6 +13,14 @@ */ import { Request, Response, NextFunction } from 'express'; +export class ErrorWithStatus extends Error { + public statusCode: number; + public constructor(message: string, statusCode: number) { + super(message); + this.statusCode = statusCode; + } +} + interface ErrorWithDetails extends Error { errors: Error[]; } @@ -24,6 +32,7 @@ export const errorMiddleware = () => ( _next: NextFunction, ): void => { let errors; + let responseCode = 500; if (Array.isArray(err)) { errors = err; @@ -38,5 +47,11 @@ export const errorMiddleware = () => ( Object.defineProperty(error, 'message', { enumerable: true }); } - res.status(500).send({ errors }); + if (err instanceof ErrorWithStatus) { + const { statusCode, ...message } = err; + errors = message; + responseCode = statusCode; + } + + res.status(responseCode).send({ errors }); }; diff --git a/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/whitelist.ts b/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/whitelist.ts index 2ac2146a26f..5bdca04ad3c 100644 --- a/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/whitelist.ts +++ b/framework-plugins/lisk-framework-monitor-plugin/src/middlewares/whitelist.ts @@ -13,6 +13,7 @@ */ import * as ip from 'ip'; import { Request, Response, NextFunction } from 'express'; +import { ErrorWithStatus } from './errors'; const defualtOption = { whiteList: [] }; @@ -49,5 +50,5 @@ export const whiteListMiddleware = ({ return; } - next(new Error('Access Denied')); + next(new ErrorWithStatus('Access Denied', 401)); }; diff --git a/framework-plugins/lisk-framework-report-misbehavior-plugin/package.json b/framework-plugins/lisk-framework-report-misbehavior-plugin/package.json index d0d5acc6387..0669b2ccffd 100644 --- a/framework-plugins/lisk-framework-report-misbehavior-plugin/package.json +++ b/framework-plugins/lisk-framework-report-misbehavior-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@liskhq/lisk-framework-report-misbehavior-plugin", - "version": "0.1.0", + "version": "0.1.1-alpha.0", "description": "A plugin for lisk-framework that provides automatic detection of delegate misbehavior and sends a reportDelegateMisbehaviorTransaction to the running node", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -48,7 +48,7 @@ "@liskhq/lisk-validator": "^0.5.0", "debug": "4.1.1", "fs-extra": "9.0.0", - "lisk-framework": "^0.7.0" + "lisk-framework": "^0.7.1-alpha.0" }, "devDependencies": { "@types/cors": "2.8.6", diff --git a/framework/package.json b/framework/package.json index cacf286f822..8ad286c759a 100644 --- a/framework/package.json +++ b/framework/package.json @@ -1,6 +1,6 @@ { "name": "lisk-framework", - "version": "0.7.0", + "version": "0.7.1-alpha.0", "description": "Lisk blockchain application platform", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -62,7 +62,7 @@ "ws": "7.3.1" }, "devDependencies": { - "@liskhq/lisk-api-client": "^5.0.0", + "@liskhq/lisk-api-client": "^5.0.1-alpha.0", "@liskhq/lisk-passphrase": "^3.0.1", "@liskhq/lisk-transactions": "^5.0.0", "@types/bunyan": "1.8.6", diff --git a/framework/src/application.ts b/framework/src/application.ts index 198a3711c77..8c3b19ae2c0 100644 --- a/framework/src/application.ts +++ b/framework/src/application.ts @@ -13,7 +13,6 @@ */ import * as fs from 'fs-extra'; -import * as os from 'os'; import * as path from 'path'; import * as psList from 'ps-list'; import * as assert from 'assert'; @@ -140,7 +139,6 @@ export class Application { config.label ?? `lisk-${config.genesisConfig?.communityIdentifier}`; const mergedConfig = objects.mergeDeep({}, appConfig, config) as ApplicationConfig; - mergedConfig.rootPath = mergedConfig.rootPath.replace('~', os.homedir()); const applicationConfigErrors = validator.validate(applicationConfigSchema, mergedConfig); if (applicationConfigErrors.length) { throw new LiskValidationError(applicationConfigErrors); @@ -385,25 +383,12 @@ export class Application { getTransactionsFromPool: { handler: () => this._node.actions.getTransactionsFromPool(), }, - getTransactions: { - handler: async (params?: Record) => - this._node.actions.getTransactions(params as { data: unknown; peerId: string }), - }, postTransaction: { handler: async (params?: Record) => this._node.actions.postTransaction((params as unknown) as EventPostTransactionData), }, getLastBlock: { - handler: (params?: Record) => - this._node.actions.getLastBlock(params as { peerId: string }), - }, - getBlocksFromId: { - handler: async (params?: Record) => - this._node.actions.getBlocksFromId(params as { data: unknown; peerId: string }), - }, - getHighestCommonBlock: { - handler: async (params?: Record) => - this._node.actions.getHighestCommonBlock(params as { data: unknown; peerId: string }), + handler: () => this._node.actions.getLastBlock(), }, getAccount: { handler: async (params?: Record) => diff --git a/framework/src/controller/bus.ts b/framework/src/controller/bus.ts index 805073390fc..5b5b9416ac6 100644 --- a/framework/src/controller/bus.ts +++ b/framework/src/controller/bus.ts @@ -61,6 +61,16 @@ interface ChannelInfo { readonly type: ChannelType; } +const parseError = (id: JSONRPC.ID, err: Error | JSONRPC.JSONRPCError): JSONRPC.JSONRPCError => { + if (err instanceof JSONRPC.JSONRPCError) { + return err; + } + return new JSONRPC.JSONRPCError( + err.message, + JSONRPC.errorResponse(id, JSONRPC.internalError(err.message)), + ); +}; + export class Bus { public logger: Logger; @@ -217,13 +227,17 @@ export class Bus { const actionParams = action.params; const channelInfo = this.channels[action.module]; if (channelInfo.type === ChannelType.InMemory) { - const result = await (channelInfo.channel as BaseChannel).invoke( - actionFullName, - actionParams, - ); - return action.buildJSONRPCResponse({ - result, - }) as JSONRPC.ResponseObjectWithResult; + try { + const result = await (channelInfo.channel as BaseChannel).invoke( + actionFullName, + actionParams, + ); + return action.buildJSONRPCResponse({ + result, + }) as JSONRPC.ResponseObjectWithResult; + } catch (error) { + throw parseError(action.id, error); + } } // For child process channel @@ -233,7 +247,7 @@ export class Bus { action.toJSONRPCRequest(), (err: Error | undefined, data: JSONRPC.ResponseObjectWithResult) => { if (err) { - return reject(err); + return reject(parseError(action.id, err)); } return resolve(data); @@ -370,13 +384,13 @@ export class Bus { this._ipcServer.rpcServer.expose( 'invoke', - (action: string | JSONRPC.RequestObject, cb: NodeCallback) => { - this.invoke(action) + (message: string | JSONRPC.RequestObject, cb: NodeCallback) => { + this.invoke(message) .then(data => { cb(null, data as JSONRPC.ResponseObjectWithResult); }) - .catch(error => { - cb(error as JSONRPC.ResponseObjectWithError); + .catch((error: JSONRPC.JSONRPCError) => { + cb(error, error.response); }); }, ); @@ -393,20 +407,8 @@ export class Bus { .then(data => { socket.send(JSON.stringify(data as JSONRPC.ResponseObjectWithResult)); }) - .catch(error => { - if (error instanceof JSONRPC.JSONRPCError) { - return socket.send(JSON.stringify(error.response)); - } - const parsedAction = Action.fromJSONRPCRequest(message); - - return socket.send( - JSON.stringify( - JSONRPC.errorResponse( - parsedAction.id, - JSONRPC.internalError((error as Error).message), - ), - ), - ); + .catch((error: JSONRPC.JSONRPCError) => { + socket.send(JSON.stringify(error.response)); }); }); } diff --git a/framework/src/controller/channels/ipc_channel.ts b/framework/src/controller/channels/ipc_channel.ts index 0f1fdb56aa0..c18b210bcf2 100644 --- a/framework/src/controller/channels/ipc_channel.ts +++ b/framework/src/controller/channels/ipc_channel.ts @@ -166,17 +166,10 @@ export class IPCChannel extends BaseChannel { this._rpcClient.call( 'invoke', action.toJSONRPCRequest(), - ( - err: JSONRPC.ResponseObjectWithError | undefined, - res: JSONRPC.ResponseObjectWithResult, - ) => { + (err: JSONRPC.JSONRPCError | undefined, res: JSONRPC.ResponseObjectWithResult) => { if (err) { return reject(err); } - if (res.error) { - return reject(res.error); - } - return resolve(res.result); }, ); diff --git a/framework/src/node/network/network.ts b/framework/src/node/network/network.ts index 5a2b7595228..eb34d8888b2 100644 --- a/framework/src/node/network/network.ts +++ b/framework/src/node/network/network.ts @@ -48,13 +48,6 @@ const DB_KEY_NETWORK_NODE_SECRET = 'network:nodeSecret'; const DB_KEY_NETWORK_TRIED_PEERS_LIST = 'network:triedPeersList'; const DEFAULT_PEER_SAVE_INTERVAL = 10 * 60 * 1000; // 10min in ms -const REMOTE_ACTIONS_WHITE_LIST = [ - 'getTransactions', - 'getLastBlock', - 'getBlocksFromId', - 'getHighestCommonBlock', -]; - const REMOTE_EVENTS_WHITE_LIST = ['postTransactionsAnnouncement', 'postBlock', 'postNodeInfo']; interface NodeInfoOptions { @@ -90,6 +83,12 @@ interface P2PRequest { readonly error: (result: object) => void; } +type P2PRPCEndpointHandler = (input: { data: unknown; peerId: string }) => unknown; + +interface P2PRPCEndpoints { + [key: string]: P2PRPCEndpointHandler; +} + export class Network { private readonly _options: NetworkConfig; private readonly _channel: InMemoryChannel; @@ -99,6 +98,7 @@ export class Network { private _networkID!: string; private _secret: number | undefined; private _p2p!: liskP2P.P2P; + private _endpoints: P2PRPCEndpoints; public constructor({ options, channel, logger, nodeDB, networkVersion }: NetworkConstructor) { this._options = options; @@ -106,6 +106,7 @@ export class Network { this._logger = logger; this._nodeDB = nodeDB; this._networkVersion = networkVersion; + this._endpoints = {}; this._secret = undefined; } @@ -307,7 +308,7 @@ export class Network { return; } - if (!REMOTE_ACTIONS_WHITE_LIST.includes(request.procedure)) { + if (!Object.keys(this._endpoints).includes(request.procedure)) { const error = new Error(`Requested procedure "${request.procedure}" is not permitted.`); this._logger.error( { err: error, procedure: request.procedure }, @@ -323,18 +324,15 @@ export class Network { } try { - const result = await this._channel.invoke( - `app:${request.procedure}`, - { - data: request.data, - peerId: request.peerId, - }, - ); + const result = await this._endpoints[request.procedure]({ + data: request.data, + peerId: request.peerId, + }); this._logger.trace( { procedure: request.procedure }, 'Peer request fulfilled event: Responded to peer request', ); - request.end(result); // Send the response back to the peer. + request.end(result as object); // Send the response back to the peer. } catch (error) { this._logger.error( { err: error as Error, procedure: request.procedure }, @@ -400,6 +398,13 @@ export class Network { } } + public registerEndpoint(endpoint: string, handler: P2PRPCEndpointHandler): void { + if (this._endpoints[endpoint]) { + throw new Error(`Endpoint ${endpoint} has already been registered.`); + } + this._endpoints[endpoint] = handler; + } + public async request( requestPacket: liskP2P.p2pTypes.P2PRequestPacket, ): Promise { diff --git a/framework/src/node/node.ts b/framework/src/node/node.ts index 46514355825..c2ebbbe80e5 100644 --- a/framework/src/node/node.ts +++ b/framework/src/node/node.ts @@ -42,11 +42,7 @@ import { } from '../constants'; import { Forger } from './forger'; -import { - Transport, - HandleRPCGetTransactionsReturn, - handlePostTransactionReturn, -} from './transport'; +import { Transport, handlePostTransactionReturn } from './transport'; import { Synchronizer, BlockSynchronizationMechanism, @@ -270,6 +266,19 @@ export class Node { }, }); } + // Initialize callable P2P endpoints + this._networkModule.registerEndpoint('getTransactions', async ({ data, peerId }) => + this._transport.handleRPCGetTransactions(data, peerId), + ); + this._networkModule.registerEndpoint('getLastBlock', ({ peerId }) => + this._transport.handleRPCGetLastBlock(peerId), + ); + this._networkModule.registerEndpoint('getBlocksFromId', async ({ data, peerId }) => + this._transport.handleRPCGetBlocksFromId(data, peerId), + ); + this._networkModule.registerEndpoint('getHighestCommonBlock', async ({ data, peerId }) => + this._transport.handleRPCGetHighestCommonBlock(data, peerId), + ); // Network needs to be initialized first to call events await this._networkModule.bootstrap(this.networkIdentifier); @@ -459,11 +468,6 @@ export class Node { } return transactions.map(tx => tx.getBytes().toString('hex')); }, - getTransactions: async (params: { - data: unknown; - peerId: string; - }): Promise => - this._transport.handleRPCGetTransactions(params.data, params.peerId), getForgingStatus: async (): Promise => { const forgingStatus = await this._forger.getForgingStatusOfAllDelegates(); if (forgingStatus) { @@ -480,15 +484,8 @@ export class Node { params: EventPostTransactionData, ): Promise => this._transport.handleEventPostTransaction(params), // eslint-disable-next-line @typescript-eslint/require-await - getLastBlock: (params: { peerId: string }): string => - this._transport.handleRPCGetLastBlock(params.peerId), - getBlocksFromId: async (params: { data: unknown; peerId: string }): Promise => - this._transport.handleRPCGetBlocksFromId(params.data, params.peerId), - getHighestCommonBlock: async (params: { - data: unknown; - peerId: string; - }): Promise => - this._transport.handleRPCGetHighestCommonBlock(params.data, params.peerId), + getLastBlock: (): string => + this._chain.dataAccess.encode(this._chain.lastBlock).toString('hex'), getSchema: () => this.getSchema(), getRegisteredModules: () => this.getRegisteredModules(), getNodeInfo: () => ({ diff --git a/framework/src/node/transport/transport.ts b/framework/src/node/transport/transport.ts index e5d720fde54..e0f52347a24 100644 --- a/framework/src/node/transport/transport.ts +++ b/framework/src/node/transport/transport.ts @@ -58,7 +58,8 @@ export interface handlePostTransactionReturn { message?: string; errors?: Error[] | Error; } -export interface HandleRPCGetTransactionsReturn { + +interface HandleRPCGetTransactionsReturn { transactions: string[]; } diff --git a/framework/src/system_dirs.ts b/framework/src/system_dirs.ts index 43f1f85095d..1615db5dd5e 100644 --- a/framework/src/system_dirs.ts +++ b/framework/src/system_dirs.ts @@ -12,14 +12,18 @@ * Removal or modification of this copyright notice is prohibited. */ -import * as path from 'path'; +import { homedir } from 'os'; +import { join, resolve } from 'path'; // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types,@typescript-eslint/explicit-function-return-type -export const systemDirs = (appLabel: string, rootPath: string) => ({ - dataPath: path.join(rootPath, appLabel), - data: path.join(rootPath, appLabel, 'data'), - tmp: path.join(rootPath, appLabel, 'tmp'), - logs: path.join(rootPath, appLabel, 'logs'), - sockets: path.join(rootPath, appLabel, 'tmp', 'sockets'), - pids: path.join(rootPath, appLabel, 'tmp', 'pids'), -}); +export const systemDirs = (appLabel: string, rootPath: string) => { + const rootPathWithoutTilde = rootPath.replace('~', homedir()); + return { + dataPath: resolve(join(rootPathWithoutTilde, appLabel)), + data: resolve(join(rootPathWithoutTilde, appLabel, 'data')), + tmp: resolve(join(rootPathWithoutTilde, appLabel, 'tmp')), + logs: resolve(join(rootPathWithoutTilde, appLabel, 'logs')), + sockets: resolve(join(rootPathWithoutTilde, appLabel, 'tmp', 'sockets')), + pids: resolve(join(rootPathWithoutTilde, appLabel, 'tmp', 'pids')), + }; +}; diff --git a/framework/test/functional/rpc-api/ipc/ipc_client.spec.ts b/framework/test/functional/rpc-api/ipc/ipc_client.spec.ts index a00ff92831d..e60c6b1d4c6 100644 --- a/framework/test/functional/rpc-api/ipc/ipc_client.spec.ts +++ b/framework/test/functional/rpc-api/ipc/ipc_client.spec.ts @@ -91,8 +91,8 @@ describe('api client ipc mode', () => { it('should throw an error when action fails due to missing argument', async () => { // Assert - await expect(client.invoke('app:getBlocksFromId')).rejects.toThrow( - 'Peer not found: undefined', + await expect(client.invoke('app:getAccount')).rejects.toThrow( + 'The first argument must be of type string or an instance of Buffer', ); }); diff --git a/framework/test/functional/rpc-api/ws/ws_client.spec.ts b/framework/test/functional/rpc-api/ws/ws_client.spec.ts index 00910e15f73..149cb4c2470 100644 --- a/framework/test/functional/rpc-api/ws/ws_client.spec.ts +++ b/framework/test/functional/rpc-api/ws/ws_client.spec.ts @@ -86,8 +86,8 @@ describe('api client ws mode', () => { it('should throw an error when action fails due to missing argument', async () => { // Assert - await expect(client.invoke('app:getBlocksFromId')).rejects.toThrow( - 'Peer not found: undefined', + await expect(client.invoke('app:getAccount')).rejects.toThrow( + 'The first argument must be of type string or an instance of Buffer', ); }); diff --git a/framework/test/unit/application.spec.ts b/framework/test/unit/application.spec.ts index 9755bb99196..544ef52a842 100644 --- a/framework/test/unit/application.spec.ts +++ b/framework/test/unit/application.spec.ts @@ -83,7 +83,7 @@ describe('Application', () => { (createLogger as jest.Mock).mockReturnValue(loggerMock); beforeEach(() => { - jest.spyOn(os, 'homedir').mockReturnValue('~'); + jest.spyOn(os, 'homedir').mockReturnValue('/user'); jest.spyOn(IPCServer.prototype, 'start').mockResolvedValue(); jest.spyOn(WSServer.prototype, 'start').mockResolvedValue(jest.fn() as never); jest.spyOn(Node.prototype, 'init').mockResolvedValue(); @@ -696,7 +696,7 @@ describe('Application', () => { it('should call clearControllerPidFileSpy method with correct pid file location', async () => { const unlinkSyncSpy = jest.spyOn(fs, 'unlinkSync').mockReturnValue(); await app.shutdown(); - expect(unlinkSyncSpy).toHaveBeenCalledWith('~/.lisk/devnet/tmp/pids/controller.pid'); + expect(unlinkSyncSpy).toHaveBeenCalledWith('/user/.lisk/devnet/tmp/pids/controller.pid'); }); }); }); diff --git a/framework/test/unit/controller/controller.spec.ts b/framework/test/unit/controller/controller.spec.ts index 607ff33876a..02ef23ba14c 100644 --- a/framework/test/unit/controller/controller.spec.ts +++ b/framework/test/unit/controller/controller.spec.ts @@ -13,6 +13,7 @@ */ import * as childProcess from 'child_process'; +import * as os from 'os'; import { when } from 'jest-when'; import { BasePlugin } from '../../../src'; @@ -74,7 +75,7 @@ describe('Controller Class', () => { publish: jest.fn(), }; const config = { - rootPath: '~/.lisk', + rootPath: '/user/.lisk', rpc: { enable: false, mode: 'ipc', @@ -96,7 +97,7 @@ describe('Controller Class', () => { pids: `${config.rootPath}/${appLabel}/tmp/pids`, }; const configController = { - dataPath: '~/.lisk/#LABEL', + dataPath: '/user/.lisk/#LABEL', dirs: systemDirs, socketsPath: { root: `unix://${systemDirs.sockets}`, @@ -126,6 +127,7 @@ describe('Controller Class', () => { jest .spyOn(childProcess, 'fork') .mockReturnValue((childProcessMock as unknown) as childProcess.ChildProcess); + jest.spyOn(os, 'homedir').mockReturnValue('/user'); }); afterEach(async () => { diff --git a/framework/test/unit/system_dirs.spec.ts b/framework/test/unit/system_dirs.spec.ts index 64f0105d335..6fcda500abf 100644 --- a/framework/test/unit/system_dirs.spec.ts +++ b/framework/test/unit/system_dirs.spec.ts @@ -12,8 +12,13 @@ * Removal or modification of this copyright notice is prohibited. */ +import * as os from 'os'; import { systemDirs } from '../../src/system_dirs'; +beforeEach(() => { + jest.spyOn(os, 'homedir').mockReturnValue('/user'); +}); + describe('systemDirs', () => { it('Should return directories configuration with given app label.', () => { // Arrange @@ -25,12 +30,69 @@ describe('systemDirs', () => { // Assert expect(dirsObj).toEqual({ - dataPath: `${rootPath}/${appLabel}`, - data: `${rootPath}/${appLabel}/data`, - tmp: `${rootPath}/${appLabel}/tmp`, - logs: `${rootPath}/${appLabel}/logs`, - sockets: `${rootPath}/${appLabel}/tmp/sockets`, - pids: `${rootPath}/${appLabel}/tmp/pids`, + dataPath: `/user/.lisk/${appLabel}`, + data: `/user/.lisk/${appLabel}/data`, + tmp: `/user/.lisk/${appLabel}/tmp`, + logs: `/user/.lisk/${appLabel}/logs`, + sockets: `/user/.lisk/${appLabel}/tmp/sockets`, + pids: `/user/.lisk/${appLabel}/tmp/pids`, + }); + }); + + it('Should be able to resolve relative path correctly.', () => { + // Arrange + const appLabel = 'LABEL'; + const rootPath = '/user/../.lisk'; + + // Act + const dirsObj = systemDirs(appLabel, rootPath); + + // Assert + expect(dirsObj).toEqual({ + dataPath: `/.lisk/${appLabel}`, + data: `/.lisk/${appLabel}/data`, + tmp: `/.lisk/${appLabel}/tmp`, + logs: `/.lisk/${appLabel}/logs`, + sockets: `/.lisk/${appLabel}/tmp/sockets`, + pids: `/.lisk/${appLabel}/tmp/pids`, + }); + }); + + it('Should be able to resolve absolute path correctly.', () => { + // Arrange + const appLabel = 'LABEL'; + const rootPath = '/customPath/.lisk'; + + // Act + const dirsObj = systemDirs(appLabel, rootPath); + + // Assert + expect(dirsObj).toEqual({ + dataPath: `/customPath/.lisk/${appLabel}`, + data: `/customPath/.lisk/${appLabel}/data`, + tmp: `/customPath/.lisk/${appLabel}/tmp`, + logs: `/customPath/.lisk/${appLabel}/logs`, + sockets: `/customPath/.lisk/${appLabel}/tmp/sockets`, + pids: `/customPath/.lisk/${appLabel}/tmp/pids`, + }); + }); + + it('Should be able to resolve home path correctly.', () => { + // Arrange + const appLabel = 'LABEL'; + const rootPath = '~/.lisk'; + + // Act + const dirsObj = systemDirs(appLabel, rootPath); + + // Assert + expect(dirsObj).toEqual({ + dataPath: `/user/.lisk/${appLabel}`, + data: `/user/.lisk/${appLabel}/data`, + tmp: `/user/.lisk/${appLabel}/tmp`, + logs: `/user/.lisk/${appLabel}/logs`, + sockets: `/user/.lisk/${appLabel}/tmp/sockets`, + pids: `/user/.lisk/${appLabel}/tmp/pids`, }); }); }); diff --git a/sdk/package.json b/sdk/package.json index 515f5ca35bc..c5342704664 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "lisk-sdk", - "version": "5.0.0", + "version": "5.0.1-alpha.0", "description": "Official SDK for the Lisk blockchain application platform", "author": "Lisk Foundation , lightcurve GmbH ", "license": "Apache-2.0", @@ -29,16 +29,16 @@ "build": "tsc" }, "dependencies": { - "@liskhq/lisk-api-client": "^5.0.0", + "@liskhq/lisk-api-client": "^5.0.1-alpha.0", "@liskhq/lisk-bft": "^0.2.0", "@liskhq/lisk-chain": "^0.2.0", "@liskhq/lisk-codec": "^0.1.0", "@liskhq/lisk-cryptography": "^3.0.0", "@liskhq/lisk-db": "^0.1.0", - "@liskhq/lisk-framework-forger-plugin": "^0.1.0", - "@liskhq/lisk-framework-http-api-plugin": "^0.1.0", - "@liskhq/lisk-framework-monitor-plugin": "^0.1.0", - "@liskhq/lisk-framework-report-misbehavior-plugin": "^0.1.0", + "@liskhq/lisk-framework-forger-plugin": "^0.1.1-alpha.0", + "@liskhq/lisk-framework-http-api-plugin": "^0.1.1-alpha.0", + "@liskhq/lisk-framework-monitor-plugin": "^0.1.1-alpha.0", + "@liskhq/lisk-framework-report-misbehavior-plugin": "^0.1.1-alpha.0", "@liskhq/lisk-genesis": "^0.1.0", "@liskhq/lisk-p2p": "^0.6.0", "@liskhq/lisk-passphrase": "^3.0.1", @@ -47,7 +47,7 @@ "@liskhq/lisk-tree": "^0.1.0", "@liskhq/lisk-utils": "^0.1.0", "@liskhq/lisk-validator": "^0.5.0", - "lisk-framework": "^0.7.0" + "lisk-framework": "^0.7.1-alpha.0" }, "devDependencies": { "eslint": "7.8.1", diff --git a/yarn.lock b/yarn.lock index fa83faa735f..02971a0648c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -395,6 +395,18 @@ unique-filename "^1.1.1" which "^1.3.1" +"@hapi/hoek@^9.0.0": + version "9.1.0" + resolved "https://registry.yarnpkg.com/@hapi/hoek/-/hoek-9.1.0.tgz#6c9eafc78c1529248f8f4d92b0799a712b6052c6" + integrity sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw== + +"@hapi/topo@^5.0.0": + version "5.0.0" + resolved "https://registry.yarnpkg.com/@hapi/topo/-/topo-5.0.0.tgz#c19af8577fa393a06e9c77b60995af959be721e7" + integrity sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw== + dependencies: + "@hapi/hoek" "^9.0.0" + "@istanbuljs/load-nyc-config@^1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b" @@ -1713,6 +1725,23 @@ dependencies: any-observable "^0.3.0" +"@sideway/address@^4.1.0": + version "4.1.0" + resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.0.tgz#0b301ada10ac4e0e3fa525c90615e0b61a72b78d" + integrity sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA== + dependencies: + "@hapi/hoek" "^9.0.0" + +"@sideway/formula@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@sideway/formula/-/formula-3.0.0.tgz#fe158aee32e6bd5de85044be615bc08478a0a13c" + integrity sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg== + +"@sideway/pinpoint@^2.0.0": + version "2.0.0" + resolved "https://registry.yarnpkg.com/@sideway/pinpoint/-/pinpoint-2.0.0.tgz#cff8ffadc372ad29fd3f78277aeb29e632cc70df" + integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ== + "@sinonjs/commons@^1", "@sinonjs/commons@^1.6.0", "@sinonjs/commons@^1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.1.tgz#da5fd19a5f71177a53778073978873964f49acf1" @@ -3014,7 +3043,7 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== -axios@0.19.2: +axios@0.19.2, axios@^0.19.2: version "0.19.2" resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== @@ -3172,7 +3201,7 @@ blob-util@2.0.2: resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== -bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: +bluebird@3.7.2, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -3709,7 +3738,7 @@ check-error@^1.0.2: resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= -check-more-types@^2.24.0: +check-more-types@2.24.0, check-more-types@^2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= @@ -4483,6 +4512,13 @@ debug@4.1.1, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" +debug@4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== + dependencies: + ms "2.1.2" + debug@^3.0, debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -4781,7 +4817,7 @@ duplexer2@^0.1.2, duplexer2@~0.1.0, duplexer2@~0.1.2: dependencies: readable-stream "^2.0.2" -duplexer@^0.1.1: +duplexer@^0.1.1, duplexer@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.2.tgz#3abe43aef3835f8ae077d136ddce0f276b0400e6" integrity sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg== @@ -5238,6 +5274,19 @@ etag@~1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +event-stream@=3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + integrity sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE= + dependencies: + duplexer "~0.1.1" + from "~0" + map-stream "~0.1.0" + pause-stream "0.0.11" + split "0.3" + stream-combiner "~0.0.4" + through "~2.3.1" + eventemitter2@6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.0.0.tgz#218eb512c3603c5341724b6af7b686a1aa5ab8f5" @@ -5271,6 +5320,22 @@ exec-sh@^0.3.2: resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A== +execa@3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89" + integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + p-finally "^2.0.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" @@ -5808,6 +5873,11 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +from@~0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + integrity sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4= + fromentries@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/fromentries/-/fromentries-1.2.0.tgz#e6aa06f240d6267f913cea422075ef88b63e7897" @@ -7716,6 +7786,17 @@ jest@26.4.2: import-local "^3.0.2" jest-cli "^26.4.2" +joi@^17.1.1: + version "17.3.0" + resolved "https://registry.yarnpkg.com/joi/-/joi-17.3.0.tgz#f1be4a6ce29bc1716665819ac361dfa139fff5d2" + integrity sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg== + dependencies: + "@hapi/hoek" "^9.0.0" + "@hapi/topo" "^5.0.0" + "@sideway/address" "^4.1.0" + "@sideway/formula" "^3.0.0" + "@sideway/pinpoint" "^2.0.0" + jquery@^3.4.0: version "3.5.1" resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.5.1.tgz#d7b4d08e1bfdb86ad2f1a3d039ea17304717abb5" @@ -7938,7 +8019,7 @@ labeled-stream-splicer@^2.0.0: inherits "^2.0.1" stream-splicer "^2.0.0" -lazy-ass@^1.6.0: +lazy-ass@1.6.0, lazy-ass@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= @@ -8451,6 +8532,11 @@ map-obj@^4.0.0: resolved "https://registry.yarnpkg.com/map-obj/-/map-obj-4.1.0.tgz#b91221b542734b9f14256c0132c897c5d7256fd5" integrity sha512-glc9y00wgtwcDmp7GaE/0b0OnxpNJsVf3ael/An6Fe2Q51LLwN1er6sdomLRzz5h0+yMpiYLhWYF5R7HeqVd4g== +map-stream@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + integrity sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ= + map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" @@ -9394,6 +9480,11 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= +p-finally@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561" + integrity sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw== + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -9728,6 +9819,13 @@ pathval@^1.1.0: resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= +pause-stream@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + integrity sha1-/lo0sMvOErWqaitAPuLnO2AvFEU= + dependencies: + through "~2.3" + pbkdf2@^3.0.3, pbkdf2@^3.0.9: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -10048,6 +10146,13 @@ ps-list@7.0.0: resolved "https://registry.yarnpkg.com/ps-list/-/ps-list-7.0.0.tgz#fd740a839786605d257117b899031db9b34b8b4b" integrity sha512-ZDhdxqb+kE895BAvqIdGnWwfvB43h7KHMIcJC0hw7xLbbiJoprS+bqZxuGZ0jWdDxZEvB3jpnfgJyOn3lmsH+Q== +ps-tree@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.2.0.tgz#5e7425b89508736cdd4f2224d028f7bb3f722ebd" + integrity sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA== + dependencies: + event-stream "=3.3.4" + psl@^1.1.28: version "1.8.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" @@ -11257,6 +11362,13 @@ split2@^2.0.0: dependencies: through2 "^2.0.2" +split@0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + integrity sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8= + dependencies: + through "2" + split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" @@ -11308,6 +11420,19 @@ stampit@4.3.1: resolved "https://registry.yarnpkg.com/stampit/-/stampit-4.3.1.tgz#90d813671af18f9fc9dcd6816085d7f99174a454" integrity sha512-pcCXPy7VUXsxKE+oN2xroBmaQRGIIaRSIeD+HICoZb8w9CS5ojGmKPp3OfkXnO4D9UklcKCm9WmkAvAdrrlfZg== +start-server-and-test@1.11.6: + version "1.11.6" + resolved "https://registry.yarnpkg.com/start-server-and-test/-/start-server-and-test-1.11.6.tgz#d1ddc41bc49a61302829eecc7b799ca98562ee9f" + integrity sha512-+0T83W/R7CVgIE2HJcrpJDleLt7Skc2Xj8jWWsItRGdpZwenAv0YtIpBEKoL64pwUtPAPoHuYUtvWUOfCRoVjg== + dependencies: + bluebird "3.7.2" + check-more-types "2.24.0" + debug "4.3.1" + execa "3.4.0" + lazy-ass "1.6.0" + ps-tree "1.2.0" + wait-on "5.2.0" + static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" @@ -11350,6 +11475,13 @@ stream-combiner2@^1.1.1: duplexer2 "~0.1.0" readable-stream "^2.0.2" +stream-combiner@~0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" + integrity sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ= + dependencies: + duplexer "~0.1.1" + stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" @@ -11804,7 +11936,7 @@ through2@^3.0.0: inherits "^2.0.4" readable-stream "2 || 3" -through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8: +through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8, through@~2.3, through@~2.3.1: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -12373,6 +12505,17 @@ w3c-xmlserializer@^2.0.0: dependencies: xml-name-validator "^3.0.0" +wait-on@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/wait-on/-/wait-on-5.2.0.tgz#6711e74422523279714a36d52cf49fb47c9d9597" + integrity sha512-U1D9PBgGw2XFc6iZqn45VBubw02VsLwnZWteQ1au4hUVHasTZuFSKRzlTB2dqgLhji16YVI8fgpEpwUdCr8B6g== + dependencies: + axios "^0.19.2" + joi "^17.1.1" + lodash "^4.17.19" + minimist "^1.2.5" + rxjs "^6.5.5" + walker@^1.0.7, walker@~1.0.5: version "1.0.7" resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"