@pezkuwi/rpc-provider 16.5.5 → 16.5.8

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