@siebly/kraken-api 0.0.9

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 (204) hide show
  1. package/LICENSE.md +7 -0
  2. package/README.md +624 -0
  3. package/dist/cjs/DerivativesClient.d.ts +657 -0
  4. package/dist/cjs/DerivativesClient.js +726 -0
  5. package/dist/cjs/DerivativesClient.js.map +1 -0
  6. package/dist/cjs/InstitutionalClient.d.ts +183 -0
  7. package/dist/cjs/InstitutionalClient.js +266 -0
  8. package/dist/cjs/InstitutionalClient.js.map +1 -0
  9. package/dist/cjs/PartnerClient.d.ts +257 -0
  10. package/dist/cjs/PartnerClient.js +344 -0
  11. package/dist/cjs/PartnerClient.js.map +1 -0
  12. package/dist/cjs/SpotClient.d.ts +547 -0
  13. package/dist/cjs/SpotClient.js +607 -0
  14. package/dist/cjs/SpotClient.js.map +1 -0
  15. package/dist/cjs/WebsocketAPIClient.d.ts +95 -0
  16. package/dist/cjs/WebsocketAPIClient.js +136 -0
  17. package/dist/cjs/WebsocketAPIClient.js.map +1 -0
  18. package/dist/cjs/WebsocketClient.d.ts +120 -0
  19. package/dist/cjs/WebsocketClient.js +669 -0
  20. package/dist/cjs/WebsocketClient.js.map +1 -0
  21. package/dist/cjs/index.d.ts +24 -0
  22. package/dist/cjs/index.js +41 -0
  23. package/dist/cjs/index.js.map +1 -0
  24. package/dist/cjs/lib/BaseRestClient.d.ts +66 -0
  25. package/dist/cjs/lib/BaseRestClient.js +548 -0
  26. package/dist/cjs/lib/BaseRestClient.js.map +1 -0
  27. package/dist/cjs/lib/BaseWSClient.d.ts +230 -0
  28. package/dist/cjs/lib/BaseWSClient.js +935 -0
  29. package/dist/cjs/lib/BaseWSClient.js.map +1 -0
  30. package/dist/cjs/lib/misc-util.d.ts +1 -0
  31. package/dist/cjs/lib/misc-util.js +7 -0
  32. package/dist/cjs/lib/misc-util.js.map +1 -0
  33. package/dist/cjs/lib/requestUtils.d.ts +68 -0
  34. package/dist/cjs/lib/requestUtils.js +82 -0
  35. package/dist/cjs/lib/requestUtils.js.map +1 -0
  36. package/dist/cjs/lib/webCryptoAPI.d.ts +15 -0
  37. package/dist/cjs/lib/webCryptoAPI.js +85 -0
  38. package/dist/cjs/lib/webCryptoAPI.js.map +1 -0
  39. package/dist/cjs/lib/websocket/WsStore.d.ts +75 -0
  40. package/dist/cjs/lib/websocket/WsStore.js +303 -0
  41. package/dist/cjs/lib/websocket/WsStore.js.map +1 -0
  42. package/dist/cjs/lib/websocket/WsStore.types.d.ts +51 -0
  43. package/dist/cjs/lib/websocket/WsStore.types.js +14 -0
  44. package/dist/cjs/lib/websocket/WsStore.types.js.map +1 -0
  45. package/dist/cjs/lib/websocket/logger.d.ts +7 -0
  46. package/dist/cjs/lib/websocket/logger.js +16 -0
  47. package/dist/cjs/lib/websocket/logger.js.map +1 -0
  48. package/dist/cjs/lib/websocket/rest-client-cache.d.ts +22 -0
  49. package/dist/cjs/lib/websocket/rest-client-cache.js +66 -0
  50. package/dist/cjs/lib/websocket/rest-client-cache.js.map +1 -0
  51. package/dist/cjs/lib/websocket/websocket-util.d.ts +76 -0
  52. package/dist/cjs/lib/websocket/websocket-util.js +59 -0
  53. package/dist/cjs/lib/websocket/websocket-util.js.map +1 -0
  54. package/dist/cjs/package.json +3 -0
  55. package/dist/cjs/types/request/derivatives.types.d.ts +186 -0
  56. package/dist/cjs/types/request/derivatives.types.js +6 -0
  57. package/dist/cjs/types/request/derivatives.types.js.map +1 -0
  58. package/dist/cjs/types/request/institutional.types.d.ts +313 -0
  59. package/dist/cjs/types/request/institutional.types.js +4 -0
  60. package/dist/cjs/types/request/institutional.types.js.map +1 -0
  61. package/dist/cjs/types/request/partner.types.d.ts +291 -0
  62. package/dist/cjs/types/request/partner.types.js +4 -0
  63. package/dist/cjs/types/request/partner.types.js.map +1 -0
  64. package/dist/cjs/types/request/spot.types.d.ts +324 -0
  65. package/dist/cjs/types/request/spot.types.js +6 -0
  66. package/dist/cjs/types/request/spot.types.js.map +1 -0
  67. package/dist/cjs/types/request/wsapi.types.d.ts +90 -0
  68. package/dist/cjs/types/request/wsapi.types.js +6 -0
  69. package/dist/cjs/types/request/wsapi.types.js.map +1 -0
  70. package/dist/cjs/types/response/derivatives.types.d.ts +712 -0
  71. package/dist/cjs/types/response/derivatives.types.js +6 -0
  72. package/dist/cjs/types/response/derivatives.types.js.map +1 -0
  73. package/dist/cjs/types/response/institutional.types.d.ts +318 -0
  74. package/dist/cjs/types/response/institutional.types.js +4 -0
  75. package/dist/cjs/types/response/institutional.types.js.map +1 -0
  76. package/dist/cjs/types/response/partner.types.d.ts +332 -0
  77. package/dist/cjs/types/response/partner.types.js +4 -0
  78. package/dist/cjs/types/response/partner.types.js.map +1 -0
  79. package/dist/cjs/types/response/shared.types.d.ts +27 -0
  80. package/dist/cjs/types/response/shared.types.js +3 -0
  81. package/dist/cjs/types/response/shared.types.js.map +1 -0
  82. package/dist/cjs/types/response/spot.types.d.ts +590 -0
  83. package/dist/cjs/types/response/spot.types.js +6 -0
  84. package/dist/cjs/types/response/spot.types.js.map +1 -0
  85. package/dist/cjs/types/response/ws.d.ts +11 -0
  86. package/dist/cjs/types/response/ws.js +3 -0
  87. package/dist/cjs/types/response/ws.js.map +1 -0
  88. package/dist/cjs/types/response/wsapi.types.d.ts +44 -0
  89. package/dist/cjs/types/response/wsapi.types.js +6 -0
  90. package/dist/cjs/types/response/wsapi.types.js.map +1 -0
  91. package/dist/cjs/types/websockets/ws-api.d.ts +71 -0
  92. package/dist/cjs/types/websockets/ws-api.js +18 -0
  93. package/dist/cjs/types/websockets/ws-api.js.map +1 -0
  94. package/dist/cjs/types/websockets/ws-events.d.ts +11 -0
  95. package/dist/cjs/types/websockets/ws-events.js +11 -0
  96. package/dist/cjs/types/websockets/ws-events.js.map +1 -0
  97. package/dist/cjs/types/websockets/ws-general.d.ts +67 -0
  98. package/dist/cjs/types/websockets/ws-general.js +3 -0
  99. package/dist/cjs/types/websockets/ws-general.js.map +1 -0
  100. package/dist/cjs/types/websockets/ws-subscriptions.d.ts +11 -0
  101. package/dist/cjs/types/websockets/ws-subscriptions.js +31 -0
  102. package/dist/cjs/types/websockets/ws-subscriptions.js.map +1 -0
  103. package/dist/mjs/DerivativesClient.d.ts +657 -0
  104. package/dist/mjs/DerivativesClient.js +722 -0
  105. package/dist/mjs/DerivativesClient.js.map +1 -0
  106. package/dist/mjs/InstitutionalClient.d.ts +183 -0
  107. package/dist/mjs/InstitutionalClient.js +262 -0
  108. package/dist/mjs/InstitutionalClient.js.map +1 -0
  109. package/dist/mjs/PartnerClient.d.ts +257 -0
  110. package/dist/mjs/PartnerClient.js +340 -0
  111. package/dist/mjs/PartnerClient.js.map +1 -0
  112. package/dist/mjs/SpotClient.d.ts +547 -0
  113. package/dist/mjs/SpotClient.js +603 -0
  114. package/dist/mjs/SpotClient.js.map +1 -0
  115. package/dist/mjs/WebsocketAPIClient.d.ts +95 -0
  116. package/dist/mjs/WebsocketAPIClient.js +132 -0
  117. package/dist/mjs/WebsocketAPIClient.js.map +1 -0
  118. package/dist/mjs/WebsocketClient.d.ts +120 -0
  119. package/dist/mjs/WebsocketClient.js +665 -0
  120. package/dist/mjs/WebsocketClient.js.map +1 -0
  121. package/dist/mjs/index.d.ts +24 -0
  122. package/dist/mjs/index.js +25 -0
  123. package/dist/mjs/index.js.map +1 -0
  124. package/dist/mjs/lib/BaseRestClient.d.ts +66 -0
  125. package/dist/mjs/lib/BaseRestClient.js +541 -0
  126. package/dist/mjs/lib/BaseRestClient.js.map +1 -0
  127. package/dist/mjs/lib/BaseWSClient.d.ts +230 -0
  128. package/dist/mjs/lib/BaseWSClient.js +928 -0
  129. package/dist/mjs/lib/BaseWSClient.js.map +1 -0
  130. package/dist/mjs/lib/misc-util.d.ts +1 -0
  131. package/dist/mjs/lib/misc-util.js +4 -0
  132. package/dist/mjs/lib/misc-util.js.map +1 -0
  133. package/dist/mjs/lib/requestUtils.d.ts +68 -0
  134. package/dist/mjs/lib/requestUtils.js +76 -0
  135. package/dist/mjs/lib/requestUtils.js.map +1 -0
  136. package/dist/mjs/lib/webCryptoAPI.d.ts +15 -0
  137. package/dist/mjs/lib/webCryptoAPI.js +80 -0
  138. package/dist/mjs/lib/webCryptoAPI.js.map +1 -0
  139. package/dist/mjs/lib/websocket/WsStore.d.ts +75 -0
  140. package/dist/mjs/lib/websocket/WsStore.js +298 -0
  141. package/dist/mjs/lib/websocket/WsStore.js.map +1 -0
  142. package/dist/mjs/lib/websocket/WsStore.types.d.ts +51 -0
  143. package/dist/mjs/lib/websocket/WsStore.types.js +11 -0
  144. package/dist/mjs/lib/websocket/WsStore.types.js.map +1 -0
  145. package/dist/mjs/lib/websocket/logger.d.ts +7 -0
  146. package/dist/mjs/lib/websocket/logger.js +13 -0
  147. package/dist/mjs/lib/websocket/logger.js.map +1 -0
  148. package/dist/mjs/lib/websocket/rest-client-cache.d.ts +22 -0
  149. package/dist/mjs/lib/websocket/rest-client-cache.js +62 -0
  150. package/dist/mjs/lib/websocket/rest-client-cache.js.map +1 -0
  151. package/dist/mjs/lib/websocket/websocket-util.d.ts +76 -0
  152. package/dist/mjs/lib/websocket/websocket-util.js +54 -0
  153. package/dist/mjs/lib/websocket/websocket-util.js.map +1 -0
  154. package/dist/mjs/package.json +3 -0
  155. package/dist/mjs/types/request/derivatives.types.d.ts +186 -0
  156. package/dist/mjs/types/request/derivatives.types.js +5 -0
  157. package/dist/mjs/types/request/derivatives.types.js.map +1 -0
  158. package/dist/mjs/types/request/institutional.types.d.ts +313 -0
  159. package/dist/mjs/types/request/institutional.types.js +3 -0
  160. package/dist/mjs/types/request/institutional.types.js.map +1 -0
  161. package/dist/mjs/types/request/partner.types.d.ts +291 -0
  162. package/dist/mjs/types/request/partner.types.js +3 -0
  163. package/dist/mjs/types/request/partner.types.js.map +1 -0
  164. package/dist/mjs/types/request/spot.types.d.ts +324 -0
  165. package/dist/mjs/types/request/spot.types.js +5 -0
  166. package/dist/mjs/types/request/spot.types.js.map +1 -0
  167. package/dist/mjs/types/request/wsapi.types.d.ts +90 -0
  168. package/dist/mjs/types/request/wsapi.types.js +5 -0
  169. package/dist/mjs/types/request/wsapi.types.js.map +1 -0
  170. package/dist/mjs/types/response/derivatives.types.d.ts +712 -0
  171. package/dist/mjs/types/response/derivatives.types.js +5 -0
  172. package/dist/mjs/types/response/derivatives.types.js.map +1 -0
  173. package/dist/mjs/types/response/institutional.types.d.ts +318 -0
  174. package/dist/mjs/types/response/institutional.types.js +3 -0
  175. package/dist/mjs/types/response/institutional.types.js.map +1 -0
  176. package/dist/mjs/types/response/partner.types.d.ts +332 -0
  177. package/dist/mjs/types/response/partner.types.js +3 -0
  178. package/dist/mjs/types/response/partner.types.js.map +1 -0
  179. package/dist/mjs/types/response/shared.types.d.ts +27 -0
  180. package/dist/mjs/types/response/shared.types.js +2 -0
  181. package/dist/mjs/types/response/shared.types.js.map +1 -0
  182. package/dist/mjs/types/response/spot.types.d.ts +590 -0
  183. package/dist/mjs/types/response/spot.types.js +5 -0
  184. package/dist/mjs/types/response/spot.types.js.map +1 -0
  185. package/dist/mjs/types/response/ws.d.ts +11 -0
  186. package/dist/mjs/types/response/ws.js +2 -0
  187. package/dist/mjs/types/response/ws.js.map +1 -0
  188. package/dist/mjs/types/response/wsapi.types.d.ts +44 -0
  189. package/dist/mjs/types/response/wsapi.types.js +5 -0
  190. package/dist/mjs/types/response/wsapi.types.js.map +1 -0
  191. package/dist/mjs/types/websockets/ws-api.d.ts +71 -0
  192. package/dist/mjs/types/websockets/ws-api.js +15 -0
  193. package/dist/mjs/types/websockets/ws-api.js.map +1 -0
  194. package/dist/mjs/types/websockets/ws-events.d.ts +11 -0
  195. package/dist/mjs/types/websockets/ws-events.js +8 -0
  196. package/dist/mjs/types/websockets/ws-events.js.map +1 -0
  197. package/dist/mjs/types/websockets/ws-general.d.ts +67 -0
  198. package/dist/mjs/types/websockets/ws-general.js +2 -0
  199. package/dist/mjs/types/websockets/ws-general.js.map +1 -0
  200. package/dist/mjs/types/websockets/ws-subscriptions.d.ts +11 -0
  201. package/dist/mjs/types/websockets/ws-subscriptions.js +28 -0
  202. package/dist/mjs/types/websockets/ws-subscriptions.js.map +1 -0
  203. package/llms.txt +10925 -0
  204. package/package.json +89 -0
@@ -0,0 +1,665 @@
1
+ import { BaseWebsocketClient, } from './lib/BaseWSClient.js';
2
+ import { neverGuard } from './lib/misc-util.js';
3
+ import { hashMessage, signMessage, } from './lib/webCryptoAPI.js';
4
+ import { RestClientCache } from './lib/websocket/rest-client-cache.js';
5
+ import { getPromiseRefForWSAPIRequest, WS_KEY_MAP, } from './lib/websocket/websocket-util.js';
6
+ import { WS_API_Operations, } from './types/websockets/ws-api.js';
7
+ import { WS_DERIVATIVES_PRIVATE_TOPICS, WS_SPOT_PRIVATE_TOPICS, } from './types/websockets/ws-subscriptions.js';
8
+ const WS_LOGGER_CATEGORY_ID = 'kraken-ws';
9
+ const WS_LOGGER_CATEGORY = {
10
+ category: WS_LOGGER_CATEGORY_ID,
11
+ };
12
+ export class WebsocketClient extends BaseWebsocketClient {
13
+ restClientCache = new RestClientCache();
14
+ wsChallengeCache = new Map();
15
+ constructor(options, logger) {
16
+ super({ ...options, wsLoggerCategory: WS_LOGGER_CATEGORY_ID }, logger);
17
+ this.restClientCache.setLogger(this.logger, WS_LOGGER_CATEGORY);
18
+ }
19
+ /**
20
+ * Request connection of all dependent (public & private) websockets, instead of waiting for automatic connection by library.
21
+ *
22
+ * Returns array of promises that individually resolve when each connection is successfully opened.
23
+ */
24
+ connectAll() {
25
+ return [this.connect(WS_KEY_MAP.spotPrivateV2)];
26
+ }
27
+ /**
28
+ * Ensures the WS API connection is active and ready.
29
+ *
30
+ * You do not need to call this, but if you call this before making any WS API requests,
31
+ * it can accelerate the first request (by preparing the connection in advance).
32
+ */
33
+ connectWSAPI(wsKey, skipAuth) {
34
+ if (skipAuth) {
35
+ return this.assertIsConnected(wsKey);
36
+ }
37
+ /** This call automatically ensures the connection is active AND authenticated before resolving */
38
+ return this.assertIsAuthenticated(wsKey);
39
+ }
40
+ /**
41
+ * Request subscription to one or more topics. Pass topics as either an array of strings, or array of objects (if the topic has parameters).
42
+ * Objects should be formatted as {topic: string, params: object}.
43
+ *
44
+ * - Subscriptions are automatically routed to the correct websocket connection.
45
+ * - Authentication/connection is automatic.
46
+ * - Resubscribe after network issues is automatic.
47
+ *
48
+ * Call `unsubscribe(topics)` to remove topics
49
+ */
50
+ subscribe(requests, wsKey) {
51
+ if (!Array.isArray(requests)) {
52
+ this.subscribeTopicsForWsKey([requests], wsKey);
53
+ return;
54
+ }
55
+ if (requests.length) {
56
+ this.subscribeTopicsForWsKey(requests, wsKey);
57
+ }
58
+ }
59
+ /**
60
+ * Unsubscribe from one or more topics. Similar to subscribe() but in reverse.
61
+ *
62
+ * - Requests are automatically routed to the correct websocket connection.
63
+ * - These topics will be removed from the topic cache, so they won't be subscribed to again.
64
+ */
65
+ unsubscribe(requests, wsKey) {
66
+ if (!Array.isArray(requests)) {
67
+ this.unsubscribeTopicsForWsKey([requests], wsKey);
68
+ return;
69
+ }
70
+ if (requests.length) {
71
+ this.unsubscribeTopicsForWsKey(requests, wsKey);
72
+ }
73
+ }
74
+ async sendWSAPIRequest(wsKey, operation, params, requestFlags) {
75
+ /**
76
+ * Base Info:
77
+ * - https://docs.kraken.com/api/docs/websocket-v2/add_order
78
+ *
79
+ * Currently only supported for Spot markets
80
+ */
81
+ this.logger.trace(`sendWSAPIRequest(): assert "${wsKey}" is connected`, {
82
+ ...WS_LOGGER_CATEGORY,
83
+ });
84
+ await this.assertIsConnected(wsKey);
85
+ // Some commands don't require authentication.
86
+ if (requestFlags?.authIsOptional !== true) {
87
+ this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey})...');
88
+ await this.assertIsAuthenticated(wsKey);
89
+ this.logger.trace('sendWSAPIRequest(): assertIsAuthenticated(${wsKey}) ok');
90
+ }
91
+ const requestEvent = {
92
+ method: operation,
93
+ params: params,
94
+ req_id: this.getNewRequestId(),
95
+ };
96
+ // Sign request
97
+ const signedEvent = await this.signWSAPIRequest(requestEvent);
98
+ // Store deferred promise
99
+ const promiseRef = getPromiseRefForWSAPIRequest(wsKey, requestEvent);
100
+ const deferredPromise = this.getWsStore().createDeferredPromise(wsKey, promiseRef, false);
101
+ // Enrich returned promise with request context for easier debugging
102
+ deferredPromise.promise
103
+ ?.then((res) => {
104
+ if (!Array.isArray(res)) {
105
+ res.request = {
106
+ wsKey: wsKey,
107
+ ...signedEvent,
108
+ };
109
+ }
110
+ return res;
111
+ })
112
+ .catch((e) => {
113
+ if (typeof e === 'string') {
114
+ this.logger.error('unexpcted string', { e });
115
+ return e;
116
+ }
117
+ e.request = {
118
+ wsKey: wsKey,
119
+ operation,
120
+ payload: signedEvent.params,
121
+ };
122
+ return e;
123
+ });
124
+ // Send event
125
+ const throwExceptions = true;
126
+ this.tryWsSend(wsKey, JSON.stringify(signedEvent), throwExceptions);
127
+ this.logger.trace(`sendWSAPIRequest(): sent "${operation}" event with promiseRef(${promiseRef})`, signedEvent);
128
+ // Return deferred promise, so caller can await this call
129
+ return deferredPromise.promise;
130
+ }
131
+ /**
132
+ *
133
+ * Internal methods - not intended for public use
134
+ *
135
+ */
136
+ getRestClientOptions() {
137
+ return {
138
+ ...this.options,
139
+ ...this.options.restOptions,
140
+ apiKey: this.options.apiKey,
141
+ apiSecret: this.options.apiSecret,
142
+ };
143
+ }
144
+ /**
145
+ * Note: implementing this method will wipe the WsStore state for this WsKey, once this method returns
146
+ */
147
+ isCustomReconnectionNeeded() {
148
+ return false;
149
+ }
150
+ async triggerCustomReconnectionWorkflow() {
151
+ return;
152
+ }
153
+ async getWsUrl(wsKey) {
154
+ if (this.options.wsUrl) {
155
+ return this.options.wsUrl;
156
+ }
157
+ switch (wsKey) {
158
+ /**
159
+ * https://docs.kraken.com/api/docs/guides/spot-ws-intro/
160
+ *
161
+ * Note: Kraken's v2 WebSockets clean up a number idiosyncrasies and ambiguities from v1 with the overall aim to enable easier integration with applications. It is intended that v1 will be maintained but future enhancements will be developed in v2.
162
+ *
163
+ * Given the above, we are only integrating with V2 for now.
164
+ *
165
+ * V2 SpotWebSocket Reference: https://docs.kraken.com/api/docs/websocket-v2/add_order/
166
+ */
167
+ case WS_KEY_MAP.spotPublicV2: {
168
+ return 'wss://ws.kraken.com/v2';
169
+ }
170
+ case WS_KEY_MAP.spotPrivateV2: {
171
+ return 'wss://ws-auth.kraken.com/v2';
172
+ }
173
+ case WS_KEY_MAP.spotBetaPublicV2: {
174
+ return 'wss://beta-ws.kraken.com/v2';
175
+ }
176
+ case WS_KEY_MAP.spotBetaPrivateV2: {
177
+ return 'wss://beta-ws-auth.kraken.com/v2';
178
+ }
179
+ // Uses the same URL, but we maintain separate connections for easier management
180
+ case WS_KEY_MAP.derivativesPublicV1:
181
+ case WS_KEY_MAP.derivativesPrivateV1: {
182
+ if (this.options.testnet) {
183
+ return 'wss://demo-futures.kraken.com/ws/v1';
184
+ }
185
+ return 'wss://futures.kraken.com/ws/v1';
186
+ }
187
+ default: {
188
+ throw neverGuard(wsKey, `Unhandled WsKey "${wsKey}"`);
189
+ }
190
+ }
191
+ }
192
+ sendPingEvent(wsKey) {
193
+ // let pingChannel: WsRequestPing['channel'];
194
+ switch (wsKey) {
195
+ case WS_KEY_MAP.derivativesPublicV1:
196
+ case WS_KEY_MAP.derivativesPrivateV1: {
197
+ const ws = this.getWsStore().get(wsKey)?.ws;
198
+ if (ws) {
199
+ ws.ping();
200
+ }
201
+ break;
202
+ }
203
+ case WS_KEY_MAP.spotPublicV2:
204
+ case WS_KEY_MAP.spotPrivateV2:
205
+ case WS_KEY_MAP.spotBetaPublicV2:
206
+ case WS_KEY_MAP.spotBetaPrivateV2: {
207
+ // Spot: https://docs.kraken.com/api/docs/websocket-v2/ping
208
+ return this.tryWsSend(wsKey, `{ "method": "ping", "req_id": ${this.getNewRequestId()} }`);
209
+ }
210
+ default: {
211
+ throw neverGuard(wsKey, `Unhandled WsKey "${wsKey}"`);
212
+ }
213
+ }
214
+ }
215
+ sendPongEvent(wsKey) {
216
+ try {
217
+ this.logger.trace('Sending upstream ws PONGFRAME: ', {
218
+ ...WS_LOGGER_CATEGORY,
219
+ wsMessage: 'PONGFRAME',
220
+ wsKey,
221
+ });
222
+ if (!wsKey) {
223
+ throw new Error('Cannot send PONGFRAME, no wsKey provided');
224
+ }
225
+ const wsState = this.getWsStore().get(wsKey);
226
+ if (!wsState || !wsState?.ws) {
227
+ throw new Error(`Cannot send PONGFRAME, ${wsKey} socket not connected yet`);
228
+ }
229
+ // Send a protocol layer pong
230
+ wsState.ws.pong();
231
+ }
232
+ catch (e) {
233
+ this.logger.error('Failed to send WS PONG', {
234
+ ...WS_LOGGER_CATEGORY,
235
+ wsMessage: 'PONGFRAME',
236
+ wsKey,
237
+ exception: e,
238
+ });
239
+ }
240
+ }
241
+ // NOT IN USE for kraken
242
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
243
+ isWsPing(_msg) {
244
+ return false;
245
+ }
246
+ isWsPong(msg) {
247
+ // Pre-parsed in resolveEmittableEvents into "pong" eventType:
248
+ if (msg?.eventType) {
249
+ if (msg?.eventType === 'pong') {
250
+ return true;
251
+ }
252
+ }
253
+ return false;
254
+ }
255
+ /**
256
+ * Parse incoming events into categories, before emitting to the user
257
+ */
258
+ resolveEmittableEvents(wsKey, event) {
259
+ const results = [];
260
+ try {
261
+ const parsed = JSON.parse(event.data);
262
+ // derivatives sends 'challenge' on successful auth-init (used for sign during subscribe)
263
+ const responseEvents = [
264
+ // spot confirmation for subscription success
265
+ 'subscribe',
266
+ 'unsubscribe',
267
+ 'info',
268
+ // derivatives confirmation for subscription success
269
+ 'subscribed',
270
+ 'unsubscribed',
271
+ ];
272
+ const authenticatedEvents = ['challenge'];
273
+ const derivativesEventAction = parsed.event || parsed.feed;
274
+ const spotEventAction = parsed.method ||
275
+ parsed.type ||
276
+ parsed.event ||
277
+ parsed?.header?.data ||
278
+ parsed.channel;
279
+ const eventAction = spotEventAction || derivativesEventAction;
280
+ const promiseRef = [wsKey, eventAction, parsed?.req_id].join('_');
281
+ // WS API
282
+ if (WS_API_Operations.includes(eventAction)) {
283
+ const isError = parsed.success !== true;
284
+ // WS API Exception
285
+ if (isError) {
286
+ try {
287
+ this.getWsStore().rejectDeferredPromise(wsKey, promiseRef, {
288
+ wsKey,
289
+ ...parsed,
290
+ }, true);
291
+ }
292
+ catch (e) {
293
+ this.logger.error('Exception trying to reject WSAPI promise', {
294
+ wsKey,
295
+ promiseRef,
296
+ parsedEvent: parsed,
297
+ error: e,
298
+ });
299
+ }
300
+ results.push({
301
+ eventType: 'exception',
302
+ event: parsed,
303
+ isWSAPIResponse: true,
304
+ });
305
+ return results;
306
+ }
307
+ // WS API Success
308
+ try {
309
+ this.getWsStore().resolveDeferredPromise(wsKey, promiseRef, {
310
+ wsKey,
311
+ ...parsed,
312
+ }, true);
313
+ }
314
+ catch (e) {
315
+ this.logger.error('Exception trying to resolve WSAPI promise', {
316
+ wsKey,
317
+ promiseRef,
318
+ parsedEvent: parsed,
319
+ error: e,
320
+ });
321
+ }
322
+ results.push({
323
+ eventType: 'response',
324
+ event: parsed,
325
+ isWSAPIResponse: true,
326
+ });
327
+ return results;
328
+ } // end of WS API response processing
329
+ if (typeof eventAction === 'string') {
330
+ if (parsed.success === false) {
331
+ results.push({
332
+ eventType: 'exception',
333
+ event: parsed,
334
+ });
335
+ return results;
336
+ }
337
+ // exceptions with derivatives v1 WS. E.g. { event: 'alert', message: 'Bad websocket message' }
338
+ if (eventAction === 'alert') {
339
+ results.push({
340
+ eventType: 'exception',
341
+ event: parsed,
342
+ });
343
+ return results;
344
+ }
345
+ // Most events use event: "update" or "snapshot" for topic updates
346
+ if (['update', 'snapshot'].includes(eventAction)) {
347
+ results.push({
348
+ eventType: 'message',
349
+ event: parsed,
350
+ });
351
+ return results;
352
+ }
353
+ // These are request/reply pattern events (e.g. after subscribing to topics or authenticating)
354
+ // e.g. "method":"subscribe"
355
+ if (responseEvents.includes(eventAction)) {
356
+ results.push({
357
+ eventType: 'response',
358
+ event: parsed,
359
+ });
360
+ return results;
361
+ }
362
+ // derivatives events include the "feed" property to identify the channel name
363
+ if (typeof parsed.feed === 'string') {
364
+ results.push({
365
+ eventType: 'message',
366
+ event: parsed,
367
+ });
368
+ return results;
369
+ }
370
+ // Request/reply pattern for authentication success
371
+ if (authenticatedEvents.includes(eventAction)) {
372
+ if (wsKey === WS_KEY_MAP.derivativesPrivateV1) {
373
+ const challenge = parsed.message;
374
+ if (challenge) {
375
+ this.logger.trace(`Stored challenge for derivatives auth on wsKey "${wsKey}": "${challenge}"`);
376
+ this.wsChallengeCache.set(wsKey, challenge);
377
+ }
378
+ }
379
+ results.push({
380
+ eventType: 'authenticated',
381
+ event: parsed,
382
+ });
383
+ return results;
384
+ }
385
+ if (eventAction === 'heartbeat' || eventAction === 'pong') {
386
+ results.push({
387
+ eventType: 'pong',
388
+ event: parsed,
389
+ });
390
+ return results;
391
+ }
392
+ this.logger.error(`!! Unhandled string "eventAction" "${eventAction}". Defaulting to "message" channel... Parsed:`, parsed);
393
+ }
394
+ else {
395
+ this.logger.error(`!! Unhandled non-string "eventAction" "${eventAction}". Defaulting to "message" channel... Parsed:`, parsed);
396
+ }
397
+ results.push({
398
+ eventType: 'message',
399
+ event: parsed,
400
+ });
401
+ }
402
+ catch (e) {
403
+ results.push({
404
+ event: {
405
+ message: 'Failed to parse event data due to exception',
406
+ exception: e,
407
+ eventData: event.data,
408
+ },
409
+ eventType: 'exception',
410
+ });
411
+ this.logger.error('Failed to parse event data due to exception: ', {
412
+ exception: e,
413
+ eventData: event.data,
414
+ });
415
+ }
416
+ return results;
417
+ }
418
+ /**
419
+ * Determines if a topic is for a private channel, using a hardcoded list of strings
420
+ */
421
+ isPrivateTopicRequest(request) {
422
+ const topicName = request?.topic?.toLowerCase();
423
+ if (!topicName) {
424
+ return false;
425
+ }
426
+ if (WS_SPOT_PRIVATE_TOPICS.includes(topicName)) {
427
+ return true;
428
+ }
429
+ if (WS_DERIVATIVES_PRIVATE_TOPICS.includes(topicName)) {
430
+ return true;
431
+ }
432
+ return false;
433
+ }
434
+ /**
435
+ * Not in use for Kraken
436
+ */
437
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
438
+ getWsMarketForWsKey(_wsKey) {
439
+ return 'futures';
440
+ }
441
+ /**
442
+ * Whether key represents a private connection. Feeds into automatic auth on connect.
443
+ */
444
+ getPrivateWSKeys() {
445
+ return [
446
+ WS_KEY_MAP.spotPrivateV2,
447
+ WS_KEY_MAP.spotBetaPrivateV2,
448
+ WS_KEY_MAP.derivativesPrivateV1,
449
+ ];
450
+ }
451
+ isAuthOnConnectWsKey(wsKey) {
452
+ return this.getPrivateWSKeys().includes(wsKey);
453
+ }
454
+ /** Force subscription requests to be sent in smaller batches, if a number is returned */
455
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
456
+ getMaxTopicsPerSubscribeEvent(_wsKey) {
457
+ return 1;
458
+ }
459
+ authPrivateConnectionsOnConnect(wsKey) {
460
+ // derivatives require you to send a challenge on connect
461
+ if (wsKey === WS_KEY_MAP.derivativesPrivateV1) {
462
+ return true;
463
+ }
464
+ return this.options.authPrivateConnectionsOnConnect;
465
+ }
466
+ /**
467
+ * @returns one or more correctly structured request events for performing a operations over WS. This can vary per exchange spec.
468
+ */
469
+ async getWsRequestEvents(wsKey, operation, requests) {
470
+ const wsRequestEvents = [];
471
+ const wsRequestBuildingErrors = [];
472
+ // Previously used to track topics in a request. Keeping this for subscribe/unsubscribe requests, no need for incremental values
473
+ for (const topicRequest of requests) {
474
+ const req_id = this.getNewRequestId();
475
+ switch (wsKey) {
476
+ case WS_KEY_MAP.spotPublicV2:
477
+ case WS_KEY_MAP.spotPrivateV2:
478
+ case WS_KEY_MAP.spotBetaPublicV2:
479
+ case WS_KEY_MAP.spotBetaPrivateV2: {
480
+ const wsEvent = {
481
+ method: operation,
482
+ params: {
483
+ channel: topicRequest.topic,
484
+ ...topicRequest.payload,
485
+ },
486
+ req_id: req_id,
487
+ };
488
+ if (this.options.authPrivateRequests &&
489
+ (wsKey === WS_KEY_MAP.spotPrivateV2 ||
490
+ wsKey === WS_KEY_MAP.spotBetaPrivateV2)) {
491
+ // Get token from REST client cache
492
+ const tokenResult = await this.restClientCache.fetchSpotWebSocketToken(this.getRestClientOptions(), this.options.requestOptions);
493
+ if (!tokenResult?.token) {
494
+ wsRequestBuildingErrors.push(new Error(`No WS auth token could be retrieved for private spot WS request for topic "${topicRequest.topic}"`));
495
+ continue;
496
+ }
497
+ wsEvent.params.token = tokenResult.token;
498
+ }
499
+ // Cache midflight subs on the req ID
500
+ // Enrich response with subs for that req ID
501
+ const midflightWsEvent = {
502
+ requestKey: wsEvent.req_id,
503
+ requestEvent: wsEvent,
504
+ };
505
+ wsRequestEvents.push({
506
+ ...midflightWsEvent,
507
+ });
508
+ break;
509
+ }
510
+ // No auth needed, it's public topics only here
511
+ case WS_KEY_MAP.derivativesPublicV1: {
512
+ const wsEvent = {
513
+ event: operation,
514
+ feed: topicRequest.topic,
515
+ ...topicRequest.payload,
516
+ req_id: req_id,
517
+ };
518
+ // Cache midflight subs on the req ID
519
+ // Enrich response with subs for that req ID
520
+ const midflightWsEvent = {
521
+ requestKey: wsEvent.req_id,
522
+ requestEvent: wsEvent,
523
+ };
524
+ wsRequestEvents.push({
525
+ ...midflightWsEvent,
526
+ });
527
+ break;
528
+ }
529
+ case WS_KEY_MAP.derivativesPrivateV1: {
530
+ const wsEvent = {
531
+ event: operation,
532
+ feed: topicRequest.topic,
533
+ ...topicRequest.payload,
534
+ req_id: req_id,
535
+ };
536
+ // https://docs.kraken.com/api/docs/guides/futures-websockets
537
+ // Authenticated requests must include both the original challenge message (original_challenge) and the signed (signed_challenge) in JSON format.
538
+ if (!this.wsChallengeCache.has(wsKey)) {
539
+ this.logger.trace(`No challenge key cached for wsKey ${wsKey}, asserting authentication...`, { ...WS_LOGGER_CATEGORY, wsKey });
540
+ await this.assertIsAuthenticated(wsKey);
541
+ }
542
+ const challengeKey = this.wsChallengeCache.get(wsKey);
543
+ if (!challengeKey) {
544
+ this.logger.error(`Auth-check passed but no challenge key could be retrieved from cache for wsKey ${wsKey}`, { ...WS_LOGGER_CATEGORY, wsKey });
545
+ throw new Error('No challenge key cached, cannot send authenticated request');
546
+ }
547
+ wsEvent.original_challenge = challengeKey;
548
+ wsEvent.api_key = this.options.apiKey;
549
+ const hashedChallenge = await hashMessage(challengeKey, 'binary', 'SHA-256');
550
+ if (!this.options.apiSecret) {
551
+ throw new Error('API Secret missing, cannot sign challenge for authenticated WS request');
552
+ }
553
+ const challengeSign = await this.signMessage(hashedChallenge, this.options.apiSecret, 'base64', 'SHA-512', {
554
+ isSecretB64Encoded: true,
555
+ isInputBinaryString: true,
556
+ });
557
+ wsEvent.signed_challenge = challengeSign;
558
+ // Cache midflight subs on the req ID
559
+ // Enrich response with subs for that req ID
560
+ const midflightWsEvent = {
561
+ requestKey: wsEvent.req_id,
562
+ requestEvent: wsEvent,
563
+ };
564
+ wsRequestEvents.push({
565
+ ...midflightWsEvent,
566
+ });
567
+ break;
568
+ }
569
+ default: {
570
+ throw neverGuard(wsKey, `Unhandled WsKey "${wsKey}"`);
571
+ }
572
+ }
573
+ }
574
+ if (wsRequestBuildingErrors.length) {
575
+ const label = wsRequestBuildingErrors.length === requests.length ? 'all' : 'some';
576
+ this.logger.error(`Failed to build/send ${wsRequestBuildingErrors.length} event(s) for ${label} WS requests due to exceptions`, {
577
+ ...WS_LOGGER_CATEGORY,
578
+ wsRequestBuildingErrors,
579
+ wsRequestBuildingErrorsStringified: JSON.stringify(wsRequestBuildingErrors, null, 2),
580
+ });
581
+ }
582
+ return wsRequestEvents;
583
+ }
584
+ async signMessage(paramsStr, secret, method, algorithm, options) {
585
+ if (typeof this.options.customSignMessageFn === 'function') {
586
+ return this.options.customSignMessageFn(paramsStr, secret);
587
+ }
588
+ return await signMessage(paramsStr, secret, method, algorithm, options);
589
+ }
590
+ async getWsAuthRequestEvent(wsKey, eventToAuth) {
591
+ try {
592
+ switch (wsKey) {
593
+ case WS_KEY_MAP.spotPrivateV2:
594
+ case WS_KEY_MAP.spotBetaPrivateV2: {
595
+ // Not needed here, handled automatically with request during subscribe
596
+ this.logger.trace(`getWsAuthRequestEvent(${wsKey}): no auth request required for private WS...`);
597
+ return;
598
+ }
599
+ case WS_KEY_MAP.spotPublicV2:
600
+ case WS_KEY_MAP.spotBetaPublicV2:
601
+ case WS_KEY_MAP.derivativesPublicV1: {
602
+ // Public WS - no auth
603
+ this.logger.trace(`getWsAuthRequestEvent(${wsKey}): no auth required for public WS...`);
604
+ return;
605
+ }
606
+ case WS_KEY_MAP.derivativesPrivateV1: {
607
+ // cleanup old challenge key (in case we were reconnected)
608
+ this.wsChallengeCache.delete(wsKey);
609
+ // https://docs.kraken.com/api/docs/futures-api/websocket/challenge/
610
+ this.logger.trace(`getWsAuthRequestEvent(${wsKey}): preparing auth challenge request...`, { ...WS_LOGGER_CATEGORY, wsKey });
611
+ const challengeRequest = {
612
+ event: 'challenge',
613
+ api_key: this.options.apiKey,
614
+ };
615
+ return challengeRequest;
616
+ }
617
+ default: {
618
+ throw neverGuard(wsKey, `Unhandled WsKey "${wsKey}"`);
619
+ }
620
+ }
621
+ }
622
+ catch (e) {
623
+ this.logger.error(`getWsAuthRequestEvent(${wsKey}): Exception preparing auth request: `, {
624
+ wsKey,
625
+ eventToAuth,
626
+ exception: e,
627
+ exceptionBody: e?.body,
628
+ stack: e?.stack,
629
+ });
630
+ throw e;
631
+ }
632
+ }
633
+ /**
634
+ *
635
+ * @param requestEvent
636
+ * @returns A signed updated WS API request object, ready to be sent
637
+ */
638
+ async signWSAPIRequest(requestEvent) {
639
+ if (!this.options.apiSecret) {
640
+ throw new Error('API Secret missing');
641
+ }
642
+ // Get token from REST client cache
643
+ const tokenResult = await this.restClientCache.fetchSpotWebSocketToken(this.getRestClientOptions(), this.options.requestOptions);
644
+ if (!tokenResult?.token) {
645
+ const error = new Error(`No WS auth token could be retrieved for private spot WS request for topic "${requestEvent.method}"`);
646
+ error.cause = tokenResult;
647
+ throw error;
648
+ }
649
+ const authParams = {};
650
+ if (tokenResult.token) {
651
+ authParams.token = tokenResult.token;
652
+ }
653
+ if (requestEvent.method === 'add_order') {
654
+ // authParams.broker = APIIDMain;
655
+ }
656
+ return {
657
+ ...requestEvent,
658
+ params: {
659
+ ...requestEvent.params,
660
+ ...authParams,
661
+ },
662
+ };
663
+ }
664
+ }
665
+ //# sourceMappingURL=WebsocketClient.js.map