@grvt/client 1.5.0-alpha.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/ws/ws.d.ts +12 -2
  3. package/ws/ws.js +130 -11
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.0",
4
4
  "description": "Node.js & JavaScript client for GRVT REST APIs & WebSockets",
5
5
  "repository": {
6
6
  "type": "git",
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
  */
@@ -634,12 +659,24 @@ class WS {
634
659
  }
635
660
  _subscribeCurrentPairs() {
636
661
  const pairs = Object.keys(this._pairs);
637
- for (const pair of pairs) {
662
+ const groupStreams = {};
663
+ // keep last primary stream
664
+ for (const pair of pairs.reverse()) {
638
665
  const { stream, feed } = this._parsePair(pair);
666
+ if (!groupStreams[stream]) {
667
+ groupStreams[stream] = [];
668
+ }
669
+ const primaryFeed = feed.split('@')[0];
670
+ if (!groupStreams[stream].some((f) => f.startsWith(`${primaryFeed}@`))) {
671
+ groupStreams[stream].push(feed);
672
+ }
673
+ }
674
+ const streams = Object.keys(groupStreams);
675
+ for (const stream of streams) {
639
676
  this._sendMessage({
640
677
  method: 'subscribe',
641
678
  stream,
642
- feed: [feed]
679
+ feed: groupStreams[stream]
643
680
  });
644
681
  }
645
682
  }
@@ -696,8 +733,90 @@ class WS {
696
733
  void this._subscribe(pair, subscribePayload).catch(onError);
697
734
  return this._addConsumer(pair, onMessage);
698
735
  }
699
- unsubscribe(pairedConsumerKey) {
700
- this._removeConsumer(pairedConsumerKey);
736
+ subscribes(...subscriptions) {
737
+ var _a;
738
+ if (!subscriptions.length) {
739
+ return [];
740
+ }
741
+ const pairs = [];
742
+ const payloads = [];
743
+ const groupStreams = {};
744
+ for (const subscription of subscriptions) {
745
+ const { onData: onMessage, onError } = subscription, options = __rest(subscription, ["onData", "onError"]);
746
+ const subscribePayload = this._parseStream(options);
747
+ const stream = subscribePayload === null || subscribePayload === void 0 ? void 0 : subscribePayload.stream;
748
+ const feed = (_a = subscribePayload === null || subscribePayload === void 0 ? void 0 : subscribePayload.feed) === null || _a === void 0 ? void 0 : _a[0];
749
+ if (!stream || !feed) {
750
+ throw new Error('Unknown stream or feed');
751
+ }
752
+ if (!groupStreams[stream]) {
753
+ groupStreams[stream] = [];
754
+ }
755
+ const primaryFeed = feed.split('@')[0];
756
+ if (groupStreams[stream].some((f) => f.startsWith(`${primaryFeed}@`))) {
757
+ throw new Error(`Attempted to subscribe to same primary selector twice, in same request: ${primaryFeed}`);
758
+ }
759
+ groupStreams[stream].push(feed);
760
+ payloads.push(subscribePayload);
761
+ pairs.push({
762
+ pair: this._getPair({ stream, feed }),
763
+ onData: onMessage,
764
+ onError
765
+ });
766
+ }
767
+ /**
768
+ * Subscribe in parallel and listen for responses
769
+ */
770
+ setTimeout(() => __awaiter(this, void 0, void 0, function* () {
771
+ var _b;
772
+ yield this.ready();
773
+ for (const { pair, onError } of pairs) {
774
+ let _resolve;
775
+ const onPaired = (e) => {
776
+ var _a, _b, _c;
777
+ const message = utils_1.JsonUtils.parse(e.data);
778
+ 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)) {
779
+ return;
780
+ }
781
+ 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}.`, '');
782
+ const { stream, feed } = this._parsePair(pair);
783
+ const asset = feed.split('@')[0];
784
+ const subs = message.s1;
785
+ const isResolved = stream === responseStream && (subs.includes(asset) ||
786
+ subs.includes(asset.toLowerCase()) ||
787
+ subs.includes(feed) ||
788
+ subs.includes(feed.toLowerCase()));
789
+ if (isResolved) {
790
+ _resolve();
791
+ }
792
+ };
793
+ const promise = new Promise((resolve) => {
794
+ _resolve = resolve;
795
+ this._ws.addEventListener('message', onPaired);
796
+ });
797
+ utils_1.Utils.timeout(promise, (_b = this._options.timeout) !== null && _b !== void 0 ? _b : 30000, new Error('Subscribe Timeout: ' + pair))
798
+ .catch((error) => onError === null || onError === void 0 ? void 0 : onError(error))
799
+ .finally(() => {
800
+ this._ws.removeEventListener('message', onPaired);
801
+ });
802
+ }
803
+ const streams = Object.keys(groupStreams);
804
+ for (const stream of streams) {
805
+ this._sendMessage({
806
+ method: 'subscribe',
807
+ stream,
808
+ feed: groupStreams[stream]
809
+ });
810
+ }
811
+ }));
812
+ return pairs.map(({ pair, onData }) => {
813
+ return this._addConsumer(pair, onData);
814
+ });
815
+ }
816
+ unsubscribe(...pairedConsumerKeys) {
817
+ for (const pairedConsumerKey of pairedConsumerKeys) {
818
+ this._removeConsumer(pairedConsumerKey);
819
+ }
701
820
  return this;
702
821
  }
703
822
  connect() {