@tchain/tcabci-read-js-client 1.3.12 → 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,291 +1,439 @@
1
- const ReconnectingWebSocket = require('reconnecting-websocket/dist/reconnecting-websocket-cjs')
2
- const {
3
- NOT_CONNECTED,
4
- ALREADY_CONNECTED,
5
- INVALID_ARGUMENTS,
6
- NOT_SUBSCRIBED,
7
- ADDRESSES_IS_EMPTY,
8
- BLOCK_NOT_FOUND,
9
- TRANSACTION_TYPE_NOT_VALID,
10
- TRANSACTION_NOT_BROADCAST
11
- } = require("./errors")
12
- const {MESSAGE_TYPE, READ_NODE_ADDRESS, READ_NODE_WS_ADDRESS, TX_TYPE} = require("./constants")
13
- const Message = require("./message")
14
- const {toJSON} = require("./util")
15
- const axios = require("axios")
16
-
17
- class TCAbciClient {
18
- subscribed = false
19
- subscribedAddresses = []
20
- connected = false
21
- version = "v1.3.12"
22
- errorCb = null
23
- listenCb = null
24
- ws = null
25
- httpClient = null
26
- readNodeAddress = READ_NODE_ADDRESS
27
- readNodeWSAddress = READ_NODE_WS_ADDRESS
28
-
29
- /**
30
- *
31
- * @param {Array<String>} readNodeAddresses
32
- * @param axiosAdapter Axios Adapter
33
- */
34
- constructor(readNodeAddresses = [], axiosAdapter = null) {
35
- if (readNodeAddresses.length === 2) {
36
- this.readNodeAddress = readNodeAddresses[0]
37
- this.readNodeWSAddress = readNodeAddresses[1]
38
- }
39
- this.httpClient = axios.create({
40
- baseURL: this.readNodeAddress,
41
- timeout: 10000,
42
- headers: {'Client': `tcabaci-read-js-client${this.version}`},
43
- ...(axiosAdapter ? { adapter: axiosAdapter } : {})
44
- })
45
- }
46
- Socket() {
47
- return this.ws
48
- }
49
- SetError(cb) {
50
- this.errorCb = cb
51
- }
52
- SetListen(cb) {
53
- 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
54
54
  }
55
- Start() {
56
- 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}`,
57
67
  }
58
- Stop() {
59
- if (!this.getConnected()) {
60
- throw NOT_CONNECTED
61
- }
62
- this.Disconnect(1000)
63
- this.setConnected(false)
64
- this.setSubscribed(false)
68
+
69
+ if (typeof AbortSignal !== 'undefined') {
70
+ req.signal = AbortSignal.timeout(10000)
65
71
  }
66
- Subscribe(addresses = [], txTypes = []) {
67
- if (!Array.isArray(addresses)) {
68
- throw INVALID_ARGUMENTS
69
- }
70
72
 
71
- if (txTypes && !Array.isArray(addresses)) {
72
- throw INVALID_ARGUMENTS
73
- }
73
+ req.priority = 'high'
74
74
 
75
- if (txTypes.length && txTypes.length > Object.values(TX_TYPE).length) {
76
- throw INVALID_ARGUMENTS
77
- }
75
+ return fetch(this.readNodeAddress + uri, req).then((response) => {
76
+ if (response.ok) return response.json()
78
77
 
79
- if (!this.getConnected()) {
80
- throw NOT_CONNECTED
81
- }
78
+ return Promise.reject(
79
+ new FetchError(response.statusText, response.status),
80
+ )
81
+ })
82
+ }
82
83
 
83
- let addrs = []
84
+ Socket() {
85
+ return this.ws
86
+ }
84
87
 
85
- if (addresses.length === 0) {
86
- throw ADDRESSES_IS_EMPTY
87
- }
88
+ SetErrorCallback(cb) {
89
+ this.errorCb = cb
90
+ }
88
91
 
89
- addrs = addresses
92
+ SetListenCallback(cb) {
93
+ this.listenCb = cb
94
+ }
90
95
 
91
- if (this.getSubscribeAddresses().length > 0) {
92
- let newAddress = []
93
- for (let i = 0; i < addresses.length; i++) {
94
- if (this.getSubscribeAddresses().indexOf(addresses[i]) === -1 ){
95
- newAddress.push(addresses[i])
96
- }
97
- }
98
- addrs = newAddress
99
- }
96
+ async Start() {
97
+ return this.connect()
98
+ }
100
99
 
101
- const message = new Message(true, MESSAGE_TYPE.SUBSCRIBE, addrs, txTypes)
102
- this.ws.send(message.ToJSONString())
103
- this.setSubscribeAddresses(addrs, true)
104
- this.setSubscribed(true)
100
+ Stop() {
101
+ if (!this.getConnected()) {
102
+ throw NOT_CONNECTED
105
103
  }
106
- Unsubscribe() {
107
- if (!this.getSubscribed()) {
108
- throw NOT_SUBSCRIBED
109
- }
110
- this.ws.send(new Message(true, MESSAGE_TYPE.UNSUBSCRIBE, this.getSubscribeAddresses()).ToJSONString())
111
- this.setSubscribed(false)
112
- 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
113
112
  }
114
- LastBlock() {
115
- return this.httpClient.get("/v1/blocks?limit=1&offset=0")
116
- .then(res => { return { blocks: res.data.data, total_count: res.data.total_count } } )
117
- .catch(e => {
118
- throw BLOCK_NOT_FOUND
119
- })
113
+
114
+ if (txTypes && !Array.isArray(addresses)) {
115
+ throw INVALID_ARGUMENTS
120
116
  }
121
- TxSummary({recipientAddrs, senderAddrs, typ}) {
122
- return this.httpClient.post(
123
- "/v1/tx_summary",
124
- {
125
- recipient_addrs: recipientAddrs,
126
- sender_addrs: senderAddrs,
127
- typ: typ,
128
- })
129
- .then((res) => {
130
- return {
131
- first_block_height: res.data.data.first_block_height,
132
- first_transaction: res.data.data.first_transaction,
133
- last_block_height: res.data.data.last_block_height,
134
- last_transaction: res.data.data.last_transaction,
135
- total_count: res.data.total_count,
136
- }
137
- })
138
- .catch(e => {
139
- switch (e.response.status) {
140
- case 400:
141
- throw INVALID_ARGUMENTS
142
- default:
143
- throw e
144
- }
145
- })
117
+
118
+ if (txTypes.length && txTypes.length > Object.values(TX_TYPE).length) {
119
+ throw INVALID_ARGUMENTS
146
120
  }
147
- TxSearch({heightOperator, height, recipientAddrs, senderAddrs, hashes, typ, limit, offset, orderField, orderBy}) {
148
- return this.httpClient.post(
149
- "/v1/tx_search/p",
150
- {
151
- height: `${heightOperator} ${height}`,
152
- recipient_addrs: recipientAddrs,
153
- sender_addrs: senderAddrs,
154
- hashes: hashes,
155
- typ,
156
- limit,
157
- offset,
158
- order_field: orderField,
159
- order_by: orderBy
160
- })
161
- .then(res => {
162
- return {
163
- txs: res.data.data, total_count: res.data.total_count
164
- }
165
- })
166
- .catch(e => {
167
- switch (e.response.status) {
168
- case 400:
169
- throw INVALID_ARGUMENTS
170
- default:
171
- throw e
172
- }
173
- })
121
+
122
+ if (!this.getConnected()) {
123
+ throw NOT_CONNECTED
174
124
  }
175
- Status() {
176
- return {
177
- connected: this.connected,
178
- subscribed: this.subscribed,
179
- }
125
+
126
+ let addrs = []
127
+
128
+ if (addresses.length === 0) {
129
+ throw ADDRESSES_IS_EMPTY
180
130
  }
181
- Broadcast({id, version, type, data, sender_addr, recipient_addr, sign, fee}) {
182
- if(Object.values(TX_TYPE).indexOf(type) < 0) {
183
- 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])
184
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
+ }
185
151
 
186
- return this.httpClient.post(
187
- "/v1/tx",
188
- {
189
- id,
190
- version,
191
- type,
192
- data,
193
- sender_addr,
194
- recipient_addr,
195
- sign,
196
- fee
197
- }
198
- ).then(res => { return { data: res.data.data } })
199
- .catch(e => { throw TRANSACTION_NOT_BROADCAST })
152
+ Unsubscribe() {
153
+ if (!this.getSubscribed()) {
154
+ throw NOT_SUBSCRIBED
200
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
+ }
201
166
 
202
- Bulk(addresses = []) {
203
- 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)
204
190
  }
205
191
 
206
- Disconnect(code = 1000) {
207
- if (this.getConnected()) {
208
- 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,
209
207
  }
210
- }
208
+ })
209
+ .catch((e) => this.handleRestError(e))
210
+ }
211
211
 
212
- Reconnect(code = 1000) {
213
- if (this.getConnected()) {
214
- 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,
215
257
  }
216
- }
258
+ })
259
+ .catch((e) => this.handleRestError(e))
260
+ }
217
261
 
218
- connect() {
219
- return new Promise((resolve, reject) => {
220
- if (this.getConnected()) {
221
- reject(ALREADY_CONNECTED)
222
- }
223
-
224
- const options = {
225
- WebSocket: this.wsClient(),
226
- connectionTimeout: 1000,
227
- maxRetries: 10,
228
- }
229
-
230
- this.ws = new ReconnectingWebSocket(this.readNodeWSAddress, [], options)
231
-
232
- this.ws.onerror = (event) => {
233
- this.setConnected(false)
234
- if (this.errorCb) {
235
- this.errorCb(event.error)
236
- }
237
- reject(event)
238
- }
239
- this.ws.onopen = (event) => {
240
- this.setConnected(true)
241
- resolve(event)
242
- }
243
- this.ws.onmessage = (message) => {
244
- if (message.data === "OK" && message.data.length < 10) {
245
- return { status: message.data }
246
- }
247
- if (this.listenCb) {
248
- this.listenCb(toJSON(message.data))
249
- }
250
- }
251
- this.ws.onclose = (event) => {
252
- this.setConnected(false)
253
- if (this.errorCb) {
254
- this.errorCb(event.error)
255
- }
256
- }
257
- })
262
+ Status() {
263
+ return {
264
+ connected: this.connected,
265
+ subscribed: this.subscribed,
258
266
  }
267
+ }
259
268
 
260
- wsClient() {
261
- if (typeof window !== "undefined" && typeof window.WebSocket !== "undefined") {
262
- return window.WebSocket
263
- }
264
- const { WebSocket } = require("ws")
265
- 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
266
292
  }
267
- getConnected() {
268
- 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)
269
323
  }
270
- setConnected(value) {
271
- this.connected = value
324
+ }
325
+
326
+ Reconnect(code = 1000) {
327
+ if (this.getConnected()) {
328
+ this.ws.reconnect(code)
272
329
  }
273
- getSubscribed() {
274
- 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
275
382
  }
276
- setSubscribed(value) {
277
- 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
278
413
  }
279
- getSubscribeAddresses() {
280
- 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
281
428
  }
282
- setSubscribeAddresses(addresses, push = false) {
283
- if (push) {
284
- this.subscribedAddresses.push(...addresses)
285
- return
286
- }
287
- 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
288
437
  }
438
+ }
289
439
  }
290
-
291
- module.exports = TCAbciClient