@agg-build/sdk 4.36.0 → 4.37.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.
@@ -472,9 +472,12 @@ var IndexedOrderbook = class {
472
472
  /**
473
473
  * Agg platform WebSocket client.
474
474
  *
475
- * Single connection, refcounted subscriptions. Orderbook/trades subscriptions
476
- * are keyed by venueMarketOutcomeId (the `outcomes[].id` on REST market
477
- * responses — NOT a venueMarketId); arb subscriptions are keyed by marketId.
475
+ * Refcounted subscriptions. Orderbook/trades subscriptions are keyed by
476
+ * venueMarketOutcomeId (the `outcomes[].id` on REST market responses — NOT a
477
+ * venueMarketId); arb subscriptions are keyed by marketId.
478
+ * One socket unless `orderbookUrl` is set, in which case only the orderbook
479
+ * channel uses that second socket. An orderbook-only subscription then leaves
480
+ * the primary socket idle. Trades, arb, catalog, and user events still use it.
478
481
  * Maintains in-memory orderbook state per subscribed outcome id.
479
482
  * All timestamps converted from server ms to seconds before invoking callbacks.
480
483
  *
@@ -515,7 +518,8 @@ const WATCHDOG_BATCH_MAX = 100;
515
518
  const WATCHDOG_REQUEST_TIMEOUT_MS = 15e3;
516
519
  const CATALOG_FRAME_ID_PREFIX = "catalog-";
517
520
  /**
518
- * Agg WebSocket client. Single WS connection with refcounted market subscriptions.
521
+ * Agg WebSocket client. Refcounted market subscriptions on one socket,
522
+ * or a second socket for orderbooks when `orderbookUrl` is set.
519
523
  */
520
524
  var AggWebSocket = class {
521
525
  constructor(options) {
@@ -552,6 +556,14 @@ var AggWebSocket = class {
552
556
  this.lastMessageTime = 0;
553
557
  this.destroyed = false;
554
558
  this.historyControllers = /* @__PURE__ */ new Set();
559
+ this.obWs = null;
560
+ this.obConnecting = false;
561
+ this._obConnected = false;
562
+ this.obReconnectAttempts = 0;
563
+ this.obReconnectTimer = null;
564
+ this.obStaleTimer = null;
565
+ this.obLastMessageTime = 0;
566
+ this.obRejected = false;
555
567
  this.lastEventId = {};
556
568
  this.seenEventIds = /* @__PURE__ */ new Set();
557
569
  this.hasConnected = false;
@@ -563,6 +575,8 @@ var AggWebSocket = class {
563
575
  this.rejected = false;
564
576
  this.deferBookViews = options.deferBookViews ?? false;
565
577
  this.url = options.url;
578
+ this.orderbookUrl = options.orderbookUrl || void 0;
579
+ this.orderbookLatency = options.orderbookLatency === "realtime" ? "realtime" : "conflated";
566
580
  this.webSocketImpl = options.webSocketImpl;
567
581
  this.headers = options.headers;
568
582
  this.resumeEvents = options.resumeEvents ?? true;
@@ -669,6 +683,7 @@ var AggWebSocket = class {
669
683
  this.scheduleFlush();
670
684
  }
671
685
  }
686
+ this.releasePrimaryIfIdle();
672
687
  }
673
688
  };
674
689
  }
@@ -693,6 +708,7 @@ var AggWebSocket = class {
693
708
  action: "unsubscribe",
694
709
  channel: "arb-feed"
695
710
  });
711
+ this.releasePrimaryIfIdle();
696
712
  };
697
713
  }
698
714
  /**
@@ -726,14 +742,17 @@ var AggWebSocket = class {
726
742
  }
727
743
  }
728
744
  return () => {
729
- if (!this.catalogSubs.delete(sub) || !this._connected) return;
730
- if (this.catalogSubs.size === 0) {
731
- this.catalogSentKey = null;
732
- this.send({
733
- action: "unsubscribe",
734
- channel: "catalog"
735
- });
736
- } else this.sendCatalogSubscribe();
745
+ if (!this.catalogSubs.delete(sub)) return;
746
+ if (this._connected) {
747
+ if (this.catalogSubs.size === 0) {
748
+ this.catalogSentKey = null;
749
+ this.send({
750
+ action: "unsubscribe",
751
+ channel: "catalog"
752
+ });
753
+ } else this.sendCatalogSubscribe();
754
+ }
755
+ this.releasePrimaryIfIdle();
737
756
  };
738
757
  }
739
758
  /**
@@ -786,8 +805,8 @@ var AggWebSocket = class {
786
805
  if (sub.refCount === 0) {
787
806
  this.bookCheckedAt.set(outcomeId, Date.now());
788
807
  this.pendingSubs.add(outcomeId);
789
- this.ensureConnected();
790
- if (this._connected) {
808
+ this.ensureOrderbookTransport();
809
+ if (this.orderbookTransportReady()) {
791
810
  this.pendingFlushUnsubs.delete(outcomeId);
792
811
  this.pendingFlushSubs.add(outcomeId);
793
812
  this.pendingSubs.delete(outcomeId);
@@ -796,6 +815,7 @@ var AggWebSocket = class {
796
815
  }
797
816
  sub.refCount++;
798
817
  let released = false;
818
+ this.releasePrimaryIfIdle();
799
819
  return () => {
800
820
  if (released) return;
801
821
  released = true;
@@ -812,7 +832,7 @@ var AggWebSocket = class {
812
832
  this.bookCheckedAt.delete(outcomeId);
813
833
  this.snapshotRetries.delete(outcomeId);
814
834
  this.watchdogResnapshotted.delete(outcomeId);
815
- if (this._connected) {
835
+ if (this.orderbookTransportReady()) {
816
836
  if (this.pendingFlushSubs.has(outcomeId)) this.pendingFlushSubs.delete(outcomeId);
817
837
  else {
818
838
  this.pendingFlushUnsubs.add(outcomeId);
@@ -821,6 +841,7 @@ var AggWebSocket = class {
821
841
  }
822
842
  this.pendingSubs.delete(outcomeId);
823
843
  this.pendingResnapshots.delete(outcomeId);
844
+ this.releasePrimaryIfIdle();
824
845
  }
825
846
  };
826
847
  }
@@ -875,6 +896,7 @@ var AggWebSocket = class {
875
896
  this.books.delete(outcomeId);
876
897
  this.resyncing.delete(outcomeId);
877
898
  }
899
+ this.releasePrimaryIfIdle();
878
900
  }
879
901
  };
880
902
  }
@@ -904,6 +926,7 @@ var AggWebSocket = class {
904
926
  action: "unsubscribe",
905
927
  channel: "app-events"
906
928
  });
929
+ this.releasePrimaryIfIdle();
907
930
  };
908
931
  }
909
932
  sendAppEventsSubscribe() {
@@ -931,21 +954,34 @@ var AggWebSocket = class {
931
954
  /** Connect (idempotent). Called automatically on first subscribe. */
932
955
  connect() {
933
956
  this.rejected = false;
934
- this.ensureConnected();
957
+ this.obRejected = false;
958
+ if (!this.orderbookUrl || this.needsPrimarySocket()) this.ensureConnected();
959
+ if (this.orderbookUrl && (this.activeOrderbookMarkets().length > 0 || this.pendingSubs.size > 0 || this.pendingResnapshots.size > 0)) this.ensureOrderbookConnected();
935
960
  }
936
961
  /** Disconnect and clean up. Reusable — call connect() to reconnect. */
937
962
  disconnect() {
938
963
  this.cancelReconnect();
964
+ this.cancelOrderbookReconnect();
939
965
  this.cancelStaleTimer();
966
+ this.cancelOrderbookStaleTimer();
940
967
  this.clearSnapshotRetries();
968
+ const orderbookWasConnected = !!this.orderbookUrl && this._obConnected;
941
969
  this._connected = false;
970
+ this._obConnected = false;
942
971
  this.connecting = false;
972
+ this.obConnecting = false;
943
973
  if (this.ws) {
944
974
  this.ws.onclose = null;
945
975
  this.ws.close();
946
976
  this.ws = null;
947
977
  }
978
+ if (this.obWs) {
979
+ this.obWs.onclose = null;
980
+ this.obWs.close();
981
+ this.obWs = null;
982
+ }
948
983
  this.callbacks.onConnectionStateChange?.(false);
984
+ if (orderbookWasConnected) this.callbacks.onOrderbookConnectionStateChange?.(false);
949
985
  }
950
986
  /** Permanently destroy. Cannot reconnect after this. */
951
987
  destroy() {
@@ -992,6 +1028,50 @@ var AggWebSocket = class {
992
1028
  if (this.ws?.readyState === WS_OPEN || this.connecting) return;
993
1029
  this.doConnect();
994
1030
  }
1031
+ /** Trades, arb, catalog, app events, and user sessions. Orderbooks count only on the shared socket. */
1032
+ needsPrimarySocket() {
1033
+ if (this.initialToken) return true;
1034
+ if (this.arbSubs.size > 0 || this.arbFeedCbs.size > 0 || this.appEventCbs.size > 0 || this.catalogSubs.size > 0) return true;
1035
+ for (const sub of this.subs.values()) {
1036
+ if (sub.tradeRefs > 0) return true;
1037
+ if (!this.orderbookUrl && sub.refCount > 0) return true;
1038
+ }
1039
+ return false;
1040
+ }
1041
+ /** Drop a primary socket that only an orderbook subscription was holding open. */
1042
+ releasePrimaryIfIdle() {
1043
+ if (!this.orderbookUrl || this.needsPrimarySocket()) return;
1044
+ this.cancelReconnect();
1045
+ const wasConnected = this._connected;
1046
+ this.connecting = false;
1047
+ this._connected = false;
1048
+ this.cancelStaleTimer();
1049
+ const ws = this.ws;
1050
+ this.ws = null;
1051
+ if (ws) {
1052
+ ws.onclose = null;
1053
+ ws.close();
1054
+ }
1055
+ if (wasConnected) this.callbacks.onConnectionStateChange?.(false);
1056
+ }
1057
+ /** True when orderbook subscriptions use the separate `orderbookUrl` socket. */
1058
+ get usesOrderbookSocket() {
1059
+ return !!this.orderbookUrl;
1060
+ }
1061
+ /** Orderbook readiness. Without `orderbookUrl` this is the primary socket. */
1062
+ orderbookTransportReady() {
1063
+ return this.orderbookUrl ? this._obConnected : this._connected;
1064
+ }
1065
+ ensureOrderbookTransport() {
1066
+ if (this.orderbookUrl) this.ensureOrderbookConnected();
1067
+ else this.ensureConnected();
1068
+ }
1069
+ ensureOrderbookConnected() {
1070
+ if (!this.orderbookUrl) return;
1071
+ if (this.destroyed || this.obRejected) return;
1072
+ if (this.obWs?.readyState === WS_OPEN || this.obConnecting) return;
1073
+ this.doConnectOrderbook();
1074
+ }
995
1075
  doConnect() {
996
1076
  if (this.destroyed) return;
997
1077
  const Impl = this.webSocketImpl ?? globalThis.WebSocket;
@@ -1022,7 +1102,7 @@ var AggWebSocket = class {
1022
1102
  const wasConnected = this._connected;
1023
1103
  this._connected = false;
1024
1104
  this.cancelStaleTimer();
1025
- this.clearSnapshotRetries();
1105
+ if (!this.orderbookUrl) this.clearSnapshotRetries();
1026
1106
  if (wasConnected) this.callbacks.onConnectionStateChange?.(false);
1027
1107
  const code = event?.code ?? 0;
1028
1108
  if (NON_RETRYABLE_CLOSE_CODES.has(code)) {
@@ -1034,7 +1114,7 @@ var AggWebSocket = class {
1034
1114
  });
1035
1115
  return;
1036
1116
  }
1037
- if (!this.destroyed && (this.subs.size > 0 || this.arbSubs.size > 0 || this.arbFeedCbs.size > 0 || this.appEventCbs.size > 0 || this.catalogSubs.size > 0 || !!this.initialToken)) this.scheduleReconnect();
1117
+ if (!this.destroyed && this.needsPrimarySocket()) this.scheduleReconnect();
1038
1118
  };
1039
1119
  ws.onerror = () => {};
1040
1120
  }
@@ -1076,6 +1156,89 @@ var AggWebSocket = class {
1076
1156
  this.staleTimer = null;
1077
1157
  }
1078
1158
  }
1159
+ doConnectOrderbook() {
1160
+ if (this.destroyed || !this.orderbookUrl) return;
1161
+ const Impl = this.webSocketImpl ?? globalThis.WebSocket;
1162
+ if (!Impl) throw new Error("No WebSocket implementation available. Pass `webSocketImpl` (e.g. the `ws` package).");
1163
+ this.obConnecting = true;
1164
+ const ws = this.headers ? new Impl(this.orderbookUrl, { headers: this.headers }) : new Impl(this.orderbookUrl);
1165
+ this.obWs = ws;
1166
+ ws.onopen = () => {
1167
+ if (this.destroyed || this.obWs !== ws) return;
1168
+ this.obConnecting = false;
1169
+ this.resetOrderbookStaleTimer();
1170
+ };
1171
+ ws.onmessage = (event) => {
1172
+ if (this.destroyed || this.obWs !== ws) return;
1173
+ this.obLastMessageTime = Date.now();
1174
+ this.callbacks.onFrameReceived?.(this.obLastMessageTime);
1175
+ this.resetOrderbookStaleTimer();
1176
+ try {
1177
+ const raw = JSON.parse(event.data);
1178
+ this.handleOrderbookSocketMessage(raw);
1179
+ } catch {}
1180
+ };
1181
+ ws.onclose = (event) => {
1182
+ if (this.obWs !== ws) return;
1183
+ this.obWs = null;
1184
+ this.obConnecting = false;
1185
+ const wasConnected = this._obConnected;
1186
+ this._obConnected = false;
1187
+ this.cancelOrderbookStaleTimer();
1188
+ this.clearSnapshotRetries();
1189
+ if (wasConnected) this.callbacks.onOrderbookConnectionStateChange?.(false);
1190
+ const code = event?.code ?? 0;
1191
+ if (NON_RETRYABLE_CLOSE_CODES.has(code)) {
1192
+ this.obRejected = true;
1193
+ this.emitDiagnostic({
1194
+ type: "connection_rejected",
1195
+ code,
1196
+ reason: event?.reason ?? ""
1197
+ });
1198
+ return;
1199
+ }
1200
+ if (!this.destroyed && (this.activeOrderbookMarkets().length > 0 || this.pendingSubs.size > 0 || this.pendingResnapshots.size > 0)) this.scheduleOrderbookReconnect();
1201
+ };
1202
+ ws.onerror = () => {};
1203
+ }
1204
+ scheduleOrderbookReconnect() {
1205
+ this.cancelOrderbookReconnect();
1206
+ const step = Math.min(RECONNECT_BASE_MS * Math.pow(2, this.obReconnectAttempts), RECONNECT_MAX_MS);
1207
+ const delay = Math.round(step * (.5 + Math.random() * .5));
1208
+ this.obReconnectAttempts++;
1209
+ this.emitDiagnostic({
1210
+ type: "reconnect",
1211
+ attempt: this.obReconnectAttempts,
1212
+ delayMs: delay
1213
+ });
1214
+ this.obReconnectTimer = setTimeout(() => {
1215
+ this.obReconnectTimer = null;
1216
+ this.doConnectOrderbook();
1217
+ }, delay);
1218
+ }
1219
+ cancelOrderbookReconnect() {
1220
+ if (this.obReconnectTimer) {
1221
+ clearTimeout(this.obReconnectTimer);
1222
+ this.obReconnectTimer = null;
1223
+ }
1224
+ }
1225
+ resetOrderbookStaleTimer() {
1226
+ this.cancelOrderbookStaleTimer();
1227
+ this.obStaleTimer = setTimeout(() => {
1228
+ const age = Date.now() - this.obLastMessageTime;
1229
+ this.emitDiagnostic({
1230
+ type: "stale_connection",
1231
+ lastMessageAge: age
1232
+ });
1233
+ if (this.obWs) this.obWs.close();
1234
+ }, STALE_TIMEOUT_MS);
1235
+ }
1236
+ cancelOrderbookStaleTimer() {
1237
+ if (this.obStaleTimer) {
1238
+ clearTimeout(this.obStaleTimer);
1239
+ this.obStaleTimer = null;
1240
+ }
1241
+ }
1079
1242
  handleMessage(raw) {
1080
1243
  const type = raw.type;
1081
1244
  if (USER_EVENT_TYPES.has(type) && !this.acceptEvent(raw)) return;
@@ -1211,16 +1374,7 @@ var AggWebSocket = class {
1211
1374
  if (this.bookWatchdog && !this.watchdogTimer && !this.destroyed) this.watchdogTimer = setInterval(() => void this.checkSilentBooks(), this.bookWatchdog.checkIntervalMs ?? WATCHDOG_CHECK_INTERVAL_MS);
1212
1375
  this.callbacks.onConnectionStateChange?.(true);
1213
1376
  this.callbacks.onConnected?.(msg);
1214
- const orderbookSet = new Set(this.activeOrderbookMarkets());
1215
- const reconnectedAt = Date.now();
1216
- for (const id of orderbookSet) if (this.bookCheckedAt.has(id)) this.bookCheckedAt.set(id, reconnectedAt);
1217
- this.pendingSubs.forEach((id) => orderbookSet.add(id));
1218
- this.pendingFlushSubs.forEach((id) => orderbookSet.add(id));
1219
- const orderbookIds = Array.from(orderbookSet);
1220
- if (orderbookIds.length > 0) this.sendSubscribe(orderbookIds);
1221
- this.pendingSubs.clear();
1222
- this.pendingFlushSubs.clear();
1223
- this.pendingFlushUnsubs.clear();
1377
+ if (!this.orderbookUrl) this.flushOrderbookSubscriptions();
1224
1378
  const tradeSet = new Set(this.activeTradeMarkets());
1225
1379
  this.pendingTradeSubs.forEach((id) => tradeSet.add(id));
1226
1380
  this.pendingFlushTradeSubs.forEach((id) => tradeSet.add(id));
@@ -1233,13 +1387,7 @@ var AggWebSocket = class {
1233
1387
  this.pendingTradeSubs.clear();
1234
1388
  this.pendingFlushTradeSubs.clear();
1235
1389
  this.pendingFlushTradeUnsubs.clear();
1236
- const resnapshotIds = Array.from(this.pendingResnapshots);
1237
- if (resnapshotIds.length > 0) this.send({
1238
- action: "resnapshot",
1239
- channel: "orderbook",
1240
- outcomeIds: resnapshotIds
1241
- });
1242
- this.pendingResnapshots.clear();
1390
+ if (!this.orderbookUrl) this.flushOrderbookResnapshots();
1243
1391
  const arbSet = new Set(this.activeArbMarkets());
1244
1392
  this.pendingFlushArbSubs.forEach((id) => arbSet.add(id));
1245
1393
  const arbIds = Array.from(arbSet);
@@ -1259,6 +1407,89 @@ var AggWebSocket = class {
1259
1407
  this.catalogAckedSeq = this.catalogFrameSeq;
1260
1408
  if (this.catalogSubs.size > 0) this.sendCatalogSubscribe(...this.catalogSubs);
1261
1409
  }
1410
+ /** Resubscribe orderbooks on the socket that owns that channel. */
1411
+ flushOrderbookSubscriptions() {
1412
+ const orderbookSet = new Set(this.activeOrderbookMarkets());
1413
+ const reconnectedAt = Date.now();
1414
+ for (const id of orderbookSet) if (this.bookCheckedAt.has(id)) this.bookCheckedAt.set(id, reconnectedAt);
1415
+ this.pendingSubs.forEach((id) => orderbookSet.add(id));
1416
+ this.pendingFlushSubs.forEach((id) => orderbookSet.add(id));
1417
+ const orderbookIds = Array.from(orderbookSet);
1418
+ if (orderbookIds.length > 0) this.sendSubscribe(orderbookIds);
1419
+ this.pendingSubs.clear();
1420
+ this.pendingFlushSubs.clear();
1421
+ this.pendingFlushUnsubs.clear();
1422
+ }
1423
+ flushOrderbookResnapshots() {
1424
+ const resnapshotIds = Array.from(this.pendingResnapshots);
1425
+ if (resnapshotIds.length > 0) this.sendOrderbook({
1426
+ action: "resnapshot",
1427
+ channel: "orderbook",
1428
+ outcomeIds: resnapshotIds
1429
+ });
1430
+ this.pendingResnapshots.clear();
1431
+ }
1432
+ /**
1433
+ * The orderbook socket confirmed the app. It does not carry trades, arb,
1434
+ * catalog, or user events, and it does not change the primary connection flag.
1435
+ */
1436
+ handleOrderbookConnected() {
1437
+ this.obReconnectAttempts = 0;
1438
+ this._obConnected = true;
1439
+ this.callbacks.onOrderbookConnectionStateChange?.(true);
1440
+ if (this.bookWatchdog && !this.watchdogTimer && !this.destroyed) this.watchdogTimer = setInterval(() => void this.checkSilentBooks(), this.bookWatchdog.checkIntervalMs ?? WATCHDOG_CHECK_INTERVAL_MS);
1441
+ this.flushOrderbookSubscriptions();
1442
+ this.flushOrderbookResnapshots();
1443
+ }
1444
+ handleOrderbookSocketMessage(raw) {
1445
+ const type = raw.type;
1446
+ if ((type === "orderbook_snapshot" || type === "orderbook_delta") && !validMarketFrame(raw)) {
1447
+ this.callbacks.onError?.({
1448
+ type: "error",
1449
+ message: "Invalid market-data frame"
1450
+ });
1451
+ if (typeof raw.outcomeId === "string" && this.subs.has(raw.outcomeId)) this.requestResnapshot(raw.outcomeId);
1452
+ return;
1453
+ }
1454
+ switch (type) {
1455
+ case "orderbook_snapshot":
1456
+ this.handleSnapshot(raw);
1457
+ break;
1458
+ case "orderbook_delta":
1459
+ this.handleDelta(raw);
1460
+ break;
1461
+ case "heartbeat":
1462
+ this.handleHeartbeat(raw);
1463
+ break;
1464
+ case "subscribed":
1465
+ this.callbacks.onSubscribed?.(raw);
1466
+ break;
1467
+ case "unsubscribed":
1468
+ this.callbacks.onUnsubscribed?.(raw);
1469
+ break;
1470
+ case "connected":
1471
+ this.handleOrderbookConnected();
1472
+ break;
1473
+ case "market_resolved":
1474
+ this.handleMarketResolved(raw);
1475
+ break;
1476
+ case "error": {
1477
+ const msg = raw;
1478
+ if (msg.code === "snapshot_unavailable") for (const outcomeId of msg.outcomeIds ?? []) this.queueSnapshotRetry(outcomeId);
1479
+ this.callbacks.onError?.(msg);
1480
+ break;
1481
+ }
1482
+ case "stream_interrupted": {
1483
+ const msg = raw;
1484
+ this.emitDiagnostic({
1485
+ type: "stream_interrupted",
1486
+ channels: msg.channels
1487
+ });
1488
+ this.callbacks.onStreamInterrupted?.(msg);
1489
+ break;
1490
+ }
1491
+ }
1492
+ }
1262
1493
  /**
1263
1494
  * Track `eventId` per stream and drop duplicates a resume re-delivers.
1264
1495
  * App-stream frames go to `subscribeAppEvents` listeners only. Returns
@@ -1438,7 +1669,7 @@ var AggWebSocket = class {
1438
1669
  */
1439
1670
  async checkSilentBooks() {
1440
1671
  const opts = this.bookWatchdog;
1441
- if (!opts || !this._connected || this.watchdogAbort) return;
1672
+ if (!opts || !this.orderbookTransportReady() || this.watchdogAbort) return;
1442
1673
  const now = Date.now();
1443
1674
  if (now < this.watchdogPausedUntil) return;
1444
1675
  const silenceMs = opts.silenceMs ?? WATCHDOG_SILENCE_MS;
@@ -1493,7 +1724,7 @@ var AggWebSocket = class {
1493
1724
  this.watchdogResnapshotted.delete(outcomeId);
1494
1725
  this.books.delete(outcomeId);
1495
1726
  this.resyncing.add(outcomeId);
1496
- this.send({
1727
+ this.sendOrderbook({
1497
1728
  action: "unsubscribe",
1498
1729
  channel: "orderbook",
1499
1730
  outcomeIds: [outcomeId]
@@ -1516,7 +1747,7 @@ var AggWebSocket = class {
1516
1747
  }
1517
1748
  /** Retry only explicitly failed active books; never reconnect healthy sockets. */
1518
1749
  queueSnapshotRetry(outcomeId) {
1519
- if (this.destroyed || !this._connected || !this.subs.get(outcomeId)?.refCount) return;
1750
+ if (this.destroyed || !this.orderbookTransportReady() || !this.subs.get(outcomeId)?.refCount) return;
1520
1751
  const previous = this.snapshotRetries.get(outcomeId);
1521
1752
  if (previous && (Number.isFinite(previous.dueAt) || previous.attempts >= SNAPSHOT_RETRY_MAX_ATTEMPTS)) return;
1522
1753
  const attempts = (previous?.attempts ?? 0) + 1;
@@ -1528,14 +1759,14 @@ var AggWebSocket = class {
1528
1759
  this.scheduleSnapshotRetries();
1529
1760
  }
1530
1761
  scheduleSnapshotRetries() {
1531
- if (this.snapshotRetryTimer || !this._connected || this.destroyed) return;
1762
+ if (this.snapshotRetryTimer || !this.orderbookTransportReady() || this.destroyed) return;
1532
1763
  let nextDueAt = Infinity;
1533
1764
  for (const retry of this.snapshotRetries.values()) nextDueAt = Math.min(nextDueAt, retry.dueAt);
1534
1765
  if (!Number.isFinite(nextDueAt)) return;
1535
1766
  const delay = Math.max(0, nextDueAt - Date.now(), this.snapshotRetryLastSentAt + SNAPSHOT_RETRY_TICK_MS - Date.now());
1536
1767
  this.snapshotRetryTimer = setTimeout(() => {
1537
1768
  this.snapshotRetryTimer = null;
1538
- if (!this._connected || this.destroyed) return;
1769
+ if (!this.orderbookTransportReady() || this.destroyed) return;
1539
1770
  const ids = [];
1540
1771
  for (const [id, retry] of [...this.snapshotRetries].sort((a, b) => a[1].dueAt - b[1].dueAt)) if (!this.subs.get(id)?.refCount) this.snapshotRetries.delete(id);
1541
1772
  else if (retry.dueAt <= Date.now() && ids.length < SNAPSHOT_RETRY_BATCH_SIZE) {
@@ -1545,7 +1776,7 @@ var AggWebSocket = class {
1545
1776
  }
1546
1777
  if (ids.length) {
1547
1778
  this.snapshotRetryLastSentAt = Date.now();
1548
- this.send({
1779
+ this.sendOrderbook({
1549
1780
  action: "resnapshot",
1550
1781
  channel: "orderbook",
1551
1782
  outcomeIds: ids
@@ -1562,8 +1793,8 @@ var AggWebSocket = class {
1562
1793
  requestResnapshot(outcomeId, force = false) {
1563
1794
  if (this.resyncing.has(outcomeId) && !force) return;
1564
1795
  this.resyncing.add(outcomeId);
1565
- if (this._connected) {
1566
- this.send({
1796
+ if (this.orderbookTransportReady()) {
1797
+ this.sendOrderbook({
1567
1798
  action: "resnapshot",
1568
1799
  channel: "orderbook",
1569
1800
  outcomeIds: [outcomeId]
@@ -1572,7 +1803,7 @@ var AggWebSocket = class {
1572
1803
  return;
1573
1804
  }
1574
1805
  this.pendingResnapshots.add(outcomeId);
1575
- this.ensureConnected();
1806
+ this.ensureOrderbookTransport();
1576
1807
  }
1577
1808
  /**
1578
1809
  * Schedule a microtask that flushes queued subscribe/unsubscribe frames.
@@ -1584,35 +1815,52 @@ var AggWebSocket = class {
1584
1815
  this.flushScheduled = true;
1585
1816
  queueMicrotask(() => {
1586
1817
  this.flushScheduled = false;
1587
- if (!this._connected) {
1818
+ if (!this.orderbookUrl) {
1819
+ if (!this._connected) {
1820
+ this.pendingFlushSubs.clear();
1821
+ this.pendingFlushUnsubs.clear();
1822
+ this.pendingFlushTradeSubs.clear();
1823
+ this.pendingFlushTradeUnsubs.clear();
1824
+ this.pendingFlushArbSubs.clear();
1825
+ this.pendingFlushArbUnsubs.clear();
1826
+ return;
1827
+ }
1828
+ this.flushPending();
1829
+ return;
1830
+ }
1831
+ if (!this._obConnected) {
1588
1832
  this.pendingFlushSubs.clear();
1589
1833
  this.pendingFlushUnsubs.clear();
1834
+ }
1835
+ if (!this._connected) {
1590
1836
  this.pendingFlushTradeSubs.clear();
1591
1837
  this.pendingFlushTradeUnsubs.clear();
1592
1838
  this.pendingFlushArbSubs.clear();
1593
1839
  this.pendingFlushArbUnsubs.clear();
1594
- return;
1595
1840
  }
1596
- this.flushPending();
1841
+ if (this._obConnected || this._connected) this.flushPending();
1597
1842
  });
1598
1843
  }
1599
1844
  flushPending() {
1600
- for (const id of [...this.pendingFlushSubs]) if (this.pendingFlushUnsubs.has(id)) {
1601
- this.pendingFlushSubs.delete(id);
1602
- this.pendingFlushUnsubs.delete(id);
1603
- }
1604
- if (this.pendingFlushSubs.size > 0) {
1605
- this.sendSubscribe(Array.from(this.pendingFlushSubs));
1606
- this.pendingFlushSubs.clear();
1607
- }
1608
- if (this.pendingFlushUnsubs.size > 0) {
1609
- this.send({
1610
- action: "unsubscribe",
1611
- channel: "orderbook",
1612
- outcomeIds: Array.from(this.pendingFlushUnsubs)
1613
- });
1614
- this.pendingFlushUnsubs.clear();
1845
+ if (!this.orderbookUrl || this._obConnected) {
1846
+ for (const id of [...this.pendingFlushSubs]) if (this.pendingFlushUnsubs.has(id)) {
1847
+ this.pendingFlushSubs.delete(id);
1848
+ this.pendingFlushUnsubs.delete(id);
1849
+ }
1850
+ if (this.pendingFlushSubs.size > 0) {
1851
+ this.sendSubscribe(Array.from(this.pendingFlushSubs));
1852
+ this.pendingFlushSubs.clear();
1853
+ }
1854
+ if (this.pendingFlushUnsubs.size > 0) {
1855
+ this.sendOrderbook({
1856
+ action: "unsubscribe",
1857
+ channel: "orderbook",
1858
+ outcomeIds: Array.from(this.pendingFlushUnsubs)
1859
+ });
1860
+ this.pendingFlushUnsubs.clear();
1861
+ }
1615
1862
  }
1863
+ if (!this._connected) return;
1616
1864
  for (const id of [...this.pendingFlushTradeSubs]) if (this.pendingFlushTradeUnsubs.has(id)) {
1617
1865
  this.pendingFlushTradeSubs.delete(id);
1618
1866
  this.pendingFlushTradeUnsubs.delete(id);
@@ -1655,25 +1903,33 @@ var AggWebSocket = class {
1655
1903
  }
1656
1904
  }
1657
1905
  sendSubscribe(outcomeIds) {
1658
- this.send({
1906
+ const msg = {
1659
1907
  action: "subscribe",
1660
1908
  channel: "orderbook",
1661
1909
  outcomeIds
1662
- });
1910
+ };
1911
+ if (this.orderbookLatency === "realtime") msg.latency = "realtime";
1912
+ this.sendOrderbook(msg);
1913
+ }
1914
+ sendOrderbook(msg) {
1915
+ this.sendOn(this.orderbookUrl ? this.obWs : this.ws, msg);
1663
1916
  }
1664
1917
  send(msg) {
1665
- if (this.ws?.readyState !== WS_OPEN) return;
1918
+ this.sendOn(this.ws, msg);
1919
+ }
1920
+ sendOn(ws, msg) {
1921
+ if (ws?.readyState !== WS_OPEN) return;
1666
1922
  const m = msg;
1667
1923
  const key = m.outcomeIds ? "outcomeIds" : m.marketIds ? "marketIds" : void 0;
1668
1924
  const ids = key ? m[key] : void 0;
1669
1925
  if (key && ids && ids.length > MAX_IDS_PER_FRAME) {
1670
- for (let i = 0; i < ids.length; i += MAX_IDS_PER_FRAME) this.ws.send(JSON.stringify({
1926
+ for (let i = 0; i < ids.length; i += MAX_IDS_PER_FRAME) ws.send(JSON.stringify({
1671
1927
  ...m,
1672
1928
  [key]: ids.slice(i, i + MAX_IDS_PER_FRAME)
1673
1929
  }));
1674
1930
  return;
1675
1931
  }
1676
- this.ws.send(JSON.stringify(msg));
1932
+ ws.send(JSON.stringify(msg));
1677
1933
  }
1678
1934
  activeOrderbookMarkets() {
1679
1935
  const markets = [];
@@ -1737,4 +1993,4 @@ function compareEventIds(a, b) {
1737
1993
  //#endregion
1738
1994
  export { loadImmutableHistory as a, orderbookCrc32 as c, snapshotToOrderbook as i, projectedOrderbookChecksum as l, applyOrderbookDelta as n, decodeChartHistory as o, computeChecksum as r, readChartHistoryText as s, AggWebSocket as t };
1739
1995
 
1740
- //# sourceMappingURL=agg-websocket-BVu_Tnr3.mjs.map
1996
+ //# sourceMappingURL=agg-websocket-C9ELOadG.mjs.map