@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
@@ -1,43 +1,29 @@
1
1
  // deno-lint-ignore-file no-explicit-any
2
2
  import { delay } from "../../../deps/jsr.io/@std/async/1.0.13/delay.js";
3
- import { TransportError } from "../../base.js";
3
+ import { TransportError } from "../base.js";
4
4
  /** Simple FIFO (First In, First Out) buffer implementation. */
5
5
  class FIFOMessageBuffer {
6
- constructor() {
7
- Object.defineProperty(this, "messages", {
8
- enumerable: true,
9
- configurable: true,
10
- writable: true,
11
- value: []
12
- });
6
+ queue = [];
7
+ push(data, signal) {
8
+ this.queue.push({ data, signal });
13
9
  }
14
- push(data) {
15
- this.messages.push(data);
16
- }
17
- shift() {
18
- return this.messages.shift();
19
- }
20
- clear() {
21
- this.messages = [];
10
+ *[Symbol.iterator]() {
11
+ while (this.queue.length > 0) {
12
+ const { data, signal } = this.queue.shift();
13
+ if (signal?.aborted)
14
+ continue;
15
+ yield data;
16
+ }
22
17
  }
23
18
  }
24
19
  /** Error thrown when reconnection problems occur. */
25
20
  export class ReconnectingWebSocketError extends TransportError {
26
- constructor(code, originalError) {
21
+ code;
22
+ constructor(code, cause) {
27
23
  super(`Error when reconnecting WebSocket: ${code}`);
28
- Object.defineProperty(this, "code", {
29
- enumerable: true,
30
- configurable: true,
31
- writable: true,
32
- value: code
33
- });
34
- Object.defineProperty(this, "originalError", {
35
- enumerable: true,
36
- configurable: true,
37
- writable: true,
38
- value: originalError
39
- });
24
+ this.code = code;
40
25
  this.name = "ReconnectingWebSocketError";
26
+ this.cause = cause;
41
27
  }
42
28
  }
43
29
  /**
@@ -45,131 +31,13 @@ export class ReconnectingWebSocketError extends TransportError {
45
31
  * Fully compatible with standard WebSocket API.
46
32
  */
47
33
  export class ReconnectingWebSocket {
34
+ _socket;
35
+ _protocols;
36
+ _listeners = [];
37
+ _attempt = 0;
38
+ reconnectOptions;
39
+ reconnectAbortController = new AbortController();
48
40
  constructor(url, protocols, options) {
49
- Object.defineProperty(this, "_socket", {
50
- enumerable: true,
51
- configurable: true,
52
- writable: true,
53
- value: void 0
54
- });
55
- Object.defineProperty(this, "_protocols", {
56
- enumerable: true,
57
- configurable: true,
58
- writable: true,
59
- value: void 0
60
- });
61
- Object.defineProperty(this, "_listeners", {
62
- enumerable: true,
63
- configurable: true,
64
- writable: true,
65
- value: []
66
- });
67
- Object.defineProperty(this, "_attempt", {
68
- enumerable: true,
69
- configurable: true,
70
- writable: true,
71
- value: 0
72
- });
73
- Object.defineProperty(this, "reconnectOptions", {
74
- enumerable: true,
75
- configurable: true,
76
- writable: true,
77
- value: void 0
78
- });
79
- Object.defineProperty(this, "reconnectAbortController", {
80
- enumerable: true,
81
- configurable: true,
82
- writable: true,
83
- value: new AbortController()
84
- });
85
- Object.defineProperty(this, "_open", {
86
- enumerable: true,
87
- configurable: true,
88
- writable: true,
89
- value: () => {
90
- // Reset the attempt counter
91
- this._attempt = 0;
92
- // Send all buffered messages
93
- let message;
94
- while ((message = this.reconnectOptions.messageBuffer.shift()) !== undefined) {
95
- this._socket.send(message);
96
- }
97
- }
98
- });
99
- Object.defineProperty(this, "_close", {
100
- enumerable: true,
101
- configurable: true,
102
- writable: true,
103
- value: async (event) => {
104
- try {
105
- // If the event was triggered but the socket is not closing, ignore it
106
- if (this._socket.readyState !== ReconnectingWebSocket.CLOSING &&
107
- this._socket.readyState !== ReconnectingWebSocket.CLOSED)
108
- return;
109
- // If the instance is terminated, do not attempt to reconnect
110
- if (this.reconnectAbortController.signal.aborted)
111
- return;
112
- // Check if reconnection should be attempted
113
- if (++this._attempt > this.reconnectOptions.maxRetries) {
114
- this._cleanup("RECONNECTION_LIMIT_REACHED");
115
- return;
116
- }
117
- const userDecision = await this.reconnectOptions.shouldReconnect(event, this.reconnectAbortController.signal);
118
- if (this.reconnectAbortController.signal.aborted)
119
- return;
120
- if (!userDecision) {
121
- this._cleanup("RECONNECTION_STOPPED_BY_USER");
122
- return;
123
- }
124
- // Delay before reconnecting
125
- const reconnectDelay = typeof this.reconnectOptions.connectionDelay === "number"
126
- ? this.reconnectOptions.connectionDelay
127
- : await this.reconnectOptions.connectionDelay(this._attempt, this.reconnectAbortController.signal);
128
- if (this.reconnectAbortController.signal.aborted)
129
- return;
130
- await delay(reconnectDelay, { signal: this.reconnectAbortController.signal });
131
- // Create a new WebSocket instance
132
- const { onclose, onerror, onmessage, onopen } = this._socket;
133
- this._socket = this._createSocket(this._socket.url, this._protocols);
134
- // Reconnect all listeners
135
- this._setupEventListeners();
136
- this._listeners.forEach(({ type, listenerProxy, options }) => {
137
- this._socket.addEventListener(type, listenerProxy, options);
138
- });
139
- this._socket.onclose = onclose;
140
- this._socket.onerror = onerror;
141
- this._socket.onmessage = onmessage;
142
- this._socket.onopen = onopen;
143
- }
144
- catch (error) {
145
- this._cleanup("UNKNOWN_ERROR", error);
146
- }
147
- }
148
- });
149
- Object.defineProperty(this, "CONNECTING", {
150
- enumerable: true,
151
- configurable: true,
152
- writable: true,
153
- value: 0
154
- });
155
- Object.defineProperty(this, "OPEN", {
156
- enumerable: true,
157
- configurable: true,
158
- writable: true,
159
- value: 1
160
- });
161
- Object.defineProperty(this, "CLOSING", {
162
- enumerable: true,
163
- configurable: true,
164
- writable: true,
165
- value: 2
166
- });
167
- Object.defineProperty(this, "CLOSED", {
168
- enumerable: true,
169
- configurable: true,
170
- writable: true,
171
- value: 3
172
- });
173
41
  this.reconnectOptions = {
174
42
  maxRetries: options?.maxRetries ?? 3,
175
43
  connectionTimeout: options?.connectionTimeout === undefined ? 10_000 : options.connectionTimeout,
@@ -207,10 +75,62 @@ export class ReconnectingWebSocket {
207
75
  this._socket.addEventListener("open", this._open, { once: true });
208
76
  this._socket.addEventListener("close", this._close, { once: true });
209
77
  }
78
+ _open = () => {
79
+ // Reset the attempt counter
80
+ this._attempt = 0;
81
+ // Send all buffered messages
82
+ for (const message of this.reconnectOptions.messageBuffer) {
83
+ this._socket.send(message);
84
+ }
85
+ };
86
+ _close = async (event) => {
87
+ try {
88
+ // If the event was triggered but the socket is not closing, ignore it
89
+ if (this._socket.readyState !== ReconnectingWebSocket.CLOSING &&
90
+ this._socket.readyState !== ReconnectingWebSocket.CLOSED)
91
+ return;
92
+ // If the instance is terminated, do not attempt to reconnect
93
+ if (this.reconnectAbortController.signal.aborted)
94
+ return;
95
+ // Check if reconnection should be attempted
96
+ if (++this._attempt > this.reconnectOptions.maxRetries) {
97
+ this._cleanup("RECONNECTION_LIMIT_REACHED");
98
+ return;
99
+ }
100
+ const userDecision = await this.reconnectOptions.shouldReconnect(event, this.reconnectAbortController.signal);
101
+ if (this.reconnectAbortController.signal.aborted)
102
+ return;
103
+ if (!userDecision) {
104
+ this._cleanup("RECONNECTION_STOPPED_BY_USER");
105
+ return;
106
+ }
107
+ // Delay before reconnecting
108
+ const reconnectDelay = typeof this.reconnectOptions.connectionDelay === "number"
109
+ ? this.reconnectOptions.connectionDelay
110
+ : await this.reconnectOptions.connectionDelay(this._attempt, this.reconnectAbortController.signal);
111
+ if (this.reconnectAbortController.signal.aborted)
112
+ return;
113
+ await delay(reconnectDelay, { signal: this.reconnectAbortController.signal });
114
+ // Create a new WebSocket instance
115
+ const { onclose, onerror, onmessage, onopen } = this._socket;
116
+ this._socket = this._createSocket(this._socket.url, this._protocols);
117
+ // Reconnect all listeners
118
+ this._setupEventListeners();
119
+ this._listeners.forEach(({ type, listenerProxy, options }) => {
120
+ this._socket.addEventListener(type, listenerProxy, options);
121
+ });
122
+ this._socket.onclose = onclose;
123
+ this._socket.onerror = onerror;
124
+ this._socket.onmessage = onmessage;
125
+ this._socket.onopen = onopen;
126
+ }
127
+ catch (error) {
128
+ this._cleanup("UNKNOWN_ERROR", error);
129
+ }
130
+ };
210
131
  /** Clean up internal resources. */
211
- _cleanup(code, error) {
212
- this.reconnectAbortController.abort(new ReconnectingWebSocketError(code, error));
213
- this.reconnectOptions.messageBuffer.clear();
132
+ _cleanup(code, cause) {
133
+ this.reconnectAbortController.abort(new ReconnectingWebSocketError(code, cause));
214
134
  this._listeners = [];
215
135
  this._socket.close();
216
136
  }
@@ -236,6 +156,14 @@ export class ReconnectingWebSocket {
236
156
  set binaryType(value) {
237
157
  this._socket.binaryType = value;
238
158
  }
159
+ CONNECTING = 0;
160
+ OPEN = 1;
161
+ CLOSING = 2;
162
+ CLOSED = 3;
163
+ static CONNECTING = 0;
164
+ static OPEN = 1;
165
+ static CLOSING = 2;
166
+ static CLOSED = 3;
239
167
  get onclose() {
240
168
  return this._socket.onclose;
241
169
  }
@@ -269,11 +197,14 @@ export class ReconnectingWebSocket {
269
197
  this._cleanup("USER_INITIATED_CLOSE");
270
198
  }
271
199
  /**
200
+ * @param signal - `AbortSignal` to cancel sending a message if it was in the buffer.
272
201
  * @note If the connection is not open, the data will be buffered and sent when the connection is established.
273
202
  */
274
- send(data) {
203
+ send(data, signal) {
204
+ if (signal?.aborted)
205
+ return;
275
206
  if (this._socket.readyState !== ReconnectingWebSocket.OPEN && !this.reconnectAbortController.signal.aborted) {
276
- this.reconnectOptions.messageBuffer.push(data);
207
+ this.reconnectOptions.messageBuffer.push(data, signal);
277
208
  }
278
209
  else {
279
210
  this._socket.send(data);
@@ -337,30 +268,6 @@ export class ReconnectingWebSocket {
337
268
  return this._socket.dispatchEvent(event);
338
269
  }
339
270
  }
340
- Object.defineProperty(ReconnectingWebSocket, "CONNECTING", {
341
- enumerable: true,
342
- configurable: true,
343
- writable: true,
344
- value: 0
345
- });
346
- Object.defineProperty(ReconnectingWebSocket, "OPEN", {
347
- enumerable: true,
348
- configurable: true,
349
- writable: true,
350
- value: 1
351
- });
352
- Object.defineProperty(ReconnectingWebSocket, "CLOSING", {
353
- enumerable: true,
354
- configurable: true,
355
- writable: true,
356
- value: 2
357
- });
358
- Object.defineProperty(ReconnectingWebSocket, "CLOSED", {
359
- enumerable: true,
360
- configurable: true,
361
- writable: true,
362
- value: 3
363
- });
364
271
  /** Check if two event listeners are the same (just like EventTarget). */
365
272
  function listenersMatch(a, b) {
366
273
  // 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,177 @@
1
+ import { TransportError } from "../base.js";
2
+ /**
3
+ * Error thrown when a WebSocket request fails:
4
+ * - When the WebSocket connection is closed
5
+ * - When the server responds with an error message
6
+ */
7
+ export class WebSocketRequestError extends TransportError {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "WebSocketRequestError";
11
+ }
12
+ }
13
+ /**
14
+ * Manages WebSocket requests to the Hyperliquid API.
15
+ * Handles request creation, sending, and mapping responses to their corresponding requests.
16
+ */
17
+ export class WebSocketAsyncRequest {
18
+ socket;
19
+ lastId = 0;
20
+ queue = [];
21
+ lastRequestTime = 0;
22
+ /**
23
+ * Creates a new WebSocket async request handler.
24
+ * @param socket - WebSocket connection instance for sending requests to the Hyperliquid WebSocket API
25
+ * @param hlEvents - Used to recognize Hyperliquid responses and match them with sent requests
26
+ */
27
+ constructor(socket, hlEvents) {
28
+ this.socket = socket;
29
+ // Monitor responses and match the pending request
30
+ hlEvents.addEventListener("subscriptionResponse", (event) => {
31
+ // Use a stringified request as an id
32
+ const id = WebSocketAsyncRequest.requestToId(event.detail);
33
+ this.queue.findLast((item) => item.id === id)?.resolve(event.detail);
34
+ });
35
+ hlEvents.addEventListener("post", (event) => {
36
+ const data = event.detail.response.type === "info"
37
+ ? event.detail.response.payload.data
38
+ : event.detail.response.payload;
39
+ this.queue.findLast((item) => item.id === event.detail.id)?.resolve(data);
40
+ });
41
+ hlEvents.addEventListener("pong", () => {
42
+ this.queue.findLast((item) => item.id === "ping")?.resolve();
43
+ });
44
+ hlEvents.addEventListener("error", (event) => {
45
+ try {
46
+ // Error event doesn't have an id, use original request to match
47
+ const request = event.detail.match(/{.*}/)?.[0];
48
+ if (!request)
49
+ return;
50
+ const parsedRequest = JSON.parse(request);
51
+ // For `post` requests
52
+ if ("id" in parsedRequest && typeof parsedRequest.id === "number") {
53
+ this.queue.findLast((item) => item.id === parsedRequest.id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
54
+ return;
55
+ }
56
+ // For `subscribe` and `unsubscribe` requests
57
+ if ("subscription" in parsedRequest &&
58
+ typeof parsedRequest.subscription === "object" && parsedRequest.subscription !== null) {
59
+ const id = WebSocketAsyncRequest.requestToId(parsedRequest);
60
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
61
+ return;
62
+ }
63
+ // For already/invalid subscribed requests
64
+ if (event.detail.startsWith("Already subscribed") || event.detail.startsWith("Invalid subscription")) {
65
+ const id = WebSocketAsyncRequest.requestToId({
66
+ method: "subscribe",
67
+ subscription: parsedRequest,
68
+ });
69
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
70
+ return;
71
+ }
72
+ // For already unsubscribed requests
73
+ if (event.detail.startsWith("Already unsubscribed")) {
74
+ const id = WebSocketAsyncRequest.requestToId({
75
+ method: "unsubscribe",
76
+ subscription: parsedRequest,
77
+ });
78
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
79
+ return;
80
+ }
81
+ // For unknown requests
82
+ const id = WebSocketAsyncRequest.requestToId(parsedRequest);
83
+ this.queue.findLast((item) => item.id === id)?.reject(new WebSocketRequestError(`Cannot complete WebSocket request: ${event.detail}`));
84
+ }
85
+ catch {
86
+ // Ignore JSON parsing errors
87
+ }
88
+ });
89
+ // Throws all pending requests if the connection is dropped
90
+ socket.addEventListener("close", () => {
91
+ this.queue.forEach(({ reject }) => {
92
+ reject(new WebSocketRequestError("Cannot complete WebSocket request: connection is closed"));
93
+ });
94
+ this.queue = [];
95
+ });
96
+ }
97
+ async request(method, payload_or_signal, maybeSignal) {
98
+ const payload = payload_or_signal instanceof AbortSignal ? undefined : payload_or_signal;
99
+ const signal = payload_or_signal instanceof AbortSignal ? payload_or_signal : maybeSignal;
100
+ // Reject the request if the signal is aborted
101
+ if (signal?.aborted)
102
+ return Promise.reject(signal.reason);
103
+ // Create a request
104
+ let id;
105
+ let request;
106
+ if (method === "post") {
107
+ id = ++this.lastId;
108
+ request = { method, id, request: payload };
109
+ }
110
+ else if (method === "ping") {
111
+ id = "ping";
112
+ request = { method };
113
+ }
114
+ else {
115
+ request = { method, subscription: payload };
116
+ id = WebSocketAsyncRequest.requestToId(request);
117
+ }
118
+ // Send the request
119
+ this.socket.send(JSON.stringify(request), signal);
120
+ this.lastRequestTime = Date.now();
121
+ // Wait for a response
122
+ const { promise, resolve, reject } = Promise.withResolvers();
123
+ this.queue.push({ id, resolve, reject });
124
+ const onAbort = () => reject(signal?.reason);
125
+ signal?.addEventListener("abort", onAbort, { once: true });
126
+ return await promise.finally(() => {
127
+ const index = this.queue.findLastIndex((item) => item.id === id);
128
+ if (index !== -1)
129
+ this.queue.splice(index, 1);
130
+ signal?.removeEventListener("abort", onAbort);
131
+ });
132
+ }
133
+ /** Normalizes an object and then converts it to a string. */
134
+ static requestToId(value) {
135
+ const lowerHex = containsUppercaseHex(value) ? deepLowerHex(value) : value;
136
+ const sorted = deepSortKeys(lowerHex);
137
+ return JSON.stringify(sorted); // Also removes undefined
138
+ }
139
+ }
140
+ /** Deeply converts hexadecimal strings in an object/array to lowercase. */
141
+ function deepLowerHex(obj) {
142
+ if (typeof obj === "string") {
143
+ return /^(0X[0-9a-fA-F]*|0x[0-9a-fA-F]*[A-F][0-9a-fA-F]*)$/.test(obj) ? obj.toLowerCase() : obj;
144
+ }
145
+ if (Array.isArray(obj)) {
146
+ return obj.map(deepLowerHex);
147
+ }
148
+ if (typeof obj === "object" && obj !== null) {
149
+ const result = {};
150
+ const entries = Object.entries(obj);
151
+ for (const [key, value] of entries) {
152
+ result[key] = deepLowerHex(value);
153
+ }
154
+ return result;
155
+ }
156
+ return obj;
157
+ }
158
+ /** Check if an object contains uppercase hexadecimal strings. */
159
+ function containsUppercaseHex(obj) {
160
+ const str = JSON.stringify(obj);
161
+ return /0X[0-9a-fA-F]*|0x[0-9a-fA-F]*[A-F][0-9a-fA-F]*/.test(str);
162
+ }
163
+ /** Deeply sort the keys of an object. */
164
+ function deepSortKeys(obj) {
165
+ if (typeof obj !== "object" || obj === null) {
166
+ return obj;
167
+ }
168
+ if (Array.isArray(obj)) {
169
+ return obj.map(deepSortKeys);
170
+ }
171
+ const result = {};
172
+ const keys = Object.keys(obj).sort();
173
+ for (const key of keys) {
174
+ result[key] = deepSortKeys(obj[key]);
175
+ }
176
+ return result;
177
+ }