@nktkas/hyperliquid 0.24.3 → 0.25.0-beta.2
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.
- package/README.md +141 -32
- package/esm/mod.d.ts +11 -11
- package/esm/mod.d.ts.map +1 -1
- package/esm/mod.js +2 -1
- package/esm/mod.js.map +1 -0
- package/esm/src/clients/exchange.d.ts +29 -25
- package/esm/src/clients/exchange.d.ts.map +1 -1
- package/esm/src/clients/exchange.js +54 -53
- package/esm/src/clients/exchange.js.map +1 -0
- package/esm/src/clients/info.d.ts +80 -20
- package/esm/src/clients/info.d.ts.map +1 -1
- package/esm/src/clients/info.js +312 -73
- package/esm/src/clients/info.js.map +1 -0
- package/esm/src/clients/multiSign.d.ts +11 -7
- package/esm/src/clients/multiSign.d.ts.map +1 -1
- package/esm/src/clients/multiSign.js +2 -1
- package/esm/src/clients/multiSign.js.map +1 -0
- package/esm/src/clients/subscription.d.ts +9 -9
- package/esm/src/clients/subscription.d.ts.map +1 -1
- package/esm/src/clients/subscription.js +29 -27
- package/esm/src/clients/subscription.js.map +1 -0
- package/esm/src/{base.d.ts → errors.d.ts} +1 -1
- package/esm/src/errors.d.ts.map +1 -0
- package/esm/src/{base.js → errors.js} +1 -0
- package/esm/src/errors.js.map +1 -0
- package/esm/src/schemas/_base.d.ts +14 -0
- package/esm/src/schemas/_base.d.ts.map +1 -0
- package/esm/src/schemas/_base.js +15 -0
- package/esm/src/schemas/_base.js.map +1 -0
- package/esm/src/schemas/exchange/requests.d.ts +8953 -0
- package/esm/src/schemas/exchange/requests.d.ts.map +1 -0
- package/esm/src/schemas/exchange/requests.js +1414 -0
- package/esm/src/schemas/exchange/requests.js.map +1 -0
- package/esm/src/schemas/exchange/responses.d.ts +567 -0
- package/esm/src/schemas/exchange/responses.d.ts.map +1 -0
- package/esm/src/schemas/exchange/responses.js +244 -0
- package/esm/src/schemas/exchange/responses.js.map +1 -0
- package/esm/src/schemas/explorer/requests.d.ts +44 -0
- package/esm/src/schemas/explorer/requests.d.ts.map +1 -0
- package/esm/src/schemas/explorer/requests.js +33 -0
- package/esm/src/schemas/explorer/requests.js.map +1 -0
- package/esm/src/schemas/explorer/responses.d.ts +360 -0
- package/esm/src/schemas/explorer/responses.d.ts.map +1 -0
- package/esm/src/schemas/explorer/responses.js +57 -0
- package/esm/src/schemas/explorer/responses.js.map +1 -0
- package/esm/src/schemas/info/accounts.d.ts +2159 -0
- package/esm/src/schemas/info/accounts.d.ts.map +1 -0
- package/esm/src/schemas/info/accounts.js +623 -0
- package/esm/src/schemas/info/accounts.js.map +1 -0
- package/esm/src/schemas/info/assets.d.ts +974 -0
- package/esm/src/schemas/info/assets.d.ts.map +1 -0
- package/esm/src/schemas/info/assets.js +285 -0
- package/esm/src/schemas/info/assets.js.map +1 -0
- package/esm/src/schemas/info/markets.d.ts +155 -0
- package/esm/src/schemas/info/markets.d.ts.map +1 -0
- package/esm/src/schemas/info/markets.js +70 -0
- package/esm/src/schemas/info/markets.js.map +1 -0
- package/esm/src/schemas/info/orders.d.ts +957 -0
- package/esm/src/schemas/info/orders.d.ts.map +1 -0
- package/esm/src/schemas/info/orders.js +298 -0
- package/esm/src/schemas/info/orders.js.map +1 -0
- package/esm/src/schemas/info/requests.d.ts +924 -0
- package/esm/src/schemas/info/requests.d.ts.map +1 -0
- package/esm/src/schemas/info/requests.js +687 -0
- package/esm/src/schemas/info/requests.js.map +1 -0
- package/esm/src/schemas/info/validators.d.ts +326 -0
- package/esm/src/schemas/info/validators.d.ts.map +1 -0
- package/esm/src/schemas/info/validators.js +126 -0
- package/esm/src/schemas/info/validators.js.map +1 -0
- package/esm/src/schemas/info/vaults.d.ts +447 -0
- package/esm/src/schemas/info/vaults.d.ts.map +1 -0
- package/esm/src/schemas/info/vaults.js +111 -0
- package/esm/src/schemas/info/vaults.js.map +1 -0
- package/esm/src/schemas/mod.d.ts +101 -0
- package/esm/src/schemas/mod.d.ts.map +1 -0
- package/esm/src/schemas/mod.js +115 -0
- package/esm/src/schemas/mod.js.map +1 -0
- package/esm/src/schemas/subscriptions/requests.d.ts +332 -0
- package/esm/src/schemas/subscriptions/requests.d.ts.map +1 -0
- package/esm/src/schemas/subscriptions/requests.js +259 -0
- package/esm/src/schemas/subscriptions/requests.js.map +1 -0
- package/esm/src/schemas/subscriptions/responses.d.ts +3643 -0
- package/esm/src/schemas/subscriptions/responses.d.ts.map +1 -0
- package/esm/src/schemas/subscriptions/responses.js +234 -0
- package/esm/src/schemas/subscriptions/responses.js.map +1 -0
- package/esm/src/signing/_signTypedData/ethers.js +1 -0
- package/esm/src/signing/_signTypedData/ethers.js.map +1 -0
- package/esm/src/signing/_signTypedData/mod.js +1 -0
- package/esm/src/signing/_signTypedData/mod.js.map +1 -0
- package/esm/src/signing/_signTypedData/private_key.js +1 -0
- package/esm/src/signing/_signTypedData/private_key.js.map +1 -0
- package/esm/src/signing/_signTypedData/viem.js +1 -0
- package/esm/src/signing/_signTypedData/viem.js.map +1 -0
- package/esm/src/signing/mod.d.ts +109 -29
- package/esm/src/signing/mod.d.ts.map +1 -1
- package/esm/src/signing/mod.js +136 -29
- package/esm/src/signing/mod.js.map +1 -0
- package/esm/src/transports/base.d.ts +4 -4
- package/esm/src/transports/base.d.ts.map +1 -1
- package/esm/src/transports/base.js +3 -2
- package/esm/src/transports/base.js.map +1 -0
- package/esm/src/transports/http/http_transport.d.ts +3 -2
- package/esm/src/transports/http/http_transport.d.ts.map +1 -1
- package/esm/src/transports/http/http_transport.js +1 -0
- package/esm/src/transports/http/http_transport.js.map +1 -0
- package/esm/src/transports/websocket/_hyperliquid_event_target.d.ts +1 -1
- package/esm/src/transports/websocket/_hyperliquid_event_target.d.ts.map +1 -1
- package/esm/src/transports/websocket/_hyperliquid_event_target.js +1 -0
- package/esm/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
- package/esm/src/transports/websocket/_reconnecting_websocket.js +1 -0
- package/esm/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
- package/esm/src/transports/websocket/_websocket_async_request.js +1 -0
- package/esm/src/transports/websocket/_websocket_async_request.js.map +1 -0
- package/esm/src/transports/websocket/websocket_transport.d.ts +9 -1
- package/esm/src/transports/websocket/websocket_transport.d.ts.map +1 -1
- package/esm/src/transports/websocket/websocket_transport.js +4 -0
- package/esm/src/transports/websocket/websocket_transport.js.map +1 -0
- package/package.json +6 -5
- package/script/mod.d.ts +11 -11
- package/script/mod.d.ts.map +1 -1
- package/script/mod.js +2 -1
- package/script/mod.js.map +1 -0
- package/script/src/clients/exchange.d.ts +29 -25
- package/script/src/clients/exchange.d.ts.map +1 -1
- package/script/src/clients/exchange.js +60 -59
- package/script/src/clients/exchange.js.map +1 -0
- package/script/src/clients/info.d.ts +80 -20
- package/script/src/clients/info.d.ts.map +1 -1
- package/script/src/clients/info.js +312 -73
- package/script/src/clients/info.js.map +1 -0
- package/script/src/clients/multiSign.d.ts +11 -7
- package/script/src/clients/multiSign.d.ts.map +1 -1
- package/script/src/clients/multiSign.js +2 -1
- package/script/src/clients/multiSign.js.map +1 -0
- package/script/src/clients/subscription.d.ts +9 -9
- package/script/src/clients/subscription.d.ts.map +1 -1
- package/script/src/clients/subscription.js +29 -27
- package/script/src/clients/subscription.js.map +1 -0
- package/script/src/{base.d.ts → errors.d.ts} +1 -1
- package/script/src/errors.d.ts.map +1 -0
- package/script/src/{base.js → errors.js} +1 -0
- package/script/src/errors.js.map +1 -0
- package/script/src/schemas/_base.d.ts +14 -0
- package/script/src/schemas/_base.d.ts.map +1 -0
- package/script/src/schemas/_base.js +51 -0
- package/script/src/schemas/_base.js.map +1 -0
- package/script/src/schemas/exchange/requests.d.ts +8953 -0
- package/script/src/schemas/exchange/requests.d.ts.map +1 -0
- package/script/src/schemas/exchange/requests.js +1450 -0
- package/script/src/schemas/exchange/requests.js.map +1 -0
- package/script/src/schemas/exchange/responses.d.ts +567 -0
- package/script/src/schemas/exchange/responses.d.ts.map +1 -0
- package/script/src/schemas/exchange/responses.js +280 -0
- package/script/src/schemas/exchange/responses.js.map +1 -0
- package/script/src/schemas/explorer/requests.d.ts +44 -0
- package/script/src/schemas/explorer/requests.d.ts.map +1 -0
- package/script/src/schemas/explorer/requests.js +69 -0
- package/script/src/schemas/explorer/requests.js.map +1 -0
- package/script/src/schemas/explorer/responses.d.ts +360 -0
- package/script/src/schemas/explorer/responses.d.ts.map +1 -0
- package/script/src/schemas/explorer/responses.js +93 -0
- package/script/src/schemas/explorer/responses.js.map +1 -0
- package/script/src/schemas/info/accounts.d.ts +2159 -0
- package/script/src/schemas/info/accounts.d.ts.map +1 -0
- package/script/src/schemas/info/accounts.js +659 -0
- package/script/src/schemas/info/accounts.js.map +1 -0
- package/script/src/schemas/info/assets.d.ts +974 -0
- package/script/src/schemas/info/assets.d.ts.map +1 -0
- package/script/src/schemas/info/assets.js +321 -0
- package/script/src/schemas/info/assets.js.map +1 -0
- package/script/src/schemas/info/markets.d.ts +155 -0
- package/script/src/schemas/info/markets.d.ts.map +1 -0
- package/script/src/schemas/info/markets.js +106 -0
- package/script/src/schemas/info/markets.js.map +1 -0
- package/script/src/schemas/info/orders.d.ts +957 -0
- package/script/src/schemas/info/orders.d.ts.map +1 -0
- package/script/src/schemas/info/orders.js +334 -0
- package/script/src/schemas/info/orders.js.map +1 -0
- package/script/src/schemas/info/requests.d.ts +924 -0
- package/script/src/schemas/info/requests.d.ts.map +1 -0
- package/script/src/schemas/info/requests.js +724 -0
- package/script/src/schemas/info/requests.js.map +1 -0
- package/script/src/schemas/info/validators.d.ts +326 -0
- package/script/src/schemas/info/validators.d.ts.map +1 -0
- package/script/src/schemas/info/validators.js +162 -0
- package/script/src/schemas/info/validators.js.map +1 -0
- package/script/src/schemas/info/vaults.d.ts +447 -0
- package/script/src/schemas/info/vaults.d.ts.map +1 -0
- package/script/src/schemas/info/vaults.js +147 -0
- package/script/src/schemas/info/vaults.js.map +1 -0
- package/script/src/schemas/mod.d.ts +101 -0
- package/script/src/schemas/mod.d.ts.map +1 -0
- package/script/src/schemas/mod.js +157 -0
- package/script/src/schemas/mod.js.map +1 -0
- package/script/src/schemas/subscriptions/requests.d.ts +332 -0
- package/script/src/schemas/subscriptions/requests.d.ts.map +1 -0
- package/script/src/schemas/subscriptions/requests.js +295 -0
- package/script/src/schemas/subscriptions/requests.js.map +1 -0
- package/script/src/schemas/subscriptions/responses.d.ts +3643 -0
- package/script/src/schemas/subscriptions/responses.d.ts.map +1 -0
- package/script/src/schemas/subscriptions/responses.js +270 -0
- package/script/src/schemas/subscriptions/responses.js.map +1 -0
- package/script/src/signing/_signTypedData/ethers.js +1 -0
- package/script/src/signing/_signTypedData/ethers.js.map +1 -0
- package/script/src/signing/_signTypedData/mod.js +1 -0
- package/script/src/signing/_signTypedData/mod.js.map +1 -0
- package/script/src/signing/_signTypedData/private_key.js +1 -0
- package/script/src/signing/_signTypedData/private_key.js.map +1 -0
- package/script/src/signing/_signTypedData/viem.js +1 -0
- package/script/src/signing/_signTypedData/viem.js.map +1 -0
- package/script/src/signing/mod.d.ts +109 -29
- package/script/src/signing/mod.d.ts.map +1 -1
- package/script/src/signing/mod.js +138 -33
- package/script/src/signing/mod.js.map +1 -0
- package/script/src/transports/base.d.ts +4 -4
- package/script/src/transports/base.d.ts.map +1 -1
- package/script/src/transports/base.js +4 -3
- package/script/src/transports/base.js.map +1 -0
- package/script/src/transports/http/http_transport.d.ts +3 -2
- package/script/src/transports/http/http_transport.d.ts.map +1 -1
- package/script/src/transports/http/http_transport.js +1 -0
- package/script/src/transports/http/http_transport.js.map +1 -0
- package/script/src/transports/websocket/_hyperliquid_event_target.d.ts +1 -1
- package/script/src/transports/websocket/_hyperliquid_event_target.d.ts.map +1 -1
- package/script/src/transports/websocket/_hyperliquid_event_target.js +1 -0
- package/script/src/transports/websocket/_hyperliquid_event_target.js.map +1 -0
- package/script/src/transports/websocket/_reconnecting_websocket.js +1 -0
- package/script/src/transports/websocket/_reconnecting_websocket.js.map +1 -0
- package/script/src/transports/websocket/_websocket_async_request.js +1 -0
- package/script/src/transports/websocket/_websocket_async_request.js.map +1 -0
- package/script/src/transports/websocket/websocket_transport.d.ts +9 -1
- package/script/src/transports/websocket/websocket_transport.d.ts.map +1 -1
- package/script/src/transports/websocket/websocket_transport.js +4 -0
- package/script/src/transports/websocket/websocket_transport.js.map +1 -0
- package/src/mod.ts +28 -0
- package/src/src/clients/exchange.ts +2246 -0
- package/src/src/clients/info.ts +2076 -0
- package/src/src/clients/multiSign.ts +183 -0
- package/src/src/clients/subscription.ts +841 -0
- package/src/src/errors.ts +7 -0
- package/src/src/schemas/_base.ts +43 -0
- package/src/src/schemas/exchange/requests.ts +3057 -0
- package/src/src/schemas/exchange/responses.ts +540 -0
- package/src/src/schemas/explorer/requests.ts +65 -0
- package/src/src/schemas/explorer/responses.ts +138 -0
- package/src/src/schemas/info/accounts.ts +1490 -0
- package/src/src/schemas/info/assets.ts +693 -0
- package/src/src/schemas/info/markets.ts +171 -0
- package/src/src/schemas/info/orders.ts +597 -0
- package/src/src/schemas/info/requests.ts +1369 -0
- package/src/src/schemas/info/validators.ts +299 -0
- package/src/src/schemas/info/vaults.ts +262 -0
- package/src/src/schemas/mod.ts +121 -0
- package/src/src/schemas/subscriptions/requests.ts +504 -0
- package/src/src/schemas/subscriptions/responses.ts +576 -0
- package/src/src/signing/_signTypedData/ethers.ts +59 -0
- package/src/src/signing/_signTypedData/mod.ts +121 -0
- package/src/src/signing/_signTypedData/private_key.ts +229 -0
- package/src/src/signing/_signTypedData/viem.ts +55 -0
- package/src/src/signing/mod.ts +572 -0
- package/src/src/transports/base.ts +54 -0
- package/src/src/transports/http/http_transport.ts +208 -0
- package/src/src/transports/websocket/_hyperliquid_event_target.ts +118 -0
- package/src/src/transports/websocket/_reconnecting_websocket.ts +404 -0
- package/src/src/transports/websocket/_websocket_async_request.ts +229 -0
- package/src/src/transports/websocket/websocket_transport.ts +394 -0
- package/esm/src/base.d.ts.map +0 -1
- package/esm/src/signing/_sorter.d.ts +0 -127
- package/esm/src/signing/_sorter.d.ts.map +0 -1
- package/esm/src/signing/_sorter.js +0 -693
- package/esm/src/types/exchange/requests.d.ts +0 -1345
- package/esm/src/types/exchange/requests.d.ts.map +0 -1
- package/esm/src/types/exchange/requests.js +0 -1
- package/esm/src/types/exchange/responses.d.ts +0 -233
- package/esm/src/types/exchange/responses.d.ts.map +0 -1
- package/esm/src/types/exchange/responses.js +0 -1
- package/esm/src/types/explorer/requests.d.ts +0 -32
- package/esm/src/types/explorer/requests.d.ts.map +0 -1
- package/esm/src/types/explorer/requests.js +0 -1
- package/esm/src/types/explorer/responses.d.ts +0 -58
- package/esm/src/types/explorer/responses.d.ts.map +0 -1
- package/esm/src/types/explorer/responses.js +0 -1
- package/esm/src/types/info/accounts.d.ts +0 -864
- package/esm/src/types/info/accounts.d.ts.map +0 -1
- package/esm/src/types/info/accounts.js +0 -1
- package/esm/src/types/info/assets.d.ts +0 -354
- package/esm/src/types/info/assets.d.ts.map +0 -1
- package/esm/src/types/info/assets.js +0 -1
- package/esm/src/types/info/markets.d.ts +0 -79
- package/esm/src/types/info/markets.d.ts.map +0 -1
- package/esm/src/types/info/markets.js +0 -1
- package/esm/src/types/info/orders.d.ts +0 -266
- package/esm/src/types/info/orders.d.ts.map +0 -1
- package/esm/src/types/info/orders.js +0 -1
- package/esm/src/types/info/requests.d.ts +0 -640
- package/esm/src/types/info/requests.d.ts.map +0 -1
- package/esm/src/types/info/requests.js +0 -1
- package/esm/src/types/info/validators.d.ts +0 -147
- package/esm/src/types/info/validators.d.ts.map +0 -1
- package/esm/src/types/info/validators.js +0 -1
- package/esm/src/types/info/vaults.d.ts +0 -119
- package/esm/src/types/info/vaults.d.ts.map +0 -1
- package/esm/src/types/info/vaults.js +0 -1
- package/esm/src/types/mod.d.ts +0 -38
- package/esm/src/types/mod.d.ts.map +0 -1
- package/esm/src/types/mod.js +0 -24
- package/esm/src/types/subscriptions/requests.d.ts +0 -154
- package/esm/src/types/subscriptions/requests.d.ts.map +0 -1
- package/esm/src/types/subscriptions/requests.js +0 -1
- package/esm/src/types/subscriptions/responses.d.ts +0 -238
- package/esm/src/types/subscriptions/responses.d.ts.map +0 -1
- package/esm/src/types/subscriptions/responses.js +0 -1
- package/script/src/base.d.ts.map +0 -1
- package/script/src/signing/_sorter.d.ts +0 -127
- package/script/src/signing/_sorter.d.ts.map +0 -1
- package/script/src/signing/_sorter.js +0 -696
- package/script/src/types/exchange/requests.d.ts +0 -1345
- package/script/src/types/exchange/requests.d.ts.map +0 -1
- package/script/src/types/exchange/requests.js +0 -2
- package/script/src/types/exchange/responses.d.ts +0 -233
- package/script/src/types/exchange/responses.d.ts.map +0 -1
- package/script/src/types/exchange/responses.js +0 -2
- package/script/src/types/explorer/requests.d.ts +0 -32
- package/script/src/types/explorer/requests.d.ts.map +0 -1
- package/script/src/types/explorer/requests.js +0 -2
- package/script/src/types/explorer/responses.d.ts +0 -58
- package/script/src/types/explorer/responses.d.ts.map +0 -1
- package/script/src/types/explorer/responses.js +0 -2
- package/script/src/types/info/accounts.d.ts +0 -864
- package/script/src/types/info/accounts.d.ts.map +0 -1
- package/script/src/types/info/accounts.js +0 -2
- package/script/src/types/info/assets.d.ts +0 -354
- package/script/src/types/info/assets.d.ts.map +0 -1
- package/script/src/types/info/assets.js +0 -2
- package/script/src/types/info/markets.d.ts +0 -79
- package/script/src/types/info/markets.d.ts.map +0 -1
- package/script/src/types/info/markets.js +0 -2
- package/script/src/types/info/orders.d.ts +0 -266
- package/script/src/types/info/orders.d.ts.map +0 -1
- package/script/src/types/info/orders.js +0 -2
- package/script/src/types/info/requests.d.ts +0 -640
- package/script/src/types/info/requests.d.ts.map +0 -1
- package/script/src/types/info/requests.js +0 -2
- package/script/src/types/info/validators.d.ts +0 -147
- package/script/src/types/info/validators.d.ts.map +0 -1
- package/script/src/types/info/validators.js +0 -2
- package/script/src/types/info/vaults.d.ts +0 -119
- package/script/src/types/info/vaults.d.ts.map +0 -1
- package/script/src/types/info/vaults.js +0 -2
- package/script/src/types/mod.d.ts +0 -38
- package/script/src/types/mod.d.ts.map +0 -1
- package/script/src/types/mod.js +0 -25
- package/script/src/types/subscriptions/requests.d.ts +0 -154
- package/script/src/types/subscriptions/requests.d.ts.map +0 -1
- package/script/src/types/subscriptions/requests.js +0 -2
- package/script/src/types/subscriptions/responses.d.ts +0 -238
- package/script/src/types/subscriptions/responses.d.ts.map +0 -1
- package/script/src/types/subscriptions/responses.js +0 -2
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import type { ReconnectingWebSocket } from "./_reconnecting_websocket.js";
|
|
2
|
+
import type { HyperliquidEventMap, HyperliquidEventTarget } from "./_hyperliquid_event_target.js";
|
|
3
|
+
import { WebSocketRequestError } from "./websocket_transport.js";
|
|
4
|
+
|
|
5
|
+
interface PostRequest {
|
|
6
|
+
method: "post";
|
|
7
|
+
id: number;
|
|
8
|
+
request: unknown;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface SubscribeUnsubscribeRequest {
|
|
12
|
+
method: "subscribe" | "unsubscribe";
|
|
13
|
+
subscription: unknown;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface PingRequest {
|
|
17
|
+
method: "ping";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Manages WebSocket requests to the Hyperliquid API.
|
|
22
|
+
* Handles request creation, sending, and mapping responses to their corresponding requests.
|
|
23
|
+
*/
|
|
24
|
+
export class WebSocketAsyncRequest {
|
|
25
|
+
protected lastId: number = 0;
|
|
26
|
+
protected queue: {
|
|
27
|
+
id: number | string;
|
|
28
|
+
// deno-lint-ignore no-explicit-any
|
|
29
|
+
resolve: (value?: any) => void;
|
|
30
|
+
// deno-lint-ignore no-explicit-any
|
|
31
|
+
reject: (reason?: any) => void;
|
|
32
|
+
}[] = [];
|
|
33
|
+
lastRequestTime: number = 0;
|
|
34
|
+
|
|
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(protected socket: ReconnectingWebSocket, hlEvents: HyperliquidEventTarget) {
|
|
41
|
+
// Monitor responses and match the pending request
|
|
42
|
+
hlEvents.addEventListener("subscriptionResponse", (event) => {
|
|
43
|
+
const detail = (event as HyperliquidEventMap["subscriptionResponse"]).detail;
|
|
44
|
+
|
|
45
|
+
// Use a stringified request as an id
|
|
46
|
+
const id = WebSocketAsyncRequest.requestToId(detail);
|
|
47
|
+
this.queue.findLast((item) => item.id === id)?.resolve(detail);
|
|
48
|
+
});
|
|
49
|
+
hlEvents.addEventListener("post", (event) => {
|
|
50
|
+
const detail = (event as HyperliquidEventMap["post"]).detail;
|
|
51
|
+
|
|
52
|
+
const data = detail.response.type === "info" ? detail.response.payload.data : detail.response.payload;
|
|
53
|
+
this.queue.findLast((item) => item.id === detail.id)?.resolve(data);
|
|
54
|
+
});
|
|
55
|
+
hlEvents.addEventListener("pong", () => {
|
|
56
|
+
this.queue.findLast((item) => item.id === "ping")?.resolve();
|
|
57
|
+
});
|
|
58
|
+
hlEvents.addEventListener("error", (event) => {
|
|
59
|
+
const detail = (event as HyperliquidEventMap["error"]).detail;
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
// Error event doesn't have an id, use original request to match
|
|
63
|
+
const request = detail.match(/{.*}/)?.[0];
|
|
64
|
+
if (!request) return;
|
|
65
|
+
|
|
66
|
+
const parsedRequest = JSON.parse(request) as Record<string, unknown>;
|
|
67
|
+
|
|
68
|
+
// For `post` requests
|
|
69
|
+
if ("id" in parsedRequest && typeof parsedRequest.id === "number") {
|
|
70
|
+
this.queue.findLast((item) => item.id === parsedRequest.id)
|
|
71
|
+
?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// For `subscribe` and `unsubscribe` requests
|
|
76
|
+
if (
|
|
77
|
+
"subscription" in parsedRequest &&
|
|
78
|
+
typeof parsedRequest.subscription === "object" && parsedRequest.subscription !== null
|
|
79
|
+
) {
|
|
80
|
+
const id = WebSocketAsyncRequest.requestToId(parsedRequest);
|
|
81
|
+
this.queue.findLast((item) => item.id === id)
|
|
82
|
+
?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// For `Already subscribed` and `Invalid subscription` requests
|
|
87
|
+
if (detail.startsWith("Already subscribed") || detail.startsWith("Invalid subscription")) {
|
|
88
|
+
const id = WebSocketAsyncRequest.requestToId({
|
|
89
|
+
method: "subscribe",
|
|
90
|
+
subscription: parsedRequest,
|
|
91
|
+
});
|
|
92
|
+
this.queue.findLast((item) => item.id === id)
|
|
93
|
+
?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// For `Already unsubscribed` requests
|
|
97
|
+
if (detail.startsWith("Already unsubscribed")) {
|
|
98
|
+
const id = WebSocketAsyncRequest.requestToId({
|
|
99
|
+
method: "unsubscribe",
|
|
100
|
+
subscription: parsedRequest,
|
|
101
|
+
});
|
|
102
|
+
this.queue.findLast((item) => item.id === id)
|
|
103
|
+
?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// For unknown requests
|
|
108
|
+
const id = WebSocketAsyncRequest.requestToId(parsedRequest);
|
|
109
|
+
this.queue.findLast((item) => item.id === id)
|
|
110
|
+
?.reject(new WebSocketRequestError(`Server error: ${detail}`, { cause: detail }));
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore JSON parsing errors
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Throws all pending requests if the connection is dropped
|
|
117
|
+
socket.addEventListener("close", () => {
|
|
118
|
+
this.queue.forEach(({ reject }) => {
|
|
119
|
+
reject(new WebSocketRequestError("WebSocket connection closed."));
|
|
120
|
+
});
|
|
121
|
+
this.queue = [];
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Sends a request to the Hyperliquid API.
|
|
127
|
+
* @returns A promise that resolves with the parsed JSON response body.
|
|
128
|
+
*/
|
|
129
|
+
async request(method: "ping", signal?: AbortSignal): Promise<void>;
|
|
130
|
+
async request<T>(method: "post" | "subscribe" | "unsubscribe", payload: unknown, signal?: AbortSignal): Promise<T>;
|
|
131
|
+
async request<T>(
|
|
132
|
+
method: "post" | "subscribe" | "unsubscribe" | "ping",
|
|
133
|
+
payload_or_signal?: unknown | AbortSignal,
|
|
134
|
+
maybeSignal?: AbortSignal,
|
|
135
|
+
): Promise<T> {
|
|
136
|
+
const payload = payload_or_signal instanceof AbortSignal ? undefined : payload_or_signal;
|
|
137
|
+
const signal = payload_or_signal instanceof AbortSignal ? payload_or_signal : maybeSignal;
|
|
138
|
+
|
|
139
|
+
// Reject the request if the signal is aborted
|
|
140
|
+
if (signal?.aborted) return Promise.reject(signal.reason);
|
|
141
|
+
// or if the WebSocket connection is permanently closed
|
|
142
|
+
if (this.socket.reconnectAbortController.signal.aborted) {
|
|
143
|
+
return Promise.reject(this.socket.reconnectAbortController.signal.reason);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Create a request
|
|
147
|
+
let id: string | number;
|
|
148
|
+
let request: SubscribeUnsubscribeRequest | PostRequest | PingRequest;
|
|
149
|
+
if (method === "post") {
|
|
150
|
+
id = ++this.lastId;
|
|
151
|
+
request = { method, id, request: payload };
|
|
152
|
+
} else if (method === "ping") {
|
|
153
|
+
id = "ping";
|
|
154
|
+
request = { method };
|
|
155
|
+
} else {
|
|
156
|
+
request = { method, subscription: payload };
|
|
157
|
+
id = WebSocketAsyncRequest.requestToId(request);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Send the request
|
|
161
|
+
this.socket.send(JSON.stringify(request), signal);
|
|
162
|
+
this.lastRequestTime = Date.now();
|
|
163
|
+
|
|
164
|
+
// Wait for a response
|
|
165
|
+
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
166
|
+
this.queue.push({ id, resolve, reject });
|
|
167
|
+
|
|
168
|
+
const onAbort = () => reject(signal?.reason);
|
|
169
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
170
|
+
|
|
171
|
+
return await promise.finally(() => {
|
|
172
|
+
const index = this.queue.findLastIndex((item) => item.id === id);
|
|
173
|
+
if (index !== -1) this.queue.splice(index, 1);
|
|
174
|
+
|
|
175
|
+
signal?.removeEventListener("abort", onAbort);
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Normalizes an object and then converts it to a string. */
|
|
180
|
+
static requestToId(value: unknown): string {
|
|
181
|
+
const lowerHex = deepLowerHex(value);
|
|
182
|
+
const sorted = deepSortKeys(lowerHex);
|
|
183
|
+
return JSON.stringify(sorted); // Also removes undefined
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** Deeply converts hexadecimal strings in an object/array to lowercase. */
|
|
188
|
+
function deepLowerHex(obj: unknown): unknown {
|
|
189
|
+
if (typeof obj === "string") {
|
|
190
|
+
return /^(0X[0-9a-fA-F]*|0x[0-9a-fA-F]*[A-F][0-9a-fA-F]*)$/.test(obj) ? obj.toLowerCase() : obj;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (Array.isArray(obj)) {
|
|
194
|
+
return obj.map((value) => deepLowerHex(value));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (typeof obj === "object" && obj !== null) {
|
|
198
|
+
const result: Record<string, unknown> = {};
|
|
199
|
+
const entries = Object.entries(obj);
|
|
200
|
+
|
|
201
|
+
for (const [key, value] of entries) {
|
|
202
|
+
result[key] = deepLowerHex(value);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return obj;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Deeply sort the keys of an object. */
|
|
212
|
+
function deepSortKeys<T>(obj: T): T {
|
|
213
|
+
if (typeof obj !== "object" || obj === null) {
|
|
214
|
+
return obj;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (Array.isArray(obj)) {
|
|
218
|
+
return obj.map(deepSortKeys) as T;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const result: Record<string, unknown> = {};
|
|
222
|
+
const keys = Object.keys(obj).sort();
|
|
223
|
+
|
|
224
|
+
for (const key of keys) {
|
|
225
|
+
result[key] = deepSortKeys((obj as Record<string, unknown>)[key]);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
return result as T;
|
|
229
|
+
}
|