Skip to content

Commit 7695dd5

Browse files
committed
Handle client and server disconnections
1 parent bcaee89 commit 7695dd5

File tree

11 files changed

+228
-132
lines changed

11 files changed

+228
-132
lines changed

backend/matching-service/server.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const io = new Server(server, {
1010
origin: allowedOrigins,
1111
methods: ["GET", "POST"],
1212
},
13-
connectionStateRecovery: {}, // TODO: rejoin room?
13+
connectionStateRecovery: {},
1414
});
1515

1616
io.on("connection", (socket) => {

backend/matching-service/src/handlers/matchHandler.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ import { v4 as uuidv4 } from "uuid";
22
import { sendRabbitMq } from "../../config/rabbitmq";
33
import { sendMatchFound } from "./websocketHandler";
44

5-
interface MatchUser {
5+
interface Match {
6+
matchUser1: MatchUser;
7+
matchUser2: MatchUser;
8+
accepted: boolean;
9+
}
10+
11+
export interface MatchUser {
612
id: string;
713
username: string;
814
profile?: string;
915
}
1016

11-
interface Match {
12-
uid1: string;
13-
uid2: string;
14-
accepted: boolean;
15-
}
16-
1717
export interface MatchRequest {
1818
user: MatchUser;
1919
complexities: string[];
@@ -55,13 +55,16 @@ export const createMatch = (
5555
requestItem2: MatchRequestItem
5656
) => {
5757
const matchId = uuidv4();
58+
const matchUser1 = requestItem1.user;
59+
const matchUser2 = requestItem2.user;
60+
5861
matches.set(matchId, {
59-
uid1: requestItem1.user.id,
60-
uid2: requestItem2.user.id,
62+
matchUser1: matchUser1,
63+
matchUser2: matchUser2,
6164
accepted: false,
6265
});
6366

64-
sendMatchFound(matchId, requestItem1, requestItem2);
67+
sendMatchFound(matchId, matchUser1, matchUser2);
6568
};
6669

6770
export const handleMatchAccept = (matchId: string): boolean => {
@@ -80,9 +83,22 @@ export const handleMatchDelete = (matchId: string): boolean =>
8083

8184
export const getMatchIdByUid = (uid: string): string | null => {
8285
for (const [matchId, match] of matches) {
83-
if (match.uid1 === uid || match.uid2 === uid) {
86+
if (match.matchUser1.id === uid || match.matchUser2.id === uid) {
8487
return matchId;
8588
}
8689
}
8790
return null;
8891
};
92+
93+
export const getMatchByUid = (
94+
uid: string
95+
): { matchId: string; partner: MatchUser } | null => {
96+
for (const [matchId, match] of matches) {
97+
if (match.matchUser1.id === uid) {
98+
return { matchId: matchId, partner: match.matchUser2 };
99+
} else if (match.matchUser2.id === uid) {
100+
return { matchId: matchId, partner: match.matchUser1 };
101+
}
102+
}
103+
return null;
104+
};
Lines changed: 79 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -1,151 +1,173 @@
11
import { Socket } from "socket.io";
2-
import {
3-
REMATCH_REQUEST,
4-
MATCH_REQUEST,
5-
SOCKET_DISCONNECT,
6-
USER_CONNECTED,
7-
USER_DISCONNECTED,
8-
MATCH_DECLINE_REQUEST,
9-
MATCH_UNSUCCESSFUL,
10-
MATCH_SUCCESSFUL,
11-
MATCH_END_REQUEST,
12-
MATCH_ENDED,
13-
MATCH_ACCEPT_REQUEST,
14-
MATCH_FOUND,
15-
MATCH_REQUEST_EXISTS,
16-
MATCH_REQUEST_ERROR,
17-
CANCEL_MATCH_REQUEST,
18-
} from "../utils/constants";
192
import {
203
sendMatchRequest,
214
handleMatchAccept,
225
MatchRequest,
236
handleMatchDelete,
24-
MatchRequestItem,
257
getMatchIdByUid,
8+
MatchUser,
9+
getMatchByUid,
2610
} from "./matchHandler";
2711
import { io } from "../../server";
2812

13+
enum MatchEvents {
14+
// Receive
15+
MATCH_REQUEST = "match_request",
16+
CANCEL_MATCH_REQUEST = "cancel_match_request",
17+
MATCH_ACCEPT_REQUEST = "match_accept_request",
18+
MATCH_DECLINE_REQUEST = "match_decline_request",
19+
REMATCH_REQUEST = "rematch_request",
20+
MATCH_END_REQUEST = "match_end_request",
21+
MATCH_STATUS_REQUEST = "match_status_request",
22+
23+
USER_CONNECTED = "user_connected",
24+
USER_DISCONNECTED = "user_disconnected",
25+
SOCKET_DISCONNECT = "disconnect",
26+
SOCKET_CLIENT_DISCONNECT = "client namespace disconnect",
27+
28+
// Send
29+
MATCH_FOUND = "match_found",
30+
MATCH_SUCCESSFUL = "match_successful",
31+
MATCH_UNSUCCESSFUL = "match_unsuccessful",
32+
MATCH_ENDED = "match_ended",
33+
MATCH_REQUEST_EXISTS = "match_request_exists",
34+
MATCH_REQUEST_ERROR = "match_request_error",
35+
}
36+
2937
interface UserConnection {
3038
socket: Socket;
3139
connectionTimeout?: NodeJS.Timeout;
40+
isDisconnecting: boolean;
3241
}
3342

34-
// TODO: do a match lost due to poor connection?
3543
const connectionDelay = 3000; // time window to allow for page navigation / refresh
3644
const userConnections = new Map<string, UserConnection>();
3745

3846
export const handleWebsocketMatchEvents = (socket: Socket) => {
3947
socket.removeAllListeners();
4048

41-
socket.on(USER_CONNECTED, (uid: string) => {
49+
socket.on(MatchEvents.USER_CONNECTED, (uid: string) => {
50+
console.log(`uid: ${uid} and socket id: ${socket.id}`);
4251
clearTimeout(userConnections.get(uid)?.connectionTimeout);
43-
userConnections.set(uid, { socket: socket });
44-
console.log(`socket: ${socket.id}`);
52+
if (userConnections.has(uid)) {
53+
const matchId = getMatchIdByUid(uid);
54+
if (matchId) {
55+
socket.join(matchId);
56+
}
57+
}
58+
userConnections.set(uid, { socket: socket, isDisconnecting: false });
4559
});
4660

47-
socket.on(USER_DISCONNECTED, (uid: string, matchId: string | null) => {
61+
socket.on(MatchEvents.USER_DISCONNECTED, (uid: string) => {
4862
if (!userConnections.has(uid)) {
4963
return;
5064
}
5165

5266
clearTimeout(userConnections.get(uid)?.connectionTimeout);
5367

5468
const connectionTimeout = setTimeout(() => {
55-
endMatchOnUserDisconnect(socket, uid, matchId);
56-
console.log(`server disconnect connection: ${uid}`);
69+
endMatchOnUserDisconnect(socket, uid);
5770
socket.disconnect();
58-
}, connectionDelay); // TODO: Clearing up your previous match (in case user immediately sends a new match request)
71+
}, connectionDelay);
5972

6073
userConnections.set(uid, {
6174
socket: socket,
6275
connectionTimeout: connectionTimeout,
76+
isDisconnecting: true,
6377
});
6478
});
6579

6680
socket.on(
67-
MATCH_REQUEST,
81+
MatchEvents.MATCH_REQUEST,
6882
async (
6983
matchRequest: MatchRequest,
7084
callback: (requested: boolean) => void
7185
) => {
7286
const uid = matchRequest.user.id;
73-
if (isUserConnected(uid)) {
74-
socket.emit(MATCH_REQUEST_EXISTS);
87+
if (isUserConnected(uid) && !userConnections.get(uid)?.isDisconnecting) {
88+
socket.emit(MatchEvents.MATCH_REQUEST_EXISTS);
7589
callback(false);
7690
return;
7791
}
7892

79-
userConnections.set(uid, { socket: socket });
93+
userConnections.set(uid, { socket: socket, isDisconnecting: false });
8094

8195
const sent = await sendMatchRequest(matchRequest);
8296
if (!sent) {
83-
socket.emit(MATCH_REQUEST_ERROR);
97+
socket.emit(MatchEvents.MATCH_REQUEST_ERROR);
8498
userConnections.delete(uid);
8599
socket.disconnect();
86100
}
87101
callback(sent);
88102
}
89103
);
90104

91-
socket.on(CANCEL_MATCH_REQUEST, (uid: string) => {
105+
socket.on(MatchEvents.CANCEL_MATCH_REQUEST, (uid: string) => {
92106
userConnections.delete(uid);
93107
});
94108

95-
socket.on(MATCH_ACCEPT_REQUEST, (matchId: string) => {
109+
socket.on(MatchEvents.MATCH_ACCEPT_REQUEST, (matchId: string) => {
96110
const partnerAccepted = handleMatchAccept(matchId);
97111
if (partnerAccepted) {
98-
io.to(matchId).emit(MATCH_SUCCESSFUL);
112+
io.to(matchId).emit(MatchEvents.MATCH_SUCCESSFUL);
99113
}
100114
});
101115

102116
socket.on(
103-
MATCH_DECLINE_REQUEST,
117+
MatchEvents.MATCH_DECLINE_REQUEST,
104118
(uid: string, matchId: string, isTimeout: boolean) => {
105119
userConnections.delete(uid);
106120
const matchDeleted = handleMatchDelete(matchId);
107121
if (matchDeleted && !isTimeout) {
108-
socket.to(matchId).emit(MATCH_UNSUCCESSFUL);
122+
socket.to(matchId).emit(MatchEvents.MATCH_UNSUCCESSFUL);
109123
}
110124
}
111125
);
112126

113127
socket.on(
114-
REMATCH_REQUEST,
128+
MatchEvents.REMATCH_REQUEST,
115129
async (
116130
matchId: string,
117131
rematchRequest: MatchRequest,
118132
callback: (result: boolean) => void
119133
) => {
120134
const matchDeleted = handleMatchDelete(matchId);
121135
if (matchDeleted) {
122-
socket.to(matchId).emit(MATCH_UNSUCCESSFUL);
136+
socket.to(matchId).emit(MatchEvents.MATCH_UNSUCCESSFUL);
123137
}
124138

125139
const sent = await sendMatchRequest(rematchRequest);
126140
if (!sent) {
127-
socket.emit(MATCH_REQUEST_ERROR);
141+
socket.emit(MatchEvents.MATCH_REQUEST_ERROR);
128142
}
129143
callback(sent);
130144
}
131145
);
132146

133-
socket.on(MATCH_END_REQUEST, (uid: string, matchId: string) => {
147+
socket.on(MatchEvents.MATCH_END_REQUEST, (uid: string, matchId: string) => {
134148
userConnections.delete(uid);
135149
const matchDeleted = handleMatchDelete(matchId);
136150
if (matchDeleted) {
137-
socket.to(matchId).emit(MATCH_ENDED);
151+
socket.to(matchId).emit(MatchEvents.MATCH_ENDED);
138152
}
139153
});
140154

141-
// TODO: handle client reconnect failure
142-
socket.on(SOCKET_DISCONNECT, () => {
155+
socket.on(
156+
MatchEvents.MATCH_STATUS_REQUEST,
157+
(
158+
uid: string,
159+
callback: (match: { matchId: string; partner: MatchUser } | null) => void
160+
) => {
161+
const match = getMatchByUid(uid);
162+
callback(match);
163+
}
164+
);
165+
166+
socket.on(MatchEvents.SOCKET_DISCONNECT, () => {
143167
for (const [uid, userConnection] of userConnections) {
144168
if (userConnection.socket.id === socket.id) {
145169
if (!userConnections.get(uid)?.connectionTimeout) {
146-
const matchId = getMatchIdByUid(uid);
147-
endMatchOnUserDisconnect(socket, uid, matchId);
148-
console.log(`force delete connection: ${uid}`);
170+
endMatchOnUserDisconnect(socket, uid);
149171
}
150172
break;
151173
}
@@ -155,33 +177,30 @@ export const handleWebsocketMatchEvents = (socket: Socket) => {
155177

156178
export const sendMatchFound = (
157179
matchId: string,
158-
requestItem1: MatchRequestItem,
159-
requestItem2: MatchRequestItem
180+
matchUser1: MatchUser,
181+
matchUser2: MatchUser
160182
) => {
161-
userConnections.get(requestItem1.user.id)?.socket.join(matchId);
162-
userConnections.get(requestItem2.user.id)?.socket.join(matchId);
163-
io.to(matchId).emit(MATCH_FOUND, {
183+
userConnections.get(matchUser1.id)?.socket.join(matchId);
184+
userConnections.get(matchUser2.id)?.socket.join(matchId);
185+
io.to(matchId).emit(MatchEvents.MATCH_FOUND, {
164186
matchId: matchId,
165-
user1: requestItem1.user,
166-
user2: requestItem2.user,
187+
user1: matchUser1,
188+
user2: matchUser2,
167189
});
168190
};
169191

170192
export const isUserConnected = (uid: string) => {
171193
return userConnections.has(uid);
172194
};
173195

174-
const endMatchOnUserDisconnect = (
175-
socket: Socket,
176-
uid: string,
177-
matchId: string | null
178-
) => {
196+
const endMatchOnUserDisconnect = (socket: Socket, uid: string) => {
179197
userConnections.delete(uid);
198+
const matchId = getMatchIdByUid(uid);
180199
if (matchId) {
181200
const matchDeleted = handleMatchDelete(matchId);
182201
if (matchDeleted) {
183-
socket.to(matchId).emit(MATCH_UNSUCCESSFUL); // on matching page
184-
socket.to(matchId).emit(MATCH_ENDED); // on collab page
202+
socket.to(matchId).emit(MatchEvents.MATCH_UNSUCCESSFUL); // on matching page
203+
socket.to(matchId).emit(MatchEvents.MATCH_ENDED); // on collab page
185204
}
186205
}
187206
};

backend/matching-service/src/utils/constants.ts

Lines changed: 0 additions & 21 deletions
This file was deleted.

frontend/src/components/NoDirectAccessRoutes/index.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ export const useAppNavigate = () => {
55
const navigate = useNavigate();
66

77
const appNavigate = (path: string) => {
8-
navigate(path, { replace: true, state: { from: "app-navigation" } });
8+
navigate(path, {
9+
replace: location.pathname !== "/home",
10+
state: { from: "app-navigation" },
11+
});
912
};
1013

1114
return appNavigate;
@@ -15,7 +18,7 @@ const NoDirectAccessRoutes: React.FC = () => {
1518
const location = useLocation();
1619

1720
if (location.state?.from !== "app-navigation") {
18-
return <Navigate to="/home" />;
21+
return <Navigate to="/home" replace />;
1922
}
2023

2124
return <Outlet />;

0 commit comments

Comments
 (0)