@pezkuwi/rpc-provider 16.5.17 → 16.5.19

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.
Files changed (210) hide show
  1. package/build/LICENSE +201 -0
  2. package/build/README.md +68 -0
  3. package/build/bizinikiwi-connect/Health.js +259 -0
  4. package/build/bizinikiwi-connect/index.js +319 -0
  5. package/build/bundle.js +5 -0
  6. package/build/cjs/bizinikiwi-connect/Health.d.ts +7 -0
  7. package/build/cjs/bizinikiwi-connect/Health.js +264 -0
  8. package/build/cjs/bizinikiwi-connect/index.d.ts +22 -0
  9. package/build/cjs/bizinikiwi-connect/index.js +323 -0
  10. package/build/cjs/bizinikiwi-connect/types.d.ts +12 -0
  11. package/build/cjs/bizinikiwi-connect/types.js +2 -0
  12. package/build/cjs/bundle.d.ts +5 -0
  13. package/build/cjs/bundle.js +14 -0
  14. package/build/cjs/coder/error.js +53 -0
  15. package/build/cjs/coder/index.js +63 -0
  16. package/build/cjs/defaults.js +8 -0
  17. package/build/cjs/http/index.js +196 -0
  18. package/build/cjs/http/types.js +2 -0
  19. package/build/cjs/index.js +5 -0
  20. package/build/cjs/lru.js +150 -0
  21. package/build/cjs/mock/index.js +196 -0
  22. package/build/cjs/mock/mockHttp.js +17 -0
  23. package/build/cjs/mock/mockWs.js +47 -0
  24. package/build/cjs/mock/types.js +2 -0
  25. package/build/cjs/packageDetect.d.ts +1 -0
  26. package/build/cjs/packageInfo.js +4 -0
  27. package/build/cjs/types.js +2 -0
  28. package/build/cjs/ws/errors.js +41 -0
  29. package/build/cjs/ws/index.js +529 -0
  30. package/build/coder/error.d.ts +29 -0
  31. package/build/coder/error.js +50 -0
  32. package/build/coder/index.d.ts +8 -0
  33. package/build/coder/index.js +58 -0
  34. package/build/defaults.d.ts +5 -0
  35. package/build/defaults.js +6 -0
  36. package/build/http/index.d.ts +81 -0
  37. package/build/http/index.js +191 -0
  38. package/build/http/types.d.ts +7 -0
  39. package/build/http/types.js +1 -0
  40. package/build/index.d.ts +2 -0
  41. package/build/index.js +2 -0
  42. package/build/lru.d.ts +15 -0
  43. package/build/lru.js +146 -0
  44. package/build/mock/index.d.ts +35 -0
  45. package/build/mock/index.js +191 -0
  46. package/build/mock/mockHttp.d.ts +9 -0
  47. package/build/mock/mockHttp.js +12 -0
  48. package/build/mock/mockWs.d.ts +26 -0
  49. package/build/mock/mockWs.js +43 -0
  50. package/build/mock/types.d.ts +23 -0
  51. package/build/mock/types.js +1 -0
  52. package/build/package.json +344 -0
  53. package/build/packageDetect.d.ts +1 -0
  54. package/build/packageDetect.js +4 -0
  55. package/build/packageInfo.d.ts +6 -0
  56. package/build/packageInfo.js +1 -0
  57. package/build/types.d.ts +85 -0
  58. package/build/types.js +1 -0
  59. package/build/ws/errors.d.ts +1 -0
  60. package/build/ws/errors.js +38 -0
  61. package/build/ws/index.d.ts +121 -0
  62. package/build/ws/index.js +524 -0
  63. package/build-deno/README.md +66 -0
  64. package/build-deno/bizinikiwi-connect/Health.ts +323 -0
  65. package/build-deno/bizinikiwi-connect/index.ts +417 -0
  66. package/build-deno/bizinikiwi-connect/types.ts +14 -0
  67. package/build-deno/bundle.ts +6 -0
  68. package/build-deno/coder/error.ts +64 -0
  69. package/build-deno/coder/index.ts +86 -0
  70. package/build-deno/defaults.ts +8 -0
  71. package/build-deno/http/index.ts +236 -0
  72. package/build-deno/http/types.ts +9 -0
  73. package/build-deno/index.ts +4 -0
  74. package/build-deno/lru.ts +189 -0
  75. package/build-deno/mock/index.ts +257 -0
  76. package/build-deno/mock/mockHttp.ts +33 -0
  77. package/build-deno/mock/mockWs.ts +87 -0
  78. package/build-deno/mock/types.ts +34 -0
  79. package/build-deno/mod.ts +2 -0
  80. package/build-deno/packageDetect.ts +8 -0
  81. package/build-deno/packageInfo.ts +3 -0
  82. package/build-deno/types.ts +99 -0
  83. package/build-deno/ws/errors.ts +38 -0
  84. package/build-deno/ws/index.ts +650 -0
  85. package/build-tsc-cjs/packageDetect.js +6 -0
  86. package/{cjs → build-tsc-cjs}/packageInfo.js +1 -1
  87. package/{packageInfo.js → build-tsc-esm/packageInfo.js} +1 -1
  88. package/package.json +16 -16
  89. package/src/bizinikiwi-connect/Health.ts +325 -0
  90. package/src/bizinikiwi-connect/index.spec.ts +675 -0
  91. package/src/bizinikiwi-connect/index.ts +427 -0
  92. package/src/bizinikiwi-connect/types.ts +16 -0
  93. package/src/bundle.ts +8 -0
  94. package/src/coder/decodeResponse.spec.ts +70 -0
  95. package/src/coder/encodeJson.spec.ts +20 -0
  96. package/src/coder/encodeObject.spec.ts +25 -0
  97. package/src/coder/error.spec.ts +111 -0
  98. package/src/coder/error.ts +66 -0
  99. package/src/coder/index.ts +88 -0
  100. package/src/defaults.ts +10 -0
  101. package/src/http/index.spec.ts +72 -0
  102. package/src/http/index.ts +238 -0
  103. package/src/http/send.spec.ts +61 -0
  104. package/src/http/types.ts +11 -0
  105. package/src/index.ts +6 -0
  106. package/src/lru.spec.ts +74 -0
  107. package/src/lru.ts +197 -0
  108. package/src/mock/index.ts +259 -0
  109. package/src/mock/mockHttp.ts +35 -0
  110. package/src/mock/mockWs.ts +92 -0
  111. package/src/mock/on.spec.ts +43 -0
  112. package/src/mock/send.spec.ts +38 -0
  113. package/src/mock/subscribe.spec.ts +81 -0
  114. package/src/mock/types.ts +36 -0
  115. package/src/mock/unsubscribe.spec.ts +57 -0
  116. package/src/mod.ts +4 -0
  117. package/src/packageDetect.ts +12 -0
  118. package/src/packageInfo.ts +6 -0
  119. package/src/types.ts +101 -0
  120. package/src/ws/connect.spec.ts +167 -0
  121. package/src/ws/errors.ts +41 -0
  122. package/src/ws/index.spec.ts +97 -0
  123. package/src/ws/index.ts +652 -0
  124. package/src/ws/send.spec.ts +126 -0
  125. package/src/ws/state.spec.ts +20 -0
  126. package/src/ws/subscribe.spec.ts +68 -0
  127. package/src/ws/unsubscribe.spec.ts +100 -0
  128. package/tsconfig.build.json +17 -0
  129. package/tsconfig.build.tsbuildinfo +1 -0
  130. package/tsconfig.spec.json +18 -0
  131. package/tsconfig.spec.tsbuildinfo +1 -0
  132. /package/{cjs → build}/bizinikiwi-connect/Health.d.ts +0 -0
  133. /package/{cjs → build}/bizinikiwi-connect/index.d.ts +0 -0
  134. /package/{cjs → build}/bizinikiwi-connect/types.d.ts +0 -0
  135. /package/{packageDetect.d.ts → build/bizinikiwi-connect/types.js} +0 -0
  136. /package/{cjs → build}/bundle.d.ts +0 -0
  137. /package/{coder → build/cjs/coder}/error.d.ts +0 -0
  138. /package/{coder → build/cjs/coder}/index.d.ts +0 -0
  139. /package/{defaults.d.ts → build/cjs/defaults.d.ts} +0 -0
  140. /package/{http → build/cjs/http}/index.d.ts +0 -0
  141. /package/{http → build/cjs/http}/types.d.ts +0 -0
  142. /package/{index.d.ts → build/cjs/index.d.ts} +0 -0
  143. /package/{lru.d.ts → build/cjs/lru.d.ts} +0 -0
  144. /package/{mock → build/cjs/mock}/index.d.ts +0 -0
  145. /package/{mock → build/cjs/mock}/mockHttp.d.ts +0 -0
  146. /package/{mock → build/cjs/mock}/mockWs.d.ts +0 -0
  147. /package/{mock → build/cjs/mock}/types.d.ts +0 -0
  148. /package/{cjs → build/cjs}/package.json +0 -0
  149. /package/{cjs → build/cjs}/packageDetect.js +0 -0
  150. /package/{packageInfo.d.ts → build/cjs/packageInfo.d.ts} +0 -0
  151. /package/{types.d.ts → build/cjs/types.d.ts} +0 -0
  152. /package/{ws → build/cjs/ws}/errors.d.ts +0 -0
  153. /package/{ws → build/cjs/ws}/index.d.ts +0 -0
  154. /package/{bizinikiwi-connect → build-tsc/bizinikiwi-connect}/Health.d.ts +0 -0
  155. /package/{bizinikiwi-connect → build-tsc/bizinikiwi-connect}/index.d.ts +0 -0
  156. /package/{bizinikiwi-connect → build-tsc/bizinikiwi-connect}/types.d.ts +0 -0
  157. /package/{bundle.d.ts → build-tsc/bundle.d.ts} +0 -0
  158. /package/{cjs → build-tsc}/coder/error.d.ts +0 -0
  159. /package/{cjs → build-tsc}/coder/index.d.ts +0 -0
  160. /package/{cjs → build-tsc}/defaults.d.ts +0 -0
  161. /package/{cjs → build-tsc}/http/index.d.ts +0 -0
  162. /package/{cjs → build-tsc}/http/types.d.ts +0 -0
  163. /package/{cjs → build-tsc}/index.d.ts +0 -0
  164. /package/{cjs → build-tsc}/lru.d.ts +0 -0
  165. /package/{cjs → build-tsc}/mock/index.d.ts +0 -0
  166. /package/{cjs → build-tsc}/mock/mockHttp.d.ts +0 -0
  167. /package/{cjs → build-tsc}/mock/mockWs.d.ts +0 -0
  168. /package/{cjs → build-tsc}/mock/types.d.ts +0 -0
  169. /package/{cjs → build-tsc}/packageDetect.d.ts +0 -0
  170. /package/{cjs → build-tsc}/packageInfo.d.ts +0 -0
  171. /package/{cjs → build-tsc}/types.d.ts +0 -0
  172. /package/{cjs → build-tsc}/ws/errors.d.ts +0 -0
  173. /package/{cjs → build-tsc}/ws/index.d.ts +0 -0
  174. /package/{cjs → build-tsc-cjs}/bizinikiwi-connect/Health.js +0 -0
  175. /package/{cjs → build-tsc-cjs}/bizinikiwi-connect/index.js +0 -0
  176. /package/{cjs → build-tsc-cjs}/bizinikiwi-connect/types.js +0 -0
  177. /package/{cjs → build-tsc-cjs}/bundle.js +0 -0
  178. /package/{cjs → build-tsc-cjs}/coder/error.js +0 -0
  179. /package/{cjs → build-tsc-cjs}/coder/index.js +0 -0
  180. /package/{cjs → build-tsc-cjs}/defaults.js +0 -0
  181. /package/{cjs → build-tsc-cjs}/http/index.js +0 -0
  182. /package/{cjs → build-tsc-cjs}/http/types.js +0 -0
  183. /package/{cjs → build-tsc-cjs}/index.js +0 -0
  184. /package/{cjs → build-tsc-cjs}/lru.js +0 -0
  185. /package/{cjs → build-tsc-cjs}/mock/index.js +0 -0
  186. /package/{cjs → build-tsc-cjs}/mock/mockHttp.js +0 -0
  187. /package/{cjs → build-tsc-cjs}/mock/mockWs.js +0 -0
  188. /package/{cjs → build-tsc-cjs}/mock/types.js +0 -0
  189. /package/{cjs → build-tsc-cjs}/types.js +0 -0
  190. /package/{cjs → build-tsc-cjs}/ws/errors.js +0 -0
  191. /package/{cjs → build-tsc-cjs}/ws/index.js +0 -0
  192. /package/{bizinikiwi-connect → build-tsc-esm/bizinikiwi-connect}/Health.js +0 -0
  193. /package/{bizinikiwi-connect → build-tsc-esm/bizinikiwi-connect}/index.js +0 -0
  194. /package/{bizinikiwi-connect → build-tsc-esm/bizinikiwi-connect}/types.js +0 -0
  195. /package/{bundle.js → build-tsc-esm/bundle.js} +0 -0
  196. /package/{coder → build-tsc-esm/coder}/error.js +0 -0
  197. /package/{coder → build-tsc-esm/coder}/index.js +0 -0
  198. /package/{defaults.js → build-tsc-esm/defaults.js} +0 -0
  199. /package/{http → build-tsc-esm/http}/index.js +0 -0
  200. /package/{http → build-tsc-esm/http}/types.js +0 -0
  201. /package/{index.js → build-tsc-esm/index.js} +0 -0
  202. /package/{lru.js → build-tsc-esm/lru.js} +0 -0
  203. /package/{mock → build-tsc-esm/mock}/index.js +0 -0
  204. /package/{mock → build-tsc-esm/mock}/mockHttp.js +0 -0
  205. /package/{mock → build-tsc-esm/mock}/mockWs.js +0 -0
  206. /package/{mock → build-tsc-esm/mock}/types.js +0 -0
  207. /package/{packageDetect.js → build-tsc-esm/packageDetect.js} +0 -0
  208. /package/{types.js → build-tsc-esm/types.js} +0 -0
  209. /package/{ws → build-tsc-esm/ws}/errors.js +0 -0
  210. /package/{ws → build-tsc-esm/ws}/index.js +0 -0
@@ -0,0 +1,650 @@
1
+
2
+ import type { Class } from 'https://deno.land/x/pezkuwi/util/types.ts';
3
+ import type RpcError from '../coder/error.ts';
4
+ import type { EndpointStats, JsonRpcResponse, ProviderInterface, ProviderInterfaceCallback, ProviderInterfaceEmitCb, ProviderInterfaceEmitted, ProviderStats } from '../types.ts';
5
+
6
+ import { EventEmitter } from 'https://esm.sh/eventemitter3@5.0.1';
7
+
8
+ import { isChildClass, isNull, isUndefined, logger, noop, objectSpread, stringify } from 'https://deno.land/x/pezkuwi/util/mod.ts';
9
+ import { xglobal } from 'https://deno.land/x/pezkuwi/x-global/mod.ts';
10
+ import { WebSocket } from 'https://deno.land/x/pezkuwi/x-ws/mod.ts';
11
+
12
+ import { RpcCoder } from '../coder/index.ts';
13
+ import defaults from '../defaults.ts';
14
+ import { DEFAULT_CAPACITY, DEFAULT_TTL, LRUCache } from '../lru.ts';
15
+ import { getWSErrorString } from './errors.ts';
16
+
17
+ interface SubscriptionHandler {
18
+ callback: ProviderInterfaceCallback;
19
+ type: string;
20
+ }
21
+
22
+ interface WsStateAwaiting {
23
+ callback: ProviderInterfaceCallback;
24
+ method: string;
25
+ params: unknown[];
26
+ start: number;
27
+ subscription?: SubscriptionHandler | undefined;
28
+ }
29
+
30
+ interface WsStateSubscription extends SubscriptionHandler {
31
+ method: string;
32
+ params: unknown[];
33
+ }
34
+
35
+ const ALIASES: Record<string, string> = {
36
+ chain_finalisedHead: 'chain_finalizedHead',
37
+ chain_subscribeFinalisedHeads: 'chain_subscribeFinalizedHeads',
38
+ chain_unsubscribeFinalisedHeads: 'chain_unsubscribeFinalizedHeads'
39
+ };
40
+
41
+ const RETRY_DELAY = 2_500;
42
+
43
+ const DEFAULT_TIMEOUT_MS = 60 * 1000;
44
+ const TIMEOUT_INTERVAL = 5_000;
45
+
46
+ const l = logger('api-ws');
47
+
48
+ /** @internal Clears a Record<*> of all keys, optionally with all callback on clear */
49
+ function eraseRecord<T> (record: Record<string, T>, cb?: (item: T) => void): void {
50
+ Object.keys(record).forEach((key): void => {
51
+ if (cb) {
52
+ cb(record[key]);
53
+ }
54
+
55
+ delete record[key];
56
+ });
57
+ }
58
+
59
+ /** @internal Creates a default/empty stats object */
60
+ function defaultEndpointStats (): EndpointStats {
61
+ return { bytesRecv: 0, bytesSent: 0, cached: 0, errors: 0, requests: 0, subscriptions: 0, timeout: 0 };
62
+ }
63
+
64
+ /**
65
+ * # @pezkuwi/rpc-provider/ws
66
+ *
67
+ * @name WsProvider
68
+ *
69
+ * @description The WebSocket Provider allows sending requests using WebSocket to a WebSocket RPC server TCP port. Unlike the [[HttpProvider]], it does support subscriptions and allows listening to events such as new blocks or balance changes.
70
+ *
71
+ * @example
72
+ * <BR>
73
+ *
74
+ * ```javascript
75
+ * import Api from 'https://deno.land/x/pezkuwi/api/promise/index.ts';
76
+ * import { WsProvider } from 'https://deno.land/x/pezkuwi/rpc-provider/ws/index.ts';
77
+ *
78
+ * const provider = new WsProvider('ws://127.0.0.1:9944');
79
+ * const api = new Api(provider);
80
+ * ```
81
+ *
82
+ * @see [[HttpProvider]]
83
+ */
84
+ export class WsProvider implements ProviderInterface {
85
+ readonly #callCache: LRUCache;
86
+ readonly #coder: RpcCoder;
87
+ readonly #endpoints: string[];
88
+ readonly #headers: Record<string, string>;
89
+ readonly #eventemitter: EventEmitter;
90
+ readonly #handlers: Record<string, WsStateAwaiting> = {};
91
+ readonly #isReadyPromise: Promise<WsProvider>;
92
+ readonly #stats: ProviderStats;
93
+ readonly #waitingForId: Record<string, JsonRpcResponse<unknown>> = {};
94
+ readonly #cacheCapacity: number;
95
+ readonly #ttl: number | null | undefined;
96
+
97
+ #autoConnectMs: number;
98
+ #endpointIndex: number;
99
+ #endpointStats: EndpointStats;
100
+ #isConnected = false;
101
+ #subscriptions: Record<string, WsStateSubscription> = {};
102
+ #timeoutId?: ReturnType<typeof setInterval> | null = null;
103
+ #websocket: WebSocket | null;
104
+ #timeout: number;
105
+
106
+ /**
107
+ * @param {string | string[]} endpoint The endpoint url. Usually `ws://ip:9944` or `wss://ip:9944`, may provide an array of endpoint strings.
108
+ * @param {number | false} autoConnectMs Whether to connect automatically or not (default). Provided value is used as a delay between retries.
109
+ * @param {Record<string, string>} headers The headers provided to the underlying WebSocket
110
+ * @param {number} [timeout] Custom timeout value used per request . Defaults to `DEFAULT_TIMEOUT_MS`
111
+ * @param {number} [cacheCapacity] Custom size of the WsProvider LRUCache. Defaults to `DEFAULT_CAPACITY` (1024)
112
+ * @param {number} [cacheTtl] Custom TTL of the WsProvider LRUCache. Determines how long an object can live in the cache. Defaults to DEFAULT_TTL` (30000)
113
+ */
114
+ constructor (endpoint: string | string[] = defaults.WS_URL, autoConnectMs: number | false = RETRY_DELAY, headers: Record<string, string> = {}, timeout?: number, cacheCapacity?: number, cacheTtl?: number | null) {
115
+ const endpoints = Array.isArray(endpoint)
116
+ ? endpoint
117
+ : [endpoint];
118
+
119
+ if (endpoints.length === 0) {
120
+ throw new Error('WsProvider requires at least one Endpoint');
121
+ }
122
+
123
+ endpoints.forEach((endpoint) => {
124
+ if (!/^(wss|ws):\/\//.test(endpoint)) {
125
+ throw new Error(`Endpoint should start with 'ws://', received '${endpoint}'`);
126
+ }
127
+ });
128
+ const ttl = cacheTtl === undefined ? DEFAULT_TTL : cacheTtl;
129
+
130
+ this.#callCache = new LRUCache(cacheCapacity === 0 ? 0 : cacheCapacity || DEFAULT_CAPACITY, ttl);
131
+ this.#ttl = cacheTtl;
132
+ this.#cacheCapacity = cacheCapacity || DEFAULT_CAPACITY;
133
+ this.#eventemitter = new EventEmitter();
134
+ this.#autoConnectMs = autoConnectMs || 0;
135
+ this.#coder = new RpcCoder();
136
+ this.#endpointIndex = -1;
137
+ this.#endpoints = endpoints;
138
+ this.#headers = headers;
139
+ this.#websocket = null;
140
+ this.#stats = {
141
+ active: { requests: 0, subscriptions: 0 },
142
+ total: defaultEndpointStats()
143
+ };
144
+ this.#endpointStats = defaultEndpointStats();
145
+ this.#timeout = timeout || DEFAULT_TIMEOUT_MS;
146
+
147
+ if (autoConnectMs && autoConnectMs > 0) {
148
+ this.connectWithRetry().catch(noop);
149
+ }
150
+
151
+ this.#isReadyPromise = new Promise((resolve): void => {
152
+ this.#eventemitter.once('connected', (): void => {
153
+ resolve(this);
154
+ });
155
+ });
156
+ }
157
+
158
+ /**
159
+ * @summary `true` when this provider supports subscriptions
160
+ */
161
+ public get hasSubscriptions (): boolean {
162
+ return !!true;
163
+ }
164
+
165
+ /**
166
+ * @summary `true` when this provider supports clone()
167
+ */
168
+ public get isClonable (): boolean {
169
+ return !!true;
170
+ }
171
+
172
+ /**
173
+ * @summary Whether the node is connected or not.
174
+ * @return {boolean} true if connected
175
+ */
176
+ public get isConnected (): boolean {
177
+ return this.#isConnected;
178
+ }
179
+
180
+ /**
181
+ * @description Promise that resolves the first time we are connected and loaded
182
+ */
183
+ public get isReady (): Promise<WsProvider> {
184
+ return this.#isReadyPromise;
185
+ }
186
+
187
+ public get endpoint (): string {
188
+ return this.#endpoints[this.#endpointIndex];
189
+ }
190
+
191
+ /**
192
+ * @description Returns a clone of the object
193
+ */
194
+ public clone (): WsProvider {
195
+ return new WsProvider(this.#endpoints);
196
+ }
197
+
198
+ protected selectEndpointIndex (endpoints: string[]): number {
199
+ return (this.#endpointIndex + 1) % endpoints.length;
200
+ }
201
+
202
+ /**
203
+ * @summary Manually connect
204
+ * @description The [[WsProvider]] connects automatically by default, however if you decided otherwise, you may
205
+ * connect manually using this method.
206
+ */
207
+ // eslint-disable-next-line @typescript-eslint/require-await
208
+ public async connect (): Promise<void> {
209
+ if (this.#websocket) {
210
+ throw new Error('WebSocket is already connected');
211
+ }
212
+
213
+ try {
214
+ this.#endpointIndex = this.selectEndpointIndex(this.#endpoints);
215
+
216
+ // the as here is Deno-specific - not available on the globalThis
217
+ this.#websocket = typeof xglobal.WebSocket !== 'undefined' && isChildClass(xglobal.WebSocket as unknown as Class<WebSocket>, WebSocket)
218
+ ? new WebSocket(this.endpoint)
219
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
220
+ // @ts-ignore - WS may be an instance of ws, which supports options
221
+ : new WebSocket(this.endpoint, undefined, {
222
+ headers: this.#headers
223
+ });
224
+
225
+ if (this.#websocket) {
226
+ this.#websocket.onclose = this.#onSocketClose;
227
+ this.#websocket.onerror = this.#onSocketError;
228
+ this.#websocket.onmessage = this.#onSocketMessage;
229
+ this.#websocket.onopen = this.#onSocketOpen;
230
+ }
231
+
232
+ // timeout any handlers that have not had a response
233
+ this.#timeoutId = setInterval(() => this.#timeoutHandlers(), TIMEOUT_INTERVAL);
234
+ } catch (error) {
235
+ l.error(error);
236
+
237
+ this.#emit('error', error);
238
+
239
+ throw error;
240
+ }
241
+ }
242
+
243
+ /**
244
+ * @description Connect, never throwing an error, but rather forcing a retry
245
+ */
246
+ public async connectWithRetry (): Promise<void> {
247
+ if (this.#autoConnectMs > 0) {
248
+ try {
249
+ await this.connect();
250
+ } catch {
251
+ setTimeout((): void => {
252
+ this.connectWithRetry().catch(noop);
253
+ }, this.#autoConnectMs);
254
+ }
255
+ }
256
+ }
257
+
258
+ /**
259
+ * @description Manually disconnect from the connection, clearing auto-connect logic
260
+ */
261
+ // eslint-disable-next-line @typescript-eslint/require-await
262
+ public async disconnect (): Promise<void> {
263
+ // switch off autoConnect, we are in manual mode now
264
+ this.#autoConnectMs = 0;
265
+
266
+ try {
267
+ if (this.#websocket) {
268
+ // 1000 - Normal closure; the connection successfully completed
269
+ this.#websocket.close(1000);
270
+ }
271
+ } catch (error) {
272
+ l.error(error);
273
+
274
+ this.#emit('error', error);
275
+
276
+ throw error;
277
+ }
278
+ }
279
+
280
+ /**
281
+ * @description Returns the connection stats
282
+ */
283
+ public get stats (): ProviderStats {
284
+ return {
285
+ active: {
286
+ requests: Object.keys(this.#handlers).length,
287
+ subscriptions: Object.keys(this.#subscriptions).length
288
+ },
289
+ total: this.#stats.total
290
+ };
291
+ }
292
+
293
+ /**
294
+ * @description Returns the connection stats
295
+ */
296
+ public get ttl (): number | null | undefined {
297
+ return this.#ttl;
298
+ }
299
+
300
+ public get endpointStats (): EndpointStats {
301
+ return this.#endpointStats;
302
+ }
303
+
304
+ /**
305
+ * @summary Listens on events after having subscribed using the [[subscribe]] function.
306
+ * @param {ProviderInterfaceEmitted} type Event
307
+ * @param {ProviderInterfaceEmitCb} sub Callback
308
+ * @return unsubscribe function
309
+ */
310
+ public on (type: ProviderInterfaceEmitted, sub: ProviderInterfaceEmitCb): () => void {
311
+ this.#eventemitter.on(type, sub);
312
+
313
+ return (): void => {
314
+ this.#eventemitter.removeListener(type, sub);
315
+ };
316
+ }
317
+
318
+ /**
319
+ * @summary Send JSON data using WebSockets to configured HTTP Endpoint or queue.
320
+ * @param method The RPC methods to execute
321
+ * @param params Encoded parameters as applicable for the method
322
+ * @param subscription Subscription details (internally used)
323
+ */
324
+ public send <T = any> (method: string, params: unknown[], isCacheable?: boolean, subscription?: SubscriptionHandler): Promise<T> {
325
+ this.#endpointStats.requests++;
326
+ this.#stats.total.requests++;
327
+
328
+ const [id, body] = this.#coder.encodeJson(method, params);
329
+
330
+ if (this.#cacheCapacity === 0) {
331
+ return this.#send(id, body, method, params, subscription);
332
+ }
333
+
334
+ const cacheKey = isCacheable ? `${method}::${stringify(params)}` : '';
335
+ let resultPromise: Promise<T> | null = isCacheable
336
+ ? this.#callCache.get(cacheKey)
337
+ : null;
338
+
339
+ if (!resultPromise) {
340
+ resultPromise = this.#send(id, body, method, params, subscription);
341
+
342
+ if (isCacheable) {
343
+ this.#callCache.set(cacheKey, resultPromise);
344
+ }
345
+ } else {
346
+ this.#endpointStats.cached++;
347
+ this.#stats.total.cached++;
348
+ }
349
+
350
+ return resultPromise;
351
+ }
352
+
353
+ async #send <T> (id: number, body: string, method: string, params: unknown[], subscription?: SubscriptionHandler): Promise<T> {
354
+ return new Promise<T>((resolve, reject): void => {
355
+ try {
356
+ if (!this.isConnected || this.#websocket === null) {
357
+ throw new Error('WebSocket is not connected');
358
+ }
359
+
360
+ const callback = (error?: Error | null, result?: T): void => {
361
+ error
362
+ ? reject(error)
363
+ : resolve(result as T);
364
+ };
365
+
366
+ l.debug(() => ['calling', method, body]);
367
+
368
+ this.#handlers[id] = {
369
+ callback,
370
+ method,
371
+ params,
372
+ start: Date.now(),
373
+ subscription
374
+ };
375
+
376
+ const bytesSent = body.length;
377
+
378
+ this.#endpointStats.bytesSent += bytesSent;
379
+ this.#stats.total.bytesSent += bytesSent;
380
+
381
+ this.#websocket.send(body);
382
+ } catch (error) {
383
+ this.#endpointStats.errors++;
384
+ this.#stats.total.errors++;
385
+
386
+ const rpcError: RpcError = error as RpcError;
387
+ const failedRequest = `\nFailed WS Request: ${JSON.stringify({ method, params })}`;
388
+
389
+ // Provide WS Request alongside the error
390
+ rpcError.message = `${rpcError.message}${failedRequest}`;
391
+ reject(rpcError);
392
+ }
393
+ });
394
+ }
395
+
396
+ /**
397
+ * @name subscribe
398
+ * @summary Allows subscribing to a specific event.
399
+ *
400
+ * @example
401
+ * <BR>
402
+ *
403
+ * ```javascript
404
+ * const provider = new WsProvider('ws://127.0.0.1:9944');
405
+ * const rpc = new Rpc(provider);
406
+ *
407
+ * rpc.state.subscribeStorage([[storage.system.account, <Address>]], (_, values) => {
408
+ * console.log(values)
409
+ * }).then((subscriptionId) => {
410
+ * console.log('balance changes subscription id: ', subscriptionId)
411
+ * })
412
+ * ```
413
+ */
414
+ public subscribe (type: string, method: string, params: unknown[], callback: ProviderInterfaceCallback): Promise<number | string> {
415
+ this.#endpointStats.subscriptions++;
416
+ this.#stats.total.subscriptions++;
417
+
418
+ // subscriptions are not cached, LRU applies to .at(<blockHash>) only
419
+ return this.send<number | string>(method, params, false, { callback, type });
420
+ }
421
+
422
+ /**
423
+ * @summary Allows unsubscribing to subscriptions made with [[subscribe]].
424
+ */
425
+ public async unsubscribe (type: string, method: string, id: number | string): Promise<boolean> {
426
+ const subscription = `${type}::${id}`;
427
+
428
+ // FIXME This now could happen with re-subscriptions. The issue is that with a re-sub
429
+ // the assigned id now does not match what the API user originally received. It has
430
+ // a slight complication in solving - since we cannot rely on the send id, but rather
431
+ // need to find the actual subscription id to map it
432
+ if (isUndefined(this.#subscriptions[subscription])) {
433
+ l.debug(() => `Unable to find active subscription=${subscription}`);
434
+
435
+ return false;
436
+ }
437
+
438
+ delete this.#subscriptions[subscription];
439
+
440
+ try {
441
+ return this.isConnected && !isNull(this.#websocket)
442
+ ? this.send<boolean>(method, [id])
443
+ : true;
444
+ } catch {
445
+ return false;
446
+ }
447
+ }
448
+
449
+ #emit = (type: ProviderInterfaceEmitted, ...args: unknown[]): void => {
450
+ this.#eventemitter.emit(type, ...args);
451
+ };
452
+
453
+ #onSocketClose = (event: CloseEvent): void => {
454
+ const error = new Error(`disconnected from ${this.endpoint}: ${event.code}:: ${event.reason || getWSErrorString(event.code)}`);
455
+
456
+ if (this.#autoConnectMs > 0) {
457
+ l.error(error.message);
458
+ }
459
+
460
+ this.#isConnected = false;
461
+
462
+ if (this.#websocket) {
463
+ this.#websocket.onclose = null;
464
+ this.#websocket.onerror = null;
465
+ this.#websocket.onmessage = null;
466
+ this.#websocket.onopen = null;
467
+ this.#websocket = null;
468
+ }
469
+
470
+ if (this.#timeoutId) {
471
+ clearInterval(this.#timeoutId);
472
+ this.#timeoutId = null;
473
+ }
474
+
475
+ // reject all hanging requests
476
+ eraseRecord(this.#handlers, (h) => {
477
+ try {
478
+ h.callback(error, undefined);
479
+ } catch (err) {
480
+ // does not throw
481
+ l.error(err);
482
+ }
483
+ });
484
+ eraseRecord(this.#waitingForId);
485
+
486
+ // Reset stats for active endpoint
487
+ this.#endpointStats = defaultEndpointStats();
488
+
489
+ this.#emit('disconnected');
490
+
491
+ if (this.#autoConnectMs > 0) {
492
+ setTimeout((): void => {
493
+ this.connectWithRetry().catch(noop);
494
+ }, this.#autoConnectMs);
495
+ }
496
+ };
497
+
498
+ #onSocketError = (error: Event): void => {
499
+ l.debug(() => ['socket error', error]);
500
+ this.#emit('error', error);
501
+ };
502
+
503
+ #onSocketMessage = (message: MessageEvent<string>): void => {
504
+ l.debug(() => ['received', message.data]);
505
+
506
+ const bytesRecv = message.data.length;
507
+
508
+ this.#endpointStats.bytesRecv += bytesRecv;
509
+ this.#stats.total.bytesRecv += bytesRecv;
510
+
511
+ const response = JSON.parse(message.data) as JsonRpcResponse<string>;
512
+
513
+ return isUndefined(response.method)
514
+ ? this.#onSocketMessageResult(response)
515
+ : this.#onSocketMessageSubscribe(response);
516
+ };
517
+
518
+ #onSocketMessageResult = (response: JsonRpcResponse<string>): void => {
519
+ const handler = this.#handlers[response.id];
520
+
521
+ if (!handler) {
522
+ l.debug(() => `Unable to find handler for id=${response.id}`);
523
+
524
+ return;
525
+ }
526
+
527
+ try {
528
+ const { method, params, subscription } = handler;
529
+ const result = this.#coder.decodeResponse<string>(response);
530
+
531
+ // first send the result - in case of subs, we may have an update
532
+ // immediately if we have some queued results already
533
+ handler.callback(null, result);
534
+
535
+ if (subscription) {
536
+ const subId = `${subscription.type}::${result}`;
537
+
538
+ this.#subscriptions[subId] = objectSpread({}, subscription, {
539
+ method,
540
+ params
541
+ });
542
+
543
+ // if we have a result waiting for this subscription already
544
+ if (this.#waitingForId[subId]) {
545
+ this.#onSocketMessageSubscribe(this.#waitingForId[subId]);
546
+ }
547
+ }
548
+ } catch (error) {
549
+ this.#endpointStats.errors++;
550
+ this.#stats.total.errors++;
551
+
552
+ handler.callback(error as Error, undefined);
553
+ }
554
+
555
+ delete this.#handlers[response.id];
556
+ };
557
+
558
+ #onSocketMessageSubscribe = (response: JsonRpcResponse<unknown>): void => {
559
+ if (!response.method) {
560
+ throw new Error('No method found in JSONRPC response');
561
+ }
562
+
563
+ const method = ALIASES[response.method] || response.method;
564
+ const subId = `${method}::${response.params.subscription}`;
565
+ const handler = this.#subscriptions[subId];
566
+
567
+ if (!handler) {
568
+ // store the JSON, we could have out-of-order subid coming in
569
+ this.#waitingForId[subId] = response;
570
+
571
+ l.debug(() => `Unable to find handler for subscription=${subId}`);
572
+
573
+ return;
574
+ }
575
+
576
+ // housekeeping
577
+ delete this.#waitingForId[subId];
578
+
579
+ try {
580
+ const result = this.#coder.decodeResponse(response);
581
+
582
+ handler.callback(null, result);
583
+ } catch (error) {
584
+ this.#endpointStats.errors++;
585
+ this.#stats.total.errors++;
586
+
587
+ handler.callback(error as Error, undefined);
588
+ }
589
+ };
590
+
591
+ #onSocketOpen = (): boolean => {
592
+ if (this.#websocket === null) {
593
+ throw new Error('WebSocket cannot be null in onOpen');
594
+ }
595
+
596
+ l.debug(() => ['connected to', this.endpoint]);
597
+
598
+ this.#isConnected = true;
599
+
600
+ this.#resubscribe();
601
+
602
+ this.#emit('connected');
603
+
604
+ return true;
605
+ };
606
+
607
+ #resubscribe = (): void => {
608
+ const subscriptions = this.#subscriptions;
609
+
610
+ this.#subscriptions = {};
611
+
612
+ Promise.all(Object.keys(subscriptions).map(async (id): Promise<void> => {
613
+ const { callback, method, params, type } = subscriptions[id];
614
+
615
+ // only re-create subscriptions which are not in author (only area where
616
+ // transactions are created, i.e. submissions such as 'author_submitAndWatchExtrinsic'
617
+ // are not included (and will not be re-broadcast)
618
+ if (type.startsWith('author_')) {
619
+ return;
620
+ }
621
+
622
+ try {
623
+ await this.subscribe(type, method, params, callback);
624
+ } catch (error) {
625
+ l.error(error);
626
+ }
627
+ })).catch(l.error);
628
+ };
629
+
630
+ #timeoutHandlers = (): void => {
631
+ const now = Date.now();
632
+ const ids = Object.keys(this.#handlers);
633
+
634
+ for (let i = 0, count = ids.length; i < count; i++) {
635
+ const handler = this.#handlers[ids[i]];
636
+
637
+ if ((now - handler.start) > this.#timeout) {
638
+ try {
639
+ handler.callback(new Error(`No response received from RPC endpoint in ${this.#timeout / 1000}s`), undefined);
640
+ } catch {
641
+ // ignore
642
+ }
643
+
644
+ this.#endpointStats.timeout++;
645
+ this.#stats.total.timeout++;
646
+ delete this.#handlers[ids[i]];
647
+ }
648
+ }
649
+ };
650
+ }
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const packageInfo_1 = require("@pezkuwi/types/packageInfo");
4
+ const util_1 = require("@pezkuwi/util");
5
+ const packageInfo_js_1 = require("./packageInfo.js");
6
+ (0, util_1.detectPackage)(packageInfo_js_1.packageInfo, null, [packageInfo_1.packageInfo]);
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.packageInfo = void 0;
4
- exports.packageInfo = { name: '@pezkuwi/rpc-provider', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '16.5.17' };
4
+ exports.packageInfo = { name: '@pezkuwi/rpc-provider', path: typeof __dirname === 'string' ? __dirname : 'auto', type: 'cjs', version: '16.5.18' };
@@ -1 +1 @@
1
- export const packageInfo = { name: '@pezkuwi/rpc-provider', path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto', type: 'esm', version: '16.5.17' };
1
+ export const packageInfo = { name: '@pezkuwi/rpc-provider', path: (import.meta && import.meta.url) ? new URL(import.meta.url).pathname.substring(0, new URL(import.meta.url).pathname.lastIndexOf('/') + 1) : 'auto', type: 'esm', version: '16.5.18' };