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