@taphubhq/sdk-core 0.24.0 → 0.24.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.
package/dist/index.cjs CHANGED
@@ -64,6 +64,35 @@ __export(index_exports, {
64
64
  });
65
65
  module.exports = __toCommonJS(index_exports);
66
66
 
67
+ // src/clock/ClockSync.ts
68
+ var ClockSync = class {
69
+ #offsetMs = 0;
70
+ #synced = false;
71
+ /** True once an offset has been recorded. */
72
+ get synced() {
73
+ return this.#synced;
74
+ }
75
+ /**
76
+ * Milliseconds to add to the client clock to reach the server clock.
77
+ * Returns 0 until the first successful sync.
78
+ */
79
+ getOffset() {
80
+ return this.#offsetMs;
81
+ }
82
+ /**
83
+ * Record a single clock sample from a round-trip that returned the server time.
84
+ * No-op if already synced (set-once), or if any input is not a finite number — a
85
+ * missing/garbage `serverTime` must never corrupt the offset.
86
+ */
87
+ recordSync(sample) {
88
+ if (this.#synced) return;
89
+ const { t0, serverTime, t1 } = sample;
90
+ if (!Number.isFinite(t0) || !Number.isFinite(serverTime) || !Number.isFinite(t1)) return;
91
+ this.#offsetMs = Math.round(serverTime + (t1 - t0) / 2 - t1);
92
+ this.#synced = true;
93
+ }
94
+ };
95
+
67
96
  // src/errors/index.ts
68
97
  var TaphubError = class _TaphubError extends Error {
69
98
  code;
@@ -518,8 +547,10 @@ var isCancelled = (bid) => bid.status === "cancelled";
518
547
  // src/modules/bid/index.ts
519
548
  var BidModule = class {
520
549
  #graphql;
550
+ #getClockOffset;
521
551
  constructor(deps) {
522
552
  this.#graphql = deps.graphql;
553
+ this.#getClockOffset = deps.getClockOffset;
523
554
  }
524
555
  async placeBid(input, opts) {
525
556
  const variables = {
@@ -537,6 +568,12 @@ var BidModule = class {
537
568
  coefficient: input.coefficient,
538
569
  amount: input.amount,
539
570
  slippage: input.slippage,
571
+ // bid-260619-client-time-meta: auto-stamp the client's RAW send time and the
572
+ // measured clock offset so the server can record skew-corrected latency into
573
+ // bid.meta. Stamped here at dispatch (not caller-supplied) so `ct` is the actual
574
+ // send moment. The server treats both as untrusted tracing data only.
575
+ ct: Date.now(),
576
+ clockOffset: this.#getClockOffset(),
540
577
  // QA-only late-bid passthrough (bid-260610). Omitted entirely when absent
541
578
  // so no `bidToken: null` is sent. sdk-react sets it from localStorage.
542
579
  ...input.bidToken ? { bidToken: input.bidToken } : {}
@@ -930,6 +967,7 @@ function normalisePairInfo(node) {
930
967
 
931
968
  // src/modules/pair/queries.ts
932
969
  var PAIR_QUERY = `query AgencyPair($pairId: ID!) {
970
+ serverTime
933
971
  agencyPair(pairId: $pairId) {
934
972
  id pair { id pair source thumb } status createdAt
935
973
  config {
@@ -959,8 +997,10 @@ var BUILDER_AVAILABLE_GAME_PAIRS_QUERY = `query BuilderAvailableGamePairs($gamep
959
997
  // src/modules/pair/index.ts
960
998
  var PairModule = class {
961
999
  #graphql;
1000
+ #clockSync;
962
1001
  constructor(deps) {
963
1002
  this.#graphql = deps.graphql;
1003
+ this.#clockSync = deps.clockSync;
964
1004
  }
965
1005
  // REVIEW[bid-260602-v2]: keyed by pairId (the #2 game_pairs.id, e.g. "grid-ETH-USD").
966
1006
  // Dropped the `pair` symbol arg + `gameplaySlug` option — the gameplay is already
@@ -968,6 +1008,7 @@ var PairModule = class {
968
1008
  async get(pairId, opts) {
969
1009
  const variables = { pairId };
970
1010
  let body;
1011
+ const t0 = Date.now();
971
1012
  try {
972
1013
  body = await this.#graphql.request(PAIR_QUERY, variables, opts);
973
1014
  } catch (err) {
@@ -979,6 +1020,7 @@ var PairModule = class {
979
1020
  }
980
1021
  throw err;
981
1022
  }
1023
+ this.#clockSync.recordSync({ t0, serverTime: body.serverTime, t1: Date.now() });
982
1024
  if (body.agencyPair === null) {
983
1025
  throw new TaphubValidationError("Game not found for pair", {
984
1026
  code: "GameNotFound",
@@ -1983,20 +2025,9 @@ function classifyNetworkLevel(rtt) {
1983
2025
  // src/network/RttSmoother.ts
1984
2026
  var EMA_ALPHA = 0.25;
1985
2027
  var OUTLIER_MULTIPLIER = 3;
1986
- var DEBOUNCE_DEGRADE = 2;
1987
- var DEBOUNCE_IMPROVE = 5;
1988
- var LEVEL_RANK = {
1989
- good: 0,
1990
- fair: 1,
1991
- poor: 2,
1992
- offline: 3
1993
- };
1994
2028
  var RttSmoother = class {
1995
2029
  ema = 0;
1996
2030
  level = "good";
1997
- committed = "good";
1998
- candidate = "good";
1999
- candidateCount = 0;
2000
2031
  overrideOffline = false;
2001
2032
  consecutiveOutliers = 0;
2002
2033
  add(rtt) {
@@ -2008,23 +2039,7 @@ var RttSmoother = class {
2008
2039
  }
2009
2040
  const coldStart = this.ema === 0;
2010
2041
  this.ema = coldStart ? rtt : EMA_ALPHA * rtt + (1 - EMA_ALPHA) * this.ema;
2011
- const next = classifyNetworkLevel(this.ema);
2012
- if (coldStart) {
2013
- this.committed = next;
2014
- this.candidate = next;
2015
- this.candidateCount = 1;
2016
- } else {
2017
- if (next === this.candidate) {
2018
- this.candidateCount += 1;
2019
- } else {
2020
- this.candidate = next;
2021
- this.candidateCount = 1;
2022
- }
2023
- if (this.candidateCount >= this.requiredSamplesFor(next)) {
2024
- this.committed = next;
2025
- }
2026
- }
2027
- this.level = this.overrideOffline ? "offline" : this.committed;
2042
+ this.level = this.overrideOffline ? "offline" : classifyNetworkLevel(this.ema);
2028
2043
  }
2029
2044
  forceOffline() {
2030
2045
  this.overrideOffline = true;
@@ -2032,20 +2047,14 @@ var RttSmoother = class {
2032
2047
  }
2033
2048
  releaseOffline() {
2034
2049
  this.overrideOffline = false;
2035
- this.level = this.committed;
2050
+ this.level = classifyNetworkLevel(this.ema);
2036
2051
  }
2037
2052
  reset() {
2038
2053
  this.ema = 0;
2039
- this.committed = "good";
2040
- this.candidate = "good";
2041
- this.candidateCount = 0;
2042
2054
  this.consecutiveOutliers = 0;
2043
2055
  this.overrideOffline = false;
2044
2056
  this.level = "good";
2045
2057
  }
2046
- requiredSamplesFor(next) {
2047
- return LEVEL_RANK[next] > LEVEL_RANK[this.committed] ? DEBOUNCE_DEGRADE : DEBOUNCE_IMPROVE;
2048
- }
2049
2058
  };
2050
2059
 
2051
2060
  // src/network/NetworkQualityMonitor.ts
@@ -2072,7 +2081,6 @@ var NetworkQualityMonitor = class {
2072
2081
  smoother = new RttSmoother();
2073
2082
  mqttConnected = false;
2074
2083
  mqttDisconnectedAt = null;
2075
- committedNetwork = "good";
2076
2084
  committedBackend = "ok";
2077
2085
  // The last snapshot we emitted, used to decide whether the next recompute is
2078
2086
  // worth emitting. Seeded with the initial default so the first real change
@@ -2150,7 +2158,6 @@ var NetworkQualityMonitor = class {
2150
2158
  this.smoother.reset();
2151
2159
  this.mqttConnected = false;
2152
2160
  this.mqttDisconnectedAt = null;
2153
- this.committedNetwork = "good";
2154
2161
  this.committedBackend = "ok";
2155
2162
  this.emitIfChanged(this.snapshot());
2156
2163
  }
@@ -2214,26 +2221,12 @@ var NetworkQualityMonitor = class {
2214
2221
  const useConnection = realSamples.length < COLD_START_REAL_SAMPLE_THRESHOLD;
2215
2222
  const effectiveSamples = useConnection ? allSamples : realSamples;
2216
2223
  const httpSamples = effectiveSamples.filter((s) => isHttpSource(s.source));
2217
- const backend = classifyBackendHealth(httpSamples);
2218
- const metrics = this.deriveMetricsFrom(effectiveSamples);
2219
- const candidate = classifyNetworkLevel(metrics.emaForLevel);
2220
- let nextNetwork = this.smoother.level;
2221
- if (effectiveSamples.length === 0) {
2222
- nextNetwork = this.committedNetwork;
2223
- } else if (this.smoother.ema > 0) {
2224
- nextNetwork = this.smoother.level;
2225
- } else {
2226
- nextNetwork = candidate;
2227
- }
2224
+ this.committedBackend = classifyBackendHealth(httpSamples);
2228
2225
  if (this.isHardOffline()) {
2229
2226
  this.smoother.forceOffline();
2230
- nextNetwork = "offline";
2231
2227
  } else if (this.smoother.level === "offline") {
2232
2228
  this.smoother.releaseOffline();
2233
- nextNetwork = this.smoother.level;
2234
2229
  }
2235
- this.committedNetwork = nextNetwork;
2236
- this.committedBackend = backend;
2237
2230
  this.emitIfChanged(this.snapshot());
2238
2231
  }
2239
2232
  // Emits network:change when any *displayed* field of the snapshot moves. This
@@ -2294,10 +2287,12 @@ var NetworkQualityMonitor = class {
2294
2287
  snapshot() {
2295
2288
  const samples = this.allHttpAndMqttSamples();
2296
2289
  const metrics = this.deriveMetricsFrom(samples);
2290
+ const rtt = Math.round(metrics.emaForLevel);
2291
+ const network = this.smoother.level === "offline" ? "offline" : classifyNetworkLevel(rtt);
2297
2292
  return {
2298
- network: this.committedNetwork,
2293
+ network,
2299
2294
  backend: this.committedBackend,
2300
- rtt: Math.round(metrics.emaForLevel),
2295
+ rtt,
2301
2296
  jitter: Math.round(metrics.jitter),
2302
2297
  lossRate: metrics.lossRate,
2303
2298
  mqttConnected: this.mqttConnected,
@@ -2905,8 +2900,12 @@ var TaphubClient = class {
2905
2900
  this.user.clearCurrencies();
2906
2901
  }
2907
2902
  });
2908
- this.pair = new PairModule({ graphql: this.#graphql });
2909
- this.bid = new BidModule({ graphql: this.#graphql });
2903
+ const clockSync = new ClockSync();
2904
+ this.pair = new PairModule({ graphql: this.#graphql, clockSync });
2905
+ this.bid = new BidModule({
2906
+ graphql: this.#graphql,
2907
+ getClockOffset: () => clockSync.getOffset()
2908
+ });
2910
2909
  this.leaderboard = new LeaderboardModule({ graphql: this.#graphql });
2911
2910
  this.agencyPairs = new AgencyPairModule({ graphql: this.#graphql });
2912
2911
  this.locale = new LocaleModule({ graphql: this.#graphql });
package/dist/index.d.mts CHANGED
@@ -270,6 +270,7 @@ declare const isCancelled: (bid: Bid) => boolean;
270
270
 
271
271
  interface BidModuleDeps {
272
272
  graphql: GraphQLTransport;
273
+ getClockOffset: () => number;
273
274
  }
274
275
  declare class BidModule {
275
276
  #private;
@@ -496,6 +497,44 @@ declare class LocaleModule {
496
497
  }): Promise<LocaleRefreshResult>;
497
498
  }
498
499
 
500
+ /**
501
+ * ClockSync estimates the offset between the client clock and the server clock, so that
502
+ * timestamps the client reports (e.g. a bid's send time) can be expressed on the server's
503
+ * timeline and latency measurements are not distorted by client clock skew.
504
+ *
505
+ * The offset is measured ONCE per session (set-once) from a single round-trip whose
506
+ * response carries the server time, using Cristian's algorithm:
507
+ *
508
+ * offset = serverTime + (t1 - t0) / 2 - t1
509
+ *
510
+ * where `t0` / `t1` are the client clock immediately before / after the request. `offset`
511
+ * is the number of milliseconds to ADD to the client clock to obtain the server clock.
512
+ *
513
+ * This is adequate for millisecond-grade latency tracing (the `bid-260619-server-time-sync`
514
+ * capability); it is intentionally NOT a high-precision NTP implementation, and it does not
515
+ * re-sync to correct drift.
516
+ */
517
+ declare class ClockSync {
518
+ #private;
519
+ /** True once an offset has been recorded. */
520
+ get synced(): boolean;
521
+ /**
522
+ * Milliseconds to add to the client clock to reach the server clock.
523
+ * Returns 0 until the first successful sync.
524
+ */
525
+ getOffset(): number;
526
+ /**
527
+ * Record a single clock sample from a round-trip that returned the server time.
528
+ * No-op if already synced (set-once), or if any input is not a finite number — a
529
+ * missing/garbage `serverTime` must never corrupt the offset.
530
+ */
531
+ recordSync(sample: {
532
+ t0: number;
533
+ serverTime: number;
534
+ t1: number;
535
+ }): void;
536
+ }
537
+
499
538
  interface Pair {
500
539
  id: string;
501
540
  pair: string;
@@ -572,6 +611,7 @@ interface Candle {
572
611
 
573
612
  interface PairModuleDeps {
574
613
  graphql: GraphQLTransport;
614
+ clockSync: ClockSync;
575
615
  }
576
616
  declare class PairModule {
577
617
  #private;
@@ -1099,7 +1139,6 @@ declare class NetworkQualityMonitor {
1099
1139
  private readonly smoother;
1100
1140
  private mqttConnected;
1101
1141
  private mqttDisconnectedAt;
1102
- private committedNetwork;
1103
1142
  private committedBackend;
1104
1143
  private lastEmitted;
1105
1144
  private tickHandle;
package/dist/index.d.ts CHANGED
@@ -270,6 +270,7 @@ declare const isCancelled: (bid: Bid) => boolean;
270
270
 
271
271
  interface BidModuleDeps {
272
272
  graphql: GraphQLTransport;
273
+ getClockOffset: () => number;
273
274
  }
274
275
  declare class BidModule {
275
276
  #private;
@@ -496,6 +497,44 @@ declare class LocaleModule {
496
497
  }): Promise<LocaleRefreshResult>;
497
498
  }
498
499
 
500
+ /**
501
+ * ClockSync estimates the offset between the client clock and the server clock, so that
502
+ * timestamps the client reports (e.g. a bid's send time) can be expressed on the server's
503
+ * timeline and latency measurements are not distorted by client clock skew.
504
+ *
505
+ * The offset is measured ONCE per session (set-once) from a single round-trip whose
506
+ * response carries the server time, using Cristian's algorithm:
507
+ *
508
+ * offset = serverTime + (t1 - t0) / 2 - t1
509
+ *
510
+ * where `t0` / `t1` are the client clock immediately before / after the request. `offset`
511
+ * is the number of milliseconds to ADD to the client clock to obtain the server clock.
512
+ *
513
+ * This is adequate for millisecond-grade latency tracing (the `bid-260619-server-time-sync`
514
+ * capability); it is intentionally NOT a high-precision NTP implementation, and it does not
515
+ * re-sync to correct drift.
516
+ */
517
+ declare class ClockSync {
518
+ #private;
519
+ /** True once an offset has been recorded. */
520
+ get synced(): boolean;
521
+ /**
522
+ * Milliseconds to add to the client clock to reach the server clock.
523
+ * Returns 0 until the first successful sync.
524
+ */
525
+ getOffset(): number;
526
+ /**
527
+ * Record a single clock sample from a round-trip that returned the server time.
528
+ * No-op if already synced (set-once), or if any input is not a finite number — a
529
+ * missing/garbage `serverTime` must never corrupt the offset.
530
+ */
531
+ recordSync(sample: {
532
+ t0: number;
533
+ serverTime: number;
534
+ t1: number;
535
+ }): void;
536
+ }
537
+
499
538
  interface Pair {
500
539
  id: string;
501
540
  pair: string;
@@ -572,6 +611,7 @@ interface Candle {
572
611
 
573
612
  interface PairModuleDeps {
574
613
  graphql: GraphQLTransport;
614
+ clockSync: ClockSync;
575
615
  }
576
616
  declare class PairModule {
577
617
  #private;
@@ -1099,7 +1139,6 @@ declare class NetworkQualityMonitor {
1099
1139
  private readonly smoother;
1100
1140
  private mqttConnected;
1101
1141
  private mqttDisconnectedAt;
1102
- private committedNetwork;
1103
1142
  private committedBackend;
1104
1143
  private lastEmitted;
1105
1144
  private tickHandle;
package/dist/index.js CHANGED
@@ -1,3 +1,32 @@
1
+ // src/clock/ClockSync.ts
2
+ var ClockSync = class {
3
+ #offsetMs = 0;
4
+ #synced = false;
5
+ /** True once an offset has been recorded. */
6
+ get synced() {
7
+ return this.#synced;
8
+ }
9
+ /**
10
+ * Milliseconds to add to the client clock to reach the server clock.
11
+ * Returns 0 until the first successful sync.
12
+ */
13
+ getOffset() {
14
+ return this.#offsetMs;
15
+ }
16
+ /**
17
+ * Record a single clock sample from a round-trip that returned the server time.
18
+ * No-op if already synced (set-once), or if any input is not a finite number — a
19
+ * missing/garbage `serverTime` must never corrupt the offset.
20
+ */
21
+ recordSync(sample) {
22
+ if (this.#synced) return;
23
+ const { t0, serverTime, t1 } = sample;
24
+ if (!Number.isFinite(t0) || !Number.isFinite(serverTime) || !Number.isFinite(t1)) return;
25
+ this.#offsetMs = Math.round(serverTime + (t1 - t0) / 2 - t1);
26
+ this.#synced = true;
27
+ }
28
+ };
29
+
1
30
  // src/errors/index.ts
2
31
  var TaphubError = class _TaphubError extends Error {
3
32
  code;
@@ -452,8 +481,10 @@ var isCancelled = (bid) => bid.status === "cancelled";
452
481
  // src/modules/bid/index.ts
453
482
  var BidModule = class {
454
483
  #graphql;
484
+ #getClockOffset;
455
485
  constructor(deps) {
456
486
  this.#graphql = deps.graphql;
487
+ this.#getClockOffset = deps.getClockOffset;
457
488
  }
458
489
  async placeBid(input, opts) {
459
490
  const variables = {
@@ -471,6 +502,12 @@ var BidModule = class {
471
502
  coefficient: input.coefficient,
472
503
  amount: input.amount,
473
504
  slippage: input.slippage,
505
+ // bid-260619-client-time-meta: auto-stamp the client's RAW send time and the
506
+ // measured clock offset so the server can record skew-corrected latency into
507
+ // bid.meta. Stamped here at dispatch (not caller-supplied) so `ct` is the actual
508
+ // send moment. The server treats both as untrusted tracing data only.
509
+ ct: Date.now(),
510
+ clockOffset: this.#getClockOffset(),
474
511
  // QA-only late-bid passthrough (bid-260610). Omitted entirely when absent
475
512
  // so no `bidToken: null` is sent. sdk-react sets it from localStorage.
476
513
  ...input.bidToken ? { bidToken: input.bidToken } : {}
@@ -864,6 +901,7 @@ function normalisePairInfo(node) {
864
901
 
865
902
  // src/modules/pair/queries.ts
866
903
  var PAIR_QUERY = `query AgencyPair($pairId: ID!) {
904
+ serverTime
867
905
  agencyPair(pairId: $pairId) {
868
906
  id pair { id pair source thumb } status createdAt
869
907
  config {
@@ -893,8 +931,10 @@ var BUILDER_AVAILABLE_GAME_PAIRS_QUERY = `query BuilderAvailableGamePairs($gamep
893
931
  // src/modules/pair/index.ts
894
932
  var PairModule = class {
895
933
  #graphql;
934
+ #clockSync;
896
935
  constructor(deps) {
897
936
  this.#graphql = deps.graphql;
937
+ this.#clockSync = deps.clockSync;
898
938
  }
899
939
  // REVIEW[bid-260602-v2]: keyed by pairId (the #2 game_pairs.id, e.g. "grid-ETH-USD").
900
940
  // Dropped the `pair` symbol arg + `gameplaySlug` option — the gameplay is already
@@ -902,6 +942,7 @@ var PairModule = class {
902
942
  async get(pairId, opts) {
903
943
  const variables = { pairId };
904
944
  let body;
945
+ const t0 = Date.now();
905
946
  try {
906
947
  body = await this.#graphql.request(PAIR_QUERY, variables, opts);
907
948
  } catch (err) {
@@ -913,6 +954,7 @@ var PairModule = class {
913
954
  }
914
955
  throw err;
915
956
  }
957
+ this.#clockSync.recordSync({ t0, serverTime: body.serverTime, t1: Date.now() });
916
958
  if (body.agencyPair === null) {
917
959
  throw new TaphubValidationError("Game not found for pair", {
918
960
  code: "GameNotFound",
@@ -1917,20 +1959,9 @@ function classifyNetworkLevel(rtt) {
1917
1959
  // src/network/RttSmoother.ts
1918
1960
  var EMA_ALPHA = 0.25;
1919
1961
  var OUTLIER_MULTIPLIER = 3;
1920
- var DEBOUNCE_DEGRADE = 2;
1921
- var DEBOUNCE_IMPROVE = 5;
1922
- var LEVEL_RANK = {
1923
- good: 0,
1924
- fair: 1,
1925
- poor: 2,
1926
- offline: 3
1927
- };
1928
1962
  var RttSmoother = class {
1929
1963
  ema = 0;
1930
1964
  level = "good";
1931
- committed = "good";
1932
- candidate = "good";
1933
- candidateCount = 0;
1934
1965
  overrideOffline = false;
1935
1966
  consecutiveOutliers = 0;
1936
1967
  add(rtt) {
@@ -1942,23 +1973,7 @@ var RttSmoother = class {
1942
1973
  }
1943
1974
  const coldStart = this.ema === 0;
1944
1975
  this.ema = coldStart ? rtt : EMA_ALPHA * rtt + (1 - EMA_ALPHA) * this.ema;
1945
- const next = classifyNetworkLevel(this.ema);
1946
- if (coldStart) {
1947
- this.committed = next;
1948
- this.candidate = next;
1949
- this.candidateCount = 1;
1950
- } else {
1951
- if (next === this.candidate) {
1952
- this.candidateCount += 1;
1953
- } else {
1954
- this.candidate = next;
1955
- this.candidateCount = 1;
1956
- }
1957
- if (this.candidateCount >= this.requiredSamplesFor(next)) {
1958
- this.committed = next;
1959
- }
1960
- }
1961
- this.level = this.overrideOffline ? "offline" : this.committed;
1976
+ this.level = this.overrideOffline ? "offline" : classifyNetworkLevel(this.ema);
1962
1977
  }
1963
1978
  forceOffline() {
1964
1979
  this.overrideOffline = true;
@@ -1966,20 +1981,14 @@ var RttSmoother = class {
1966
1981
  }
1967
1982
  releaseOffline() {
1968
1983
  this.overrideOffline = false;
1969
- this.level = this.committed;
1984
+ this.level = classifyNetworkLevel(this.ema);
1970
1985
  }
1971
1986
  reset() {
1972
1987
  this.ema = 0;
1973
- this.committed = "good";
1974
- this.candidate = "good";
1975
- this.candidateCount = 0;
1976
1988
  this.consecutiveOutliers = 0;
1977
1989
  this.overrideOffline = false;
1978
1990
  this.level = "good";
1979
1991
  }
1980
- requiredSamplesFor(next) {
1981
- return LEVEL_RANK[next] > LEVEL_RANK[this.committed] ? DEBOUNCE_DEGRADE : DEBOUNCE_IMPROVE;
1982
- }
1983
1992
  };
1984
1993
 
1985
1994
  // src/network/NetworkQualityMonitor.ts
@@ -2006,7 +2015,6 @@ var NetworkQualityMonitor = class {
2006
2015
  smoother = new RttSmoother();
2007
2016
  mqttConnected = false;
2008
2017
  mqttDisconnectedAt = null;
2009
- committedNetwork = "good";
2010
2018
  committedBackend = "ok";
2011
2019
  // The last snapshot we emitted, used to decide whether the next recompute is
2012
2020
  // worth emitting. Seeded with the initial default so the first real change
@@ -2084,7 +2092,6 @@ var NetworkQualityMonitor = class {
2084
2092
  this.smoother.reset();
2085
2093
  this.mqttConnected = false;
2086
2094
  this.mqttDisconnectedAt = null;
2087
- this.committedNetwork = "good";
2088
2095
  this.committedBackend = "ok";
2089
2096
  this.emitIfChanged(this.snapshot());
2090
2097
  }
@@ -2148,26 +2155,12 @@ var NetworkQualityMonitor = class {
2148
2155
  const useConnection = realSamples.length < COLD_START_REAL_SAMPLE_THRESHOLD;
2149
2156
  const effectiveSamples = useConnection ? allSamples : realSamples;
2150
2157
  const httpSamples = effectiveSamples.filter((s) => isHttpSource(s.source));
2151
- const backend = classifyBackendHealth(httpSamples);
2152
- const metrics = this.deriveMetricsFrom(effectiveSamples);
2153
- const candidate = classifyNetworkLevel(metrics.emaForLevel);
2154
- let nextNetwork = this.smoother.level;
2155
- if (effectiveSamples.length === 0) {
2156
- nextNetwork = this.committedNetwork;
2157
- } else if (this.smoother.ema > 0) {
2158
- nextNetwork = this.smoother.level;
2159
- } else {
2160
- nextNetwork = candidate;
2161
- }
2158
+ this.committedBackend = classifyBackendHealth(httpSamples);
2162
2159
  if (this.isHardOffline()) {
2163
2160
  this.smoother.forceOffline();
2164
- nextNetwork = "offline";
2165
2161
  } else if (this.smoother.level === "offline") {
2166
2162
  this.smoother.releaseOffline();
2167
- nextNetwork = this.smoother.level;
2168
2163
  }
2169
- this.committedNetwork = nextNetwork;
2170
- this.committedBackend = backend;
2171
2164
  this.emitIfChanged(this.snapshot());
2172
2165
  }
2173
2166
  // Emits network:change when any *displayed* field of the snapshot moves. This
@@ -2228,10 +2221,12 @@ var NetworkQualityMonitor = class {
2228
2221
  snapshot() {
2229
2222
  const samples = this.allHttpAndMqttSamples();
2230
2223
  const metrics = this.deriveMetricsFrom(samples);
2224
+ const rtt = Math.round(metrics.emaForLevel);
2225
+ const network = this.smoother.level === "offline" ? "offline" : classifyNetworkLevel(rtt);
2231
2226
  return {
2232
- network: this.committedNetwork,
2227
+ network,
2233
2228
  backend: this.committedBackend,
2234
- rtt: Math.round(metrics.emaForLevel),
2229
+ rtt,
2235
2230
  jitter: Math.round(metrics.jitter),
2236
2231
  lossRate: metrics.lossRate,
2237
2232
  mqttConnected: this.mqttConnected,
@@ -2839,8 +2834,12 @@ var TaphubClient = class {
2839
2834
  this.user.clearCurrencies();
2840
2835
  }
2841
2836
  });
2842
- this.pair = new PairModule({ graphql: this.#graphql });
2843
- this.bid = new BidModule({ graphql: this.#graphql });
2837
+ const clockSync = new ClockSync();
2838
+ this.pair = new PairModule({ graphql: this.#graphql, clockSync });
2839
+ this.bid = new BidModule({
2840
+ graphql: this.#graphql,
2841
+ getClockOffset: () => clockSync.getOffset()
2842
+ });
2844
2843
  this.leaderboard = new LeaderboardModule({ graphql: this.#graphql });
2845
2844
  this.agencyPairs = new AgencyPairModule({ graphql: this.#graphql });
2846
2845
  this.locale = new LocaleModule({ graphql: this.#graphql });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@taphubhq/sdk-core",
3
- "version": "0.24.0",
3
+ "version": "0.24.2",
4
4
  "description": "Core SDK for building on the TabHub platform",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.cjs",