@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,259 @@
1
+ import { stringify } from '@pezkuwi/util';
2
+ /*
3
+ * Creates a new health checker.
4
+ *
5
+ * The role of the health checker is to report to the user the health of a smoldot chain.
6
+ *
7
+ * In order to use it, start by creating a health checker, and call `setSendJsonRpc` to set the
8
+ * way to send a JSON-RPC request to a chain. The health checker is disabled by default. Use
9
+ * `start()` in order to start the health checks. The `start()` function must be passed a callback called
10
+ * when an update to the health of the node is available.
11
+ *
12
+ * In order to send a JSON-RPC request to the chain, you **must** use the `sendJsonRpc` function
13
+ * of the health checker. The health checker rewrites the `id` of the requests it receives.
14
+ *
15
+ * When the chain send a JSON-RPC response, it must be passed to `responsePassThrough()`. This
16
+ * function intercepts the responses destined to the requests that have been emitted by the health
17
+ * checker and returns `null`. If the response doesn't concern the health checker, the response is
18
+ * simply returned by the function.
19
+ *
20
+ * # How it works
21
+ *
22
+ * The health checker periodically calls the `system_health` JSON-RPC call in order to determine
23
+ * the health of the chain.
24
+ *
25
+ * In addition to this, as long as the health check reports that `isSyncing` is `true`, the
26
+ * health checker also maintains a subscription to new best blocks using `chain_subscribeNewHeads`.
27
+ * Whenever a new block is notified, a health check is performed immediately in order to determine
28
+ * whether `isSyncing` has changed to `false`.
29
+ *
30
+ * Thanks to this subscription, the latency of the report of the switch from `isSyncing: true` to
31
+ * `isSyncing: false` is very low.
32
+ *
33
+ */
34
+ export function healthChecker() {
35
+ // `null` if health checker is not started.
36
+ let checker = null;
37
+ let sendJsonRpc = null;
38
+ return {
39
+ responsePassThrough: (jsonRpcResponse) => {
40
+ if (checker === null) {
41
+ return jsonRpcResponse;
42
+ }
43
+ return checker.responsePassThrough(jsonRpcResponse);
44
+ },
45
+ sendJsonRpc: (request) => {
46
+ if (!sendJsonRpc) {
47
+ throw new Error('setSendJsonRpc must be called before sending requests');
48
+ }
49
+ if (checker === null) {
50
+ sendJsonRpc(request);
51
+ }
52
+ else {
53
+ checker.sendJsonRpc(request);
54
+ }
55
+ },
56
+ setSendJsonRpc: (cb) => {
57
+ sendJsonRpc = cb;
58
+ },
59
+ start: (healthCallback) => {
60
+ if (checker !== null) {
61
+ throw new Error("Can't start the health checker multiple times in parallel");
62
+ }
63
+ else if (!sendJsonRpc) {
64
+ throw new Error('setSendJsonRpc must be called before starting the health checks');
65
+ }
66
+ checker = new InnerChecker(healthCallback, sendJsonRpc);
67
+ checker.update(true);
68
+ },
69
+ stop: () => {
70
+ if (checker === null) {
71
+ return;
72
+ } // Already stopped.
73
+ checker.destroy();
74
+ checker = null;
75
+ }
76
+ };
77
+ }
78
+ class InnerChecker {
79
+ #healthCallback;
80
+ #currentHealthCheckId = null;
81
+ #currentHealthTimeout = null;
82
+ #currentSubunsubRequestId = null;
83
+ #currentSubscriptionId = null;
84
+ #requestToSmoldot;
85
+ #isSyncing = false;
86
+ #nextRequestId = 0;
87
+ constructor(healthCallback, requestToSmoldot) {
88
+ this.#healthCallback = healthCallback;
89
+ this.#requestToSmoldot = (request) => requestToSmoldot(stringify(request));
90
+ }
91
+ sendJsonRpc = (request) => {
92
+ // Replace the `id` in the request to prefix the request ID with `extern:`.
93
+ let parsedRequest;
94
+ try {
95
+ parsedRequest = JSON.parse(request);
96
+ }
97
+ catch {
98
+ return;
99
+ }
100
+ if (parsedRequest.id) {
101
+ const newId = 'extern:' + stringify(parsedRequest.id);
102
+ parsedRequest.id = newId;
103
+ }
104
+ this.#requestToSmoldot(parsedRequest);
105
+ };
106
+ responsePassThrough = (jsonRpcResponse) => {
107
+ let parsedResponse;
108
+ try {
109
+ parsedResponse = JSON.parse(jsonRpcResponse);
110
+ }
111
+ catch {
112
+ return jsonRpcResponse;
113
+ }
114
+ // Check whether response is a response to `system_health`.
115
+ if (parsedResponse.id && this.#currentHealthCheckId === parsedResponse.id) {
116
+ this.#currentHealthCheckId = null;
117
+ // Check whether query was successful. It is possible for queries to fail for
118
+ // various reasons, such as the client being overloaded.
119
+ if (!parsedResponse.result) {
120
+ this.update(false);
121
+ return null;
122
+ }
123
+ this.#healthCallback(parsedResponse.result);
124
+ this.#isSyncing = parsedResponse.result.isSyncing;
125
+ this.update(false);
126
+ return null;
127
+ }
128
+ // Check whether response is a response to the subscription or unsubscription.
129
+ if (parsedResponse.id &&
130
+ this.#currentSubunsubRequestId === parsedResponse.id) {
131
+ this.#currentSubunsubRequestId = null;
132
+ // Check whether query was successful. It is possible for queries to fail for
133
+ // various reasons, such as the client being overloaded.
134
+ if (!parsedResponse.result) {
135
+ this.update(false);
136
+ return null;
137
+ }
138
+ if (this.#currentSubscriptionId) {
139
+ this.#currentSubscriptionId = null;
140
+ }
141
+ else {
142
+ this.#currentSubscriptionId = parsedResponse.result;
143
+ }
144
+ this.update(false);
145
+ return null;
146
+ }
147
+ // Check whether response is a notification to a subscription.
148
+ if (parsedResponse.params &&
149
+ this.#currentSubscriptionId &&
150
+ parsedResponse.params.subscription === this.#currentSubscriptionId) {
151
+ // Note that after a successful subscription, a notification containing
152
+ // the current best block is always returned. Considering that a
153
+ // subscription is performed in response to a health check, calling
154
+ // `startHealthCheck()` here will lead to a second health check.
155
+ // It might seem redundant to perform two health checks in a quick
156
+ // succession, but doing so doesn't lead to any problem, and it is
157
+ // actually possible for the health to have changed in between as the
158
+ // current best block might have been updated during the subscription
159
+ // request.
160
+ this.update(true);
161
+ return null;
162
+ }
163
+ // Response doesn't concern us.
164
+ if (parsedResponse.id) {
165
+ const id = parsedResponse.id;
166
+ // Need to remove the `extern:` prefix.
167
+ if (!id.startsWith('extern:')) {
168
+ throw new Error('State inconsistency in health checker');
169
+ }
170
+ const newId = JSON.parse(id.slice('extern:'.length));
171
+ parsedResponse.id = newId;
172
+ }
173
+ return stringify(parsedResponse);
174
+ };
175
+ update = (startNow) => {
176
+ // If `startNow`, clear `#currentHealthTimeout` so that it is set below.
177
+ if (startNow && this.#currentHealthTimeout) {
178
+ clearTimeout(this.#currentHealthTimeout);
179
+ this.#currentHealthTimeout = null;
180
+ }
181
+ if (!this.#currentHealthTimeout) {
182
+ const startHealthRequest = () => {
183
+ this.#currentHealthTimeout = null;
184
+ // No matter what, don't start a health request if there is already one in progress.
185
+ // This is sane to do because receiving a response to a health request calls `update()`.
186
+ if (this.#currentHealthCheckId) {
187
+ return;
188
+ }
189
+ // Actual request starting.
190
+ this.#currentHealthCheckId = `health-checker:${this.#nextRequestId}`;
191
+ this.#nextRequestId += 1;
192
+ this.#requestToSmoldot({
193
+ id: this.#currentHealthCheckId,
194
+ jsonrpc: '2.0',
195
+ method: 'system_health',
196
+ params: []
197
+ });
198
+ };
199
+ if (startNow) {
200
+ startHealthRequest();
201
+ }
202
+ else {
203
+ this.#currentHealthTimeout = setTimeout(startHealthRequest, 1000);
204
+ }
205
+ }
206
+ if (this.#isSyncing &&
207
+ !this.#currentSubscriptionId &&
208
+ !this.#currentSubunsubRequestId) {
209
+ this.startSubscription();
210
+ }
211
+ if (!this.#isSyncing &&
212
+ this.#currentSubscriptionId &&
213
+ !this.#currentSubunsubRequestId) {
214
+ this.endSubscription();
215
+ }
216
+ };
217
+ startSubscription = () => {
218
+ if (this.#currentSubunsubRequestId || this.#currentSubscriptionId) {
219
+ throw new Error('Internal error in health checker');
220
+ }
221
+ this.#currentSubunsubRequestId = `health-checker:${this.#nextRequestId}`;
222
+ this.#nextRequestId += 1;
223
+ this.#requestToSmoldot({
224
+ id: this.#currentSubunsubRequestId,
225
+ jsonrpc: '2.0',
226
+ method: 'chain_subscribeNewHeads',
227
+ params: []
228
+ });
229
+ };
230
+ endSubscription = () => {
231
+ if (this.#currentSubunsubRequestId || !this.#currentSubscriptionId) {
232
+ throw new Error('Internal error in health checker');
233
+ }
234
+ this.#currentSubunsubRequestId = `health-checker:${this.#nextRequestId}`;
235
+ this.#nextRequestId += 1;
236
+ this.#requestToSmoldot({
237
+ id: this.#currentSubunsubRequestId,
238
+ jsonrpc: '2.0',
239
+ method: 'chain_unsubscribeNewHeads',
240
+ params: [this.#currentSubscriptionId]
241
+ });
242
+ };
243
+ destroy = () => {
244
+ if (this.#currentHealthTimeout) {
245
+ clearTimeout(this.#currentHealthTimeout);
246
+ this.#currentHealthTimeout = null;
247
+ }
248
+ };
249
+ }
250
+ export class HealthCheckError extends Error {
251
+ #cause;
252
+ getCause() {
253
+ return this.#cause;
254
+ }
255
+ constructor(response, message = 'Got error response asking for system health') {
256
+ super(message);
257
+ this.#cause = response;
258
+ }
259
+ }
@@ -0,0 +1,319 @@
1
+ import { EventEmitter } from 'eventemitter3';
2
+ import { isError, isFunction, isObject, logger, noop, objectSpread } from '@pezkuwi/util';
3
+ import { RpcCoder } from '../coder/index.js';
4
+ import { healthChecker } from './Health.js';
5
+ const l = logger('api-bizinikiwi-connect');
6
+ const subscriptionUnsubscriptionMethods = new Map([
7
+ ['author_submitAndWatchExtrinsic', 'author_unwatchExtrinsic'],
8
+ ['chain_subscribeAllHeads', 'chain_unsubscribeAllHeads'],
9
+ ['chain_subscribeFinalizedHeads', 'chain_unsubscribeFinalizedHeads'],
10
+ ['chain_subscribeFinalisedHeads', 'chain_subscribeFinalisedHeads'],
11
+ ['chain_subscribeNewHeads', 'chain_unsubscribeNewHeads'],
12
+ ['chain_subscribeNewHead', 'chain_unsubscribeNewHead'],
13
+ ['chain_subscribeRuntimeVersion', 'chain_unsubscribeRuntimeVersion'],
14
+ ['subscribe_newHead', 'unsubscribe_newHead'],
15
+ ['state_subscribeRuntimeVersion', 'state_unsubscribeRuntimeVersion'],
16
+ ['state_subscribeStorage', 'state_unsubscribeStorage']
17
+ ]);
18
+ const scClients = new WeakMap();
19
+ export class ScProvider {
20
+ #Sc;
21
+ #coder = new RpcCoder();
22
+ #spec;
23
+ #sharedSandbox;
24
+ #subscriptions = new Map();
25
+ #resubscribeMethods = new Map();
26
+ #requests = new Map();
27
+ #wellKnownChains;
28
+ #eventemitter = new EventEmitter();
29
+ #chain = null;
30
+ #isChainReady = false;
31
+ constructor(Sc, spec, sharedSandbox) {
32
+ if (!isObject(Sc) || !isObject(Sc.WellKnownChain) || !isFunction(Sc.createScClient)) {
33
+ throw new Error('Expected an @bizinikiwi/connect interface as first parameter to ScProvider');
34
+ }
35
+ this.#Sc = Sc;
36
+ this.#spec = spec;
37
+ this.#sharedSandbox = sharedSandbox;
38
+ this.#wellKnownChains = new Set(Object.values(Sc.WellKnownChain));
39
+ }
40
+ get hasSubscriptions() {
41
+ // Indicates that subscriptions are supported
42
+ return !!true;
43
+ }
44
+ get isClonable() {
45
+ return !!false;
46
+ }
47
+ get isConnected() {
48
+ return !!this.#chain && this.#isChainReady;
49
+ }
50
+ clone() {
51
+ throw new Error('clone() is not supported.');
52
+ }
53
+ // Config details can be found in @bizinikiwi/connect repo following the link:
54
+ // https://github.com/pezkuwichain/bizinikiwi-connect/blob/main/packages/connect/src/connector/index.ts
55
+ async connect(config, checkerFactory = healthChecker) {
56
+ if (this.isConnected) {
57
+ throw new Error('Already connected!');
58
+ }
59
+ // it could happen that after emitting `disconnected` due to the fact that
60
+ // smoldot is syncing, the consumer tries to reconnect after a certain amount
61
+ // of time... In which case we want to make sure that we don't create a new
62
+ // chain.
63
+ if (this.#chain) {
64
+ await this.#chain;
65
+ return;
66
+ }
67
+ if (this.#sharedSandbox && !this.#sharedSandbox.isConnected) {
68
+ await this.#sharedSandbox.connect();
69
+ }
70
+ const client = this.#sharedSandbox
71
+ ? scClients.get(this.#sharedSandbox)
72
+ : this.#Sc.createScClient(config);
73
+ if (!client) {
74
+ throw new Error('Unknown ScProvider!');
75
+ }
76
+ scClients.set(this, client);
77
+ const hc = checkerFactory();
78
+ const onResponse = (res) => {
79
+ const hcRes = hc.responsePassThrough(res);
80
+ if (!hcRes) {
81
+ return;
82
+ }
83
+ const response = JSON.parse(hcRes);
84
+ let decodedResponse;
85
+ try {
86
+ decodedResponse = this.#coder.decodeResponse(response);
87
+ }
88
+ catch (e) {
89
+ decodedResponse = e;
90
+ }
91
+ // It's not a subscription message, but rather a standar RPC response
92
+ if (response.params?.subscription === undefined || !response.method) {
93
+ return this.#requests.get(response.id)?.(decodedResponse);
94
+ }
95
+ // We are dealing with a subscription message
96
+ const subscriptionId = `${response.method}::${response.params.subscription}`;
97
+ const callback = this.#subscriptions.get(subscriptionId)?.[0];
98
+ callback?.(decodedResponse);
99
+ };
100
+ const addChain = this.#sharedSandbox
101
+ ? (async (...args) => {
102
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
103
+ const source = this.#sharedSandbox;
104
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
105
+ return (await source.#chain).addChain(...args);
106
+ })
107
+ : this.#wellKnownChains.has(this.#spec)
108
+ ? client.addWellKnownChain
109
+ : client.addChain;
110
+ this.#chain = addChain(this.#spec).then((chain) => {
111
+ hc.setSendJsonRpc(chain.sendJsonRpc);
112
+ // Start async response processing loop
113
+ // This replaces the callback-based API from older @substrate/connect versions
114
+ (async () => {
115
+ try {
116
+ for await (const res of chain.jsonRpcResponses) {
117
+ onResponse(res);
118
+ }
119
+ }
120
+ catch {
121
+ // Chain was removed or connection closed - this is expected
122
+ }
123
+ })();
124
+ this.#isChainReady = false;
125
+ const cleanup = () => {
126
+ // If there are any callbacks left, we have to reject/error them.
127
+ // Otherwise, that would cause a memory leak.
128
+ const disconnectionError = new Error('Disconnected');
129
+ this.#requests.forEach((cb) => cb(disconnectionError));
130
+ this.#subscriptions.forEach(([cb]) => cb(disconnectionError));
131
+ this.#subscriptions.clear();
132
+ };
133
+ const staleSubscriptions = [];
134
+ const killStaleSubscriptions = () => {
135
+ if (staleSubscriptions.length === 0) {
136
+ return;
137
+ }
138
+ const stale = staleSubscriptions.pop();
139
+ if (!stale) {
140
+ throw new Error('Unable to get stale subscription');
141
+ }
142
+ const { id, unsubscribeMethod } = stale;
143
+ Promise
144
+ .race([
145
+ this.send(unsubscribeMethod, [id]).catch(noop),
146
+ new Promise((resolve) => setTimeout(resolve, 500))
147
+ ])
148
+ .then(killStaleSubscriptions)
149
+ .catch(noop);
150
+ };
151
+ hc.start((health) => {
152
+ const isReady = !health.isSyncing && (health.peers > 0 || !health.shouldHavePeers);
153
+ // if it's the same as before, then nothing has changed and we are done
154
+ if (this.#isChainReady === isReady) {
155
+ return;
156
+ }
157
+ this.#isChainReady = isReady;
158
+ if (!isReady) {
159
+ // If we've reached this point, that means that the chain used to be "ready"
160
+ // and now we are about to emit `disconnected`.
161
+ //
162
+ // This will cause the PezkuwiJs API think that the connection is
163
+ // actually dead. In reality the smoldot chain is not dead, of course.
164
+ // However, we have to cleanup all the existing callbacks because when
165
+ // the smoldot chain stops syncing, then we will emit `connected` and
166
+ // the PezkuwiJs API will try to re-create the previous
167
+ // subscriptions and requests. Although, now is not a good moment
168
+ // to be sending unsubscription messages to the smoldot chain, we
169
+ // should wait until is no longer syncing to send the unsubscription
170
+ // messages from the stale subscriptions of the previous connection.
171
+ //
172
+ // That's why -before we perform the cleanup of `this.#subscriptions`-
173
+ // we keep the necessary information that we will need later on to
174
+ // kill the stale subscriptions.
175
+ [...this.#subscriptions.values()].forEach((s) => {
176
+ staleSubscriptions.push(s[1]);
177
+ });
178
+ cleanup();
179
+ this.#eventemitter.emit('disconnected');
180
+ }
181
+ else {
182
+ killStaleSubscriptions();
183
+ this.#eventemitter.emit('connected');
184
+ if (this.#resubscribeMethods.size) {
185
+ this.#resubscribe();
186
+ }
187
+ }
188
+ });
189
+ return objectSpread({}, chain, {
190
+ remove: () => {
191
+ hc.stop();
192
+ chain.remove();
193
+ cleanup();
194
+ },
195
+ sendJsonRpc: hc.sendJsonRpc.bind(hc)
196
+ });
197
+ });
198
+ try {
199
+ await this.#chain;
200
+ }
201
+ catch (e) {
202
+ this.#chain = null;
203
+ this.#eventemitter.emit('error', e);
204
+ throw e;
205
+ }
206
+ }
207
+ #resubscribe = () => {
208
+ const promises = [];
209
+ this.#resubscribeMethods.forEach((subDetails) => {
210
+ // only re-create subscriptions which are not in author (only area where
211
+ // transactions are created, i.e. submissions such as 'author_submitAndWatchExtrinsic'
212
+ // are not included (and will not be re-broadcast)
213
+ if (subDetails.type.startsWith('author_')) {
214
+ return;
215
+ }
216
+ try {
217
+ const promise = new Promise((resolve) => {
218
+ this.subscribe(subDetails.type, subDetails.method, subDetails.params, subDetails.callback).catch((error) => console.log(error));
219
+ resolve();
220
+ });
221
+ promises.push(promise);
222
+ }
223
+ catch (error) {
224
+ l.error(error);
225
+ }
226
+ });
227
+ Promise.all(promises).catch((err) => l.log(err));
228
+ };
229
+ async disconnect() {
230
+ if (!this.#chain) {
231
+ return;
232
+ }
233
+ const chain = await this.#chain;
234
+ this.#chain = null;
235
+ this.#isChainReady = false;
236
+ try {
237
+ chain.remove();
238
+ }
239
+ catch (_) { }
240
+ this.#eventemitter.emit('disconnected');
241
+ }
242
+ on(type, sub) {
243
+ // It's possible. Although, quite unlikely, that by the time that pezkuwi
244
+ // subscribes to the `connected` event, the Provider is already connected.
245
+ // In that case, we must emit to let the consumer know that we are connected.
246
+ if (type === 'connected' && this.isConnected) {
247
+ sub();
248
+ }
249
+ this.#eventemitter.on(type, sub);
250
+ return () => {
251
+ this.#eventemitter.removeListener(type, sub);
252
+ };
253
+ }
254
+ async send(method, params) {
255
+ if (!this.isConnected || !this.#chain) {
256
+ throw new Error('Provider is not connected');
257
+ }
258
+ const chain = await this.#chain;
259
+ const [id, json] = this.#coder.encodeJson(method, params);
260
+ const result = new Promise((resolve, reject) => {
261
+ this.#requests.set(id, (response) => {
262
+ (isError(response) ? reject : resolve)(response);
263
+ });
264
+ try {
265
+ chain.sendJsonRpc(json);
266
+ }
267
+ catch (e) {
268
+ this.#chain = null;
269
+ try {
270
+ chain.remove();
271
+ }
272
+ catch (_) { }
273
+ this.#eventemitter.emit('error', e);
274
+ }
275
+ });
276
+ try {
277
+ return await result;
278
+ }
279
+ finally {
280
+ // let's ensure that once the Promise is resolved/rejected, then we remove
281
+ // remove its entry from the internal #requests
282
+ this.#requests.delete(id);
283
+ }
284
+ }
285
+ async subscribe(type, method, params, callback) {
286
+ if (!subscriptionUnsubscriptionMethods.has(method)) {
287
+ throw new Error(`Unsupported subscribe method: ${method}`);
288
+ }
289
+ const id = await this.send(method, params);
290
+ const subscriptionId = `${type}::${id}`;
291
+ const cb = (response) => {
292
+ if (response instanceof Error) {
293
+ callback(response, undefined);
294
+ }
295
+ else {
296
+ callback(null, response);
297
+ }
298
+ };
299
+ const unsubscribeMethod = subscriptionUnsubscriptionMethods.get(method);
300
+ if (!unsubscribeMethod) {
301
+ throw new Error('Invalid unsubscribe method found');
302
+ }
303
+ this.#resubscribeMethods.set(subscriptionId, { callback, method, params, type });
304
+ this.#subscriptions.set(subscriptionId, [cb, { id, unsubscribeMethod }]);
305
+ return id;
306
+ }
307
+ unsubscribe(type, method, id) {
308
+ if (!this.isConnected) {
309
+ throw new Error('Provider is not connected');
310
+ }
311
+ const subscriptionId = `${type}::${id}`;
312
+ if (!this.#subscriptions.has(subscriptionId)) {
313
+ return Promise.reject(new Error(`Unable to find active subscription=${subscriptionId}`));
314
+ }
315
+ this.#resubscribeMethods.delete(subscriptionId);
316
+ this.#subscriptions.delete(subscriptionId);
317
+ return this.send(method, [id]);
318
+ }
319
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,5 @@
1
+ export { ScProvider } from './bizinikiwi-connect/index.js';
2
+ export { HttpProvider } from './http/index.js';
3
+ export { DEFAULT_CAPACITY, LRUCache } from './lru.js';
4
+ export { packageInfo } from './packageInfo.js';
5
+ export { WsProvider } from './ws/index.js';
@@ -0,0 +1,50 @@
1
+ import { isFunction } from '@pezkuwi/util';
2
+ const UNKNOWN = -99999;
3
+ function extend(that, name, value) {
4
+ Object.defineProperty(that, name, {
5
+ configurable: true,
6
+ enumerable: false,
7
+ value
8
+ });
9
+ }
10
+ /**
11
+ * @name RpcError
12
+ * @summary Extension to the basic JS Error.
13
+ * @description
14
+ * The built-in JavaScript Error class is extended by adding a code to allow for Error categorization. In addition to the normal `stack`, `message`, the numeric `code` and `data` (any types) parameters are available on the object.
15
+ * @example
16
+ * <BR>
17
+ *
18
+ * ```javascript
19
+ * const { RpcError } from '@pezkuwi/util');
20
+ *
21
+ * throw new RpcError('some message', RpcError.CODES.METHOD_NOT_FOUND); // => error.code = -32601
22
+ * ```
23
+ */
24
+ export default class RpcError extends Error {
25
+ code;
26
+ data;
27
+ message;
28
+ name;
29
+ stack;
30
+ constructor(message = '', code = UNKNOWN, data) {
31
+ super();
32
+ extend(this, 'message', String(message));
33
+ extend(this, 'name', this.constructor.name);
34
+ extend(this, 'data', data);
35
+ extend(this, 'code', code);
36
+ if (isFunction(Error.captureStackTrace)) {
37
+ Error.captureStackTrace(this, this.constructor);
38
+ }
39
+ else {
40
+ const { stack } = new Error(message);
41
+ stack && extend(this, 'stack', stack);
42
+ }
43
+ }
44
+ static CODES = {
45
+ ASSERT: -90009,
46
+ INVALID_JSONRPC: -99998,
47
+ METHOD_NOT_FOUND: -32601, // Rust client
48
+ UNKNOWN
49
+ };
50
+ }