@nktkas/hyperliquid 0.19.2 → 0.21.0

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 (129) hide show
  1. package/README.md +61 -79
  2. package/esm/mod.d.ts +5 -3
  3. package/esm/mod.d.ts.map +1 -1
  4. package/esm/mod.js +4 -3
  5. package/esm/src/base.d.ts +1 -47
  6. package/esm/src/base.d.ts.map +1 -1
  7. package/esm/src/base.js +1 -8
  8. package/{script/src/clients/wallet.d.ts → esm/src/clients/exchange.d.ts} +111 -112
  9. package/esm/src/clients/exchange.d.ts.map +1 -0
  10. package/esm/src/clients/{wallet.js → exchange.js} +96 -306
  11. package/esm/src/clients/{public.d.ts → info.d.ts} +121 -102
  12. package/esm/src/clients/info.d.ts.map +1 -0
  13. package/esm/src/clients/{public.js → info.js} +73 -57
  14. package/{script/src/clients/event.d.ts → esm/src/clients/subscription.d.ts} +61 -80
  15. package/esm/src/clients/subscription.d.ts.map +1 -0
  16. package/esm/src/clients/{event.js → subscription.js} +106 -136
  17. package/esm/src/signing.d.ts +4 -0
  18. package/esm/src/signing.d.ts.map +1 -1
  19. package/esm/src/signing.js +23 -5
  20. package/esm/src/transports/base.d.ts +47 -0
  21. package/esm/src/transports/base.d.ts.map +1 -0
  22. package/esm/src/transports/base.js +8 -0
  23. package/esm/src/transports/http/http_transport.d.ts +8 -5
  24. package/esm/src/transports/http/http_transport.d.ts.map +1 -1
  25. package/esm/src/transports/http/http_transport.js +15 -62
  26. package/esm/src/transports/websocket/_hyperliquid_event_target.d.ts +36 -39
  27. package/esm/src/transports/websocket/_hyperliquid_event_target.d.ts.map +1 -1
  28. package/esm/src/transports/websocket/_reconnecting_websocket.d.ts +21 -27
  29. package/esm/src/transports/websocket/_reconnecting_websocket.d.ts.map +1 -1
  30. package/esm/src/transports/websocket/_reconnecting_websocket.js +89 -182
  31. package/{script/src/transports/websocket/_websocket_request_dispatcher.d.ts → esm/src/transports/websocket/_websocket_async_request.d.ts} +17 -19
  32. package/esm/src/transports/websocket/_websocket_async_request.d.ts.map +1 -0
  33. package/esm/src/transports/websocket/_websocket_async_request.js +177 -0
  34. package/esm/src/transports/websocket/websocket_transport.d.ts +54 -41
  35. package/esm/src/transports/websocket/websocket_transport.d.ts.map +1 -1
  36. package/esm/src/transports/websocket/websocket_transport.js +101 -113
  37. package/esm/src/types/info/accounts.d.ts +1 -0
  38. package/esm/src/types/info/accounts.d.ts.map +1 -1
  39. package/esm/src/types/info/assets.d.ts +0 -48
  40. package/esm/src/types/info/assets.d.ts.map +1 -1
  41. package/esm/src/types/info/markets.d.ts +52 -0
  42. package/esm/src/types/info/markets.d.ts.map +1 -0
  43. package/esm/src/types/info/markets.js +1 -0
  44. package/esm/src/types/info/requests.d.ts +9 -0
  45. package/esm/src/types/info/requests.d.ts.map +1 -1
  46. package/esm/src/types/mod.d.ts +3 -0
  47. package/esm/src/types/mod.d.ts.map +1 -1
  48. package/esm/src/types/mod.js +2 -0
  49. package/package.json +12 -9
  50. package/script/mod.d.ts +5 -3
  51. package/script/mod.d.ts.map +1 -1
  52. package/script/mod.js +5 -4
  53. package/script/src/base.d.ts +1 -47
  54. package/script/src/base.d.ts.map +1 -1
  55. package/script/src/base.js +2 -10
  56. package/{esm/src/clients/wallet.d.ts → script/src/clients/exchange.d.ts} +111 -112
  57. package/script/src/clients/exchange.d.ts.map +1 -0
  58. package/script/src/clients/{wallet.js → exchange.js} +98 -308
  59. package/script/src/clients/{public.d.ts → info.d.ts} +121 -102
  60. package/script/src/clients/info.d.ts.map +1 -0
  61. package/script/src/clients/{public.js → info.js} +75 -59
  62. package/{esm/src/clients/event.d.ts → script/src/clients/subscription.d.ts} +61 -80
  63. package/script/src/clients/subscription.d.ts.map +1 -0
  64. package/script/src/clients/{event.js → subscription.js} +108 -138
  65. package/script/src/signing.d.ts +4 -0
  66. package/script/src/signing.d.ts.map +1 -1
  67. package/script/src/signing.js +25 -7
  68. package/script/src/transports/base.d.ts +47 -0
  69. package/script/src/transports/base.d.ts.map +1 -0
  70. package/script/src/transports/base.js +22 -0
  71. package/script/src/transports/http/http_transport.d.ts +8 -5
  72. package/script/src/transports/http/http_transport.d.ts.map +1 -1
  73. package/script/src/transports/http/http_transport.js +16 -63
  74. package/script/src/transports/websocket/_hyperliquid_event_target.d.ts +36 -39
  75. package/script/src/transports/websocket/_hyperliquid_event_target.d.ts.map +1 -1
  76. package/script/src/transports/websocket/_reconnecting_websocket.d.ts +21 -27
  77. package/script/src/transports/websocket/_reconnecting_websocket.d.ts.map +1 -1
  78. package/script/src/transports/websocket/_reconnecting_websocket.js +90 -183
  79. package/{esm/src/transports/websocket/_websocket_request_dispatcher.d.ts → script/src/transports/websocket/_websocket_async_request.d.ts} +17 -19
  80. package/script/src/transports/websocket/_websocket_async_request.d.ts.map +1 -0
  81. package/script/src/transports/websocket/_websocket_async_request.js +192 -0
  82. package/script/src/transports/websocket/websocket_transport.d.ts +54 -41
  83. package/script/src/transports/websocket/websocket_transport.d.ts.map +1 -1
  84. package/script/src/transports/websocket/websocket_transport.js +103 -115
  85. package/script/src/types/info/accounts.d.ts +1 -0
  86. package/script/src/types/info/accounts.d.ts.map +1 -1
  87. package/script/src/types/info/assets.d.ts +0 -48
  88. package/script/src/types/info/assets.d.ts.map +1 -1
  89. package/script/src/types/info/markets.d.ts +52 -0
  90. package/script/src/types/info/markets.d.ts.map +1 -0
  91. package/script/{deps/jsr.io/@noble/hashes/1.8.0/src/crypto.js → src/types/info/markets.js} +0 -2
  92. package/script/src/types/info/requests.d.ts +9 -0
  93. package/script/src/types/info/requests.d.ts.map +1 -1
  94. package/script/src/types/mod.d.ts +3 -0
  95. package/script/src/types/mod.d.ts.map +1 -1
  96. package/script/src/types/mod.js +2 -0
  97. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/_u64.d.ts +0 -55
  98. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/_u64.d.ts.map +0 -1
  99. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/_u64.js +0 -66
  100. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/crypto.d.ts +0 -2
  101. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/crypto.d.ts.map +0 -1
  102. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/crypto.js +0 -1
  103. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/sha3.d.ts +0 -53
  104. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/sha3.d.ts.map +0 -1
  105. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/sha3.js +0 -294
  106. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/utils.d.ts +0 -161
  107. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/utils.d.ts.map +0 -1
  108. package/esm/deps/jsr.io/@noble/hashes/1.8.0/src/utils.js +0 -280
  109. package/esm/src/clients/event.d.ts.map +0 -1
  110. package/esm/src/clients/public.d.ts.map +0 -1
  111. package/esm/src/clients/wallet.d.ts.map +0 -1
  112. package/esm/src/transports/websocket/_websocket_request_dispatcher.d.ts.map +0 -1
  113. package/esm/src/transports/websocket/_websocket_request_dispatcher.js +0 -191
  114. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/_u64.d.ts +0 -55
  115. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/_u64.d.ts.map +0 -1
  116. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/_u64.js +0 -99
  117. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/crypto.d.ts +0 -2
  118. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/crypto.d.ts.map +0 -1
  119. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/sha3.d.ts +0 -53
  120. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/sha3.d.ts.map +0 -1
  121. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/sha3.js +0 -309
  122. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/utils.d.ts +0 -161
  123. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/utils.d.ts.map +0 -1
  124. package/script/deps/jsr.io/@noble/hashes/1.8.0/src/utils.js +0 -322
  125. package/script/src/clients/event.d.ts.map +0 -1
  126. package/script/src/clients/public.d.ts.map +0 -1
  127. package/script/src/clients/wallet.d.ts.map +0 -1
  128. package/script/src/transports/websocket/_websocket_request_dispatcher.d.ts.map +0 -1
  129. package/script/src/transports/websocket/_websocket_request_dispatcher.js +0 -206
@@ -4,7 +4,7 @@
4
4
  if (v !== undefined) module.exports = v;
5
5
  }
6
6
  else if (typeof define === "function" && define.amd) {
7
- define(["require", "exports", "../../../deps/jsr.io/@std/async/1.0.13/delay.js", "../../base.js"], factory);
7
+ define(["require", "exports", "../../../deps/jsr.io/@std/async/1.0.13/delay.js", "../base.js"], factory);
8
8
  }
9
9
  })(function (require, exports) {
10
10
  "use strict";
@@ -12,44 +12,30 @@
12
12
  exports.ReconnectingWebSocket = exports.ReconnectingWebSocketError = void 0;
13
13
  // deno-lint-ignore-file no-explicit-any
14
14
  const delay_js_1 = require("../../../deps/jsr.io/@std/async/1.0.13/delay.js");
15
- const base_js_1 = require("../../base.js");
15
+ const base_js_1 = require("../base.js");
16
16
  /** Simple FIFO (First In, First Out) buffer implementation. */
17
17
  class FIFOMessageBuffer {
18
- constructor() {
19
- Object.defineProperty(this, "messages", {
20
- enumerable: true,
21
- configurable: true,
22
- writable: true,
23
- value: []
24
- });
18
+ queue = [];
19
+ push(data, signal) {
20
+ this.queue.push({ data, signal });
25
21
  }
26
- push(data) {
27
- this.messages.push(data);
28
- }
29
- shift() {
30
- return this.messages.shift();
31
- }
32
- clear() {
33
- this.messages = [];
22
+ *[Symbol.iterator]() {
23
+ while (this.queue.length > 0) {
24
+ const { data, signal } = this.queue.shift();
25
+ if (signal?.aborted)
26
+ continue;
27
+ yield data;
28
+ }
34
29
  }
35
30
  }
36
31
  /** Error thrown when reconnection problems occur. */
37
32
  class ReconnectingWebSocketError extends base_js_1.TransportError {
38
- constructor(code, originalError) {
33
+ code;
34
+ constructor(code, cause) {
39
35
  super(`Error when reconnecting WebSocket: ${code}`);
40
- Object.defineProperty(this, "code", {
41
- enumerable: true,
42
- configurable: true,
43
- writable: true,
44
- value: code
45
- });
46
- Object.defineProperty(this, "originalError", {
47
- enumerable: true,
48
- configurable: true,
49
- writable: true,
50
- value: originalError
51
- });
36
+ this.code = code;
52
37
  this.name = "ReconnectingWebSocketError";
38
+ this.cause = cause;
53
39
  }
54
40
  }
55
41
  exports.ReconnectingWebSocketError = ReconnectingWebSocketError;
@@ -58,131 +44,13 @@
58
44
  * Fully compatible with standard WebSocket API.
59
45
  */
60
46
  class ReconnectingWebSocket {
47
+ _socket;
48
+ _protocols;
49
+ _listeners = [];
50
+ _attempt = 0;
51
+ reconnectOptions;
52
+ reconnectAbortController = new AbortController();
61
53
  constructor(url, protocols, options) {
62
- Object.defineProperty(this, "_socket", {
63
- enumerable: true,
64
- configurable: true,
65
- writable: true,
66
- value: void 0
67
- });
68
- Object.defineProperty(this, "_protocols", {
69
- enumerable: true,
70
- configurable: true,
71
- writable: true,
72
- value: void 0
73
- });
74
- Object.defineProperty(this, "_listeners", {
75
- enumerable: true,
76
- configurable: true,
77
- writable: true,
78
- value: []
79
- });
80
- Object.defineProperty(this, "_attempt", {
81
- enumerable: true,
82
- configurable: true,
83
- writable: true,
84
- value: 0
85
- });
86
- Object.defineProperty(this, "reconnectOptions", {
87
- enumerable: true,
88
- configurable: true,
89
- writable: true,
90
- value: void 0
91
- });
92
- Object.defineProperty(this, "reconnectAbortController", {
93
- enumerable: true,
94
- configurable: true,
95
- writable: true,
96
- value: new AbortController()
97
- });
98
- Object.defineProperty(this, "_open", {
99
- enumerable: true,
100
- configurable: true,
101
- writable: true,
102
- value: () => {
103
- // Reset the attempt counter
104
- this._attempt = 0;
105
- // Send all buffered messages
106
- let message;
107
- while ((message = this.reconnectOptions.messageBuffer.shift()) !== undefined) {
108
- this._socket.send(message);
109
- }
110
- }
111
- });
112
- Object.defineProperty(this, "_close", {
113
- enumerable: true,
114
- configurable: true,
115
- writable: true,
116
- value: async (event) => {
117
- try {
118
- // If the event was triggered but the socket is not closing, ignore it
119
- if (this._socket.readyState !== ReconnectingWebSocket.CLOSING &&
120
- this._socket.readyState !== ReconnectingWebSocket.CLOSED)
121
- return;
122
- // If the instance is terminated, do not attempt to reconnect
123
- if (this.reconnectAbortController.signal.aborted)
124
- return;
125
- // Check if reconnection should be attempted
126
- if (++this._attempt > this.reconnectOptions.maxRetries) {
127
- this._cleanup("RECONNECTION_LIMIT_REACHED");
128
- return;
129
- }
130
- const userDecision = await this.reconnectOptions.shouldReconnect(event, this.reconnectAbortController.signal);
131
- if (this.reconnectAbortController.signal.aborted)
132
- return;
133
- if (!userDecision) {
134
- this._cleanup("RECONNECTION_STOPPED_BY_USER");
135
- return;
136
- }
137
- // Delay before reconnecting
138
- const reconnectDelay = typeof this.reconnectOptions.connectionDelay === "number"
139
- ? this.reconnectOptions.connectionDelay
140
- : await this.reconnectOptions.connectionDelay(this._attempt, this.reconnectAbortController.signal);
141
- if (this.reconnectAbortController.signal.aborted)
142
- return;
143
- await (0, delay_js_1.delay)(reconnectDelay, { signal: this.reconnectAbortController.signal });
144
- // Create a new WebSocket instance
145
- const { onclose, onerror, onmessage, onopen } = this._socket;
146
- this._socket = this._createSocket(this._socket.url, this._protocols);
147
- // Reconnect all listeners
148
- this._setupEventListeners();
149
- this._listeners.forEach(({ type, listenerProxy, options }) => {
150
- this._socket.addEventListener(type, listenerProxy, options);
151
- });
152
- this._socket.onclose = onclose;
153
- this._socket.onerror = onerror;
154
- this._socket.onmessage = onmessage;
155
- this._socket.onopen = onopen;
156
- }
157
- catch (error) {
158
- this._cleanup("UNKNOWN_ERROR", error);
159
- }
160
- }
161
- });
162
- Object.defineProperty(this, "CONNECTING", {
163
- enumerable: true,
164
- configurable: true,
165
- writable: true,
166
- value: 0
167
- });
168
- Object.defineProperty(this, "OPEN", {
169
- enumerable: true,
170
- configurable: true,
171
- writable: true,
172
- value: 1
173
- });
174
- Object.defineProperty(this, "CLOSING", {
175
- enumerable: true,
176
- configurable: true,
177
- writable: true,
178
- value: 2
179
- });
180
- Object.defineProperty(this, "CLOSED", {
181
- enumerable: true,
182
- configurable: true,
183
- writable: true,
184
- value: 3
185
- });
186
54
  this.reconnectOptions = {
187
55
  maxRetries: options?.maxRetries ?? 3,
188
56
  connectionTimeout: options?.connectionTimeout === undefined ? 10_000 : options.connectionTimeout,
@@ -220,10 +88,62 @@
220
88
  this._socket.addEventListener("open", this._open, { once: true });
221
89
  this._socket.addEventListener("close", this._close, { once: true });
222
90
  }
91
+ _open = () => {
92
+ // Reset the attempt counter
93
+ this._attempt = 0;
94
+ // Send all buffered messages
95
+ for (const message of this.reconnectOptions.messageBuffer) {
96
+ this._socket.send(message);
97
+ }
98
+ };
99
+ _close = async (event) => {
100
+ try {
101
+ // If the event was triggered but the socket is not closing, ignore it
102
+ if (this._socket.readyState !== ReconnectingWebSocket.CLOSING &&
103
+ this._socket.readyState !== ReconnectingWebSocket.CLOSED)
104
+ return;
105
+ // If the instance is terminated, do not attempt to reconnect
106
+ if (this.reconnectAbortController.signal.aborted)
107
+ return;
108
+ // Check if reconnection should be attempted
109
+ if (++this._attempt > this.reconnectOptions.maxRetries) {
110
+ this._cleanup("RECONNECTION_LIMIT_REACHED");
111
+ return;
112
+ }
113
+ const userDecision = await this.reconnectOptions.shouldReconnect(event, this.reconnectAbortController.signal);
114
+ if (this.reconnectAbortController.signal.aborted)
115
+ return;
116
+ if (!userDecision) {
117
+ this._cleanup("RECONNECTION_STOPPED_BY_USER");
118
+ return;
119
+ }
120
+ // Delay before reconnecting
121
+ const reconnectDelay = typeof this.reconnectOptions.connectionDelay === "number"
122
+ ? this.reconnectOptions.connectionDelay
123
+ : await this.reconnectOptions.connectionDelay(this._attempt, this.reconnectAbortController.signal);
124
+ if (this.reconnectAbortController.signal.aborted)
125
+ return;
126
+ await (0, delay_js_1.delay)(reconnectDelay, { signal: this.reconnectAbortController.signal });
127
+ // Create a new WebSocket instance
128
+ const { onclose, onerror, onmessage, onopen } = this._socket;
129
+ this._socket = this._createSocket(this._socket.url, this._protocols);
130
+ // Reconnect all listeners
131
+ this._setupEventListeners();
132
+ this._listeners.forEach(({ type, listenerProxy, options }) => {
133
+ this._socket.addEventListener(type, listenerProxy, options);
134
+ });
135
+ this._socket.onclose = onclose;
136
+ this._socket.onerror = onerror;
137
+ this._socket.onmessage = onmessage;
138
+ this._socket.onopen = onopen;
139
+ }
140
+ catch (error) {
141
+ this._cleanup("UNKNOWN_ERROR", error);
142
+ }
143
+ };
223
144
  /** Clean up internal resources. */
224
- _cleanup(code, error) {
225
- this.reconnectAbortController.abort(new ReconnectingWebSocketError(code, error));
226
- this.reconnectOptions.messageBuffer.clear();
145
+ _cleanup(code, cause) {
146
+ this.reconnectAbortController.abort(new ReconnectingWebSocketError(code, cause));
227
147
  this._listeners = [];
228
148
  this._socket.close();
229
149
  }
@@ -249,6 +169,14 @@
249
169
  set binaryType(value) {
250
170
  this._socket.binaryType = value;
251
171
  }
172
+ CONNECTING = 0;
173
+ OPEN = 1;
174
+ CLOSING = 2;
175
+ CLOSED = 3;
176
+ static CONNECTING = 0;
177
+ static OPEN = 1;
178
+ static CLOSING = 2;
179
+ static CLOSED = 3;
252
180
  get onclose() {
253
181
  return this._socket.onclose;
254
182
  }
@@ -282,11 +210,14 @@
282
210
  this._cleanup("USER_INITIATED_CLOSE");
283
211
  }
284
212
  /**
213
+ * @param signal - `AbortSignal` to cancel sending a message if it was in the buffer.
285
214
  * @note If the connection is not open, the data will be buffered and sent when the connection is established.
286
215
  */
287
- send(data) {
216
+ send(data, signal) {
217
+ if (signal?.aborted)
218
+ return;
288
219
  if (this._socket.readyState !== ReconnectingWebSocket.OPEN && !this.reconnectAbortController.signal.aborted) {
289
- this.reconnectOptions.messageBuffer.push(data);
220
+ this.reconnectOptions.messageBuffer.push(data, signal);
290
221
  }
291
222
  else {
292
223
  this._socket.send(data);
@@ -351,30 +282,6 @@
351
282
  }
352
283
  }
353
284
  exports.ReconnectingWebSocket = ReconnectingWebSocket;
354
- Object.defineProperty(ReconnectingWebSocket, "CONNECTING", {
355
- enumerable: true,
356
- configurable: true,
357
- writable: true,
358
- value: 0
359
- });
360
- Object.defineProperty(ReconnectingWebSocket, "OPEN", {
361
- enumerable: true,
362
- configurable: true,
363
- writable: true,
364
- value: 1
365
- });
366
- Object.defineProperty(ReconnectingWebSocket, "CLOSING", {
367
- enumerable: true,
368
- configurable: true,
369
- writable: true,
370
- value: 2
371
- });
372
- Object.defineProperty(ReconnectingWebSocket, "CLOSED", {
373
- enumerable: true,
374
- configurable: true,
375
- writable: true,
376
- value: 3
377
- });
378
285
  /** Check if two event listeners are the same (just like EventTarget). */
379
286
  function listenersMatch(a, b) {
380
287
  // EventTarget only compares capture in options, even if one is an object and the other is boolean
@@ -1,4 +1,5 @@
1
- import { TransportError } from "../../base.js";
1
+ import { TransportError } from "../base.js";
2
+ import type { ReconnectingWebSocket } from "./_reconnecting_websocket.js";
2
3
  import type { HyperliquidEventTarget } from "./_hyperliquid_event_target.js";
3
4
  /**
4
5
  * Error thrown when a WebSocket request fails:
@@ -12,31 +13,28 @@ export declare class WebSocketRequestError extends TransportError {
12
13
  * Manages WebSocket requests to the Hyperliquid API.
13
14
  * Handles request creation, sending, and mapping responses to their corresponding requests.
14
15
  */
15
- export declare class WebSocketRequestDispatcher {
16
- private socket;
17
- /** Last used post request ID */
18
- private lastId;
19
- /** Map of pending requests waiting for responses */
20
- private pending;
16
+ export declare class WebSocketAsyncRequest {
17
+ protected socket: ReconnectingWebSocket;
18
+ protected lastId: number;
19
+ protected queue: {
20
+ id: number | string;
21
+ resolve: (value?: any) => void;
22
+ reject: (reason?: any) => void;
23
+ }[];
24
+ lastRequestTime: number;
21
25
  /**
22
- * Creates a new WebSocket request dispatcher.
26
+ * Creates a new WebSocket async request handler.
23
27
  * @param socket - WebSocket connection instance for sending requests to the Hyperliquid WebSocket API
24
28
  * @param hlEvents - Used to recognize Hyperliquid responses and match them with sent requests
25
29
  */
26
- constructor(socket: WebSocket, hlEvents: HyperliquidEventTarget);
30
+ constructor(socket: ReconnectingWebSocket, hlEvents: HyperliquidEventTarget);
27
31
  /**
28
32
  * Sends a request to the Hyperliquid API.
29
- * @param method - The method of websocket request.
30
- * @param payload - The payload to send with the request.
31
- * @param signal - An optional abort signal.
32
33
  * @returns A promise that resolves with the parsed JSON response body.
33
34
  */
34
- request(method: "post" | "subscribe" | "unsubscribe", payload: unknown, signal?: AbortSignal): Promise<unknown>;
35
- /**
36
- * Normalizes a request object to an ID.
37
- * @param value - A request object.
38
- * @returns A stringified request.
39
- */
35
+ request(method: "ping", signal?: AbortSignal): Promise<void>;
36
+ request<T>(method: "post" | "subscribe" | "unsubscribe", payload: unknown, signal?: AbortSignal): Promise<T>;
37
+ /** Normalizes an object and then converts it to a string. */
40
38
  static requestToId(value: unknown): string;
41
39
  }
42
- //# sourceMappingURL=_websocket_request_dispatcher.d.ts.map
40
+ //# sourceMappingURL=_websocket_async_request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_websocket_async_request.d.ts","sourceRoot":"","sources":["../../../../src/src/transports/websocket/_websocket_async_request.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AAiB7E;;;;GAIG;AACH,qBAAa,qBAAsB,SAAQ,cAAc;gBACzC,OAAO,EAAE,MAAM;CAI9B;AAED;;;GAGG;AACH,qBAAa,qBAAqB;IAgBlB,SAAS,CAAC,MAAM,EAAE,qBAAqB;IAfnD,SAAS,CAAC,MAAM,EAAE,MAAM,CAAK;IAC7B,SAAS,CAAC,KAAK,EAAE;QACb,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;QAEpB,OAAO,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;QAE/B,MAAM,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;KAClC,EAAE,CAAM;IACT,eAAe,EAAE,MAAM,CAAK;IAE5B;;;;OAIG;gBACmB,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,sBAAsB;IAsFrF;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAC5D,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,GAAG,WAAW,GAAG,aAAa,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC;IA6ClH,6DAA6D;IAC7D,MAAM,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM;CAK7C"}
@@ -0,0 +1,192 @@
1
+ (function (factory) {
2
+ if (typeof module === "object" && typeof module.exports === "object") {
3
+ var v = factory(require, exports);
4
+ if (v !== undefined) module.exports = v;
5
+ }
6
+ else if (typeof define === "function" && define.amd) {
7
+ define(["require", "exports", "../base.js"], factory);
8
+ }
9
+ })(function (require, exports) {
10
+ "use strict";
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.WebSocketAsyncRequest = exports.WebSocketRequestError = void 0;
13
+ const base_js_1 = require("../base.js");
14
+ /**
15
+ * Error thrown when a WebSocket request fails:
16
+ * - When the WebSocket connection is closed
17
+ * - When the server responds with an error message
18
+ */
19
+ class WebSocketRequestError extends base_js_1.TransportError {
20
+ constructor(message) {
21
+ super(message);
22
+ this.name = "WebSocketRequestError";
23
+ }
24
+ }
25
+ exports.WebSocketRequestError = WebSocketRequestError;
26
+ /**
27
+ * Manages WebSocket requests to the Hyperliquid API.
28
+ * Handles request creation, sending, and mapping responses to their corresponding requests.
29
+ */
30
+ class WebSocketAsyncRequest {
31
+ socket;
32
+ lastId = 0;
33
+ queue = [];
34
+ lastRequestTime = 0;
35
+ /**
36
+ * Creates a new WebSocket async request handler.
37
+ * @param socket - WebSocket connection instance for sending requests to the Hyperliquid WebSocket API
38
+ * @param hlEvents - Used to recognize Hyperliquid responses and match them with sent requests
39
+ */
40
+ constructor(socket, hlEvents) {
41
+ this.socket = socket;
42
+ // Monitor responses and match the pending request
43
+ hlEvents.addEventListener("subscriptionResponse", (event) => {
44
+ // Use a stringified request as an id
45
+ const id = WebSocketAsyncRequest.requestToId(event.detail);
46
+ this.queue.findLast((item) => item.id === id)?.resolve(event.detail);
47
+ });
48
+ hlEvents.addEventListener("post", (event) => {
49
+ const data = event.detail.response.type === "info"
50
+ ? event.detail.response.payload.data
51
+ : event.detail.response.payload;
52
+ this.queue.findLast((item) => item.id === event.detail.id)?.resolve(data);
53
+ });
54
+ hlEvents.addEventListener("pong", () => {
55
+ this.queue.findLast((item) => item.id === "ping")?.resolve();
56
+ });
57
+ hlEvents.addEventListener("error", (event) => {
58
+ try {
59
+ // Error event doesn't have an id, use original request to match
60
+ const request = event.detail.match(/{.*}/)?.[0];
61
+ if (!request)
62
+ return;
63
+ const parsedRequest = JSON.parse(request);
64
+ // For `post` requests
65
+ if ("id" in parsedRequest && typeof parsedRequest.id === "number") {
66
+ this.queue.findLast((item) => item.id === parsedRequest.id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
67
+ return;
68
+ }
69
+ // For `subscribe` and `unsubscribe` requests
70
+ if ("subscription" in parsedRequest &&
71
+ typeof parsedRequest.subscription === "object" && parsedRequest.subscription !== null) {
72
+ const id = WebSocketAsyncRequest.requestToId(parsedRequest);
73
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
74
+ return;
75
+ }
76
+ // For already/invalid subscribed requests
77
+ if (event.detail.startsWith("Already subscribed") || event.detail.startsWith("Invalid subscription")) {
78
+ const id = WebSocketAsyncRequest.requestToId({
79
+ method: "subscribe",
80
+ subscription: parsedRequest,
81
+ });
82
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
83
+ return;
84
+ }
85
+ // For already unsubscribed requests
86
+ if (event.detail.startsWith("Already unsubscribed")) {
87
+ const id = WebSocketAsyncRequest.requestToId({
88
+ method: "unsubscribe",
89
+ subscription: parsedRequest,
90
+ });
91
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
92
+ return;
93
+ }
94
+ // For unknown requests
95
+ const id = WebSocketAsyncRequest.requestToId(parsedRequest);
96
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
97
+ }
98
+ catch {
99
+ // Ignore JSON parsing errors
100
+ }
101
+ });
102
+ // Throws all pending requests if the connection is dropped
103
+ socket.addEventListener("close", () => {
104
+ this.queue.forEach(({ reject }) => {
105
+ reject(new WebSocketRequestError("Cannot complete WebSocket request: connection is closed"));
106
+ });
107
+ this.queue = [];
108
+ });
109
+ }
110
+ async request(method, payload_or_signal, maybeSignal) {
111
+ const payload = payload_or_signal instanceof AbortSignal ? undefined : payload_or_signal;
112
+ const signal = payload_or_signal instanceof AbortSignal ? payload_or_signal : maybeSignal;
113
+ // Reject the request if the signal is aborted
114
+ if (signal?.aborted)
115
+ return Promise.reject(signal.reason);
116
+ // Create a request
117
+ let id;
118
+ let request;
119
+ if (method === "post") {
120
+ id = ++this.lastId;
121
+ request = { method, id, request: payload };
122
+ }
123
+ else if (method === "ping") {
124
+ id = "ping";
125
+ request = { method };
126
+ }
127
+ else {
128
+ request = { method, subscription: payload };
129
+ id = WebSocketAsyncRequest.requestToId(request);
130
+ }
131
+ // Send the request
132
+ this.socket.send(JSON.stringify(request), signal);
133
+ this.lastRequestTime = Date.now();
134
+ // Wait for a response
135
+ const { promise, resolve, reject } = Promise.withResolvers();
136
+ this.queue.push({ id, resolve, reject });
137
+ const onAbort = () => reject(signal?.reason);
138
+ signal?.addEventListener("abort", onAbort, { once: true });
139
+ return await promise.finally(() => {
140
+ const index = this.queue.findLastIndex((item) => item.id === id);
141
+ if (index !== -1)
142
+ this.queue.splice(index, 1);
143
+ signal?.removeEventListener("abort", onAbort);
144
+ });
145
+ }
146
+ /** Normalizes an object and then converts it to a string. */
147
+ static requestToId(value) {
148
+ const lowerHex = containsUppercaseHex(value) ? deepLowerHex(value) : value;
149
+ const sorted = deepSortKeys(lowerHex);
150
+ return JSON.stringify(sorted); // Also removes undefined
151
+ }
152
+ }
153
+ exports.WebSocketAsyncRequest = WebSocketAsyncRequest;
154
+ /** Deeply converts hexadecimal strings in an object/array to lowercase. */
155
+ function deepLowerHex(obj) {
156
+ if (typeof obj === "string") {
157
+ return /^(0X[0-9a-fA-F]*|0x[0-9a-fA-F]*[A-F][0-9a-fA-F]*)$/.test(obj) ? obj.toLowerCase() : obj;
158
+ }
159
+ if (Array.isArray(obj)) {
160
+ return obj.map(deepLowerHex);
161
+ }
162
+ if (typeof obj === "object" && obj !== null) {
163
+ const result = {};
164
+ const entries = Object.entries(obj);
165
+ for (const [key, value] of entries) {
166
+ result[key] = deepLowerHex(value);
167
+ }
168
+ return result;
169
+ }
170
+ return obj;
171
+ }
172
+ /** Check if an object contains uppercase hexadecimal strings. */
173
+ function containsUppercaseHex(obj) {
174
+ const str = JSON.stringify(obj);
175
+ return /0X[0-9a-fA-F]*|0x[0-9a-fA-F]*[A-F][0-9a-fA-F]*/.test(str);
176
+ }
177
+ /** Deeply sort the keys of an object. */
178
+ function deepSortKeys(obj) {
179
+ if (typeof obj !== "object" || obj === null) {
180
+ return obj;
181
+ }
182
+ if (Array.isArray(obj)) {
183
+ return obj.map(deepSortKeys);
184
+ }
185
+ const result = {};
186
+ const keys = Object.keys(obj).sort();
187
+ for (const key of keys) {
188
+ result[key] = deepSortKeys(obj[key]);
189
+ }
190
+ return result;
191
+ }
192
+ });