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