๐ฎ Player Test Access
Test Account Credentials (Ready to Use)
๐ Overview
5018. It provides REST HTTP APIs and a WebSocket endpoint.All protected routes require a JWT token in the
Authorization: Bearer <token> header.
http://SERVER_IP:5018WebSocket:
ws://SERVER_IP:5018/wsContent-Type:
application/json
UnityWebRequest for REST APIs and WebSocket or NativeWebSocket package for real-time game events.
๐ Authentication
| Field | Type | Note |
|---|---|---|
| userName | string | required |
| password | string | required |
Response
{ "success": true, "token": "eyJhbGci..." }
| Field | Type | Note |
|---|---|---|
| userName | string | required |
| password | string | required |
| devicename | string | optional Android / iOS / PC |
Response
{ "success": true, "token": "eyJhbGci..." }
Authorization: Bearer TOKEN| Field | Type | Note |
|---|---|---|
| userName | string | required unique |
| password | string | required |
| referralId | UUID | required parent user UUID |
| role | string | optional player/agent/distributor/superAdmin |
| creditPoint | number | optional starting balance |
| commissionPercentage | number | optional |
| Field | Type | Note |
|---|---|---|
| userName | string | required |
| mobile | string | required |
| newPassword | string | required |
| otp | string | optional |
{ "success": true, "message": "Password updated" }
๐ค Player APIs
All routes under /api/player/* require JWT token. Send as: Authorization: Bearer TOKEN
Response Fields
| Field | Type | Description |
|---|---|---|
| id | UUID | Player UUID |
| userName | string | Username |
| role | string | player / agent / superAdmin |
| creditPoint | number | ๐ฐ Current balance |
| isActive | bool | Account active status |
| type | string | RV or TN (commission mode) |
{
"success": true,
"data": {
"id": "035e2653-50a1-4c68-977b-ffc25badcede",
"userName": "player01",
"role": "player",
"creditPoint": 1500,
"isActive": true,
"isLogin": true,
"type": "RV"
}
}
| Query Param | Type | Default |
|---|---|---|
| page | int | 1 |
| limit | int | 20 (max 100) |
{
"bets": [
{ "id": "uuid", "game": "rouletteTimer40", "betAmount": 100,
"wonAmount": 3600, "winPosition": "7", "createdAt": "..." }
],
"page": 1, "limit": 20
}
| Query Param | Type | Note |
|---|---|---|
| game | string | required game name |
| limit | int | default 15 |
{
"game": "rouletteTimer40",
"results": ["26", "3", "11", "0", "36"],
"x": [1, 1, 1, 1, 1]
}
| Field | Type | Note |
|---|---|---|
| point | number | required |
| comment | string | optional reason |
| fromId | UUID | optional agent UUID |
{ "message": "Point request submitted", "requestId": "uuid" }
| Field | Type | |
|---|---|---|
| oldPassword | string | required |
| newPassword | string | required |
{ "success": true, "message": "Logged out" }
๐ก๏ธ Admin APIs
Requires admin/agent/distributor/superAdmin JWT token.
{ "count": 5, "players": [{ "id":"...", "userName":"player01", "creditPoint": 1500 }] }
{
"gameName": "spinToWin",
"totalCollection": 50000,
"totalPayment": 43200,
"lastResults": ["3","7","1"],
"xs": [1,1,1]
}
| Param | Type | Note |
|---|---|---|
| role | string | player/agent/distributor (optional filter) |
| page | int | default 1, 50 per page |
| Field | Type | Note |
|---|---|---|
| amount | number | required |
| op | string | "add" (default) or "set" |
{ "isActive": false }
โก WebSocket Connection
ws://SERVER_IP:5018/wsAll WebSocket messages are JSON in this format:
Client โ Server:
{ "event": "eventName", "data": { ... } }Server โ Client:
{ "event": "eventName", "data": { ... }, "status": 1 }
WebSocket ws = new WebSocket("ws://SERVER_IP:5018/ws");
Unity Connection Code Example
// 1. Connect
WebSocket ws = new WebSocket("ws://SERVER_IP:5018/ws");
await ws.Connect();
// 2. Send event
string msg = JsonConvert.SerializeObject(new {
@event = "join",
data = new { token = playerToken, gameName = "rouletteTimer40" }
});
await ws.SendText(msg);
// 3. Receive
ws.OnMessage += (bytes) => {
var text = System.Text.Encoding.UTF8.GetString(bytes);
var msg = JsonConvert.DeserializeObject<SocketMsg>(text);
Debug.Log($"Event: {msg.@event}, Data: {msg.data}");
};
๐ค Client โ Server Events
| Field | Type | Note |
|---|---|---|
| token | string | required JWT token from login |
| gameName | string | required see game list below |
// Send:
{ "event": "join", "data": { "token": "eyJ...", "gameName": "rouletteTimer40" } }
// Response event: "join"
{
"event": "join",
"data": {
"creditPoint": 1500,
"gameName": "rouletteTimer40",
"gamelastdata": ["26","3","11","7","0"],
"x": [1,1,1,1,1],
"gameState": { "positions": {}, "adminBalance": 0 }
},
"status": 1
}
Send this on every WebSocket open โ for fresh logins and reconnects. Server checks the reconnect-grace buffer; if the user dropped within the last 45s their previous game room is auto-restored. See Reconnect & Grace for full flow.
// Request
{ "event": "checkLogin", "data": { "token": "eyJ..." } }
Server emits ONE of the following sequences:
(A) Resume successful โ player was in a room and reconnected within 45s
{ "event": "state_snapshot", "data": {
"room": "rouletteTimer40", "resumed": true,
"creditPoint": 1500.0,
"state": { /* current round state from GameEngine */ },
"gamelastdata": ["7","26","11"], "x": [1,1,1]
} }
{ "event": "session_status", "data": { "resumed": true, "room": "rouletteTimer40" } }
Unity action: restore game UI from state_snapshot. Do NOT send join again.
(B) Fresh login OR grace expired (token still valid)
{ "event": "session_status", "data": { "resumed": false, "room": "" } }
Unity action: go to lobby / game-select screen. User picks a game โ send join normally.
(C) Token expired or invalid
{ "event": "error", "data": { "message": "Invalid token" } }
Unity action: clear stored token โ login screen.
(D) Another device hijacked the session
{ "event": "logout", "data": { "message": "Another device login" } }
Unity action: clear stored token โ login screen with warning.
| Field | Type | Note |
|---|---|---|
| playerId | UUID | required |
| gameName | string | required |
| betPoint | number | required total bet amount |
| position | object | required { "7": 100, "red": 50 } |
// Send:
{
"event": "placeBet",
"data": {
"playerId": "035e2653-50a1-4c68-977b-ffc25badcede",
"gameName": "rouletteTimer40",
"betPoint": 150,
"position": { "7": 100, "red": 50 }
}
}
// Response event: "placeBet"
{ "event": "placeBet", "data": { "handId": "bet-uuid", "gameName": "rouletteTimer40", "result": "Bet placed successfully" }, "status": 1 }
| Field | Type | Note |
|---|---|---|
| playerId | UUID | required |
| gameName | string | "andarBahar" |
| betPoint | number | required |
| position | object | { "side": "A" } or { "side": "B" } |
| round | int | 1 or 2 |
{ "event": "placeBetAB", "data": { "playerId": "...", "gameName": "andarBahar", "betPoint": 100, "position": { "side": "A" }, "round": 1 } }
// Send:
{ "event": "placeBetRoulette", "data": { "playerId": "...", "betPoint": 200, "position": { "7": 100, "red": 50, "even": 50 } } }
// Instant Response:
{ "event": "result", "data": { "handId": "uuid", "gameName": "roulette", "data": "7", "winAmount": 3600 } }
{ "event": "placeBetManualSpin", "data": { "playerId": "...", "betPoint": 100, "position": { "5": 100 } } }
// Instant:
{ "event": "result", "data": { "handId": "uuid", "gameName": "manualSpin", "data": "5", "winAmount": 900 } }
{ "event": "joinAdmin", "data": { "adminId": "admin-uuid", "gameName": "rouletteTimer40" } }
// Response:
{ "event": "resAdmin", "data": { "numbers": [...], "totalCollection": 5000, "totalPayment": 4200 } }
{ "event": "winByAdmin", "data": { "gameName": "rouletteTimer40", "cardNumber": 7, "y": 1.0 } }
{ "event": "balanceupdate", "data": { "userid": "player-uuid" } }
// Response: "UPDATED_WALLET" event with new balance number
๐ฅ Server โ Client Events
These events are pushed by the server. Unity should listen for all of them.
| Event | When | Key Data Fields |
|---|---|---|
| session_status | Always after checkLogin | resumed (bool), room (string) |
| state_snapshot | Reconnect within 45s grace | room, resumed, creditPoint, state, gamelastdata[], x[] |
| join | After successful join | creditPoint, gameName, gamelastdata[], x[], gameState, gameId |
| newRound | New round starts (timer games) | gameName, gamelastdata[], x[], state |
| betClosed | Betting window closed | gameName |
| round2 | Andar Bahar round 2 starts | gameName, round: 2 |
| result | Round result announced | gameName, data (winning number), x, gamelastdata[] |
| win | Round won (manual roulette / instant games) | handId, gameName, data, winAmount |
| placeBet | Bet placed confirmation | handId (betID), gameName, result (message) |
| UPDATED_WALLET | Balance changed | new balance (number) |
| logout | Account blocked / force-kick / another device / idle | message |
| maintenance | Server entered maintenance mode | message |
| error | Any error (invalid token, bad bet, etc.) | message |
| boop | Reply to beep keepalive | (empty) |
| resAdmin | Admin join response | numbers[], totalCollection, totalPayment |
| resAdminResult | Round result (admin room) | gameName, result |
| resAdminBetData | Bet placed (admin room) | gameName, totalCollection, totalPayment |
| version | App version info | android (url), exe (url), version |
Timer Game Round Flow (rouletteTimer40 example)
[0s] Server โ "newRound" { gameName, gamelastdata, state }
[0-45s] Player โ "placeBet" { position, betPoint }
[45s] Server โ "betClosed" { gameName }
[53s] Server โ "result" { data: "7", x: 1, gamelastdata: ["7","26",...] }
Server โ "UPDATED_WALLET" 1400 (only to winner)
[53s] Next round begins โ "newRound" again
Andar Bahar Round Flow
[0s] Server โ "newRound" { gameName: "andarBahar", round: 1 }
[0-30s] Player โ "placeBetAB" { side: "A", betPoint: 100 }
[30s] Server โ "betClosed" { round: 1 }
[30s] Server โ "round2" { gameName, round: 2 }
[30-45s] Player โ "placeBetAB" { side: "B", betPoint: 50 }
[45s] Server โ "betClosed" { round: 2 }
[45s] Server โ "result" { data: "A" or "B" } pays 2x
๐ Reconnect & Grace Period
When a mobile app goes to background the OS freezes / kills the WebSocket within ~30s. To avoid kicking the player out of an in-progress round, the server holds disconnected players in a 45-second reconnect-grace buffer before running the real cleanup. If the player reconnects within that window, the previous game room and session are restored seamlessly.
45 seconds (constant reconnectGrace in internal/hub/hub.go)What is buffered: only role=
player sockets. Admins and unauthed sockets clean up instantly.What is NOT buffered: ForceKick / KickAllForMaintenance / 15-minute idle logout โ these bypass the grace and clean up immediately.
Server-side behavior on disconnect
| Step | Action |
|---|---|
| 1 | Player socket dies (background / network drop / pong timeout after 60s) |
| 2 | Player removed from broadcast room (no point sending newRound/result to a dead socket) |
| 3 | userClients[userID] entry kept; is_login=true in DB; is_online=true in Redis |
| 4 | Pending entry created with 45s timer |
| 5a | Reconnect within 45s: timer cancelled, new socket auto-rejoins previous room, state_snapshot + session_status emitted |
| 5b | Timer expires: full cleanup โ DelOnline, SetLogout, RecordLogout(reason=disconnect), removed from userClients |
Unity reconnect flow
[App background]
โ (close socket explicitly on OnApplicationPause(true))
Server: buffer player โ 45s grace timer started
[App foreground, 30s later]
โ open new WebSocket
โ Send: { "event": "checkLogin", "data": { "token": "..." } }
โ
Server matches userID against pending buffer:
โโโ Found โ emit "state_snapshot" + "session_status" {resumed:true}
โโโ Not found โ emit "session_status" {resumed:false}
โโโ Bad token โ emit "error" {message:"Invalid token"}
โโโ Hijacked โ emit "logout" {message:"Another device login"}
[Unity reads session_status]
โโโ resumed:true โ restore game UI from state_snapshot โ DONE
โโโ resumed:false โ go to lobby, send "join" event
โโโ error/logout โ clear token, return to login
Unity SocketManager pseudocode
void OnApplicationPause(bool paused) {
if (paused) {
socket?.Close(); // clean signal to server
} else {
StartCoroutine(ReconnectWithBackoff()); // 1s, 2s, 4s, 8s, 15s
}
}
void OnMessage(SocketMsg msg) {
switch (msg.@event) {
case "state_snapshot":
GameUI.RestoreFromSnapshot(msg.data); break;
case "session_status":
if (!msg.data.resumed && wasInGame) GoToLobby(); break;
case "error":
if (msg.data.message == "Invalid token") ClearTokenAndGoToLogin(); break;
case "logout":
ClearTokenAndGoToLogin(msg.data.message); break;
case "maintenance":
ShowMaintenanceScreen(msg.data.message); break;
// ... game events: newRound, betClosed, result, win, UPDATED_WALLET ...
}
}
โข On reconnect, send only
checkLogin โ NOT join. The server will auto-rejoin the room and push state_snapshot.โข Always wait for
session_status before deciding next screen โ it is the deterministic signal that resume succeeded or not.โข If
session_status doesn't arrive within 3 seconds of checkLogin, treat as network failure and retry.โข Server is authoritative โ disable bet UI on
betClosed, don't rely on client-side timers.
Server log signatures (for debugging)
// Disconnect โ buffered: Client disconnected (buffered 45s): <userID> room=rouletteTimer40 // Reconnect within grace: Reconnect within grace: user=<userID> prev_room=rouletteTimer40 // Grace expired (no reconnect within 45s): Reconnect grace expired: user=<userID> โ full cleanup done
Live monitor: journalctl -u fairdealplus-go -f | grep -E "buffered|grace|Reconnect"
๐งช WebSocket Live Tester
Click to send pre-built events:
๐ฒ Game List
| Game Name | Type | Timer | Positions | Payout |
|---|---|---|---|---|
| rouletteTimer40 | Timer | 53s round | 0-36 (37 nums) + colors/dozens | 36x single, 2x color |
| rouletteTimer60 | Timer | 73s round | 0-36 (37 nums) + colors/dozens | 36x single, 2x color |
| spinToWin | Timer | 60s round | 1-9 | 9x |
| funTarget | Timer | 60s round | 1-9 | 9x |
| funRoulette | Timer | 60s round | 0-36 | 36x / 2x |
| andarBahar | Timer | 30s+15s | side: "A" or "B" | 2x |
| roulette | Manual | Instant | 0-36 + red/black/even/odd/dozens/cols | 36x / 2x / 3x |
| manualSpin | Manual | Instant | 1-9 | 9x |
๐ฐ Roulette Position Reference
| Key | Description | Payout |
|---|---|---|
| "0" to "36" | Single number | 36x |
| "red" | Red numbers | 2x |
| "black" | Black numbers | 2x |
| "even" | Even numbers (2,4,6...) | 2x |
| "odd" | Odd numbers | 2x |
| "low" | 1-18 | 2x |
| "high" | 19-36 | 2x |
| "dozen1" | 1-12 | 3x |
| "dozen2" | 13-24 | 3x |
| "dozen3" | 25-36 | 3x |
| "col1" | Column 1 (1,4,7...) | 3x |
| "col2" | Column 2 (2,5,8...) | 3x |
| "col3" | Column 3 (3,6,9...) | 3x |
Example Multi-position Bet
{
"event": "placeBet",
"data": {
"playerId": "035e2653-50a1-4c68-977b-ffc25badcede",
"gameName": "rouletteTimer40",
"betPoint": 350,
"position": {
"7": 100, // 100 on number 7 โ wins 3600 if 7 hits
"red": 100, // 100 on red โ wins 200 if red hits
"odd": 100, // 100 on odd โ wins 200 if odd hits
"dozen1": 50 // 50 on 1-12 โ wins 150 if 1-12 hits
}
}
}