Skip to content

Commit af8fb3d

Browse files
committed
Integrate matching websocket in BE and FE
1 parent 7b9cb16 commit af8fb3d

File tree

20 files changed

+553
-438
lines changed

20 files changed

+553
-438
lines changed
Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import amqplib, { Connection } from "amqplib";
22
import dotenv from "dotenv";
3-
import { matchUsers } from "../utils/mq_utils";
3+
import { matchUsers } from "../src/utils/mq_utils";
4+
import { MatchItem } from "../src/types/matchTypes";
45

56
dotenv.config();
67

@@ -15,11 +16,7 @@ export const connectRabbitMq = async () => {
1516

1617
consumerChannel.consume(queue, async (msg) => {
1718
if (msg !== null) {
18-
try {
19-
await matchUsers(msg.content.toString());
20-
} catch (error) {
21-
console.error(error);
22-
}
19+
matchUsers(msg.content.toString());
2320
consumerChannel.ack(msg);
2421
}
2522
});
@@ -29,20 +26,13 @@ export const connectRabbitMq = async () => {
2926
}
3027
};
3128

32-
type MatchRequestMessage = {
33-
userId: string;
34-
categories: string[] | string;
35-
complexities: string[] | string;
36-
sentTimestamp: number;
37-
ttlInSecs: number;
38-
};
39-
40-
export const sendRabbitMq = async (data: MatchRequestMessage) => {
29+
export const sendRabbitMq = async (data: MatchItem): Promise<boolean> => {
4130
try {
4231
const senderChannel = await mrConnection.createChannel();
4332
senderChannel.sendToQueue(queue, Buffer.from(JSON.stringify(data)));
33+
return true;
4434
} catch (error) {
4535
console.log(error);
46-
throw new Error("Failed to send match request");
36+
return false;
4737
}
4838
};

backend/matching-service/server.ts

Lines changed: 1 addition & 19 deletions
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: {},
13+
connectionStateRecovery: {}, // TODO: rejoin room?
1414
});
1515

1616
io.on("connection", (socket) => {
@@ -29,24 +29,6 @@ if (process.env.NODE_ENV !== "test") {
2929
`Matching service server listening on http://localhost:${PORT}`
3030
);
3131
});
32-
33-
//can use this to test if rabbitmq works for you (import sendRabbitMq from rabbitmq.ts first)
34-
/*const message1 = {
35-
userId: "1",
36-
categories: "Algorithms",
37-
complexities: "Easy",
38-
sentTimestamp: Date.now(),
39-
ttlInSecs: 30,
40-
};
41-
sendRabbitMq(message1);
42-
const message2 = {
43-
userId: "2",
44-
categories: "Algorithms",
45-
complexities: "Medium",
46-
sentTimestamp: Date.now(),
47-
ttlInSecs: 30,
48-
};
49-
sendRabbitMq(message2);*/
5032
})
5133
.catch((err) => {
5234
console.error("Failed to connect to RabbitMq");

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

Lines changed: 47 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,50 @@ import { io } from "../../server";
22
import { Match, MatchItem, MatchRequest } from "../types/matchTypes";
33
import { v4 as uuidv4 } from "uuid";
44
import {
5-
MATCH_ACCEPTANCE_TIMEOUT,
65
MATCH_FOUND,
76
MATCH_IN_PROGRESS,
87
MATCH_SUCCESSFUL,
9-
MATCH_TIMEOUT,
108
MATCH_UNSUCCESSFUL,
9+
MATCH_REQUEST_ERROR,
1110
} from "../utils/constants";
12-
import { appendToMatchQueue } from "./queueHandler";
1311
import { Socket } from "socket.io";
12+
import { sendRabbitMq } from "../../config/rabbitmq";
1413

1514
const matches: Match = {};
15+
export const userSockets: Map<string, Socket> = new Map<string, Socket>();
1616

17-
export const createMatchItem = (socket: Socket, matchRequest: MatchRequest) => {
17+
export const createMatchItem = async (
18+
socket: Socket,
19+
matchRequest: MatchRequest
20+
): Promise<boolean> => {
1821
const { user, complexities, categories, languages, timeout } = matchRequest;
1922

20-
const matchTimeout = setTimeout(() => {
21-
socket.emit(MATCH_TIMEOUT);
22-
}, timeout * 1000);
23+
if (userSockets.has(user.id)) {
24+
console.log(`user request exists: ${user.username}`);
25+
socket.emit(MATCH_IN_PROGRESS);
26+
return false;
27+
}
28+
29+
userSockets.set(user.id, socket);
2330

2431
const matchQueueItem: MatchItem = {
25-
socket: socket,
2632
user: user,
2733
complexities: complexities,
2834
categories: categories,
2935
languages: languages,
30-
timeout: matchTimeout,
36+
sentTimestamp: Date.now(),
37+
ttlInSecs: timeout,
3138
acceptedMatch: false,
3239
};
3340

34-
const result = appendToMatchQueue(matchQueueItem);
41+
const result = await sendRabbitMq(matchQueueItem);
3542
if (!result) {
36-
socket.emit(MATCH_IN_PROGRESS);
43+
socket.emit(MATCH_REQUEST_ERROR);
3744
}
45+
return result;
3846
};
3947

40-
export const createMatch = (matchItems: MatchItem[]) => {
41-
const matchItem1 = matchItems[0];
42-
const matchItem2 = matchItems[1];
43-
44-
clearTimeout(matchItem1.timeout);
45-
clearTimeout(matchItem2.timeout);
46-
48+
export const createMatch = (matchItem1: MatchItem, matchItem2: MatchItem) => {
4749
const matchId = uuidv4();
4850
matches[matchId] = {
4951
item1: matchItem1,
@@ -52,57 +54,56 @@ export const createMatch = (matchItems: MatchItem[]) => {
5254
accepted: false,
5355
};
5456

55-
matchItem1.socket.join(matchId);
56-
matchItem2.socket.join(matchId);
57+
// check for disconnection? or just send the match (disconnected user will timeout anyway)
58+
userSockets.get(matchItem1.user.id)!.join(matchId);
59+
userSockets.get(matchItem2.user.id)!.join(matchId);
5760
io.to(matchId).emit(MATCH_FOUND, {
5861
matchId: matchId,
5962
user1: matchItem1.user,
6063
user2: matchItem2.user,
6164
});
6265
};
6366

64-
export const setMatchTimeout = (matchId: string) => {
65-
const match = matches[matchId];
66-
if (!match) {
67-
return;
68-
}
69-
70-
const timeout = setTimeout(() => {
71-
io.to(matchId).emit(MATCH_UNSUCCESSFUL);
72-
delete matches[matchId];
73-
}, MATCH_ACCEPTANCE_TIMEOUT);
74-
75-
match.timeout = timeout;
76-
};
77-
7867
export const handleMatchAcceptance = (matchId: string) => {
7968
const match = matches[matchId];
8069
if (!match) {
8170
return;
8271
}
8372

8473
if (match.accepted) {
85-
clearTimeout(match.timeout!);
8674
io.to(matchId).emit(MATCH_SUCCESSFUL);
87-
delete matches[matchId];
8875
} else {
8976
match.accepted = true;
9077
}
9178
};
9279

93-
export const handleMatchDecline = (matchId: string) => {
80+
export const handleRematch = (
81+
socket: Socket,
82+
matchId: string,
83+
rematchRequest: MatchRequest
84+
) => {
9485
const match = matches[matchId];
95-
if (!match) {
96-
return;
86+
if (match) {
87+
delete matches[matchId];
88+
socket.to(matchId).emit(MATCH_UNSUCCESSFUL);
9789
}
9890

99-
clearTimeout(match.timeout!);
100-
io.to(matchId).emit(MATCH_UNSUCCESSFUL);
101-
delete matches[matchId];
91+
createMatchItem(socket, rematchRequest);
10292
};
10393

104-
export const isUserMatched = (userId: string): boolean => {
105-
return !!Object.values(matches).find(
106-
(match) => match.item1.user.id === userId || match.item2.user.id === userId
107-
);
94+
export const handleMatchTermination = (terminatedSocket: Socket) => {
95+
for (const [uid, socket] of userSockets) {
96+
if (socket.id === terminatedSocket.id) {
97+
userSockets.delete(uid);
98+
break;
99+
}
100+
}
101+
102+
// TODO: no access to rooms
103+
const matchId = Array.from(terminatedSocket.rooms)[1];
104+
const match = matches[matchId];
105+
if (match) {
106+
delete matches[matchId];
107+
terminatedSocket.to(matchId).emit(MATCH_UNSUCCESSFUL);
108+
}
108109
};

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

Lines changed: 0 additions & 49 deletions
This file was deleted.
Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,41 @@
11
import { Socket } from "socket.io";
22
import {
33
MATCH_ACCEPTED,
4-
MATCH_DECLINED,
5-
MATCH_RECEIVED,
4+
REMATCH_REQUEST,
65
MATCH_REQUEST,
6+
SOCKET_CLIENT_DISCONNECT,
7+
SOCKET_DISCONNECT,
78
} from "../utils/constants";
89
import { MatchRequest } from "../types/matchTypes";
910
import {
1011
createMatchItem,
1112
handleMatchAcceptance,
12-
handleMatchDecline,
13-
setMatchTimeout,
13+
handleMatchTermination,
14+
handleRematch,
1415
} from "./matchHandler";
1516

1617
export const handleWebsocketMatchEvents = (socket: Socket) => {
17-
socket.on(MATCH_REQUEST, (matchRequest: MatchRequest) => {
18-
createMatchItem(socket, matchRequest);
19-
});
18+
socket.on(
19+
MATCH_REQUEST,
20+
async (matchRequest: MatchRequest, callback: (result: boolean) => void) => {
21+
const result = await createMatchItem(socket, matchRequest);
22+
callback(result);
23+
}
24+
);
2025

21-
socket.on(MATCH_RECEIVED, (matchId: string) => {
22-
setMatchTimeout(matchId);
23-
});
26+
socket.on(MATCH_ACCEPTED, (matchId: string) =>
27+
handleMatchAcceptance(matchId)
28+
);
2429

25-
socket.on(MATCH_ACCEPTED, (matchId: string) => {
26-
handleMatchAcceptance(matchId);
27-
});
30+
socket.on(REMATCH_REQUEST, (matchId: string, rematchRequest: MatchRequest) =>
31+
handleRematch(socket, matchId, rematchRequest)
32+
);
2833

29-
socket.on(MATCH_DECLINED, (matchId: string) => {
30-
handleMatchDecline(matchId);
34+
// TODO: handle client reconnect failure
35+
socket.on(SOCKET_DISCONNECT, (reason) => {
36+
if (reason === SOCKET_CLIENT_DISCONNECT) {
37+
console.log("Client manually disconnected");
38+
handleMatchTermination(socket);
39+
}
3140
});
3241
};

backend/matching-service/src/types/matchTypes.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
import { Socket } from "socket.io";
2-
31
export interface MatchUser {
42
id: string;
53
username: string;
6-
profile: string;
4+
profile?: string;
75
}
86

97
export interface MatchRequest {
@@ -15,12 +13,12 @@ export interface MatchRequest {
1513
}
1614

1715
export interface MatchItem {
18-
socket: Socket;
1916
user: MatchUser;
2017
complexities: string[];
2118
categories: string[];
2219
languages: string[];
23-
timeout: NodeJS.Timeout;
20+
sentTimestamp: number;
21+
ttlInSecs: number;
2422
acceptedMatch: boolean;
2523
}
2624

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/* Websocket Match Events */
22
export const MATCH_REQUEST = "match_request";
3-
export const MATCH_TIMEOUT = "match_timeout";
3+
export const MATCH_REQUEST_ERROR = "match_request_error";
44
export const MATCH_FOUND = "match_found";
55
export const MATCH_IN_PROGRESS = "match_in_progress";
6-
export const MATCH_RECEIVED = "match_received";
76
export const MATCH_ACCEPTED = "match_accepted";
8-
export const MATCH_DECLINED = "match_declined";
7+
export const REMATCH_REQUEST = "rematch_request";
98
export const MATCH_SUCCESSFUL = "match_successful";
109
export const MATCH_UNSUCCESSFUL = "match_unsuccessful";
1110

12-
export const MATCH_ACCEPTANCE_TIMEOUT = 10000;
11+
export const SOCKET_DISCONNECT = "disconnect";
12+
export const SOCKET_CLIENT_DISCONNECT = "client namespace disconnect";

0 commit comments

Comments
 (0)