@tchain/tcabci-read-js-client 1.3.13 → 2.0.0

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/client.js CHANGED
@@ -1,296 +1,439 @@
1
- const ReconnectingWebSocket = require('reconnecting-websocket/dist/reconnecting-websocket-cjs')
2
- const {
3
- NOT_CONNECTED,
4
- ERR_NETWORK,
5
- ALREADY_CONNECTED,
6
- INVALID_ARGUMENTS,
7
- NOT_SUBSCRIBED,
8
- ADDRESSES_IS_EMPTY,
9
- BLOCK_NOT_FOUND,
10
- TRANSACTION_TYPE_NOT_VALID,
11
- TRANSACTION_NOT_BROADCAST
12
- } = require("./errors")
13
- const {MESSAGE_TYPE, READ_NODE_ADDRESS, READ_NODE_WS_ADDRESS, TX_TYPE} = require("./constants")
14
- const Message = require("./message")
15
- const {toJSON} = require("./util")
16
- const axios = require("axios")
17
-
18
- class TCAbciClient {
19
- subscribed = false
20
- subscribedAddresses = []
21
- connected = false
22
- version = "v1.3.13"
23
- errorCb = null
24
- listenCb = null
25
- ws = null
26
- httpClient = null
27
- readNodeAddress = READ_NODE_ADDRESS
28
- readNodeWSAddress = READ_NODE_WS_ADDRESS
29
-
30
- /**
31
- *
32
- * @param {Array<String>} readNodeAddresses
33
- * @param axiosAdapter Axios Adapter
34
- */
35
- constructor(readNodeAddresses = [], axiosAdapter = null) {
36
- if (readNodeAddresses.length === 2) {
37
- this.readNodeAddress = readNodeAddresses[0]
38
- this.readNodeWSAddress = readNodeAddresses[1]
39
- }
40
- this.httpClient = axios.create({
41
- baseURL: this.readNodeAddress,
42
- timeout: 10000,
43
- headers: {'Client': `tcabaci-read-js-client${this.version}`},
44
- ...(axiosAdapter ? { adapter: axiosAdapter } : {})
45
- })
46
- }
47
- Socket() {
48
- return this.ws
49
- }
50
- SetError(cb) {
51
- this.errorCb = cb
52
- }
53
- SetListen(cb) {
54
- this.listenCb = cb
1
+ import ReconnectingWebSocket from 'reconnecting-websocket'
2
+ import {
3
+ ADDRESSES_IS_EMPTY,
4
+ ALREADY_CONNECTED,
5
+ BLOCK_NOT_FOUND,
6
+ ERR_NETWORK,
7
+ FetchError,
8
+ INVALID_ARGUMENTS,
9
+ NOT_CONNECTED,
10
+ NOT_SUBSCRIBED,
11
+ TRANSACTION_NOT_BROADCAST,
12
+ TRANSACTION_TYPE_NOT_VALID,
13
+ } from './errors.js'
14
+ import {
15
+ MESSAGE_TYPE,
16
+ READ_NODE_ADDRESS,
17
+ READ_NODE_WS_ADDRESS,
18
+ TX_TYPE,
19
+ } from './constants.js'
20
+ import Message from './message.js'
21
+ import { toJSON } from './util.js'
22
+
23
+ let fetch
24
+
25
+ if (typeof window !== 'undefined') {
26
+ fetch = window.fetch
27
+ } else {
28
+ fetch = (...args) =>
29
+ import('node-fetch').then(({ default: fetch }) => fetch(...args))
30
+ }
31
+
32
+ export default class TCAbciClient {
33
+ subscribed = false
34
+ subscribedAddresses = []
35
+ connected = false
36
+ version = 'v2.0.0'
37
+ errorCb = null
38
+ listenCb = null
39
+ ws = null
40
+ readNodeAddress = READ_NODE_ADDRESS
41
+ readNodeWSAddress = READ_NODE_WS_ADDRESS
42
+
43
+ /**
44
+ *
45
+ * @param {Array<String>} readNodeAddresses
46
+ */
47
+ constructor(readNodeAddresses = []) {
48
+ if (readNodeAddresses.length === 2) {
49
+ this.readNodeAddress = readNodeAddresses[0]
50
+ this.readNodeWSAddress = readNodeAddresses[1]
51
+
52
+ if (!readNodeAddresses[0].startsWith('http')) throw INVALID_ARGUMENTS
53
+ if (!readNodeAddresses[1].startsWith('ws')) throw INVALID_ARGUMENTS
55
54
  }
56
- Start() {
57
- return this.connect()
55
+ }
56
+
57
+ /**
58
+ * @param {string} uri
59
+ * @param {RequestInit} req
60
+ * @return {Promise<any>}
61
+ */
62
+ httpClient(uri, req) {
63
+ req.cache = 'no-cache'
64
+ req.headers = {
65
+ Client: `tcabaci-read-js-client${this.version}`,
66
+ 'X-Client': `tcabaci-read-js-client${this.version}`,
58
67
  }
59
- Stop() {
60
- if (!this.getConnected()) {
61
- throw NOT_CONNECTED
62
- }
63
- this.Disconnect(1000)
64
- this.setConnected(false)
65
- this.setSubscribed(false)
68
+
69
+ if (typeof AbortSignal !== 'undefined') {
70
+ req.signal = AbortSignal.timeout(10000)
66
71
  }
67
- Subscribe(addresses = [], txTypes = []) {
68
- if (!Array.isArray(addresses)) {
69
- throw INVALID_ARGUMENTS
70
- }
71
72
 
72
- if (txTypes && !Array.isArray(addresses)) {
73
- throw INVALID_ARGUMENTS
74
- }
73
+ req.priority = 'high'
75
74
 
76
- if (txTypes.length && txTypes.length > Object.values(TX_TYPE).length) {
77
- throw INVALID_ARGUMENTS
78
- }
75
+ return fetch(this.readNodeAddress + uri, req).then((response) => {
76
+ if (response.ok) return response.json()
79
77
 
80
- if (!this.getConnected()) {
81
- throw NOT_CONNECTED
82
- }
78
+ return Promise.reject(
79
+ new FetchError(response.statusText, response.status),
80
+ )
81
+ })
82
+ }
83
83
 
84
- let addrs = []
84
+ Socket() {
85
+ return this.ws
86
+ }
85
87
 
86
- if (addresses.length === 0) {
87
- throw ADDRESSES_IS_EMPTY
88
- }
88
+ SetErrorCallback(cb) {
89
+ this.errorCb = cb
90
+ }
89
91
 
90
- addrs = addresses
92
+ SetListenCallback(cb) {
93
+ this.listenCb = cb
94
+ }
91
95
 
92
- if (this.getSubscribeAddresses().length > 0) {
93
- let newAddress = []
94
- for (let i = 0; i < addresses.length; i++) {
95
- if (this.getSubscribeAddresses().indexOf(addresses[i]) === -1 ){
96
- newAddress.push(addresses[i])
97
- }
98
- }
99
- addrs = newAddress
100
- }
96
+ async Start() {
97
+ return this.connect()
98
+ }
101
99
 
102
- const message = new Message(true, MESSAGE_TYPE.SUBSCRIBE, addrs, txTypes)
103
- this.ws.send(message.ToJSONString())
104
- this.setSubscribeAddresses(addrs, true)
105
- this.setSubscribed(true)
100
+ Stop() {
101
+ if (!this.getConnected()) {
102
+ throw NOT_CONNECTED
106
103
  }
107
- Unsubscribe() {
108
- if (!this.getSubscribed()) {
109
- throw NOT_SUBSCRIBED
110
- }
111
- this.ws.send(new Message(true, MESSAGE_TYPE.UNSUBSCRIBE, this.getSubscribeAddresses()).ToJSONString())
112
- this.setSubscribed(false)
113
- this.setSubscribeAddresses([])
104
+ this.Disconnect(1000)
105
+ this.setConnected(false)
106
+ this.setSubscribed(false)
107
+ }
108
+
109
+ Subscribe(addresses = [], txTypes = []) {
110
+ if (!Array.isArray(addresses)) {
111
+ throw INVALID_ARGUMENTS
114
112
  }
115
- LastBlock() {
116
- return this.httpClient.get("/v1/blocks?limit=1&offset=0")
117
- .then(res => { return { blocks: res.data.data, total_count: res.data.total_count } } )
118
- .catch(e => {
119
- throw BLOCK_NOT_FOUND
120
- })
113
+
114
+ if (txTypes && !Array.isArray(addresses)) {
115
+ throw INVALID_ARGUMENTS
121
116
  }
122
- TxSummary({recipientAddrs, senderAddrs, typ}) {
123
- return this.httpClient.post(
124
- "/v1/tx_summary",
125
- {
126
- recipient_addrs: recipientAddrs,
127
- sender_addrs: senderAddrs,
128
- typ: typ,
129
- })
130
- .then((res) => {
131
- return {
132
- first_block_height: res.data.data.first_block_height,
133
- first_transaction: res.data.data.first_transaction,
134
- last_block_height: res.data.data.last_block_height,
135
- last_transaction: res.data.data.last_transaction,
136
- total_count: res.data.total_count,
137
- }
138
- })
139
- .catch(e => {
140
- switch (e?.response?.status ?? e?.status ?? e?.code) {
141
- case 400:
142
- throw INVALID_ARGUMENTS
143
- case 'ERR_NETWORK':
144
- throw ERR_NETWORK
145
- default:
146
- throw e
147
- }
148
- })
117
+
118
+ if (txTypes.length && txTypes.length > Object.values(TX_TYPE).length) {
119
+ throw INVALID_ARGUMENTS
149
120
  }
150
- TxSearch({heightOperator, height, recipientAddrs, senderAddrs, hashes, typ, limit, offset, orderField, orderBy}) {
151
- return this.httpClient.post(
152
- "/v1/tx_search/p",
153
- {
154
- height: `${heightOperator} ${height}`,
155
- recipient_addrs: recipientAddrs,
156
- sender_addrs: senderAddrs,
157
- hashes: hashes,
158
- typ,
159
- limit,
160
- offset,
161
- order_field: orderField,
162
- order_by: orderBy
163
- })
164
- .then(res => {
165
- return {
166
- txs: res.data.data, total_count: res.data.total_count
167
- }
168
- })
169
- .catch(e => {
170
- switch (e?.response?.status ?? e?.status ?? e?.code) {
171
- case 400:
172
- throw INVALID_ARGUMENTS
173
- case 'ERR_NETWORK':
174
- throw ERR_NETWORK
175
- default:
176
- throw e
177
- }
178
- })
121
+
122
+ if (!this.getConnected()) {
123
+ throw NOT_CONNECTED
179
124
  }
180
- Status() {
181
- return {
182
- connected: this.connected,
183
- subscribed: this.subscribed,
184
- }
125
+
126
+ let addrs = []
127
+
128
+ if (addresses.length === 0) {
129
+ throw ADDRESSES_IS_EMPTY
185
130
  }
186
- Broadcast({id, version, type, data, sender_addr, recipient_addr, sign, fee}) {
187
- if(Object.values(TX_TYPE).indexOf(type) < 0) {
188
- throw TRANSACTION_TYPE_NOT_VALID
131
+
132
+ addrs = addresses
133
+
134
+ if (this.getSubscribeAddresses().length > 0) {
135
+ let newAddress = []
136
+
137
+ for (let i = 0; i < addresses.length; i++) {
138
+ if (this.getSubscribeAddresses().indexOf(addresses[i]) === -1) {
139
+ newAddress.push(addresses[i])
189
140
  }
141
+ }
142
+ addrs = newAddress
143
+ }
144
+
145
+ const message = new Message(true, MESSAGE_TYPE.SUBSCRIBE, addrs, txTypes)
146
+
147
+ this.ws.send(message.ToJSONString())
148
+ this.setSubscribeAddresses(addrs, true)
149
+ this.setSubscribed(true)
150
+ }
190
151
 
191
- return this.httpClient.post(
192
- "/v1/tx",
193
- {
194
- id,
195
- version,
196
- type,
197
- data,
198
- sender_addr,
199
- recipient_addr,
200
- sign,
201
- fee
202
- }
203
- ).then(res => { return { data: res.data.data } })
204
- .catch(e => { throw TRANSACTION_NOT_BROADCAST })
152
+ Unsubscribe() {
153
+ if (!this.getSubscribed()) {
154
+ throw NOT_SUBSCRIBED
205
155
  }
156
+ this.ws.send(
157
+ new Message(
158
+ true,
159
+ MESSAGE_TYPE.UNSUBSCRIBE,
160
+ this.getSubscribeAddresses(),
161
+ ).ToJSONString(),
162
+ )
163
+ this.setSubscribed(false)
164
+ this.setSubscribeAddresses([])
165
+ }
206
166
 
207
- Bulk(addresses = []) {
208
- return this.httpClient.post('/v1/bulk_tx', { addresses: addresses })
167
+ /**
168
+ * @return {Promise<any>}
169
+ */
170
+ LastBlock() {
171
+ return this.httpClient('/v1/blocks?limit=1&offset=0', {
172
+ method: 'GET',
173
+ })
174
+ .then((res) => {
175
+ return { blocks: res.data, total_count: res.total_count }
176
+ })
177
+ .catch((e) => this.handleRestError(e, BLOCK_NOT_FOUND))
178
+ }
179
+
180
+ /**
181
+ * @param {?Array<string>} recipientAddrs
182
+ * @param {?Array<string>} senderAddrs
183
+ * @param {?string} typ
184
+ * @param {?Array<string>}
185
+ * @return {Promise<any>}
186
+ */
187
+ TxSummary({ recipientAddrs, senderAddrs, typ, types }) {
188
+ if (!recipientAddrs && !senderAddrs) {
189
+ return Promise.reject(INVALID_ARGUMENTS)
209
190
  }
210
191
 
211
- Disconnect(code = 1000) {
212
- if (this.getConnected()) {
213
- this.ws.close(code)
192
+ return this.httpClient('/v1/tx_summary', {
193
+ method: 'POST',
194
+ body: JSON.stringify({
195
+ recipient_addrs: recipientAddrs,
196
+ sender_addrs: senderAddrs,
197
+ ...(types ? { types: types } : { typ: typ }),
198
+ }),
199
+ })
200
+ .then((res) => {
201
+ return {
202
+ first_block_height: res.data.first_block_height,
203
+ first_transaction: res.data.first_transaction,
204
+ last_block_height: res.data.last_block_height,
205
+ last_transaction: res.data.last_transaction,
206
+ total_count: res.total_count,
214
207
  }
215
- }
208
+ })
209
+ .catch((e) => this.handleRestError(e))
210
+ }
216
211
 
217
- Reconnect(code = 1000) {
218
- if (this.getConnected()) {
219
- this.ws.reconnect(code)
212
+ /**
213
+ * @param {string} heightOperator
214
+ * @param {number} height
215
+ * @param {?Array<string>} recipientAddrs
216
+ * @param {?Array<string>} senderAddrs
217
+ * @param {?Array<string>} hashes
218
+ * @param {string} typ
219
+ * @param {?Array<string>} types
220
+ * @param {number} limit
221
+ * @param {number} offset
222
+ * @param {string} orderField
223
+ * @param {string} orderBy
224
+ * @return {Promise<any>}
225
+ */
226
+ TxSearch({
227
+ heightOperator,
228
+ height,
229
+ recipientAddrs,
230
+ senderAddrs,
231
+ hashes,
232
+ typ,
233
+ types,
234
+ limit,
235
+ offset,
236
+ orderField,
237
+ orderBy,
238
+ }) {
239
+ return this.httpClient('/v1/tx_search/p', {
240
+ method: 'POST',
241
+ body: JSON.stringify({
242
+ height: `${heightOperator} ${height}`,
243
+ recipient_addrs: recipientAddrs,
244
+ sender_addrs: senderAddrs,
245
+ hashes: hashes,
246
+ limit,
247
+ offset,
248
+ order_field: orderField,
249
+ order_by: orderBy,
250
+ ...(types ? { types: types } : { typ: typ }),
251
+ }),
252
+ })
253
+ .then((res) => {
254
+ return {
255
+ txs: res.data,
256
+ total_count: res.total_count,
220
257
  }
221
- }
258
+ })
259
+ .catch((e) => this.handleRestError(e))
260
+ }
222
261
 
223
- connect() {
224
- return new Promise((resolve, reject) => {
225
- if (this.getConnected()) {
226
- reject(ALREADY_CONNECTED)
227
- }
228
-
229
- const options = {
230
- WebSocket: this.wsClient(),
231
- connectionTimeout: 1000,
232
- maxRetries: 10,
233
- }
234
-
235
- this.ws = new ReconnectingWebSocket(this.readNodeWSAddress, [], options)
236
-
237
- this.ws.onerror = (event) => {
238
- this.setConnected(false)
239
- if (this.errorCb) {
240
- this.errorCb(event.error)
241
- }
242
- reject(event)
243
- }
244
- this.ws.onopen = (event) => {
245
- this.setConnected(true)
246
- resolve(event)
247
- }
248
- this.ws.onmessage = (message) => {
249
- if (message.data === "OK" && message.data.length < 10) {
250
- return { status: message.data }
251
- }
252
- if (this.listenCb) {
253
- this.listenCb(toJSON(message.data))
254
- }
255
- }
256
- this.ws.onclose = (event) => {
257
- this.setConnected(false)
258
- if (this.errorCb) {
259
- this.errorCb(event.error)
260
- }
261
- }
262
- })
262
+ Status() {
263
+ return {
264
+ connected: this.connected,
265
+ subscribed: this.subscribed,
263
266
  }
267
+ }
264
268
 
265
- wsClient() {
266
- if (typeof window !== "undefined" && typeof window.WebSocket !== "undefined") {
267
- return window.WebSocket
268
- }
269
- const { WebSocket } = require("ws")
270
- return WebSocket
269
+ /**
270
+ * @param {string} id
271
+ * @param {number} version
272
+ * @param {string} type
273
+ * @param {string} data
274
+ * @param {string} sender_addr
275
+ * @param {string} recipient_addr
276
+ * @param {string} sign
277
+ * @param {number} fee
278
+ * @return {Promise<any>}
279
+ */
280
+ Broadcast({
281
+ id,
282
+ version,
283
+ type,
284
+ data,
285
+ sender_addr,
286
+ recipient_addr,
287
+ sign,
288
+ fee,
289
+ }) {
290
+ if (Object.values(TX_TYPE).indexOf(type) < 0) {
291
+ throw TRANSACTION_TYPE_NOT_VALID
271
292
  }
272
- getConnected() {
273
- return this.connected
293
+
294
+ return this.httpClient('/v1/tx', {
295
+ method: 'POST',
296
+ body: JSON.stringify({
297
+ id,
298
+ version,
299
+ type,
300
+ data,
301
+ sender_addr,
302
+ recipient_addr,
303
+ sign,
304
+ fee,
305
+ }),
306
+ })
307
+ .then((res) => {
308
+ return { data: res.data.data }
309
+ })
310
+ .catch((e) => this.handleRestError(e, { 400: TRANSACTION_NOT_BROADCAST }))
311
+ }
312
+
313
+ Bulk(addresses = []) {
314
+ return this.httpClient('/v1/bulk_tx', {
315
+ method: 'POST',
316
+ body: JSON.stringify({ addresses: addresses }),
317
+ })
318
+ }
319
+
320
+ Disconnect(code = 1000) {
321
+ if (this.getConnected()) {
322
+ this.ws.close(code)
274
323
  }
275
- setConnected(value) {
276
- this.connected = value
324
+ }
325
+
326
+ Reconnect(code = 1000) {
327
+ if (this.getConnected()) {
328
+ this.ws.reconnect(code)
277
329
  }
278
- getSubscribed() {
279
- return this.subscribed
330
+ }
331
+
332
+ async connect() {
333
+ const wsClient = await this.wsClient()
334
+
335
+ return new Promise((resolve, reject) => {
336
+ if (this.getConnected()) {
337
+ reject(ALREADY_CONNECTED)
338
+ }
339
+
340
+ const options = {
341
+ WebSocket: wsClient,
342
+ connectionTimeout: 1000,
343
+ maxRetries: 10,
344
+ }
345
+
346
+ this.ws = new ReconnectingWebSocket(this.readNodeWSAddress, [], options)
347
+
348
+ this.ws.onerror = (event) => {
349
+ this.setConnected(false)
350
+ if (this.errorCb) {
351
+ this.errorCb(event.error)
352
+ }
353
+ reject(event.error)
354
+ }
355
+ this.ws.onopen = (event) => {
356
+ this.setConnected(true)
357
+ resolve(event)
358
+ }
359
+ this.ws.onmessage = (message) => {
360
+ if (message.data === 'OK' && message.data.length < 10) {
361
+ return { status: message.data }
362
+ }
363
+ if (this.listenCb) {
364
+ this.listenCb(toJSON(message.data))
365
+ }
366
+ }
367
+ this.ws.onclose = (event) => {
368
+ this.setConnected(false)
369
+ if (this.errorCb) {
370
+ this.errorCb(event.error)
371
+ }
372
+ }
373
+ })
374
+ }
375
+
376
+ async wsClient() {
377
+ if (
378
+ typeof window !== 'undefined' &&
379
+ typeof window.WebSocket !== 'undefined'
380
+ ) {
381
+ return window.WebSocket
280
382
  }
281
- setSubscribed(value) {
282
- this.subscribed = value
383
+ const { WebSocket } = await import('ws')
384
+
385
+ return WebSocket
386
+ }
387
+
388
+ getConnected() {
389
+ return this.connected
390
+ }
391
+
392
+ setConnected(value) {
393
+ this.connected = value
394
+ }
395
+
396
+ getSubscribed() {
397
+ return this.subscribed
398
+ }
399
+
400
+ setSubscribed(value) {
401
+ this.subscribed = value
402
+ }
403
+
404
+ getSubscribeAddresses() {
405
+ return this.subscribedAddresses
406
+ }
407
+
408
+ setSubscribeAddresses(addresses, push = false) {
409
+ if (push) {
410
+ this.subscribedAddresses.push(...addresses)
411
+
412
+ return
283
413
  }
284
- getSubscribeAddresses() {
285
- return this.subscribedAddresses
414
+ this.subscribedAddresses = addresses
415
+ }
416
+
417
+ /**
418
+ * @param {Error} err
419
+ * @param {?Object|Error} custom
420
+ */
421
+ handleRestError(err, custom = null) {
422
+ const code = err.code
423
+
424
+ if (custom && typeof custom === 'object' && custom[code]) {
425
+ throw custom[code]
426
+ } else if (custom instanceof Error) {
427
+ throw custom
286
428
  }
287
- setSubscribeAddresses(addresses, push = false) {
288
- if (push) {
289
- this.subscribedAddresses.push(...addresses)
290
- return
291
- }
292
- this.subscribedAddresses = addresses
429
+
430
+ switch (code) {
431
+ case 400:
432
+ throw INVALID_ARGUMENTS
433
+ case 'ERR_NETWORK':
434
+ throw ERR_NETWORK
435
+ default:
436
+ throw err
293
437
  }
438
+ }
294
439
  }
295
-
296
- module.exports = TCAbciClient