@tchain/tcabci-read-js-client 2.8.2 → 2.8.4
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.d.ts +3 -0
- package/client.js +62 -6
- package/constants.js +1 -1
- package/message.d.ts +1 -0
- package/message.js +18 -1
- package/package.json +2 -1
- package/passKey.d.ts +4 -0
- package/passKey.js +48 -0
- package/websocketOptions.js +1 -1
package/client.d.ts
CHANGED
|
@@ -21,6 +21,8 @@ export type {
|
|
|
21
21
|
WebSocketConstructor
|
|
22
22
|
} from './types.js'
|
|
23
23
|
|
|
24
|
+
export type Randomizer = (data: Uint8Array) => Uint8Array
|
|
25
|
+
|
|
24
26
|
export interface TxQuery {
|
|
25
27
|
recipientAddrs?: readonly string[]
|
|
26
28
|
senderAddrs?: readonly string[]
|
|
@@ -75,6 +77,7 @@ export default class TCaBCIClient {
|
|
|
75
77
|
|
|
76
78
|
SetIsWeb(isWeb: boolean): this
|
|
77
79
|
SetDebug(debug: boolean): this
|
|
80
|
+
SetRandomizer(randomizer: Randomizer): this
|
|
78
81
|
|
|
79
82
|
SetSuccessCallback(cb: SuccessCallback | null): this
|
|
80
83
|
|
package/client.js
CHANGED
|
@@ -31,6 +31,7 @@ import { Options } from './websocketOptions.js'
|
|
|
31
31
|
import { TWebSocket } from './websocket.js'
|
|
32
32
|
import { Transaction, TX_TYPE_LIST } from './transaction.js'
|
|
33
33
|
import { Block } from './block.js'
|
|
34
|
+
import { passKey } from './passKey.js'
|
|
34
35
|
import { HTTP } from './http.js'
|
|
35
36
|
|
|
36
37
|
/**
|
|
@@ -66,6 +67,8 @@ export default class TCaBCIClient {
|
|
|
66
67
|
_subscribed = false
|
|
67
68
|
_subscribedAddresses = []
|
|
68
69
|
_SubscribedSignedData = {}
|
|
70
|
+
_clientInstanceID = null
|
|
71
|
+
_instanceIDPromise = null
|
|
69
72
|
_listenRequested = false
|
|
70
73
|
_connected = false
|
|
71
74
|
_chainName = 'transferchain'
|
|
@@ -98,6 +101,11 @@ export default class TCaBCIClient {
|
|
|
98
101
|
_ws
|
|
99
102
|
_readNodeAddress = READ_NODE_ADDRESS
|
|
100
103
|
_readNodeWSAddress = READ_NODE_WS_ADDRESS
|
|
104
|
+
/**
|
|
105
|
+
* @type {(Uint8Array) => Uint8Array}
|
|
106
|
+
* @private
|
|
107
|
+
*/
|
|
108
|
+
_randomizer = null
|
|
101
109
|
|
|
102
110
|
/**
|
|
103
111
|
* Create an HTTP/WebSocket client. Start initiates connection setup;
|
|
@@ -172,6 +180,15 @@ export default class TCaBCIClient {
|
|
|
172
180
|
return this
|
|
173
181
|
}
|
|
174
182
|
|
|
183
|
+
/**
|
|
184
|
+
* @param {(Uint8Array) => Uint8Array} randomizer
|
|
185
|
+
* @return {TCaBCIClient}
|
|
186
|
+
*/
|
|
187
|
+
SetRandomizer(randomizer) {
|
|
188
|
+
this._randomizer = randomizer
|
|
189
|
+
return this
|
|
190
|
+
}
|
|
191
|
+
|
|
175
192
|
/**
|
|
176
193
|
* @param {SuccessCallback} cb
|
|
177
194
|
* @return {TCaBCIClient}
|
|
@@ -311,7 +328,11 @@ export default class TCaBCIClient {
|
|
|
311
328
|
if (!this.IsConnected()) throw new Error(NOT_CONNECTED)
|
|
312
329
|
|
|
313
330
|
this._ws.send(
|
|
314
|
-
new Message({
|
|
331
|
+
new Message({
|
|
332
|
+
isWeb: this._isWeb,
|
|
333
|
+
type: LISTENMessage,
|
|
334
|
+
clientInstanceID: this._clientInstanceID ?? undefined
|
|
335
|
+
}).ToJSON()
|
|
315
336
|
)
|
|
316
337
|
this._listenRequested = true
|
|
317
338
|
|
|
@@ -322,6 +343,7 @@ export default class TCaBCIClient {
|
|
|
322
343
|
* Subscribe to at most 251 unique addresses, each at most 2048 characters.
|
|
323
344
|
* The combined active address list is bounded. Proof maps are copied;
|
|
324
345
|
* caller objects remain mutable. ACK updates IsSubscribed asynchronously.
|
|
346
|
+
* A random instance ID is retained by this client across reconnects.
|
|
325
347
|
* @param {readonly string[]} addresses
|
|
326
348
|
* @param {Readonly<Record<string, string>>} signedData
|
|
327
349
|
* @param {readonly string[]|null} [txTypes=null]
|
|
@@ -329,7 +351,7 @@ export default class TCaBCIClient {
|
|
|
329
351
|
*/
|
|
330
352
|
Subscribe(addresses, signedData, txTypes = null) {
|
|
331
353
|
const requested = validateAddresses(addresses),
|
|
332
|
-
|
|
354
|
+
suppliedProof = validateSignedData(signedData)
|
|
333
355
|
|
|
334
356
|
if (
|
|
335
357
|
txTypes !== null &&
|
|
@@ -342,13 +364,20 @@ export default class TCaBCIClient {
|
|
|
342
364
|
if (!this.IsConnected()) throw new Error(NOT_CONNECTED)
|
|
343
365
|
|
|
344
366
|
const addrs = validateAddresses([
|
|
345
|
-
|
|
346
|
-
|
|
367
|
+
...new Set([...this._subscribedAddresses, ...requested])
|
|
368
|
+
]),
|
|
369
|
+
proof = Object.fromEntries(
|
|
370
|
+
addrs.map((address) => [
|
|
371
|
+
address,
|
|
372
|
+
suppliedProof[address] ?? this._SubscribedSignedData[address]
|
|
373
|
+
])
|
|
374
|
+
)
|
|
347
375
|
|
|
348
376
|
this._ws.send(
|
|
349
377
|
new Message({
|
|
350
378
|
isWeb: true,
|
|
351
379
|
type: SUBSCRIBEMessage,
|
|
380
|
+
clientInstanceID: this._clientInstanceID,
|
|
352
381
|
addrs: addrs,
|
|
353
382
|
signedData: proof,
|
|
354
383
|
txTypes
|
|
@@ -870,7 +899,13 @@ export default class TCaBCIClient {
|
|
|
870
899
|
this._callCloseCallback(e)
|
|
871
900
|
})
|
|
872
901
|
|
|
873
|
-
const start =
|
|
902
|
+
const start = (async () => {
|
|
903
|
+
await this._prepareInstanceID(this._randomizer)
|
|
904
|
+
|
|
905
|
+
if (!current()) return ws
|
|
906
|
+
|
|
907
|
+
return ws.connect()
|
|
908
|
+
})()
|
|
874
909
|
|
|
875
910
|
this._startPromise = start
|
|
876
911
|
|
|
@@ -885,6 +920,27 @@ export default class TCaBCIClient {
|
|
|
885
920
|
}
|
|
886
921
|
}
|
|
887
922
|
|
|
923
|
+
_prepareInstanceID(randomizer = null) {
|
|
924
|
+
if (this._clientInstanceID !== null)
|
|
925
|
+
return Promise.resolve(this._clientInstanceID)
|
|
926
|
+
|
|
927
|
+
if (this._instanceIDPromise) return this._instanceIDPromise
|
|
928
|
+
|
|
929
|
+
const pending = passKey(randomizer)
|
|
930
|
+
.then((identity) => {
|
|
931
|
+
this._clientInstanceID ??= identity
|
|
932
|
+
|
|
933
|
+
return this._clientInstanceID
|
|
934
|
+
})
|
|
935
|
+
.finally(() => {
|
|
936
|
+
if (this._instanceIDPromise === pending) this._instanceIDPromise = null
|
|
937
|
+
})
|
|
938
|
+
|
|
939
|
+
this._instanceIDPromise = pending
|
|
940
|
+
|
|
941
|
+
return pending
|
|
942
|
+
}
|
|
943
|
+
|
|
888
944
|
async _disconnect(code = 1000) {
|
|
889
945
|
code = closeCode(code)
|
|
890
946
|
|
|
@@ -1030,7 +1086,7 @@ export default class TCaBCIClient {
|
|
|
1030
1086
|
break
|
|
1031
1087
|
case Subscription:
|
|
1032
1088
|
if (_msg.State === OK) {
|
|
1033
|
-
this._setSubscribed(
|
|
1089
|
+
this._setSubscribed(this._subscribedAddresses.length > 0)
|
|
1034
1090
|
} else {
|
|
1035
1091
|
this._clearSubscription()
|
|
1036
1092
|
}
|
package/constants.js
CHANGED
package/message.d.ts
CHANGED
package/message.js
CHANGED
|
@@ -26,6 +26,7 @@ export default class Message {
|
|
|
26
26
|
_txTypes
|
|
27
27
|
_data
|
|
28
28
|
_state
|
|
29
|
+
_clientInstanceID
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
32
|
* @param {boolean} isWeb
|
|
@@ -34,6 +35,7 @@ export default class Message {
|
|
|
34
35
|
* @param {?Object} signedData
|
|
35
36
|
* @param {?Array<string>} txTypes
|
|
36
37
|
* @param {*} data
|
|
38
|
+
* @param {string|undefined} clientInstanceID Optional 64- or 128-character lowercase hex instance ID.
|
|
37
39
|
*/
|
|
38
40
|
constructor({
|
|
39
41
|
isWeb,
|
|
@@ -41,10 +43,12 @@ export default class Message {
|
|
|
41
43
|
addrs,
|
|
42
44
|
signedData = null,
|
|
43
45
|
txTypes = null,
|
|
44
|
-
data = null
|
|
46
|
+
data = null,
|
|
47
|
+
clientInstanceID
|
|
45
48
|
} = {}) {
|
|
46
49
|
this._is_web = isWeb
|
|
47
50
|
this._type = type
|
|
51
|
+
this._clientInstanceID = clientInstanceID
|
|
48
52
|
|
|
49
53
|
if (addrs && Array.isArray(addrs))
|
|
50
54
|
this._addresses = Object.freeze(addresses(addrs))
|
|
@@ -128,6 +132,9 @@ export default class Message {
|
|
|
128
132
|
|
|
129
133
|
if (typeof obj.state !== 'undefined') msg._state = obj.state
|
|
130
134
|
|
|
135
|
+
if (typeof obj.client_instance_id !== 'undefined')
|
|
136
|
+
msg._clientInstanceID = obj.client_instance_id
|
|
137
|
+
|
|
131
138
|
msg._validate()
|
|
132
139
|
|
|
133
140
|
return { message: Object.freeze(msg), error: null }
|
|
@@ -142,6 +149,9 @@ export default class Message {
|
|
|
142
149
|
return JSON.stringify({
|
|
143
150
|
is_web: this._is_web,
|
|
144
151
|
type: this._type,
|
|
152
|
+
...(typeof this._clientInstanceID !== 'undefined'
|
|
153
|
+
? { client_instance_id: this._clientInstanceID }
|
|
154
|
+
: {}),
|
|
145
155
|
...(this._addresses ? { addresses: this._addresses } : {}),
|
|
146
156
|
...(this._signedAddresses
|
|
147
157
|
? { signed_addresses: this._signedAddresses }
|
|
@@ -153,6 +163,13 @@ export default class Message {
|
|
|
153
163
|
}
|
|
154
164
|
|
|
155
165
|
_validate() {
|
|
166
|
+
if (
|
|
167
|
+
typeof this._clientInstanceID !== 'undefined' &&
|
|
168
|
+
(typeof this._clientInstanceID !== 'string' ||
|
|
169
|
+
!/^(?:[0-9a-f]{64}|[0-9a-f]{128})$/.test(this._clientInstanceID))
|
|
170
|
+
)
|
|
171
|
+
throw new Error(INVALID_ARGUMENT_WITH_CS('client_instance_id'))
|
|
172
|
+
|
|
156
173
|
if (
|
|
157
174
|
typeof this._is_web !== 'undefined' &&
|
|
158
175
|
typeof this._is_web !== 'boolean'
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tchain/tcabci-read-js-client",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.4",
|
|
4
4
|
"description": "TransferChain Fastest Read Network WebSocket Client",
|
|
5
5
|
"homepage": "https://github.com/TransferChain/tcabci-read-js-client#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"client.js",
|
|
22
22
|
"errors.js",
|
|
23
23
|
"message.js",
|
|
24
|
+
"passKey.js",
|
|
24
25
|
"transaction.js",
|
|
25
26
|
"util.js",
|
|
26
27
|
"websocket.js",
|
package/passKey.d.ts
ADDED
package/passKey.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SHA-512 of four concatenated lowercase UUID-v4 ASCII identifiers, matching
|
|
3
|
+
* SDK passKey defaults. Uses WebCrypto without a dependency on the SDK.
|
|
4
|
+
* Owned entropy, ASCII and digest bytes are cleared on success and failure.
|
|
5
|
+
* Immutable returned hex and runtime/native copies cannot be explicitly erased.
|
|
6
|
+
* @param {(Uint8Array) => Uint8Array} randomizer
|
|
7
|
+
* @returns {Promise<string>} 128 lowercase hex characters; not an auth credential.
|
|
8
|
+
*/
|
|
9
|
+
export async function passKey(randomizer = null) {
|
|
10
|
+
const entropy = new Uint8Array(64),
|
|
11
|
+
ascii = new Uint8Array(144)
|
|
12
|
+
let digest
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
if (!randomizer) globalThis.crypto.getRandomValues(entropy)
|
|
16
|
+
else randomizer(entropy)
|
|
17
|
+
|
|
18
|
+
let offset = 0
|
|
19
|
+
|
|
20
|
+
for (let uuid = 0; uuid < 4; uuid++) {
|
|
21
|
+
const start = uuid * 16
|
|
22
|
+
|
|
23
|
+
entropy[start + 6] = (entropy[start + 6] & 15) | 64
|
|
24
|
+
entropy[start + 8] = (entropy[start + 8] & 63) | 128
|
|
25
|
+
|
|
26
|
+
for (let index = 0; index < 16; index++) {
|
|
27
|
+
if ([4, 6, 8, 10].includes(index)) ascii[offset++] = 45
|
|
28
|
+
|
|
29
|
+
const value = entropy[start + index]
|
|
30
|
+
|
|
31
|
+
for (const nibble of [value >>> 4, value & 15])
|
|
32
|
+
ascii[offset++] = nibble < 10 ? 48 + nibble : 87 + nibble
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
digest = new Uint8Array(
|
|
37
|
+
await globalThis.crypto.subtle.digest('SHA-512', ascii)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return Array.from(digest, (byte) =>
|
|
41
|
+
byte.toString(16).padStart(2, '0')
|
|
42
|
+
).join('')
|
|
43
|
+
} finally {
|
|
44
|
+
entropy.fill(0)
|
|
45
|
+
ascii.fill(0)
|
|
46
|
+
digest?.fill(0)
|
|
47
|
+
}
|
|
48
|
+
}
|
package/websocketOptions.js
CHANGED
|
@@ -342,7 +342,7 @@ export class Options {
|
|
|
342
342
|
constructor(url, protocols) {
|
|
343
343
|
const authority = options._authority
|
|
344
344
|
|
|
345
|
-
if (requiresAuthority && !authority
|
|
345
|
+
if (requiresAuthority && !authority)
|
|
346
346
|
throw new Error('Websocket authority configuration released')
|
|
347
347
|
|
|
348
348
|
super(url, protocols, {
|