admin interface basic functionalities

This commit is contained in:
Quentin Roussel
2024-03-26 01:21:26 +01:00
parent 2478ee32ec
commit b23d2a63e6
24 changed files with 1504 additions and 40 deletions

130
traque-back/.gitignore vendored Normal file
View File

@@ -0,0 +1,130 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

106
traque-back/game.js Normal file
View File

@@ -0,0 +1,106 @@
import { Socket } from "socket.io";
export default class Game {
constructor() {
this.teams = [];
this.started = false;
}
start() {
this.started = true;
}
stop() {
this.started = false;
}
getNewTeamId() {
let id = null;
while(id === null || this.teams.find(t => t.id === id)) {
id = Math.floor(Math.random() * 1000000);
}
return id;
}
addTeam(teamName) {
let id = this.getNewTeamId();
this.teams.push({
id: id,
name: teamName,
chasing: null,
chased: null,
currentLocation: [0, 0],
lastSentLocation: [0, 0],
enemyLocation: [0, 0]
});
this.updateTeamChasing();
return true;
}
updateTeamChasing() {
if(this.teams.length <= 1) {
return false;
}
this.teams[0].chased = this.teams[this.teams.length - 1].id;
this.teams[this.teams.length - 1].chasing = this.teams[0].id;
for(let i = 0; i < this.teams.length - 1; i++) {
this.teams[i].chasing = this.teams[i + 1].id;
this.teams[i+1].chased = this.teams[i].id;
}
return true;
}
reorderTeams(newOrder) {
this.teams = newOrder;
return this.updateTeamChasing();
}
getTeam(teamId) {
return this.teams.find(t => t.id === teamId);
}
renameTeam(teamId, newName) {
let team = this.getTeam(teamId);
if(team == undefined) {
return false;
}
team.name = newName;
return true;
}
updateLocation(teamId, location) {
if(!this.started) {
return false;
}
let team = this.getTeam(teamId);
if(team == undefined) {
return false;
}
team.currentLocation = location;
return true;
}
sendLocation(teamId) {
if(!this.started) {
return false;
}
let team = this.getTeam(teamId);
if(team == undefined) {
return false;
}
team.lastSentLocation = team.currentLocation;
team.enemyLocation = this.getTeam(team.chasing).lastSentLocation;
return team;
}
removeTeam(teamId) {
if(this.getTeam(teamId) == undefined) {
return false;
}
//remove the team from the list
this.teams = this.teams.filter(t => t.id !== teamId);
this.updateTeamChasing();
return true;
}
}

186
traque-back/index.js Normal file
View File

@@ -0,0 +1,186 @@
import { createServer } from "http";
import { Server } from "socket.io";
import Game from "./game.js";
const httpServer = createServer();
//Password that socket clients will have to send to be able to send admin commands
//TODO: put this in an environment variable
const ADMIN_PASSWORD = "admin";
//set cors to allow all origins
const io = new Server(httpServer, {
cors: {
origin: "*",
methods: ["GET", "POST"]
}
});
/**
* Send a message to all logged in sockets
* @param {String} event The event name
* @param {String} data The data to send
*/
function secureBroadcast(event, data) {
loggedInSockets.forEach(s => {
io.of("admin").to(s).emit(event, data);
});
}
const game = new Game();
//Array of logged in sockets
let loggedInSockets = [];
//Admin namespace
io.of("admin").on("connection", (socket) => {
//Flag to check if the user is logged in, defined for each socket
let loggedIn = false;
socket.on("disconnect", () => {
console.log("user disconnected");
//Remove the socket from the logged in sockets array
loggedInSockets = loggedInSockets.filter(s => s !== socket.id);
});
//User is attempting to log in
socket.on("login", (password) => {
if (password === ADMIN_PASSWORD && !loggedIn) {
//Attempt successful
socket.emit("login_response", true);
loggedInSockets.push(socket.id);
loggedIn = true;
} else {
//Attempt unsuccessful
socket.emit("login_response", false);
}
});
//User is attempting to add a new team
socket.on("add_team", (teamName) => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
if(game.addTeam(teamName)) {
secureBroadcast("teams", game.teams);
}else {
socket.emit("error", "Error adding team");
}
});
//User is attempting to remove a team
socket.on("remove_team", (teamId) => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
if(game.removeTeam(teamId)) {
secureBroadcast("teams", game.teams);
}else {
socket.emit("error", "Error removing team");
}
});
//User is attempting to start the game
socket.on("start_game", () => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
if(game.start()) {
secureBroadcast("game_started", true);
}else {
socket.emit("error", "Error starting game");
}
});
//User is attempting to stop the game
socket.on("stop_game", () => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
if(game.stop()) {
secureBroadcast("game_started", false);
}else {
socket.emit("error", "Error stopping game");
}
});
//Use is sending a new list containing the new order of the teams
//Note that we never check if the new order contains the same teams as the old order, so it behaves more like a setTeams function
//But the frontend should always send the same teams in a different order
socket.on("reorder_teams", (newOrder) => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
if(game.reorderTeams(newOrder)) {
secureBroadcast("teams", game.teams);
} else {
socket.emit("error", "Error reordering teams");
}
});
//Change the name of a team given its id
socket.on("rename_team", (teamId, newName) => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
if(game.renameTeam(teamId, newName)) {
secureBroadcast("teams", game.teams);
} else {
socket.emit("error", "Error renaming team");
}
});
//Request an update of the team list
//We only reply to the sender to prevent spam
socket.on("get_teams", () => {
if(!loggedIn) {
socket.emit("error", "Not logged in");
return;
}
socket.emit("teams", game.teams);
});
});
io.of("player").on("connection", (socket) => {
let teamId = null;
console.log("a user connected");
socket.on("disconnect", () => {
console.log("user disconnected");
game.getTeam(teamId).sockets = game.getTeam(teamId).sockets.filter(s => s !== socket.id);
});
socket.on("login", (teamId) => {
if(game.getTeam(teamId) === undefined) {
socket.emit("login_response", false);
return;
}
game.getTeam(teamId).sockets.push(socket.id);
socket.emit("login_response", true);
});
socket.on("update_position", (position) => {
game.updateLocation(teamId, position);
});
socket.on("send_position", () => {
console.log("send_position", position);
game.sendLocation(teamId);
game.getTeam(teamId).sockets.forEach(s => {
io.of("player").to(s).emit("enemy_position", game.getTeam(teamId).enemyLocation);
});
});
});
httpServer.listen(3000);

237
traque-back/package-lock.json generated Normal file
View File

@@ -0,0 +1,237 @@
{
"name": "traque-back",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "traque-back",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"socket.io": "^4.7.5"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@types/cookie": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz",
"integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q=="
},
"node_modules/@types/cors": {
"version": "2.8.17",
"resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz",
"integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "20.11.30",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.30.tgz",
"integrity": "sha512-dHM6ZxwlmuZaRmUPfv1p+KrdD1Dci04FbdEm/9wEMouFqxYoFl5aMkt0VMAUtYRQDyYvD41WJLukhq/ha3YuTw==",
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/base64id": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz",
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
"engines": {
"node": "^4.5.0 || >= 5.9"
}
},
"node_modules/cookie": {
"version": "0.4.2",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cors": {
"version": "2.8.5",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
"dependencies": {
"object-assign": "^4",
"vary": "^1"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/engine.io": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz",
"integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
"@types/node": ">=10.0.0",
"accepts": "~1.3.4",
"base64id": "2.0.0",
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.2.1",
"ws": "~8.11.0"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
"integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/socket.io": {
"version": "4.7.5",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.5.tgz",
"integrity": "sha512-DmeAkF6cwM9jSfmp6Dr/5/mfMwb5Z5qRrSXLpo3Fq5SqyU8CMF15jIN4ZhfSwu35ksM1qmHZDQ/DK5XTccSTvA==",
"dependencies": {
"accepts": "~1.3.4",
"base64id": "~2.0.0",
"cors": "~2.8.5",
"debug": "~4.3.2",
"engine.io": "~6.5.2",
"socket.io-adapter": "~2.5.2",
"socket.io-parser": "~4.2.4"
},
"engines": {
"node": ">=10.2.0"
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz",
"integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.11.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
"integrity": "sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
}
}
}

View File

@@ -4,8 +4,13 @@
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
"author": "Quentin Roussel",
"license": "ISC"
"license": "ISC",
"type": "module",
"dependencies": {
"socket.io": "^4.7.5"
}
}