@nktkas/hyperliquid 0.25.0-beta.1 → 0.25.0-beta.3

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 (284) hide show
  1. package/README.md +64 -16
  2. package/esm/bin/cli.d.ts +3 -0
  3. package/esm/bin/cli.d.ts.map +1 -0
  4. package/esm/bin/cli.js +452 -0
  5. package/esm/bin/cli.js.map +1 -0
  6. package/{script/src/errors.d.ts → esm/src/_errors.d.ts} +1 -1
  7. package/esm/src/_errors.d.ts.map +1 -0
  8. package/esm/src/{errors.js → _errors.js} +1 -0
  9. package/esm/src/_errors.js.map +1 -0
  10. package/esm/src/clients/exchange.d.ts +4 -11
  11. package/esm/src/clients/exchange.d.ts.map +1 -1
  12. package/esm/src/clients/exchange.js +8 -9
  13. package/esm/src/clients/exchange.js.map +1 -0
  14. package/esm/src/clients/info.d.ts +86 -2
  15. package/esm/src/clients/info.d.ts.map +1 -1
  16. package/esm/src/clients/info.js +103 -1
  17. package/esm/src/clients/info.js.map +1 -0
  18. package/esm/src/clients/multiSign.d.ts +1 -1
  19. package/esm/src/clients/multiSign.d.ts.map +1 -1
  20. package/esm/src/clients/multiSign.js +3 -2
  21. package/esm/src/clients/multiSign.js.map +1 -0
  22. package/esm/src/clients/subscription.js +1 -0
  23. package/esm/src/clients/subscription.js.map +1 -0
  24. package/esm/src/mod.d.ts +21 -0
  25. package/esm/src/mod.d.ts.map +1 -0
  26. package/esm/src/mod.js +14 -0
  27. package/esm/src/mod.js.map +1 -0
  28. package/esm/src/schemas/_base.d.ts +11 -3
  29. package/esm/src/schemas/_base.d.ts.map +1 -1
  30. package/esm/src/schemas/_base.js +30 -2
  31. package/esm/src/schemas/_base.js.map +1 -0
  32. package/esm/src/schemas/exchange/requests.d.ts +1872 -1872
  33. package/esm/src/schemas/exchange/requests.d.ts.map +1 -1
  34. package/esm/src/schemas/exchange/requests.js +277 -281
  35. package/esm/src/schemas/exchange/requests.js.map +1 -0
  36. package/esm/src/schemas/exchange/responses.d.ts +51 -51
  37. package/esm/src/schemas/exchange/responses.js +55 -54
  38. package/esm/src/schemas/exchange/responses.js.map +1 -0
  39. package/esm/src/schemas/explorer/requests.d.ts +5 -5
  40. package/esm/src/schemas/explorer/requests.js +6 -5
  41. package/esm/src/schemas/explorer/requests.js.map +1 -0
  42. package/esm/src/schemas/explorer/responses.d.ts +10 -10
  43. package/esm/src/schemas/explorer/responses.js +12 -11
  44. package/esm/src/schemas/explorer/responses.js.map +1 -0
  45. package/esm/src/schemas/info/accounts.d.ts +1042 -296
  46. package/esm/src/schemas/info/accounts.d.ts.map +1 -1
  47. package/esm/src/schemas/info/accounts.js +154 -109
  48. package/esm/src/schemas/info/accounts.js.map +1 -0
  49. package/esm/src/schemas/info/assets.d.ts +130 -101
  50. package/esm/src/schemas/info/assets.d.ts.map +1 -1
  51. package/esm/src/schemas/info/assets.js +69 -49
  52. package/esm/src/schemas/info/assets.js.map +1 -0
  53. package/esm/src/schemas/info/markets.d.ts +35 -18
  54. package/esm/src/schemas/info/markets.d.ts.map +1 -1
  55. package/esm/src/schemas/info/markets.js +27 -15
  56. package/esm/src/schemas/info/markets.js.map +1 -0
  57. package/esm/src/schemas/info/orders.d.ts +151 -151
  58. package/esm/src/schemas/info/orders.js +37 -36
  59. package/esm/src/schemas/info/orders.js.map +1 -0
  60. package/esm/src/schemas/info/requests.d.ts +145 -88
  61. package/esm/src/schemas/info/requests.d.ts.map +1 -1
  62. package/esm/src/schemas/info/requests.js +116 -73
  63. package/esm/src/schemas/info/requests.js.map +1 -0
  64. package/esm/src/schemas/info/validators.d.ts +60 -39
  65. package/esm/src/schemas/info/validators.d.ts.map +1 -1
  66. package/esm/src/schemas/info/validators.js +40 -26
  67. package/esm/src/schemas/info/validators.js.map +1 -0
  68. package/esm/src/schemas/info/vaults.d.ts +59 -59
  69. package/esm/src/schemas/info/vaults.js +20 -19
  70. package/esm/src/schemas/info/vaults.js.map +1 -0
  71. package/esm/src/schemas/mod.d.ts +4 -3
  72. package/esm/src/schemas/mod.d.ts.map +1 -1
  73. package/esm/src/schemas/mod.js +5 -3
  74. package/esm/src/schemas/mod.js.map +1 -0
  75. package/esm/src/schemas/subscriptions/requests.d.ts +23 -23
  76. package/esm/src/schemas/subscriptions/requests.d.ts.map +1 -1
  77. package/esm/src/schemas/subscriptions/requests.js +25 -24
  78. package/esm/src/schemas/subscriptions/requests.js.map +1 -0
  79. package/esm/src/schemas/subscriptions/responses.d.ts +547 -547
  80. package/esm/src/schemas/subscriptions/responses.js +41 -40
  81. package/esm/src/schemas/subscriptions/responses.js.map +1 -0
  82. package/esm/src/signing/mod.d.ts +1 -1
  83. package/esm/src/signing/mod.d.ts.map +1 -1
  84. package/esm/src/signing/mod.js +3 -2
  85. package/esm/src/signing/mod.js.map +1 -0
  86. package/esm/src/signing/signTypedData/ethers.d.ts.map +1 -0
  87. package/esm/src/signing/{_signTypedData → signTypedData}/ethers.js +1 -0
  88. package/esm/src/signing/signTypedData/ethers.js.map +1 -0
  89. package/esm/src/signing/signTypedData/mod.d.ts.map +1 -0
  90. package/esm/src/signing/{_signTypedData → signTypedData}/mod.js +1 -0
  91. package/esm/src/signing/signTypedData/mod.js.map +1 -0
  92. package/esm/src/signing/signTypedData/private_key.d.ts.map +1 -0
  93. package/esm/src/signing/{_signTypedData → signTypedData}/private_key.js +10 -8
  94. package/esm/src/signing/signTypedData/private_key.js.map +1 -0
  95. package/esm/src/signing/signTypedData/viem.d.ts.map +1 -0
  96. package/esm/src/signing/{_signTypedData → signTypedData}/viem.js +1 -0
  97. package/esm/src/signing/signTypedData/viem.js.map +1 -0
  98. package/esm/src/transports/base.d.ts +4 -4
  99. package/esm/src/transports/base.d.ts.map +1 -1
  100. package/esm/src/transports/base.js +3 -2
  101. package/esm/src/transports/base.js.map +1 -0
  102. package/esm/src/transports/http/http_transport.d.ts +1 -1
  103. package/esm/src/transports/http/http_transport.js +1 -0
  104. package/esm/src/transports/http/http_transport.js.map +1 -0
  105. package/esm/src/transports/websocket/_hyperliquid_event_target.js +1 -0
  106. package/esm/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
  107. package/esm/src/transports/websocket/_reconnecting_websocket.js +1 -0
  108. package/esm/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
  109. package/esm/src/transports/websocket/_websocket_async_request.js +1 -0
  110. package/esm/src/transports/websocket/_websocket_async_request.js.map +1 -0
  111. package/esm/src/transports/websocket/websocket_transport.d.ts +7 -0
  112. package/esm/src/transports/websocket/websocket_transport.d.ts.map +1 -1
  113. package/esm/src/transports/websocket/websocket_transport.js +4 -0
  114. package/esm/src/transports/websocket/websocket_transport.js.map +1 -0
  115. package/package.json +15 -7
  116. package/script/bin/cli.d.ts +3 -0
  117. package/script/bin/cli.d.ts.map +1 -0
  118. package/script/bin/cli.js +490 -0
  119. package/script/bin/cli.js.map +1 -0
  120. package/{esm/src/errors.d.ts → script/src/_errors.d.ts} +1 -1
  121. package/script/src/_errors.d.ts.map +1 -0
  122. package/script/src/{errors.js → _errors.js} +1 -0
  123. package/script/src/_errors.js.map +1 -0
  124. package/script/src/clients/exchange.d.ts +4 -11
  125. package/script/src/clients/exchange.d.ts.map +1 -1
  126. package/script/src/clients/exchange.js +9 -10
  127. package/script/src/clients/exchange.js.map +1 -0
  128. package/script/src/clients/info.d.ts +86 -2
  129. package/script/src/clients/info.d.ts.map +1 -1
  130. package/script/src/clients/info.js +102 -0
  131. package/script/src/clients/info.js.map +1 -0
  132. package/script/src/clients/multiSign.d.ts +1 -1
  133. package/script/src/clients/multiSign.d.ts.map +1 -1
  134. package/script/src/clients/multiSign.js +3 -2
  135. package/script/src/clients/multiSign.js.map +1 -0
  136. package/script/src/clients/subscription.js +1 -0
  137. package/script/src/clients/subscription.js.map +1 -0
  138. package/script/src/mod.d.ts +21 -0
  139. package/script/src/mod.d.ts.map +1 -0
  140. package/script/{mod.js → src/mod.js} +13 -8
  141. package/script/src/mod.js.map +1 -0
  142. package/script/src/schemas/_base.d.ts +11 -3
  143. package/script/src/schemas/_base.d.ts.map +1 -1
  144. package/script/src/schemas/_base.js +31 -3
  145. package/script/src/schemas/_base.js.map +1 -0
  146. package/script/src/schemas/exchange/requests.d.ts +1872 -1872
  147. package/script/src/schemas/exchange/requests.d.ts.map +1 -1
  148. package/script/src/schemas/exchange/requests.js +276 -280
  149. package/script/src/schemas/exchange/requests.js.map +1 -0
  150. package/script/src/schemas/exchange/responses.d.ts +51 -51
  151. package/script/src/schemas/exchange/responses.js +54 -53
  152. package/script/src/schemas/exchange/responses.js.map +1 -0
  153. package/script/src/schemas/explorer/requests.d.ts +5 -5
  154. package/script/src/schemas/explorer/requests.js +5 -4
  155. package/script/src/schemas/explorer/requests.js.map +1 -0
  156. package/script/src/schemas/explorer/responses.d.ts +10 -10
  157. package/script/src/schemas/explorer/responses.js +11 -10
  158. package/script/src/schemas/explorer/responses.js.map +1 -0
  159. package/script/src/schemas/info/accounts.d.ts +1042 -296
  160. package/script/src/schemas/info/accounts.d.ts.map +1 -1
  161. package/script/src/schemas/info/accounts.js +154 -109
  162. package/script/src/schemas/info/accounts.js.map +1 -0
  163. package/script/src/schemas/info/assets.d.ts +130 -101
  164. package/script/src/schemas/info/assets.d.ts.map +1 -1
  165. package/script/src/schemas/info/assets.js +69 -49
  166. package/script/src/schemas/info/assets.js.map +1 -0
  167. package/script/src/schemas/info/markets.d.ts +35 -18
  168. package/script/src/schemas/info/markets.d.ts.map +1 -1
  169. package/script/src/schemas/info/markets.js +27 -15
  170. package/script/src/schemas/info/markets.js.map +1 -0
  171. package/script/src/schemas/info/orders.d.ts +151 -151
  172. package/script/src/schemas/info/orders.js +36 -35
  173. package/script/src/schemas/info/orders.js.map +1 -0
  174. package/script/src/schemas/info/requests.d.ts +145 -88
  175. package/script/src/schemas/info/requests.d.ts.map +1 -1
  176. package/script/src/schemas/info/requests.js +116 -73
  177. package/script/src/schemas/info/requests.js.map +1 -0
  178. package/script/src/schemas/info/validators.d.ts +60 -39
  179. package/script/src/schemas/info/validators.d.ts.map +1 -1
  180. package/script/src/schemas/info/validators.js +40 -26
  181. package/script/src/schemas/info/validators.js.map +1 -0
  182. package/script/src/schemas/info/vaults.d.ts +59 -59
  183. package/script/src/schemas/info/vaults.js +19 -18
  184. package/script/src/schemas/info/vaults.js.map +1 -0
  185. package/script/src/schemas/mod.d.ts +4 -3
  186. package/script/src/schemas/mod.d.ts.map +1 -1
  187. package/script/src/schemas/mod.js +8 -5
  188. package/script/src/schemas/mod.js.map +1 -0
  189. package/script/src/schemas/subscriptions/requests.d.ts +23 -23
  190. package/script/src/schemas/subscriptions/requests.d.ts.map +1 -1
  191. package/script/src/schemas/subscriptions/requests.js +24 -23
  192. package/script/src/schemas/subscriptions/requests.js.map +1 -0
  193. package/script/src/schemas/subscriptions/responses.d.ts +547 -547
  194. package/script/src/schemas/subscriptions/responses.js +40 -39
  195. package/script/src/schemas/subscriptions/responses.js.map +1 -0
  196. package/script/src/signing/mod.d.ts +1 -1
  197. package/script/src/signing/mod.d.ts.map +1 -1
  198. package/script/src/signing/mod.js +4 -3
  199. package/script/src/signing/mod.js.map +1 -0
  200. package/script/src/signing/signTypedData/ethers.d.ts.map +1 -0
  201. package/script/src/signing/{_signTypedData → signTypedData}/ethers.js +1 -0
  202. package/script/src/signing/signTypedData/ethers.js.map +1 -0
  203. package/script/src/signing/signTypedData/mod.d.ts.map +1 -0
  204. package/script/src/signing/{_signTypedData → signTypedData}/mod.js +1 -0
  205. package/script/src/signing/signTypedData/mod.js.map +1 -0
  206. package/script/src/signing/signTypedData/private_key.d.ts.map +1 -0
  207. package/script/src/signing/{_signTypedData → signTypedData}/private_key.js +17 -15
  208. package/script/src/signing/signTypedData/private_key.js.map +1 -0
  209. package/script/src/signing/signTypedData/viem.d.ts.map +1 -0
  210. package/script/src/signing/{_signTypedData → signTypedData}/viem.js +1 -0
  211. package/script/src/signing/signTypedData/viem.js.map +1 -0
  212. package/script/src/transports/base.d.ts +4 -4
  213. package/script/src/transports/base.d.ts.map +1 -1
  214. package/script/src/transports/base.js +4 -3
  215. package/script/src/transports/base.js.map +1 -0
  216. package/script/src/transports/http/http_transport.d.ts +1 -1
  217. package/script/src/transports/http/http_transport.js +1 -0
  218. package/script/src/transports/http/http_transport.js.map +1 -0
  219. package/script/src/transports/websocket/_hyperliquid_event_target.js +1 -0
  220. package/script/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
  221. package/script/src/transports/websocket/_reconnecting_websocket.js +1 -0
  222. package/script/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
  223. package/script/src/transports/websocket/_websocket_async_request.js +1 -0
  224. package/script/src/transports/websocket/_websocket_async_request.js.map +1 -0
  225. package/script/src/transports/websocket/websocket_transport.d.ts +7 -0
  226. package/script/src/transports/websocket/websocket_transport.d.ts.map +1 -1
  227. package/script/src/transports/websocket/websocket_transport.js +4 -0
  228. package/script/src/transports/websocket/websocket_transport.js.map +1 -0
  229. package/src/bin/cli.ts +481 -0
  230. package/src/src/_errors.ts +7 -0
  231. package/src/src/clients/exchange.ts +2246 -0
  232. package/src/src/clients/info.ts +2110 -0
  233. package/src/src/clients/multiSign.ts +183 -0
  234. package/src/src/clients/subscription.ts +841 -0
  235. package/src/src/mod.ts +29 -0
  236. package/src/src/schemas/_base.ts +82 -0
  237. package/src/src/schemas/exchange/requests.ts +3052 -0
  238. package/src/src/schemas/exchange/responses.ts +540 -0
  239. package/src/src/schemas/explorer/requests.ts +65 -0
  240. package/src/src/schemas/explorer/responses.ts +138 -0
  241. package/src/src/schemas/info/accounts.ts +1598 -0
  242. package/src/src/schemas/info/assets.ts +693 -0
  243. package/src/src/schemas/info/markets.ts +171 -0
  244. package/src/src/schemas/info/orders.ts +597 -0
  245. package/src/src/schemas/info/requests.ts +1401 -0
  246. package/src/src/schemas/info/validators.ts +297 -0
  247. package/src/src/schemas/info/vaults.ts +262 -0
  248. package/src/src/schemas/mod.ts +121 -0
  249. package/src/src/schemas/subscriptions/requests.ts +514 -0
  250. package/src/src/schemas/subscriptions/responses.ts +576 -0
  251. package/src/src/signing/mod.ts +572 -0
  252. package/src/src/signing/signTypedData/ethers.ts +59 -0
  253. package/src/src/signing/signTypedData/mod.ts +121 -0
  254. package/src/src/signing/signTypedData/private_key.ts +234 -0
  255. package/src/src/signing/signTypedData/viem.ts +55 -0
  256. package/src/src/transports/base.ts +54 -0
  257. package/src/src/transports/http/http_transport.ts +208 -0
  258. package/src/src/transports/websocket/_hyperliquid_event_target.ts +118 -0
  259. package/src/src/transports/websocket/_reconnecting_websocket.ts +404 -0
  260. package/src/src/transports/websocket/_websocket_async_request.ts +229 -0
  261. package/src/src/transports/websocket/websocket_transport.ts +394 -0
  262. package/esm/mod.d.ts +0 -20
  263. package/esm/mod.d.ts.map +0 -1
  264. package/esm/mod.js +0 -11
  265. package/esm/src/errors.d.ts.map +0 -1
  266. package/esm/src/signing/_signTypedData/ethers.d.ts.map +0 -1
  267. package/esm/src/signing/_signTypedData/mod.d.ts.map +0 -1
  268. package/esm/src/signing/_signTypedData/private_key.d.ts.map +0 -1
  269. package/esm/src/signing/_signTypedData/viem.d.ts.map +0 -1
  270. package/script/mod.d.ts +0 -20
  271. package/script/mod.d.ts.map +0 -1
  272. package/script/src/errors.d.ts.map +0 -1
  273. package/script/src/signing/_signTypedData/ethers.d.ts.map +0 -1
  274. package/script/src/signing/_signTypedData/mod.d.ts.map +0 -1
  275. package/script/src/signing/_signTypedData/private_key.d.ts.map +0 -1
  276. package/script/src/signing/_signTypedData/viem.d.ts.map +0 -1
  277. /package/esm/src/signing/{_signTypedData → signTypedData}/ethers.d.ts +0 -0
  278. /package/esm/src/signing/{_signTypedData → signTypedData}/mod.d.ts +0 -0
  279. /package/esm/src/signing/{_signTypedData → signTypedData}/private_key.d.ts +0 -0
  280. /package/esm/src/signing/{_signTypedData → signTypedData}/viem.d.ts +0 -0
  281. /package/script/src/signing/{_signTypedData → signTypedData}/ethers.d.ts +0 -0
  282. /package/script/src/signing/{_signTypedData → signTypedData}/mod.d.ts +0 -0
  283. /package/script/src/signing/{_signTypedData → signTypedData}/private_key.d.ts +0 -0
  284. /package/script/src/signing/{_signTypedData → signTypedData}/viem.d.ts +0 -0
@@ -0,0 +1,118 @@
1
+ import type { BlockDetails, TxDetails } from "../../schemas/mod.js";
2
+
3
+ /** Represents a message from the Hyperliquid WebSocket API. */
4
+ interface HyperliquidMsg {
5
+ /** Event channel name. */
6
+ channel: string;
7
+ /** Channel-specific data. */
8
+ data: unknown;
9
+ }
10
+
11
+ /** Response to subscribe to or unsubscribe from an event. */
12
+ interface SubscriptionResponse {
13
+ /** Type of subscription operation. */
14
+ method: "subscribe" | "unsubscribe";
15
+ /** Original subscription request. */
16
+ subscription: unknown;
17
+ }
18
+
19
+ /** Response to post request. */
20
+ interface PostResponse {
21
+ /** Unique request identifier. */
22
+ id: number;
23
+ /** Server response. */
24
+ response:
25
+ /** Response containing requested information. */
26
+ | {
27
+ /** Indicates that this is an informational response. */
28
+ type: "info";
29
+ /** Contains the information data. */
30
+ payload: {
31
+ /** Type of information being returned. */
32
+ type: string;
33
+ /** Information specific data. */
34
+ data: unknown;
35
+ };
36
+ }
37
+ /** Response containing action result. */
38
+ | {
39
+ /** Indicates that this is an action response. */
40
+ type: "action";
41
+ /** Action result. */
42
+ payload: {
43
+ /** Response status indicating success or failure of the action. */
44
+ status: "ok" | "err";
45
+ /** Success data or error message. */
46
+ response:
47
+ | {
48
+ /** Type of operation. */
49
+ type: string;
50
+ /** Specific data for the action. */
51
+ data?: unknown;
52
+ }
53
+ | string;
54
+ };
55
+ };
56
+ }
57
+
58
+ /** Base system events and dynamic channel events for Hyperliquid WebSocket API. */
59
+ export interface HyperliquidEventMap {
60
+ subscriptionResponse: CustomEvent<SubscriptionResponse>;
61
+ post: CustomEvent<PostResponse>;
62
+ error: CustomEvent<string>;
63
+ pong: CustomEvent<undefined>;
64
+ _explorerBlock: CustomEvent<Omit<BlockDetails, "txs">[]>;
65
+ _explorerTxs: CustomEvent<TxDetails[]>;
66
+ // deno-lint-ignore no-explicit-any
67
+ [key: string]: CustomEvent<any>;
68
+ }
69
+
70
+ /** Listens for WebSocket messages and sends them as Hyperliquid typed events. */
71
+ export class HyperliquidEventTarget extends EventTarget {
72
+ constructor(socket: WebSocket) {
73
+ super();
74
+ socket.addEventListener("message", (event) => {
75
+ try {
76
+ const msg = JSON.parse(event.data) as unknown;
77
+ if (isHyperliquidMsg(msg)) {
78
+ this.dispatchEvent(new CustomEvent(msg.channel, { detail: msg.data }));
79
+ } else if (isExplorerBlockMsg(msg)) {
80
+ this.dispatchEvent(new CustomEvent("_explorerBlock", { detail: msg }));
81
+ } else if (isExplorerTxsMsg(msg)) {
82
+ this.dispatchEvent(new CustomEvent("_explorerTxs", { detail: msg }));
83
+ }
84
+ } catch {
85
+ // Ignore JSON parsing errors
86
+ }
87
+ });
88
+ }
89
+ }
90
+
91
+ /** Type guard for Hyperliquid messages. */
92
+ function isHyperliquidMsg(value: unknown): value is HyperliquidMsg {
93
+ return typeof value === "object" && value !== null &&
94
+ "channel" in value && typeof value.channel === "string";
95
+ }
96
+
97
+ /** Type guard for explorer block messages. */
98
+ function isExplorerBlockMsg(value: unknown): value is Omit<BlockDetails, "txs">[] {
99
+ return Array.isArray(value) && value.length > 0 &&
100
+ (typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0]) &&
101
+ "height" in value[0] && typeof value[0].height === "number" &&
102
+ "blockTime" in value[0] && typeof value[0].blockTime === "number" &&
103
+ "hash" in value[0] && typeof value[0].hash === "string" &&
104
+ "proposer" in value[0] && typeof value[0].proposer === "string" &&
105
+ "numTxs" in value[0] && typeof value[0].numTxs === "number");
106
+ }
107
+
108
+ /** Type guard for explorer transactions messages. */
109
+ function isExplorerTxsMsg(value: unknown): value is TxDetails[] {
110
+ return Array.isArray(value) && value.length > 0 &&
111
+ (typeof value[0] === "object" && value[0] !== null && !Array.isArray(value[0]) &&
112
+ "action" in value[0] && typeof value[0].action === "object" && value[0].action !== null &&
113
+ "block" in value[0] && typeof value[0].block === "number" &&
114
+ "error" in value[0] && (typeof value[0].error === "string" || value[0].error === null) &&
115
+ "hash" in value[0] && typeof value[0].hash === "string" &&
116
+ "time" in value[0] && typeof value[0].time === "number" &&
117
+ "user" in value[0] && typeof value[0].user === "string");
118
+ }
@@ -0,0 +1,404 @@
1
+ // deno-lint-ignore-file no-explicit-any
2
+ import { TransportError } from "../base.js";
3
+
4
+ type MaybePromise<T> = T | Promise<T>;
5
+
6
+ /** Configuration options for the `ReconnectingWebSocket`. */
7
+ export interface ReconnectingWebSocketOptions {
8
+ /**
9
+ * Maximum number of reconnection attempts.
10
+ * @defaultValue `3`
11
+ */
12
+ maxRetries?: number;
13
+
14
+ /**
15
+ * Maximum time in ms to wait for a connection to open.
16
+ * Set to `null` to disable.
17
+ * @defaultValue `10_000`
18
+ */
19
+ connectionTimeout?: number | null;
20
+
21
+ /**
22
+ * Delay between reconnection attempts in ms.
23
+ * May be a number or a function that returns a number.
24
+ * @param attempt - The current attempt number.
25
+ * @defaultValue `(attempt) => Math.min(~~(1 << attempt) * 150, 10_000)` - Exponential backoff (max 10s)
26
+ */
27
+ connectionDelay?: number | ((attempt: number, signal: AbortSignal) => MaybePromise<number>);
28
+
29
+ /**
30
+ * Custom logic to determine if reconnection is required.
31
+ * @param event - The close event that occurred during the connection.
32
+ * @returns A boolean indicating if reconnection should be attempted.
33
+ * @defaultValue `() => true` - Always reconnect
34
+ */
35
+ shouldReconnect?: (event: CloseEvent, signal: AbortSignal) => MaybePromise<boolean>;
36
+
37
+ /**
38
+ * Message buffering strategy between reconnection attempts.
39
+ * @defaultValue `new FIFOMessageBuffer()`
40
+ */
41
+ messageBuffer?: MessageBufferStrategy;
42
+ }
43
+
44
+ /** Message buffer strategy interface. */
45
+ export interface MessageBufferStrategy {
46
+ push(data: string | ArrayBufferLike | Blob | ArrayBufferView, signal?: AbortSignal): void;
47
+ [Symbol.iterator](): Iterator<string | ArrayBufferLike | Blob | ArrayBufferView>;
48
+ }
49
+
50
+ /** Simple FIFO (First In, First Out) buffer implementation. */
51
+ class FIFOMessageBuffer implements MessageBufferStrategy {
52
+ queue: {
53
+ data: string | ArrayBufferLike | Blob | ArrayBufferView;
54
+ signal?: AbortSignal;
55
+ }[] = [];
56
+
57
+ push(data: string | ArrayBufferLike | Blob | ArrayBufferView, signal?: AbortSignal): void {
58
+ this.queue.push({ data, signal });
59
+ }
60
+
61
+ *[Symbol.iterator](): Iterator<string | ArrayBufferLike | Blob | ArrayBufferView> {
62
+ while (this.queue.length > 0) {
63
+ const { data, signal } = this.queue.shift()!;
64
+ if (signal?.aborted) continue;
65
+ yield data;
66
+ }
67
+ }
68
+ }
69
+
70
+ /** Error thrown when reconnection problems occur. */
71
+ export class ReconnectingWebSocketError extends TransportError {
72
+ constructor(
73
+ public code:
74
+ | "RECONNECTION_LIMIT_REACHED"
75
+ | "RECONNECTION_STOPPED_BY_USER"
76
+ | "USER_INITIATED_CLOSE"
77
+ | "UNKNOWN_ERROR",
78
+ cause?: unknown,
79
+ ) {
80
+ super(`Error when reconnecting WebSocket: ${code}`);
81
+ this.name = "ReconnectingWebSocketError";
82
+ this.cause = cause;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * A WebSocket that automatically reconnects when disconnected.
88
+ * Fully compatible with standard WebSocket API.
89
+ */
90
+ export class ReconnectingWebSocket implements WebSocket {
91
+ protected _socket: WebSocket;
92
+ protected _protocols?: string | string[];
93
+ protected _listeners: {
94
+ type: string;
95
+ listener: EventListenerOrEventListenerObject;
96
+ options?: boolean | AddEventListenerOptions;
97
+ listenerProxy: EventListenerOrEventListenerObject;
98
+ }[] = [];
99
+ protected _attempt = 0;
100
+ reconnectOptions: Required<ReconnectingWebSocketOptions>;
101
+ readonly reconnectAbortController: AbortController = new AbortController();
102
+
103
+ constructor(url: string | URL, protocols?: string | string[], options?: ReconnectingWebSocketOptions) {
104
+ this.reconnectOptions = {
105
+ maxRetries: options?.maxRetries ?? 3,
106
+ connectionTimeout: options?.connectionTimeout === undefined ? 10_000 : options.connectionTimeout,
107
+ connectionDelay: options?.connectionDelay ?? ((n) => Math.min(~~(1 << n) * 150, 10_000)),
108
+ shouldReconnect: options?.shouldReconnect ?? (() => true),
109
+ messageBuffer: options?.messageBuffer ?? new FIFOMessageBuffer(),
110
+ };
111
+
112
+ this._socket = this._createSocket(url, protocols);
113
+ this._protocols = protocols;
114
+ this._setupEventListeners();
115
+ }
116
+
117
+ protected _createSocket(url: string | URL, protocols?: string | string[]): WebSocket {
118
+ const socket = new WebSocket(url, protocols);
119
+ if (this.reconnectOptions.connectionTimeout === null) return socket;
120
+
121
+ const timeoutId = setTimeout(() => {
122
+ socket.removeEventListener("open", openHandler);
123
+ socket.removeEventListener("close", closeHandler);
124
+ socket.close(3008, "Timeout"); // https://www.iana.org/assignments/websocket/websocket.xml#close-code-number
125
+ }, this.reconnectOptions.connectionTimeout);
126
+
127
+ const openHandler = () => {
128
+ socket.removeEventListener("close", closeHandler);
129
+ clearTimeout(timeoutId);
130
+ };
131
+ const closeHandler = () => {
132
+ socket.removeEventListener("open", openHandler);
133
+ clearTimeout(timeoutId);
134
+ };
135
+
136
+ socket.addEventListener("open", openHandler, { once: true });
137
+ socket.addEventListener("close", closeHandler, { once: true });
138
+
139
+ return socket;
140
+ }
141
+
142
+ /** Initializes the internal event listeners for the socket. */
143
+ protected _setupEventListeners() {
144
+ this._socket.addEventListener("open", this._open, { once: true });
145
+ this._socket.addEventListener("close", this._close, { once: true });
146
+ }
147
+ protected _open: () => void = () => {
148
+ // Reset the attempt counter
149
+ this._attempt = 0;
150
+
151
+ // Send all buffered messages
152
+ for (const message of this.reconnectOptions.messageBuffer) {
153
+ this._socket.send(message);
154
+ }
155
+ };
156
+ protected _close = async (event: CloseEvent) => {
157
+ try {
158
+ // If the event was triggered but the socket is not closing, ignore it
159
+ if (
160
+ this._socket.readyState !== ReconnectingWebSocket.CLOSING &&
161
+ this._socket.readyState !== ReconnectingWebSocket.CLOSED
162
+ ) return;
163
+
164
+ // If the instance is terminated, do not attempt to reconnect
165
+ if (this.reconnectAbortController.signal.aborted) return;
166
+
167
+ // Check if reconnection should be attempted
168
+ if (++this._attempt > this.reconnectOptions.maxRetries) {
169
+ this._cleanup("RECONNECTION_LIMIT_REACHED");
170
+ return;
171
+ }
172
+
173
+ const userDecision = await this.reconnectOptions.shouldReconnect(
174
+ event,
175
+ this.reconnectAbortController.signal,
176
+ );
177
+ if (this.reconnectAbortController.signal.aborted) return;
178
+ if (!userDecision) {
179
+ this._cleanup("RECONNECTION_STOPPED_BY_USER");
180
+ return;
181
+ }
182
+
183
+ // Delay before reconnecting
184
+ const reconnectDelay = typeof this.reconnectOptions.connectionDelay === "number"
185
+ ? this.reconnectOptions.connectionDelay
186
+ : await this.reconnectOptions.connectionDelay(this._attempt, this.reconnectAbortController.signal);
187
+ if (this.reconnectAbortController.signal.aborted) return;
188
+ await delay(reconnectDelay, this.reconnectAbortController.signal);
189
+
190
+ // Create a new WebSocket instance
191
+ const { onclose, onerror, onmessage, onopen } = this._socket;
192
+ this._socket = this._createSocket(this._socket.url, this._protocols);
193
+
194
+ // Reconnect all listeners
195
+ this._setupEventListeners();
196
+
197
+ this._listeners.forEach(({ type, listenerProxy, options }) => {
198
+ this._socket.addEventListener(type, listenerProxy, options);
199
+ });
200
+
201
+ this._socket.onclose = onclose;
202
+ this._socket.onerror = onerror;
203
+ this._socket.onmessage = onmessage;
204
+ this._socket.onopen = onopen;
205
+ } catch (error) {
206
+ this._cleanup("UNKNOWN_ERROR", error);
207
+ }
208
+ };
209
+
210
+ /** Clean up internal resources. */
211
+ protected _cleanup(code: ConstructorParameters<typeof ReconnectingWebSocketError>[0], cause?: unknown) {
212
+ this.reconnectAbortController.abort(new ReconnectingWebSocketError(code, cause));
213
+ this._listeners = [];
214
+ this._socket.close();
215
+ }
216
+
217
+ // WebSocket property implementations
218
+ get url(): string {
219
+ return this._socket.url;
220
+ }
221
+ get readyState(): number {
222
+ return this._socket.readyState;
223
+ }
224
+ get bufferedAmount(): number {
225
+ return this._socket.bufferedAmount;
226
+ }
227
+ get extensions(): string {
228
+ return this._socket.extensions;
229
+ }
230
+ get protocol(): string {
231
+ return this._socket.protocol;
232
+ }
233
+ get binaryType(): BinaryType {
234
+ return this._socket.binaryType;
235
+ }
236
+ set binaryType(value: BinaryType) {
237
+ this._socket.binaryType = value;
238
+ }
239
+
240
+ readonly CONNECTING = 0;
241
+ readonly OPEN = 1;
242
+ readonly CLOSING = 2;
243
+ readonly CLOSED = 3;
244
+
245
+ static readonly CONNECTING = 0;
246
+ static readonly OPEN = 1;
247
+ static readonly CLOSING = 2;
248
+ static readonly CLOSED = 3;
249
+
250
+ get onclose(): ((this: WebSocket, ev: CloseEvent) => any) | null {
251
+ return this._socket.onclose;
252
+ }
253
+ set onclose(value: ((this: WebSocket, ev: CloseEvent) => any) | null) {
254
+ this._socket.onclose = value;
255
+ }
256
+
257
+ get onerror(): ((this: WebSocket, ev: Event) => any) | null {
258
+ return this._socket.onerror;
259
+ }
260
+ set onerror(value: ((this: WebSocket, ev: Event) => any) | null) {
261
+ this._socket.onerror = value;
262
+ }
263
+
264
+ get onmessage(): ((this: WebSocket, ev: MessageEvent<any>) => any) | null {
265
+ return this._socket.onmessage;
266
+ }
267
+ set onmessage(value: ((this: WebSocket, ev: MessageEvent<any>) => any) | null) {
268
+ this._socket.onmessage = value;
269
+ }
270
+
271
+ get onopen(): ((this: WebSocket, ev: Event) => any) | null {
272
+ return this._socket.onopen;
273
+ }
274
+ set onopen(value: ((this: WebSocket, ev: Event) => any) | null) {
275
+ this._socket.onopen = value;
276
+ }
277
+
278
+ /**
279
+ * @param permanently - If `true`, the connection will be permanently closed. Default is `true`.
280
+ */
281
+ close(code?: number, reason?: string, permanently: boolean = true): void {
282
+ this._socket.close(code, reason);
283
+ if (permanently) this._cleanup("USER_INITIATED_CLOSE");
284
+ }
285
+
286
+ /**
287
+ * @param signal - `AbortSignal` to cancel sending a message if it was in the buffer.
288
+ * @note If the connection is not open, the data will be buffered and sent when the connection is established.
289
+ */
290
+ send(data: string | ArrayBufferLike | Blob | ArrayBufferView, signal?: AbortSignal): void {
291
+ if (signal?.aborted) return;
292
+ if (this._socket.readyState !== ReconnectingWebSocket.OPEN && !this.reconnectAbortController.signal.aborted) {
293
+ this.reconnectOptions.messageBuffer.push(data, signal);
294
+ } else {
295
+ this._socket.send(data);
296
+ }
297
+ }
298
+
299
+ addEventListener<K extends keyof WebSocketEventMap>(
300
+ type: K,
301
+ listener:
302
+ | ((this: ReconnectingWebSocket, ev: WebSocketEventMap[K]) => any)
303
+ | { handleEvent: (event: WebSocketEventMap[K]) => any },
304
+ options?: boolean | AddEventListenerOptions,
305
+ ): void;
306
+ addEventListener(
307
+ type: string,
308
+ listener: EventListenerOrEventListenerObject,
309
+ options?: boolean | AddEventListenerOptions,
310
+ ): void {
311
+ // Wrap the listener to handle reconnection
312
+ let listenerProxy: EventListenerOrEventListenerObject;
313
+ if (this.reconnectAbortController.signal.aborted) {
314
+ // If the instance is terminated, use the original listener
315
+ listenerProxy = listener;
316
+ } else {
317
+ // Check if the listener is already registered
318
+ const index = this._listeners.findIndex((e) => listenersMatch(e, { type, listener, options }));
319
+ if (index !== -1) {
320
+ // Use the existing listener proxy
321
+ listenerProxy = this._listeners[index].listenerProxy;
322
+ } else {
323
+ // Wrap the original listener to follow the once option when reconnecting
324
+ listenerProxy = (event: Event) => {
325
+ try {
326
+ if (typeof listener === "function") {
327
+ listener.call(this, event);
328
+ } else {
329
+ listener.handleEvent(event);
330
+ }
331
+ } finally {
332
+ // If the listener is marked as once, remove it after the first invocation
333
+ if (typeof options === "object" && options.once === true) {
334
+ const index = this._listeners.findIndex((e) =>
335
+ listenersMatch(e, { type, listener, options })
336
+ );
337
+ if (index !== -1) {
338
+ this._listeners.splice(index, 1);
339
+ }
340
+ }
341
+ }
342
+ };
343
+ this._listeners.push({ type, listener, options, listenerProxy });
344
+ }
345
+ }
346
+
347
+ // Add the wrapped (or original) listener
348
+ this._socket.addEventListener(type, listenerProxy, options);
349
+ }
350
+
351
+ removeEventListener<K extends keyof WebSocketEventMap>(
352
+ type: K,
353
+ listener:
354
+ | ((this: ReconnectingWebSocket, ev: WebSocketEventMap[K]) => any)
355
+ | { handleEvent: (event: WebSocketEventMap[K]) => any },
356
+ options?: boolean | EventListenerOptions,
357
+ ): void;
358
+ removeEventListener(
359
+ type: string,
360
+ listener: EventListenerOrEventListenerObject,
361
+ options?: boolean | EventListenerOptions,
362
+ ): void {
363
+ // Remove a wrapped listener, not an original listener
364
+ const index = this._listeners.findIndex((e) => listenersMatch(e, { type, listener, options }));
365
+ if (index !== -1) {
366
+ const { listenerProxy } = this._listeners[index];
367
+ this._socket.removeEventListener(type, listenerProxy, options);
368
+ this._listeners.splice(index, 1);
369
+ } else {
370
+ // If the wrapped listener is not found, remove the original listener
371
+ this._socket.removeEventListener(type, listener, options);
372
+ }
373
+ }
374
+
375
+ dispatchEvent(event: Event): boolean {
376
+ return this._socket.dispatchEvent(event);
377
+ }
378
+ }
379
+
380
+ function listenersMatch(
381
+ a: { type: string; listener: EventListenerOrEventListenerObject; options?: boolean | AddEventListenerOptions },
382
+ b: { type: string; listener: EventListenerOrEventListenerObject; options?: boolean | AddEventListenerOptions },
383
+ ): boolean {
384
+ // EventTarget only compares capture in options, even if one is an object and the other is boolean
385
+ const aCapture = Boolean(typeof a.options === "object" ? a.options.capture : a.options);
386
+ const bCapture = Boolean(typeof b.options === "object" ? b.options.capture : b.options);
387
+ return a.type === b.type && a.listener === b.listener && aCapture === bCapture;
388
+ }
389
+
390
+ function delay(ms: number, signal?: AbortSignal): Promise<void> {
391
+ if (signal?.aborted) return Promise.reject(signal.reason);
392
+ return new Promise((resolve, reject) => {
393
+ const onAbort = () => {
394
+ clearTimeout(timer);
395
+ reject(signal?.reason);
396
+ };
397
+ const onTimeout = () => {
398
+ signal?.removeEventListener("abort", onAbort);
399
+ resolve();
400
+ };
401
+ const timer = setTimeout(onTimeout, ms);
402
+ signal?.addEventListener("abort", onAbort, { once: true });
403
+ });
404
+ }