@@ -11,17 +11,25 @@ import { ClientProxy } from '@nestjs/microservices';
11
11
import { Inject } from '@nestjs/common' ;
12
12
import { firstValueFrom } from 'rxjs' ;
13
13
import { RedisService } from './redis.service' ;
14
- import { MatchRequestDto } from './dto' ;
14
+ import { v4 as uuidv4 } from 'uuid' ;
15
+ import { MatchAcceptDto , MatchDeclineDto , MatchRequestDto } from './dto' ;
15
16
import {
16
17
MATCH_FOUND ,
17
18
MATCH_CANCELLED ,
18
19
MATCH_CONFIRMED ,
19
20
MATCH_TIMEOUT ,
20
21
MATCH_REQUESTED ,
22
+ MATCH_ACCEPTED ,
21
23
MATCH_ERROR ,
22
24
EXCEPTION ,
25
+ MATCH_DECLINED ,
23
26
} from './match.event' ;
24
- import { CANCEL_MATCH , FIND_MATCH } from './match.message' ;
27
+ import {
28
+ ACCEPT_MATCH ,
29
+ CANCEL_MATCH ,
30
+ DECLINE_MATCH ,
31
+ FIND_MATCH ,
32
+ } from './match.message' ;
25
33
26
34
@WebSocketGateway ( {
27
35
namespace : '/match' ,
@@ -35,6 +43,8 @@ import { CANCEL_MATCH, FIND_MATCH } from './match.message';
35
43
export class MatchGateway implements OnGatewayInit {
36
44
@WebSocketServer ( ) server : Server ;
37
45
private userSockets : Map < string , string > = new Map ( ) ;
46
+ private matchConfirmations : Map < string , Set < string > > = new Map ( ) ;
47
+ private matchParticipants : Map < string , Set < string > > = new Map ( ) ;
38
48
39
49
constructor (
40
50
@Inject ( 'MATCHING_SERVICE' ) private matchingClient : ClientProxy ,
@@ -63,7 +73,7 @@ export class MatchGateway implements OnGatewayInit {
63
73
! payload . selectedTopic ||
64
74
! payload . selectedDifficulty
65
75
) {
66
- client . emit ( EXCEPTION , 'Invalid match request payload.' ) ;
76
+ client . emit ( MATCH_ERROR , 'Invalid match request payload.' ) ;
67
77
return ;
68
78
}
69
79
@@ -81,7 +91,7 @@ export class MatchGateway implements OnGatewayInit {
81
91
message : result . message ,
82
92
} ) ;
83
93
} else {
84
- client . emit ( EXCEPTION , result . message ) ;
94
+ client . emit ( MATCH_ERROR , result . message ) ;
85
95
}
86
96
} catch ( error ) {
87
97
client . emit ( EXCEPTION , `Error requesting match: ${ error . message } ` ) ;
@@ -95,10 +105,7 @@ export class MatchGateway implements OnGatewayInit {
95
105
@MessageBody ( ) payload : { userId : string } ,
96
106
) {
97
107
if ( ! payload . userId ) {
98
- client . emit (
99
- EXCEPTION ,
100
- 'Invalid userId. Please check your payload and try again.' ,
101
- ) ;
108
+ client . emit ( MATCH_ERROR , 'Invalid userId in payload.' ) ;
102
109
return ;
103
110
}
104
111
@@ -116,7 +123,7 @@ export class MatchGateway implements OnGatewayInit {
116
123
message : result . message ,
117
124
} ) ;
118
125
} else {
119
- client . emit ( EXCEPTION , result . message ) ;
126
+ client . emit ( MATCH_ERROR , result . message ) ;
120
127
}
121
128
} catch ( error ) {
122
129
console . log ( error ) ;
@@ -125,23 +132,141 @@ export class MatchGateway implements OnGatewayInit {
125
132
}
126
133
}
127
134
128
- // Notify both matched users via WebSocket
135
+ @SubscribeMessage ( ACCEPT_MATCH )
136
+ async handleAcceptMatch (
137
+ @ConnectedSocket ( ) client : Socket ,
138
+ @MessageBody ( ) payload : MatchAcceptDto ,
139
+ ) {
140
+ const { userId, matchId } = payload ;
141
+
142
+ if ( ! userId || ! matchId ) {
143
+ client . emit ( MATCH_ERROR , 'Invalid payload.' ) ;
144
+ return ;
145
+ }
146
+
147
+ if ( ! this . validateUserId ( client , userId ) ) {
148
+ return ;
149
+ }
150
+
151
+ // Validate if the matchId exists and check if the user is a valid participant
152
+ const participants = this . matchParticipants . get ( matchId ) ;
153
+ if ( ! participants || ! participants . has ( userId ) ) {
154
+ client . emit ( MATCH_ERROR , 'You are not a participant of this match.' ) ;
155
+ return ;
156
+ }
157
+
158
+ // Check if the user has already accepted the match
159
+ const confirmations = this . matchConfirmations . get ( matchId ) || new Set ( ) ;
160
+ if ( confirmations . has ( userId ) ) {
161
+ client . emit ( MATCH_ERROR , 'You have already accepted this match.' ) ;
162
+ return ;
163
+ }
164
+
165
+ // Add user's confirmation for the match
166
+ confirmations . add ( userId ) ;
167
+ this . matchConfirmations . set ( matchId , confirmations ) ;
168
+
169
+ // Check if both participants have confirmed the match
170
+ if ( confirmations . size === 2 ) {
171
+ this . notifyUsersMatchConfirmed ( matchId , [ ...confirmations ] ) ;
172
+ } else {
173
+ client . emit ( MATCH_ACCEPTED , {
174
+ message : 'Waiting for the other user to accept the match.' ,
175
+ } ) ;
176
+ }
177
+ }
178
+
179
+ @SubscribeMessage ( DECLINE_MATCH )
180
+ async handleDeclineMatch (
181
+ @ConnectedSocket ( ) client : Socket ,
182
+ @MessageBody ( ) payload : MatchDeclineDto ,
183
+ ) {
184
+ const { userId, matchId } = payload ;
185
+
186
+ if ( ! userId || ! matchId ) {
187
+ client . emit ( MATCH_ERROR , 'Invalid payload.' ) ;
188
+ return ;
189
+ }
190
+
191
+ if ( ! this . validateUserId ( client , userId ) ) {
192
+ return ;
193
+ }
194
+
195
+ // Validate if the matchId exists and check if the user is a valid participant
196
+ const participants = this . matchParticipants . get ( matchId ) ;
197
+ if ( ! participants || ! participants . has ( userId ) ) {
198
+ client . emit ( MATCH_ERROR , 'You are not a participant of this match.' ) ;
199
+ return ;
200
+ }
201
+
202
+ // Notify the other user that the match has been declined
203
+ this . notifyOtherUserMatchDeclined ( matchId , userId ) ;
204
+
205
+ // Remove match-related data
206
+ this . matchParticipants . delete ( matchId ) ;
207
+ this . matchConfirmations . delete ( matchId ) ;
208
+ client . emit ( MATCH_DECLINED , { message : 'You have declined the match.' } ) ;
209
+ }
210
+
211
+ // Notify both users when they are matched
129
212
notifyUsersWithMatch ( matchedUsers : string [ ] ) {
130
213
const [ user1 , user2 ] = matchedUsers ;
131
214
const user1SocketId = this . getUserSocketId ( user1 ) ;
132
215
const user2SocketId = this . getUserSocketId ( user2 ) ;
216
+
133
217
if ( user1SocketId && user2SocketId ) {
218
+ const matchId = this . generateMatchId ( ) ;
134
219
this . server . to ( user1SocketId ) . emit ( MATCH_FOUND , {
135
- message : `You have been matched with user ${ user2 } ` ,
136
- matchedUserId : user2 ,
220
+ message : `You have found a match` ,
221
+ matchUserId : user2 ,
222
+ matchId,
137
223
} ) ;
138
224
this . server . to ( user2SocketId ) . emit ( MATCH_FOUND , {
139
- message : `You have been matched with user ${ user1 } ` ,
140
- matchedUserId : user1 ,
225
+ message : `You have found a match` ,
226
+ matchUserId : user1 ,
227
+ matchId,
141
228
} ) ;
229
+
230
+ // Store participants for this matchId
231
+ this . matchParticipants . set ( matchId , new Set ( [ user1 , user2 ] ) ) ;
142
232
}
143
233
}
144
234
235
+ // Notify both users when they both accept the match
236
+ notifyUsersMatchConfirmed ( matchId : string , users : string [ ] ) {
237
+ const sessionId = this . generateSessionId ( ) ;
238
+ users . forEach ( ( user ) => {
239
+ const socketId = this . getUserSocketId ( user ) ;
240
+ if ( socketId ) {
241
+ this . server . to ( socketId ) . emit ( MATCH_CONFIRMED , {
242
+ message : `Match confirmed! New session created.` ,
243
+ sessionId,
244
+ } ) ;
245
+ }
246
+ } ) ;
247
+
248
+ // Clean up match participants and confirmations
249
+ this . matchConfirmations . delete ( matchId ) ;
250
+ this . matchParticipants . delete ( matchId ) ;
251
+ }
252
+
253
+ private notifyOtherUserMatchDeclined (
254
+ matchId : string ,
255
+ decliningUserId : string ,
256
+ ) {
257
+ const participants = this . matchParticipants . get ( matchId ) ;
258
+ participants ?. forEach ( ( participantId ) => {
259
+ if ( participantId !== decliningUserId ) {
260
+ const socketId = this . getUserSocketId ( participantId ) ;
261
+ if ( socketId ) {
262
+ this . server . to ( socketId ) . emit ( MATCH_DECLINED , {
263
+ message : 'The other user has declined the match.' ,
264
+ } ) ;
265
+ }
266
+ }
267
+ } ) ;
268
+ }
269
+
145
270
notifyUsersWithTimeout ( timedOutUsers : string [ ] ) {
146
271
timedOutUsers . forEach ( ( user ) => {
147
272
const socketId = this . getUserSocketId ( user ) ;
@@ -213,19 +338,17 @@ export class MatchGateway implements OnGatewayInit {
213
338
) ;
214
339
215
340
if ( result . success ) {
216
- console . log ( `Match canceled successfully for user ${ userId } ` ) ;
341
+ console . log ( `Match auto cancelled user ${ userId } at disconnect ` ) ;
217
342
} else {
218
- console . warn (
219
- `Match cancellation failed for user ${ userId } : ${ result . message } ` ,
220
- ) ;
343
+ console . log ( `No match cancelled: ${ result . message } ` ) ;
221
344
}
222
345
223
346
this . userSockets . delete ( userId ) ;
224
347
console . log ( `User ${ userId } disconnected and removed from userSockets.` ) ;
225
348
} catch ( error ) {
226
349
client . emit (
227
350
EXCEPTION ,
228
- `Error canceling match for user ${ userId } : ${ error . message } ` ,
351
+ `Error disconnecting user ${ userId } : ${ error . message } ` ,
229
352
) ;
230
353
}
231
354
}
@@ -234,8 +357,17 @@ export class MatchGateway implements OnGatewayInit {
234
357
return this . userSockets . get ( userId ) ;
235
358
}
236
359
360
+ private generateMatchId ( ) : string {
361
+ return uuidv4 ( ) ;
362
+ }
363
+
364
+ // TODO - to be replaced with colab method in the future
365
+ private generateSessionId ( ) : string {
366
+ return uuidv4 ( ) ;
367
+ }
368
+
237
369
private emitExceptionAndDisconnect ( client : Socket , message : string ) {
238
- client . emit ( EXCEPTION , `Error connecting to /match socket : ${ message } ` ) ;
370
+ client . emit ( EXCEPTION , `Error: ${ message } ` ) ;
239
371
client . disconnect ( ) ;
240
372
}
241
373
0 commit comments