First stage renames, rpc-{core,provider,rx} (#220)
* Docs renames (& removal for re-gen) * Rename api, api-provider & api-rx * Apoi -> Rpc, RxApi -> RpcRx (classes) * Add .nodoc * Bump version * Link updates (summary) * Cleanup deleted (master version) * Deleted re-instated * More... ;) * Types... * Revert .nodoc
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
ISC License (ISC)
|
||||
|
||||
Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
PERFORMANCE OF THIS SOFTWARE.
|
||||
@@ -0,0 +1,32 @@
|
||||
[](https://polkadot.js.org)
|
||||

|
||||
[](https://github.com/Flet/semistandard)
|
||||
[](https://www.npmjs.com/package/@polkadot/rpc-provider)
|
||||
[](https://travis-ci.org/polkadot-js/api)
|
||||
[](https://codeclimate.com/github/polkadot-js/api/maintainability)
|
||||
[](https://coveralls.io/github/polkadot-js/api?branch=master)
|
||||
[](https://david-dm.org/polkadot-js/api?path=packages/rpc-provider)
|
||||
[](https://david-dm.org/polkadot-js/api?path=packages/rpc-provider#info=devDependencies)
|
||||
|
||||
# @polkadot/rpc-provider
|
||||
|
||||
Generic transport providers to handle the transport of method calls to and from Polkadot clients from applications interacting with it. Generally, unless you are operating at a low-level and taking care of encoding and decoding of parameters/results, it won't be directly used. API interfaces building on top these providers can support various transports with the same underlying interfaces.
|
||||
|
||||
## Usage
|
||||
|
||||
Installation -
|
||||
|
||||
```
|
||||
npm install --save @polkadot/rpc-provider
|
||||
```
|
||||
|
||||
Initialisation -
|
||||
|
||||
```js
|
||||
import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
|
||||
const provider = new WsProvider('http://127.0.0.1:9944');
|
||||
const version = await provider.send('client_version', []);
|
||||
|
||||
console.log('clientVersion', version);
|
||||
```
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "@polkadot/rpc-provider",
|
||||
"version": "0.31.0",
|
||||
"description": "Transport providers for the API",
|
||||
"main": "index.js",
|
||||
"keywords": [
|
||||
"Polkadot",
|
||||
"JsonRPC"
|
||||
],
|
||||
"author": "Jaco Greeff <jacogr@gmail.com>",
|
||||
"maintainers": [
|
||||
"Jaco Greeff <jacogr@gmail.com>"
|
||||
],
|
||||
"contributors": [],
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=6.4"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public",
|
||||
"registry": "https://registry.npmjs.org"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/polkadot-js/api.git"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/polkadot-js/api/issues"
|
||||
},
|
||||
"homepage": "https://github.com/polkadot-js/api/tree/master/packages/rpc-provider#readme",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.0.0",
|
||||
"@polkadot/storage": "^0.31.0",
|
||||
"@polkadot/util": "^0.30.7",
|
||||
"@polkadot/util-crypto": "^0.30.7",
|
||||
"@polkadot/util-keyring": "^0.30.7",
|
||||
"@types/nock": "^9.1.3",
|
||||
"eventemitter3": "^3.1.0",
|
||||
"isomorphic-fetch": "^2.2.1",
|
||||
"websocket": "^1.0.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mock-socket": "^8.0.2",
|
||||
"nock": "^9.6.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import createCoder from './index';
|
||||
|
||||
describe('decodeResponse', () => {
|
||||
let coder;
|
||||
|
||||
beforeEach(() => {
|
||||
coder = createCoder();
|
||||
});
|
||||
|
||||
it('expects a non-empty input object', () => {
|
||||
expect(
|
||||
() => coder.decodeResponse()
|
||||
).toThrow(/Empty response/);
|
||||
});
|
||||
|
||||
it('expects a valid jsonrpc field', () => {
|
||||
expect(
|
||||
() => coder.decodeResponse({})
|
||||
).toThrow(/Invalid jsonrpc/);
|
||||
});
|
||||
|
||||
it('expects a valid id field', () => {
|
||||
expect(
|
||||
() => coder.decodeResponse({ jsonrpc: '2.0' })
|
||||
).toThrow(/Invalid id/);
|
||||
});
|
||||
|
||||
it('expects a valid result field', () => {
|
||||
expect(
|
||||
() => coder.decodeResponse({ id: 1, jsonrpc: '2.0' })
|
||||
).toThrow(/No result/);
|
||||
});
|
||||
|
||||
it('throws any error found', () => {
|
||||
expect(
|
||||
() => coder.decodeResponse({ id: 1, jsonrpc: '2.0', error: { code: 123, message: 'test error' } })
|
||||
).toThrow(/\[123\]: test error/);
|
||||
});
|
||||
|
||||
it('returns the result', () => {
|
||||
expect(
|
||||
coder.decodeResponse({ id: 1, jsonrpc: '2.0', result: 'some result' })
|
||||
).toEqual('some result');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { JsonRpcResponse, JsonRpcResponseBase$Error } from '../../types';
|
||||
import { RpcCoderState } from './types';
|
||||
|
||||
import assert from '@polkadot/util/assert';
|
||||
import isNumber from '@polkadot/util/is/number';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
|
||||
function checkError (error?: JsonRpcResponseBase$Error) {
|
||||
if (!error) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { code, message } = error;
|
||||
|
||||
throw new Error(`[${code}]: ${message}`);
|
||||
}
|
||||
|
||||
export default function decodeResponse (self: RpcCoderState, response: JsonRpcResponse): any {
|
||||
assert(response, 'Empty response object received');
|
||||
assert(response.jsonrpc === '2.0', 'Invalid jsonrpc field in decoded object');
|
||||
|
||||
const isSubscription = !isUndefined(response.params) && !isUndefined(response.method);
|
||||
|
||||
assert(isNumber(response.id) || (isSubscription && isNumber(response.params.subscription)), 'Invalid id field in decoded object');
|
||||
|
||||
checkError(response.error);
|
||||
|
||||
assert(!isUndefined(response.result) || isSubscription, 'No result found in JsonRpc response');
|
||||
|
||||
if (isSubscription) {
|
||||
checkError(response.params.error);
|
||||
|
||||
return response.params.result;
|
||||
}
|
||||
|
||||
return response.result;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import createCoder from './index';
|
||||
|
||||
describe('encodeJson', () => {
|
||||
let coder;
|
||||
|
||||
beforeEach(() => {
|
||||
coder = createCoder();
|
||||
});
|
||||
|
||||
it('encodes a valid JsonRPC JSON string', () => {
|
||||
expect(
|
||||
coder.encodeJson('method', 'params')
|
||||
).toEqual('{"id":1,"jsonrpc":"2.0","method":"method","params":"params"}');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { RpcCoderState } from './types';
|
||||
|
||||
import encodeObject from './encodeObject';
|
||||
|
||||
export default function encodeJson (self: RpcCoderState, method: string, params: Array<any>): string {
|
||||
return JSON.stringify(
|
||||
encodeObject(self, method, params)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import createCoder from './index';
|
||||
|
||||
describe('encodeObject', () => {
|
||||
let coder;
|
||||
|
||||
beforeEach(() => {
|
||||
coder = createCoder();
|
||||
});
|
||||
|
||||
it('starts with id === 0 (nothing sent)', () => {
|
||||
expect(coder.getId()).toEqual(0);
|
||||
});
|
||||
|
||||
it('encodes a valid JsonRPC object', () => {
|
||||
expect(
|
||||
coder.encodeObject('method', 'params')
|
||||
).toEqual({
|
||||
id: 1,
|
||||
jsonrpc: '2.0',
|
||||
method: 'method',
|
||||
params: 'params'
|
||||
});
|
||||
expect(coder.getId()).toEqual(1);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { JsonRpcRequest } from '../../types';
|
||||
import { RpcCoderState } from './types';
|
||||
|
||||
export default function encodeObject (self: RpcCoderState, method: string, params: Array<any>): JsonRpcRequest {
|
||||
return {
|
||||
id: ++self.id,
|
||||
jsonrpc: '2.0',
|
||||
method,
|
||||
params
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { JsonRpcRequest, JsonRpcResponse } from '../../types';
|
||||
import { RpcCoder, RpcCoderState } from './types';
|
||||
|
||||
import decodeResponse from './decodeResponse';
|
||||
import encodeJson from './encodeJson';
|
||||
import encodeObject from './encodeObject';
|
||||
|
||||
export default function rpcCoder (): RpcCoder {
|
||||
const self: RpcCoderState = {
|
||||
id: 0
|
||||
};
|
||||
|
||||
return {
|
||||
decodeResponse: (response: JsonRpcResponse): any =>
|
||||
decodeResponse(self, response),
|
||||
encodeJson: (method: string, params: Array<any>): string =>
|
||||
encodeJson(self, method, params),
|
||||
encodeObject: (method: string, params: Array<any>): JsonRpcRequest =>
|
||||
encodeObject(self, method, params),
|
||||
getId: (): number =>
|
||||
self.id
|
||||
};
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { JsonRpcRequest, JsonRpcResponse } from '../../types';
|
||||
|
||||
export type RpcCoder = {
|
||||
decodeResponse: (response: JsonRpcResponse) => any,
|
||||
encodeObject: (method: string, params: Array<any>) => JsonRpcRequest,
|
||||
encodeJson: (method: string, params: Array<any>) => string,
|
||||
getId: () => number
|
||||
};
|
||||
|
||||
export type RpcCoderState = {
|
||||
id: number
|
||||
};
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { TEST_HTTP_URL } from '../../test/mockHttp';
|
||||
|
||||
import Http from './index';
|
||||
|
||||
describe('Http', () => {
|
||||
let http;
|
||||
|
||||
beforeEach(() => {
|
||||
http = new Http(TEST_HTTP_URL);
|
||||
});
|
||||
|
||||
it('requires an http:// prefixed endpoint', () => {
|
||||
expect(
|
||||
() => new Http('ws://')
|
||||
).toThrow(/with 'http/);
|
||||
});
|
||||
|
||||
it('allows https:// endpoints', () => {
|
||||
expect(
|
||||
() => new Http('https://')
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('always returns isConnected true', () => {
|
||||
expect(http.isConnected()).toEqual(true);
|
||||
});
|
||||
|
||||
it('does not (yet) support subscribe', () => {
|
||||
return http.subscribe().catch((error) => {
|
||||
expect(error.message).toMatch(/does not have subscriptions/);
|
||||
});
|
||||
});
|
||||
|
||||
it('does not (yet) support unsubscribe', () => {
|
||||
return http.unsubscribe().catch((error) => {
|
||||
expect(error.message).toMatch(/does not have subscriptions/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Logger } from '@polkadot/util/types';
|
||||
import { RpcCoder } from '../coder/json/types';
|
||||
import { ProviderInterface, ProviderInterface$Callback, ProviderInterface$Emitted, ProviderInterface$EmitCb } from '../types';
|
||||
|
||||
import './polyfill';
|
||||
|
||||
import assert from '@polkadot/util/assert';
|
||||
import logger from '@polkadot/util/logger';
|
||||
|
||||
import coder from '../coder/json';
|
||||
|
||||
const ERROR_SUBSCRIBE = 'HTTP Provider does not have subscriptions, use WebSockets instead';
|
||||
|
||||
/**
|
||||
* @name HttpProvider
|
||||
* @summary The HTTP Provider allows sending requests using HTTP to a HTTP RPC server TCP port.
|
||||
* @description It does not support subscriptions so you won't be able to listen to events
|
||||
* such as new blocks or balance changes. It is usually preferrable using the [[WsProvider]].
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/rpc-core';
|
||||
* import HttpProvider from '@polkadot/rpc-provider/http';
|
||||
*
|
||||
* const provider = new HttpProvider('http://127.0.0.1:9933');
|
||||
* const api = new Api(provider);
|
||||
* ```
|
||||
*
|
||||
* @see [[WsProvider]]
|
||||
*/
|
||||
export default class HttpProvider implements ProviderInterface {
|
||||
private coder: RpcCoder;
|
||||
private endpoint: string;
|
||||
private l: Logger;
|
||||
|
||||
/**
|
||||
* @param {string} endpoint The endpoint url starting with http://
|
||||
*/
|
||||
constructor (endpoint: string) {
|
||||
assert(/^(https|http):\/\//.test(endpoint), `Endpoint should start with 'http://', received '${endpoint}'`);
|
||||
|
||||
this.coder = coder();
|
||||
this.endpoint = endpoint;
|
||||
this.l = logger('api-http');
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Whether the node is connected or not.
|
||||
* @return {boolean} true if connected
|
||||
*/
|
||||
isConnected (): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Events are not supported with the HttpProvider, see [[WsProvider]].
|
||||
* @description HTTP Provider does not have 'on' emitters. WebSockets should be used instead.
|
||||
*/
|
||||
on (type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): void {
|
||||
this.l.error(`HTTP Provider does not have 'on' emitters, use WebSockets instead`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Send HTTP POST Request with Body to configured HTTP Endpoint.
|
||||
*/
|
||||
async send (method: string, params: Array<any>): Promise<any> {
|
||||
const body = this.coder.encodeJson(method, params);
|
||||
const response = await fetch(this.endpoint, {
|
||||
body,
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Length': `${body.length}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
assert(response.ok, `[${response.status}]: ${response.statusText}`);
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
return this.coder.decodeResponse(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Subscriptions are not supported with the HttpProvider, see [[WsProvider]].
|
||||
*/
|
||||
async subscribe (types: string, method: string, params: Array<any>, cb: ProviderInterface$Callback): Promise<number> {
|
||||
this.l.error(ERROR_SUBSCRIBE);
|
||||
|
||||
throw new Error(ERROR_SUBSCRIBE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Subscriptions are not supported with the HttpProvider, see [[WsProvider]].
|
||||
*/
|
||||
async unsubscribe (type: string, method: string, id: number): Promise<boolean> {
|
||||
this.l.error(ERROR_SUBSCRIBE);
|
||||
|
||||
throw new Error(ERROR_SUBSCRIBE);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
describe('http/polyfill', () => {
|
||||
let origFetch;
|
||||
|
||||
beforeEach(() => {
|
||||
origFetch = global.fetch;
|
||||
global.fetch = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.fetch = origFetch;
|
||||
});
|
||||
|
||||
it('polyfills with no exceptions (without fetch)', () => {
|
||||
expect(require('./polyfill')).toBeDefined();
|
||||
});
|
||||
|
||||
it('polyfills with no exceptions (with fetch)', () => {
|
||||
global.fetch = () => true;
|
||||
|
||||
expect(require('./polyfill')).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
// tslint:disable-next-line
|
||||
if (typeof fetch === 'undefined') {
|
||||
require('isomorphic-fetch');
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockHttp, TEST_HTTP_URL } from '../../test/mockHttp';
|
||||
|
||||
import Http from './index';
|
||||
|
||||
describe('send', () => {
|
||||
let http;
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
http = new Http(TEST_HTTP_URL);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('passes the body through correctly', () => {
|
||||
mock = mockHttp([{
|
||||
method: 'test_body',
|
||||
reply: {
|
||||
result: 'ok'
|
||||
}
|
||||
}]);
|
||||
|
||||
return http
|
||||
.send('test_body', ['param'])
|
||||
.then((result) => {
|
||||
expect(mock.body['test_body']).toEqual({
|
||||
id: 1,
|
||||
jsonrpc: '2.0',
|
||||
method: 'test_body',
|
||||
params: ['param']
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('throws error when !response.ok', () => {
|
||||
mock = mockHttp([{
|
||||
code: 500,
|
||||
method: 'test_error'
|
||||
}]);
|
||||
|
||||
return http
|
||||
.send('test_error', [])
|
||||
.catch((error) => {
|
||||
expect(error.message).toMatch(/\[500\]: Internal Server/);
|
||||
});
|
||||
});
|
||||
});
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Logger } from '@polkadot/util/types';
|
||||
import { RpcCoder } from '../coder/json/types';
|
||||
|
||||
export type HttpState = {
|
||||
coder: RpcCoder,
|
||||
endpoint: string,
|
||||
l: Logger
|
||||
};
|
||||
@@ -0,0 +1,6 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
export { default as HttpProvider } from './http';
|
||||
export { default as WsProvider } from './ws';
|
||||
@@ -0,0 +1,35 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { ProviderInterface, ProviderInterface$Emitted, ProviderInterface$EmitCb } from '../types';
|
||||
|
||||
import mocks from './mocks';
|
||||
import on from './on';
|
||||
import send from './send';
|
||||
import state from './state';
|
||||
import subscribe from './subscribe';
|
||||
import unsubscribe from './unsubscribe';
|
||||
|
||||
/**
|
||||
* A moock provider mainly used for testing.
|
||||
* @return {ProviderInterface} The mock provider
|
||||
*/
|
||||
export default function mockProvider (): ProviderInterface {
|
||||
const self = state();
|
||||
|
||||
mocks(self);
|
||||
|
||||
return {
|
||||
isConnected: (): boolean =>
|
||||
true,
|
||||
on: (type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): void =>
|
||||
on(self, type, sub),
|
||||
send: (method: string, params: Array<any>): Promise<any> =>
|
||||
send(self, method, params),
|
||||
subscribe: (type: string, method: string, ...params: Array<any>): Promise<number> =>
|
||||
subscribe(self, type, method, params),
|
||||
unsubscribe: (type: string, method: string, id: number): Promise<boolean> =>
|
||||
unsubscribe(self, type, method, id)
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
// FIXME: This file is way too long and way too messy
|
||||
|
||||
import { StorageFunction } from '@polkadot/types/StorageKey';
|
||||
import { KeyringPair } from '@polkadot/util-keyring/types';
|
||||
import { ProviderInterface$Emitted } from '../types';
|
||||
import { MockState, MockState$Db, MockState$Subscriptions } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import storage from '@polkadot/storage/static';
|
||||
import Header from '@polkadot/types/Header';
|
||||
import bnToU8a from '@polkadot/util/bn/toU8a';
|
||||
import u8aToHex from '@polkadot/util/u8a/toHex';
|
||||
import randomAsU8a from '@polkadot/util-crypto/random/asU8a';
|
||||
import testKeyring from '@polkadot/util-keyring/testing';
|
||||
|
||||
const keyring = testKeyring();
|
||||
|
||||
const emitEvents: Array<ProviderInterface$Emitted> = ['connected', 'disconnected'];
|
||||
let emitIndex = 0;
|
||||
|
||||
function makeBlockHeader (prevNumber: BN) {
|
||||
const blockNumber = prevNumber.addn(1);
|
||||
|
||||
return {
|
||||
digest: {
|
||||
logs: []
|
||||
},
|
||||
extrinsicsRoot: randomAsU8a(),
|
||||
number: blockNumber,
|
||||
parentHash: prevNumber.eqn(-1)
|
||||
? new Uint8Array(32)
|
||||
: bnToU8a(prevNumber, 256, false),
|
||||
stateRoot: bnToU8a(blockNumber, 256, false)
|
||||
};
|
||||
}
|
||||
|
||||
function updateSubs (subscriptions: MockState$Subscriptions, method: string, value: any) {
|
||||
subscriptions[method].lastValue = value;
|
||||
|
||||
Object
|
||||
.values(subscriptions[method].callbacks)
|
||||
.forEach((cb) => {
|
||||
try {
|
||||
cb(null, value);
|
||||
} catch (error) {
|
||||
console.error(`Error on '${method}' subscription`, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setStorageBn (db: MockState$Db, createKey: StorageFunction, value: BN | number, ...keyParams: Array<Uint8Array>): void {
|
||||
const keyValue = u8aToHex(
|
||||
createKey(...keyParams)
|
||||
);
|
||||
|
||||
db[keyValue] = bnToU8a(value, 64, true);
|
||||
}
|
||||
|
||||
export default function mocks ({ emitter, db, subscriptions }: MockState): void {
|
||||
let newHead = makeBlockHeader(new BN(-1));
|
||||
|
||||
setInterval(() => {
|
||||
if (++emitIndex === emitEvents.length) {
|
||||
emitIndex = 0;
|
||||
}
|
||||
|
||||
emitter.emit(emitEvents[emitIndex]);
|
||||
|
||||
newHead = makeBlockHeader(newHead.number);
|
||||
|
||||
keyring.getPairs().forEach(({ publicKey }: KeyringPair, index: number) => {
|
||||
setStorageBn(db, storage.balances.freeBalance, newHead.number.muln(3).iaddn(index), publicKey());
|
||||
setStorageBn(db, storage.system.accountNonce, newHead.number.addn(index), publicKey());
|
||||
});
|
||||
|
||||
setStorageBn(db, storage.timestamp.now, Math.floor(Date.now() / 1000));
|
||||
|
||||
updateSubs(subscriptions, 'chain_newHead', new Header(newHead).toJSON());
|
||||
}, 5000);
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { ProviderInterface$Emitted, ProviderInterface$EmitCb } from '../types';
|
||||
import { MockState } from './types';
|
||||
|
||||
export default function on (self: MockState, type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): void {
|
||||
self.emitter.on(type, sub);
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { MockState } from './types';
|
||||
|
||||
export default async function send ({ requests, db }: MockState, method: string, params: Array<any>): Promise<any> {
|
||||
if (!requests[method]) {
|
||||
throw new Error(`provider.send: Invalid method '${method}'`);
|
||||
}
|
||||
|
||||
return requests[method](db, params);
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import createState from './state';
|
||||
|
||||
describe('state', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createState();
|
||||
});
|
||||
|
||||
it('generates a sane initial state', () => {
|
||||
expect(
|
||||
state
|
||||
).toMatchObject({
|
||||
subscriptionId: 0,
|
||||
subscriptionMap: {}
|
||||
});
|
||||
});
|
||||
|
||||
it('generates a proper section_method map', () => {
|
||||
expect(
|
||||
state.subscriptions
|
||||
).toMatchObject({
|
||||
'chain_newHead': {
|
||||
callbacks: {},
|
||||
lastValue: null
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { MockState, MockState$Db, MockState$Subscriptions } from './types';
|
||||
|
||||
import E3 from 'eventemitter3';
|
||||
import interfaces from '@polkadot/jsonrpc/index';
|
||||
import u8aToHex from '@polkadot/util/u8a/toHex';
|
||||
import logger from '@polkadot/util/logger';
|
||||
|
||||
const l = logger('api-mock');
|
||||
|
||||
const SUBSCRIPTIONS: string[] = Array.prototype.concat.apply(
|
||||
[], Object.values(interfaces).map((area) =>
|
||||
Object
|
||||
.values(area.methods)
|
||||
.filter((method) =>
|
||||
method.isSubscription
|
||||
)
|
||||
.map(({ method, section }) =>
|
||||
`${section}_${method}`
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const REQUESTS = {
|
||||
'state_getStorage': (storage: MockState$Db, params: Array<any>): string => {
|
||||
return u8aToHex(
|
||||
storage[(params[0] as string)]
|
||||
);
|
||||
},
|
||||
'system_chain': (): string => 'mockChain',
|
||||
'system_name': (): string => 'mockClient',
|
||||
'system_version': (): string => '9.8.7'
|
||||
};
|
||||
|
||||
export default function state (): MockState {
|
||||
const db = {};
|
||||
const subscriptions: MockState$Subscriptions = SUBSCRIPTIONS.reduce((subs, name) => {
|
||||
subs[name] = {
|
||||
callbacks: {},
|
||||
lastValue: null
|
||||
};
|
||||
|
||||
return subs;
|
||||
}, ({} as MockState$Subscriptions));
|
||||
|
||||
return {
|
||||
emitter: new E3.EventEmitter(),
|
||||
l,
|
||||
requests: Object.assign({}, REQUESTS),
|
||||
db,
|
||||
subscriptionId: 0,
|
||||
subscriptionMap: {},
|
||||
subscriptions
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import createState from './state';
|
||||
import subscribe from './subscribe';
|
||||
|
||||
describe('subscribe', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createState();
|
||||
});
|
||||
|
||||
it('fails on unknown methods', () => {
|
||||
return subscribe(state, 'test', 'test_notFound').catch((error) => {
|
||||
expect(error.message).toMatch(/Invalid method 'test_notFound'/);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns a subscription id', () => {
|
||||
return subscribe(state, 'chain_newHead', 'chain_newHead', [() => void 0]).then((id) => {
|
||||
expect(id).toEqual(state.subscriptionId);
|
||||
});
|
||||
});
|
||||
|
||||
it('stores the mapping values', () => {
|
||||
const cb = () => void 0;
|
||||
|
||||
return subscribe(state, 'chain_newHead', 'chain_newHead', [cb]).then((id) => {
|
||||
expect(state.subscriptionMap[id]).toEqual('chain_newHead');
|
||||
expect(state.subscriptions['chain_newHead'].callbacks[id]).toEqual(cb);
|
||||
});
|
||||
});
|
||||
|
||||
it('calls back with the last known value', (done) => {
|
||||
state.subscriptions['chain_newHead'].lastValue = 'testValue';
|
||||
|
||||
subscribe(state, 'chain_newHead', 'chain_newHead', [(_, value) => {
|
||||
expect(value).toEqual('testValue');
|
||||
done();
|
||||
}]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { MockState, MockState$Subscription$Callback } from './types';
|
||||
|
||||
export default async function subscribe (self: MockState, type: string, method: string, params: Array<any>): Promise<number> {
|
||||
self.l.debug(() => ['subscribe', method, params]);
|
||||
|
||||
if (self.subscriptions[method]) {
|
||||
const callback: MockState$Subscription$Callback = params.pop();
|
||||
const id = ++self.subscriptionId;
|
||||
|
||||
self.subscriptions[method].callbacks[id] = callback;
|
||||
self.subscriptionMap[id] = method;
|
||||
|
||||
callback(null, self.subscriptions[method].lastValue);
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
throw new Error(`provider.subscribe: Invalid method '${method}'`);
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import EventEmitter from 'eventemitter3';
|
||||
import { Logger } from '@polkadot/util/types';
|
||||
|
||||
export type MockState$Subscription$Callback = (error: Error | null, value: any) => void;
|
||||
|
||||
export type MockState$Subscriptions = {
|
||||
[index: string]: {
|
||||
callbacks: {
|
||||
[index: number]: MockState$Subscription$Callback
|
||||
},
|
||||
lastValue: any
|
||||
}
|
||||
};
|
||||
|
||||
export type MockState$Db = {
|
||||
[index: string]: Uint8Array
|
||||
};
|
||||
|
||||
export type MockState$Requests = {
|
||||
[index: string]: (db: MockState$Db, params: Array<any>) => string
|
||||
};
|
||||
|
||||
export type MockState = {
|
||||
emitter: EventEmitter,
|
||||
l: Logger,
|
||||
requests: MockState$Requests,
|
||||
db: MockState$Db,
|
||||
subscriptionId: number,
|
||||
subscriptionMap: {
|
||||
[index: number]: string
|
||||
},
|
||||
subscriptions: MockState$Subscriptions
|
||||
};
|
||||
@@ -0,0 +1,45 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import createState from './state';
|
||||
import subscribe from './subscribe';
|
||||
import unsubscribe from './unsubscribe';
|
||||
|
||||
describe('unsubscribe', () => {
|
||||
let state;
|
||||
let id;
|
||||
|
||||
beforeEach(() => {
|
||||
state = createState();
|
||||
|
||||
return subscribe(state, 'chain_newHead', 'chain_newHead', [() => void 0]).then((_id) => {
|
||||
id = _id;
|
||||
});
|
||||
});
|
||||
|
||||
it('fails on unknown ids', () => {
|
||||
return unsubscribe(state, 'chain_newHead', 'chain_newHead', 5).catch((error) => {
|
||||
expect(error.message).toMatch(/Unable to find/);
|
||||
});
|
||||
});
|
||||
|
||||
it('unsubscribes successfully', () => {
|
||||
return unsubscribe(state, 'chain_newHead', 'chain_newHead', id);
|
||||
});
|
||||
|
||||
it('fails on double unsubscribe', () => {
|
||||
return unsubscribe(state, 'chain_newHead', 'chain_newHead', id)
|
||||
.then(() => unsubscribe(state, id))
|
||||
.catch((error) => {
|
||||
expect(error.message).toMatch(/Unable to find/);
|
||||
});
|
||||
});
|
||||
|
||||
it('clears the subscriptions', () => {
|
||||
return unsubscribe(state, 'chain_newHead', 'chain_newHead', id).then(() => {
|
||||
expect(state.subscriptionMap[id]).not.toBeDefined();
|
||||
expect(state.subscriptions['chain_newHead'].callbacks[id]).not.toBeDefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { MockState } from './types';
|
||||
|
||||
export default async function unsubscribe (self: MockState, type: string, name: string, id: number): Promise<boolean> {
|
||||
const method = self.subscriptionMap[id];
|
||||
|
||||
self.l.debug(() => ['unsubscribe', id, method]);
|
||||
|
||||
if (!method) {
|
||||
throw new Error(`Unable to find subscription for ${id}`);
|
||||
}
|
||||
|
||||
delete self.subscriptionMap[id];
|
||||
delete self.subscriptions[method].callbacks[id];
|
||||
|
||||
return true;
|
||||
}
|
||||
Vendored
+50
@@ -0,0 +1,50 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
export type JsonRpcObject = {
|
||||
id: number;
|
||||
jsonrpc: '2.0';
|
||||
};
|
||||
|
||||
export type JsonRpcRequest = JsonRpcObject & {
|
||||
method: string;
|
||||
params: Array<any>;
|
||||
};
|
||||
|
||||
export type JsonRpcResponseBase$Error = {
|
||||
code: number,
|
||||
message: string
|
||||
};
|
||||
|
||||
type JsonRpcResponse$Single = {
|
||||
error?: JsonRpcResponseBase$Error;
|
||||
result?: any;
|
||||
};
|
||||
|
||||
type JsonRpcResponse$Subscription = {
|
||||
method?: string;
|
||||
params: {
|
||||
error?: JsonRpcResponseBase$Error;
|
||||
result: any;
|
||||
subscription: number;
|
||||
}
|
||||
};
|
||||
|
||||
export type JsonRpcResponseBase = JsonRpcResponse$Single & JsonRpcResponse$Subscription;
|
||||
|
||||
export type JsonRpcResponse = JsonRpcObject & JsonRpcResponseBase;
|
||||
|
||||
export type ProviderInterface$Callback = (error: Error | null, result: any) => void;
|
||||
|
||||
export type ProviderInterface$Emitted = 'connected' | 'disconnected';
|
||||
|
||||
export type ProviderInterface$EmitCb = (value?: any) => any;
|
||||
|
||||
export interface ProviderInterface {
|
||||
isConnected (): boolean,
|
||||
on (type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): void,
|
||||
send (method: string, params: Array<any>): Promise<any>,
|
||||
subscribe (type: string, method: string, params: Array<any>, cb: ProviderInterface$Callback): Promise<number>,
|
||||
unsubscribe (type: string, method: string, id: number): Promise<boolean>
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockWs, TEST_WS_URL } from '../../test/mockWs';
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
describe('onConnect', () => {
|
||||
let mock;
|
||||
|
||||
beforeEach(() => {
|
||||
mock = mockWs([]);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('sets up the on* handlers', () => {
|
||||
const ws = new Ws(TEST_WS_URL, false);
|
||||
|
||||
ws.connect();
|
||||
|
||||
expect(ws.websocket.onclose[0]).toBeDefined();
|
||||
expect(ws.websocket.onerror[0]).toBeDefined();
|
||||
expect(ws.websocket.onmessage[0]).toBeDefined();
|
||||
expect(ws.websocket.onopen[0]).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockWs, TEST_WS_URL } from '../../test/mockWs';
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
let ws;
|
||||
let mock;
|
||||
|
||||
function createWs (requests, autoConnect) {
|
||||
mock = mockWs(requests);
|
||||
ws = new Ws(TEST_WS_URL, autoConnect);
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
describe('Ws', () => {
|
||||
afterEach(() => {
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('returns the connected state', () => {
|
||||
expect(
|
||||
createWs([]).isConnected()
|
||||
).toEqual(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,316 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Logger } from '@polkadot/util/types';
|
||||
import { RpcCoder } from '../coder/json/types';
|
||||
import { JsonRpcResponse, ProviderInterface, ProviderInterface$Callback, ProviderInterface$Emitted, ProviderInterface$EmitCb } from '../types';
|
||||
|
||||
import './polyfill';
|
||||
|
||||
import E3 from 'eventemitter3';
|
||||
import assert from '@polkadot/util/assert';
|
||||
import isNull from '@polkadot/util/is/null';
|
||||
import isUndefined from '@polkadot/util/is/undefined';
|
||||
import logger from '@polkadot/util/logger';
|
||||
|
||||
import coder from '../coder/json';
|
||||
|
||||
type SubscriptionHandler = {
|
||||
callback: ProviderInterface$Callback,
|
||||
type: string
|
||||
};
|
||||
|
||||
type WsState$Awaiting = {
|
||||
callback: ProviderInterface$Callback,
|
||||
method: string,
|
||||
params: Array<any>,
|
||||
subscription?: SubscriptionHandler
|
||||
};
|
||||
|
||||
type WsState$Subscription = SubscriptionHandler & {
|
||||
method: string,
|
||||
params: Array<any>
|
||||
};
|
||||
|
||||
interface WSProviderInterface extends ProviderInterface {
|
||||
connect (): void;
|
||||
}
|
||||
|
||||
/**
|
||||
* @name WsProvider
|
||||
* @summary The WebSocket Provider allows sending requests using WebSocket to a WebSocket RPC server TCP port.
|
||||
* @description Unlike the [[HttpProvider]], it does support subscriptions and allows
|
||||
* listening to events such as new blocks or balance changes.
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* import Api from '@polkadot/rpc-core';
|
||||
* import WsProvider from '@polkadot/rpc-provider/ws';
|
||||
*
|
||||
* const provider = new WsProvider('ws://127.0.0.1:9944');
|
||||
* const api = new Api(provider);
|
||||
* ```
|
||||
*
|
||||
* @see [[HttpProvider]]
|
||||
*/
|
||||
export default class WsProvider extends E3.EventEmitter implements WSProviderInterface {
|
||||
private autoConnect: boolean;
|
||||
private coder: RpcCoder;
|
||||
private endpoint: string;
|
||||
private handlers: {
|
||||
[index: number]: WsState$Awaiting
|
||||
};
|
||||
private _isConnected: boolean;
|
||||
private l: Logger;
|
||||
private queued: {
|
||||
[index: string]: string
|
||||
};
|
||||
private subscriptions: {
|
||||
[index: string]: WsState$Subscription
|
||||
};
|
||||
private websocket: WebSocket | null;
|
||||
|
||||
/**
|
||||
* @param {string} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944`
|
||||
* @param {boolean} autoConnect Whether to connect automatically or not.
|
||||
*/
|
||||
constructor (endpoint: string, autoConnect: boolean = true) {
|
||||
super();
|
||||
|
||||
assert(/^(wss|ws):\/\//.test(endpoint), `Endpoint should start with 'ws://', received '${endpoint}'`);
|
||||
|
||||
this.autoConnect = autoConnect;
|
||||
this.coder = coder();
|
||||
this.endpoint = endpoint;
|
||||
this._isConnected = false;
|
||||
this.handlers = {};
|
||||
this.l = logger('api-ws');
|
||||
this.queued = {};
|
||||
this.subscriptions = {};
|
||||
this.websocket = null;
|
||||
|
||||
if (autoConnect) {
|
||||
this.connect();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Manually connect
|
||||
* @description The [[WsProvider]] connects automatically by default, however if you decided otherwise, you may
|
||||
* connect manually using this method.
|
||||
*/
|
||||
connect (): void {
|
||||
try {
|
||||
this.websocket = new WebSocket(this.endpoint);
|
||||
|
||||
this.websocket.onclose = this.onSocketClose;
|
||||
this.websocket.onerror = this.onSocketError;
|
||||
this.websocket.onmessage = this.onSocketMessage;
|
||||
this.websocket.onopen = this.onSocketOpen;
|
||||
} catch (error) {
|
||||
this.l.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Whether the node is connected or not.
|
||||
* @return {boolean} true if connected
|
||||
*/
|
||||
isConnected (): boolean {
|
||||
return this._isConnected;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Listens on events after having subscribed using the [[subscribe]] function.
|
||||
* @param {ProviderInterface$Emitted} type Event
|
||||
* @param {ProviderInterface$EmitCb} sub Callback
|
||||
* @return {this} [description]
|
||||
*/
|
||||
on (type: ProviderInterface$Emitted, sub: ProviderInterface$EmitCb): this {
|
||||
return super.on(type, sub);
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Send JSON data using WebSockets to configured HTTP Endpoint or queue.
|
||||
*/
|
||||
async send (method: string, params: Array<any>, subscription?: SubscriptionHandler): Promise<any> {
|
||||
return new Promise((resolve, reject): void => {
|
||||
try {
|
||||
const json = this.coder.encodeJson(method, params);
|
||||
const id = this.coder.getId();
|
||||
const callback = (error: Error | null, result: any) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else {
|
||||
resolve(result);
|
||||
}
|
||||
};
|
||||
|
||||
this.l.debug(() => ['calling', method, params, json, !!subscription]);
|
||||
|
||||
this.handlers[id] = {
|
||||
callback,
|
||||
method,
|
||||
params,
|
||||
subscription
|
||||
};
|
||||
|
||||
if (this.isConnected() && !isNull(this.websocket)) {
|
||||
this.websocket.send(json);
|
||||
} else {
|
||||
this.queued[id] = json;
|
||||
}
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name subscribe
|
||||
* @summary Allows subscribing to a specific event.
|
||||
* @param {string} type Subscription type
|
||||
* @param {string} method Subscription method
|
||||
* @param {Array<any>} params Parameters
|
||||
* @param {ProviderInterface$Callback} callback Callback
|
||||
* @return {Promise<number>} Promise resolving to the dd of the subscription you can use with [[unsubscribe]].
|
||||
*
|
||||
* @example
|
||||
* <BR>
|
||||
*
|
||||
* ```javascript
|
||||
* const provider = new WsProvider('ws://127.0.0.1:9944');
|
||||
* const api = new Api(provider);
|
||||
*
|
||||
* api.state.storage([[storage.balances.freeBalance, <Address>]], (_, values) => {
|
||||
* console.log(values)
|
||||
* }).then((subscriptionId) => {
|
||||
* console.log('balance changes subscription id: ', subscriptionId)
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
async subscribe (type: string, method: string, params: Array<any>, callback: ProviderInterface$Callback): Promise<number> {
|
||||
const id = await this.send(method, params, { callback, type });
|
||||
|
||||
return id as number;
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Allows unsubscribing to subscriptions made with [[subscribe]].
|
||||
*/
|
||||
async unsubscribe (type: string, method: string, id: number): Promise<boolean> {
|
||||
const subscription = `${type}::${id}`;
|
||||
|
||||
assert(!isUndefined(this.subscriptions[subscription]), `Unable to find active subscription=${subscription}`);
|
||||
|
||||
delete this.subscriptions[subscription];
|
||||
|
||||
const result = await this.send(method, [id]);
|
||||
|
||||
return result as boolean;
|
||||
}
|
||||
|
||||
private onSocketClose = (): void => {
|
||||
this.l.debug(() => ['disconnected from', this.endpoint]);
|
||||
|
||||
this._isConnected = false;
|
||||
this.emit('disconnected');
|
||||
|
||||
if (this.autoConnect) {
|
||||
setTimeout(() => {
|
||||
this.connect();
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketError = (error: Event): void => {
|
||||
this.l.error(error);
|
||||
}
|
||||
|
||||
private onSocketMessage = (message: MessageEvent): void => {
|
||||
this.l.debug(() => ['received', message.data]);
|
||||
|
||||
const response: JsonRpcResponse = JSON.parse(message.data as string);
|
||||
|
||||
return isUndefined(response.method)
|
||||
? this.onSocketMessageResult(response)
|
||||
: this.onSocketMessageSubscribe(response);
|
||||
}
|
||||
|
||||
private onSocketMessageResult = (response: JsonRpcResponse): void => {
|
||||
this.l.debug(() => ['handling: response =', response, 'id =', response.id]);
|
||||
|
||||
const handler = this.handlers[response.id];
|
||||
|
||||
if (!handler) {
|
||||
this.l.error(`Unable to find handler for id=${response.id}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { method, params, subscription } = handler;
|
||||
const result = this.coder.decodeResponse(response);
|
||||
|
||||
if (subscription) {
|
||||
this.subscriptions[`${subscription.type}::${result}`] = {
|
||||
...subscription,
|
||||
method,
|
||||
params
|
||||
};
|
||||
}
|
||||
|
||||
handler.callback(null, result);
|
||||
} catch (error) {
|
||||
handler.callback(error, undefined);
|
||||
}
|
||||
|
||||
delete this.handlers[response.id];
|
||||
}
|
||||
|
||||
private onSocketMessageSubscribe = (response: JsonRpcResponse): void => {
|
||||
const subscription = `${response.method}::${response.params.subscription}`;
|
||||
|
||||
this.l.debug(() => ['handling: response =', response, 'subscription =', subscription]);
|
||||
|
||||
const handler = this.subscriptions[subscription];
|
||||
|
||||
if (!handler) {
|
||||
this.l.error(`Unable to find handler for subscription=${subscription}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = this.coder.decodeResponse(response);
|
||||
|
||||
handler.callback(null, result);
|
||||
} catch (error) {
|
||||
handler.callback(error, undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private onSocketOpen = (): boolean => {
|
||||
assert(!isNull(this.websocket), 'WebSocket cannot be null in onOpen');
|
||||
|
||||
this.l.debug(() => ['connected to', this.endpoint]);
|
||||
|
||||
this._isConnected = true;
|
||||
this.emit('connected');
|
||||
|
||||
Object.keys(this.queued).forEach((id) => {
|
||||
try {
|
||||
// @ts-ignore checked above
|
||||
this.websocket.send(
|
||||
this.queued[id]
|
||||
);
|
||||
|
||||
delete this.queued[id];
|
||||
} catch (error) {
|
||||
this.l.error(error);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
describe('onError', () => {
|
||||
let ws;
|
||||
let errorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
ws = new Ws('ws://127.0.0.1:1234', false);
|
||||
errorSpy = jest.spyOn(console, 'error');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('logs the error', () => {
|
||||
ws.onSocketError('test error');
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
expect.anything(), expect.anything(), 'test error'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
describe('onMessageResult', () => {
|
||||
let ws;
|
||||
let errorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
ws = new Ws('ws://127.0.0.1:1234', false);
|
||||
errorSpy = jest.spyOn(console, 'error');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('fails with log when handler not found', () => {
|
||||
ws.onSocketMessage({ data: '{"id":2}' });
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
expect.anything(), expect.anything(), 'Unable to find handler for id=2'
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the handler when found', (done) => {
|
||||
ws.handlers[5] = {
|
||||
callback: (_, result) => {
|
||||
expect(result).toEqual('test');
|
||||
done();
|
||||
}
|
||||
};
|
||||
|
||||
ws.onSocketMessage({ data: '{"jsonrpc":"2.0","id":5,"result":"test"}' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
describe('onMessageSubscribe', () => {
|
||||
let ws;
|
||||
let errorSpy;
|
||||
|
||||
beforeEach(() => {
|
||||
ws = new Ws('ws://127.0.0.1:1234', false);
|
||||
errorSpy = jest.spyOn(console, 'error');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
errorSpy.mockRestore();
|
||||
});
|
||||
|
||||
it('fails with log when handler not found', () => {
|
||||
ws.onSocketMessage({ data: '{"method":"test","params":{"subscription":2}}' });
|
||||
|
||||
expect(errorSpy).toHaveBeenCalledWith(
|
||||
expect.anything(), expect.anything(), 'Unable to find handler for subscription=test::2'
|
||||
);
|
||||
});
|
||||
|
||||
it('calls the subscriber with data', (done) => {
|
||||
ws.handlers[11] = {
|
||||
callback: (_, id) => {},
|
||||
method: 'test',
|
||||
subscription: {
|
||||
callback: (_, result) => {
|
||||
expect(result).toEqual('test');
|
||||
done();
|
||||
},
|
||||
type: 'test'
|
||||
}
|
||||
};
|
||||
|
||||
ws.onSocketMessage({ data: '{"jsonrpc":"2.0","id":11,"result":22}' });
|
||||
ws.onSocketMessage({ data: '{"jsonrpc":"2.0","method":"test","params":{"subscription":22,"result":"test"}}' });
|
||||
});
|
||||
|
||||
it('calls the subscriber with error', (done) => {
|
||||
ws.handlers[11] = {
|
||||
callback: (_, id) => {},
|
||||
method: 'test',
|
||||
subscription: {
|
||||
callback: (error) => {
|
||||
expect(error.message).toMatch(/test/);
|
||||
done();
|
||||
},
|
||||
type: 'test'
|
||||
}
|
||||
};
|
||||
|
||||
ws.onSocketMessage({ data: '{"jsonrpc":"2.0","id":11,"result":22}' });
|
||||
ws.onSocketMessage({ data: '{"jsonrpc":"2.0","method":"test","params":{"subscription":22,"error":{"message":"test"}}}' });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockWs, TEST_WS_URL } from '../../test/mockWs';
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
let ws;
|
||||
let mock;
|
||||
|
||||
function createWs (requests, autoConnect) {
|
||||
mock = mockWs(requests);
|
||||
ws = new Ws(TEST_WS_URL, autoConnect);
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
describe('onOpen', () => {
|
||||
afterEach(() => {
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('sends messages when connected', () => {
|
||||
const ws = createWs([{
|
||||
id: 1,
|
||||
method: 'test_queue',
|
||||
reply: {
|
||||
result: 'ok'
|
||||
}
|
||||
}], false);
|
||||
const sendPromise = ws.send('test_queue', []);
|
||||
|
||||
ws.connect();
|
||||
|
||||
return sendPromise.then((result) => {
|
||||
expect(result).toEqual('ok');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,26 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
describe('ws/polyfill', () => {
|
||||
let origWs;
|
||||
|
||||
beforeEach(() => {
|
||||
origWs = global.WebSocket;
|
||||
global.WebSocket = null;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.WebSocket = origWs;
|
||||
});
|
||||
|
||||
it('polyfills with no exceptions (without WebSocket)', () => {
|
||||
expect(require('./polyfill')).toBeDefined();
|
||||
});
|
||||
|
||||
it('polyfills with no exceptions (with WebSocket)', () => {
|
||||
global.WebSocket = () => true;
|
||||
|
||||
expect(require('./polyfill')).toBeDefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
// tslint:disable-next-line
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
// @ts-ignore
|
||||
global.WebSocket = require('websocket').w3cwebsocket;
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockWs, TEST_WS_URL } from '../../test/mockWs';
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
let ws;
|
||||
let mock;
|
||||
|
||||
function createMock (requests) {
|
||||
mock = mockWs(requests);
|
||||
}
|
||||
|
||||
function createWs (autoConnect) {
|
||||
ws = new Ws(TEST_WS_URL, autoConnect);
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
describe('send', () => {
|
||||
let globalWs;
|
||||
|
||||
beforeEach(() => {
|
||||
globalWs = global.WebSocket;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.WebSocket = globalWs;
|
||||
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('handles internal errors', () => {
|
||||
createMock([]);
|
||||
|
||||
let websocket;
|
||||
|
||||
global.WebSocket = class {
|
||||
constructor () {
|
||||
websocket = this;
|
||||
}
|
||||
|
||||
send () {
|
||||
throw new Error('send error');
|
||||
}
|
||||
};
|
||||
|
||||
ws = createWs();
|
||||
websocket.onopen();
|
||||
|
||||
return ws
|
||||
.send('test_encoding', ['param'])
|
||||
.catch((error) => {
|
||||
expect(error.message).toEqual('send error');
|
||||
});
|
||||
});
|
||||
|
||||
it('passes the body through correctly', () => {
|
||||
createMock([{
|
||||
id: 1,
|
||||
method: 'test_body',
|
||||
reply: {
|
||||
result: 'ok'
|
||||
}
|
||||
}]);
|
||||
|
||||
return createWs()
|
||||
.send('test_body', ['param'])
|
||||
.then((result) => {
|
||||
expect(
|
||||
mock.body['test_body']
|
||||
).toEqual('{"id":1,"jsonrpc":"2.0","method":"test_body","params":["param"]}');
|
||||
});
|
||||
});
|
||||
|
||||
it('throws error when !response.ok', () => {
|
||||
createMock([{
|
||||
id: 1,
|
||||
error: {
|
||||
code: 666,
|
||||
message: 'error'
|
||||
}
|
||||
}]);
|
||||
|
||||
return createWs()
|
||||
.send('test_error', [])
|
||||
.catch((error) => {
|
||||
expect(error.message).toMatch(/\[666\]: error/);
|
||||
});
|
||||
});
|
||||
|
||||
it('adds subscriptions', () => {
|
||||
createMock([{
|
||||
id: 1,
|
||||
method: 'test_sub',
|
||||
reply: {
|
||||
result: 1
|
||||
}
|
||||
}]);
|
||||
|
||||
return createWs()
|
||||
.send('test_sub', [], () => {})
|
||||
.then((id) => {
|
||||
expect(id).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
describe('state', () => {
|
||||
it('requires an ws:// prefixed endpoint', () => {
|
||||
expect(
|
||||
() => new Ws('http://', false)
|
||||
).toThrow(/with 'ws/);
|
||||
});
|
||||
|
||||
it('allows wss:// endpoints', () => {
|
||||
expect(
|
||||
() => new Ws('wss://', false)
|
||||
).not.toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,53 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockWs, TEST_WS_URL } from '../../test/mockWs';
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
let ws;
|
||||
let mock;
|
||||
|
||||
function createMock (requests) {
|
||||
mock = mockWs(requests);
|
||||
}
|
||||
|
||||
function createWs (autoConnect) {
|
||||
ws = new Ws(TEST_WS_URL, autoConnect);
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
describe('subscribe', () => {
|
||||
let globalWs;
|
||||
|
||||
beforeEach(() => {
|
||||
globalWs = global.WebSocket;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.WebSocket = globalWs;
|
||||
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('adds subscriptions', () => {
|
||||
createMock([{
|
||||
id: 1,
|
||||
method: 'test_sub',
|
||||
reply: {
|
||||
result: 1
|
||||
}
|
||||
}]);
|
||||
|
||||
return createWs()
|
||||
.subscribe('test_sub', [], () => {})
|
||||
.then((id) => {
|
||||
expect(id).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { mockWs, TEST_WS_URL } from '../../test/mockWs';
|
||||
|
||||
import Ws from './index';
|
||||
|
||||
let ws;
|
||||
let mock;
|
||||
|
||||
function createMock (requests) {
|
||||
mock = mockWs(requests);
|
||||
}
|
||||
|
||||
function createWs (autoConnect) {
|
||||
ws = new Ws(TEST_WS_URL, autoConnect);
|
||||
|
||||
return ws;
|
||||
}
|
||||
|
||||
describe('subscribe', () => {
|
||||
let globalWs;
|
||||
|
||||
beforeEach(() => {
|
||||
globalWs = global.WebSocket;
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.WebSocket = globalWs;
|
||||
|
||||
if (mock) {
|
||||
mock.done();
|
||||
mock = null;
|
||||
}
|
||||
});
|
||||
|
||||
it('removes subscriptions', () => {
|
||||
createMock([
|
||||
{
|
||||
id: 1,
|
||||
method: 'subscribe_test',
|
||||
reply: {
|
||||
result: 1
|
||||
}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
method: 'unsubscribe_test',
|
||||
reply: {
|
||||
result: true
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
const ws = createWs();
|
||||
|
||||
return ws
|
||||
.subscribe('test', 'subscribe_test', [], () => {})
|
||||
.then((id) => {
|
||||
return ws.unsubscribe('test', 'subscribe_test', id);
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when sub not found', () => {
|
||||
createMock([{
|
||||
id: 1,
|
||||
method: 'subscribe_test',
|
||||
reply: {
|
||||
result: 1
|
||||
}
|
||||
}]);
|
||||
|
||||
const ws = createWs();
|
||||
|
||||
return ws
|
||||
.subscribe('test', 'subscribe_test', [], () => {})
|
||||
.then((id) => {
|
||||
return ws.unsubscribe('test', 'subscribe_test', 111);
|
||||
})
|
||||
.catch((error) => {
|
||||
expect(error.message).toMatch(/find active subscription/);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import nock from 'nock';
|
||||
|
||||
const TEST_HTTP_URL = 'http://localhost:9944';
|
||||
|
||||
function mockHttp (requests: any[]) {
|
||||
nock.cleanAll();
|
||||
|
||||
return requests.reduce((scope, request: any, index: number) => {
|
||||
return scope
|
||||
.post('/')
|
||||
.reply(request.code || 200, (uri: string, body: { id: string }) => {
|
||||
scope.body = scope.body || {};
|
||||
scope.body[request.method] = body;
|
||||
|
||||
return Object.assign({ id: body.id, jsonrpc: '2.0' }, request.reply || {});
|
||||
});
|
||||
}, nock(TEST_HTTP_URL));
|
||||
}
|
||||
|
||||
export {
|
||||
TEST_HTTP_URL,
|
||||
mockHttp
|
||||
};
|
||||
@@ -0,0 +1,83 @@
|
||||
// Copyright 2017-2018 @polkadot/rpc-provider authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the ISC license. See the LICENSE file for details.
|
||||
|
||||
import { Server } from 'mock-socket';
|
||||
|
||||
const TEST_WS_URL = 'ws://localhost:9955';
|
||||
|
||||
let server: Server;
|
||||
|
||||
type ErrorDef = {
|
||||
id: number,
|
||||
error: {
|
||||
code: number,
|
||||
message: string
|
||||
}
|
||||
};
|
||||
|
||||
type ReplyDef = {
|
||||
id: number,
|
||||
reply: {
|
||||
result: any
|
||||
}
|
||||
};
|
||||
|
||||
function createError ({ id, error: { code, message } }: ErrorDef) {
|
||||
return {
|
||||
id,
|
||||
jsonrpc: '2.0',
|
||||
error: {
|
||||
code,
|
||||
message
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function createReply ({ id, reply: { result } }: ReplyDef) {
|
||||
return {
|
||||
id,
|
||||
jsonrpc: '2.0',
|
||||
result
|
||||
};
|
||||
}
|
||||
|
||||
function mockWs (requests: Array<{ method: string }>) {
|
||||
server = new Server(TEST_WS_URL);
|
||||
|
||||
let requestCount = 0;
|
||||
const scope: { body: { [index: string]: {} }, requests: number, server: Server, done: any } = {
|
||||
body: {},
|
||||
requests: 0,
|
||||
server,
|
||||
done: () =>
|
||||
server.stop(() => {
|
||||
// ignore
|
||||
})
|
||||
};
|
||||
|
||||
server.on('connection', (socket) => {
|
||||
// @ts-ignore definitions are wrong, this is 'on', not 'onmessage'
|
||||
socket.on('message', (body: {}) => {
|
||||
const request = requests[requestCount];
|
||||
// @ts-ignore Yes, SHOULD be fixed, this is a mess
|
||||
const response = request.error
|
||||
// @ts-ignore Yes, SHOULD be fixed, this is a mess
|
||||
? createError(request)
|
||||
// @ts-ignore Yes, SHOULD be fixed, this is a mess
|
||||
: createReply(request);
|
||||
|
||||
scope.body[request.method] = body;
|
||||
requestCount++;
|
||||
|
||||
socket.send(JSON.stringify(response));
|
||||
});
|
||||
});
|
||||
|
||||
return scope;
|
||||
}
|
||||
|
||||
export {
|
||||
TEST_WS_URL,
|
||||
mockWs
|
||||
};
|
||||
Reference in New Issue
Block a user