@dimcool/sdk 0.1.30 → 0.1.31

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/README.md CHANGED
@@ -2971,6 +2971,59 @@ const sdk = new SDK({
2971
2971
  These stores are updated by the `WsRouter` and are the single source of truth for
2972
2972
  realtime UI. Room membership is ref-counted and mapped to backend join/leave emits.
2973
2973
 
2974
+ ### `gameActionsStore` Selectors
2975
+
2976
+ Pure functions that extract derived state from `GameActionsStoreState` without any React dependency. Pass them to `useSdkSelector` in React or call them directly in Node.
2977
+
2978
+ #### `selectGameLifecycleState(gameState)`
2979
+
2980
+ Extracts the common lifecycle fields from any `GameStateResponse`. Accepts the state directly so it can be called after a stable `useSdkSelector` call — the selector itself returns the existing store reference, then you project outside it. This avoids creating a new object inside `getSnapshot`, which would break `useSyncExternalStore`'s referential-equality contract.
2981
+
2982
+ ```ts
2983
+ import { selectGameLifecycleState } from '@dimcool/sdk';
2984
+
2985
+ // Correct: selector returns stable store ref, projection happens outside
2986
+ const rawState = useSdkSelector(store, (s) => s.statesByGameId[gameId] ?? null);
2987
+ const lifecycle = selectGameLifecycleState(rawState);
2988
+ // { status, winnerId, betAmount, wonAmount, totalPotMinor, currentPlayerId }
2989
+ ```
2990
+
2991
+ #### `getCountdownDigit(gameId, nowMs)`
2992
+
2993
+ Returns the current pre-game countdown digit (`3 | 2 | 1`) or `null` when the buffer has elapsed. Works for all game types (turn-based: reads `bufferEndsAt`; RPS: reads `roundState.selectionEndsAt` during the `starting` phase).
2994
+
2995
+ ```ts
2996
+ const digit = sdk.gameActionsStore.getCountdownDigit(gameId, Date.now());
2997
+ ```
2998
+
2999
+ #### `getChessClockTimes(gameId, nowMs)` / `getTicTacToeClockTimes` / `getConnect4ClockTimes`
3000
+
3001
+ Returns live per-color clock values deducting elapsed time from the current player's budget.
3002
+
3003
+ ```ts
3004
+ const clocks = sdk.gameActionsStore.getChessClockTimes(gameId, Date.now());
3005
+ // { whiteTimeMs, blackTimeMs }
3006
+
3007
+ const tttClocks = sdk.gameActionsStore.getTicTacToeClockTimes(
3008
+ gameId,
3009
+ Date.now(),
3010
+ );
3011
+ // { xTimeMs, oTimeMs }
3012
+
3013
+ const c4Clocks = sdk.gameActionsStore.getConnect4ClockTimes(gameId, Date.now());
3014
+ // { redTimeMs, yellowTimeMs }
3015
+ ```
3016
+
3017
+ #### `getChessCapturedPieces(gameId)`
3018
+
3019
+ Derives captured pieces from the FEN placement string — no external chess library needed.
3020
+
3021
+ ```ts
3022
+ const pieces = sdk.gameActionsStore.getChessCapturedPieces(gameId);
3023
+ // { capturedByWhite: string[], capturedByBlack: string[] }
3024
+ // e.g. capturedByWhite: ['bp', 'bp', 'br'] means white captured 2 black pawns + 1 rook
3025
+ ```
3026
+
2974
3027
  ### Event Bus (low-level)
2975
3028
 
2976
3029
  If you need raw event streams, use `sdk.events.subscribe(eventName, handler)`.
package/dist/index.cjs CHANGED
@@ -242,6 +242,7 @@ __export(index_exports, {
242
242
  formatMoneyMinor: () => import_utils.formatMoneyMinor,
243
243
  isRetryableError: () => isRetryableError,
244
244
  logger: () => logger,
245
+ selectGameLifecycleState: () => selectGameLifecycleState,
245
246
  toMajor: () => import_utils.toMajor,
246
247
  withRetry: () => withRetry
247
248
  });
@@ -796,6 +797,9 @@ var Admin = class {
796
797
  this.http = http;
797
798
  this.logger = logger2;
798
799
  }
800
+ async getInternalBots() {
801
+ return this.http.get("/admin/internal-bots");
802
+ }
799
803
  async getUserById(id) {
800
804
  return this.http.get(`/admin/users/${id}`);
801
805
  }
@@ -3463,6 +3467,19 @@ function createGameStore(transport) {
3463
3467
  }
3464
3468
 
3465
3469
  // src/stores/game-actions-store.ts
3470
+ function selectGameLifecycleState(gameState) {
3471
+ const s = gameState;
3472
+ if (!s) return null;
3473
+ const raw = s;
3474
+ return {
3475
+ status: raw.status,
3476
+ winnerId: raw.winnerId ?? null,
3477
+ betAmount: raw.betAmount ?? 0,
3478
+ wonAmount: raw.wonAmount ?? null,
3479
+ totalPotMinor: raw.totalPotMinor,
3480
+ currentPlayerId: raw.currentPlayerId ?? null
3481
+ };
3482
+ }
3466
3483
  var isRpsCompletionPayload = (payload) => payload.gameType === "rock-paper-scissors";
3467
3484
  function createGameActionsStore(transport) {
3468
3485
  const store = createSdkStore({
@@ -3484,8 +3501,21 @@ function createGameActionsStore(transport) {
3484
3501
  });
3485
3502
  };
3486
3503
  const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
3504
+ const pendingEvents = /* @__PURE__ */ new Map();
3505
+ function enqueue(gameId, event) {
3506
+ const q = pendingEvents.get(gameId) ?? [];
3507
+ q.push(event);
3508
+ pendingEvents.set(gameId, q);
3509
+ }
3510
+ function drainQueue(gameId) {
3511
+ const q = pendingEvents.get(gameId);
3512
+ if (!q?.length) return;
3513
+ pendingEvents.delete(gameId);
3514
+ for (const ev of q) applyWsEvent(ev);
3515
+ }
3487
3516
  const setBaseState = (gameId, state) => {
3488
3517
  updateState(gameId, state);
3518
+ drainQueue(gameId);
3489
3519
  };
3490
3520
  const clearState = (gameId) => {
3491
3521
  store.updateState((state) => {
@@ -3495,6 +3525,7 @@ function createGameActionsStore(transport) {
3495
3525
  const { [gameId]: _, ...rest } = state.statesByGameId;
3496
3526
  return { ...state, statesByGameId: rest };
3497
3527
  });
3528
+ pendingEvents.delete(gameId);
3498
3529
  };
3499
3530
  const applyWsEvent = (event) => {
3500
3531
  switch (event.event) {
@@ -3513,7 +3544,10 @@ function createGameActionsStore(transport) {
3513
3544
  }
3514
3545
  case "game:rps:starting": {
3515
3546
  const current = store.getState().statesByGameId[event.payload.gameId];
3516
- if (!current || !isRpsState(current)) return;
3547
+ if (!current || !isRpsState(current)) {
3548
+ enqueue(event.payload.gameId, event);
3549
+ return;
3550
+ }
3517
3551
  const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
3518
3552
  const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
3519
3553
  const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
@@ -3534,7 +3568,10 @@ function createGameActionsStore(transport) {
3534
3568
  }
3535
3569
  case "game:rps:round:started": {
3536
3570
  const current = store.getState().statesByGameId[event.payload.gameId];
3537
- if (!current || !isRpsState(current)) return;
3571
+ if (!current || !isRpsState(current)) {
3572
+ enqueue(event.payload.gameId, event);
3573
+ return;
3574
+ }
3538
3575
  const actions = {};
3539
3576
  const baseUsers = /* @__PURE__ */ new Set();
3540
3577
  Object.keys(current.roundState.actions).forEach(
@@ -3568,7 +3605,10 @@ function createGameActionsStore(transport) {
3568
3605
  }
3569
3606
  case "game:rps:action:received": {
3570
3607
  const current = store.getState().statesByGameId[event.payload.gameId];
3571
- if (!current || !isRpsState(current)) return;
3608
+ if (!current || !isRpsState(current)) {
3609
+ enqueue(event.payload.gameId, event);
3610
+ return;
3611
+ }
3572
3612
  const updated = {
3573
3613
  ...current,
3574
3614
  roundState: {
@@ -3587,7 +3627,10 @@ function createGameActionsStore(transport) {
3587
3627
  }
3588
3628
  case "game:rps:timer:cutoff": {
3589
3629
  const current = store.getState().statesByGameId[event.payload.gameId];
3590
- if (!current || !isRpsState(current)) return;
3630
+ if (!current || !isRpsState(current)) {
3631
+ enqueue(event.payload.gameId, event);
3632
+ return;
3633
+ }
3591
3634
  const updated = {
3592
3635
  ...current,
3593
3636
  roundState: {
@@ -3601,7 +3644,10 @@ function createGameActionsStore(transport) {
3601
3644
  }
3602
3645
  case "game:rps:round:reveal": {
3603
3646
  const current = store.getState().statesByGameId[event.payload.gameId];
3604
- if (!current || !isRpsState(current)) return;
3647
+ if (!current || !isRpsState(current)) {
3648
+ enqueue(event.payload.gameId, event);
3649
+ return;
3650
+ }
3605
3651
  const actions = {};
3606
3652
  const payloadActions = event.payload.actions;
3607
3653
  Object.keys(payloadActions || {}).forEach((userId) => {
@@ -3625,7 +3671,10 @@ function createGameActionsStore(transport) {
3625
3671
  }
3626
3672
  case "game:rps:round:completed": {
3627
3673
  const current = store.getState().statesByGameId[event.payload.gameId];
3628
- if (!current || !isRpsState(current)) return;
3674
+ if (!current || !isRpsState(current)) {
3675
+ enqueue(event.payload.gameId, event);
3676
+ return;
3677
+ }
3629
3678
  const roundHistory = [
3630
3679
  ...current.roundHistory || [],
3631
3680
  {
@@ -3650,7 +3699,10 @@ function createGameActionsStore(transport) {
3650
3699
  }
3651
3700
  case "game:rps:timeout": {
3652
3701
  const current = store.getState().statesByGameId[event.payload.gameId];
3653
- if (!current || !isRpsState(current)) return;
3702
+ if (!current || !isRpsState(current)) {
3703
+ enqueue(event.payload.gameId, event);
3704
+ return;
3705
+ }
3654
3706
  const timedOutUser = event.payload.playerId;
3655
3707
  const action = event.payload.action;
3656
3708
  const updated = {
@@ -3675,7 +3727,10 @@ function createGameActionsStore(transport) {
3675
3727
  const payload = event.payload;
3676
3728
  const { gameId } = payload;
3677
3729
  const current = store.getState().statesByGameId[gameId];
3678
- if (!current) return;
3730
+ if (!current) {
3731
+ enqueue(gameId, event);
3732
+ return;
3733
+ }
3679
3734
  const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
3680
3735
  ...current,
3681
3736
  status: "completed",
@@ -3724,11 +3779,15 @@ function createGameActionsStore(transport) {
3724
3779
  const current = store.getState().statesByGameId[gameId];
3725
3780
  const updated = current ? { ...current, ...incoming } : incoming;
3726
3781
  updateState(gameId, updated);
3782
+ drainQueue(gameId);
3727
3783
  break;
3728
3784
  }
3729
3785
  case "game:rematch:requested": {
3730
3786
  const current = store.getState().statesByGameId[event.payload.gameId];
3731
- if (!current) return;
3787
+ if (!current) {
3788
+ enqueue(event.payload.gameId, event);
3789
+ return;
3790
+ }
3732
3791
  const requestedBy = event.payload.requestedBy;
3733
3792
  const userId = event.payload.userId;
3734
3793
  const requested = new Set(
@@ -3744,7 +3803,10 @@ function createGameActionsStore(transport) {
3744
3803
  }
3745
3804
  case "game:rematch:cancelled": {
3746
3805
  const current = store.getState().statesByGameId[event.payload.gameId];
3747
- if (!current) return;
3806
+ if (!current) {
3807
+ enqueue(event.payload.gameId, event);
3808
+ return;
3809
+ }
3748
3810
  const requestedBy = event.payload.requestedBy ?? [];
3749
3811
  const updated = {
3750
3812
  ...current,
@@ -3755,7 +3817,10 @@ function createGameActionsStore(transport) {
3755
3817
  }
3756
3818
  case "game:rematch:started": {
3757
3819
  const current = store.getState().statesByGameId[event.payload.gameId];
3758
- if (!current) return;
3820
+ if (!current) {
3821
+ enqueue(event.payload.gameId, event);
3822
+ return;
3823
+ }
3759
3824
  const updated = {
3760
3825
  ...current,
3761
3826
  rematchRequestedBy: event.payload.playerIds ?? []
@@ -3766,7 +3831,10 @@ function createGameActionsStore(transport) {
3766
3831
  case "game:pot:updated": {
3767
3832
  const { gameId, totalPotMinor } = event.payload;
3768
3833
  const current = store.getState().statesByGameId[gameId];
3769
- if (!current) return;
3834
+ if (!current) {
3835
+ enqueue(gameId, event);
3836
+ return;
3837
+ }
3770
3838
  const updated = {
3771
3839
  ...current,
3772
3840
  totalPotMinor
@@ -3779,12 +3847,123 @@ function createGameActionsStore(transport) {
3779
3847
  }
3780
3848
  };
3781
3849
  const joinGame = (gameId) => transport.joinRoom(`game:${gameId}`);
3850
+ const getCountdownDigit = (gameId, nowMs) => {
3851
+ const state = store.getState().statesByGameId[gameId];
3852
+ if (!state) return null;
3853
+ if (isRpsState(state)) {
3854
+ if (state.roundState.phase !== "starting") return null;
3855
+ const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
3856
+ if (remaining <= 0) return null;
3857
+ return Math.ceil(remaining / 1e3);
3858
+ }
3859
+ const bufferEndsAt = state.bufferEndsAt;
3860
+ if (bufferEndsAt) {
3861
+ const remaining = new Date(bufferEndsAt).getTime() - nowMs;
3862
+ if (remaining <= 0) return null;
3863
+ return Math.ceil(remaining / 1e3);
3864
+ }
3865
+ return null;
3866
+ };
3867
+ const getChessClockTimes = (gameId, nowMs) => {
3868
+ const state = store.getState().statesByGameId[gameId];
3869
+ if (!state || state.gameType !== "chess") return null;
3870
+ const s = state;
3871
+ let whiteMs = s.whiteTimeMs ?? 0;
3872
+ let blackMs = s.blackTimeMs ?? 0;
3873
+ if (s.status === "active" && s.currentPlayerId) {
3874
+ const startedAt = Date.parse(s.turnStartedAt);
3875
+ if (!Number.isNaN(startedAt)) {
3876
+ const elapsed = Math.max(0, nowMs - startedAt);
3877
+ if (s.currentPlayerId === s.whitePlayerId) {
3878
+ whiteMs = Math.max(0, whiteMs - elapsed);
3879
+ } else if (s.currentPlayerId === s.blackPlayerId) {
3880
+ blackMs = Math.max(0, blackMs - elapsed);
3881
+ }
3882
+ }
3883
+ }
3884
+ return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
3885
+ };
3886
+ const getChessCapturedPieces = (gameId) => {
3887
+ const state = store.getState().statesByGameId[gameId];
3888
+ if (!state || state.gameType !== "chess") return null;
3889
+ const fen = state.fen;
3890
+ if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
3891
+ const placement = fen.split(" ")[0];
3892
+ const white = {};
3893
+ const black = {};
3894
+ for (const char of placement) {
3895
+ if (char === "/" || char >= "1" && char <= "8") continue;
3896
+ const lower = char.toLowerCase();
3897
+ if (char === lower) {
3898
+ black[lower] = (black[lower] ?? 0) + 1;
3899
+ } else {
3900
+ white[lower] = (white[lower] ?? 0) + 1;
3901
+ }
3902
+ }
3903
+ const INITIAL = {
3904
+ p: 8,
3905
+ r: 2,
3906
+ n: 2,
3907
+ b: 2,
3908
+ q: 1,
3909
+ k: 1
3910
+ };
3911
+ const capturedByWhite = [];
3912
+ const capturedByBlack = [];
3913
+ for (const [type, initial] of Object.entries(INITIAL)) {
3914
+ const missingBlack = initial - (black[type] ?? 0);
3915
+ for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
3916
+ const missingWhite = initial - (white[type] ?? 0);
3917
+ for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
3918
+ }
3919
+ return { capturedByWhite, capturedByBlack };
3920
+ };
3921
+ const getTicTacToeClockTimes = (gameId, nowMs) => {
3922
+ const state = store.getState().statesByGameId[gameId];
3923
+ if (!state || state.gameType !== "tic-tac-toe") return null;
3924
+ const s = state;
3925
+ let xMs = s.xTimeMs ?? 0;
3926
+ let oMs = s.oTimeMs ?? 0;
3927
+ if (s.status === "active" && s.currentPlayerId) {
3928
+ const currentMark = s.playerMarks[s.currentPlayerId];
3929
+ const startedAt = Date.parse(s.turnStartedAt);
3930
+ if (!Number.isNaN(startedAt)) {
3931
+ const elapsed = Math.max(0, nowMs - startedAt);
3932
+ if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
3933
+ else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
3934
+ }
3935
+ }
3936
+ return { xTimeMs: xMs, oTimeMs: oMs };
3937
+ };
3938
+ const getConnect4ClockTimes = (gameId, nowMs) => {
3939
+ const state = store.getState().statesByGameId[gameId];
3940
+ if (!state || state.gameType !== "connect-four") return null;
3941
+ const s = state;
3942
+ let redMs = s.redTimeMs ?? 0;
3943
+ let yellowMs = s.yellowTimeMs ?? 0;
3944
+ if (s.status === "active" && s.currentPlayerId) {
3945
+ const currentColor = s.playerColors[s.currentPlayerId];
3946
+ const startedAt = Date.parse(s.turnStartedAt);
3947
+ if (!Number.isNaN(startedAt)) {
3948
+ const elapsed = Math.max(0, nowMs - startedAt);
3949
+ if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
3950
+ else if (currentColor === "YELLOW")
3951
+ yellowMs = Math.max(0, yellowMs - elapsed);
3952
+ }
3953
+ }
3954
+ return { redTimeMs: redMs, yellowTimeMs: yellowMs };
3955
+ };
3782
3956
  return {
3783
3957
  store,
3784
3958
  setBaseState,
3785
3959
  clearState,
3786
3960
  applyWsEvent,
3787
- joinGame
3961
+ joinGame,
3962
+ getCountdownDigit,
3963
+ getChessClockTimes,
3964
+ getChessCapturedPieces,
3965
+ getTicTacToeClockTimes,
3966
+ getConnect4ClockTimes
3788
3967
  };
3789
3968
  }
3790
3969
  function isRpsState(state) {
@@ -4454,12 +4633,14 @@ var WsRouter = class {
4454
4633
  }
4455
4634
  const decoded = decodeWsEvent(eventName, payload);
4456
4635
  if (!decoded) return;
4457
- this.deps.lobbyStore.applyWsEvent(decoded);
4458
- this.deps.gameStore.applyWsEvent(decoded);
4459
- this.deps.gameActionsStore.applyWsEvent(decoded);
4460
- this.deps.chatStore.applyWsEvent(decoded);
4461
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4462
- this.deps.notificationsStore.applyWsEvent(decoded);
4636
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4637
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4638
+ this.deps.lobbyStore.applyWsEvent(event);
4639
+ this.deps.gameStore.applyWsEvent(event);
4640
+ this.deps.gameActionsStore.applyWsEvent(event);
4641
+ this.deps.chatStore.applyWsEvent(event);
4642
+ this.deps.dmThreadsStore.applyWsEvent(event);
4643
+ this.deps.notificationsStore.applyWsEvent(event);
4463
4644
  });
4464
4645
  }
4465
4646
  stop() {
@@ -4471,12 +4652,14 @@ var WsRouter = class {
4471
4652
  this.transport.subscribeEvent(eventName, (payload) => {
4472
4653
  const decoded = decodeWsEvent(eventName, payload);
4473
4654
  if (!decoded) return;
4474
- this.deps.lobbyStore.applyWsEvent(decoded);
4475
- this.deps.gameStore.applyWsEvent(decoded);
4476
- this.deps.gameActionsStore.applyWsEvent(decoded);
4477
- this.deps.chatStore.applyWsEvent(decoded);
4478
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4479
- this.deps.notificationsStore.applyWsEvent(decoded);
4655
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4656
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4657
+ this.deps.lobbyStore.applyWsEvent(event);
4658
+ this.deps.gameStore.applyWsEvent(event);
4659
+ this.deps.gameActionsStore.applyWsEvent(event);
4660
+ this.deps.chatStore.applyWsEvent(event);
4661
+ this.deps.dmThreadsStore.applyWsEvent(event);
4662
+ this.deps.notificationsStore.applyWsEvent(event);
4480
4663
  });
4481
4664
  }
4482
4665
  }
@@ -4877,6 +5060,7 @@ var import_utils = __toESM(require_dist(), 1);
4877
5060
  formatMoneyMinor,
4878
5061
  isRetryableError,
4879
5062
  logger,
5063
+ selectGameLifecycleState,
4880
5064
  toMajor,
4881
5065
  withRetry
4882
5066
  });
package/dist/index.d.cts CHANGED
@@ -1121,6 +1121,11 @@ declare class Admin {
1121
1121
  private http;
1122
1122
  private logger?;
1123
1123
  constructor(http: IHttpClient, logger?: ILogger | undefined);
1124
+ getInternalBots(): Promise<(User & {
1125
+ sol: number;
1126
+ usdc: number;
1127
+ publicKey: string;
1128
+ })[]>;
1124
1129
  getUserById(id: string): Promise<User>;
1125
1130
  getUserBalance(userId: string): Promise<{
1126
1131
  sol: number;
@@ -1434,12 +1439,55 @@ declare function createGameStore(transport: WsTransport): GameStore;
1434
1439
  type GameActionsStoreState = {
1435
1440
  statesByGameId: Record<string, GameStateResponse>;
1436
1441
  };
1442
+ interface ChessClockTimes {
1443
+ whiteTimeMs: number;
1444
+ blackTimeMs: number;
1445
+ }
1446
+ interface ChessCapturedPieces {
1447
+ capturedByWhite: string[];
1448
+ capturedByBlack: string[];
1449
+ }
1450
+ interface TicTacToeClockTimes {
1451
+ xTimeMs: number;
1452
+ oTimeMs: number;
1453
+ }
1454
+ interface Connect4ClockTimes {
1455
+ redTimeMs: number;
1456
+ yellowTimeMs: number;
1457
+ }
1458
+ /**
1459
+ * Common lifecycle fields extracted from any game state.
1460
+ * Used by useGameLifecycle to avoid casting the raw union type.
1461
+ */
1462
+ interface GameLifecycleState {
1463
+ status: string;
1464
+ winnerId: string | null;
1465
+ betAmount: number;
1466
+ wonAmount: number | null;
1467
+ totalPotMinor: number | undefined;
1468
+ currentPlayerId: string | null;
1469
+ }
1470
+ /**
1471
+ * Pure selector — extracts lifecycle fields from any game state variant.
1472
+ * Accepts a single GameStateResponse so callers can use it after selecting
1473
+ * the stable store entry via useSdkSelector, avoiding new-object-per-call issues.
1474
+ */
1475
+ declare function selectGameLifecycleState(gameState: GameStateResponse | null | undefined): GameLifecycleState | null;
1437
1476
  type GameActionsStore = {
1438
1477
  store: SdkStore<GameActionsStoreState>;
1439
1478
  setBaseState: (gameId: string, state: GameStateResponse) => void;
1440
1479
  clearState: (gameId: string) => void;
1441
1480
  applyWsEvent: (event: WsEvent) => void;
1442
1481
  joinGame: (gameId: string) => () => void;
1482
+ getCountdownDigit: (gameId: string, nowMs: number) => number | null;
1483
+ /** Live chess clock — deducts elapsed time since turnStartedAt. */
1484
+ getChessClockTimes: (gameId: string, nowMs: number) => ChessClockTimes | null;
1485
+ /** Captured pieces derived from the current FEN position. */
1486
+ getChessCapturedPieces: (gameId: string) => ChessCapturedPieces | null;
1487
+ /** Live TTT clock — deducts elapsed from the current player's mark (X or O). */
1488
+ getTicTacToeClockTimes: (gameId: string, nowMs: number) => TicTacToeClockTimes | null;
1489
+ /** Live Connect Four clock — deducts elapsed from the current player's color. */
1490
+ getConnect4ClockTimes: (gameId: string, nowMs: number) => Connect4ClockTimes | null;
1443
1491
  };
1444
1492
  declare function createGameActionsStore(transport: WsTransport): GameActionsStore;
1445
1493
 
@@ -1743,7 +1791,7 @@ type LobbyDepositUpdatedPayload = {
1743
1791
  status: 'confirmed' | 'failed' | 'refunded' | 'refund_failed';
1744
1792
  allConfirmed: boolean;
1745
1793
  };
1746
- type WsEvent = {
1794
+ type WsEventBase = {
1747
1795
  event: 'lobby:created';
1748
1796
  payload: Lobby;
1749
1797
  } | {
@@ -1941,6 +1989,9 @@ type WsEvent = {
1941
1989
  event: 'notification';
1942
1990
  payload: unknown;
1943
1991
  };
1992
+ type WsEvent = WsEventBase & {
1993
+ _serverTs?: number;
1994
+ };
1944
1995
 
1945
1996
  type LobbyMatchedEvent = {
1946
1997
  gameId: string;
@@ -3006,4 +3057,4 @@ declare class HttpClient implements IHttpClient {
3006
3057
  private payChallenge;
3007
3058
  }
3008
3059
 
3009
- export { type AcceptChallengeResponse, type Achievement, Activity, type ActivityFeedItem, type ActivityFeedItemType, type ActivityFeedResponse, type ActivityFeedUser, Admin, type AdminDailyStats, type AdminDailyStatsItem, type AdminFeatureFlag, type AdminGameHistory, type AdminGameHistoryFeeBreakdown, type AdminGameHistoryFeePlayer, type AdminGameHistoryPlayer, type AdminMarketDailyStats, type AdminMarketDailyStatsItem, type AdminMarketDetail, type AdminMarketStats, type AdminStats, type AdminWalletActivityItem, type AdminWalletActivityResponse, type AnalyticsUserData, type ApiError, type AppNotification, type AppNotificationType, type ApplyReferralCodeResponse, type BalanceResponse, type BetOption, type BroadcastTipRequest, BrowserLocalStorage, Challenges, Chat, type ChatContext, type ChatContextType, type ChatMessage, type ChatMessageReply, type ChatMessageType, type ChatReaction, type ChatReadBy, type ChatState, type ChatStore, type ChatStoreState, type ClaimReferralRewardsResponse, type CreateChallengeRequest, type CreateChallengeResponse, type CreateLobbyRequest, type CreateTicketData, type CriticalIncident, type CriticalIncidentCategory, type CriticalIncidentImpactType, type CriticalIncidentSeverity, type CriticalIncidentStatus, type CriticalIncidentSummary, type CurrentGame, Daily, type DailyParticipant, type DailyRoom, type DailyToken, type DepositForLobbyResponse, type DepositStatus, type DepositStatusResponse, type DmThread, type DmThreadsStore, type DmThreadsStoreState, type DonateToGameResponse, ESTIMATED_SOL_FEE_LAMPORTS, Escrow, type EscrowSweepPreview, type EscrowSweepRecord, type FaucetResponse, type FeatureFlag, type FriendRequestItem, type FriendsStore, type FriendsStoreState, type FriendshipStatus, type Game, type GameActionsStore, type GameActionsStoreState, type GameHistoryItem, type GameMetrics, type GamePlayer, type GameStateResponse, type GameStore, type GameStoreState, type GameType, Games, type GenerateHandshakeResponse, type GetTicketsOptions, HttpClient, type IAnalyticsClient, type IHttpClient, type ILogger, type IStorage, type ImageUploadPayload, type InviteFriendRequest, type LeaderboardEntry, type LeaderboardQuery, type LeaderboardRange, type LeaderboardResponse, Leaderboards, type LivePlayer, type LivePlayersPage, Lobbies, type Lobby, type LobbyDepositUpdatedPayload, type LobbyMatchedEvent, type LobbyPlayer, type LobbyStore, type LobbyStoreState, type LogLevel, type LoginResponse, MIN_SOL_TRANSFER_AMOUNT, MIN_TRANSFER_AMOUNT, type MarketBuyResult, type MarketPosition, type MarketSellResult, type MarketState, Markets, type MoneyMinor, NodeStorage, NoopAnalyticsClient, type NotificationEvent, type NotificationsStore, type NotificationsStoreState, type PaginatedCriticalIncidents, type PaginatedFriendRequests, type PaginatedFriends, type PaginatedNotificationsResponse, type PaginatedPlatformFees, type PaginatedReports, type PaginatedSearchUsers, type PaginatedSessions, type PaginatedSupportTickets, type PaginatedTransactionJobs, type PaginatedUsers, type PaymentRequiredChallenge, type PlatformFeeItem, type PrepareDepositResponse, type PrepareTipRequest, type PrepareTipResponse, type PrepareTransferRequest, type PrepareTransferResponse, type PublicUser, type QueueStats, type RedeemResult, type ReferralRewardItem, type ReferralRewardStatus, type ReferralRewardsResponse, type ReferralSummary, type ReferralTreeItem, type ReferralTreeResponse, Referrals, type Report, type ReportCount, type ReportStatus, type ReportUser, Reports, type RetryOptions, SDK, type SDKConfig, SDK_VERSION, SOL_DECIMALS, SOL_MINT, type SdkStore, type SdkUpgradeInfo, type SearchUser, type SendMessageRequest, type SendTipResponse, type SendTransferResponse, type Session, type SessionStats, SharedWorkerTransport, Spectate, type SpectatorMetrics, type SpectatorMetricsByUser, StandaloneWsTransport, type SubmitDepositResponse, type SubmitTransferRequest, type SubmitTransferResponse, Support, type SupportMessage, type SupportMessageSenderRole, type SupportTicket, type SupportTicketCategory, type SupportTicketPriority, type SupportTicketStatus, type SupportTicketUser, type SystemMessageType, TOKEN_KEY, TRANSFER_FEE_MINOR, Tips, type TransactionJob, type TransactionJobStatus, type TransactionJobType, type TransactionQueueStats, type TransferToken, type TypingUser, type UpdateTicketData, type User, type UserAchievement, type UserActivity, type UserActivityStatus, type UserStats, type UsernameAvailabilityResponse, Users, type ValidAction, Wallet, type WalletActivityCounterpartyType, type WalletActivityDirection, type WalletActivityItem, type WalletActivityKind, type WalletActivityResponse, type WalletActivityStatus, type WalletClaimAction, type WalletMeta, type WalletResponse, type WalletSigner, type WsEvent, WsEventBus, type WsEventName, type WsTransport, type ConnectionState as WsTransportState, createChatStore, createDmThreadsStore, createFriendsStore, createGameActionsStore, createGameStore, createLobbyStore, createLogger, createNotificationsStore, createSdkStore, isRetryableError, logger, withRetry };
3060
+ export { type AcceptChallengeResponse, type Achievement, Activity, type ActivityFeedItem, type ActivityFeedItemType, type ActivityFeedResponse, type ActivityFeedUser, Admin, type AdminDailyStats, type AdminDailyStatsItem, type AdminFeatureFlag, type AdminGameHistory, type AdminGameHistoryFeeBreakdown, type AdminGameHistoryFeePlayer, type AdminGameHistoryPlayer, type AdminMarketDailyStats, type AdminMarketDailyStatsItem, type AdminMarketDetail, type AdminMarketStats, type AdminStats, type AdminWalletActivityItem, type AdminWalletActivityResponse, type AnalyticsUserData, type ApiError, type AppNotification, type AppNotificationType, type ApplyReferralCodeResponse, type BalanceResponse, type BetOption, type BroadcastTipRequest, BrowserLocalStorage, Challenges, Chat, type ChatContext, type ChatContextType, type ChatMessage, type ChatMessageReply, type ChatMessageType, type ChatReaction, type ChatReadBy, type ChatState, type ChatStore, type ChatStoreState, type ChessCapturedPieces, type ChessClockTimes, type ClaimReferralRewardsResponse, type Connect4ClockTimes, type CreateChallengeRequest, type CreateChallengeResponse, type CreateLobbyRequest, type CreateTicketData, type CriticalIncident, type CriticalIncidentCategory, type CriticalIncidentImpactType, type CriticalIncidentSeverity, type CriticalIncidentStatus, type CriticalIncidentSummary, type CurrentGame, Daily, type DailyParticipant, type DailyRoom, type DailyToken, type DepositForLobbyResponse, type DepositStatus, type DepositStatusResponse, type DmThread, type DmThreadsStore, type DmThreadsStoreState, type DonateToGameResponse, ESTIMATED_SOL_FEE_LAMPORTS, Escrow, type EscrowSweepPreview, type EscrowSweepRecord, type FaucetResponse, type FeatureFlag, type FriendRequestItem, type FriendsStore, type FriendsStoreState, type FriendshipStatus, type Game, type GameActionsStore, type GameActionsStoreState, type GameHistoryItem, type GameLifecycleState, type GameMetrics, type GamePlayer, type GameStateResponse, type GameStore, type GameStoreState, type GameType, Games, type GenerateHandshakeResponse, type GetTicketsOptions, HttpClient, type IAnalyticsClient, type IHttpClient, type ILogger, type IStorage, type ImageUploadPayload, type InviteFriendRequest, type LeaderboardEntry, type LeaderboardQuery, type LeaderboardRange, type LeaderboardResponse, Leaderboards, type LivePlayer, type LivePlayersPage, Lobbies, type Lobby, type LobbyDepositUpdatedPayload, type LobbyMatchedEvent, type LobbyPlayer, type LobbyStore, type LobbyStoreState, type LogLevel, type LoginResponse, MIN_SOL_TRANSFER_AMOUNT, MIN_TRANSFER_AMOUNT, type MarketBuyResult, type MarketPosition, type MarketSellResult, type MarketState, Markets, type MoneyMinor, NodeStorage, NoopAnalyticsClient, type NotificationEvent, type NotificationsStore, type NotificationsStoreState, type PaginatedCriticalIncidents, type PaginatedFriendRequests, type PaginatedFriends, type PaginatedNotificationsResponse, type PaginatedPlatformFees, type PaginatedReports, type PaginatedSearchUsers, type PaginatedSessions, type PaginatedSupportTickets, type PaginatedTransactionJobs, type PaginatedUsers, type PaymentRequiredChallenge, type PlatformFeeItem, type PrepareDepositResponse, type PrepareTipRequest, type PrepareTipResponse, type PrepareTransferRequest, type PrepareTransferResponse, type PublicUser, type QueueStats, type RedeemResult, type ReferralRewardItem, type ReferralRewardStatus, type ReferralRewardsResponse, type ReferralSummary, type ReferralTreeItem, type ReferralTreeResponse, Referrals, type Report, type ReportCount, type ReportStatus, type ReportUser, Reports, type RetryOptions, SDK, type SDKConfig, SDK_VERSION, SOL_DECIMALS, SOL_MINT, type SdkStore, type SdkUpgradeInfo, type SearchUser, type SendMessageRequest, type SendTipResponse, type SendTransferResponse, type Session, type SessionStats, SharedWorkerTransport, Spectate, type SpectatorMetrics, type SpectatorMetricsByUser, StandaloneWsTransport, type SubmitDepositResponse, type SubmitTransferRequest, type SubmitTransferResponse, Support, type SupportMessage, type SupportMessageSenderRole, type SupportTicket, type SupportTicketCategory, type SupportTicketPriority, type SupportTicketStatus, type SupportTicketUser, type SystemMessageType, TOKEN_KEY, TRANSFER_FEE_MINOR, type TicTacToeClockTimes, Tips, type TransactionJob, type TransactionJobStatus, type TransactionJobType, type TransactionQueueStats, type TransferToken, type TypingUser, type UpdateTicketData, type User, type UserAchievement, type UserActivity, type UserActivityStatus, type UserStats, type UsernameAvailabilityResponse, Users, type ValidAction, Wallet, type WalletActivityCounterpartyType, type WalletActivityDirection, type WalletActivityItem, type WalletActivityKind, type WalletActivityResponse, type WalletActivityStatus, type WalletClaimAction, type WalletMeta, type WalletResponse, type WalletSigner, type WsEvent, WsEventBus, type WsEventName, type WsTransport, type ConnectionState as WsTransportState, createChatStore, createDmThreadsStore, createFriendsStore, createGameActionsStore, createGameStore, createLobbyStore, createLogger, createNotificationsStore, createSdkStore, isRetryableError, logger, selectGameLifecycleState, withRetry };
package/dist/index.d.ts CHANGED
@@ -1121,6 +1121,11 @@ declare class Admin {
1121
1121
  private http;
1122
1122
  private logger?;
1123
1123
  constructor(http: IHttpClient, logger?: ILogger | undefined);
1124
+ getInternalBots(): Promise<(User & {
1125
+ sol: number;
1126
+ usdc: number;
1127
+ publicKey: string;
1128
+ })[]>;
1124
1129
  getUserById(id: string): Promise<User>;
1125
1130
  getUserBalance(userId: string): Promise<{
1126
1131
  sol: number;
@@ -1434,12 +1439,55 @@ declare function createGameStore(transport: WsTransport): GameStore;
1434
1439
  type GameActionsStoreState = {
1435
1440
  statesByGameId: Record<string, GameStateResponse>;
1436
1441
  };
1442
+ interface ChessClockTimes {
1443
+ whiteTimeMs: number;
1444
+ blackTimeMs: number;
1445
+ }
1446
+ interface ChessCapturedPieces {
1447
+ capturedByWhite: string[];
1448
+ capturedByBlack: string[];
1449
+ }
1450
+ interface TicTacToeClockTimes {
1451
+ xTimeMs: number;
1452
+ oTimeMs: number;
1453
+ }
1454
+ interface Connect4ClockTimes {
1455
+ redTimeMs: number;
1456
+ yellowTimeMs: number;
1457
+ }
1458
+ /**
1459
+ * Common lifecycle fields extracted from any game state.
1460
+ * Used by useGameLifecycle to avoid casting the raw union type.
1461
+ */
1462
+ interface GameLifecycleState {
1463
+ status: string;
1464
+ winnerId: string | null;
1465
+ betAmount: number;
1466
+ wonAmount: number | null;
1467
+ totalPotMinor: number | undefined;
1468
+ currentPlayerId: string | null;
1469
+ }
1470
+ /**
1471
+ * Pure selector — extracts lifecycle fields from any game state variant.
1472
+ * Accepts a single GameStateResponse so callers can use it after selecting
1473
+ * the stable store entry via useSdkSelector, avoiding new-object-per-call issues.
1474
+ */
1475
+ declare function selectGameLifecycleState(gameState: GameStateResponse | null | undefined): GameLifecycleState | null;
1437
1476
  type GameActionsStore = {
1438
1477
  store: SdkStore<GameActionsStoreState>;
1439
1478
  setBaseState: (gameId: string, state: GameStateResponse) => void;
1440
1479
  clearState: (gameId: string) => void;
1441
1480
  applyWsEvent: (event: WsEvent) => void;
1442
1481
  joinGame: (gameId: string) => () => void;
1482
+ getCountdownDigit: (gameId: string, nowMs: number) => number | null;
1483
+ /** Live chess clock — deducts elapsed time since turnStartedAt. */
1484
+ getChessClockTimes: (gameId: string, nowMs: number) => ChessClockTimes | null;
1485
+ /** Captured pieces derived from the current FEN position. */
1486
+ getChessCapturedPieces: (gameId: string) => ChessCapturedPieces | null;
1487
+ /** Live TTT clock — deducts elapsed from the current player's mark (X or O). */
1488
+ getTicTacToeClockTimes: (gameId: string, nowMs: number) => TicTacToeClockTimes | null;
1489
+ /** Live Connect Four clock — deducts elapsed from the current player's color. */
1490
+ getConnect4ClockTimes: (gameId: string, nowMs: number) => Connect4ClockTimes | null;
1443
1491
  };
1444
1492
  declare function createGameActionsStore(transport: WsTransport): GameActionsStore;
1445
1493
 
@@ -1743,7 +1791,7 @@ type LobbyDepositUpdatedPayload = {
1743
1791
  status: 'confirmed' | 'failed' | 'refunded' | 'refund_failed';
1744
1792
  allConfirmed: boolean;
1745
1793
  };
1746
- type WsEvent = {
1794
+ type WsEventBase = {
1747
1795
  event: 'lobby:created';
1748
1796
  payload: Lobby;
1749
1797
  } | {
@@ -1941,6 +1989,9 @@ type WsEvent = {
1941
1989
  event: 'notification';
1942
1990
  payload: unknown;
1943
1991
  };
1992
+ type WsEvent = WsEventBase & {
1993
+ _serverTs?: number;
1994
+ };
1944
1995
 
1945
1996
  type LobbyMatchedEvent = {
1946
1997
  gameId: string;
@@ -3006,4 +3057,4 @@ declare class HttpClient implements IHttpClient {
3006
3057
  private payChallenge;
3007
3058
  }
3008
3059
 
3009
- export { type AcceptChallengeResponse, type Achievement, Activity, type ActivityFeedItem, type ActivityFeedItemType, type ActivityFeedResponse, type ActivityFeedUser, Admin, type AdminDailyStats, type AdminDailyStatsItem, type AdminFeatureFlag, type AdminGameHistory, type AdminGameHistoryFeeBreakdown, type AdminGameHistoryFeePlayer, type AdminGameHistoryPlayer, type AdminMarketDailyStats, type AdminMarketDailyStatsItem, type AdminMarketDetail, type AdminMarketStats, type AdminStats, type AdminWalletActivityItem, type AdminWalletActivityResponse, type AnalyticsUserData, type ApiError, type AppNotification, type AppNotificationType, type ApplyReferralCodeResponse, type BalanceResponse, type BetOption, type BroadcastTipRequest, BrowserLocalStorage, Challenges, Chat, type ChatContext, type ChatContextType, type ChatMessage, type ChatMessageReply, type ChatMessageType, type ChatReaction, type ChatReadBy, type ChatState, type ChatStore, type ChatStoreState, type ClaimReferralRewardsResponse, type CreateChallengeRequest, type CreateChallengeResponse, type CreateLobbyRequest, type CreateTicketData, type CriticalIncident, type CriticalIncidentCategory, type CriticalIncidentImpactType, type CriticalIncidentSeverity, type CriticalIncidentStatus, type CriticalIncidentSummary, type CurrentGame, Daily, type DailyParticipant, type DailyRoom, type DailyToken, type DepositForLobbyResponse, type DepositStatus, type DepositStatusResponse, type DmThread, type DmThreadsStore, type DmThreadsStoreState, type DonateToGameResponse, ESTIMATED_SOL_FEE_LAMPORTS, Escrow, type EscrowSweepPreview, type EscrowSweepRecord, type FaucetResponse, type FeatureFlag, type FriendRequestItem, type FriendsStore, type FriendsStoreState, type FriendshipStatus, type Game, type GameActionsStore, type GameActionsStoreState, type GameHistoryItem, type GameMetrics, type GamePlayer, type GameStateResponse, type GameStore, type GameStoreState, type GameType, Games, type GenerateHandshakeResponse, type GetTicketsOptions, HttpClient, type IAnalyticsClient, type IHttpClient, type ILogger, type IStorage, type ImageUploadPayload, type InviteFriendRequest, type LeaderboardEntry, type LeaderboardQuery, type LeaderboardRange, type LeaderboardResponse, Leaderboards, type LivePlayer, type LivePlayersPage, Lobbies, type Lobby, type LobbyDepositUpdatedPayload, type LobbyMatchedEvent, type LobbyPlayer, type LobbyStore, type LobbyStoreState, type LogLevel, type LoginResponse, MIN_SOL_TRANSFER_AMOUNT, MIN_TRANSFER_AMOUNT, type MarketBuyResult, type MarketPosition, type MarketSellResult, type MarketState, Markets, type MoneyMinor, NodeStorage, NoopAnalyticsClient, type NotificationEvent, type NotificationsStore, type NotificationsStoreState, type PaginatedCriticalIncidents, type PaginatedFriendRequests, type PaginatedFriends, type PaginatedNotificationsResponse, type PaginatedPlatformFees, type PaginatedReports, type PaginatedSearchUsers, type PaginatedSessions, type PaginatedSupportTickets, type PaginatedTransactionJobs, type PaginatedUsers, type PaymentRequiredChallenge, type PlatformFeeItem, type PrepareDepositResponse, type PrepareTipRequest, type PrepareTipResponse, type PrepareTransferRequest, type PrepareTransferResponse, type PublicUser, type QueueStats, type RedeemResult, type ReferralRewardItem, type ReferralRewardStatus, type ReferralRewardsResponse, type ReferralSummary, type ReferralTreeItem, type ReferralTreeResponse, Referrals, type Report, type ReportCount, type ReportStatus, type ReportUser, Reports, type RetryOptions, SDK, type SDKConfig, SDK_VERSION, SOL_DECIMALS, SOL_MINT, type SdkStore, type SdkUpgradeInfo, type SearchUser, type SendMessageRequest, type SendTipResponse, type SendTransferResponse, type Session, type SessionStats, SharedWorkerTransport, Spectate, type SpectatorMetrics, type SpectatorMetricsByUser, StandaloneWsTransport, type SubmitDepositResponse, type SubmitTransferRequest, type SubmitTransferResponse, Support, type SupportMessage, type SupportMessageSenderRole, type SupportTicket, type SupportTicketCategory, type SupportTicketPriority, type SupportTicketStatus, type SupportTicketUser, type SystemMessageType, TOKEN_KEY, TRANSFER_FEE_MINOR, Tips, type TransactionJob, type TransactionJobStatus, type TransactionJobType, type TransactionQueueStats, type TransferToken, type TypingUser, type UpdateTicketData, type User, type UserAchievement, type UserActivity, type UserActivityStatus, type UserStats, type UsernameAvailabilityResponse, Users, type ValidAction, Wallet, type WalletActivityCounterpartyType, type WalletActivityDirection, type WalletActivityItem, type WalletActivityKind, type WalletActivityResponse, type WalletActivityStatus, type WalletClaimAction, type WalletMeta, type WalletResponse, type WalletSigner, type WsEvent, WsEventBus, type WsEventName, type WsTransport, type ConnectionState as WsTransportState, createChatStore, createDmThreadsStore, createFriendsStore, createGameActionsStore, createGameStore, createLobbyStore, createLogger, createNotificationsStore, createSdkStore, isRetryableError, logger, withRetry };
3060
+ export { type AcceptChallengeResponse, type Achievement, Activity, type ActivityFeedItem, type ActivityFeedItemType, type ActivityFeedResponse, type ActivityFeedUser, Admin, type AdminDailyStats, type AdminDailyStatsItem, type AdminFeatureFlag, type AdminGameHistory, type AdminGameHistoryFeeBreakdown, type AdminGameHistoryFeePlayer, type AdminGameHistoryPlayer, type AdminMarketDailyStats, type AdminMarketDailyStatsItem, type AdminMarketDetail, type AdminMarketStats, type AdminStats, type AdminWalletActivityItem, type AdminWalletActivityResponse, type AnalyticsUserData, type ApiError, type AppNotification, type AppNotificationType, type ApplyReferralCodeResponse, type BalanceResponse, type BetOption, type BroadcastTipRequest, BrowserLocalStorage, Challenges, Chat, type ChatContext, type ChatContextType, type ChatMessage, type ChatMessageReply, type ChatMessageType, type ChatReaction, type ChatReadBy, type ChatState, type ChatStore, type ChatStoreState, type ChessCapturedPieces, type ChessClockTimes, type ClaimReferralRewardsResponse, type Connect4ClockTimes, type CreateChallengeRequest, type CreateChallengeResponse, type CreateLobbyRequest, type CreateTicketData, type CriticalIncident, type CriticalIncidentCategory, type CriticalIncidentImpactType, type CriticalIncidentSeverity, type CriticalIncidentStatus, type CriticalIncidentSummary, type CurrentGame, Daily, type DailyParticipant, type DailyRoom, type DailyToken, type DepositForLobbyResponse, type DepositStatus, type DepositStatusResponse, type DmThread, type DmThreadsStore, type DmThreadsStoreState, type DonateToGameResponse, ESTIMATED_SOL_FEE_LAMPORTS, Escrow, type EscrowSweepPreview, type EscrowSweepRecord, type FaucetResponse, type FeatureFlag, type FriendRequestItem, type FriendsStore, type FriendsStoreState, type FriendshipStatus, type Game, type GameActionsStore, type GameActionsStoreState, type GameHistoryItem, type GameLifecycleState, type GameMetrics, type GamePlayer, type GameStateResponse, type GameStore, type GameStoreState, type GameType, Games, type GenerateHandshakeResponse, type GetTicketsOptions, HttpClient, type IAnalyticsClient, type IHttpClient, type ILogger, type IStorage, type ImageUploadPayload, type InviteFriendRequest, type LeaderboardEntry, type LeaderboardQuery, type LeaderboardRange, type LeaderboardResponse, Leaderboards, type LivePlayer, type LivePlayersPage, Lobbies, type Lobby, type LobbyDepositUpdatedPayload, type LobbyMatchedEvent, type LobbyPlayer, type LobbyStore, type LobbyStoreState, type LogLevel, type LoginResponse, MIN_SOL_TRANSFER_AMOUNT, MIN_TRANSFER_AMOUNT, type MarketBuyResult, type MarketPosition, type MarketSellResult, type MarketState, Markets, type MoneyMinor, NodeStorage, NoopAnalyticsClient, type NotificationEvent, type NotificationsStore, type NotificationsStoreState, type PaginatedCriticalIncidents, type PaginatedFriendRequests, type PaginatedFriends, type PaginatedNotificationsResponse, type PaginatedPlatformFees, type PaginatedReports, type PaginatedSearchUsers, type PaginatedSessions, type PaginatedSupportTickets, type PaginatedTransactionJobs, type PaginatedUsers, type PaymentRequiredChallenge, type PlatformFeeItem, type PrepareDepositResponse, type PrepareTipRequest, type PrepareTipResponse, type PrepareTransferRequest, type PrepareTransferResponse, type PublicUser, type QueueStats, type RedeemResult, type ReferralRewardItem, type ReferralRewardStatus, type ReferralRewardsResponse, type ReferralSummary, type ReferralTreeItem, type ReferralTreeResponse, Referrals, type Report, type ReportCount, type ReportStatus, type ReportUser, Reports, type RetryOptions, SDK, type SDKConfig, SDK_VERSION, SOL_DECIMALS, SOL_MINT, type SdkStore, type SdkUpgradeInfo, type SearchUser, type SendMessageRequest, type SendTipResponse, type SendTransferResponse, type Session, type SessionStats, SharedWorkerTransport, Spectate, type SpectatorMetrics, type SpectatorMetricsByUser, StandaloneWsTransport, type SubmitDepositResponse, type SubmitTransferRequest, type SubmitTransferResponse, Support, type SupportMessage, type SupportMessageSenderRole, type SupportTicket, type SupportTicketCategory, type SupportTicketPriority, type SupportTicketStatus, type SupportTicketUser, type SystemMessageType, TOKEN_KEY, TRANSFER_FEE_MINOR, type TicTacToeClockTimes, Tips, type TransactionJob, type TransactionJobStatus, type TransactionJobType, type TransactionQueueStats, type TransferToken, type TypingUser, type UpdateTicketData, type User, type UserAchievement, type UserActivity, type UserActivityStatus, type UserStats, type UsernameAvailabilityResponse, Users, type ValidAction, Wallet, type WalletActivityCounterpartyType, type WalletActivityDirection, type WalletActivityItem, type WalletActivityKind, type WalletActivityResponse, type WalletActivityStatus, type WalletClaimAction, type WalletMeta, type WalletResponse, type WalletSigner, type WsEvent, WsEventBus, type WsEventName, type WsTransport, type ConnectionState as WsTransportState, createChatStore, createDmThreadsStore, createFriendsStore, createGameActionsStore, createGameStore, createLobbyStore, createLogger, createNotificationsStore, createSdkStore, isRetryableError, logger, selectGameLifecycleState, withRetry };
package/dist/index.js CHANGED
@@ -743,6 +743,9 @@ var Admin = class {
743
743
  this.http = http;
744
744
  this.logger = logger2;
745
745
  }
746
+ async getInternalBots() {
747
+ return this.http.get("/admin/internal-bots");
748
+ }
746
749
  async getUserById(id) {
747
750
  return this.http.get(`/admin/users/${id}`);
748
751
  }
@@ -3410,6 +3413,19 @@ function createGameStore(transport) {
3410
3413
  }
3411
3414
 
3412
3415
  // src/stores/game-actions-store.ts
3416
+ function selectGameLifecycleState(gameState) {
3417
+ const s = gameState;
3418
+ if (!s) return null;
3419
+ const raw = s;
3420
+ return {
3421
+ status: raw.status,
3422
+ winnerId: raw.winnerId ?? null,
3423
+ betAmount: raw.betAmount ?? 0,
3424
+ wonAmount: raw.wonAmount ?? null,
3425
+ totalPotMinor: raw.totalPotMinor,
3426
+ currentPlayerId: raw.currentPlayerId ?? null
3427
+ };
3428
+ }
3413
3429
  var isRpsCompletionPayload = (payload) => payload.gameType === "rock-paper-scissors";
3414
3430
  function createGameActionsStore(transport) {
3415
3431
  const store = createSdkStore({
@@ -3431,8 +3447,21 @@ function createGameActionsStore(transport) {
3431
3447
  });
3432
3448
  };
3433
3449
  const isNonRpsState = (state) => Boolean(state && !isRpsState(state));
3450
+ const pendingEvents = /* @__PURE__ */ new Map();
3451
+ function enqueue(gameId, event) {
3452
+ const q = pendingEvents.get(gameId) ?? [];
3453
+ q.push(event);
3454
+ pendingEvents.set(gameId, q);
3455
+ }
3456
+ function drainQueue(gameId) {
3457
+ const q = pendingEvents.get(gameId);
3458
+ if (!q?.length) return;
3459
+ pendingEvents.delete(gameId);
3460
+ for (const ev of q) applyWsEvent(ev);
3461
+ }
3434
3462
  const setBaseState = (gameId, state) => {
3435
3463
  updateState(gameId, state);
3464
+ drainQueue(gameId);
3436
3465
  };
3437
3466
  const clearState = (gameId) => {
3438
3467
  store.updateState((state) => {
@@ -3442,6 +3471,7 @@ function createGameActionsStore(transport) {
3442
3471
  const { [gameId]: _, ...rest } = state.statesByGameId;
3443
3472
  return { ...state, statesByGameId: rest };
3444
3473
  });
3474
+ pendingEvents.delete(gameId);
3445
3475
  };
3446
3476
  const applyWsEvent = (event) => {
3447
3477
  switch (event.event) {
@@ -3460,7 +3490,10 @@ function createGameActionsStore(transport) {
3460
3490
  }
3461
3491
  case "game:rps:starting": {
3462
3492
  const current = store.getState().statesByGameId[event.payload.gameId];
3463
- if (!current || !isRpsState(current)) return;
3493
+ if (!current || !isRpsState(current)) {
3494
+ enqueue(event.payload.gameId, event);
3495
+ return;
3496
+ }
3464
3497
  const betAmount = typeof event.payload.betAmount === "number" ? event.payload.betAmount : void 0;
3465
3498
  const startedAt = typeof event.payload.startedAt === "string" ? event.payload.startedAt : current.roundState.startedAt;
3466
3499
  const bufferEndsAt = typeof event.payload.bufferEndsAt === "string" ? event.payload.bufferEndsAt : current.roundState.selectionEndsAt;
@@ -3481,7 +3514,10 @@ function createGameActionsStore(transport) {
3481
3514
  }
3482
3515
  case "game:rps:round:started": {
3483
3516
  const current = store.getState().statesByGameId[event.payload.gameId];
3484
- if (!current || !isRpsState(current)) return;
3517
+ if (!current || !isRpsState(current)) {
3518
+ enqueue(event.payload.gameId, event);
3519
+ return;
3520
+ }
3485
3521
  const actions = {};
3486
3522
  const baseUsers = /* @__PURE__ */ new Set();
3487
3523
  Object.keys(current.roundState.actions).forEach(
@@ -3515,7 +3551,10 @@ function createGameActionsStore(transport) {
3515
3551
  }
3516
3552
  case "game:rps:action:received": {
3517
3553
  const current = store.getState().statesByGameId[event.payload.gameId];
3518
- if (!current || !isRpsState(current)) return;
3554
+ if (!current || !isRpsState(current)) {
3555
+ enqueue(event.payload.gameId, event);
3556
+ return;
3557
+ }
3519
3558
  const updated = {
3520
3559
  ...current,
3521
3560
  roundState: {
@@ -3534,7 +3573,10 @@ function createGameActionsStore(transport) {
3534
3573
  }
3535
3574
  case "game:rps:timer:cutoff": {
3536
3575
  const current = store.getState().statesByGameId[event.payload.gameId];
3537
- if (!current || !isRpsState(current)) return;
3576
+ if (!current || !isRpsState(current)) {
3577
+ enqueue(event.payload.gameId, event);
3578
+ return;
3579
+ }
3538
3580
  const updated = {
3539
3581
  ...current,
3540
3582
  roundState: {
@@ -3548,7 +3590,10 @@ function createGameActionsStore(transport) {
3548
3590
  }
3549
3591
  case "game:rps:round:reveal": {
3550
3592
  const current = store.getState().statesByGameId[event.payload.gameId];
3551
- if (!current || !isRpsState(current)) return;
3593
+ if (!current || !isRpsState(current)) {
3594
+ enqueue(event.payload.gameId, event);
3595
+ return;
3596
+ }
3552
3597
  const actions = {};
3553
3598
  const payloadActions = event.payload.actions;
3554
3599
  Object.keys(payloadActions || {}).forEach((userId) => {
@@ -3572,7 +3617,10 @@ function createGameActionsStore(transport) {
3572
3617
  }
3573
3618
  case "game:rps:round:completed": {
3574
3619
  const current = store.getState().statesByGameId[event.payload.gameId];
3575
- if (!current || !isRpsState(current)) return;
3620
+ if (!current || !isRpsState(current)) {
3621
+ enqueue(event.payload.gameId, event);
3622
+ return;
3623
+ }
3576
3624
  const roundHistory = [
3577
3625
  ...current.roundHistory || [],
3578
3626
  {
@@ -3597,7 +3645,10 @@ function createGameActionsStore(transport) {
3597
3645
  }
3598
3646
  case "game:rps:timeout": {
3599
3647
  const current = store.getState().statesByGameId[event.payload.gameId];
3600
- if (!current || !isRpsState(current)) return;
3648
+ if (!current || !isRpsState(current)) {
3649
+ enqueue(event.payload.gameId, event);
3650
+ return;
3651
+ }
3601
3652
  const timedOutUser = event.payload.playerId;
3602
3653
  const action = event.payload.action;
3603
3654
  const updated = {
@@ -3622,7 +3673,10 @@ function createGameActionsStore(transport) {
3622
3673
  const payload = event.payload;
3623
3674
  const { gameId } = payload;
3624
3675
  const current = store.getState().statesByGameId[gameId];
3625
- if (!current) return;
3676
+ if (!current) {
3677
+ enqueue(gameId, event);
3678
+ return;
3679
+ }
3626
3680
  const updated = isRpsCompletionPayload(payload) && isRpsState(current) ? {
3627
3681
  ...current,
3628
3682
  status: "completed",
@@ -3671,11 +3725,15 @@ function createGameActionsStore(transport) {
3671
3725
  const current = store.getState().statesByGameId[gameId];
3672
3726
  const updated = current ? { ...current, ...incoming } : incoming;
3673
3727
  updateState(gameId, updated);
3728
+ drainQueue(gameId);
3674
3729
  break;
3675
3730
  }
3676
3731
  case "game:rematch:requested": {
3677
3732
  const current = store.getState().statesByGameId[event.payload.gameId];
3678
- if (!current) return;
3733
+ if (!current) {
3734
+ enqueue(event.payload.gameId, event);
3735
+ return;
3736
+ }
3679
3737
  const requestedBy = event.payload.requestedBy;
3680
3738
  const userId = event.payload.userId;
3681
3739
  const requested = new Set(
@@ -3691,7 +3749,10 @@ function createGameActionsStore(transport) {
3691
3749
  }
3692
3750
  case "game:rematch:cancelled": {
3693
3751
  const current = store.getState().statesByGameId[event.payload.gameId];
3694
- if (!current) return;
3752
+ if (!current) {
3753
+ enqueue(event.payload.gameId, event);
3754
+ return;
3755
+ }
3695
3756
  const requestedBy = event.payload.requestedBy ?? [];
3696
3757
  const updated = {
3697
3758
  ...current,
@@ -3702,7 +3763,10 @@ function createGameActionsStore(transport) {
3702
3763
  }
3703
3764
  case "game:rematch:started": {
3704
3765
  const current = store.getState().statesByGameId[event.payload.gameId];
3705
- if (!current) return;
3766
+ if (!current) {
3767
+ enqueue(event.payload.gameId, event);
3768
+ return;
3769
+ }
3706
3770
  const updated = {
3707
3771
  ...current,
3708
3772
  rematchRequestedBy: event.payload.playerIds ?? []
@@ -3713,7 +3777,10 @@ function createGameActionsStore(transport) {
3713
3777
  case "game:pot:updated": {
3714
3778
  const { gameId, totalPotMinor } = event.payload;
3715
3779
  const current = store.getState().statesByGameId[gameId];
3716
- if (!current) return;
3780
+ if (!current) {
3781
+ enqueue(gameId, event);
3782
+ return;
3783
+ }
3717
3784
  const updated = {
3718
3785
  ...current,
3719
3786
  totalPotMinor
@@ -3726,12 +3793,123 @@ function createGameActionsStore(transport) {
3726
3793
  }
3727
3794
  };
3728
3795
  const joinGame = (gameId) => transport.joinRoom(`game:${gameId}`);
3796
+ const getCountdownDigit = (gameId, nowMs) => {
3797
+ const state = store.getState().statesByGameId[gameId];
3798
+ if (!state) return null;
3799
+ if (isRpsState(state)) {
3800
+ if (state.roundState.phase !== "starting") return null;
3801
+ const remaining = new Date(state.roundState.selectionEndsAt).getTime() - nowMs;
3802
+ if (remaining <= 0) return null;
3803
+ return Math.ceil(remaining / 1e3);
3804
+ }
3805
+ const bufferEndsAt = state.bufferEndsAt;
3806
+ if (bufferEndsAt) {
3807
+ const remaining = new Date(bufferEndsAt).getTime() - nowMs;
3808
+ if (remaining <= 0) return null;
3809
+ return Math.ceil(remaining / 1e3);
3810
+ }
3811
+ return null;
3812
+ };
3813
+ const getChessClockTimes = (gameId, nowMs) => {
3814
+ const state = store.getState().statesByGameId[gameId];
3815
+ if (!state || state.gameType !== "chess") return null;
3816
+ const s = state;
3817
+ let whiteMs = s.whiteTimeMs ?? 0;
3818
+ let blackMs = s.blackTimeMs ?? 0;
3819
+ if (s.status === "active" && s.currentPlayerId) {
3820
+ const startedAt = Date.parse(s.turnStartedAt);
3821
+ if (!Number.isNaN(startedAt)) {
3822
+ const elapsed = Math.max(0, nowMs - startedAt);
3823
+ if (s.currentPlayerId === s.whitePlayerId) {
3824
+ whiteMs = Math.max(0, whiteMs - elapsed);
3825
+ } else if (s.currentPlayerId === s.blackPlayerId) {
3826
+ blackMs = Math.max(0, blackMs - elapsed);
3827
+ }
3828
+ }
3829
+ }
3830
+ return { whiteTimeMs: whiteMs, blackTimeMs: blackMs };
3831
+ };
3832
+ const getChessCapturedPieces = (gameId) => {
3833
+ const state = store.getState().statesByGameId[gameId];
3834
+ if (!state || state.gameType !== "chess") return null;
3835
+ const fen = state.fen;
3836
+ if (!fen) return { capturedByWhite: [], capturedByBlack: [] };
3837
+ const placement = fen.split(" ")[0];
3838
+ const white = {};
3839
+ const black = {};
3840
+ for (const char of placement) {
3841
+ if (char === "/" || char >= "1" && char <= "8") continue;
3842
+ const lower = char.toLowerCase();
3843
+ if (char === lower) {
3844
+ black[lower] = (black[lower] ?? 0) + 1;
3845
+ } else {
3846
+ white[lower] = (white[lower] ?? 0) + 1;
3847
+ }
3848
+ }
3849
+ const INITIAL = {
3850
+ p: 8,
3851
+ r: 2,
3852
+ n: 2,
3853
+ b: 2,
3854
+ q: 1,
3855
+ k: 1
3856
+ };
3857
+ const capturedByWhite = [];
3858
+ const capturedByBlack = [];
3859
+ for (const [type, initial] of Object.entries(INITIAL)) {
3860
+ const missingBlack = initial - (black[type] ?? 0);
3861
+ for (let i = 0; i < missingBlack; i++) capturedByWhite.push(`b${type}`);
3862
+ const missingWhite = initial - (white[type] ?? 0);
3863
+ for (let i = 0; i < missingWhite; i++) capturedByBlack.push(`w${type}`);
3864
+ }
3865
+ return { capturedByWhite, capturedByBlack };
3866
+ };
3867
+ const getTicTacToeClockTimes = (gameId, nowMs) => {
3868
+ const state = store.getState().statesByGameId[gameId];
3869
+ if (!state || state.gameType !== "tic-tac-toe") return null;
3870
+ const s = state;
3871
+ let xMs = s.xTimeMs ?? 0;
3872
+ let oMs = s.oTimeMs ?? 0;
3873
+ if (s.status === "active" && s.currentPlayerId) {
3874
+ const currentMark = s.playerMarks[s.currentPlayerId];
3875
+ const startedAt = Date.parse(s.turnStartedAt);
3876
+ if (!Number.isNaN(startedAt)) {
3877
+ const elapsed = Math.max(0, nowMs - startedAt);
3878
+ if (currentMark === "X") xMs = Math.max(0, xMs - elapsed);
3879
+ else if (currentMark === "O") oMs = Math.max(0, oMs - elapsed);
3880
+ }
3881
+ }
3882
+ return { xTimeMs: xMs, oTimeMs: oMs };
3883
+ };
3884
+ const getConnect4ClockTimes = (gameId, nowMs) => {
3885
+ const state = store.getState().statesByGameId[gameId];
3886
+ if (!state || state.gameType !== "connect-four") return null;
3887
+ const s = state;
3888
+ let redMs = s.redTimeMs ?? 0;
3889
+ let yellowMs = s.yellowTimeMs ?? 0;
3890
+ if (s.status === "active" && s.currentPlayerId) {
3891
+ const currentColor = s.playerColors[s.currentPlayerId];
3892
+ const startedAt = Date.parse(s.turnStartedAt);
3893
+ if (!Number.isNaN(startedAt)) {
3894
+ const elapsed = Math.max(0, nowMs - startedAt);
3895
+ if (currentColor === "RED") redMs = Math.max(0, redMs - elapsed);
3896
+ else if (currentColor === "YELLOW")
3897
+ yellowMs = Math.max(0, yellowMs - elapsed);
3898
+ }
3899
+ }
3900
+ return { redTimeMs: redMs, yellowTimeMs: yellowMs };
3901
+ };
3729
3902
  return {
3730
3903
  store,
3731
3904
  setBaseState,
3732
3905
  clearState,
3733
3906
  applyWsEvent,
3734
- joinGame
3907
+ joinGame,
3908
+ getCountdownDigit,
3909
+ getChessClockTimes,
3910
+ getChessCapturedPieces,
3911
+ getTicTacToeClockTimes,
3912
+ getConnect4ClockTimes
3735
3913
  };
3736
3914
  }
3737
3915
  function isRpsState(state) {
@@ -4401,12 +4579,14 @@ var WsRouter = class {
4401
4579
  }
4402
4580
  const decoded = decodeWsEvent(eventName, payload);
4403
4581
  if (!decoded) return;
4404
- this.deps.lobbyStore.applyWsEvent(decoded);
4405
- this.deps.gameStore.applyWsEvent(decoded);
4406
- this.deps.gameActionsStore.applyWsEvent(decoded);
4407
- this.deps.chatStore.applyWsEvent(decoded);
4408
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4409
- this.deps.notificationsStore.applyWsEvent(decoded);
4582
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4583
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4584
+ this.deps.lobbyStore.applyWsEvent(event);
4585
+ this.deps.gameStore.applyWsEvent(event);
4586
+ this.deps.gameActionsStore.applyWsEvent(event);
4587
+ this.deps.chatStore.applyWsEvent(event);
4588
+ this.deps.dmThreadsStore.applyWsEvent(event);
4589
+ this.deps.notificationsStore.applyWsEvent(event);
4410
4590
  });
4411
4591
  }
4412
4592
  stop() {
@@ -4418,12 +4598,14 @@ var WsRouter = class {
4418
4598
  this.transport.subscribeEvent(eventName, (payload) => {
4419
4599
  const decoded = decodeWsEvent(eventName, payload);
4420
4600
  if (!decoded) return;
4421
- this.deps.lobbyStore.applyWsEvent(decoded);
4422
- this.deps.gameStore.applyWsEvent(decoded);
4423
- this.deps.gameActionsStore.applyWsEvent(decoded);
4424
- this.deps.chatStore.applyWsEvent(decoded);
4425
- this.deps.dmThreadsStore.applyWsEvent(decoded);
4426
- this.deps.notificationsStore.applyWsEvent(decoded);
4601
+ const serverTs = payload !== null && typeof payload === "object" ? payload._serverTs : void 0;
4602
+ const event = serverTs !== void 0 ? { ...decoded, _serverTs: serverTs } : decoded;
4603
+ this.deps.lobbyStore.applyWsEvent(event);
4604
+ this.deps.gameStore.applyWsEvent(event);
4605
+ this.deps.gameActionsStore.applyWsEvent(event);
4606
+ this.deps.chatStore.applyWsEvent(event);
4607
+ this.deps.dmThreadsStore.applyWsEvent(event);
4608
+ this.deps.notificationsStore.applyWsEvent(event);
4427
4609
  });
4428
4610
  }
4429
4611
  }
@@ -4826,6 +5008,7 @@ export {
4826
5008
  export_formatMoneyMinor as formatMoneyMinor,
4827
5009
  isRetryableError,
4828
5010
  logger,
5011
+ selectGameLifecycleState,
4829
5012
  export_toMajor as toMajor,
4830
5013
  withRetry
4831
5014
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dimcool/sdk",
3
- "version": "0.1.30",
3
+ "version": "0.1.31",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",