@stryke-xyz/premarket-sdk 1.2.1 → 1.2.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.
Files changed (37) hide show
  1. package/dist/cjs/config/index.js +3 -3
  2. package/dist/cjs/package.json +1 -1
  3. package/dist/cjs/sync/clients/activity-client.js +325 -454
  4. package/dist/cjs/sync/clients/order-client.js +609 -429
  5. package/dist/esm/config/index.js +3 -3
  6. package/dist/esm/package.json +1 -1
  7. package/dist/esm/sync/clients/activity-client.js +325 -454
  8. package/dist/esm/sync/clients/order-client.js +609 -429
  9. package/dist/types/sync/clients/activity-client.d.ts +39 -32
  10. package/dist/types/sync/clients/order-client.d.ts +79 -58
  11. package/dist/types/sync/index.d.ts +2 -2
  12. package/package.json +58 -58
  13. package/dist/cjs/api/README.md +0 -296
  14. package/dist/cjs/config/README.md +0 -112
  15. package/dist/cjs/exchange/README.md +0 -280
  16. package/dist/cjs/registry/README.md +0 -150
  17. package/dist/cjs/shared/README.md +0 -235
  18. package/dist/cjs/shared/utils.js +0 -1
  19. package/dist/cjs/sync/README.md +0 -215
  20. package/dist/cjs/sync/clients/base-client.js +0 -518
  21. package/dist/cjs/sync/redis-ws-client.js +0 -235
  22. package/dist/cjs/utils/README.md +0 -89
  23. package/dist/cjs/vault/README.md +0 -268
  24. package/dist/esm/api/README.md +0 -296
  25. package/dist/esm/config/README.md +0 -112
  26. package/dist/esm/exchange/README.md +0 -280
  27. package/dist/esm/registry/README.md +0 -150
  28. package/dist/esm/shared/README.md +0 -235
  29. package/dist/esm/shared/utils.js +0 -0
  30. package/dist/esm/sync/README.md +0 -215
  31. package/dist/esm/sync/clients/base-client.js +0 -508
  32. package/dist/esm/sync/redis-ws-client.js +0 -225
  33. package/dist/esm/utils/README.md +0 -89
  34. package/dist/esm/vault/README.md +0 -268
  35. package/dist/types/shared/utils.d.ts +0 -0
  36. package/dist/types/sync/clients/base-client.d.ts +0 -56
  37. package/dist/types/sync/redis-ws-client.d.ts +0 -21
@@ -124,6 +124,10 @@ function _sliced_to_array(arr, i) {
124
124
  function _to_consumable_array(arr) {
125
125
  return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array(arr) || _non_iterable_spread();
126
126
  }
127
+ function _type_of(obj) {
128
+ "@swc/helpers - typeof";
129
+ return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
130
+ }
127
131
  function _unsupported_iterable_to_array(o, minLen) {
128
132
  if (!o) return;
129
133
  if (typeof o === "string") return _array_like_to_array(o, minLen);
@@ -238,21 +242,40 @@ function scheduleDeferred(callback) {
238
242
  }
239
243
  setTimeout(callback, 0);
240
244
  }
241
- /**
242
- * Normalize a price string to a canonical form for consistent map key lookups.
243
- * Converts "1.000000" and "1" both to "1" (removes trailing zeros after decimal).
244
- */ function normalizePrice(price) {
245
+ function normalizePrice(price) {
245
246
  var num = parseFloat(price);
246
247
  if (isNaN(num)) return price;
247
248
  return num.toString();
248
249
  }
249
- /** Current depth state for a token within a market subscription. */ /** One bid or ask level change in the normalized SDK format. */ /** Raw level shape inside the wire `depth_update.levels[]` array. */ /** Raw `depth_update` frame as published by the depth projector. */ /** Consolidated depth update emitted to SDK listeners after normalization. */ /** Last applied executor sequenceId for this token (== endSeq of the frame). */ /** Range covered by this frame; clients can detect gaps via startSeq != prevSeq + 1. */ /** Configuration for the market depth websocket client. */ /** Heartbeat interval in ms (default: 30000) */ /** Heartbeat timeout - if no pong received within this time, reconnect (default: 10000) */ /** Max reconnection attempts before giving up (default: Infinity) */ /** Initial reconnect delay in ms (default: 1000) */ /** Max reconnect delay in ms (default: 30000) */ // Internal state per token
250
- // price -> depth
251
- // price -> depth
252
- // Current sequence ID for this token
250
+ /** A market + its token IDs to subscribe to. */ /** Current depth state for a token within a market subscription. */ /** One bid or ask level change in the normalized SDK format. */ /** Raw level shape inside the wire `depth_update.levels[]` array. */ /** Raw `depth_update` frame as published by the depth projector. */ /** Consolidated depth update emitted to SDK listeners after normalization. */ /** Last applied executor sequenceId for this token (== endSeq of the frame). */ /** Range covered by this frame; clients can detect gaps via startSeq != prevSeq + 1. */ /**
251
+ * Configuration for the market depth websocket client.
252
+ *
253
+ * Supports multiple markets over a single WebSocket connection.
254
+ * Markets can be added/removed dynamically via subscribeMarket / unsubscribeMarket
255
+ * without closing the connection.
256
+ */ /** Initial market subscriptions. More can be added later via subscribeMarket(). */ /** Heartbeat interval in ms (default: 30000) */ /** Heartbeat timeout - if no pong received within this time, reconnect (default: 10000) */ /** Max reconnection attempts before giving up (default: Infinity) */ /** Initial reconnect delay in ms (default: 1000) */ /** Max reconnect delay in ms (default: 30000) */ /** @deprecated Use MarketDepthClientConfig with markets[] instead of marketId+tokenIds. */ // Internal state per token
257
+ // price → depth
258
+ // price → depth
253
259
  /**
254
- * Client for syncing orderbook depth data for multiple tokens in a market.
255
- * Seq is per market+token (gapless, monotonic). Each token tracks its own seq for dedup and gap detection.
260
+ * Client for syncing orderbook depth data for one or more markets over a
261
+ * single persistent WebSocket connection.
262
+ *
263
+ * Usage:
264
+ * const client = new MarketDepthSyncClient({ wsUrl: "wss://..." });
265
+ * await client.connect();
266
+ *
267
+ * // Subscribe to markets dynamically (e.g. when they scroll into view)
268
+ * client.subscribeMarket("5", ["0xabc..."]);
269
+ *
270
+ * // Unsubscribe without closing the connection (e.g. scrolled out of view)
271
+ * client.unsubscribeMarket("5");
272
+ *
273
+ * // Listen for updates
274
+ * const offDelta = client.onDelta((marketId, update) => { ... });
275
+ * const offSnap = client.onSnapshot((marketId, snapshots) => { ... });
276
+ *
277
+ * // Full disconnect (app teardown)
278
+ * await client.disconnect();
256
279
  */ export var MarketDepthSyncClient = /*#__PURE__*/ function() {
257
280
  "use strict";
258
281
  function MarketDepthSyncClient(config) {
@@ -260,11 +283,18 @@ function scheduleDeferred(callback) {
260
283
  _define_property(this, "ws", null);
261
284
  _define_property(this, "config", void 0);
262
285
  _define_property(this, "status", "disconnected");
263
- // Per-token depth state (each token has its own gapless executor-seq counter)
286
+ // Per-token depth state (tokenIds are globally unique across all subscribed markets)
264
287
  _define_property(this, "tokenStates", new Map());
265
- // Frame queue for ordering — frames received before initial snapshot are buffered.
266
- _define_property(this, "frameQueue", []);
267
- _define_property(this, "isProcessing", false);
288
+ // Active subscriptions: marketId tokenIds[]
289
+ _define_property(this, "activeSubscriptions", new Map());
290
+ // Reverse map: tokenId → marketId (for gap resync targeting)
291
+ _define_property(this, "tokenToMarket", new Map());
292
+ // Markets that have received their initial snapshot on this connection
293
+ _define_property(this, "syncedMarkets", new Set());
294
+ // Per-market frame queues: buffer depth_updates received before snapshot
295
+ _define_property(this, "frameQueues", new Map());
296
+ // Per-market processing mutex
297
+ _define_property(this, "processingMarkets", new Set());
268
298
  // Listeners
269
299
  _define_property(this, "statusListeners", new Set());
270
300
  _define_property(this, "snapshotListeners", new Set());
@@ -273,121 +303,186 @@ function scheduleDeferred(callback) {
273
303
  _define_property(this, "shouldBeConnected", false);
274
304
  _define_property(this, "reconnectAttempts", 0);
275
305
  _define_property(this, "reconnectTimeoutId", null);
276
- // Heartbeat state
306
+ // Heartbeat
277
307
  _define_property(this, "heartbeatIntervalId", null);
278
308
  _define_property(this, "heartbeatTimeoutId", null);
279
309
  _define_property(this, "lastPongTime", 0);
280
- // Visibility change handler reference (for cleanup)
310
+ // Visibility handler
281
311
  _define_property(this, "visibilityChangeHandler", null);
312
+ // Support legacy single-market config (marketId + tokenIds)
313
+ var normalizedConfig = _object_spread({}, config);
314
+ if (config.marketId && config.tokenIds) {
315
+ var _config_markets;
316
+ normalizedConfig.markets = [
317
+ {
318
+ marketId: config.marketId,
319
+ tokenIds: config.tokenIds
320
+ }
321
+ ].concat(_to_consumable_array((_config_markets = config.markets) !== null && _config_markets !== void 0 ? _config_markets : []));
322
+ }
282
323
  this.config = _object_spread({
324
+ markets: [],
283
325
  heartbeatIntervalMs: 30000,
284
326
  heartbeatTimeoutMs: 10000,
285
327
  maxReconnectAttempts: Infinity,
286
328
  initialReconnectDelayMs: 1000,
287
329
  maxReconnectDelayMs: 30000
288
- }, config);
330
+ }, normalizedConfig);
331
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
332
+ try {
333
+ // Seed initial subscriptions from config
334
+ for(var _iterator = this.config.markets[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
335
+ var spec = _step.value;
336
+ this.activeSubscriptions.set(spec.marketId, _to_consumable_array(spec.tokenIds));
337
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
338
+ try {
339
+ for(var _iterator1 = spec.tokenIds[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
340
+ var tokenId = _step1.value;
341
+ this.tokenToMarket.set(tokenId, spec.marketId);
342
+ }
343
+ } catch (err) {
344
+ _didIteratorError1 = true;
345
+ _iteratorError1 = err;
346
+ } finally{
347
+ try {
348
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
349
+ _iterator1.return();
350
+ }
351
+ } finally{
352
+ if (_didIteratorError1) {
353
+ throw _iteratorError1;
354
+ }
355
+ }
356
+ }
357
+ }
358
+ } catch (err) {
359
+ _didIteratorError = true;
360
+ _iteratorError = err;
361
+ } finally{
362
+ try {
363
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
364
+ _iterator.return();
365
+ }
366
+ } finally{
367
+ if (_didIteratorError) {
368
+ throw _iteratorError;
369
+ }
370
+ }
371
+ }
289
372
  }
290
373
  _create_class(MarketDepthSyncClient, [
291
374
  {
292
375
  key: "connect",
293
- value: /** Connects to the market depth websocket and hydrates token snapshots. */ function connect() {
376
+ value: // ---------------------------------------------------------------------------
377
+ // Public API
378
+ // ---------------------------------------------------------------------------
379
+ /** Open the WebSocket connection and subscribe to all configured markets. */ function connect() {
294
380
  return _async_to_generator(function() {
295
381
  var _this, wsUrl;
296
382
  return _ts_generator(this, function(_state) {
297
383
  _this = this;
298
- // Clean up any existing connection first
299
384
  this.stopHeartbeat();
300
385
  this.clearReconnectTimeout();
301
386
  if (this.ws) {
302
387
  this.ws.onclose = null;
303
- // Prevent triggering reconnect
304
388
  this.ws.close();
305
389
  this.ws = null;
306
390
  }
307
391
  this.shouldBeConnected = true;
392
+ this.syncedMarkets.clear();
393
+ this.frameQueues.clear();
394
+ this.processingMarkets.clear();
308
395
  this.setStatus("connecting");
309
396
  wsUrl = this.config.wsUrl;
310
397
  if (!wsUrl.startsWith("ws://") && !wsUrl.startsWith("wss://")) {
311
398
  throw new Error("Invalid WebSocket URL: ".concat(wsUrl));
312
399
  }
313
- // Prevent queue processing until snapshots are received
314
- this.isProcessing = true;
315
- // Set up visibility change handler
316
400
  this.setupVisibilityChangeHandler();
317
401
  return [
318
402
  2,
319
403
  new Promise(function(resolve, reject) {
320
404
  var settled = false;
321
405
  var resolveOnce = function() {
322
- if (settled) return;
323
- settled = true;
324
- resolve();
406
+ if (!settled) {
407
+ settled = true;
408
+ resolve();
409
+ }
325
410
  };
326
- var rejectOnce = function(error) {
327
- if (settled) return;
328
- settled = true;
329
- reject(error);
411
+ var rejectOnce = function(e) {
412
+ if (!settled) {
413
+ settled = true;
414
+ reject(e);
415
+ }
330
416
  };
331
417
  _this.ws = new WebSocket(wsUrl);
332
418
  _this.ws.onopen = function() {
333
- // Reset reconnect attempts on successful connection
334
419
  _this.reconnectAttempts = 0;
335
420
  _this.lastPongTime = Date.now();
336
- // Send subscribe_market message
337
- _this.ws.send(JSON.stringify({
338
- type: "subscribe_market",
339
- marketId: _this.config.marketId,
340
- tokenIds: _this.config.tokenIds
341
- }));
342
- // Start heartbeat
343
421
  _this.startHeartbeat();
422
+ if (_this.activeSubscriptions.size === 0) {
423
+ _this.setStatus("synced");
424
+ resolveOnce();
425
+ } else {
426
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
427
+ try {
428
+ for(var _iterator = _this.activeSubscriptions[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
429
+ var _step_value = _sliced_to_array(_step.value, 2), marketId = _step_value[0], tokenIds = _step_value[1];
430
+ _this._sendSubscribeMarket(marketId, tokenIds);
431
+ }
432
+ } catch (err) {
433
+ _didIteratorError = true;
434
+ _iteratorError = err;
435
+ } finally{
436
+ try {
437
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
438
+ _iterator.return();
439
+ }
440
+ } finally{
441
+ if (_didIteratorError) {
442
+ throw _iteratorError;
443
+ }
444
+ }
445
+ }
446
+ // Resolve immediately once WS is open — snapshots arrive async
447
+ resolveOnce();
448
+ }
344
449
  };
345
450
  _this.ws.onmessage = function(event) {
346
451
  try {
347
452
  var msg = JSON.parse(event.data);
348
- // Handle pong response
349
453
  if (msg.type === "pong") {
350
454
  _this.lastPongTime = Date.now();
351
455
  _this.clearHeartbeatTimeout();
352
456
  return;
353
457
  }
354
458
  if (msg.type === "subscribed_market") {
355
- // Received initial snapshots
356
459
  _this.handleSubscribedMarket(msg);
357
- _this.isProcessing = false;
358
- _this.setStatus("synced");
359
- void _this.processFrameQueue();
360
- resolveOnce();
361
460
  } else if (msg.type === "depth_update") {
362
461
  _this.handleDepthUpdate(msg);
363
462
  } else if (msg.type === "market_state") {
364
- // Received market state (bestBid, bestAsk, lastPrice) - preferred over last_price
365
463
  _this.handleMarketStateUpdate(msg);
366
464
  } else if (msg.type === "last_price") {
367
- // Legacy: last price only (still supported)
368
465
  _this.handleLastPriceUpdate(msg);
369
466
  } else if (msg.type === "error") {
370
- console.error("WebSocket error:", msg.message);
467
+ console.error("[MarketDepthSyncClient] server error:", msg.message);
371
468
  rejectOnce(new Error(msg.message));
372
469
  }
373
470
  } catch (error) {
374
- console.error("Error parsing message:", error);
471
+ console.error("[MarketDepthSyncClient] parse error:", error);
375
472
  }
376
473
  };
377
474
  _this.ws.onerror = function(error) {
378
- console.error("WebSocket error:", error);
379
- // Only reject if this is the initial connection
380
475
  if (_this.status === "connecting") {
381
476
  rejectOnce(error);
382
477
  }
383
478
  };
384
479
  _this.ws.onclose = function() {
385
480
  _this.stopHeartbeat();
481
+ _this.syncedMarkets.clear();
386
482
  _this.setStatus("disconnected");
387
483
  if (!settled) {
388
- rejectOnce(new Error("WebSocket closed before initial depth snapshot"));
484
+ rejectOnce(new Error("[MarketDepthSyncClient] WebSocket closed before connecting"));
389
485
  }
390
- // Attempt to reconnect if we should still be connected
391
486
  if (_this.shouldBeConnected) {
392
487
  _this.scheduleReconnect();
393
488
  }
@@ -399,210 +494,257 @@ function scheduleDeferred(callback) {
399
494
  }
400
495
  },
401
496
  {
402
- key: "setupVisibilityChangeHandler",
403
- value: function setupVisibilityChangeHandler() {
404
- var _this = this;
405
- // Only set up in browser environment
406
- if (typeof document === "undefined") return;
407
- // Remove existing handler if any
408
- this.removeVisibilityChangeHandler();
409
- this.visibilityChangeHandler = function() {
410
- if (document.visibilityState === "visible" && _this.shouldBeConnected) {
411
- // Tab became visible - check if connection is still healthy
412
- _this.checkConnectionHealth();
497
+ /**
498
+ * Subscribe to a market's depth. If the WebSocket is already open, sends the
499
+ * `subscribe_market` message immediately. Otherwise, the subscription is queued
500
+ * and sent when the connection opens. Calling this for an already-subscribed
501
+ * market re-requests a fresh snapshot.
502
+ */ key: "subscribeMarket",
503
+ value: function subscribeMarket(marketId, tokenIds) {
504
+ this.activeSubscriptions.set(marketId, _to_consumable_array(tokenIds));
505
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
506
+ try {
507
+ for(var _iterator = tokenIds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
508
+ var tokenId = _step.value;
509
+ this.tokenToMarket.set(tokenId, marketId);
413
510
  }
414
- };
415
- document.addEventListener("visibilitychange", this.visibilityChangeHandler);
511
+ } catch (err) {
512
+ _didIteratorError = true;
513
+ _iteratorError = err;
514
+ } finally{
515
+ try {
516
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
517
+ _iterator.return();
518
+ }
519
+ } finally{
520
+ if (_didIteratorError) {
521
+ throw _iteratorError;
522
+ }
523
+ }
524
+ }
525
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
526
+ this._sendSubscribeMarket(marketId, tokenIds);
527
+ }
416
528
  }
417
529
  },
418
530
  {
419
- key: "removeVisibilityChangeHandler",
420
- value: function removeVisibilityChangeHandler() {
421
- if (typeof document === "undefined") return;
422
- if (this.visibilityChangeHandler) {
423
- document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
424
- this.visibilityChangeHandler = null;
531
+ /**
532
+ * Unsubscribe from a market. Sends `unsubscribe_market` to the server and
533
+ * clears local state for that market's tokens. The WebSocket connection is
534
+ * kept open for other subscriptions.
535
+ */ key: "unsubscribeMarket",
536
+ value: function unsubscribeMarket(marketId) {
537
+ var _this_activeSubscriptions_get;
538
+ var tokenIds = (_this_activeSubscriptions_get = this.activeSubscriptions.get(marketId)) !== null && _this_activeSubscriptions_get !== void 0 ? _this_activeSubscriptions_get : [];
539
+ this.activeSubscriptions.delete(marketId);
540
+ this.syncedMarkets.delete(marketId);
541
+ this.frameQueues.delete(marketId);
542
+ this.processingMarkets.delete(marketId);
543
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
544
+ try {
545
+ for(var _iterator = tokenIds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
546
+ var tokenId = _step.value;
547
+ this.tokenStates.delete(tokenId);
548
+ this.tokenToMarket.delete(tokenId);
549
+ }
550
+ } catch (err) {
551
+ _didIteratorError = true;
552
+ _iteratorError = err;
553
+ } finally{
554
+ try {
555
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
556
+ _iterator.return();
557
+ }
558
+ } finally{
559
+ if (_didIteratorError) {
560
+ throw _iteratorError;
561
+ }
562
+ }
563
+ }
564
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
565
+ try {
566
+ this.ws.send(JSON.stringify({
567
+ type: "unsubscribe_market",
568
+ marketId: marketId
569
+ }));
570
+ } catch (unused) {}
425
571
  }
426
572
  }
427
573
  },
428
574
  {
429
- key: "checkConnectionHealth",
430
- value: function checkConnectionHealth() {
431
- var _this = this;
432
- if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
433
- // Connection is dead, trigger reconnect
434
- console.log("Connection unhealthy on visibility change, reconnecting...");
435
- this.handleConnectionLost();
436
- return;
437
- }
438
- // Send a ping to verify the connection is actually working
439
- try {
440
- this.ws.send(JSON.stringify({
441
- type: "ping"
442
- }));
443
- // Set a timeout for the pong response
444
- this.clearHeartbeatTimeout();
445
- this.heartbeatTimeoutId = setTimeout(function() {
446
- console.log("Ping timeout on visibility change, reconnecting...");
447
- _this.handleConnectionLost();
448
- }, this.config.heartbeatTimeoutMs);
449
- } catch (unused) {
450
- // Send failed, connection is dead
451
- this.handleConnectionLost();
452
- }
575
+ key: "disconnect",
576
+ value: // Ignore — connection closing
577
+ /** Fully disconnect and stop reconnecting. */ function disconnect() {
578
+ return _async_to_generator(function() {
579
+ return _ts_generator(this, function(_state) {
580
+ this.shouldBeConnected = false;
581
+ this.stopHeartbeat();
582
+ this.clearReconnectTimeout();
583
+ this.removeVisibilityChangeHandler();
584
+ if (this.ws) {
585
+ this.ws.onclose = null;
586
+ this.ws.close();
587
+ this.ws = null;
588
+ }
589
+ this.syncedMarkets.clear();
590
+ this.setStatus("disconnected");
591
+ return [
592
+ 2
593
+ ];
594
+ });
595
+ }).call(this);
453
596
  }
454
597
  },
455
598
  {
456
- key: "handleConnectionLost",
457
- value: function handleConnectionLost() {
458
- this.stopHeartbeat();
459
- if (this.ws) {
460
- this.ws.onclose = null;
461
- // Prevent double-triggering
462
- this.ws.close();
463
- this.ws = null;
464
- }
465
- this.setStatus("disconnected");
466
- if (this.shouldBeConnected) {
467
- this.scheduleReconnect();
468
- }
599
+ key: "getStatus",
600
+ value: function getStatus() {
601
+ return this.status;
469
602
  }
470
603
  },
471
604
  {
472
- key: "startHeartbeat",
473
- value: function startHeartbeat() {
474
- var _this = this;
475
- this.stopHeartbeat();
476
- this.heartbeatIntervalId = setInterval(function() {
477
- if (!_this.ws || _this.ws.readyState !== WebSocket.OPEN) {
478
- return;
479
- }
480
- try {
481
- _this.ws.send(JSON.stringify({
482
- type: "ping"
483
- }));
484
- // Set timeout for pong response
485
- _this.clearHeartbeatTimeout();
486
- _this.heartbeatTimeoutId = setTimeout(function() {
487
- console.log("Heartbeat timeout, reconnecting...");
488
- _this.handleConnectionLost();
489
- }, _this.config.heartbeatTimeoutMs);
490
- } catch (unused) {
491
- // Send failed, connection is dead
492
- _this.handleConnectionLost();
493
- }
494
- }, this.config.heartbeatIntervalMs);
605
+ key: "isSynced",
606
+ value: function isSynced() {
607
+ if (this.activeSubscriptions.size === 0) return this.status === "synced";
608
+ return this.activeSubscriptions.size === this.syncedMarkets.size;
495
609
  }
496
610
  },
497
611
  {
498
- key: "stopHeartbeat",
499
- value: function stopHeartbeat() {
500
- if (this.heartbeatIntervalId) {
501
- clearInterval(this.heartbeatIntervalId);
502
- this.heartbeatIntervalId = null;
503
- }
504
- this.clearHeartbeatTimeout();
612
+ key: "isMarketSynced",
613
+ value: function isMarketSynced(marketId) {
614
+ return this.syncedMarkets.has(marketId);
505
615
  }
506
616
  },
507
617
  {
508
- key: "clearHeartbeatTimeout",
509
- value: function clearHeartbeatTimeout() {
510
- if (this.heartbeatTimeoutId) {
511
- clearTimeout(this.heartbeatTimeoutId);
512
- this.heartbeatTimeoutId = null;
513
- }
618
+ key: "getBids",
619
+ value: function getBids(tokenId) {
620
+ var state = this.tokenStates.get(tokenId);
621
+ if (!state) return [];
622
+ return Array.from(state.bids.entries()).map(function(param) {
623
+ var _param = _sliced_to_array(param, 2), price = _param[0], depth = _param[1];
624
+ return {
625
+ price: price,
626
+ depth: depth
627
+ };
628
+ });
514
629
  }
515
630
  },
516
631
  {
517
- key: "scheduleReconnect",
518
- value: function scheduleReconnect() {
519
- var _this = this;
520
- if (!this.shouldBeConnected) return;
521
- var maxAttempts = this.config.maxReconnectAttempts;
522
- if (this.reconnectAttempts >= maxAttempts) {
523
- console.error("Max reconnection attempts (".concat(maxAttempts, ") reached, giving up"));
524
- return;
525
- }
526
- // Exponential backoff with jitter
527
- var baseDelay = this.config.initialReconnectDelayMs;
528
- var maxDelay = this.config.maxReconnectDelayMs;
529
- var delay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempts) + Math.random() * 1000, maxDelay);
530
- this.reconnectAttempts++;
531
- this.setStatus("recovering");
532
- console.log("Scheduling reconnect attempt ".concat(this.reconnectAttempts, " in ").concat(Math.round(delay), "ms"));
533
- this.clearReconnectTimeout();
534
- this.reconnectTimeoutId = setTimeout(function() {
535
- _this.performReconnect();
536
- }, delay);
632
+ key: "getAsks",
633
+ value: function getAsks(tokenId) {
634
+ var state = this.tokenStates.get(tokenId);
635
+ if (!state) return [];
636
+ return Array.from(state.asks.entries()).map(function(param) {
637
+ var _param = _sliced_to_array(param, 2), price = _param[0], depth = _param[1];
638
+ return {
639
+ price: price,
640
+ depth: depth
641
+ };
642
+ });
537
643
  }
538
644
  },
539
645
  {
540
- key: "clearReconnectTimeout",
541
- value: function clearReconnectTimeout() {
542
- if (this.reconnectTimeoutId) {
543
- clearTimeout(this.reconnectTimeoutId);
544
- this.reconnectTimeoutId = null;
545
- }
646
+ key: "getBestBid",
647
+ value: function getBestBid(tokenId) {
648
+ var _ref;
649
+ var _this_tokenStates_get;
650
+ return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.bestBid) !== null && _ref !== void 0 ? _ref : null;
546
651
  }
547
652
  },
548
653
  {
549
- key: "performReconnect",
550
- value: function performReconnect() {
551
- return _async_to_generator(function() {
552
- var error;
553
- return _ts_generator(this, function(_state) {
554
- switch(_state.label){
555
- case 0:
556
- if (!this.shouldBeConnected) return [
557
- 2
558
- ];
559
- _state.label = 1;
560
- case 1:
561
- _state.trys.push([
562
- 1,
563
- 3,
564
- ,
565
- 4
566
- ]);
567
- // Clear state before reconnecting
568
- this.tokenStates.clear();
569
- this.frameQueue = [];
570
- return [
571
- 4,
572
- this.connect()
573
- ];
574
- case 2:
575
- _state.sent();
576
- console.log("Reconnected successfully");
577
- return [
578
- 3,
579
- 4
580
- ];
581
- case 3:
582
- error = _state.sent();
583
- console.error("Reconnection failed:", error);
584
- return [
585
- 3,
586
- 4
587
- ];
588
- case 4:
589
- return [
590
- 2
591
- ];
592
- }
593
- });
594
- }).call(this);
654
+ key: "getBestAsk",
655
+ value: function getBestAsk(tokenId) {
656
+ var _ref;
657
+ var _this_tokenStates_get;
658
+ return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.bestAsk) !== null && _ref !== void 0 ? _ref : null;
595
659
  }
596
660
  },
597
661
  {
598
- key: "handleSubscribedMarket",
599
- value: // Will trigger another reconnect via onclose handler
600
- function handleSubscribedMarket(msg) {
601
- var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
602
- try {
603
- // Initialize state for each token from snapshots (seq is the executor
604
- // sequenceId, monotonic per token).
605
- for(var _iterator = msg.snapshots[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
662
+ key: "getLastPrice",
663
+ value: function getLastPrice(tokenId) {
664
+ var _ref;
665
+ var _this_tokenStates_get;
666
+ return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.lastPrice) !== null && _ref !== void 0 ? _ref : null;
667
+ }
668
+ },
669
+ {
670
+ key: "onSnapshot",
671
+ value: function onSnapshot(listener) {
672
+ var _this = this;
673
+ this.snapshotListeners.add(listener);
674
+ return function() {
675
+ return _this.snapshotListeners.delete(listener);
676
+ };
677
+ }
678
+ },
679
+ {
680
+ key: "onDelta",
681
+ value: function onDelta(listener) {
682
+ var _this = this;
683
+ this.deltaListeners.add(listener);
684
+ return function() {
685
+ return _this.deltaListeners.delete(listener);
686
+ };
687
+ }
688
+ },
689
+ {
690
+ key: "onStatus",
691
+ value: function onStatus(listener) {
692
+ var _this = this;
693
+ this.statusListeners.add(listener);
694
+ return function() {
695
+ return _this.statusListeners.delete(listener);
696
+ };
697
+ }
698
+ },
699
+ {
700
+ key: "_sendSubscribeMarket",
701
+ value: // ---------------------------------------------------------------------------
702
+ // Private — WS message handlers
703
+ // ---------------------------------------------------------------------------
704
+ function _sendSubscribeMarket(marketId, tokenIds) {
705
+ var _this_activeSubscriptions_get;
706
+ // Clear stale state so we get a clean snapshot
707
+ var existingTokenIds = (_this_activeSubscriptions_get = this.activeSubscriptions.get(marketId)) !== null && _this_activeSubscriptions_get !== void 0 ? _this_activeSubscriptions_get : tokenIds;
708
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
709
+ try {
710
+ for(var _iterator = existingTokenIds[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
711
+ var tokenId = _step.value;
712
+ this.tokenStates.delete(tokenId);
713
+ }
714
+ } catch (err) {
715
+ _didIteratorError = true;
716
+ _iteratorError = err;
717
+ } finally{
718
+ try {
719
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
720
+ _iterator.return();
721
+ }
722
+ } finally{
723
+ if (_didIteratorError) {
724
+ throw _iteratorError;
725
+ }
726
+ }
727
+ }
728
+ this.syncedMarkets.delete(marketId);
729
+ this.frameQueues.delete(marketId);
730
+ try {
731
+ this.ws.send(JSON.stringify({
732
+ type: "subscribe_market",
733
+ marketId: marketId,
734
+ tokenIds: tokenIds
735
+ }));
736
+ } catch (e) {
737
+ console.warn("[MarketDepthSyncClient] failed to send subscribe_market:", e);
738
+ }
739
+ }
740
+ },
741
+ {
742
+ key: "handleSubscribedMarket",
743
+ value: function handleSubscribedMarket(msg) {
744
+ var marketId = msg.marketId, snapshots = msg.snapshots;
745
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
746
+ try {
747
+ for(var _iterator = snapshots[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
606
748
  var snapshot = _step.value;
607
749
  var state = {
608
750
  bids: new Map(),
@@ -668,20 +810,38 @@ function scheduleDeferred(callback) {
668
810
  }
669
811
  }
670
812
  }
813
+ this.syncedMarkets.add(marketId);
814
+ if (this.activeSubscriptions.size > 0 && this.syncedMarkets.size >= this.activeSubscriptions.size) {
815
+ this.setStatus("synced");
816
+ }
671
817
  this.snapshotListeners.forEach(function(listener) {
672
818
  try {
673
- listener(msg.snapshots);
674
- } catch (error) {
675
- console.error("Error in snapshot listener:", error);
819
+ listener(marketId, snapshots);
820
+ } catch (e) {
821
+ console.error(e);
676
822
  }
677
823
  });
824
+ // Process any buffered depth_updates for this market
825
+ void this.processFrameQueue(marketId);
678
826
  }
679
827
  },
680
828
  {
681
829
  key: "handleDepthUpdate",
682
830
  value: function handleDepthUpdate(msg) {
683
- this.frameQueue.push(msg);
684
- void this.processFrameQueue();
831
+ var marketId = msg.marketId;
832
+ if (!this.syncedMarkets.has(marketId)) {
833
+ // Buffer until snapshot arrives
834
+ if (!this.frameQueues.has(marketId)) {
835
+ this.frameQueues.set(marketId, []);
836
+ }
837
+ this.frameQueues.get(marketId).push(msg);
838
+ return;
839
+ }
840
+ if (!this.frameQueues.has(marketId)) {
841
+ this.frameQueues.set(marketId, []);
842
+ }
843
+ this.frameQueues.get(marketId).push(msg);
844
+ void this.processFrameQueue(marketId);
685
845
  }
686
846
  },
687
847
  {
@@ -689,83 +849,88 @@ function scheduleDeferred(callback) {
689
849
  value: function handleMarketStateUpdate(msg) {
690
850
  var _msg_bestBid, _msg_bestAsk, _msg_lastPrice;
691
851
  var state = this.tokenStates.get(String(msg.tokenId));
692
- if (!state) {
693
- return;
694
- }
852
+ if (!state) return;
695
853
  state.bestBid = (_msg_bestBid = msg.bestBid) !== null && _msg_bestBid !== void 0 ? _msg_bestBid : state.bestBid;
696
854
  state.bestAsk = (_msg_bestAsk = msg.bestAsk) !== null && _msg_bestAsk !== void 0 ? _msg_bestAsk : state.bestAsk;
697
855
  state.lastPrice = (_msg_lastPrice = msg.lastPrice) !== null && _msg_lastPrice !== void 0 ? _msg_lastPrice : state.lastPrice;
698
- this.emitOutOfBandUpdate(msg.tokenId, state);
856
+ this.emitOutOfBandUpdate(msg.marketId, msg.tokenId, state);
699
857
  }
700
858
  },
701
859
  {
702
860
  key: "handleLastPriceUpdate",
703
861
  value: function handleLastPriceUpdate(msg) {
862
+ var _ref, _msg_marketId;
704
863
  var state = this.tokenStates.get(String(msg.tokenId));
705
- if (!state) {
706
- return;
707
- }
864
+ if (!state) return;
708
865
  state.lastPrice = msg.lastPrice;
709
- this.emitOutOfBandUpdate(msg.tokenId, state);
866
+ var marketId = (_ref = (_msg_marketId = msg.marketId) !== null && _msg_marketId !== void 0 ? _msg_marketId : this.tokenToMarket.get(msg.tokenId)) !== null && _ref !== void 0 ? _ref : "";
867
+ this.emitOutOfBandUpdate(marketId, msg.tokenId, state);
710
868
  }
711
869
  },
712
870
  {
713
871
  key: "emitOutOfBandUpdate",
714
- value: /** Emit a delta with empty levels for events that mutate side-state but no levels (market_state, last_price). */ function emitOutOfBandUpdate(tokenId, state) {
715
- var _this = this;
872
+ value: function emitOutOfBandUpdate(marketId, tokenId, state) {
873
+ var update = {
874
+ tokenId: tokenId,
875
+ levels: [],
876
+ bestBid: state.bestBid,
877
+ bestAsk: state.bestAsk,
878
+ lastPrice: state.lastPrice,
879
+ seq: state.seq,
880
+ startSeq: state.seq,
881
+ endSeq: state.seq
882
+ };
716
883
  this.deltaListeners.forEach(function(listener) {
717
884
  try {
718
- var update = {
719
- tokenId: tokenId,
720
- levels: [],
721
- bestBid: state.bestBid,
722
- bestAsk: state.bestAsk,
723
- lastPrice: state.lastPrice,
724
- seq: state.seq,
725
- startSeq: state.seq,
726
- endSeq: state.seq
727
- };
728
- listener(_this.config.marketId, update);
729
- } catch (error) {
730
- console.error("Error in delta listener:", error);
885
+ listener(marketId, update);
886
+ } catch (e) {
887
+ console.error(e);
731
888
  }
732
889
  });
733
890
  }
734
891
  },
735
892
  {
736
893
  key: "processFrameQueue",
737
- value: function processFrameQueue() {
894
+ value: function processFrameQueue(marketId) {
738
895
  return _async_to_generator(function() {
739
- var _this, _this1, _loop;
896
+ var _this, _this1, _loop, queue, _ret, queue1;
740
897
  return _ts_generator(this, function(_state) {
741
898
  _this = this;
742
- if (this.isProcessing) return [
899
+ if (this.processingMarkets.has(marketId)) return [
743
900
  2
744
901
  ];
745
- this.isProcessing = true;
902
+ this.processingMarkets.add(marketId);
746
903
  try {
747
904
  _loop = function() {
748
- var frame = _this1.frameQueue.shift();
905
+ var frame = queue.shift();
749
906
  var state = _this1.tokenStates.get(String(frame.tokenId));
750
907
  if (!state) return "continue";
751
908
  var startSeq = parseInt(frame.startSeq, 10);
752
909
  var endSeq = parseInt(frame.endSeq, 10);
753
- // Whole frame already applied (e.g. reconnect after partial JetStream replay)
754
- if (endSeq <= state.seq) return "continue";
755
- // Gap detection: startSeq should be exactly state.seq + 1.
756
- // Depth values are absolute, so a gap is a soft warning, not a hard fail.
757
- if (state.seq > 0 && startSeq > state.seq + 1) {
758
- console.warn("[MarketDepth] Gap tokenId=".concat(frame.tokenId, " expected=").concat(state.seq + 1, " got=").concat(startSeq, " (frame ").concat(startSeq, "..").concat(endSeq, "). Continuing — depth is absolute."));
910
+ // endSeq=0 is a sentinel meaning "no sequence tracking" always apply.
911
+ // Non-zero endSeq <= state.seq means this frame was already applied.
912
+ if (endSeq !== 0 && endSeq <= state.seq) return "continue";
913
+ // Gap detection: warn and trigger resync for that specific market.
914
+ if (state.seq > 0 && startSeq > 0 && startSeq > state.seq + 1) {
915
+ console.warn("[MarketDepthSyncClient] gap detected tokenId=".concat(frame.tokenId, " ") + "expected=".concat(state.seq + 1, " got=").concat(startSeq, " resyncing market ").concat(marketId));
916
+ // Re-subscribe to get a fresh snapshot (clears state, re-sends subscribe)
917
+ var tokenIds = _this1.activeSubscriptions.get(marketId);
918
+ if (tokenIds && _this1.ws && _this1.ws.readyState === WebSocket.OPEN) {
919
+ _this1._sendSubscribeMarket(marketId, tokenIds);
920
+ }
921
+ return {
922
+ v: void void 0
923
+ };
759
924
  }
925
+ // Stop processing stale queue; new snapshot will restart it
760
926
  var emittedLevels = [];
761
927
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
762
928
  try {
763
929
  for(var _iterator = frame.levels[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
764
930
  var lv = _step.value;
765
- // Per-level dedup against state.seq guards against re-applying
766
- // levels from a partially-applied previous frame.
767
931
  var lvSeq = parseInt(lv.seq, 10);
768
- if (lvSeq <= state.seq) continue;
932
+ // lvSeq=0 means no sequence → always apply (same sentinel logic as endSeq)
933
+ if (lvSeq !== 0 && lvSeq <= state.seq) continue;
769
934
  _this1.applyLevel(lv, state);
770
935
  emittedLevels.push({
771
936
  side: lv.side,
@@ -787,32 +952,44 @@ function scheduleDeferred(callback) {
787
952
  }
788
953
  }
789
954
  }
790
- state.seq = endSeq;
955
+ // Advance seq only when endSeq is meaningful
956
+ if (endSeq > state.seq) state.seq = endSeq;
791
957
  if (emittedLevels.length === 0) return "continue";
958
+ var update = {
959
+ tokenId: frame.tokenId,
960
+ levels: emittedLevels,
961
+ bestBid: state.bestBid,
962
+ bestAsk: state.bestAsk,
963
+ lastPrice: state.lastPrice,
964
+ seq: state.seq,
965
+ startSeq: startSeq,
966
+ endSeq: endSeq
967
+ };
792
968
  _this1.deltaListeners.forEach(function(listener) {
793
969
  try {
794
- var update = {
795
- tokenId: frame.tokenId,
796
- levels: emittedLevels,
797
- bestBid: state.bestBid,
798
- bestAsk: state.bestAsk,
799
- lastPrice: state.lastPrice,
800
- seq: endSeq,
801
- startSeq: startSeq,
802
- endSeq: endSeq
803
- };
804
- listener(_this.config.marketId, update);
805
- } catch (error) {
806
- console.error("Error in delta listener:", error);
970
+ listener(marketId, update);
971
+ } catch (e) {
972
+ console.error(e);
807
973
  }
808
974
  });
809
975
  };
810
- while(this.frameQueue.length > 0)_this1 = this, _loop();
976
+ queue = this.frameQueues.get(marketId);
977
+ if (!queue) return [
978
+ 2
979
+ ];
980
+ while(queue.length > 0){
981
+ _ret = (_this1 = this, _loop());
982
+ if (_type_of(_ret) === "object") return [
983
+ 2,
984
+ _ret.v
985
+ ];
986
+ }
811
987
  } finally{
812
- this.isProcessing = false;
813
- if (this.frameQueue.length > 0) {
988
+ this.processingMarkets.delete(marketId);
989
+ queue1 = this.frameQueues.get(marketId);
990
+ if (queue1 && queue1.length > 0) {
814
991
  scheduleDeferred(function() {
815
- void _this.processFrameQueue();
992
+ void _this.processFrameQueue(marketId);
816
993
  });
817
994
  }
818
995
  }
@@ -834,7 +1011,6 @@ function scheduleDeferred(callback) {
834
1011
  } else {
835
1012
  map.set(key, level.depth);
836
1013
  }
837
- // Recalculate best prices from current state
838
1014
  if (level.side === "bid") {
839
1015
  var _Math;
840
1016
  var bidPrices = Array.from(state.bids.keys()).map(function(p) {
@@ -858,186 +1034,190 @@ function scheduleDeferred(callback) {
858
1034
  this.statusListeners.forEach(function(listener) {
859
1035
  try {
860
1036
  listener(status);
861
- } catch (error) {
862
- console.error("Error in status listener:", error);
1037
+ } catch (e) {
1038
+ console.error(e);
863
1039
  }
864
1040
  });
865
1041
  }
866
1042
  },
867
1043
  {
868
- // Public API
869
- /** Returns the current connection lifecycle state. */ key: "getStatus",
870
- value: function getStatus() {
871
- return this.status;
872
- }
873
- },
874
- {
875
- /** Returns true once initial depth snapshots have been received. */ key: "isSynced",
876
- value: function isSynced() {
877
- return this.status === "synced";
878
- }
879
- },
880
- {
881
- /** Returns all token ids currently tracked by the client. */ key: "getTokenIds",
882
- value: function getTokenIds() {
883
- return Array.from(this.tokenStates.keys());
884
- }
885
- },
886
- {
887
- /** Returns raw internal token state for advanced integrations. */ key: "getTokenState",
888
- value: function getTokenState(tokenId) {
889
- return this.tokenStates.get(tokenId);
890
- }
891
- },
892
- {
893
- /** Returns the current bid ladder for a token, sorted from highest to lowest price. */ key: "getBids",
894
- value: function getBids(tokenId) {
895
- var state = this.tokenStates.get(tokenId);
896
- if (!state) return [];
897
- return Array.from(state.bids.entries()).map(function(param) {
898
- var _param = _sliced_to_array(param, 2), price = _param[0], depth = _param[1];
899
- return {
900
- price: price,
901
- depth: depth
902
- };
903
- }).sort(function(a, b) {
904
- return parseFloat(b.price) - parseFloat(a.price);
905
- });
906
- }
907
- },
908
- {
909
- // Highest first
910
- /** Returns the current ask ladder for a token, sorted from lowest to highest price. */ key: "getAsks",
911
- value: function getAsks(tokenId) {
912
- var state = this.tokenStates.get(tokenId);
913
- if (!state) return [];
914
- return Array.from(state.asks.entries()).map(function(param) {
915
- var _param = _sliced_to_array(param, 2), price = _param[0], depth = _param[1];
916
- return {
917
- price: price,
918
- depth: depth
919
- };
920
- }).sort(function(a, b) {
921
- return parseFloat(a.price) - parseFloat(b.price);
922
- });
923
- }
924
- },
925
- {
926
- // Lowest first
927
- /** Returns the best bid price for a token, if known. */ key: "getBestBid",
928
- value: function getBestBid(tokenId) {
929
- var _ref;
930
- var _this_tokenStates_get;
931
- return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.bestBid) !== null && _ref !== void 0 ? _ref : null;
1044
+ key: "setupVisibilityChangeHandler",
1045
+ value: // ---------------------------------------------------------------------------
1046
+ // Private reconnection & heartbeat
1047
+ // ---------------------------------------------------------------------------
1048
+ function setupVisibilityChangeHandler() {
1049
+ var _this = this;
1050
+ if (typeof document === "undefined") return;
1051
+ this.removeVisibilityChangeHandler();
1052
+ this.visibilityChangeHandler = function() {
1053
+ if (document.visibilityState === "visible" && _this.shouldBeConnected) {
1054
+ _this.checkConnectionHealth();
1055
+ }
1056
+ };
1057
+ document.addEventListener("visibilitychange", this.visibilityChangeHandler);
932
1058
  }
933
1059
  },
934
1060
  {
935
- /** Returns the best ask price for a token, if known. */ key: "getBestAsk",
936
- value: function getBestAsk(tokenId) {
937
- var _ref;
938
- var _this_tokenStates_get;
939
- return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.bestAsk) !== null && _ref !== void 0 ? _ref : null;
1061
+ key: "removeVisibilityChangeHandler",
1062
+ value: function removeVisibilityChangeHandler() {
1063
+ if (typeof document === "undefined") return;
1064
+ if (this.visibilityChangeHandler) {
1065
+ document.removeEventListener("visibilitychange", this.visibilityChangeHandler);
1066
+ this.visibilityChangeHandler = null;
1067
+ }
940
1068
  }
941
1069
  },
942
1070
  {
943
- /** Returns the latest trade price tracked for a token, if any. */ key: "getLastPrice",
944
- value: function getLastPrice(tokenId) {
945
- var _ref;
946
- var _this_tokenStates_get;
947
- return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.lastPrice) !== null && _ref !== void 0 ? _ref : null;
1071
+ key: "checkConnectionHealth",
1072
+ value: function checkConnectionHealth() {
1073
+ var _this = this;
1074
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
1075
+ this.handleConnectionLost();
1076
+ return;
1077
+ }
1078
+ try {
1079
+ this.ws.send(JSON.stringify({
1080
+ type: "ping"
1081
+ }));
1082
+ this.clearHeartbeatTimeout();
1083
+ this.heartbeatTimeoutId = setTimeout(function() {
1084
+ _this.handleConnectionLost();
1085
+ }, this.config.heartbeatTimeoutMs);
1086
+ } catch (unused) {
1087
+ this.handleConnectionLost();
1088
+ }
948
1089
  }
949
1090
  },
950
1091
  {
951
- /** Returns the latest applied sequence id for a token. */ key: "getSeq",
952
- value: function getSeq(tokenId) {
953
- var _ref;
954
- var _this_tokenStates_get;
955
- return (_ref = (_this_tokenStates_get = this.tokenStates.get(tokenId)) === null || _this_tokenStates_get === void 0 ? void 0 : _this_tokenStates_get.seq) !== null && _ref !== void 0 ? _ref : 0;
1092
+ key: "handleConnectionLost",
1093
+ value: function handleConnectionLost() {
1094
+ this.stopHeartbeat();
1095
+ if (this.ws) {
1096
+ this.ws.onclose = null;
1097
+ this.ws.close();
1098
+ this.ws = null;
1099
+ }
1100
+ this.setStatus("disconnected");
1101
+ if (this.shouldBeConnected) {
1102
+ this.scheduleReconnect();
1103
+ }
956
1104
  }
957
1105
  },
958
1106
  {
959
- /** Returns the current bid-ask spread for a token when both sides are available. */ key: "getSpread",
960
- value: function getSpread(tokenId) {
961
- var state = this.tokenStates.get(tokenId);
962
- if (!state || !state.bestBid || !state.bestAsk) return null;
963
- return parseFloat(state.bestAsk) - parseFloat(state.bestBid);
1107
+ key: "startHeartbeat",
1108
+ value: function startHeartbeat() {
1109
+ var _this = this;
1110
+ this.stopHeartbeat();
1111
+ this.heartbeatIntervalId = setInterval(function() {
1112
+ if (!_this.ws || _this.ws.readyState !== WebSocket.OPEN) return;
1113
+ try {
1114
+ _this.ws.send(JSON.stringify({
1115
+ type: "ping"
1116
+ }));
1117
+ _this.clearHeartbeatTimeout();
1118
+ _this.heartbeatTimeoutId = setTimeout(function() {
1119
+ _this.handleConnectionLost();
1120
+ }, _this.config.heartbeatTimeoutMs);
1121
+ } catch (unused) {
1122
+ _this.handleConnectionLost();
1123
+ }
1124
+ }, this.config.heartbeatIntervalMs);
964
1125
  }
965
1126
  },
966
1127
  {
967
- /** Returns the depth resting at one exact price level on a given side. */ key: "getDepthAtPrice",
968
- value: function getDepthAtPrice(tokenId, side, price) {
969
- var state = this.tokenStates.get(tokenId);
970
- if (!state) return null;
971
- var map = side === "bid" ? state.bids : state.asks;
972
- return map.get(normalizePrice(price)) || null;
1128
+ key: "stopHeartbeat",
1129
+ value: function stopHeartbeat() {
1130
+ if (this.heartbeatIntervalId) {
1131
+ clearInterval(this.heartbeatIntervalId);
1132
+ this.heartbeatIntervalId = null;
1133
+ }
1134
+ this.clearHeartbeatTimeout();
973
1135
  }
974
1136
  },
975
1137
  {
976
- // Event listeners
977
- /** Registers a listener for connection lifecycle updates. */ key: "onStatus",
978
- value: function onStatus(callback) {
979
- var _this = this;
980
- this.statusListeners.add(callback);
981
- return function() {
982
- return _this.statusListeners.delete(callback);
983
- };
1138
+ key: "clearHeartbeatTimeout",
1139
+ value: function clearHeartbeatTimeout() {
1140
+ if (this.heartbeatTimeoutId) {
1141
+ clearTimeout(this.heartbeatTimeoutId);
1142
+ this.heartbeatTimeoutId = null;
1143
+ }
984
1144
  }
985
1145
  },
986
1146
  {
987
- /** Registers a listener that receives full snapshots for all subscribed tokens. */ key: "onSnapshot",
988
- value: function onSnapshot(callback) {
1147
+ key: "scheduleReconnect",
1148
+ value: function scheduleReconnect() {
989
1149
  var _this = this;
990
- this.snapshotListeners.add(callback);
991
- return function() {
992
- return _this.snapshotListeners.delete(callback);
993
- };
1150
+ if (!this.shouldBeConnected) return;
1151
+ var maxAttempts = this.config.maxReconnectAttempts;
1152
+ if (this.reconnectAttempts >= maxAttempts) {
1153
+ console.error("[MarketDepthSyncClient] max reconnect attempts (".concat(maxAttempts, ") reached"));
1154
+ return;
1155
+ }
1156
+ var baseDelay = this.config.initialReconnectDelayMs;
1157
+ var maxDelay = this.config.maxReconnectDelayMs;
1158
+ var delay = Math.min(baseDelay * Math.pow(2, this.reconnectAttempts) + Math.random() * 1000, maxDelay);
1159
+ this.reconnectAttempts++;
1160
+ this.setStatus("recovering");
1161
+ this.clearReconnectTimeout();
1162
+ this.reconnectTimeoutId = setTimeout(function() {
1163
+ void _this.performReconnect();
1164
+ }, delay);
994
1165
  }
995
1166
  },
996
1167
  {
997
- /** Registers a listener for normalized incremental depth updates. */ key: "onDelta",
998
- value: function onDelta(callback) {
999
- var _this = this;
1000
- this.deltaListeners.add(callback);
1001
- return function() {
1002
- return _this.deltaListeners.delete(callback);
1003
- };
1168
+ key: "clearReconnectTimeout",
1169
+ value: function clearReconnectTimeout() {
1170
+ if (this.reconnectTimeoutId) {
1171
+ clearTimeout(this.reconnectTimeoutId);
1172
+ this.reconnectTimeoutId = null;
1173
+ }
1004
1174
  }
1005
1175
  },
1006
1176
  {
1007
- key: "disconnect",
1008
- value: /** Closes the websocket and stops automatic reconnection attempts. */ function disconnect() {
1177
+ key: "performReconnect",
1178
+ value: function performReconnect() {
1009
1179
  return _async_to_generator(function() {
1180
+ var error;
1010
1181
  return _ts_generator(this, function(_state) {
1011
- // Mark that we intentionally want to disconnect
1012
- this.shouldBeConnected = false;
1013
- this.isProcessing = true;
1014
- // Clean up all timers and handlers
1015
- this.stopHeartbeat();
1016
- this.clearReconnectTimeout();
1017
- this.removeVisibilityChangeHandler();
1018
- this.reconnectAttempts = 0;
1019
- if (this.ws) {
1020
- // Prevent onclose from triggering reconnect
1021
- this.ws.onclose = null;
1022
- // Send unsubscribe before closing
1023
- if (this.ws.readyState === WebSocket.OPEN) {
1024
- try {
1025
- this.ws.send(JSON.stringify({
1026
- type: "unsubscribe_market",
1027
- marketId: this.config.marketId
1028
- }));
1029
- } catch (unused) {}
1030
- }
1031
- // Ignore send errors during disconnect
1032
- this.ws.close();
1033
- this.ws = null;
1182
+ switch(_state.label){
1183
+ case 0:
1184
+ if (!this.shouldBeConnected) return [
1185
+ 2
1186
+ ];
1187
+ _state.label = 1;
1188
+ case 1:
1189
+ _state.trys.push([
1190
+ 1,
1191
+ 3,
1192
+ ,
1193
+ 4
1194
+ ]);
1195
+ // Clear snapshot state — will be re-populated from fresh snapshots
1196
+ this.tokenStates.clear();
1197
+ this.frameQueues.clear();
1198
+ this.processingMarkets.clear();
1199
+ return [
1200
+ 4,
1201
+ this.connect()
1202
+ ];
1203
+ case 2:
1204
+ _state.sent();
1205
+ return [
1206
+ 3,
1207
+ 4
1208
+ ];
1209
+ case 3:
1210
+ error = _state.sent();
1211
+ console.error("[MarketDepthSyncClient] reconnection failed:", error);
1212
+ return [
1213
+ 3,
1214
+ 4
1215
+ ];
1216
+ case 4:
1217
+ return [
1218
+ 2
1219
+ ];
1034
1220
  }
1035
- this.frameQueue = [];
1036
- this.tokenStates.clear();
1037
- this.setStatus("disconnected");
1038
- return [
1039
- 2
1040
- ];
1041
1221
  });
1042
1222
  }).call(this);
1043
1223
  }