@tchain/tcabci-read-js-client 2.7.12 → 2.8.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/README.md +62 -46
- package/block.d.ts +19 -10
- package/block.js +39 -41
- package/breaker.d.ts +5 -1
- package/breaker.js +47 -44
- package/bytea.d.ts +1 -0
- package/bytea.js +16 -0
- package/callback.d.ts +12 -7
- package/client.d.ts +170 -119
- package/client.js +465 -315
- package/errors.d.ts +16 -2
- package/errors.js +5 -1
- package/http.d.ts +31 -6
- package/http.js +286 -51
- package/message.d.ts +48 -45
- package/message.js +77 -53
- package/package.json +22 -17
- package/transaction.d.ts +178 -92
- package/transaction.js +96 -69
- package/types.d.ts +60 -0
- package/util.d.ts +4 -0
- package/util.js +20 -6
- package/validation.d.ts +28 -0
- package/validation.js +155 -0
- package/websocket.d.ts +35 -15
- package/websocket.js +129 -43
- package/websocketOptions.d.ts +48 -10
- package/websocketOptions.js +53 -23
package/README.md
CHANGED
|
@@ -1,65 +1,81 @@
|
|
|
1
|
-
# TCABCI Read Node
|
|
1
|
+
# TCABCI Read Node JavaScript Client
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
HTTP queries, transaction broadcasts and WebSocket subscriptions for the
|
|
4
|
+
TransferChain read network. Uses ESM and supports Node.js 24+ or browsers with
|
|
5
|
+
Fetch, WebSocket, AbortController, TextEncoder and TextDecoder.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- medusa - v2
|
|
10
|
-
|
|
11
|
-
## Installation
|
|
12
|
-
|
|
13
|
-
```shell
|
|
14
|
-
$ npm i @tchain/tcabci-read-js-client
|
|
7
|
+
```sh
|
|
8
|
+
npm install @tchain/tcabci-read-js-client
|
|
15
9
|
```
|
|
16
10
|
|
|
17
|
-
## Example
|
|
18
|
-
|
|
19
|
-
**Subscribe, Listen and Unsubscribe Example**
|
|
20
|
-
|
|
21
11
|
```js
|
|
22
12
|
import TCaBCIClient from '@tchain/tcabci-read-js-client'
|
|
23
13
|
|
|
24
14
|
const client = new TCaBCIClient()
|
|
25
|
-
// OR
|
|
26
|
-
const client = new TCaBCIClient([
|
|
27
|
-
'https://read-node-01.transferchain.io',
|
|
28
|
-
'wss://read-node-01.transferchain.io/ws'
|
|
29
|
-
])
|
|
30
|
-
// OR
|
|
31
|
-
// With addresses and WebSocket library and network name, version
|
|
32
|
-
const client = new TCaBCIClient(
|
|
33
|
-
[
|
|
34
|
-
'https://read-node-01.transferchain.io',
|
|
35
|
-
'wss://read-node-01.transferchain.io/ws'
|
|
36
|
-
],
|
|
37
|
-
WebSocket,
|
|
38
|
-
'transferchain',
|
|
39
|
-
'v1'
|
|
40
|
-
)
|
|
41
15
|
|
|
42
|
-
client.
|
|
16
|
+
client.SetSuccessCallback(() => {
|
|
17
|
+
// Obtain a valid address-to-signature map from your signing layer.
|
|
18
|
+
client.Subscribe(['<public-address>'], {
|
|
19
|
+
'<public-address>': '<signature>'
|
|
20
|
+
})
|
|
21
|
+
})
|
|
22
|
+
client.SetListenCallback((block, transaction, message) => {
|
|
23
|
+
// Handle the received immutable data snapshot.
|
|
24
|
+
})
|
|
25
|
+
client.SetErrorCallback(error => {
|
|
26
|
+
// Handle a sanitized transport/protocol error.
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
await client.Start() // Starts connecting; the success callback reports OPEN.
|
|
30
|
+
// When finished:
|
|
31
|
+
await client.Dispose() // Also clears application callbacks.
|
|
32
|
+
```
|
|
43
33
|
|
|
44
|
-
|
|
34
|
+
For an injected WebSocket implementation, pass its constructor as the second
|
|
35
|
+
argument. The first argument is an optional `[httpsURL, wssURL]` endpoint pair;
|
|
36
|
+
the third and fourth arguments select the chain. The fifth argument accepts
|
|
37
|
+
client limits and transport options.
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
```js
|
|
40
|
+
const client = new TCaBCIClient([], WebSocket, 'medusa', 'v2', {
|
|
41
|
+
timeoutMs: 30000,
|
|
42
|
+
maxRequestBytes: 16 * 1024 * 1024,
|
|
43
|
+
maxResponseBytes: 64 * 1024 * 1024,
|
|
44
|
+
maxConcurrentRequests: 32,
|
|
45
|
+
maxMessageBytes: 16 * 1024 * 1024,
|
|
46
|
+
maxEnqueuedMessages: 100,
|
|
47
|
+
maxPendingCallbacks: 1000
|
|
48
48
|
})
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
50
|
+
const controller = new AbortController()
|
|
51
|
+
const result = await client.Tx('<transaction-id>', '<signature>', {
|
|
52
|
+
signal: controller.signal,
|
|
53
|
+
timeoutMs: 10000
|
|
52
54
|
})
|
|
55
|
+
```
|
|
53
56
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
+
Each address list accepts 1–251 entries; each address accepts 1–2048 characters.
|
|
58
|
+
Active subscriptions are deduplicated and bounded to 251 addresses. Signed data
|
|
59
|
+
is an address-to-signature string map. Caller objects are neither frozen nor
|
|
60
|
+
overwritten.
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
62
|
+
HTTP redirects are rejected. Remote plaintext endpoints require explicit
|
|
63
|
+
`allowInsecure: true`; exact localhost/loopback endpoints support local
|
|
64
|
+
integration. Read-only operations may retry transient failures; broadcasts are
|
|
65
|
+
attempted once. The deadline includes retries and response consumption. HTTP
|
|
66
|
+
errors retain status, without response bodies or raw parser causes.
|
|
67
|
+
|
|
68
|
+
`Stop()` is idempotent, aborts HTTP work and releases subscription state. Await
|
|
69
|
+
pending HTTP promises to observe their cancellation and completed byte cleanup.
|
|
70
|
+
`Dispose()` additionally drops application callback references. Close codes use
|
|
71
|
+
browser-compatible values: 1000 or 3000–4999. Offline binary messages are
|
|
72
|
+
rejected; string queues have finite count and byte limits. Transport payload
|
|
73
|
+
logging is disabled, including when `SetDebug(true)` is called. Frozen snapshots
|
|
74
|
+
do not imply secure erasure of JavaScript strings.
|
|
75
|
+
|
|
76
|
+
TypeScript declarations accompany the ESM API. Tests are intentionally local
|
|
77
|
+
only under the ignored `test/` directory and are excluded from the package.
|
|
61
78
|
|
|
62
79
|
## License
|
|
63
80
|
|
|
64
|
-
|
|
65
|
-
text.
|
|
81
|
+
Apache License, Version 2.0. See [LICENSE](LICENSE).
|
package/block.d.ts
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { Transaction } from './transaction'
|
|
1
|
+
import { Transaction } from './transaction.js'
|
|
2
|
+
import { ParseResult } from './types.js'
|
|
2
3
|
|
|
3
4
|
export declare class Block {
|
|
4
|
-
get Height(): number
|
|
5
|
-
|
|
6
|
-
get
|
|
7
|
-
|
|
8
|
-
get
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
5
|
+
get Height(): number | undefined
|
|
6
|
+
|
|
7
|
+
get TXS(): number | undefined
|
|
8
|
+
|
|
9
|
+
get Hash(): string | undefined
|
|
10
|
+
|
|
11
|
+
get Transactions(): readonly Transaction[] | undefined
|
|
12
|
+
|
|
13
|
+
get ChainName(): string | undefined
|
|
14
|
+
|
|
15
|
+
get ChainVersion(): string | undefined
|
|
16
|
+
|
|
17
|
+
static FromJSON(value: string): ParseResult<'block', Readonly<Block>>
|
|
18
|
+
|
|
19
|
+
static FromObject(
|
|
20
|
+
obj: Record<string, unknown>
|
|
21
|
+
): ParseResult<'block', Readonly<Block>>
|
|
13
22
|
}
|
package/block.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { SendThrow } from './util.js'
|
|
1
2
|
import { Transaction } from './transaction.js'
|
|
2
|
-
import { Debug } from '@streetbyters/js-debug'
|
|
3
3
|
|
|
4
4
|
export class Block {
|
|
5
5
|
_height
|
|
@@ -21,6 +21,7 @@ export class Block {
|
|
|
21
21
|
return this._hash
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** @returns {ReadonlyArray<Readonly<Transaction>>|undefined} Frozen parsed transactions. */
|
|
24
25
|
get Transactions() {
|
|
25
26
|
return this._transactions
|
|
26
27
|
}
|
|
@@ -35,59 +36,54 @@ export class Block {
|
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
38
|
* @param {string} value
|
|
38
|
-
* @return {
|
|
39
|
+
* @return {import('./types.js').ParseResult<'block', Readonly<Block>>}
|
|
39
40
|
* @constructor
|
|
40
41
|
*/
|
|
41
42
|
static FromJSON(value) {
|
|
42
43
|
try {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
bl._transactions = []
|
|
47
|
-
|
|
48
|
-
if (typeof parsed.height !== 'undefined') bl._height = parsed.height
|
|
49
|
-
if (typeof parsed.txs !== 'undefined') bl._txs = parsed.txs
|
|
50
|
-
if (typeof parsed.hash !== 'undefined') bl._hash = parsed.hash
|
|
51
|
-
if (typeof parsed.chain_name !== 'undefined')
|
|
52
|
-
bl._chainName = parsed.chain_name
|
|
53
|
-
if (typeof parsed.chain_version !== 'undefined')
|
|
54
|
-
bl._chainVersion = parsed.chain_version
|
|
55
|
-
|
|
56
|
-
if (typeof parsed.transactions !== 'undefined') {
|
|
57
|
-
for (let i = 0; i < parsed.transactions.length; i++) {
|
|
58
|
-
const { transaction, error } = Transaction.FromObject(
|
|
59
|
-
parsed.transactions[i]
|
|
60
|
-
)
|
|
61
|
-
if (error) {
|
|
62
|
-
return { block: null, error: error }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
bl._transactions.push(transaction)
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return { block: bl, error: null }
|
|
70
|
-
} catch (e) {
|
|
71
|
-
Debug.error(e)
|
|
72
|
-
return { block: null, error: e }
|
|
44
|
+
return Block.FromObject(JSON.parse(value))
|
|
45
|
+
} catch {
|
|
46
|
+
return { block: null, error: new Error('Invalid block JSON') }
|
|
73
47
|
}
|
|
74
48
|
}
|
|
75
49
|
|
|
76
|
-
/**
|
|
77
|
-
* @param {object} obj
|
|
78
|
-
* @return {{block: ?Block, error: ?Error}}
|
|
79
|
-
* @constructor
|
|
80
|
-
*/
|
|
50
|
+
/** @param {Record<string, unknown>} obj @returns {import('./types.js').ParseResult<'block', Readonly<Block>>} */
|
|
81
51
|
static FromObject(obj) {
|
|
82
52
|
try {
|
|
53
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj))
|
|
54
|
+
SendThrow(new TypeError('Invalid block'))
|
|
55
|
+
|
|
56
|
+
for (const field of ['height', 'txs']) {
|
|
57
|
+
if (
|
|
58
|
+
typeof obj[field] !== 'undefined' &&
|
|
59
|
+
(!Number.isSafeInteger(obj[field]) || obj[field] < 0)
|
|
60
|
+
)
|
|
61
|
+
SendThrow(new TypeError('Invalid block counter'))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
for (const field of ['hash', 'chain_name', 'chain_version']) {
|
|
65
|
+
if (typeof obj[field] !== 'undefined' && typeof obj[field] !== 'string')
|
|
66
|
+
SendThrow(new TypeError('Invalid block field'))
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (
|
|
70
|
+
typeof obj.transactions !== 'undefined' &&
|
|
71
|
+
(!Array.isArray(obj.transactions) || obj.transactions.length > 100000)
|
|
72
|
+
)
|
|
73
|
+
SendThrow(new TypeError('Invalid block transactions'))
|
|
74
|
+
|
|
83
75
|
const bl = new Block()
|
|
84
76
|
|
|
85
77
|
bl._transactions = []
|
|
86
78
|
|
|
87
79
|
if (typeof obj.height !== 'undefined') bl._height = obj.height
|
|
80
|
+
|
|
88
81
|
if (typeof obj.txs !== 'undefined') bl._txs = obj.txs
|
|
82
|
+
|
|
89
83
|
if (typeof obj.hash !== 'undefined') bl._hash = obj.hash
|
|
84
|
+
|
|
90
85
|
if (typeof obj.chain_name !== 'undefined') bl._chainName = obj.chain_name
|
|
86
|
+
|
|
91
87
|
if (typeof obj.chain_version !== 'undefined')
|
|
92
88
|
bl._chainVersion = obj.chain_version
|
|
93
89
|
|
|
@@ -96,6 +92,7 @@ export class Block {
|
|
|
96
92
|
const { transaction, error } = Transaction.FromObject(
|
|
97
93
|
obj.transactions[i]
|
|
98
94
|
)
|
|
95
|
+
|
|
99
96
|
if (error) {
|
|
100
97
|
return { block: null, error: error }
|
|
101
98
|
}
|
|
@@ -104,10 +101,11 @@ export class Block {
|
|
|
104
101
|
}
|
|
105
102
|
}
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
104
|
+
Object.freeze(bl._transactions)
|
|
105
|
+
|
|
106
|
+
return { block: Object.freeze(bl), error: null }
|
|
107
|
+
} catch {
|
|
108
|
+
return { block: null, error: new Error('Invalid block payload') }
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
}
|
package/breaker.d.ts
CHANGED
package/breaker.js
CHANGED
|
@@ -1,52 +1,55 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
wrap
|
|
8
|
-
} from 'cockatiel'
|
|
9
|
-
import { FetchError } from './errors.js'
|
|
1
|
+
import { ConsecutiveBreaker, handleType, circuitBreaker } from 'cockatiel'
|
|
2
|
+
import { FetchError, ERR_NETWORK } from './errors.js'
|
|
3
|
+
|
|
4
|
+
const retryable = (error) =>
|
|
5
|
+
error instanceof FetchError &&
|
|
6
|
+
([502, 503, 504].includes(error.status) || error.message === ERR_NETWORK)
|
|
10
7
|
|
|
11
8
|
export class Breaker {
|
|
12
|
-
|
|
13
|
-
halfOpenAfter:
|
|
9
|
+
_circuitBreakerPolicy = circuitBreaker(handleType(FetchError, retryable), {
|
|
10
|
+
halfOpenAfter: 10000,
|
|
14
11
|
breaker: new ConsecutiveBreaker(10)
|
|
15
|
-
}
|
|
16
|
-
_retryPolicy = retry(
|
|
17
|
-
handleType(FetchError, err => [502, 503, 504].includes(err.status)).orType(
|
|
18
|
-
Error,
|
|
19
|
-
err => {
|
|
20
|
-
return err.message.toLowerCase().search('failed to fetch') > -1
|
|
21
|
-
}
|
|
22
|
-
),
|
|
23
|
-
{
|
|
24
|
-
maxAttempts: 10,
|
|
25
|
-
backoff: new ExponentialBackoff({
|
|
26
|
-
initialDelay: 500,
|
|
27
|
-
maxDelay: 15000
|
|
28
|
-
})
|
|
29
|
-
}
|
|
30
|
-
)
|
|
31
|
-
_circuitBreakerPolicy = circuitBreaker(
|
|
32
|
-
handleType(FetchError, err => [502, 503, 504].includes(err.status)).orType(
|
|
33
|
-
Error,
|
|
34
|
-
err => {
|
|
35
|
-
return err.message.toLowerCase().search('failed to fetch') > -1
|
|
36
|
-
}
|
|
37
|
-
),
|
|
38
|
-
this._options
|
|
39
|
-
)
|
|
40
|
-
_retryWithBreaker = wrap(this._retryPolicy, this._circuitBreakerPolicy)
|
|
41
|
-
constructor() {}
|
|
12
|
+
})
|
|
42
13
|
|
|
43
14
|
/**
|
|
44
|
-
* @
|
|
45
|
-
* @
|
|
15
|
+
* @template T
|
|
16
|
+
* @param {function(): Promise<T>} fn
|
|
17
|
+
* @param {AbortSignal} [signal] Cancels retry backoff immediately.
|
|
18
|
+
* @param {boolean} [retry=true] Disable for non-idempotent writes.
|
|
19
|
+
* @returns {Promise<T>}
|
|
46
20
|
*/
|
|
47
|
-
execute(fn) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
21
|
+
async execute(fn, signal, retry = true) {
|
|
22
|
+
for (let attempt = 0; ; attempt++) {
|
|
23
|
+
signal?.throwIfAborted()
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
return await this._circuitBreakerPolicy.execute(fn, signal)
|
|
27
|
+
} catch (error) {
|
|
28
|
+
if (!retry || attempt >= 3 || signal?.aborted || !retryable(error))
|
|
29
|
+
throw error
|
|
30
|
+
|
|
31
|
+
await this._delay(
|
|
32
|
+
Math.min(500 * 2 ** attempt, 15000) * (0.5 + Math.random()),
|
|
33
|
+
signal
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async _delay(ms, signal) {
|
|
40
|
+
let timer, abort
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
await new Promise((resolve, reject) => {
|
|
44
|
+
abort = () => reject(new FetchError('Request aborted'))
|
|
45
|
+
timer = setTimeout(resolve, ms)
|
|
46
|
+
|
|
47
|
+
if (signal?.aborted) abort()
|
|
48
|
+
else signal?.addEventListener('abort', abort, { once: true })
|
|
49
|
+
})
|
|
50
|
+
} finally {
|
|
51
|
+
clearTimeout(timer)
|
|
52
|
+
signal?.removeEventListener('abort', abort)
|
|
53
|
+
}
|
|
51
54
|
}
|
|
52
55
|
}
|
package/bytea.d.ts
CHANGED
package/bytea.js
ADDED
package/callback.d.ts
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { Transaction } from './transaction'
|
|
2
|
-
import { Block } from './block'
|
|
1
|
+
import { Transaction } from './transaction.js'
|
|
2
|
+
import { Block } from './block.js'
|
|
3
|
+
import Message from './message.js'
|
|
4
|
+
import { SocketCloseEvent } from './types.js'
|
|
3
5
|
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
export
|
|
7
|
-
|
|
6
|
+
export type SuccessCallback = (event: Event) => void
|
|
7
|
+
|
|
8
|
+
export type ErrorCallback = (event: Error | Event) => void
|
|
9
|
+
|
|
10
|
+
export type CloseCallback = (event: SocketCloseEvent) => void
|
|
11
|
+
|
|
12
|
+
export type ListenCallback = (
|
|
8
13
|
block: Block | null,
|
|
9
14
|
tx: Transaction | null,
|
|
10
|
-
msg:
|
|
15
|
+
msg: Message | null
|
|
11
16
|
) => void
|