@openfeed/sdk-js 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
+
5
+ ### [1.3.1](https://github.com/openfeed-org/sdk-js/compare/1.3.0...1.3.1) (2025-01-08)
6
+
7
+
8
+ ### Bug Fixes
9
+
10
+ * fix type issues ([926eb95](https://github.com/openfeed-org/sdk-js/commit/926eb95db5d3aefa743610c00531438aa8fa46d8))
package/CHANGELOG.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- ### [1.3.1](https://github.com/openfeed-org/sdk-js/compare/1.3.0...1.3.1) (2025-01-08)
5
+ ### [1.3.2](https://github.com/openfeed-org/sdk-js/compare/1.3.1...1.3.2) (2025-01-23)
6
6
 
7
7
 
8
8
  ### Bug Fixes
9
9
 
10
- * fix type issues ([926eb95](https://github.com/openfeed-org/sdk-js/commit/926eb95db5d3aefa743610c00531438aa8fa46d8))
10
+ * address issues with subscription type and error subscription messages, expand tests ([8c82453](https://github.com/openfeed-org/sdk-js/commit/8c82453f18435418b30d1f5c37a6106bb1e50a65))
package/DOCUMENTATION.md CHANGED
@@ -147,7 +147,7 @@ enum SubscriptionType {
147
147
  DEPTH_PRICE = 3,
148
148
  DEPTH_ORDER = 4,
149
149
  TRADES = 5,
150
- CUMLATIVE_VOLUME = 6,
150
+ CUMULATIVE_VOLUME = 6,
151
151
  OHLC = 7,
152
152
  OHLC_NON_REGULAR = 8,
153
153
  }
@@ -158,12 +158,12 @@ enum SubscriptionType {
158
158
  Now that we know how to use the listener and client objects, let's put together a little demo that subscribes to all MSFT messages and prints what's happening to the console:
159
159
 
160
160
  ```ts
161
- import { OpenFeedClient } from "./connection/connection";
161
+ import { OpenFeedClient } from "./connection/client";
162
162
 
163
163
  import { Service } from "../generated/openfeed";
164
164
  import { SubscriptionType } from "../generated/openfeed_api";
165
165
  import { OpenFeedListeners } from "./connection/listeners";
166
- import { IOpenFeedLogger } from "./connection/connection_interfaces";
166
+ import { IOpenFeedLogger } from "./connection/interfaces";
167
167
 
168
168
  const connect = async () => {
169
169
  const logger: IOpenFeedLogger = console;
@@ -1 +1 @@
1
- export declare const version = "1.3.1";
1
+ export declare const version = "1.3.2";
package/dist/index.js CHANGED
@@ -933,7 +933,6 @@ Long.fromBytesBE = function fromBytesBE(bytes, unsigned) {
933
933
  unsigned
934
934
  );
935
935
  };
936
- const toT = (obj) => obj;
937
936
  var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {};
938
937
  function getDefaultExportFromCjs(x) {
939
938
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
@@ -8259,7 +8258,15 @@ class ResolutionSource {
8259
8258
  return this.onError;
8260
8259
  }
8261
8260
  }
8262
- const version = "1.3.1";
8261
+ const _CorrelationId = class _CorrelationId {
8262
+ };
8263
+ __publicField(_CorrelationId, "correlationId", Long.fromNumber(-1));
8264
+ __publicField(_CorrelationId, "create", () => {
8265
+ _CorrelationId.correlationId = _CorrelationId.correlationId.add(1);
8266
+ return _CorrelationId.correlationId;
8267
+ });
8268
+ let CorrelationId = _CorrelationId;
8269
+ const toT = (obj) => obj;
8263
8270
  const send = (socket, message) => {
8264
8271
  socket.send(OpenfeedGatewayRequestEncode.encode(toT(message)).finish());
8265
8272
  };
@@ -8276,14 +8283,11 @@ const receive = (msgEvent) => {
8276
8283
  }
8277
8284
  return res;
8278
8285
  };
8279
- const _CorrelationId = class _CorrelationId {
8286
+ const TIME = {
8287
+ RECONNECT: 5e3,
8288
+ CONNECTION_TIMEOUT: 15e3,
8289
+ SUBSCRIPTION_RETRY: 100
8280
8290
  };
8281
- __publicField(_CorrelationId, "correlationId", Long.fromNumber(-1));
8282
- __publicField(_CorrelationId, "create", () => {
8283
- _CorrelationId.correlationId = _CorrelationId.correlationId.add(1);
8284
- return _CorrelationId.correlationId;
8285
- });
8286
- let CorrelationId = _CorrelationId;
8287
8291
  class DuplicateLoginError extends Error {
8288
8292
  get name() {
8289
8293
  return this.constructor.name;
@@ -8313,7 +8317,7 @@ class OpenFeedConnection {
8313
8317
  for (; ; ) {
8314
8318
  let timeoutId = null;
8315
8319
  const waitPromise = new Promise((resolve) => {
8316
- timeoutId = setTimeout(resolve, 15e3);
8320
+ timeoutId = setTimeout(resolve, TIME.CONNECTION_TIMEOUT);
8317
8321
  });
8318
8322
  try {
8319
8323
  await Promise.race([waitPromise, this.whenDisconnectedSource.whenCompleted]);
@@ -8484,7 +8488,7 @@ class OpenFeedConnection {
8484
8488
  throw new Error(`Subscription ID ${subscriptionId} does not exist.`);
8485
8489
  }
8486
8490
  const [originalRequest, sub] = subscription;
8487
- return this.fireUnsubscribeWhenReady(originalRequest, sub);
8491
+ this.fireUnsubscribeWhenReady(originalRequest, sub);
8488
8492
  });
8489
8493
  __publicField(this, "getExchanges", async () => {
8490
8494
  var _a;
@@ -8596,6 +8600,23 @@ class OpenFeedConnection {
8596
8600
  }
8597
8601
  }
8598
8602
  }
8603
+ const version = "1.3.2";
8604
+ const getClientVersion = async (clientId) => {
8605
+ var _a, _b, _c, _d, _e, _f;
8606
+ let platformDescription;
8607
+ if (typeof window !== "undefined") {
8608
+ platformDescription = navigator.userAgent;
8609
+ } else {
8610
+ const os = await import("./empty-411f875a.js");
8611
+ try {
8612
+ platformDescription = `Node.js ${process.version}; ${(_a = os.version) == null ? void 0 : _a.call(os)} ${(_b = os.release) == null ? void 0 : _b.call(os)}; ${(_c = os.arch) == null ? void 0 : _c.call(os)};`;
8613
+ } catch (e) {
8614
+ platformDescription = `Unknown OS; ${((_d = os.version) == null ? void 0 : _d.call(os)) ?? ""} ${((_e = os.release) == null ? void 0 : _e.call(os)) ?? ""}; ${((_f = os.arch) == null ? void 0 : _f.call(os)) ?? ""};`;
8615
+ }
8616
+ }
8617
+ const clientVersion = `sdk-js:${version};client-id:${clientId ?? "default"};platform:${platformDescription}`;
8618
+ return clientVersion;
8619
+ };
8599
8620
  class OpenFeedClient {
8600
8621
  constructor(url, username, password, listeners, logger, clientId) {
8601
8622
  __publicField(this, "socket", null);
@@ -8607,21 +8628,9 @@ class OpenFeedClient {
8607
8628
  __publicField(this, "subscribeResetSource", new ResolutionSource());
8608
8629
  __publicField(this, "subscriptions", /* @__PURE__ */ new Map());
8609
8630
  __publicField(this, "onOpen", async () => {
8610
- var _a, _b, _c, _d, _e, _f;
8611
8631
  if (!this.socket)
8612
8632
  return;
8613
- let platformDescription;
8614
- if (typeof window !== "undefined") {
8615
- platformDescription = navigator.userAgent;
8616
- } else {
8617
- const os = await import("./empty-411f875a.js");
8618
- try {
8619
- platformDescription = `Node.js ${process.version}; ${(_a = os.version) == null ? void 0 : _a.call(os)} ${(_b = os.release) == null ? void 0 : _b.call(os)}; ${(_c = os.arch) == null ? void 0 : _c.call(os)};`;
8620
- } catch (e) {
8621
- platformDescription = `Unknown OS; ${((_d = os.version) == null ? void 0 : _d.call(os)) ?? ""} ${((_e = os.release) == null ? void 0 : _e.call(os)) ?? ""}; ${((_f = os.arch) == null ? void 0 : _f.call(os)) ?? ""};`;
8622
- }
8623
- }
8624
- const clientVersion = `sdk-js:${version};client-id:${this.clientId ?? "default"};platform:${platformDescription}`;
8633
+ const clientVersion = await getClientVersion(this.clientId);
8625
8634
  const loginRequest = {
8626
8635
  loginRequest: {
8627
8636
  correlationId: CorrelationId.create(),
@@ -8683,7 +8692,6 @@ class OpenFeedClient {
8683
8692
  __publicField(this, "runConnectLoop", async () => {
8684
8693
  var _a, _b;
8685
8694
  for (; ; ) {
8686
- let timeoutId = null;
8687
8695
  if (this.socket) {
8688
8696
  if (this.socket.readyState !== WebSocket$1.CLOSED && this.socket.readyState !== WebSocket$1.CLOSING) {
8689
8697
  this.socket.close(1e3, "Closed from socket loop");
@@ -8733,15 +8741,9 @@ class OpenFeedClient {
8733
8741
  this.loopResetSource = new ResolutionSource();
8734
8742
  this.subscribeResetSource.resolve();
8735
8743
  this.subscribeResetSource = new ResolutionSource();
8736
- try {
8737
- await new Promise((resolve) => {
8738
- timeoutId = setTimeout(resolve, 5e3);
8739
- });
8740
- } finally {
8741
- if (timeoutId) {
8742
- clearTimeout(timeoutId);
8743
- }
8744
- }
8744
+ await new Promise((resolve) => {
8745
+ setTimeout(resolve, TIME.RECONNECT);
8746
+ });
8745
8747
  }
8746
8748
  });
8747
8749
  __publicField(this, "cleanUp", () => {
@@ -8754,11 +8756,10 @@ class OpenFeedClient {
8754
8756
  __publicField(this, "runSubscribeLoop", async (service2, subscriptionType, snapshotIntervalSeconds, symbols, marketIds, exchanges, channels, cancelSource) => {
8755
8757
  var _a;
8756
8758
  for (; ; ) {
8757
- let timeoutId = null;
8758
8759
  try {
8759
8760
  const connection = await Promise.race([this.connection, cancelSource.whenCompleted]);
8760
8761
  if (cancelSource.completed || /* can't actually happen */
8761
- !(connection instanceof OpenFeedConnection)) {
8762
+ !connection) {
8762
8763
  return;
8763
8764
  }
8764
8765
  const subscriptionId = connection.subscribe(
@@ -8781,12 +8782,8 @@ class OpenFeedClient {
8781
8782
  } catch (error) {
8782
8783
  (_a = this.logger) == null ? void 0 : _a.warn("Subscription error:", error);
8783
8784
  await new Promise((resolve) => {
8784
- timeoutId = setTimeout(resolve, 100);
8785
+ setTimeout(resolve, TIME.SUBSCRIPTION_RETRY);
8785
8786
  });
8786
- } finally {
8787
- if (timeoutId) {
8788
- clearTimeout(timeoutId);
8789
- }
8790
8787
  }
8791
8788
  }
8792
8789
  });
@@ -8849,7 +8846,6 @@ const IDGetters = [
8849
8846
  ];
8850
8847
  class OpenFeedListeners {
8851
8848
  constructor() {
8852
- __publicField(this, "instrumentBySymbol", /* @__PURE__ */ new Map());
8853
8849
  __publicField(this, "instrumentByMarketId", /* @__PURE__ */ new Map());
8854
8850
  __publicField(this, "addDetails", (message) => {
8855
8851
  var _a, _b, _c;
@@ -8859,33 +8855,35 @@ class OpenFeedListeners {
8859
8855
  const res = this.instrumentByMarketId.get(marketId.toString());
8860
8856
  return res ?? [void 0, void 0];
8861
8857
  };
8858
+ const includesSymbolSubscription = (arr, item) => {
8859
+ return arr.some(([symbol, type]) => symbol === item[0] && type === item[1]);
8860
+ };
8862
8861
  if (message.subscriptionResponse) {
8863
- const { marketId, symbol, unsubscribe } = message.subscriptionResponse;
8862
+ const { marketId, symbol, unsubscribe, status, subscriptionType } = message.subscriptionResponse;
8864
8863
  if (marketId !== Long.ZERO) {
8865
8864
  [def, symbols] = getInstrumentDefinition(marketId);
8866
- if (!unsubscribe) {
8867
- if (!symbols) {
8868
- symbols = [symbol];
8869
- } else if (!symbols.includes(symbol)) {
8870
- symbols = [...symbols, symbol];
8871
- }
8872
- } else {
8873
- if (symbols) {
8874
- symbols = symbols.filter((s) => s !== symbol);
8875
- }
8876
- if (!symbols) {
8877
- this.instrumentByMarketId.delete(marketId.toString());
8865
+ if ((status == null ? void 0 : status.result) === Result.SUCCESS) {
8866
+ const currentEntry = [symbol, subscriptionType];
8867
+ if (!unsubscribe) {
8868
+ if (!symbols) {
8869
+ symbols = [currentEntry];
8870
+ } else if (!includesSymbolSubscription(symbols, currentEntry)) {
8871
+ symbols = [...symbols, currentEntry];
8872
+ }
8873
+ } else {
8874
+ if (symbols) {
8875
+ symbols = symbols.filter(([s, t]) => !(s === symbol && t === subscriptionType));
8876
+ }
8877
+ if (!symbols) {
8878
+ this.instrumentByMarketId.delete(marketId.toString());
8879
+ }
8878
8880
  }
8879
- this.instrumentBySymbol.delete(symbol);
8881
+ this.instrumentByMarketId.set(marketId.toString(), [def, symbols]);
8880
8882
  }
8881
- this.instrumentByMarketId.set(marketId.toString(), [def, symbols]);
8882
8883
  }
8883
8884
  } else if (message.instrumentDefinition) {
8884
8885
  [def, symbols] = getInstrumentDefinition(message.instrumentDefinition.marketId);
8885
- const { instrumentDefinition } = message;
8886
8886
  this.instrumentByMarketId.set(message.instrumentDefinition.marketId.toString(), [message.instrumentDefinition, symbols]);
8887
- this.instrumentBySymbol.set(message.instrumentDefinition.symbol, instrumentDefinition);
8888
- symbols == null ? void 0 : symbols.forEach((s) => this.instrumentBySymbol.set(s, instrumentDefinition));
8889
8887
  } else if (message.instrumentAction) {
8890
8888
  const { marketId } = ((_a = message.instrumentAction) == null ? void 0 : _a.instrument) ?? {};
8891
8889
  if (message.instrumentAction.action === ActionType.ALIAS_CHANGED && marketId) {
@@ -8894,7 +8892,7 @@ class OpenFeedListeners {
8894
8892
  const newAliasNum = oldAlias.endsWith("*0") ? 0 : Number.parseInt(num, 10) + 1;
8895
8893
  const newAlias = `${root}*${newAliasNum}`;
8896
8894
  [def, symbols] = getInstrumentDefinition(marketId);
8897
- const newSymbols = (symbols == null ? void 0 : symbols.filter((s) => s !== oldAlias)) ?? [];
8895
+ const newSymbols = (symbols == null ? void 0 : symbols.filter(([s]) => s !== oldAlias)) ?? [];
8898
8896
  if (!newSymbols.length) {
8899
8897
  this.instrumentByMarketId.delete(marketId.toString());
8900
8898
  } else {
@@ -8903,7 +8901,7 @@ class OpenFeedListeners {
8903
8901
  const { marketId: newMarketId } = message.instrumentAction.newInstrument ?? {};
8904
8902
  if (newMarketId) {
8905
8903
  const [newDef, newSym] = getInstrumentDefinition(newMarketId);
8906
- const newSymbolsFiltered = (newAlias === oldAlias ? newSym : newSym == null ? void 0 : newSym.filter((s) => s !== newAlias)) ?? [];
8904
+ const newSymbolsFiltered = (newAlias === oldAlias ? newSym : newSym == null ? void 0 : newSym.filter(([s]) => s !== newAlias)) ?? [];
8907
8905
  if (!newSymbolsFiltered.length) {
8908
8906
  this.instrumentByMarketId.delete(newMarketId.toString());
8909
8907
  } else {
@@ -8928,7 +8926,8 @@ class OpenFeedListeners {
8928
8926
  }
8929
8927
  }
8930
8928
  }
8931
- return this.onMessageWithMetadata(message, symbols ?? [], def);
8929
+ const uniqueSymbols = Array.from(new Set((symbols == null ? void 0 : symbols.map(([symbol]) => symbol)) ?? []));
8930
+ return this.onMessageWithMetadata(message, uniqueSymbols, def);
8932
8931
  });
8933
8932
  /* eslint-disable class-methods-use-this */
8934
8933
  __publicField(this, "onConnected", () => {
package/dist/node.js CHANGED
@@ -4934,7 +4934,7 @@ var require_minimal2 = __commonJS({
4934
4934
  }
4935
4935
  });
4936
4936
 
4937
- // src/connection/connection.ts
4937
+ // src/connection/client.ts
4938
4938
  var import_isomorphic_ws = __toESM(require_node(), 1);
4939
4939
 
4940
4940
  // node_modules/long/index.js
@@ -5837,9 +5837,6 @@ Long.fromBytesBE = function fromBytesBE(bytes, unsigned) {
5837
5837
  };
5838
5838
  var long_default = Long;
5839
5839
 
5840
- // src/utilities/messages.ts
5841
- var toT = (obj) => obj;
5842
-
5843
5840
  // generated/openfeed_api.ts
5844
5841
  var import_minimal3 = __toESM(require_minimal2(), 1);
5845
5842
 
@@ -10828,19 +10825,19 @@ var Result = /* @__PURE__ */ ((Result2) => {
10828
10825
  Result2[Result2["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
10829
10826
  return Result2;
10830
10827
  })(Result || {});
10831
- var SubscriptionType = /* @__PURE__ */ ((SubscriptionType2) => {
10832
- SubscriptionType2[SubscriptionType2["ALL"] = 0] = "ALL";
10833
- SubscriptionType2[SubscriptionType2["QUOTE"] = 1] = "QUOTE";
10834
- SubscriptionType2[SubscriptionType2["QUOTE_PARTICIPANT"] = 2] = "QUOTE_PARTICIPANT";
10835
- SubscriptionType2[SubscriptionType2["DEPTH_PRICE"] = 3] = "DEPTH_PRICE";
10836
- SubscriptionType2[SubscriptionType2["DEPTH_ORDER"] = 4] = "DEPTH_ORDER";
10837
- SubscriptionType2[SubscriptionType2["TRADES"] = 5] = "TRADES";
10838
- SubscriptionType2[SubscriptionType2["CUMULATIVE_VOLUME"] = 6] = "CUMULATIVE_VOLUME";
10839
- SubscriptionType2[SubscriptionType2["OHLC"] = 7] = "OHLC";
10840
- SubscriptionType2[SubscriptionType2["OHLC_NON_REGULAR"] = 8] = "OHLC_NON_REGULAR";
10841
- SubscriptionType2[SubscriptionType2["SETTLEMENT"] = 9] = "SETTLEMENT";
10842
- SubscriptionType2[SubscriptionType2["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
10843
- return SubscriptionType2;
10828
+ var SubscriptionType = /* @__PURE__ */ ((SubscriptionType3) => {
10829
+ SubscriptionType3[SubscriptionType3["ALL"] = 0] = "ALL";
10830
+ SubscriptionType3[SubscriptionType3["QUOTE"] = 1] = "QUOTE";
10831
+ SubscriptionType3[SubscriptionType3["QUOTE_PARTICIPANT"] = 2] = "QUOTE_PARTICIPANT";
10832
+ SubscriptionType3[SubscriptionType3["DEPTH_PRICE"] = 3] = "DEPTH_PRICE";
10833
+ SubscriptionType3[SubscriptionType3["DEPTH_ORDER"] = 4] = "DEPTH_ORDER";
10834
+ SubscriptionType3[SubscriptionType3["TRADES"] = 5] = "TRADES";
10835
+ SubscriptionType3[SubscriptionType3["CUMULATIVE_VOLUME"] = 6] = "CUMULATIVE_VOLUME";
10836
+ SubscriptionType3[SubscriptionType3["OHLC"] = 7] = "OHLC";
10837
+ SubscriptionType3[SubscriptionType3["OHLC_NON_REGULAR"] = 8] = "OHLC_NON_REGULAR";
10838
+ SubscriptionType3[SubscriptionType3["SETTLEMENT"] = 9] = "SETTLEMENT";
10839
+ SubscriptionType3[SubscriptionType3["UNRECOGNIZED"] = -1] = "UNRECOGNIZED";
10840
+ return SubscriptionType3;
10844
10841
  })(SubscriptionType || {});
10845
10842
  var SymbolType = /* @__PURE__ */ ((SymbolType2) => {
10846
10843
  SymbolType2[SymbolType2["BARCHART"] = 0] = "BARCHART";
@@ -11882,10 +11879,19 @@ var ResolutionSource = class {
11882
11879
  }
11883
11880
  };
11884
11881
 
11885
- // generated/version.ts
11886
- var version = "1.3.1";
11882
+ // src/utilities/correlation_id.ts
11883
+ var CorrelationId = class {
11884
+ static correlationId = long_default.fromNumber(-1);
11885
+ static create = () => {
11886
+ this.correlationId = this.correlationId.add(1);
11887
+ return this.correlationId;
11888
+ };
11889
+ };
11887
11890
 
11888
- // src/connection/connection.ts
11891
+ // src/utilities/messages.ts
11892
+ var toT = (obj) => obj;
11893
+
11894
+ // src/utilities/communication.ts
11889
11895
  var send = (socket, message) => {
11890
11896
  socket.send(OpenfeedGatewayRequestEncode.encode(toT(message)).finish());
11891
11897
  };
@@ -11902,13 +11908,15 @@ var receive = (msgEvent) => {
11902
11908
  }
11903
11909
  return res;
11904
11910
  };
11905
- var CorrelationId = class {
11906
- static correlationId = long_default.fromNumber(-1);
11907
- static create = () => {
11908
- this.correlationId = this.correlationId.add(1);
11909
- return this.correlationId;
11910
- };
11911
+
11912
+ // src/utilities/constants.ts
11913
+ var TIME = {
11914
+ RECONNECT: 5e3,
11915
+ CONNECTION_TIMEOUT: 15e3,
11916
+ SUBSCRIPTION_RETRY: 100
11911
11917
  };
11918
+
11919
+ // src/connection/errors.ts
11912
11920
  var DuplicateLoginError = class extends Error {
11913
11921
  get name() {
11914
11922
  return this.constructor.name;
@@ -11924,6 +11932,8 @@ var ConnectionDisposedError = class extends Error {
11924
11932
  return this.constructor.name;
11925
11933
  }
11926
11934
  };
11935
+
11936
+ // src/connection/connection.ts
11927
11937
  var OpenFeedConnection = class {
11928
11938
  constructor(connectionToken, socket, listeners, logger) {
11929
11939
  this.connectionToken = connectionToken;
@@ -11947,7 +11957,7 @@ var OpenFeedConnection = class {
11947
11957
  for (; ; ) {
11948
11958
  let timeoutId = null;
11949
11959
  const waitPromise = new Promise((resolve) => {
11950
- timeoutId = setTimeout(resolve, 15e3);
11960
+ timeoutId = setTimeout(resolve, TIME.CONNECTION_TIMEOUT);
11951
11961
  });
11952
11962
  try {
11953
11963
  await Promise.race([waitPromise, this.whenDisconnectedSource.whenCompleted]);
@@ -12136,7 +12146,7 @@ var OpenFeedConnection = class {
12136
12146
  throw new Error(`Subscription ID ${subscriptionId} does not exist.`);
12137
12147
  }
12138
12148
  const [originalRequest, sub] = subscription;
12139
- return this.fireUnsubscribeWhenReady(originalRequest, sub);
12149
+ this.fireUnsubscribeWhenReady(originalRequest, sub);
12140
12150
  };
12141
12151
  // We are keeping this fire and forget, because our problems
12142
12152
  // would be caused by disconnection, and reconnect will clean up the rest
@@ -12221,6 +12231,29 @@ var OpenFeedConnection = class {
12221
12231
  whenDisconnected = () => this.whenDisconnectedSource.whenCompleted;
12222
12232
  dispose = () => this.disconnect(new ConnectionDisposedError("Disposed"));
12223
12233
  };
12234
+
12235
+ // generated/version.ts
12236
+ var version = "1.3.2";
12237
+
12238
+ // src/utilities/client_version.ts
12239
+ var getClientVersion = async (clientId) => {
12240
+ var _a, _b, _c, _d, _e, _f;
12241
+ let platformDescription;
12242
+ if (typeof window !== "undefined") {
12243
+ platformDescription = navigator.userAgent;
12244
+ } else {
12245
+ const os = await import("os");
12246
+ try {
12247
+ platformDescription = `Node.js ${process.version}; ${(_a = os.version) == null ? void 0 : _a.call(os)} ${(_b = os.release) == null ? void 0 : _b.call(os)}; ${(_c = os.arch) == null ? void 0 : _c.call(os)};`;
12248
+ } catch (e) {
12249
+ platformDescription = `Unknown OS; ${((_d = os.version) == null ? void 0 : _d.call(os)) ?? ""} ${((_e = os.release) == null ? void 0 : _e.call(os)) ?? ""}; ${((_f = os.arch) == null ? void 0 : _f.call(os)) ?? ""};`;
12250
+ }
12251
+ }
12252
+ const clientVersion = `sdk-js:${version};client-id:${clientId ?? "default"};platform:${platformDescription}`;
12253
+ return clientVersion;
12254
+ };
12255
+
12256
+ // src/connection/client.ts
12224
12257
  var OpenFeedClient = class {
12225
12258
  constructor(url, username, password, listeners, logger, clientId) {
12226
12259
  this.url = url;
@@ -12240,21 +12273,9 @@ var OpenFeedClient = class {
12240
12273
  subscribeResetSource = new ResolutionSource();
12241
12274
  subscriptions = /* @__PURE__ */ new Map();
12242
12275
  onOpen = async () => {
12243
- var _a, _b, _c, _d, _e, _f;
12244
12276
  if (!this.socket)
12245
12277
  return;
12246
- let platformDescription;
12247
- if (typeof window !== "undefined") {
12248
- platformDescription = navigator.userAgent;
12249
- } else {
12250
- const os = await import("os");
12251
- try {
12252
- platformDescription = `Node.js ${process.version}; ${(_a = os.version) == null ? void 0 : _a.call(os)} ${(_b = os.release) == null ? void 0 : _b.call(os)}; ${(_c = os.arch) == null ? void 0 : _c.call(os)};`;
12253
- } catch (e) {
12254
- platformDescription = `Unknown OS; ${((_d = os.version) == null ? void 0 : _d.call(os)) ?? ""} ${((_e = os.release) == null ? void 0 : _e.call(os)) ?? ""}; ${((_f = os.arch) == null ? void 0 : _f.call(os)) ?? ""};`;
12255
- }
12256
- }
12257
- const clientVersion = `sdk-js:${version};client-id:${this.clientId ?? "default"};platform:${platformDescription}`;
12278
+ const clientVersion = await getClientVersion(this.clientId);
12258
12279
  const loginRequest = {
12259
12280
  loginRequest: {
12260
12281
  correlationId: CorrelationId.create(),
@@ -12316,7 +12337,6 @@ var OpenFeedClient = class {
12316
12337
  runConnectLoop = async () => {
12317
12338
  var _a, _b;
12318
12339
  for (; ; ) {
12319
- let timeoutId = null;
12320
12340
  if (this.socket) {
12321
12341
  if (this.socket.readyState !== import_isomorphic_ws.default.CLOSED && this.socket.readyState !== import_isomorphic_ws.default.CLOSING) {
12322
12342
  this.socket.close(1e3, "Closed from socket loop");
@@ -12366,15 +12386,9 @@ var OpenFeedClient = class {
12366
12386
  this.loopResetSource = new ResolutionSource();
12367
12387
  this.subscribeResetSource.resolve();
12368
12388
  this.subscribeResetSource = new ResolutionSource();
12369
- try {
12370
- await new Promise((resolve) => {
12371
- timeoutId = setTimeout(resolve, 5e3);
12372
- });
12373
- } finally {
12374
- if (timeoutId) {
12375
- clearTimeout(timeoutId);
12376
- }
12377
- }
12389
+ await new Promise((resolve) => {
12390
+ setTimeout(resolve, TIME.RECONNECT);
12391
+ });
12378
12392
  }
12379
12393
  };
12380
12394
  cleanUp = () => {
@@ -12387,11 +12401,10 @@ var OpenFeedClient = class {
12387
12401
  runSubscribeLoop = async (service, subscriptionType, snapshotIntervalSeconds, symbols, marketIds, exchanges, channels, cancelSource) => {
12388
12402
  var _a;
12389
12403
  for (; ; ) {
12390
- let timeoutId = null;
12391
12404
  try {
12392
12405
  const connection = await Promise.race([this.connection, cancelSource.whenCompleted]);
12393
12406
  if (cancelSource.completed || /* can't actually happen */
12394
- !(connection instanceof OpenFeedConnection)) {
12407
+ !connection) {
12395
12408
  return;
12396
12409
  }
12397
12410
  const subscriptionId = connection.subscribe(
@@ -12414,12 +12427,8 @@ var OpenFeedClient = class {
12414
12427
  } catch (error) {
12415
12428
  (_a = this.logger) == null ? void 0 : _a.warn("Subscription error:", error);
12416
12429
  await new Promise((resolve) => {
12417
- timeoutId = setTimeout(resolve, 100);
12430
+ setTimeout(resolve, TIME.SUBSCRIPTION_RETRY);
12418
12431
  });
12419
- } finally {
12420
- if (timeoutId) {
12421
- clearTimeout(timeoutId);
12422
- }
12423
12432
  }
12424
12433
  }
12425
12434
  };
@@ -12475,7 +12484,6 @@ var IDGetters = [
12475
12484
  }
12476
12485
  ];
12477
12486
  var OpenFeedListeners = class {
12478
- instrumentBySymbol = /* @__PURE__ */ new Map();
12479
12487
  instrumentByMarketId = /* @__PURE__ */ new Map();
12480
12488
  constructor() {
12481
12489
  this.onMessage = this.addDetails;
@@ -12488,33 +12496,35 @@ var OpenFeedListeners = class {
12488
12496
  const res = this.instrumentByMarketId.get(marketId.toString());
12489
12497
  return res ?? [void 0, void 0];
12490
12498
  };
12499
+ const includesSymbolSubscription = (arr, item) => {
12500
+ return arr.some(([symbol, type]) => symbol === item[0] && type === item[1]);
12501
+ };
12491
12502
  if (message.subscriptionResponse) {
12492
- const { marketId, symbol, unsubscribe } = message.subscriptionResponse;
12503
+ const { marketId, symbol, unsubscribe, status, subscriptionType } = message.subscriptionResponse;
12493
12504
  if (marketId !== long_default.ZERO) {
12494
12505
  [def, symbols] = getInstrumentDefinition(marketId);
12495
- if (!unsubscribe) {
12496
- if (!symbols) {
12497
- symbols = [symbol];
12498
- } else if (!symbols.includes(symbol)) {
12499
- symbols = [...symbols, symbol];
12500
- }
12501
- } else {
12502
- if (symbols) {
12503
- symbols = symbols.filter((s) => s !== symbol);
12504
- }
12505
- if (!symbols) {
12506
- this.instrumentByMarketId.delete(marketId.toString());
12506
+ if ((status == null ? void 0 : status.result) === 1 /* SUCCESS */) {
12507
+ const currentEntry = [symbol, subscriptionType];
12508
+ if (!unsubscribe) {
12509
+ if (!symbols) {
12510
+ symbols = [currentEntry];
12511
+ } else if (!includesSymbolSubscription(symbols, currentEntry)) {
12512
+ symbols = [...symbols, currentEntry];
12513
+ }
12514
+ } else {
12515
+ if (symbols) {
12516
+ symbols = symbols.filter(([s, t]) => !(s === symbol && t === subscriptionType));
12517
+ }
12518
+ if (!symbols) {
12519
+ this.instrumentByMarketId.delete(marketId.toString());
12520
+ }
12507
12521
  }
12508
- this.instrumentBySymbol.delete(symbol);
12522
+ this.instrumentByMarketId.set(marketId.toString(), [def, symbols]);
12509
12523
  }
12510
- this.instrumentByMarketId.set(marketId.toString(), [def, symbols]);
12511
12524
  }
12512
12525
  } else if (message.instrumentDefinition) {
12513
12526
  [def, symbols] = getInstrumentDefinition(message.instrumentDefinition.marketId);
12514
- const { instrumentDefinition } = message;
12515
12527
  this.instrumentByMarketId.set(message.instrumentDefinition.marketId.toString(), [message.instrumentDefinition, symbols]);
12516
- this.instrumentBySymbol.set(message.instrumentDefinition.symbol, instrumentDefinition);
12517
- symbols == null ? void 0 : symbols.forEach((s) => this.instrumentBySymbol.set(s, instrumentDefinition));
12518
12528
  } else if (message.instrumentAction) {
12519
12529
  const { marketId } = ((_a = message.instrumentAction) == null ? void 0 : _a.instrument) ?? {};
12520
12530
  if (message.instrumentAction.action === 4 /* ALIAS_CHANGED */ && marketId) {
@@ -12523,7 +12533,7 @@ var OpenFeedListeners = class {
12523
12533
  const newAliasNum = oldAlias.endsWith("*0") ? 0 : Number.parseInt(num, 10) + 1;
12524
12534
  const newAlias = `${root}*${newAliasNum}`;
12525
12535
  [def, symbols] = getInstrumentDefinition(marketId);
12526
- const newSymbols = (symbols == null ? void 0 : symbols.filter((s) => s !== oldAlias)) ?? [];
12536
+ const newSymbols = (symbols == null ? void 0 : symbols.filter(([s]) => s !== oldAlias)) ?? [];
12527
12537
  if (!newSymbols.length) {
12528
12538
  this.instrumentByMarketId.delete(marketId.toString());
12529
12539
  } else {
@@ -12532,7 +12542,7 @@ var OpenFeedListeners = class {
12532
12542
  const { marketId: newMarketId } = message.instrumentAction.newInstrument ?? {};
12533
12543
  if (newMarketId) {
12534
12544
  const [newDef, newSym] = getInstrumentDefinition(newMarketId);
12535
- const newSymbolsFiltered = (newAlias === oldAlias ? newSym : newSym == null ? void 0 : newSym.filter((s) => s !== newAlias)) ?? [];
12545
+ const newSymbolsFiltered = (newAlias === oldAlias ? newSym : newSym == null ? void 0 : newSym.filter(([s]) => s !== newAlias)) ?? [];
12536
12546
  if (!newSymbolsFiltered.length) {
12537
12547
  this.instrumentByMarketId.delete(newMarketId.toString());
12538
12548
  } else {
@@ -12557,7 +12567,8 @@ var OpenFeedListeners = class {
12557
12567
  }
12558
12568
  }
12559
12569
  }
12560
- return this.onMessageWithMetadata(message, symbols ?? [], def);
12570
+ const uniqueSymbols = Array.from(new Set((symbols == null ? void 0 : symbols.map(([symbol]) => symbol)) ?? []));
12571
+ return this.onMessageWithMetadata(message, uniqueSymbols, def);
12561
12572
  };
12562
12573
  /* eslint-disable class-methods-use-this */
12563
12574
  onConnected = () => {
@@ -0,0 +1,32 @@
1
+ import Long from "long";
2
+ import type { SubscriptionType } from "@gen/openfeed_api";
3
+ import type { Service } from "@gen/openfeed";
4
+ import { IOpenFeedClient, IOpenFeedConnection, IOpenFeedLogger } from "./interfaces";
5
+ import { OpenFeedListeners } from "./listeners";
6
+ export declare class OpenFeedClient implements IOpenFeedClient {
7
+ private readonly url;
8
+ private readonly username;
9
+ private readonly password;
10
+ private readonly listeners;
11
+ private readonly logger?;
12
+ private readonly clientId?;
13
+ private socket;
14
+ private _connection;
15
+ private whenConnectedInternalSource;
16
+ private whenConnectedSource;
17
+ private loopResetSource;
18
+ private subscribeResetSource;
19
+ private readonly subscriptions;
20
+ constructor(url: string, username: string, password: string, listeners: OpenFeedListeners, logger?: IOpenFeedLogger | undefined, clientId?: string | undefined);
21
+ private onOpen;
22
+ private onMessage;
23
+ private onError;
24
+ private onClose;
25
+ private runConnectLoop;
26
+ private cleanUp;
27
+ private runSubscribeLoop;
28
+ subscribe: (service: Service, subscriptionType: SubscriptionType, snapshotIntervalSeconds: number, symbols?: string[] | null, marketIds?: Long[] | null, exchanges?: string[] | null, channels?: number[] | null) => Long;
29
+ unsubscribe: (subscriptionId: Long) => void;
30
+ get connection(): Promise<IOpenFeedConnection>;
31
+ dispose: () => void;
32
+ }
@@ -1,32 +1,34 @@
1
+ import WebSocket from "isomorphic-ws";
1
2
  import Long from "long";
2
- import type { SubscriptionType } from "@gen/openfeed_api";
3
+ import type { ExchangeResponse_Exchange, InstrumentReferenceResponse, SubscriptionType } from "@gen/openfeed_api";
4
+ import type { InstrumentDefinition } from "@gen/openfeed_instrument";
3
5
  import type { Service } from "@gen/openfeed";
4
- import { IOpenFeedClient, IOpenFeedConnection, IOpenFeedLogger } from "./connection_interfaces";
6
+ import { IOpenFeedConnection, IOpenFeedLogger, OpenFeedInstrumentReferenceRequest, OpenFeedInstrumentRequest } from "./interfaces";
5
7
  import { OpenFeedListeners } from "./listeners";
6
- export declare class OpenFeedClient implements IOpenFeedClient {
7
- private readonly url;
8
- private readonly username;
9
- private readonly password;
8
+ export declare class OpenFeedConnection implements IOpenFeedConnection {
9
+ private readonly connectionToken;
10
+ private readonly socket;
10
11
  private readonly listeners;
11
12
  private readonly logger?;
12
- private readonly clientId?;
13
- private socket;
14
- private _connection;
15
- private whenConnectedInternalSource;
16
- private whenConnectedSource;
17
- private loopResetSource;
18
- private subscribeResetSource;
19
- private readonly subscriptions;
20
- constructor(url: string, username: string, password: string, listeners: OpenFeedListeners, logger?: IOpenFeedLogger | undefined, clientId?: string | undefined);
21
- private onOpen;
13
+ private readonly subscriptionRequests;
14
+ private readonly exchangeRequests;
15
+ private readonly instrumentRequests;
16
+ private readonly definitionsInFlight;
17
+ private readonly instrumentReferenceRequests;
18
+ private readonly whenDisconnectedSource;
19
+ constructor(connectionToken: string, socket: WebSocket, listeners: OpenFeedListeners, logger?: IOpenFeedLogger | undefined);
20
+ private messageTriggered;
21
+ private runConnectionWatchLoop;
22
22
  private onMessage;
23
+ private disconnect;
23
24
  private onError;
24
25
  private onClose;
25
- private runConnectLoop;
26
- private cleanUp;
27
- private runSubscribeLoop;
28
26
  subscribe: (service: Service, subscriptionType: SubscriptionType, snapshotIntervalSeconds: number, symbols?: string[] | null, marketIds?: Long[] | null, exchanges?: string[] | null, channels?: number[] | null) => Long;
29
27
  unsubscribe: (subscriptionId: Long) => void;
30
- get connection(): Promise<IOpenFeedConnection>;
28
+ private fireUnsubscribeWhenReady;
29
+ getExchanges: () => Promise<ExchangeResponse_Exchange[]>;
30
+ getInstrument: (request: OpenFeedInstrumentRequest) => Promise<InstrumentDefinition[]>;
31
+ getInstrumentReference: (request: OpenFeedInstrumentReferenceRequest) => Promise<InstrumentReferenceResponse>;
32
+ whenDisconnected: () => Promise<void>;
31
33
  dispose: () => void;
32
34
  }
@@ -0,0 +1,9 @@
1
+ export declare class DuplicateLoginError extends Error {
2
+ get name(): string;
3
+ }
4
+ export declare class InvalidCredentialsError extends Error {
5
+ get name(): string;
6
+ }
7
+ export declare class ConnectionDisposedError extends Error {
8
+ get name(): string;
9
+ }
@@ -1,9 +1,8 @@
1
1
  import { OpenfeedGatewayMessage } from "@gen/openfeed_api";
2
2
  import { InstrumentDefinition } from "@gen/openfeed_instrument";
3
3
  import { HeartBeat } from "@gen/openfeed";
4
- import { IOpenFeedConnection } from "./connection_interfaces";
4
+ import { IOpenFeedConnection } from "./interfaces";
5
5
  export declare class OpenFeedListeners {
6
- private readonly instrumentBySymbol;
7
6
  private readonly instrumentByMarketId;
8
7
  constructor();
9
8
  private addDetails;
@@ -1,4 +1,4 @@
1
- export * from "./connection/connection";
2
- export * from "./connection/connection_interfaces";
1
+ export * from "./connection/client";
2
+ export * from "./connection/interfaces";
3
3
  export * from "./connection/listeners";
4
4
  export * from "../generated/index";
@@ -0,0 +1 @@
1
+ export declare const getClientVersion: (clientId?: string) => Promise<string>;
@@ -0,0 +1,5 @@
1
+ import WebSocket from "isomorphic-ws";
2
+ import { OptionalUndefined } from "@src/utilities/messages";
3
+ import type { OpenfeedGatewayMessage, OpenfeedGatewayRequest } from "@gen/openfeed_api";
4
+ export declare const send: (socket: WebSocket, message: OptionalUndefined<OpenfeedGatewayRequest>) => void;
5
+ export declare const receive: (msgEvent: WebSocket.MessageEvent) => OpenfeedGatewayMessage[];
@@ -0,0 +1,5 @@
1
+ export declare const TIME: {
2
+ RECONNECT: number;
3
+ CONNECTION_TIMEOUT: number;
4
+ SUBSCRIPTION_RETRY: number;
5
+ };
@@ -0,0 +1,5 @@
1
+ import Long from "long";
2
+ export declare class CorrelationId {
3
+ private static correlationId;
4
+ static create: () => Long;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfeed/sdk-js",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "JavaScript SDK for Barchart OpenFeed",
5
5
  "main": "dist/node.js",
6
6
  "browser": "dist/index.js",
@@ -11,10 +11,11 @@
11
11
  "license": "MIT",
12
12
  "scripts": {
13
13
  "prepare": "husky install",
14
+ "upgrade:proto": "yarn upgrade proto",
14
15
  "generate:proto": "cd node_modules\\proto && ..\\..\\protoc --plugin=..\\..\\node_modules\\.bin\\protoc-gen-ts_proto --ts_proto_opt=outputJsonMethods=false --ts_proto_opt=outputPartialMethods=false --ts_proto_opt=exportCommonSymbols=false --ts_proto_opt=esModuleInterop=true --ts_proto_opt=forceLong=long --ts_proto_opt=useExactTypes=false --ts_proto_out=../../generated *.proto",
15
16
  "generate:version": "genversion --es6 -s -d ./generated/version.ts",
16
17
  "generate:process": "tsx ./scripts/process.ts",
17
- "generate": "yarn generate:proto && yarn generate:process",
18
+ "generate": "yarn upgrade:proto && yarn generate:proto && yarn generate:process",
18
19
  "build:clear": "rimraf dist",
19
20
  "build:node": "esbuild --bundle --outfile=dist/node.js --platform=node --target=node16 --format=esm --banner:js=\"import { createRequire } from 'module';const require = createRequire(import.meta.url);\" src/index.ts",
20
21
  "build:ts": "yarn generate:version && vite build",
@@ -22,7 +23,8 @@
22
23
  "build:test-release": "standard-version --dry-run",
23
24
  "build:prepare-release": "standard-version -t ''",
24
25
  "run:browser": "yarn generate:version && vite dev",
25
- "run:node": "yarn generate:version && tsx ./src/test.ts"
26
+ "run:node": "yarn generate:version && tsx ./src/test.ts",
27
+ "test": "jest"
26
28
  },
27
29
  "devDependencies": {
28
30
  "@commitlint/cli": "^18.4.3",
@@ -49,7 +51,8 @@
49
51
  "ts-proto": "^1.156.7",
50
52
  "tsx": "^3.12.7",
51
53
  "typescript": "^5.2.2",
52
- "vite": "^4.4.9"
54
+ "vite": "^4.4.9",
55
+ "vite-plugin-checker": "^0.8.0"
53
56
  },
54
57
  "packageManager": "yarn@1.22.19",
55
58
  "dependencies": {
package/tsconfig.json CHANGED
@@ -20,5 +20,5 @@
20
20
  "@gen/*": ["./generated/*"]
21
21
  }
22
22
  },
23
- "include": ["./src", "./generated", "./scripts/"]
23
+ "include": ["./src", "./generated", "./scripts/", "./test/"]
24
24
  }