@@ -4,21 +4,14 @@ import { CONFIG } from '../config.js';
44import { DrawPayload , DrawBatchPayload , AuthenticatedSocket } from './types.js' ;
55import { LobbyService } from '../services/lobby.service.js' ;
66import jwt from 'jsonwebtoken' ;
7+ import { getLobbyUserCount , getUsersInLobby , broadcastToLobby , broadcastToOthers } from '../utils/socketUtils.js' ;
78
89export const setupSocket = ( io : Server ) => {
9- // Authentication Middleware
1010 io . use ( ( socket , next ) => {
1111 const token = socket . handshake . auth . token ;
12-
13- if ( ! token ) {
14- return next ( new Error ( "Authentication error: No token provided" ) ) ;
15- }
16-
12+ if ( ! token ) return next ( new Error ( "Authentication error: No token provided" ) ) ;
1713 jwt . verify ( token , CONFIG . JWT . SECRET , ( err : any , decoded : any ) => {
18- if ( err ) {
19- return next ( new Error ( "Authentication error: Invalid token" ) ) ;
20- }
21- // Attach user info to socket
14+ if ( err ) return next ( new Error ( "Authentication error: Invalid token" ) ) ;
2215 ( socket as AuthenticatedSocket ) . user = decoded ;
2316 socket . data . user = decoded ;
2417 next ( ) ;
@@ -28,111 +21,66 @@ export const setupSocket = (io: Server) => {
2821 io . on ( 'connection' , ( socket : Socket ) => {
2922 console . log ( `[Socket] New connection: ${ socket . id } ` ) ;
3023
31- // --- EVENT: JOIN_LOBBY ---
32- // User requests to enter a specific room
3324 socket . on ( CONFIG . EVENTS . CLIENT . JOIN_LOBBY , async ( lobbyName : string ) => {
34- console . log ( `[Socket] ${ socket . id } joining lobby: ${ lobbyName } ` ) ;
25+ try {
26+ const user = ( socket as AuthenticatedSocket ) . user ;
27+ if ( ! user ?. id ) {
28+ console . error ( `[Socket] User not found: ${ socket . id } ` ) ;
29+ socket . emit ( CONFIG . EVENTS . SERVER . ERROR , { message : "User not authenticated" } ) ;
30+ socket . disconnect ( true ) ;
31+ return ;
32+ }
3533
36- // 1. Join the Socket.io room channel
37- socket . join ( lobbyName ) ;
34+ const lobby = await LobbyService . getByName ( lobbyName ) ;
35+ if ( ! lobby ) return socket . emit ( CONFIG . EVENTS . SERVER . ERROR , { message : "Lobby not found" } ) ;
3836
39- const user = ( socket as AuthenticatedSocket ) . user ;
40- if ( ! user ) {
41- console . error ( `[Socket] Error: User not found on socket ${ socket . id } ` ) ;
42- return ;
43- }
37+ try {
38+ LobbyService . validateJoinAccess ( lobby , user . id ) ;
39+ } catch ( e : any ) {
40+ return socket . emit ( CONFIG . EVENTS . SERVER . ERROR , { message : e . message } ) ;
41+ }
4442
45- try {
46- // 2. Broadcast to others that a new user joined (First, to avoid race conditions)
47- socket . to ( lobbyName ) . emit ( CONFIG . EVENTS . SERVER . USER_JOINED , user ) ;
43+ socket . join ( lobbyName ) ; // Optimistic Join
4844
49- // 3. Send list of connected users to the new user
50- // We fetch sockets AFTER joining so the user is included in the list
51- const sockets = await io . in ( lobbyName ) . fetchSockets ( ) ;
52- const users = sockets . map ( s => s . data . user ) . filter ( u => u ) ;
53- socket . emit ( CONFIG . EVENTS . SERVER . LOBBY_USERS , users ) ;
45+ const currentCount = getLobbyUserCount ( io , lobbyName ) ;
46+ try {
47+ LobbyService . validateCapacity ( lobby , currentCount - 1 ) ;
48+ } catch ( e : any ) {
49+ socket . leave ( lobbyName ) ;
50+ return socket . emit ( CONFIG . EVENTS . SERVER . ERROR , { message : "Lobby is full" } ) ;
51+ }
5452
55- // 4. Get current state from Service & Send to user
56- const state = await CanvasService . getState ( lobbyName ) ;
57- socket . emit ( CONFIG . EVENTS . SERVER . INIT_STATE , state ) ;
53+ broadcastToOthers ( socket , lobbyName , CONFIG . EVENTS . SERVER . USER_JOINED , user ) ;
54+ socket . emit ( CONFIG . EVENTS . SERVER . LOBBY_USERS , await getUsersInLobby ( io , lobbyName ) ) ;
55+ socket . emit ( CONFIG . EVENTS . SERVER . INIT_STATE , await CanvasService . getState ( lobbyName ) ) ;
56+ console . log ( `[Socket] ${ socket . id } joined ${ lobbyName } ` ) ;
5857 } catch ( error ) {
59- console . error ( `[Socket] Error joining lobby ${ lobbyName } :` , error ) ;
58+ console . error ( `[Socket] Join Error :` , error ) ;
6059 socket . emit ( CONFIG . EVENTS . SERVER . ERROR , { message : "Failed to join lobby" } ) ;
6160 }
6261 } ) ;
6362
64- // --- EVENT: DRAW ---
65- // User wants to color a pixel
66- socket . on ( CONFIG . EVENTS . CLIENT . DRAW , ( payload : DrawPayload ) => {
67- // Payload validation could happen here or in a DTO
68- const { lobbyName, x, y, color } = payload ;
69-
70- if ( ! lobbyName ) return ;
71-
72- // 1. Process Logic via Service
73- const success = CanvasService . draw ( lobbyName , x , y , color ) ;
74-
75- // 2. Broadcast if successful
76- if ( success ) {
77- // Send to everyone in the room INCLUDING the sender (for consistency)
78- io . to ( lobbyName ) . emit ( CONFIG . EVENTS . SERVER . PIXEL_UPDATE , { x, y, color } ) ;
63+ socket . on ( CONFIG . EVENTS . CLIENT . DRAW , ( { lobbyName, x, y, color } : DrawPayload ) => {
64+ if ( lobbyName && CanvasService . draw ( lobbyName , x , y , color ) ) {
65+ broadcastToLobby ( io , lobbyName , CONFIG . EVENTS . SERVER . PIXEL_UPDATE , { x, y, color } ) ;
7966 }
8067 } ) ;
8168
82- // --- EVENT: DRAW_BATCH ---
83- // User wants to color multiple pixels (stroke)
84- socket . on ( CONFIG . EVENTS . CLIENT . DRAW_BATCH , ( payload : DrawBatchPayload ) => {
85- const { lobbyName, pixels } = payload ;
86- // pixels: { x, y, color }[]
87-
69+ socket . on ( CONFIG . EVENTS . CLIENT . DRAW_BATCH , ( { lobbyName, pixels } : DrawBatchPayload ) => {
8870 if ( ! lobbyName || ! Array . isArray ( pixels ) ) return ;
89-
90- const successfulUpdates : any [ ] = [ ] ;
91-
92- // 1. Process Logic via Service for each pixel
93- for ( const p of pixels ) {
94- // Validation: ensure p is an object and has required properties
95- if ( ! p || typeof p !== 'object' || typeof p . x !== 'number' || typeof p . y !== 'number' || typeof p . color !== 'number' ) {
96- continue ;
97- }
98- const { x, y, color } = p ;
99- const success = CanvasService . draw ( lobbyName , x , y , color ) ;
100- if ( success ) {
101- successfulUpdates . push ( { x, y, color } ) ;
102- }
103- }
104-
105- // 2. Broadcast batch if any successful
106- if ( successfulUpdates . length > 0 ) {
107- // Send to everyone in the room INCLUDING the sender (for consistency)
108- io . to ( lobbyName ) . emit ( CONFIG . EVENTS . SERVER . PIXEL_UPDATE_BATCH , { pixels : successfulUpdates } ) ;
109- }
71+ const updates = CanvasService . drawBatch ( lobbyName , pixels ) ;
72+ if ( updates . length ) broadcastToLobby ( io , lobbyName , CONFIG . EVENTS . SERVER . PIXEL_UPDATE_BATCH , { pixels : updates } ) ;
11073 } ) ;
11174
112-
113- // --- DISCONNECTING ---
11475 socket . on ( 'disconnecting' , async ( ) => {
115- // Notify rooms that user is leaving
11676 for ( const room of socket . rooms ) {
117- if ( room !== socket . id ) {
118- socket . to ( room ) . emit ( CONFIG . EVENTS . SERVER . USER_LEFT , ( socket as AuthenticatedSocket ) . user ) ;
119-
120- // Check if room is empty (excluding this socket)
121- const socketsInRoom = await io . in ( room ) . fetchSockets ( ) ;
122- const remainingUsers = socketsInRoom . length - 1 ; // fetchSockets includes the disconnecting socket
123-
124- if ( remainingUsers <= 0 ) {
125- // Room is empty, unload from hot storage
126- await CanvasService . unloadLobby ( room ) ;
127- }
128- }
77+ if ( room === socket . id ) continue ;
78+ broadcastToOthers ( socket , room , CONFIG . EVENTS . SERVER . USER_LEFT , ( socket as AuthenticatedSocket ) . user ) ;
79+ const users = await getUsersInLobby ( io , room ) ;
80+ if ( users . length - 1 <= 0 ) await CanvasService . unloadLobby ( room ) ;
12981 }
13082 } ) ;
13183
132- // --- DISCONNECT ---
133- socket . on ( 'disconnect' , ( ) => {
134- // Cleanup logic if needed (e.g., updating user count)
135- console . log ( `[Socket] Disconnected: ${ socket . id } ` ) ;
136- } ) ;
84+ socket . on ( 'disconnect' , ( ) => console . log ( `[Socket] Disconnected: ${ socket . id } ` ) ) ;
13785 } ) ;
13886} ;
0 commit comments