Socket.IO event catalogue
Reference for the files delivered in Main_File. Documentation updated 22 September 2026. Examples use fictional IDs and balances; replace them with values from your installation.
Connection and delivery model
Connect with a Socket.IO client compatible with the Node server, using the normal /socket.io/
path. The shipped server listens over HTTP behind your reverse proxy. PHP publishes envelopes to Redis channel
update; Node dispatches their msg as a Socket.IO payload. Clients do not receive the
Redis envelope.
{"channel":"room","event":"roomMessage","id":7,"rid":null,
"msg":{"type":"normal","text":"Hello","msg":"","person":{"uid":42}}}
Routing: user + id goes to user:{id}; room + positive id goes to room:{id}; room +
id=0 goes to the shared room channel; app goes to app. The user's PHP enterRoom event
is intercepted by Node to join the socket and broadcast the arriving user to room listeners.
Transport caveat in the supplied Flutter source: controllers/socket_manager.dart constructs
http://$ip:$port. Setting port 443 does not turn that string into HTTPS/WSS. A TLS-only deployment
must update that client URL construction to use HTTPS, then test the release build. Do not weaken iOS transport
security to work around it. The example below explicitly uses HTTPS.
No custom event uses an acknowledgement callback. Confirm a mutation using its PHP response; events can be missed while disconnected. Do not assume replay, ordering across PHP requests, or a durable queue. GamePix uses HTTP catalogue/configuration requests and an embedded WebView, with no dedicated game socket events; see GamePix setup.
Client → server events
| Event | Payload and example | When / result |
|---|---|---|
| conn | token:string required; apiKey:string required; prefix:string optional (compatibility, unused by
server); user:object optional (alias fullUser); rid optional but avoid it in this version.
|
Emit after each transport connection. Looks up the PHP token in Redis; joins user:{uid} and app. Replies appCon=1 or loginFailed. The optional rid branch currently calls joinRoom without user and can throw; enter via PHP after authentication. |
| joinRoom | rid:int/string required (alias room); user:object required by the implementation.
|
After conn. Joins room:{rid} and room, updates Redis presence, broadcasts enterRoom. This is socket membership, not room-password/ban authorization. Prefer PHP live/enterRoom, which supplies the validated entry payload. Do not call both and count duplicate arrival events. |
| leaveRoom | rid:int/string optional, defaults to last joined room.
|
Removes socket channel membership/presence. No explicit response event. Also POST live/leaveRoom to update database membership/seats. A PHP leave alone does not call Node's leaveRoom handler. |
| disconnect (transport lifecycle) | No application payload to send. | Close/disconnect the Socket.IO client; Node removes its tracked presence. An abrupt process failure is not guaranteed to execute cleanup. This is not a custom business event. |
loginSuccess is an internal Node EventEmitter event, not a wire event. Do not wait for it on the
client. There are no server socket.on handlers named sendGift, seatTake, seatMic or sendMessage.
Server → client event catalogue
Object shapes below list fields and types. User, Room, Seat, VIP and Box use the PHP resource dictionary. Numeric examples and shortened nested resources are illustrative. Arrays are sent directly where indicated.
| Event | Destination / trigger | Payload |
|---|---|---|
| appCon | Connecting socket, accepted conn | Integer 1, not {status:1}. |
| loginFailed | Connecting socket | {message:string}; exact messages: Invalid API key, Token is required, Invalid token, Internal server error. |
| userData | Connecting socket, only when conn.user/fullUser supplied | The supplied user object, echoed. It is not a new authoritative profile fetch. |
| forceLogout | Previous user's active socket after new login | {reason:"new_device_login"}; followed by socket disconnect. Clear the old session; do not reconnect using its revoked token. |
| enterRoom | Room listeners, PHP startLive/enterRoom or direct joinRoom | User object with users_count:int added, e.g. {uid:42,name:"Demo",users_count:2}. |
| leaveRoom | Room listeners, PHP live/leaveRoom | {uid:int,users_count:int,oldSeat:-1}. Direction differs from the identically named client leaveRoom request. |
| roomData | Room, room/updateRoom | Room object directly, not {room:Room}. |
| roomSeats | Room, seat actions, seat count changes, departure or gift score changes | Seat[] directly. Each seat: {pos:int,score:int,isHost:bool,percentageSize:1,user:User|null,mute:int,cam_state:int,state:int}. |
| seatRequest | Room, restricted seat requested | {type:"request",seat:int,user:User}. Manager accepts/rejects through live/seatRequestAccept. |
| seatEmoji | Room, live/sendEmoji | {seat:int,name:string}. |
| roomMessage | Room, live/sendMessage | {type:"normal",text:string,msg:string,person:User}. text uses request msg; legacy output msg uses request message/content. |
| roomGift | Room or all joined room sockets when gift.is_all_notify=1 | GiftEvent[]; one entry per recipient. Fields: uid,uPos,uRule,touid,toPos,toRule,gid,count,rid,liveuid,is_all_notify,coin_number:int; uName,toName,name:string; uAvatar,toAvatar,img,anim:image|null; sender,to:User. liveuid is the room owner UID here, while rid is the room ID. |
| clearChat | Room, live/clearChat | {rid:int}; clear local displayed chat. |
| clearRoomGifts | Room, live/clearRoomGifts | {rid:int}; refresh/reset room gift counters. |
| userAction | Room, live/userAction | {uid:int,rule:int,expires_at:string|null}. This is room membership, not dashboard permissions. |
| rocketProgress | Room, gift increases progress without a tier transition | {rocket:object|null,score:int,old_score:int,win:{top3:[],list:[]}}. Rocket includes level,lvl,points,score,nextPoints,nextLevel:int and icon/global/anim/icon_anim/name:string. |
| rocket | All joined room sockets, gift crosses a tier | Same progress keys plus win:{top3:array,list:array}, optional level:object (reward tier), room:Room, live:Room. Inspect room.id before applying a global event to the current room. |
| redBox | Room; all rooms when box type=2 | {box:Box}. Box liveuid equals room ID in this payload. |
| redBoxUse | Room, successful claim | {uid:int,liveuid:int,rid:int,box_id:int,reward:int,coins:int,usersCount:int}. coins is remaining box coins, not claimant wallet balance; usersCount is claimed-slot count. |
| updateUserData | Recipient user, cash/sendCoins | {coins:int}. Merge supplied fields; do not replace the entire user model. |
| privateMessage | Other participant; message, read, deletion, reaction changes | Discriminated object; see variants below. |
| stopRoom | Room, admin room closure service | Owner user payload directly. Leave voice session, stop rendering the closed room and refresh available rooms. |
privateMessage variants
{"type":"message","chat_id":8,"message":{"id":10,"chat_id":8,"uid":42,"content":"Hello"}}
{"type":"deleteMessage","messageID":10,"chat_id":8,"last_message":{"id":9,"chat_id":8}}
{"type":"reactions","msgID":10,"chat":{"id":8,"chat_id":8},"reactions":[]}
{"type":"messageState","chat_id":8,"message_ids":[10],"state":2,"target_state":2}
For the reactions socket variant, chat.id is overwritten with the conversation ID by the sender; use msgID for the reacted message. A deleted conversation's preview can be a fallback message when no surviving message exists.
Working sequence and verification
// Example for an integration using socket.io-client.
const socket = io('https://socket.example.com', {
transports: ['websocket'], autoConnect: false
});
socket.on('connect', () => socket.emit('conn', {
token: session.token, apiKey: API_KEY, prefix: 'tomchat'
}));
socket.on('appCon', async (value) => {
if (value !== 1) return;
// POST /api/live/enterRoom with Auth-Token and {rid:7,password:...}.
// Require code=200 AND data.state="ok" before joining Agora.
});
socket.on('roomSeats', seats => renderSeats(seats));
socket.on('roomMessage', message => appendMessage(message));
socket.on('roomGift', gifts => renderGifts(gifts));
socket.on('forceLogout', () => { socket.disconnect(); clearSession(); });
socket.connect();
- Sign in through PHP. Register listeners before calling socket.connect.
- Wait for appCon=1, then enter the room through PHP. Node receives the Redis event and joins the socket to that room.
- POST live/seatTake, live/sendMessage or live/sendGift; check the PHP outcome, then observe roomSeats, roomMessage or roomGift on a second client.
- To leave, POST live/leaveRoom then emit leaveRoom with the same rid. Release Agora locally.
- On reconnect, reauthenticate conn and refresh authoritative PHP state. Re-enter only after appCon. Do not replay purchases/gifts.
Source map: node_server/s1.js and src/handlers/user.js define routing and direct handlers; PHP LiveAppController, RoomAppController, ChatAppController, LiveboxAppController, CashAppController and AuthAppController publish business events; AdminRoomClosureService publishes stopRoom. Flutter's forceReconnect/reconnect are local event-emitter signals, not server catalogue entries.
Disconnect recovery
The last socket disconnect schedules room cleanup after 60 seconds. Entering the same room through PHP startLive or enterRoom cancels its Redis job; socket reauthentication or entering another room preserves it. See shared-secret configuration and callback protection.