Player Sessions

This guide covers the player session tracking system — how poker play sessions are recorded, how table join/leave events are tracked, and how session statistics are calculated.

Not to be confused with auth sessions (login/JWT). Player sessions track time spent playing poker at tables.

Architecture Overview

PlayerSession                — one per continuous play period
  └── PlayerSessionTable[]   — one per table joined during the session

Source files:

FilePurpose
src/services/player-session.service.tsCore service: lifecycle, stats, history, orphan healing

Data Model

PlayerSession

FieldTypeDescription
idstringPrimary key
userIdstringOwner
startedAtDateTimeWhen the session began
endedAtDateTime?When the session ended (null = active)
totalBuyInDecimalSum of all table buy-ins
totalCashOutDecimalSum of all table cash-outs
handsPlayedintHands played during session
rakeDecimalTotal rake paid

PlayerSessionTable

FieldTypeDescription
idstringPrimary key
sessionIdstringFK → PlayerSession (cascade delete)
tableIdstringTable identifier (no database relation)
joinedAtDateTimeWhen the player sat down
leftAtDateTime?When the player left (null = still seated)
buyInDecimalAmount bought in for
cashOutDecimal?Amount cashed out

Session Lifecycle

1. Start Session

import { startSession, getOrCreateActiveSession } from './services/player-session.service.js';

// Explicit start
const session = await startSession(userId);

// Or get-or-create (idempotent)
const session = await getOrCreateActiveSession(userId);

A user has at most one active session at a time.

2. Join / Leave Tables

import { addTableToSession, leaveTable } from './services/player-session.service.js';

// Player sits down
const sessionTable = await addTableToSession({
  sessionId: session.id,
  tableId: 'table-uuid',
  buyIn: 100,
});

// Player stands up
await leaveTable(sessionTable.id, cashOut);

3. Accumulate Stats

import { incrementHandsPlayed, addRake } from './services/player-session.service.js';

await incrementHandsPlayed(sessionId);     // +1 hand (optional count argument)
await addRake(sessionId, rakeAmount);       // accumulate rake

4. End Session

Sessions end in two ways:

  • Auto-end: checkAndEndSessionIfComplete(userId) ends the active session when all its tables have leftAt set. The WebSocket layer calls it when a player leaves a table.
  • Manual: endSession(sessionId) calculates final stats and closes the session.

EndSessionResult includes totalBuyIn, totalCashOut, netProfit, handsPlayed, rake, durationMinutes, tableCount, hourlyRate (null for sessions under a minute), biggestWin and biggestLoss. Ending a session that has already ended returns its stats without changing it.

Orphan Healing

checkAndHealActiveTables(userId) detects and auto-closes orphaned PlayerSessionTable records — entries that claim leftAt = null but have no corresponding active TableSession (e.g., after a server crash).

This function:

  1. Batch-fetches active TableSession records for all claimed-active tables (single query)
  2. Closes orphaned PlayerSessionTable records in a batch updateMany, setting leftAt to now and cashOut to 0
  3. Ends the parent PlayerSession if all tables were orphaned

It returns true when the player has at least one genuinely active table. The withdrawal and transfer services call it to verify a player is not seated before allowing financial operations.

Naming note: This function was previously named hasActiveTables. The old name is still exported as a deprecated alias, and the withdrawal and transfer services still import it under that name.

Session History API

List Sessions

getSessionList(userId, filters, pagination) returns paginated session history with optional filters:

FilterTypeDescription
startDateDate?Sessions started after this date
endDateDate?Sessions started before this date
result'win' | 'loss' | 'all'Filter by net profit sign
tableModestring?Filter by table mode (REAL_MONEY, VIRTUAL_CHIPS, TOURNAMENT)

Aggregate Stats

getSessionAggregateStats(userId, filters) returns lifetime statistics:

  • Total/completed sessions, total profit, total hands played, total rake, total duration
  • Win/loss/break-even counts and win rate
  • Average hourly rate and session duration
  • Biggest single-session win and loss

Both are exposed to players by src/api/routes/user/sessions.routes.ts as GET /poker-sessions and GET /poker-sessions/stats, with single-session details at GET /poker-sessions/:id.

Usage by Other Services

ConsumerUsage
src/services/withdrawal.service.tsBlocks withdrawals while player is seated (hasActiveTables alias)
src/services/transfer.service.tsBlocks transfers while player is seated (hasActiveTables alias)
src/websocket/game-events/player-actions.tsAdds a table on join (addTableToSession) and ends completed sessions on leave (checkAndEndSessionIfComplete)
src/websocket/table-manager.tsCounts hands (incrementHandsPlayed)
Try the live demo Pricing

Have questions about Poker Engine? We reply fast.