@dashevo/dapi-client 0.24.0-dev.6 → 0.25.0-dev.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -8
- package/lib/BlockHeadersProvider/BlockHeadersProvider.js +187 -50
- package/lib/BlockHeadersProvider/BlockHeadersReader.js +170 -88
- package/lib/BlockHeadersProvider/createBlockHeadersProviderFromOptions.js +31 -16
- package/lib/DAPIClient.js +0 -6
- package/lib/dapiAddressProvider/DAPIAddress.js +14 -40
- package/lib/dapiAddressProvider/ListDAPIAddressProvider.js +3 -4
- package/lib/dapiAddressProvider/SimplifiedMasternodeListDAPIAddressProvider.js +8 -1
- package/lib/index.js +5 -0
- package/lib/methods/platform/response/Metadata.js +21 -1
- package/lib/methods/platform/response/Proof.js +16 -5
- package/lib/test/bootstrap.js +8 -0
- package/lib/test/fixtures/getMNListDiffsFixture.js +85 -1066
- package/lib/test/fixtures/getMetadataFixture.js +9 -1
- package/lib/test/fixtures/getProofFixture.js +3 -2
- package/lib/test/mocks/BlockHeadersWithChainLocksStreamMock.js +54 -0
- package/lib/test/mocks/mockHeadersChain.js +55 -0
- package/lib/transport/GrpcTransport/GrpcTransport.js +5 -10
- package/lib/transport/GrpcTransport/createGrpcTransportError.js +11 -14
- package/lib/transport/JsonRpcTransport/JsonRpcTransport.js +1 -1
- package/lib/transport/JsonRpcTransport/requestJsonRpc.js +22 -25
- package/lib/transport/ReconnectableStream.js +284 -0
- package/package.json +15 -12
- package/polyfills/fetch-polyfill.js +10 -0
- /package/lib/{test/utils → utils}/wait.js +0 -0
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @returns {{
|
|
2
|
+
* @returns {{
|
|
3
|
+
* coreChainLockedHeight: number,
|
|
4
|
+
* height: number,
|
|
5
|
+
* signature: Buffer,
|
|
6
|
+
* protocolVersion: number,
|
|
7
|
+
* timeMs: number,
|
|
8
|
+
* }}
|
|
3
9
|
*/
|
|
4
10
|
function getMetadataFixture() {
|
|
5
11
|
return {
|
|
6
12
|
height: 10,
|
|
7
13
|
coreChainLockedHeight: 42,
|
|
14
|
+
timeMs: new Date().getTime(),
|
|
15
|
+
protocolVersion: 1,
|
|
8
16
|
};
|
|
9
17
|
}
|
|
10
18
|
|
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
* @returns {{
|
|
3
3
|
* merkleProof: Buffer,
|
|
4
4
|
* signature: Buffer,
|
|
5
|
-
*
|
|
5
|
+
* quorumHash: Buffer
|
|
6
6
|
* }}
|
|
7
7
|
*/
|
|
8
8
|
function getProofFixture() {
|
|
9
9
|
return {
|
|
10
|
-
|
|
10
|
+
quorumHash: Buffer.from('AQEBAQEBAQEBAQEB', 'base64'),
|
|
11
11
|
signature: Buffer.from('AgICAgICAgICAgIC', 'base64'),
|
|
12
12
|
merkleProof: Buffer.from('0100000001f0faf5f55674905a68eba1be2f946e667c1cb5010101', 'hex'),
|
|
13
|
+
round: 42,
|
|
13
14
|
};
|
|
14
15
|
}
|
|
15
16
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
|
|
3
|
+
class BlockHeadersWithChainLocksStreamMock extends EventEmitter {
|
|
4
|
+
constructor(sinon) {
|
|
5
|
+
super();
|
|
6
|
+
|
|
7
|
+
sinon.spy(this, 'on');
|
|
8
|
+
sinon.spy(this, 'removeListener');
|
|
9
|
+
sinon.spy(this, 'emit');
|
|
10
|
+
sinon.spy(this, 'removeAllListeners');
|
|
11
|
+
sinon.spy(this, 'cancel');
|
|
12
|
+
|
|
13
|
+
this.errored = false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
cancel() {
|
|
17
|
+
if (!this.errored) {
|
|
18
|
+
const err = new Error('CANCELED_ON_CLIENT');
|
|
19
|
+
err.code = 1;
|
|
20
|
+
this.emit('error', err);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
emit(event, data) {
|
|
25
|
+
if (event === 'error') {
|
|
26
|
+
this.errored = true;
|
|
27
|
+
}
|
|
28
|
+
super.emit(event, data);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param headers {BlockHeader[]}
|
|
33
|
+
*/
|
|
34
|
+
sendHeaders(headers) {
|
|
35
|
+
this.emit('data', {
|
|
36
|
+
getBlockHeaders: () => ({
|
|
37
|
+
getHeadersList() {
|
|
38
|
+
return headers.map((header) => header.toBuffer());
|
|
39
|
+
},
|
|
40
|
+
}),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
errorHandler(e) {
|
|
45
|
+
this.emit('error', e);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
end() {
|
|
49
|
+
this.emit('end');
|
|
50
|
+
this.removeAllListeners();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = BlockHeadersWithChainLocksStreamMock;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
const X11 = require('wasm-x11-hash');
|
|
2
|
+
const { BlockHeader, configure } = require('@dashevo/dashcore-lib');
|
|
3
|
+
const { genesis } = require('@dashevo/dash-spv');
|
|
4
|
+
|
|
5
|
+
const getRoot = (network) => {
|
|
6
|
+
switch (network) {
|
|
7
|
+
case 'testnet':
|
|
8
|
+
return genesis.getTestnetGenesis();
|
|
9
|
+
case 'devnet':
|
|
10
|
+
return genesis.getDevnetGenesis();
|
|
11
|
+
case 'regtest':
|
|
12
|
+
return genesis.getRegtestGenesis();
|
|
13
|
+
default:
|
|
14
|
+
break;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return null;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const BLOCK_TIME = 2.5 * 60;
|
|
21
|
+
|
|
22
|
+
let x11;
|
|
23
|
+
|
|
24
|
+
const mockHeadersChain = async (network, length, root) => {
|
|
25
|
+
if (!x11) {
|
|
26
|
+
x11 = await X11();
|
|
27
|
+
// Configure Dashcore lib to operate with wasm x11
|
|
28
|
+
configure({
|
|
29
|
+
x11hash: x11,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const rootHeader = root || getRoot(network);
|
|
34
|
+
|
|
35
|
+
const chain = [rootHeader];
|
|
36
|
+
|
|
37
|
+
let prevHeader = rootHeader;
|
|
38
|
+
for (let i = 0; i < length - 1; i += 1) {
|
|
39
|
+
const header = new BlockHeader({
|
|
40
|
+
version: prevHeader.version,
|
|
41
|
+
prevHash: Buffer.from(prevHeader.hash, 'hex').reverse(),
|
|
42
|
+
merkleRoot: Buffer.alloc(32),
|
|
43
|
+
time: prevHeader.time + BLOCK_TIME,
|
|
44
|
+
bits: prevHeader.bits,
|
|
45
|
+
nonce: 3861367235,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
chain.push(header);
|
|
49
|
+
prevHeader = header;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return chain;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
module.exports = mockHeadersChain;
|
|
@@ -83,7 +83,7 @@ class GrpcTransport {
|
|
|
83
83
|
throw error;
|
|
84
84
|
}
|
|
85
85
|
|
|
86
|
-
const responseError = this.createGrpcTransportError(error, address);
|
|
86
|
+
const responseError = await this.createGrpcTransportError(error, address);
|
|
87
87
|
|
|
88
88
|
if (!(responseError instanceof RetriableResponseError)) {
|
|
89
89
|
throw responseError;
|
|
@@ -133,20 +133,15 @@ class GrpcTransport {
|
|
|
133
133
|
*/
|
|
134
134
|
makeGrpcUrlFromAddress(address) {
|
|
135
135
|
let protocol = address.getProtocol();
|
|
136
|
-
let port = address.getHttpPort();
|
|
137
|
-
|
|
138
136
|
// For NodeJS Client
|
|
139
137
|
if (typeof process !== 'undefined'
|
|
140
138
|
&& process.versions != null
|
|
141
|
-
&& process.versions.node != null
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
if (address.isSelfSignedCertificateAllowed()) {
|
|
145
|
-
protocol = 'http';
|
|
146
|
-
}
|
|
139
|
+
&& process.versions.node != null
|
|
140
|
+
&& address.isSelfSignedCertificateAllowed()) {
|
|
141
|
+
protocol = 'http';
|
|
147
142
|
}
|
|
148
143
|
|
|
149
|
-
return `${protocol}://${address.getHost()}:${
|
|
144
|
+
return `${protocol}://${address.getHost()}:${address.getPort()}`;
|
|
150
145
|
}
|
|
151
146
|
}
|
|
152
147
|
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const cbor = require('cbor');
|
|
2
2
|
|
|
3
|
-
const createConsensusError = require('@dashevo/dpp/lib/errors/consensus/createConsensusError');
|
|
4
3
|
const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes');
|
|
5
|
-
const {
|
|
4
|
+
const { parseMetadata } = require('@dashevo/dapi-grpc');
|
|
6
5
|
|
|
6
|
+
const {
|
|
7
|
+
deserializeConsensusError,
|
|
8
|
+
} = require('@dashevo/wasm-dpp');
|
|
9
|
+
const { default: loadWasmDpp } = require('@dashevo/wasm-dpp');
|
|
7
10
|
const NotFoundError = require('./errors/NotFoundError');
|
|
8
11
|
const TimeoutError = require('./errors/TimeoutError');
|
|
9
12
|
const ResponseError = require('../errors/response/ResponseError');
|
|
@@ -43,22 +46,16 @@ const errorClasses = {
|
|
|
43
46
|
* @param {DAPIAddress} dapiAddress
|
|
44
47
|
* @returns {ResponseError}
|
|
45
48
|
*/
|
|
46
|
-
function createGrpcTransportError(grpcError, dapiAddress) {
|
|
49
|
+
async function createGrpcTransportError(grpcError, dapiAddress) {
|
|
50
|
+
await loadWasmDpp();
|
|
51
|
+
|
|
47
52
|
// Extract error code and data
|
|
48
53
|
let data = {};
|
|
49
54
|
let { code } = grpcError;
|
|
50
55
|
|
|
51
56
|
const message = grpcError.details || grpcError.message;
|
|
52
57
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
if (grpcError.metadata) {
|
|
56
|
-
// In cases of gRPC-Web client we get plain map instead of Metadata instance
|
|
57
|
-
metadata = grpcError.metadata;
|
|
58
|
-
if (grpcError.metadata instanceof Metadata) {
|
|
59
|
-
metadata = grpcError.metadata.getMap();
|
|
60
|
-
}
|
|
61
|
-
}
|
|
58
|
+
const metadata = parseMetadata(grpcError.metadata) || {};
|
|
62
59
|
|
|
63
60
|
// Error data
|
|
64
61
|
const driveErrorData = metadata['drive-error-data-bin'];
|
|
@@ -122,9 +119,9 @@ function createGrpcTransportError(grpcError, dapiAddress) {
|
|
|
122
119
|
|
|
123
120
|
// DPP consensus errors
|
|
124
121
|
if (code >= 1000 && code < 5000) {
|
|
125
|
-
const consensusError =
|
|
122
|
+
const consensusError = deserializeConsensusError(data.serializedError || []);
|
|
126
123
|
|
|
127
|
-
delete data.
|
|
124
|
+
delete data.serializedError;
|
|
128
125
|
|
|
129
126
|
return new InvalidRequestDPPError(consensusError, data, dapiAddress);
|
|
130
127
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
const axios = require('axios');
|
|
2
1
|
const https = require('https');
|
|
3
|
-
|
|
4
2
|
const JsonRpcError = require('./errors/JsonRpcError');
|
|
5
3
|
const WrongHttpCodeError = require('./errors/WrongHttpCodeError');
|
|
6
4
|
|
|
@@ -25,11 +23,6 @@ async function requestJsonRpc(protocol, host, port, selfSigned, method, params,
|
|
|
25
23
|
id: 1,
|
|
26
24
|
};
|
|
27
25
|
|
|
28
|
-
const postOptions = {};
|
|
29
|
-
if (options.timeout !== undefined) {
|
|
30
|
-
postOptions.timeout = options.timeout;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
26
|
const requestInfo = {
|
|
34
27
|
protocol,
|
|
35
28
|
host,
|
|
@@ -40,40 +33,44 @@ async function requestJsonRpc(protocol, host, port, selfSigned, method, params,
|
|
|
40
33
|
options,
|
|
41
34
|
};
|
|
42
35
|
|
|
43
|
-
|
|
36
|
+
const requestOptions = {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
body: JSON.stringify(payload),
|
|
39
|
+
headers: {
|
|
40
|
+
'Content-Type': 'application/json',
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
let requestTimeoutId;
|
|
45
|
+
if (options.timeout) {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
requestTimeoutId = setTimeout(() => controller.abort(), options.timeout);
|
|
48
|
+
Object.assign(requestOptions, { signal: controller.signal });
|
|
49
|
+
}
|
|
44
50
|
|
|
45
|
-
const config = { timeout: options.timeout };
|
|
46
51
|
// For NodeJS Client
|
|
47
52
|
if (typeof process !== 'undefined'
|
|
48
53
|
&& process.versions != null
|
|
49
54
|
&& process.versions.node != null
|
|
50
55
|
&& protocol === 'https'
|
|
51
56
|
&& selfSigned) {
|
|
52
|
-
|
|
57
|
+
requestOptions.agent = new https.Agent({
|
|
53
58
|
rejectUnauthorized: false,
|
|
54
59
|
});
|
|
55
60
|
}
|
|
56
61
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
url,
|
|
60
|
-
payload,
|
|
61
|
-
config,
|
|
62
|
-
);
|
|
63
|
-
} catch (error) {
|
|
64
|
-
if (error.response && error.response.status >= 500) {
|
|
65
|
-
throw new WrongHttpCodeError(requestInfo, error.response.status, error.response.statusText);
|
|
66
|
-
}
|
|
62
|
+
// eslint-disable-next-line
|
|
63
|
+
const response = await fetch(url, requestOptions);
|
|
67
64
|
|
|
68
|
-
|
|
65
|
+
if (typeof requestTimeoutId !== 'undefined') {
|
|
66
|
+
clearTimeout(requestTimeoutId);
|
|
69
67
|
}
|
|
68
|
+
const data = await response.json();
|
|
70
69
|
|
|
71
|
-
if (response.
|
|
72
|
-
throw new WrongHttpCodeError(requestInfo, response.status, response.
|
|
70
|
+
if (!response.ok) {
|
|
71
|
+
throw new WrongHttpCodeError(requestInfo, response.status, response.statusText);
|
|
73
72
|
}
|
|
74
73
|
|
|
75
|
-
const { data } = response;
|
|
76
|
-
|
|
77
74
|
if (data.error) {
|
|
78
75
|
throw new JsonRpcError(requestInfo, data.error);
|
|
79
76
|
}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const EventEmitter = require('events');
|
|
2
|
+
const GrpcErrorCodes = require('@dashevo/grpc-common/lib/server/error/GrpcErrorCodes');
|
|
3
|
+
const wait = require('../utils/wait');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef ReconnectableStreamOptions
|
|
7
|
+
* @property {number} [autoReconnectInterval]
|
|
8
|
+
* interval in MS to perform auto reconnect
|
|
9
|
+
* @property {number} [maxRetriesOnError]
|
|
10
|
+
* maximum amount of retry attempts on error happens. If set to -1, retries are infinite
|
|
11
|
+
* @property {number} [retryOnErrorDelay]
|
|
12
|
+
* delay in MS to perform retry after an error
|
|
13
|
+
*/
|
|
14
|
+
const defaultOptions = {
|
|
15
|
+
// TODO: manage timeout according to the Nginx setting of the node
|
|
16
|
+
autoReconnectInterval: 50000,
|
|
17
|
+
maxRetriesOnError: 10,
|
|
18
|
+
retryOnErrorDelay: 1000,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const EVENTS = {
|
|
22
|
+
DATA: 'data',
|
|
23
|
+
ERROR: 'error',
|
|
24
|
+
END: 'end',
|
|
25
|
+
BEFORE_RECONNECT: 'beforeReconnect',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* A wrapper around grpc-js/grpc-web streams that provides auto-reconnect
|
|
30
|
+
* and retry on error functionality
|
|
31
|
+
* - Auto reconnect is needed to not hang on one node for too long.
|
|
32
|
+
* It happens within provided interval
|
|
33
|
+
* - Retry on error logic performs retry attempts until it reaches it's limit.
|
|
34
|
+
* After reaching limit, ReconnectableStream emits an error.
|
|
35
|
+
*/
|
|
36
|
+
class ReconnectableStream extends EventEmitter {
|
|
37
|
+
/**
|
|
38
|
+
* Helper that wraps stream creation function and performs auto connection
|
|
39
|
+
*
|
|
40
|
+
* @param {Function} createStreamFunction - function returning grpc-js/grpc-web stream
|
|
41
|
+
* @param {ReconnectableStreamOptions} options
|
|
42
|
+
* @returns {function(...[*]): Promise<ReconnectableStream>}
|
|
43
|
+
*/
|
|
44
|
+
static create(createStreamFunction, options) {
|
|
45
|
+
return async (...args) => {
|
|
46
|
+
const reconnectableStream = new ReconnectableStream(createStreamFunction, options);
|
|
47
|
+
await reconnectableStream.connect(...args);
|
|
48
|
+
return reconnectableStream;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* @param {Function} createStream - function returning grpc-js/grpc-web stream
|
|
54
|
+
* @param {ReconnectableStreamOptions} options
|
|
55
|
+
*/
|
|
56
|
+
constructor(createStream, options = {}) {
|
|
57
|
+
super();
|
|
58
|
+
|
|
59
|
+
const opts = { ...defaultOptions, ...options };
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Auto-reconnect interval in millisecond
|
|
63
|
+
* It is needed to automatically reconnect to another DAPI node
|
|
64
|
+
*
|
|
65
|
+
* @type {number}
|
|
66
|
+
*/
|
|
67
|
+
this.autoReconnectInterval = opts.autoReconnectInterval;
|
|
68
|
+
this.retryOnErrorDelay = opts.retryOnErrorDelay;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Max amount of retries on error
|
|
72
|
+
*/
|
|
73
|
+
this.maxRetriesOnError = opts.maxRetriesOnError;
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Current amount of retries on error
|
|
77
|
+
* (Does not have effect if maxRetriesOnError === -1)
|
|
78
|
+
*/
|
|
79
|
+
this.retriesOnError = 0;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* createStream arguments
|
|
83
|
+
*/
|
|
84
|
+
this.args = null;
|
|
85
|
+
/**
|
|
86
|
+
* grpc stream
|
|
87
|
+
*/
|
|
88
|
+
this.stream = null;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Function to wrap around to handle interval reconnects and error retries
|
|
92
|
+
*/
|
|
93
|
+
this.createStream = createStream;
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Reference to setTimeout managed by autoReconnectInterval
|
|
97
|
+
*/
|
|
98
|
+
this.reconnectTimeout = null;
|
|
99
|
+
|
|
100
|
+
// For mocks
|
|
101
|
+
this.setTimeout = (callback, delay) => setTimeout(callback, delay);
|
|
102
|
+
this.clearTimeout = (reference) => clearTimeout(reference);
|
|
103
|
+
|
|
104
|
+
this.connect = this.connect.bind(this);
|
|
105
|
+
this.reconnect = this.reconnect.bind(this);
|
|
106
|
+
this.cancel = this.cancel.bind(this);
|
|
107
|
+
this.errorHandler = this.errorHandler.bind(this);
|
|
108
|
+
this.dataHandler = this.dataHandler.bind(this);
|
|
109
|
+
this.endHandler = this.endHandler.bind(this);
|
|
110
|
+
this.addListeners = this.addListeners.bind(this);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async connect(...args) {
|
|
114
|
+
// Memorize current stream args (which can be altered by beforeReconnect logic)
|
|
115
|
+
this.args = args;
|
|
116
|
+
|
|
117
|
+
// Create grpc stream
|
|
118
|
+
this.stream = await this.createStream(...this.args);
|
|
119
|
+
|
|
120
|
+
// Add event listeners
|
|
121
|
+
this.addListeners();
|
|
122
|
+
|
|
123
|
+
if (this.autoReconnectInterval > 0) {
|
|
124
|
+
this.startAutoReconnect();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
startAutoReconnect() {
|
|
129
|
+
if (this.reconnectTimeout) {
|
|
130
|
+
throw new Error('Auto reconnect timeout is already running.');
|
|
131
|
+
}
|
|
132
|
+
this.reconnectTimeout = this.setTimeout(
|
|
133
|
+
this.reconnect,
|
|
134
|
+
this.autoReconnectInterval,
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
reconnect() {
|
|
139
|
+
if (this.reconnectTimeout) {
|
|
140
|
+
this.reconnectTimeout = null;
|
|
141
|
+
this.stream.cancel();
|
|
142
|
+
|
|
143
|
+
let newArgs = this.args || [];
|
|
144
|
+
const updateArgs = (...args) => {
|
|
145
|
+
newArgs = args;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
this.emit(EVENTS.BEFORE_RECONNECT, updateArgs);
|
|
149
|
+
this.connect(...newArgs)
|
|
150
|
+
.catch((connectError) => this.emit(EVENTS.ERROR, connectError));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Function that adds EventEmitter style listeners
|
|
156
|
+
* to the stream and also rewires stream cancel function
|
|
157
|
+
* in order to automatically unsubscribe from events
|
|
158
|
+
*
|
|
159
|
+
* @private
|
|
160
|
+
*/
|
|
161
|
+
addListeners() {
|
|
162
|
+
this.stream.on(EVENTS.DATA, this.dataHandler);
|
|
163
|
+
this.stream.on(EVENTS.ERROR, this.errorHandler);
|
|
164
|
+
this.stream.on(EVENTS.END, this.endHandler);
|
|
165
|
+
|
|
166
|
+
const { cancel } = this.stream;
|
|
167
|
+
|
|
168
|
+
// Rewire cancel function in order to
|
|
169
|
+
// unsubscribe from DATA and END events.
|
|
170
|
+
// We don't unsubscribe from ERROR event because it
|
|
171
|
+
// handles `CANCELLED_ON_CLIENT` error after calling .cancel() and must stay
|
|
172
|
+
this.stream.cancel = () => {
|
|
173
|
+
this.stream.removeListener(EVENTS.DATA, this.dataHandler);
|
|
174
|
+
this.stream.removeListener(EVENTS.END, this.endHandler);
|
|
175
|
+
cancel.call(this.stream);
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* stream.on('data') handler
|
|
181
|
+
*
|
|
182
|
+
* @param data
|
|
183
|
+
*/
|
|
184
|
+
dataHandler(data) {
|
|
185
|
+
this.emit(EVENTS.DATA, data);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* stream.on('end') handler
|
|
190
|
+
*
|
|
191
|
+
* @private
|
|
192
|
+
*/
|
|
193
|
+
endHandler() {
|
|
194
|
+
this.stopAutoReconnect();
|
|
195
|
+
this.stream = null;
|
|
196
|
+
this.emit(EVENTS.END);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* stream.on('error') handler
|
|
201
|
+
*
|
|
202
|
+
* @private
|
|
203
|
+
* @param e
|
|
204
|
+
*/
|
|
205
|
+
errorHandler(e) {
|
|
206
|
+
// In case of cancellation nothing has to happen.
|
|
207
|
+
// TODO: do we need to propagate GrpcErrorCodes.CANCELLED further?
|
|
208
|
+
if (e.code === GrpcErrorCodes.CANCELLED) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Retry stream
|
|
213
|
+
this.retryOnError(e);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Manages retry logic
|
|
218
|
+
*
|
|
219
|
+
* @param e
|
|
220
|
+
*/
|
|
221
|
+
retryOnError(e) {
|
|
222
|
+
// Stop reconnect timeout if there is one
|
|
223
|
+
this.stopAutoReconnect();
|
|
224
|
+
|
|
225
|
+
// Unsubscribe from events in case of the external call
|
|
226
|
+
this.stream.removeListener(EVENTS.END, this.endHandler);
|
|
227
|
+
this.stream.removeListener(EVENTS.DATA, this.dataHandler);
|
|
228
|
+
|
|
229
|
+
const canRetry = this.maxRetriesOnError === -1 // Infinite retries
|
|
230
|
+
|| this.retriesOnError < this.maxRetriesOnError; // Or less than max limit
|
|
231
|
+
|
|
232
|
+
if (canRetry) {
|
|
233
|
+
// Handle 'beforeReconnect` logic
|
|
234
|
+
let newArgs = this.args || [];
|
|
235
|
+
const updateArgsBeforeReconnect = (...args) => {
|
|
236
|
+
newArgs = args;
|
|
237
|
+
};
|
|
238
|
+
|
|
239
|
+
// Emit beforeReconnect event in case parent would want to alter arguments
|
|
240
|
+
this.emit(EVENTS.BEFORE_RECONNECT, updateArgsBeforeReconnect);
|
|
241
|
+
|
|
242
|
+
// Wait before reconnecting
|
|
243
|
+
wait(this.retryOnErrorDelay)
|
|
244
|
+
.then(() => this.connect(...newArgs))// Reconnect with new args
|
|
245
|
+
.then(() => {
|
|
246
|
+
this.retriesOnError += 1; // Increase amount of current retries
|
|
247
|
+
})
|
|
248
|
+
.catch((connectError) => {
|
|
249
|
+
this.emit(EVENTS.ERROR, connectError); // Or emit error in case retry has failed
|
|
250
|
+
});
|
|
251
|
+
} else {
|
|
252
|
+
// Or simply emit error in case retry attempts were exhausted
|
|
253
|
+
this.emit(EVENTS.ERROR, e);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Stops auto reconnect timeout
|
|
259
|
+
*
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
stopAutoReconnect() {
|
|
263
|
+
if (this.reconnectTimeout) {
|
|
264
|
+
this.clearTimeout(this.reconnectTimeout);
|
|
265
|
+
this.reconnectTimeout = null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Cancels stream.
|
|
271
|
+
* Returns `stream.cancel()` to handle it from parent if needed.
|
|
272
|
+
* (grpc-js cancel() is asynchronous and grpc-web is synchronous)
|
|
273
|
+
*
|
|
274
|
+
* @returns {*}
|
|
275
|
+
*/
|
|
276
|
+
cancel() {
|
|
277
|
+
this.stopAutoReconnect();
|
|
278
|
+
return this.stream.cancel();
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
ReconnectableStream.EVENTS = EVENTS;
|
|
283
|
+
|
|
284
|
+
module.exports = ReconnectableStream;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dashevo/dapi-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.0-dev.2",
|
|
4
4
|
"description": "Client library used to access Dash DAPI endpoints",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"contributors": [
|
|
@@ -26,17 +26,18 @@
|
|
|
26
26
|
}
|
|
27
27
|
],
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@dashevo/dapi-grpc": "0.
|
|
30
|
-
"@dashevo/dash-spv": "0.
|
|
31
|
-
"@dashevo/dashcore-lib": "~0.
|
|
32
|
-
"@dashevo/dpp": "0.
|
|
33
|
-
"@dashevo/grpc-common": "0.
|
|
34
|
-
"@
|
|
35
|
-
"axios": "^0.27.2",
|
|
29
|
+
"@dashevo/dapi-grpc": "0.25.0-dev.2",
|
|
30
|
+
"@dashevo/dash-spv": "0.25.0-dev.2",
|
|
31
|
+
"@dashevo/dashcore-lib": "~0.20.3",
|
|
32
|
+
"@dashevo/dpp": "0.25.0-dev.2",
|
|
33
|
+
"@dashevo/grpc-common": "0.25.0-dev.2",
|
|
34
|
+
"@dashevo/wasm-dpp": "0.25.0-dev.2",
|
|
36
35
|
"bs58": "^4.0.1",
|
|
37
36
|
"cbor": "^8.0.0",
|
|
38
|
-
"lodash
|
|
39
|
-
"node-
|
|
37
|
+
"lodash": "^4.17.21",
|
|
38
|
+
"node-fetch": "^2.6.7",
|
|
39
|
+
"node-inspect-extracted": "^1.0.8",
|
|
40
|
+
"wasm-x11-hash": "~0.0.2"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
|
42
43
|
"@babel/core": "^7.15.5",
|
|
@@ -54,6 +55,7 @@
|
|
|
54
55
|
"eslint-plugin-import": "^2.24.2",
|
|
55
56
|
"eslint-plugin-jsdoc": "^27.0.0",
|
|
56
57
|
"events": "^3.3.0",
|
|
58
|
+
"google-protobuf": "^3.12.2",
|
|
57
59
|
"karma": "^6.4.1",
|
|
58
60
|
"karma-chai": "^0.1.0",
|
|
59
61
|
"karma-chrome-launcher": "^3.1.0",
|
|
@@ -71,12 +73,13 @@
|
|
|
71
73
|
"string_decoder": "^1.3.0",
|
|
72
74
|
"url": "^0.11.0",
|
|
73
75
|
"util": "^0.12.4",
|
|
74
|
-
"webpack": "^5.
|
|
76
|
+
"webpack": "^5.76.1",
|
|
75
77
|
"webpack-cli": "^4.9.1"
|
|
76
78
|
},
|
|
77
79
|
"files": [
|
|
78
80
|
"docs",
|
|
79
|
-
"lib"
|
|
81
|
+
"lib",
|
|
82
|
+
"polyfills"
|
|
80
83
|
],
|
|
81
84
|
"scripts": {
|
|
82
85
|
"build:web": "webpack",
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
const { default: fetch, Headers, Request, Response } = require('node-fetch');
|
|
4
|
+
|
|
5
|
+
if (!globalThis.fetch) {
|
|
6
|
+
globalThis.fetch = fetch;
|
|
7
|
+
globalThis.Headers = Headers;
|
|
8
|
+
globalThis.Request = Request;
|
|
9
|
+
globalThis.Response = Response;
|
|
10
|
+
}
|
|
File without changes
|