@agg-build/sdk 4.25.0 → 4.26.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.
@@ -488,6 +488,10 @@ var RECONNECT_BASE_MS = 1e3;
488
488
  var RECONNECT_MAX_MS = 3e4;
489
489
  var STALE_TIMEOUT_MS = 45e3;
490
490
  var ORDERBOOK_DEBUG_TOP_LEVELS = 5;
491
+ var WATCHDOG_SILENCE_MS = 5 * 6e4;
492
+ var WATCHDOG_CHECK_INTERVAL_MS = 6e4;
493
+ var WATCHDOG_BATCH_MAX = 100;
494
+ var WATCHDOG_REQUEST_TIMEOUT_MS = 15e3;
491
495
  var AggWebSocket = class {
492
496
  constructor(options) {
493
497
  this.ws = null;
@@ -513,6 +517,18 @@ var AggWebSocket = class {
513
517
  this.books = /* @__PURE__ */ new Map();
514
518
  /** Markets currently resyncing (awaiting snapshot after seq gap / checksum mismatch). */
515
519
  this.resyncing = /* @__PURE__ */ new Set();
520
+ /**
521
+ * Per subscribed orderbook outcome: last time it made progress (subscribe,
522
+ * snapshot, applied delta) or was checked against REST (ms). Tracked from
523
+ * subscribe, not from the first snapshot, so a book that never arrives or
524
+ * is stuck resyncing is still found by the watchdog.
525
+ */
526
+ this.bookCheckedAt = /* @__PURE__ */ new Map();
527
+ /** Outcomes the watchdog already resnapshotted; a repeat mismatch resubscribes. */
528
+ this.watchdogResnapshotted = /* @__PURE__ */ new Set();
529
+ this.watchdogTimer = null;
530
+ this.watchdogAbort = null;
531
+ this.watchdogPausedUntil = 0;
516
532
  // ── Connection state ──
517
533
  this.connecting = false;
518
534
  this._connected = false;
@@ -528,6 +544,7 @@ var AggWebSocket = class {
528
544
  this.callbacks = (_b = options.callbacks) != null ? _b : {};
529
545
  this.initialToken = options.initialToken;
530
546
  this.enableLogs = (_c = options.enableLogs) != null ? _c : false;
547
+ this.bookWatchdog = options.bookWatchdog;
531
548
  }
532
549
  // ── Public API ──
533
550
  get connected() {
@@ -673,6 +690,7 @@ var AggWebSocket = class {
673
690
  this.subs.set(outcomeId, sub);
674
691
  }
675
692
  if (sub.refCount === 0) {
693
+ this.bookCheckedAt.set(outcomeId, Date.now());
676
694
  this.pendingSubs.add(outcomeId);
677
695
  this.ensureConnected();
678
696
  if (this._connected) {
@@ -697,6 +715,8 @@ var AggWebSocket = class {
697
715
  this.books.delete(outcomeId);
698
716
  }
699
717
  this.resyncing.delete(outcomeId);
718
+ this.bookCheckedAt.delete(outcomeId);
719
+ this.watchdogResnapshotted.delete(outcomeId);
700
720
  if (this._connected) {
701
721
  if (this.pendingFlushSubs.has(outcomeId)) {
702
722
  this.pendingFlushSubs.delete(outcomeId);
@@ -764,6 +784,7 @@ var AggWebSocket = class {
764
784
  }
765
785
  /** Send a JWT to authenticate (or re-authenticate) the connection. */
766
786
  authenticate(token) {
787
+ this.initialToken = token;
767
788
  if (this._connected) {
768
789
  this.send({ action: "authenticate", token });
769
790
  }
@@ -795,6 +816,7 @@ var AggWebSocket = class {
795
816
  }
796
817
  /** Permanently destroy. Cannot reconnect after this. */
797
818
  destroy() {
819
+ var _a;
798
820
  this.destroyed = true;
799
821
  for (const controller of this.historyControllers)
800
822
  controller.abort(new Error("Market-data client disposed"));
@@ -805,6 +827,12 @@ var AggWebSocket = class {
805
827
  this.subs.clear();
806
828
  this.books.clear();
807
829
  this.resyncing.clear();
830
+ if (this.watchdogTimer) clearInterval(this.watchdogTimer);
831
+ this.watchdogTimer = null;
832
+ (_a = this.watchdogAbort) == null ? void 0 : _a.abort();
833
+ this.watchdogAbort = null;
834
+ this.bookCheckedAt.clear();
835
+ this.watchdogResnapshotted.clear();
808
836
  this.pendingSubs.clear();
809
837
  this.pendingTradeSubs.clear();
810
838
  this.pendingResnapshots.clear();
@@ -988,11 +1016,21 @@ var AggWebSocket = class {
988
1016
  * ready to accept subscriptions. Now flush all pending subs.
989
1017
  */
990
1018
  handleConnected(msg) {
991
- var _a, _b, _c, _d;
1019
+ var _a, _b, _c, _d, _e;
992
1020
  this._connected = true;
993
- (_b = (_a = this.callbacks).onConnectionStateChange) == null ? void 0 : _b.call(_a, true);
994
- (_d = (_c = this.callbacks).onConnected) == null ? void 0 : _d.call(_c, msg);
1021
+ if (this.bookWatchdog && !this.watchdogTimer && !this.destroyed) {
1022
+ this.watchdogTimer = setInterval(
1023
+ () => void this.checkSilentBooks(),
1024
+ (_a = this.bookWatchdog.checkIntervalMs) != null ? _a : WATCHDOG_CHECK_INTERVAL_MS
1025
+ );
1026
+ }
1027
+ (_c = (_b = this.callbacks).onConnectionStateChange) == null ? void 0 : _c.call(_b, true);
1028
+ (_e = (_d = this.callbacks).onConnected) == null ? void 0 : _e.call(_d, msg);
995
1029
  const orderbookSet = new Set(this.activeOrderbookMarkets());
1030
+ const reconnectedAt = Date.now();
1031
+ for (const id of orderbookSet) {
1032
+ if (this.bookCheckedAt.has(id)) this.bookCheckedAt.set(id, reconnectedAt);
1033
+ }
996
1034
  this.pendingSubs.forEach((id) => orderbookSet.add(id));
997
1035
  this.pendingFlushSubs.forEach((id) => orderbookSet.add(id));
998
1036
  const orderbookIds = Array.from(orderbookSet);
@@ -1035,6 +1073,7 @@ var AggWebSocket = class {
1035
1073
  const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1036
1074
  const book = snapshotToOrderbook(msg);
1037
1075
  this.books.set(msg.outcomeId, new IndexedOrderbook(book));
1076
+ this.bookCheckedAt.set(msg.outcomeId, Date.now());
1038
1077
  this.resyncing.delete(msg.outcomeId);
1039
1078
  this.pendingResnapshots.delete(msg.outcomeId);
1040
1079
  this.debugOrderbook("snapshot_applied", {
@@ -1104,6 +1143,8 @@ var AggWebSocket = class {
1104
1143
  this.requestResnapshot(outcomeId);
1105
1144
  return;
1106
1145
  }
1146
+ this.bookCheckedAt.set(outcomeId, Date.now());
1147
+ this.watchdogResnapshotted.delete(outcomeId);
1107
1148
  (_b = (_a = this.callbacks).onBookTick) == null ? void 0 : _b.call(_a, outcomeId, {
1108
1149
  midpoint: msg.midpoint,
1109
1150
  timestamp: msg.timestamp,
@@ -1126,6 +1167,8 @@ var AggWebSocket = class {
1126
1167
  var _a, _b;
1127
1168
  const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1128
1169
  this.books.delete(msg.outcomeId);
1170
+ this.bookCheckedAt.delete(msg.outcomeId);
1171
+ this.watchdogResnapshotted.delete(msg.outcomeId);
1129
1172
  this.pendingResnapshots.delete(msg.outcomeId);
1130
1173
  this.resyncing.delete(msg.outcomeId);
1131
1174
  (_b = (_a = this.callbacks).onMarketResolved) == null ? void 0 : _b.call(_a, msg);
@@ -1145,6 +1188,89 @@ var AggWebSocket = class {
1145
1188
  const msg = __spreadProps(__spreadValues({}, raw), { timestamp: raw.timestamp / 1e3 });
1146
1189
  (_b = (_a = this.callbacks).onRedeemEvent) == null ? void 0 : _b.call(_a, msg);
1147
1190
  }
1191
+ // ── Book watchdog ──
1192
+ /**
1193
+ * Heartbeats prove the socket, not each stream. Every subscribed outcome
1194
+ * with no progress for `silenceMs` is checked against the REST `seq`:
1195
+ *
1196
+ * - book present, not resyncing, same `seq` → the market is just quiet;
1197
+ * - different `seq`, no book (snapshot never arrived, or a resubscribe's
1198
+ * snapshot was lost), or stuck resyncing (any recovery whose snapshot
1199
+ * was lost) → resnapshot, then resubscribe on the next overdue check;
1200
+ * - REST can't serve it (`null`) → counted as an attempt, so the next
1201
+ * batch moves on to other outcomes instead of retrying the same 100.
1202
+ */
1203
+ checkSilentBooks() {
1204
+ return __async(this, null, function* () {
1205
+ var _a;
1206
+ const opts = this.bookWatchdog;
1207
+ if (!opts || !this._connected || this.watchdogAbort) return;
1208
+ const now = Date.now();
1209
+ if (now < this.watchdogPausedUntil) return;
1210
+ const silenceMs = (_a = opts.silenceMs) != null ? _a : WATCHDOG_SILENCE_MS;
1211
+ const silent = this.activeOrderbookMarkets().filter((id) => this.bookCheckedAt.has(id)).filter((id) => now - this.bookCheckedAt.get(id) >= silenceMs).sort((a, b) => this.bookCheckedAt.get(a) - this.bookCheckedAt.get(b)).slice(0, WATCHDOG_BATCH_MAX);
1212
+ if (silent.length === 0) return;
1213
+ const checkedAt = new Map(silent.map((id) => [id, this.bookCheckedAt.get(id)]));
1214
+ const abort = new AbortController();
1215
+ this.watchdogAbort = abort;
1216
+ const timeout = setTimeout(() => abort.abort(), WATCHDOG_REQUEST_TIMEOUT_MS);
1217
+ try {
1218
+ const url = new URL(`${opts.baseUrl.replace(/\/$/, "")}/orderbooks/outcomes`);
1219
+ url.searchParams.set("venueMarketOutcomeIds", silent.join(","));
1220
+ url.searchParams.set("depth", "1");
1221
+ const response = yield fetch(url, { headers: opts.headers, signal: abort.signal });
1222
+ if (!response.ok) {
1223
+ const retryAfterS = Number(response.headers.get("retry-after"));
1224
+ if (response.status === 429 && retryAfterS > 0) {
1225
+ this.watchdogPausedUntil = Date.now() + retryAfterS * 1e3;
1226
+ }
1227
+ this.emitDiagnostic({ type: "book_watchdog_error", status: response.status });
1228
+ return;
1229
+ }
1230
+ const body = yield response.json();
1231
+ if (this.destroyed) return;
1232
+ silent.forEach((outcomeId, i) => {
1233
+ var _a2, _b;
1234
+ if (this.bookCheckedAt.get(outcomeId) !== checkedAt.get(outcomeId)) return;
1235
+ const server = (_a2 = body.data) == null ? void 0 : _a2[i];
1236
+ if (!server) {
1237
+ this.bookCheckedAt.set(outcomeId, Date.now());
1238
+ return;
1239
+ }
1240
+ const book = this.books.get(outcomeId);
1241
+ if (book && !this.resyncing.has(outcomeId) && server.seq === book.seq) {
1242
+ this.bookCheckedAt.set(outcomeId, Date.now());
1243
+ this.watchdogResnapshotted.delete(outcomeId);
1244
+ return;
1245
+ }
1246
+ const action = this.watchdogResnapshotted.has(outcomeId) ? "resubscribe" : "resnapshot";
1247
+ this.emitDiagnostic({
1248
+ type: "book_watchdog_resync",
1249
+ outcomeId,
1250
+ localSeq: (_b = book == null ? void 0 : book.seq) != null ? _b : null,
1251
+ serverSeq: server.seq,
1252
+ action
1253
+ });
1254
+ this.bookCheckedAt.set(outcomeId, Date.now());
1255
+ if (action === "resubscribe") {
1256
+ this.watchdogResnapshotted.delete(outcomeId);
1257
+ this.books.delete(outcomeId);
1258
+ this.resyncing.add(outcomeId);
1259
+ this.send({ action: "unsubscribe", channel: "orderbook", outcomeIds: [outcomeId] });
1260
+ this.sendSubscribe([outcomeId]);
1261
+ } else {
1262
+ this.watchdogResnapshotted.add(outcomeId);
1263
+ this.requestResnapshot(outcomeId, true);
1264
+ }
1265
+ });
1266
+ } catch (e) {
1267
+ if (!this.destroyed) this.emitDiagnostic({ type: "book_watchdog_error", status: null });
1268
+ } finally {
1269
+ clearTimeout(timeout);
1270
+ if (this.watchdogAbort === abort) this.watchdogAbort = null;
1271
+ }
1272
+ });
1273
+ }
1148
1274
  // ── Resnapshot ──
1149
1275
  requestResnapshot(outcomeId, force = false) {
1150
1276
  if (this.resyncing.has(outcomeId) && !force) return;
@@ -6,7 +6,7 @@ import {
6
6
  decodeChartHistory,
7
7
  loadImmutableHistory,
8
8
  readChartHistoryText
9
- } from "./chunk-QXBVA5TW.mjs";
9
+ } from "./chunk-CPMSVNBO.mjs";
10
10
  import {
11
11
  __async,
12
12
  __spreadProps,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agg-build/sdk",
3
- "version": "4.25.0",
3
+ "version": "4.26.0",
4
4
  "description": "Vanilla TypeScript client for the AGG prediction market aggregator (auth, markets, orderbooks, charts, trading, managed execution, WebSockets). Works in browsers, Node.js, and React Native.",
5
5
  "sideEffects": false,
6
6
  "license": "MIT",