@grvt/client 1.5.0-alpha.0 → 1.5.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@grvt/client",
3
- "version": "1.5.0-alpha.0",
3
+ "version": "1.5.1",
4
4
  "description": "Node.js & JavaScript client for GRVT REST APIs & WebSockets",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "test": "nodemon dist/index.js"
34
34
  },
35
35
  "dependencies": {
36
- "axios": "^1.8.2"
36
+ "axios": "^1.11.0"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@typescript-eslint/eslint-plugin": "^6.10.0",
@@ -1,4 +1,4 @@
1
- import { type ECandlestickInterval, type ECandlestickType, type IWSCandlestickFeedDataV1, type IWSCandlestickFeedSelectorV1, type IWSDepositFeedDataV1, type IWSDepositFeedSelectorV1, type IWSFillFeedDataV1, type IWSFillFeedSelectorV1, type IWSMiniTickerFeedDataV1, type IWSMiniTickerFeedSelectorV1, type IWSOrderFeedDataV1, type IWSOrderFeedSelectorV1, type IWSOrderGroupFeedDataV1, type IWSOrderStateFeedDataV1, type IWSOrderStateFeedSelectorV1, type IWSOrderbookLevelsFeedDataV1, type IWSOrderbookLevelsFeedSelectorV1, type IWSPositionsFeedDataV1, type IWSPositionsFeedSelectorV1, type IWSTickerFeedDataV1, type IWSTickerFeedSelectorV1, type IWSTradeFeedDataV1, type IWSTradeFeedSelectorV1, type IWSTransferFeedDataV1, type IWSTransferFeedSelectorV1, type IWSWithdrawalFeedDataV1, type IWSWithdrawalFeedSelectorV1 } from '../interfaces';
1
+ import type { ECandlestickInterval, ECandlestickType, IWSCandlestickFeedDataV1, IWSCandlestickFeedSelectorV1, IWSDepositFeedDataV1, IWSDepositFeedSelectorV1, IWSFillFeedDataV1, IWSFillFeedSelectorV1, IWSMiniTickerFeedDataV1, IWSMiniTickerFeedSelectorV1, IWSOrderFeedDataV1, IWSOrderFeedSelectorV1, IWSOrderGroupFeedDataV1, IWSOrderStateFeedDataV1, IWSOrderStateFeedSelectorV1, IWSOrderbookLevelsFeedDataV1, IWSOrderbookLevelsFeedSelectorV1, IWSPositionsFeedDataV1, IWSPositionsFeedSelectorV1, IWSTickerFeedDataV1, IWSTickerFeedSelectorV1, IWSTradeFeedDataV1, IWSTradeFeedSelectorV1, IWSTransferFeedDataV1, IWSTransferFeedSelectorV1, IWSWithdrawalFeedDataV1, IWSWithdrawalFeedSelectorV1 } from '../interfaces';
2
2
  export declare enum EStream {
3
3
  CANDLE = "candle",
4
4
  MINI_DELTA = "mini.d",
package/ws/ws.d.ts CHANGED
@@ -1,4 +1,13 @@
1
1
  import type { TWSRequest } from './interfaces';
2
+ export declare class WSError extends Error {
3
+ code?: number;
4
+ status?: number;
5
+ constructor(response: {
6
+ status?: number;
7
+ code?: number;
8
+ message?: string;
9
+ });
10
+ }
2
11
  interface IOptions {
3
12
  url: string | URL;
4
13
  protocols?: string | string[];
@@ -46,14 +55,15 @@ export declare class WS {
46
55
  private readonly _onCloses;
47
56
  onClose(callback: (e: CloseEvent) => void): () => void;
48
57
  private readonly _onErrors;
49
- onError(callback: (e: Event) => void): () => void;
58
+ onError(callback: (e: Event | WSError) => void): () => void;
50
59
  private _subscribeCurrentPairs;
51
60
  private _subscribe;
52
61
  /**
53
62
  * Only supports one feed
54
63
  */
55
64
  subscribe(_options: TWSRequest): string;
56
- unsubscribe(pairedConsumerKey: string): this;
65
+ subscribes(...subscriptions: TWSRequest[]): string[];
66
+ unsubscribe(...pairedConsumerKeys: string[]): this;
57
67
  connect(): this;
58
68
  disconnect(): this;
59
69
  ready(delay?: number): Promise<unknown>;
package/ws/ws.js CHANGED
@@ -20,7 +20,7 @@ var __rest = (this && this.__rest) || function (s, e) {
20
20
  return t;
21
21
  };
22
22
  Object.defineProperty(exports, "__esModule", { value: true });
23
- exports.WS = void 0;
23
+ exports.WS = exports.WSError = void 0;
24
24
  const ws_candlestick_feed_data_v_1_1 = require("../interfaces/codegen/schema-maps/ws_candlestick_feed_data_v_1");
25
25
  const ws_deposit_feed_data_v_1_1 = require("../interfaces/codegen/schema-maps/ws_deposit_feed_data_v_1");
26
26
  const ws_fill_feed_data_v_1_1 = require("../interfaces/codegen/schema-maps/ws_fill_feed_data_v_1");
@@ -36,6 +36,15 @@ const ws_transfer_feed_data_v_1_1 = require("../interfaces/codegen/schema-maps/w
36
36
  const ws_withdrawal_feed_data_v_1_1 = require("../interfaces/codegen/schema-maps/ws_withdrawal_feed_data_v_1");
37
37
  const utils_1 = require("../utils");
38
38
  const interfaces_1 = require("./interfaces");
39
+ class WSError extends Error {
40
+ constructor(response) {
41
+ super(response.message);
42
+ this.name = 'WSError';
43
+ this.code = response.code;
44
+ this.status = response.status;
45
+ }
46
+ }
47
+ exports.WSError = WSError;
39
48
  const omitZeroStr = (str) => str === '0' ? '' : str;
40
49
  class WS {
41
50
  get _ws() {
@@ -140,7 +149,7 @@ class WS {
140
149
  reconnect();
141
150
  });
142
151
  currentWs.addEventListener('message', (e) => {
143
- var _a, _b, _c, _d, _e, _f;
152
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
144
153
  // only keep the latest ws
145
154
  if (currentWs !== this.__ws) {
146
155
  this._close(currentWs);
@@ -152,17 +161,33 @@ class WS {
152
161
  }
153
162
  const message = utils_1.JsonUtils.parse(e.data);
154
163
  /**
155
- * Ignore 400 status response messages
164
+ * Handle error response
156
165
  */
157
- if (message.status === 400) {
166
+ if ((_b = (_a = message.status) !== null && _a !== void 0 ? _a : message.c) !== null && _b !== void 0 ? _b : message.code) {
167
+ /**
168
+ * Ignore 1003 response "Request could not be processed due to malformed syntax"
169
+ * This happens when client send
170
+ */
171
+ const code = (_c = message.c) !== null && _c !== void 0 ? _c : message.code;
172
+ if (code !== 1003) {
173
+ const error = new WSError(Object.assign(Object.assign({}, message), { code, message: (_d = message.m) !== null && _d !== void 0 ? _d : message.message }));
174
+ if (this._onErrors.length) {
175
+ for (const onError of this._onErrors) {
176
+ onError(error);
177
+ }
178
+ }
179
+ else {
180
+ console.error(error);
181
+ }
182
+ }
158
183
  return;
159
184
  }
160
- const stream = message.s = (_b = (_a = message.s) === null || _a === void 0 ? void 0 : _a.replace) === null || _b === void 0 ? void 0 : _b.call(_a, `${this._version}.`, '');
185
+ const stream = message.s = (_f = (_e = message.s) === null || _e === void 0 ? void 0 : _e.replace) === null || _f === void 0 ? void 0 : _f.call(_e, `${this._version}.`, '');
161
186
  const result = this._messageLiteToFull(message);
162
187
  // no entity found
163
188
  if (!result) {
164
189
  // if no entity found and not a subscription message
165
- if (!((_c = message.s1) === null || _c === void 0 ? void 0 : _c.length)) {
190
+ if (!((_g = message.s1) === null || _g === void 0 ? void 0 : _g.length)) {
166
191
  console.warn('Error: something went wrong with message', message);
167
192
  }
168
193
  return;
@@ -171,7 +196,7 @@ class WS {
171
196
  console.warn('Error: cannot parse stream or feed from message', message);
172
197
  return;
173
198
  }
174
- const instrument = (_f = (_e = (_d = result === null || result === void 0 ? void 0 : result.legs) === null || _d === void 0 ? void 0 : _d[0]) === null || _e === void 0 ? void 0 : _e.instrument) !== null && _f !== void 0 ? _f : result === null || result === void 0 ? void 0 : result.instrument;
199
+ const instrument = (_k = (_j = (_h = result === null || result === void 0 ? void 0 : result.legs) === null || _h === void 0 ? void 0 : _h[0]) === null || _j === void 0 ? void 0 : _j.instrument) !== null && _k !== void 0 ? _k : result === null || result === void 0 ? void 0 : result.instrument;
175
200
  /**
176
201
  * Handle subscriptions with instrument
177
202
  */
@@ -572,30 +597,45 @@ class WS {
572
597
  this._pairs[pair][consumerKey] = onMessage;
573
598
  return `${pair}__${consumerKey}`;
574
599
  }
575
- _removeConsumer(pairedConsumerKey) {
576
- const [stream, feed, consumerKey] = pairedConsumerKey.split('__');
577
- const pairKey = this._getPair({ stream, feed });
578
- if (!this._pairs[pairKey]) {
579
- return;
580
- }
581
- let needUnsubscribe = true;
582
- const pairPrimary = pairKey.split('@')[0];
583
- for (const key of Object.keys(this._pairs)) {
584
- if (key.split('@')[0] !== pairPrimary) {
585
- continue;
600
+ _removeConsumer(...pairedConsumerKeys) {
601
+ const toUnsubscribe = {};
602
+ for (const pairedConsumerKey of pairedConsumerKeys) {
603
+ const [stream, feed, consumerKey] = pairedConsumerKey.split('__');
604
+ const pairKey = this._getPair({ stream, feed });
605
+ if (!this._pairs[pairKey]) {
606
+ return;
607
+ }
608
+ let needUnsubscribe = true;
609
+ const pairPrimary = pairKey.split('@')[0];
610
+ for (const key of Object.keys(this._pairs)) {
611
+ if (key.split('@')[0] !== pairPrimary) {
612
+ continue;
613
+ }
614
+ const primaryGroup = this._pairs[key];
615
+ const _a = primaryGroup, _b = consumerKey, _ = _a[_b], keep = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
616
+ this._pairs[key] = keep;
617
+ if (Object.keys(keep).length) {
618
+ needUnsubscribe = false;
619
+ }
586
620
  }
587
- const primaryGroup = this._pairs[key];
588
- const _a = primaryGroup, _b = consumerKey, _ = _a[_b], keep = __rest(_a, [typeof _b === "symbol" ? _b : _b + ""]);
589
- this._pairs[key] = keep;
590
- if (Object.keys(keep).length) {
591
- needUnsubscribe = false;
621
+ if (needUnsubscribe) {
622
+ if (!toUnsubscribe[stream]) {
623
+ toUnsubscribe[stream] = [];
624
+ }
625
+ const primaryFeed = feed.split('@')[0];
626
+ if (!toUnsubscribe[stream].some((f) => f.startsWith(`${primaryFeed}@`))) {
627
+ toUnsubscribe[stream].push(feed);
628
+ }
592
629
  }
593
630
  }
594
- if (needUnsubscribe) {
631
+ /**
632
+ * Send unsubscribes
633
+ */
634
+ for (const stream of Object.keys(toUnsubscribe)) {
595
635
  this._sendMessage({
596
636
  method: 'unsubscribe',
597
637
  stream,
598
- feed: [feed]
638
+ feed: toUnsubscribe[stream]
599
639
  });
600
640
  }
601
641
  }
@@ -634,12 +674,24 @@ class WS {
634
674
  }
635
675
  _subscribeCurrentPairs() {
636
676
  const pairs = Object.keys(this._pairs);
637
- for (const pair of pairs) {
677
+ const groupStreams = {};
678
+ // keep last primary stream
679
+ for (const pair of pairs.reverse()) {
638
680
  const { stream, feed } = this._parsePair(pair);
681
+ if (!groupStreams[stream]) {
682
+ groupStreams[stream] = [];
683
+ }
684
+ const primaryFeed = feed.split('@')[0];
685
+ if (!groupStreams[stream].some((f) => f.startsWith(`${primaryFeed}@`))) {
686
+ groupStreams[stream].push(feed);
687
+ }
688
+ }
689
+ const streams = Object.keys(groupStreams);
690
+ for (const stream of streams) {
639
691
  this._sendMessage({
640
692
  method: 'subscribe',
641
693
  stream,
642
- feed: [feed]
694
+ feed: groupStreams[stream]
643
695
  });
644
696
  }
645
697
  }
@@ -696,8 +748,88 @@ class WS {
696
748
  void this._subscribe(pair, subscribePayload).catch(onError);
697
749
  return this._addConsumer(pair, onMessage);
698
750
  }
699
- unsubscribe(pairedConsumerKey) {
700
- this._removeConsumer(pairedConsumerKey);
751
+ subscribes(...subscriptions) {
752
+ var _a;
753
+ if (!subscriptions.length) {
754
+ return [];
755
+ }
756
+ const pairs = [];
757
+ const payloads = [];
758
+ const groupStreams = {};
759
+ for (const subscription of subscriptions) {
760
+ const { onData: onMessage, onError } = subscription, options = __rest(subscription, ["onData", "onError"]);
761
+ const subscribePayload = this._parseStream(options);
762
+ const stream = subscribePayload === null || subscribePayload === void 0 ? void 0 : subscribePayload.stream;
763
+ const feed = (_a = subscribePayload === null || subscribePayload === void 0 ? void 0 : subscribePayload.feed) === null || _a === void 0 ? void 0 : _a[0];
764
+ if (!stream || !feed) {
765
+ throw new Error('Unknown stream or feed');
766
+ }
767
+ if (!groupStreams[stream]) {
768
+ groupStreams[stream] = [];
769
+ }
770
+ const primaryFeed = feed.split('@')[0];
771
+ if (groupStreams[stream].some((f) => f.startsWith(`${primaryFeed}@`))) {
772
+ throw new Error(`Attempted to subscribe to same primary selector twice, in same request: ${primaryFeed}`);
773
+ }
774
+ groupStreams[stream].push(feed);
775
+ payloads.push(subscribePayload);
776
+ pairs.push({
777
+ pair: this._getPair({ stream, feed }),
778
+ onData: onMessage,
779
+ onError
780
+ });
781
+ }
782
+ /**
783
+ * Subscribe in parallel and listen for responses
784
+ */
785
+ setTimeout(() => __awaiter(this, void 0, void 0, function* () {
786
+ var _b;
787
+ yield this.ready();
788
+ for (const { pair, onError } of pairs) {
789
+ let _resolve;
790
+ const onPaired = (e) => {
791
+ var _a, _b, _c;
792
+ const message = utils_1.JsonUtils.parse(e.data);
793
+ if (!(message === null || message === void 0 ? void 0 : message.s) || !((_a = message === null || message === void 0 ? void 0 : message.s1) === null || _a === void 0 ? void 0 : _a.length)) {
794
+ return;
795
+ }
796
+ const responseStream = (_c = (_b = message.s) === null || _b === void 0 ? void 0 : _b.replace) === null || _c === void 0 ? void 0 : _c.call(_b, `${this._version}.`, '');
797
+ const { stream, feed } = this._parsePair(pair);
798
+ const asset = feed.split('@')[0];
799
+ const subs = message.s1;
800
+ const isResolved = stream === responseStream && (subs.includes(asset) ||
801
+ subs.includes(asset.toLowerCase()) ||
802
+ subs.includes(feed) ||
803
+ subs.includes(feed.toLowerCase()));
804
+ if (isResolved) {
805
+ _resolve();
806
+ }
807
+ };
808
+ const promise = new Promise((resolve) => {
809
+ _resolve = resolve;
810
+ this._ws.addEventListener('message', onPaired);
811
+ });
812
+ utils_1.Utils.timeout(promise, (_b = this._options.timeout) !== null && _b !== void 0 ? _b : 30000, new Error('Subscribe Timeout: ' + pair))
813
+ .catch((error) => onError === null || onError === void 0 ? void 0 : onError(error))
814
+ .finally(() => {
815
+ this._ws.removeEventListener('message', onPaired);
816
+ });
817
+ }
818
+ const streams = Object.keys(groupStreams);
819
+ for (const stream of streams) {
820
+ this._sendMessage({
821
+ method: 'subscribe',
822
+ stream,
823
+ feed: groupStreams[stream]
824
+ });
825
+ }
826
+ }));
827
+ return pairs.map(({ pair, onData }) => {
828
+ return this._addConsumer(pair, onData);
829
+ });
830
+ }
831
+ unsubscribe(...pairedConsumerKeys) {
832
+ this._removeConsumer(...pairedConsumerKeys);
701
833
  return this;
702
834
  }
703
835
  connect() {