imprvd
This commit is contained in:
@@ -2,11 +2,31 @@ import express, { Request, Response } from 'express';
|
||||
import ReadSettings from './settings';
|
||||
import { GetRandomPort, OpenGameInstance } from './helpers';
|
||||
import { Settings, Player, Room, QueueEntry } from './types';
|
||||
import { authenticateMatchmakingRequest, registerAuthRoutes } from './auth_bridge';
|
||||
import {
|
||||
getPlayerLast10MatchRecord,
|
||||
insertMatchForNewRoom,
|
||||
registerMatchRoutes,
|
||||
updateMatchUserBlue,
|
||||
} from './matches';
|
||||
|
||||
const version = "v1.0";
|
||||
console.log("Starting Cupid Matchmaker for unity " + version);
|
||||
|
||||
const app = express();
|
||||
// Only parse JSON for /auth — avoids parse errors on other routes; clients must send strict JSON (double-quoted keys).
|
||||
app.use('/auth', express.json());
|
||||
app.use((req, res, next) => {
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PATCH,OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.sendStatus(204);
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
const queueGraceTime = 2000;
|
||||
|
||||
// Read settings
|
||||
@@ -20,8 +40,18 @@ if (settings == null) {
|
||||
const typedSettings = settings as Settings;
|
||||
const logLevel = typedSettings.log_level;
|
||||
|
||||
const configuredGame = typedSettings.games?.[0];
|
||||
if (!configuredGame?.exe) {
|
||||
console.error("settings.json must include at least one game entry with exe");
|
||||
process.exit(1);
|
||||
}
|
||||
const gameName = configuredGame.name || "game";
|
||||
|
||||
LogVerbose(typedSettings);
|
||||
|
||||
registerAuthRoutes(app, typedSettings);
|
||||
registerMatchRoutes(app, typedSettings);
|
||||
|
||||
let Rooms: Room[] = [];
|
||||
let Queue: QueueEntry[] = [];
|
||||
|
||||
@@ -42,21 +72,22 @@ app.get('/settings', (req: Request, res: Response) => {
|
||||
res.send(m_settings);
|
||||
});
|
||||
|
||||
app.get('/', (req: Request, res: Response) => {
|
||||
const username = req.query.username?.toString() ?? "";
|
||||
const game_name = req.query.game_name?.toString() ?? "";
|
||||
|
||||
|
||||
if (!ValidateRequest(req, res)) {
|
||||
app.get('/', async (req: Request, res: Response) => {
|
||||
const auth = await authenticateMatchmakingRequest(req, typedSettings);
|
||||
if (!auth.ok) {
|
||||
res.status(auth.status).send(auth.message);
|
||||
return;
|
||||
}
|
||||
const { userId, username } = auth.user;
|
||||
|
||||
try {
|
||||
|
||||
// Check query
|
||||
let joinedRoom: Room | null = null;
|
||||
let alreadyOnQueue = false;
|
||||
|
||||
Queue.forEach(element => {
|
||||
if (element.Name === username) {
|
||||
if (element.UserId === userId) {
|
||||
element.LastSeen = Date.now();
|
||||
alreadyOnQueue = true;
|
||||
} else {
|
||||
@@ -87,7 +118,7 @@ app.get('/', (req: Request, res: Response) => {
|
||||
}
|
||||
if (roomValid) {
|
||||
element.Players.forEach(player => {
|
||||
if (player.Name === username) {// Already in a room
|
||||
if (player.UserId === userId) {
|
||||
joinedRoom = element;
|
||||
}
|
||||
});
|
||||
@@ -98,14 +129,14 @@ app.get('/', (req: Request, res: Response) => {
|
||||
if (index > -1) {
|
||||
Rooms.splice(index, 1);
|
||||
}
|
||||
} else if (element.Players.length < typedSettings.maximum_players && element.GameName === game_name) {
|
||||
} else if (element.Players.length < typedSettings.maximum_players && element.GameName === gameName) {
|
||||
possibleRoom = element;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (joinedRoom !== null) { // Already in a room. Stopping here
|
||||
res.send(joinedRoom);
|
||||
res.send(roomResponseForViewer(joinedRoom, userId));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,29 +144,37 @@ app.get('/', (req: Request, res: Response) => {
|
||||
if (possibleRoom === null) {
|
||||
if (Queue.length >= typedSettings.minimum_players) {
|
||||
const newPort = GetRandomPort(typedSettings.port_range_min, typedSettings.port_range_max);
|
||||
const l10 = await getPlayerLast10MatchRecord(typedSettings, userId);
|
||||
const newRoom: Room = {
|
||||
Players: [{ Name: username, LastSeen: Date.now() }],
|
||||
GameName: game_name,
|
||||
Players: [{ Name: username, LastSeen: Date.now(), UserId: userId, ...l10 }],
|
||||
GameName: gameName,
|
||||
Port: newPort,
|
||||
InitTime: Date.now()
|
||||
};
|
||||
const matchId = await insertMatchForNewRoom(typedSettings, userId);
|
||||
if (matchId != null) {
|
||||
newRoom.match_id = matchId;
|
||||
}
|
||||
Rooms.push(newRoom);
|
||||
//TODO: Get the correct game exe from the game id
|
||||
OpenGameInstance(typedSettings.games[0].exe, newPort);
|
||||
res.send(newRoom);
|
||||
OpenGameInstance(configuredGame.exe, newPort, matchId ?? undefined);
|
||||
res.send(roomResponseForViewer(newRoom, userId));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// We know possibleRoom is a Room type here since we checked it's not null
|
||||
const room: Room = possibleRoom;
|
||||
room.Players.push({ Name: username, LastSeen: Date.now() });
|
||||
res.send(room);
|
||||
const l10 = await getPlayerLast10MatchRecord(typedSettings, userId);
|
||||
room.Players.push({ Name: username, LastSeen: Date.now(), UserId: userId, ...l10 });
|
||||
if (room.match_id != null) {
|
||||
await updateMatchUserBlue(typedSettings, room.match_id, userId);
|
||||
}
|
||||
res.send(roomResponseForViewer(room, userId));
|
||||
return;
|
||||
}
|
||||
|
||||
// Not even got a new room. Back to queue
|
||||
if (!alreadyOnQueue) {
|
||||
const newQueueEntry: QueueEntry = { Name: username, LastSeen: Date.now() };
|
||||
const newQueueEntry: QueueEntry = { Name: username, LastSeen: Date.now(), UserId: userId };
|
||||
LogVerbose(newQueueEntry);
|
||||
Queue.push(newQueueEntry);
|
||||
}
|
||||
@@ -146,17 +185,22 @@ app.get('/', (req: Request, res: Response) => {
|
||||
LogVerbose("Queue");
|
||||
LogVerbose(Queue);
|
||||
res.send("0");
|
||||
} catch (err) {
|
||||
console.error("Matchmaking error:", err);
|
||||
res.status(500).send("Internal error");
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/cancel', (req: Request, res: Response) => {
|
||||
const username = req.query.username?.toString() ?? "";
|
||||
|
||||
if (!ValidateRequest(req, res)) {
|
||||
app.get('/cancel', async (req: Request, res: Response) => {
|
||||
const auth = await authenticateMatchmakingRequest(req, typedSettings);
|
||||
if (!auth.ok) {
|
||||
res.status(auth.status).send(auth.message);
|
||||
return;
|
||||
}
|
||||
const { userId } = auth.user;
|
||||
let foundUser = false;
|
||||
Queue.forEach((element) => {
|
||||
if (element.Name === username) {
|
||||
if (element.UserId === userId) {
|
||||
const index = Queue.indexOf(element);
|
||||
if (index > -1) {
|
||||
Queue.splice(index, 1);
|
||||
@@ -167,23 +211,15 @@ app.get('/cancel', (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
if (!foundUser) {
|
||||
res.send("Couldn't find user " + username + " in the queue");
|
||||
res.send("Couldn't find user in the queue");
|
||||
}
|
||||
});
|
||||
|
||||
function ValidateRequest(req: Request, res: Response): boolean {
|
||||
const username = req.query.username?.toString() ?? "";
|
||||
const password = req.query.password?.toString() ?? "";
|
||||
if (password !== typedSettings.password) {
|
||||
res.send("403 Unauthorized");
|
||||
LogVerbose("Unauthorized call " + password + ":" + typedSettings.password);
|
||||
return false;
|
||||
}
|
||||
if (username.length < 2) {
|
||||
res.send("Bad credentials");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
/** GET / JSON only: same order as match DB — first joined = red, second = blue. */
|
||||
function roomResponseForViewer(room: Room, viewerUserId: number): Room {
|
||||
const idx = room.Players.findIndex((p) => p.UserId === viewerUserId);
|
||||
const your_team: 'red' | 'blue' | null = idx === 0 ? 'red' : idx === 1 ? 'blue' : null;
|
||||
return { ...room, your_team };
|
||||
}
|
||||
|
||||
function Log(msg: any): void {
|
||||
@@ -202,5 +238,18 @@ function LogVerbose(msg: any): void {
|
||||
}
|
||||
}
|
||||
|
||||
app.use((err: unknown, _req: Request, res: Response, next: express.NextFunction) => {
|
||||
const e = err as { status?: number; type?: string };
|
||||
if (e.status === 400 && e.type === 'entity.parse.failed') {
|
||||
res.status(400).json({
|
||||
ok: false,
|
||||
error:
|
||||
'Invalid JSON body. Use strict JSON with double-quoted keys, e.g. {"username":"player1","password":"secret12"}',
|
||||
});
|
||||
return;
|
||||
}
|
||||
next(err);
|
||||
});
|
||||
|
||||
app.listen(typedSettings.port);
|
||||
console.log("Listening on port " + typedSettings.port);
|
||||
Reference in New Issue
Block a user