Skip to content

Commit

Permalink
Refactor socket controller logic
Browse files Browse the repository at this point in the history
  • Loading branch information
delishad21 committed Sep 10, 2024
1 parent 2583064 commit a0556ef
Show file tree
Hide file tree
Showing 10 changed files with 135 additions and 184 deletions.
15 changes: 15 additions & 0 deletions matching-service/src/controller/api-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Request, Response } from 'express';
import {
getCurrentMatchingUsersCount,
} from '../model/matching-model';

export async function getMatchingUsersCount(req: Request, res: Response) {
try {
const count = getCurrentMatchingUsersCount();
return res.status(200).json({ count });
} catch (err) {
console.error(err);
return res.status(500).json({ message: 'Unknown error when retrieving matching users count' });
}
}

85 changes: 0 additions & 85 deletions matching-service/src/controller/matching-controller.ts

This file was deleted.

65 changes: 65 additions & 0 deletions matching-service/src/controller/socket-controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { Server, Socket } from "socket.io";
import { addUserToSearchPool, addUserToSocketMap, getSocketIdForUser, matchUsers, removeUserFromSearchPool, removeUserFromSocketMap } from "../model/matching-model";

export function initaliseData(socket: Socket) {
const { userId } = socket.data;
addUserToSocketMap(userId, socket.id);
console.log(`User ${userId} connected via socket`);
}


// Handle user registration for matching
export function handleRegisterForMatching(socket: Socket, io: Server) {
const { userId } = socket.data;
socket.on('registerForMatching', (criteria) => {
if (criteria.difficulty && criteria.topic) {
addUserToSearchPool(userId, criteria);
console.log(`User ${userId} registered with criteria`, criteria);
socket.emit('registrationSuccess', { message: `User ${userId} registered for matching successfully.` });

// Check if a match can be made for the new user
const match = matchUsers();
if (match) {
const { matchedUsers } = match;
const [user1, user2] = matchedUsers;

// Notify both clients of the match using the mapping
const socketId1 = getSocketIdForUser(user1.userId);
const socketId2 = getSocketIdForUser(user2.userId);

if (socketId1) {
io.sockets.sockets.get(socketId1)?.emit('matchFound', { matchedWith: user2.userId }); //INSERT SESSION ID HERE
}
if (socketId2) {
io.sockets.sockets.get(socketId2)?.emit('matchFound', { matchedWith: user1.userId }); //INSERT SESSION ID HERE
}

// Disconnect both users
if (socketId1) {
io.sockets.sockets.get(socketId1)?.disconnect(true);
}
if (socketId2) {
io.sockets.sockets.get(socketId2)?.disconnect(true);
}

// Remove users from the map
removeUserFromSocketMap(user1.userId);
removeUserFromSocketMap(user2.userId);
}
} else {
socket.emit('error', 'Invalid matching criteria.');
}
});
}

export function handleDisconnect(socket: Socket) {
// Handle disconnection
const { userId } = socket.data;
socket.on('disconnect', () => {
console.log(`User ${userId} disconnected`);
removeUserFromSearchPool(userId);

// Remove the user from the map
removeUserFromSocketMap(userId);
});
}
22 changes: 17 additions & 5 deletions matching-service/src/middleware/jwt-validation.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Request, Response, NextFunction } from 'express';
import jwt, { JwtPayload } from 'jsonwebtoken';
import { Socket } from 'socket.io';

const secretKey = 'your_secret_key'; // Use an environment variable for this in production

// Function to validate JWT in HTTP requests
export function validateJWT(req: Request, res: Response, next: NextFunction) {
export function validateApiJWT(req: Request, res: Response, next: NextFunction) {
const token = req.headers.authorization?.split(' ')[1]; // Extract Bearer token
if (!token) {
return res.status(401).json({ message: 'Access Denied. No token provided.' });
Expand All @@ -19,12 +20,23 @@ export function validateJWT(req: Request, res: Response, next: NextFunction) {
}
}

// Function to validate JWT for socket connections
export function validateSocketJWT(token: string): JwtPayload {
export function validateSocketJWT(socket: Socket, next: (err?: Error) => void) {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error('Authentication error: No token provided.'));
}

try {
const decoded = jwt.verify(token, secretKey) as JwtPayload;
return decoded; // Return the decoded payload (userId, etc.)
socket.data.userId = decoded.id;
console.log(`User ${decoded.id} validated via JWT`);
next();
} catch (err) {
throw new Error('Invalid token');
next(new Error('Authentication error: Invalid token.'));
}
}

export function validateJWT (token: string): JwtPayload {
return jwt.verify(token, secretKey) as JwtPayload;
}
15 changes: 15 additions & 0 deletions matching-service/src/model/matching-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,23 @@ interface UserSearch {
startTime: Date;
}

// Maintain a mapping of userId to socket.id
const userSocketMap = new Map<string, string>();

const searchPool: UserSearch[] = [];

export function addUserToSocketMap(userId: string, socketId: string) {
userSocketMap.set(userId, socketId);
}

export function removeUserFromSocketMap(userId: string) {
userSocketMap.delete(userId);
}

export function getSocketIdForUser(userId: string): string | undefined {
return userSocketMap.get(userId);
}

// Add user to the search pool
export function addUserToSearchPool(userId: string, criteria: SearchCriteria) {
const startTime = new Date();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
import { Router } from 'express';
import { registerForMatching, getMatchingTime, getMatchingUsersCount } from '../controller/matching-controller';
import { validateJWT } from '../middleware/jwt-validation';
import { getMatchingUsersCount } from '../controller/api-controller';

const router = Router();

router.get('/', (req, res) => {res.send('Hello from matching service!')}); // Test route


router.post('/match/register', validateJWT, registerForMatching); // Register for matching
router.get('/match/count', getMatchingUsersCount); // Retrieve the count of users matching

export const matchingRoutes = router;
8 changes: 8 additions & 0 deletions matching-service/src/routes/socket-routes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Server, Socket } from "socket.io";
import { handleDisconnect, handleRegisterForMatching } from "../controller/socket-controller";

export function registerEventHandlers(socket: Socket, io: Server) {
handleRegisterForMatching(socket, io);
handleDisconnect(socket);
}

91 changes: 8 additions & 83 deletions matching-service/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import {
removeUserFromSearchPool,
matchUsers
} from './model/matching-model';
import { inherits } from 'util';
import { registerEventHandlers } from './routes/socket-routes';
import { initaliseData } from './controller/socket-controller';

// Create the express app
const app = express();
Expand All @@ -22,85 +25,12 @@ const io = new Server(server, {
// Middleware for parsing JSON
app.use(express.json());

// Maintain a mapping of userId to socket.id
const userSocketMap = new Map<string, string>();

// Socket.io connection handler with JWT validation
io.use((socket, next) => {
const token = socket.handshake.auth.token;

if (!token) {
return next(new Error('Authentication error: No token provided.'));
}

try {
// Validate the token and extract the userId
const decoded = validateSocketJWT(token);
socket.data.userId = decoded.userId;
next();
} catch (err) {
next(new Error('Authentication error: Invalid token.'));
}
});

io.use(validateSocketJWT);
io.on('connection', (socket) => {
const { userId } = socket.data;

// Map the userId to the socket.id
userSocketMap.set(userId, socket.id);

console.log(`User ${userId} connected via socket`);

// Handle user registration for matching
socket.on('registerForMatching', (criteria) => {
if (criteria.difficulty && criteria.topic) {
addUserToSearchPool(userId, criteria);
console.log(`User ${userId} registered with criteria`, criteria);
socket.emit('registrationSuccess', { message: `User ${userId} registered for matching successfully.` });

// Check if a match can be made for the new user
const match = matchUsers();
if (match) {
const { matchedUsers } = match;
const [user1, user2] = matchedUsers;

// Notify both clients of the match using the mapping
const socketId1 = userSocketMap.get(user1.userId);
const socketId2 = userSocketMap.get(user2.userId);

if (socketId1) {
io.sockets.sockets.get(socketId1)?.emit('matchFound', { matchedWith: user2.userId });
}
if (socketId2) {
io.sockets.sockets.get(socketId2)?.emit('matchFound', { matchedWith: user1.userId });
}

// Disconnect both users
if (socketId1) {
io.sockets.sockets.get(socketId1)?.disconnect(true);
}
if (socketId2) {
io.sockets.sockets.get(socketId2)?.disconnect(true);
}

// Remove users from the map
userSocketMap.delete(user1.userId);
userSocketMap.delete(user2.userId);
}
} else {
socket.emit('error', 'Invalid matching criteria.');
}
});

// Handle disconnection
socket.on('disconnect', () => {
console.log(`User ${userId} disconnected`);
removeUserFromSearchPool(userId);

// Remove the user from the map
userSocketMap.delete(userId);
});
});
initaliseData(socket);
registerEventHandlers(socket, io);
})

export { server };

Expand All @@ -110,9 +40,4 @@ if (require.main === module) {
server.listen(PORT, () => {
console.log(`Server is running on port ${PORT}`);
});
}
// Start the server
// const PORT = 8002;
// server.listen(PORT, () => {
// console.log(`Server is running on port ${PORT}`);
// });
}
6 changes: 3 additions & 3 deletions matching-service/test/jwt-validation.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { validateSocketJWT } from '../src/middleware/jwt-validation';
import { validateJWT } from '../src/middleware/jwt-validation';
import jwt from 'jsonwebtoken';

describe('Socket Middleware', () => {
Expand All @@ -7,11 +7,11 @@ describe('Socket Middleware', () => {
const userId = 'testUser';
const token = jwt.sign({ id: userId }, secretKey);

const decoded = validateSocketJWT(token);
const decoded = validateJWT(token);
expect(decoded.id).toBe(userId);
});

it('should throw an error for invalid token', () => {
expect(() => validateSocketJWT('invalid_token')).toThrow();
expect(() => validateJWT('invalid_token')).toThrow();
});
});
Loading

0 comments on commit a0556ef

Please sign in to comment.