seems like the merge worked
This commit is contained in:
12
.gitignore
vendored
12
.gitignore
vendored
@@ -15,13 +15,11 @@ Thumbs.db
|
|||||||
*.log
|
*.log
|
||||||
|
|
||||||
# compiled output
|
# compiled output
|
||||||
/dist
|
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
./srcs/requirement/api_back/node_modules
|
./srcs/requirement/nestjs/api_back/dist
|
||||||
./srcs/requirement/api_back/dist
|
./srcs/requirements/svelte/api_front/public/build/
|
||||||
./srcs/requirement/api_front/node_modules
|
|
||||||
./srcs/requirement/api_front/public/build
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
@@ -53,3 +51,7 @@ lerna-debug.log*
|
|||||||
!.vscode/tasks.json
|
!.vscode/tasks.json
|
||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
|
.env
|
||||||
|
|
||||||
|
memo.txt
|
||||||
|
|||||||
18
JEU2/make.sh
18
JEU2/make.sh
@@ -1,18 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
npx tsc
|
|
||||||
|
|
||||||
mkdir -p www
|
|
||||||
cp ./src/client/*.html ./www/
|
|
||||||
cp ./src/client/*.css ./www/
|
|
||||||
|
|
||||||
mkdir -p www/js
|
|
||||||
cp ./src/client/*.js ./www/js/
|
|
||||||
|
|
||||||
mkdir -p www/js/class
|
|
||||||
cp ./src/client/class/*.js ./www/js/class/
|
|
||||||
|
|
||||||
mkdir -p www/shared_js/
|
|
||||||
cp ./src/shared_js/*.js ./www/shared_js/
|
|
||||||
|
|
||||||
mkdir -p www/shared_js/class
|
|
||||||
cp ./src/shared_js/class/*.js ./www/shared_js/class/
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
Done:
|
|
||||||
- Connexion client/serveur via un Websocket
|
|
||||||
- implémentation basique (authoritative server)
|
|
||||||
- Matchmaking
|
|
||||||
- client prediction
|
|
||||||
- server reconciliation (buffer des inputs côté client + id sur les inputs)
|
|
||||||
- amélioration collision avec Hugo
|
|
||||||
- du son (rebonds de la balle, "Oof" de Roblox sur un point)
|
|
||||||
- init de GameComponents partagé entre serveur et client.
|
|
||||||
- draw on the canvas "WIN", "LOSE", "MATCHMAKING COMPLETE", ...
|
|
||||||
- interpolation (mis à jour progressif des mouvements de l'adversaire)
|
|
||||||
- traitement groupé des inputs clients toutes les x millisecondes
|
|
||||||
(BUG désynchronisation: revenu à un traitement immédiat en attendant)
|
|
||||||
- Détruire les GameSession une fois finies.
|
|
||||||
- mode multi-balles
|
|
||||||
- mode murs mouvant (la zone de jeu rétréci / agrandi en continu)
|
|
||||||
- Selection des modes de jeu via HTML
|
|
||||||
- Selection audio on/off via HTML
|
|
||||||
|
|
||||||
TODO:
|
|
||||||
- Match Abort si tout les joueurs ne sont pas pret assez vite (~15 secondes)
|
|
||||||
- mode spectateur
|
|
||||||
- certaines utilisations de Math.floor() superflu ? Vérifier les appels.
|
|
||||||
(éventuellement Math.round() ?)
|
|
||||||
- un autre mode de jeu alternatif ?
|
|
||||||
- changer les "localhost:8080" dans le code.
|
|
||||||
- sélection couleur des raquettes (your color/opponent color) dans le profil utilisateur.
|
|
||||||
Enregistrement dans la DB.
|
|
||||||
init des couleurs dans GameComponentsClient() basé sur les variables de l'utilsateur connecté.
|
|
||||||
-----------
|
|
||||||
idées modes de jeu :
|
|
||||||
- mode 2 raquettes (un joueur haut/gauche et bas/droite)
|
|
||||||
- skin patate ???
|
|
||||||
- (prediction de l'avancement de la balle basé sur la latence serveur ?)
|
|
||||||
- d'autres sons (foule qui applaudi/musique de victoire)
|
|
||||||
-----------
|
|
||||||
- BUG: Si la balle va très vite, elle peut ignorer la collision avec une raquette ou mur.
|
|
||||||
la collision est testée seulement après le mouvement.
|
|
||||||
Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs.
|
|
||||||
- BUG mineur: sur un changement de fenêtre, les touches restent enfoncées et il faut les "décoincer"
|
|
||||||
en réappuyant. Ce n'est pas grave mais peut-on faire mieux ?
|
|
||||||
----------
|
|
||||||
OSEF, rebuts:
|
|
||||||
- reconnection
|
|
||||||
- amélioration du protocole, remplacement du JSON (compression. moins de bande passante).
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
|
|
||||||
/* From Server */
|
|
||||||
class ServerEvent {
|
|
||||||
type: en.EventTypes;
|
|
||||||
constructor(type: en.EventTypes = 0) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventAssignId extends ServerEvent {
|
|
||||||
id: string;
|
|
||||||
constructor(id: string) {
|
|
||||||
super(en.EventTypes.assignId);
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventMatchmakingComplete extends ServerEvent {
|
|
||||||
side: en.PlayerSide;
|
|
||||||
constructor(side: en.PlayerSide) {
|
|
||||||
super(en.EventTypes.matchmakingComplete);
|
|
||||||
this.side = side;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventGameUpdate extends ServerEvent {
|
|
||||||
playerLeft = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
playerRight = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
ballsArr: {
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
dirX: number,
|
|
||||||
dirY: number,
|
|
||||||
speed: number
|
|
||||||
}[] = [];
|
|
||||||
wallTop? = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
wallBottom? = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
lastInputId = 0;
|
|
||||||
constructor() { // TODO: constructor that take GameComponentsServer maybe ?
|
|
||||||
super(en.EventTypes.gameUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventScoreUpdate extends ServerEvent {
|
|
||||||
scoreLeft: number;
|
|
||||||
scoreRight: number;
|
|
||||||
constructor(scoreLeft: number, scoreRight: number) {
|
|
||||||
super(en.EventTypes.scoreUpdate);
|
|
||||||
this.scoreLeft = scoreLeft;
|
|
||||||
this.scoreRight = scoreRight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventMatchEnd extends ServerEvent {
|
|
||||||
winner: en.PlayerSide;
|
|
||||||
constructor(winner: en.PlayerSide) {
|
|
||||||
super(en.EventTypes.matchEnd);
|
|
||||||
this.winner = winner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* From Client */
|
|
||||||
class ClientEvent {
|
|
||||||
type: en.EventTypes; // readonly ?
|
|
||||||
constructor(type: en.EventTypes = 0) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClientAnnounce extends ClientEvent {
|
|
||||||
role: en.ClientRole;
|
|
||||||
clientId: string;
|
|
||||||
matchOptions: en.MatchOptions;
|
|
||||||
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
|
|
||||||
super(en.EventTypes.clientAnnounce);
|
|
||||||
this.role = role;
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.matchOptions = matchOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventInput extends ClientEvent {
|
|
||||||
input: en.InputEnum;
|
|
||||||
id: number;
|
|
||||||
constructor(input: en.InputEnum = en.InputEnum.noInput, id: number = 0) {
|
|
||||||
super(en.EventTypes.clientInput);
|
|
||||||
this.input = input;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
ServerEvent, EventAssignId, EventMatchmakingComplete,
|
|
||||||
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
|
|
||||||
ClientEvent, ClientAnnounce, EventInput
|
|
||||||
}
|
|
||||||
@@ -1,188 +0,0 @@
|
|||||||
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
import * as ev from "./Event.js"
|
|
||||||
import * as c from "../constants.js"
|
|
||||||
import { ClientPlayer } from "./Client";
|
|
||||||
import { GameComponentsServer } from "./GameComponentsServer.js";
|
|
||||||
import { clientInputListener } from "../wsServer.js";
|
|
||||||
import { random } from "../utils.js";
|
|
||||||
import { Ball } from "../../shared_js/class/Rectangle.js";
|
|
||||||
import { wallsMovements } from "../../shared_js/wallsMovement.js";
|
|
||||||
|
|
||||||
/*
|
|
||||||
Arg "s: GameSession" replace "this: GameSession" for use with setTimeout(),
|
|
||||||
because "this" is equal to "this: Timeout"
|
|
||||||
*/
|
|
||||||
class GameSession {
|
|
||||||
id: string; // url ?
|
|
||||||
playersMap: Map<string, ClientPlayer> = new Map();
|
|
||||||
unreadyPlayersMap: Map<string, ClientPlayer> = new Map();
|
|
||||||
gameLoopInterval: NodeJS.Timer | number = 0;
|
|
||||||
clientsUpdateInterval: NodeJS.Timer | number = 0;
|
|
||||||
components: GameComponentsServer;
|
|
||||||
matchOptions: en.MatchOptions;
|
|
||||||
matchEnded: boolean = false;
|
|
||||||
|
|
||||||
actual_time: number;
|
|
||||||
last_time: number;
|
|
||||||
delta_time: number;
|
|
||||||
|
|
||||||
constructor(id: string, matchOptions: en.MatchOptions) {
|
|
||||||
this.id = id;
|
|
||||||
this.matchOptions = matchOptions;
|
|
||||||
this.components = new GameComponentsServer(this.matchOptions);
|
|
||||||
}
|
|
||||||
start() {
|
|
||||||
const gc = this.components;
|
|
||||||
setTimeout(this.resume, c.matchStartDelay, this);
|
|
||||||
|
|
||||||
let timeout = c.matchStartDelay + c.newRoundDelay;
|
|
||||||
gc.ballsArr.forEach((ball) => {
|
|
||||||
setTimeout(this._newRound, timeout, this, ball);
|
|
||||||
timeout += c.newRoundDelay*0.5;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
resume(s: GameSession) {
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
client.socket.on("message", clientInputListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
s.actual_time = Date.now();
|
|
||||||
s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s);
|
|
||||||
s.clientsUpdateInterval = setInterval(s._clientsUpdate, c.clientsUpdateIntervalMS, s);
|
|
||||||
}
|
|
||||||
pause(s: GameSession) {
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
client.socket.off("message", clientInputListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
clearInterval(s.gameLoopInterval);
|
|
||||||
clearInterval(s.clientsUpdateInterval);
|
|
||||||
}
|
|
||||||
instantInputDebug(client: ClientPlayer) {
|
|
||||||
this._handleInput(c.fixedDeltaTime, client);
|
|
||||||
}
|
|
||||||
private _handleInput(delta: number, client: ClientPlayer) {
|
|
||||||
// if (client.inputBuffer === null) {return;}
|
|
||||||
const gc = this.components;
|
|
||||||
const input = client.inputBuffer.input;
|
|
||||||
|
|
||||||
if (input === en.InputEnum.up) {
|
|
||||||
client.racket.dir.y = -1;
|
|
||||||
}
|
|
||||||
else if (input === en.InputEnum.down) {
|
|
||||||
client.racket.dir.y = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input !== en.InputEnum.noInput) {
|
|
||||||
client.racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
|
||||||
}
|
|
||||||
|
|
||||||
client.lastInputId = client.inputBuffer.id;
|
|
||||||
// client.inputBuffer = null;
|
|
||||||
}
|
|
||||||
private _gameLoop(s: GameSession) {
|
|
||||||
/* s.last_time = s.actual_time;
|
|
||||||
s.actual_time = Date.now();
|
|
||||||
s.delta_time = (s.actual_time - s.last_time) / 1000; */
|
|
||||||
s.delta_time = c.fixedDeltaTime;
|
|
||||||
|
|
||||||
// WIP, replaced by instantInputDebug() to prevent desynchro
|
|
||||||
/* s.playersMap.forEach( (client) => {
|
|
||||||
s._handleInput(s.delta_time, client);
|
|
||||||
}); */
|
|
||||||
|
|
||||||
const gc = s.components;
|
|
||||||
gc.ballsArr.forEach((ball) => {
|
|
||||||
s._ballMovement(s.delta_time, ball);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (s.matchOptions & en.MatchOptions.movingWalls) {
|
|
||||||
wallsMovements(s.delta_time, gc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private _ballMovement(delta: number, ball: Ball) {
|
|
||||||
const gc = this.components;
|
|
||||||
if (ball.ballInPlay)
|
|
||||||
{
|
|
||||||
ball.moveAndBounce(delta, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
|
|
||||||
if (ball.pos.x > c.w
|
|
||||||
|| ball.pos.x < 0 - ball.width)
|
|
||||||
{
|
|
||||||
ball.ballInPlay = false;
|
|
||||||
if (this.matchEnded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ball.pos.x > c.w) { ++gc.scoreLeft; }
|
|
||||||
else if (ball.pos.x < 0 - ball.width) { ++gc.scoreRight; }
|
|
||||||
|
|
||||||
this.playersMap.forEach( (client) => {
|
|
||||||
client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight)));
|
|
||||||
});
|
|
||||||
setTimeout(this._newRound, c.newRoundDelay, this, ball);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private _clientsUpdate(s: GameSession) {
|
|
||||||
const gc = s.components;
|
|
||||||
const update = new ev.EventGameUpdate();
|
|
||||||
update.playerLeft.y = gc.playerLeft.pos.y;
|
|
||||||
update.playerRight.y = gc.playerRight.pos.y;
|
|
||||||
gc.ballsArr.forEach((ball) => {
|
|
||||||
update.ballsArr.push({
|
|
||||||
x: ball.pos.x,
|
|
||||||
y: ball.pos.y,
|
|
||||||
dirX: ball.dir.x,
|
|
||||||
dirY: ball.dir.y,
|
|
||||||
speed: ball.speed
|
|
||||||
});
|
|
||||||
});
|
|
||||||
if (s.matchOptions & en.MatchOptions.movingWalls) {
|
|
||||||
update.wallTop.y = gc.wallTop.pos.y;
|
|
||||||
update.wallBottom.y = gc.wallBottom.pos.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
update.lastInputId = client.lastInputId;
|
|
||||||
client.socket.send(JSON.stringify(update));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
private _newRound(s: GameSession, ball: Ball) {
|
|
||||||
const gc = s.components;
|
|
||||||
// https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches
|
|
||||||
if (gc.scoreLeft >= 11 || gc.scoreRight >= 11)
|
|
||||||
// if (gc.scoreLeft >= 2 || gc.scoreRight >= 2) // WIP: for testing
|
|
||||||
{
|
|
||||||
if (Math.abs(gc.scoreLeft - gc.scoreRight) >= 2)
|
|
||||||
{
|
|
||||||
s._matchEnd(s);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ball.pos.x = c.w_mid;
|
|
||||||
ball.pos.y = random(c.h*0.3, c.h*0.7);
|
|
||||||
ball.speed = ball.baseSpeed;
|
|
||||||
ball.ballInPlay = true;
|
|
||||||
}
|
|
||||||
private _matchEnd(s: GameSession) {
|
|
||||||
s.matchEnded = true;
|
|
||||||
const gc = s.components;
|
|
||||||
|
|
||||||
let eventEnd: ev.EventMatchEnd;
|
|
||||||
if (gc.scoreLeft > gc.scoreRight) {
|
|
||||||
eventEnd = new ev.EventMatchEnd(en.PlayerSide.left);
|
|
||||||
console.log("Player Left WIN");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
eventEnd = new ev.EventMatchEnd(en.PlayerSide.right);
|
|
||||||
console.log("Player Right WIN");
|
|
||||||
}
|
|
||||||
|
|
||||||
s.playersMap.forEach( (client) => {
|
|
||||||
client.socket.send(JSON.stringify(eventEnd));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {GameSession}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
|
|
||||||
import http from "http";
|
|
||||||
import url from "url";
|
|
||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
import {wsServer} from "./wsServer.js"; wsServer; // no-op, just for loading
|
|
||||||
|
|
||||||
const hostname = "localhost";
|
|
||||||
const port = 8080;
|
|
||||||
const root = "../../www/";
|
|
||||||
|
|
||||||
const server = http.createServer((req, res) => {
|
|
||||||
// let q = new URL(req.url, `http://${req.getHeaders().host}`)
|
|
||||||
let q = url.parse(req.url, true);
|
|
||||||
let filename = root + q.pathname;
|
|
||||||
fs.readFile(filename, (err, data) => {
|
|
||||||
if (err) {
|
|
||||||
res.writeHead(404, {"Content-Type": "text/html"});
|
|
||||||
return res.end("404 Not Found");
|
|
||||||
}
|
|
||||||
if (path.extname(filename) === ".html") {
|
|
||||||
res.writeHead(200, {"Content-Type": "text/html"});
|
|
||||||
}
|
|
||||||
else if (path.extname(filename) === ".js") {
|
|
||||||
res.writeHead(200, {"Content-Type": "application/javascript"});
|
|
||||||
}
|
|
||||||
else if (path.extname(filename) === ".mp3") {
|
|
||||||
res.writeHead(200, {"Content-Type": "audio/mpeg"});
|
|
||||||
}
|
|
||||||
else if (path.extname(filename) === ".ogg") {
|
|
||||||
res.writeHead(200, {"Content-Type": "audio/ogg"});
|
|
||||||
}
|
|
||||||
res.write(data);
|
|
||||||
return res.end();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
server.listen(port, hostname, () => {
|
|
||||||
console.log(`Pong running at http://${hostname}:${port}/pong.html`);
|
|
||||||
});
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
import { MovingRectangle } from "./class/Rectangle.js";
|
|
||||||
export * from "../shared_js/utils.js"
|
|
||||||
|
|
||||||
function shortId(id: string): string {
|
|
||||||
return id.substring(0, id.indexOf("-"));
|
|
||||||
}
|
|
||||||
|
|
||||||
export {shortId}
|
|
||||||
|
|
||||||
function random(min: number = 0, max: number = 1) {
|
|
||||||
return Math.random() * (max - min) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep (ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(n: number, min: number, max: number) : number
|
|
||||||
{
|
|
||||||
if (n < min)
|
|
||||||
n = min;
|
|
||||||
else if (n > max)
|
|
||||||
n = max;
|
|
||||||
return (n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typescript hack, unused
|
|
||||||
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
|
|
||||||
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {random, sleep, clamp, assertMovingRectangle}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
|
|
||||||
import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws";
|
|
||||||
|
|
||||||
export class WebSocket extends BaseLibWebSocket {
|
|
||||||
id?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
import { IncomingMessage } from "http";
|
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
|
|
||||||
import * as en from "./enums.js"
|
|
||||||
import * as ev from "./class/Event.js"
|
|
||||||
import { Client, ClientPlayer } from "./class/Client.js"
|
|
||||||
import { GameSession } from "./class/GameSession.js"
|
|
||||||
import { shortId } from "./utils.js";
|
|
||||||
|
|
||||||
// pas indispensable d'avoir un autre port si le WebSocket est relié à un serveur http préexistant ?
|
|
||||||
const wsPort = 8042;
|
|
||||||
export const wsServer = new WebSocketServer<WebSocket>({port: wsPort, path: "/pong"});
|
|
||||||
|
|
||||||
const clientsMap: Map<string, Client> = new Map; // socket.id/Client
|
|
||||||
const matchmakingPlayersMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
|
|
||||||
const gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession
|
|
||||||
|
|
||||||
wsServer.on("connection", connectionListener);
|
|
||||||
wsServer.on("error", errorListener);
|
|
||||||
wsServer.on("close", closeListener);
|
|
||||||
|
|
||||||
|
|
||||||
function connectionListener(socket: WebSocket, request: IncomingMessage)
|
|
||||||
{
|
|
||||||
const id = uuidv4();
|
|
||||||
const client = new Client(socket, id);
|
|
||||||
clientsMap.set(id, client);
|
|
||||||
socket.id = id;
|
|
||||||
|
|
||||||
socket.on("pong", function heartbeat() {
|
|
||||||
client.isAlive = true;
|
|
||||||
// console.log(`client ${shortId(client.id)} is alive`);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("message", function log(data: string) {
|
|
||||||
try {
|
|
||||||
const event: ev.ClientEvent = JSON.parse(data);
|
|
||||||
if (event.type === en.EventTypes.clientInput) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {}
|
|
||||||
console.log("data: " + data);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.once("message", clientAnnounceListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function clientAnnounceListener(this: WebSocket, data: string)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const msg : ev.ClientAnnounce = JSON.parse(data);
|
|
||||||
if (msg.type === en.EventTypes.clientAnnounce)
|
|
||||||
{
|
|
||||||
// TODO: reconnection with msg.clientId ?
|
|
||||||
// TODO: spectator/player distinction with msg.role ?
|
|
||||||
// msg.role is probably not a good idea.
|
|
||||||
// something like a different route could be better
|
|
||||||
// "/pong" to play, "/ID_OF_A_GAMESESSION" to spectate
|
|
||||||
|
|
||||||
const player = clientsMap.get(this.id) as ClientPlayer;
|
|
||||||
player.matchOptions = msg.matchOptions;
|
|
||||||
this.send(JSON.stringify( new ev.EventAssignId(this.id) ));
|
|
||||||
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ));
|
|
||||||
matchmaking(player);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid ClientAnnounce");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Invalid JSON (clientAnnounceListener)");
|
|
||||||
}
|
|
||||||
this.once("message", clientAnnounceListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function matchmaking(player: ClientPlayer)
|
|
||||||
{
|
|
||||||
const minPlayersNumber = 2;
|
|
||||||
const maxPlayersNumber = 2;
|
|
||||||
const matchOptions = player.matchOptions;
|
|
||||||
matchmakingPlayersMap.set(player.id, player);
|
|
||||||
|
|
||||||
const compatiblePlayers: ClientPlayer[] = [];
|
|
||||||
for (const [id, client] of matchmakingPlayersMap)
|
|
||||||
{
|
|
||||||
if (client.matchOptions === matchOptions)
|
|
||||||
{
|
|
||||||
compatiblePlayers.push(client);
|
|
||||||
if (compatiblePlayers.length === maxPlayersNumber) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (compatiblePlayers.length < minPlayersNumber) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const id = uuidv4();
|
|
||||||
const gameSession = new GameSession(id, matchOptions);
|
|
||||||
gameSessionsMap.set(id, gameSession);
|
|
||||||
|
|
||||||
compatiblePlayers.forEach((client) => {
|
|
||||||
matchmakingPlayersMap.delete(client.id);
|
|
||||||
client.gameSession = gameSession;
|
|
||||||
gameSession.playersMap.set(client.id, client);
|
|
||||||
gameSession.unreadyPlayersMap.set(client.id, client);
|
|
||||||
});
|
|
||||||
|
|
||||||
// WIP: Not pretty, hardcoded two players.
|
|
||||||
// Could be done in gameSession maybe ?
|
|
||||||
compatiblePlayers[0].racket = gameSession.components.playerRight;
|
|
||||||
compatiblePlayers[1].racket = gameSession.components.playerLeft;
|
|
||||||
|
|
||||||
compatiblePlayers.forEach((client) => {
|
|
||||||
client.socket.once("message", playerReadyConfirmationListener);
|
|
||||||
});
|
|
||||||
|
|
||||||
compatiblePlayers[0].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) ));
|
|
||||||
compatiblePlayers[1].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) ));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function playerReadyConfirmationListener(this: WebSocket, data: string)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
const msg : ev.ClientEvent = JSON.parse(data);
|
|
||||||
if (msg.type === en.EventTypes.clientPlayerReady)
|
|
||||||
{
|
|
||||||
const client = clientsMap.get(this.id);
|
|
||||||
const gameSession = client.gameSession;
|
|
||||||
gameSession.unreadyPlayersMap.delete(this.id);
|
|
||||||
if (gameSession.unreadyPlayersMap.size === 0) {
|
|
||||||
gameSession.playersMap.forEach( (client) => {
|
|
||||||
client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
|
||||||
});
|
|
||||||
gameSession.start();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid playerReadyConfirmation");
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Invalid JSON (playerReadyConfirmationListener)");
|
|
||||||
}
|
|
||||||
this.once("message", playerReadyConfirmationListener);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export function clientInputListener(this: WebSocket, data: string)
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
// const input: ev.ClientEvent = JSON.parse(data);
|
|
||||||
const input: ev.EventInput = JSON.parse(data);
|
|
||||||
if (input.type === en.EventTypes.clientInput)
|
|
||||||
{
|
|
||||||
const client = clientsMap.get(this.id) as ClientPlayer;
|
|
||||||
client.inputBuffer = input;
|
|
||||||
client.gameSession.instantInputDebug(client); // wip
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.log("Invalid clientInput");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log("Invalid JSON (clientInputListener)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
////////////
|
|
||||||
////////////
|
|
||||||
|
|
||||||
const pingInterval = setInterval( () => {
|
|
||||||
let deleteLog = "";
|
|
||||||
clientsMap.forEach( (client, key, map) => {
|
|
||||||
if (!client.isAlive) {
|
|
||||||
clientTerminate(client, key, map);
|
|
||||||
deleteLog += ` ${shortId(key)} |`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
client.isAlive = false;
|
|
||||||
client.socket.ping();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (deleteLog) {
|
|
||||||
console.log(`Disconnected:${deleteLog}`);
|
|
||||||
}
|
|
||||||
console.log("gameSessionMap size: " + gameSessionsMap.size);
|
|
||||||
console.log("clientsMap size: " + clientsMap.size);
|
|
||||||
console.log("matchmakingPlayersMap size: " + matchmakingPlayersMap.size);
|
|
||||||
console.log("");
|
|
||||||
}, 4200);
|
|
||||||
|
|
||||||
|
|
||||||
function clientTerminate(client: Client, key: string, map: Map<string, Client>)
|
|
||||||
{
|
|
||||||
client.socket.terminate();
|
|
||||||
if (client.gameSession)
|
|
||||||
{
|
|
||||||
client.gameSession.playersMap.delete(key);
|
|
||||||
if (client.gameSession.playersMap.size === 0)
|
|
||||||
{
|
|
||||||
clearInterval(client.gameSession.clientsUpdateInterval);
|
|
||||||
clearInterval(client.gameSession.gameLoopInterval);
|
|
||||||
gameSessionsMap.delete(client.gameSession.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
map.delete(key);
|
|
||||||
if (matchmakingPlayersMap.has(key)) {
|
|
||||||
matchmakingPlayersMap.delete(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function closeListener()
|
|
||||||
{
|
|
||||||
clearInterval(pingInterval);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function errorListener(error: Error)
|
|
||||||
{
|
|
||||||
console.log("Error: " + JSON.stringify(error));
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
{
|
|
||||||
"include": ["src"],
|
|
||||||
// "exclude": ["node_modules"],
|
|
||||||
"compilerOptions": {
|
|
||||||
// "outDir": "./build",
|
|
||||||
// "types": ["node"], /* Specify type package names to be included without being referenced in a source file. */
|
|
||||||
/* Visit https://aka.ms/tsconfig to read more about this file */
|
|
||||||
|
|
||||||
/* Projects */
|
|
||||||
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
|
||||||
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
|
||||||
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
|
||||||
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
|
||||||
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
|
||||||
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
|
||||||
|
|
||||||
/* Language and Environment */
|
|
||||||
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
|
||||||
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
|
||||||
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
|
||||||
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
|
||||||
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
|
||||||
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.gc. 'React.createElement' or 'h'. */
|
|
||||||
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.gc. 'React.Fragment' or 'Fragment'. */
|
|
||||||
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
|
||||||
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
|
||||||
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
|
||||||
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
|
||||||
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
|
||||||
|
|
||||||
/* Modules */
|
|
||||||
// "module": "commonjs", /* Specify what module code is generated. */
|
|
||||||
"module": "ES6", /* Specify what module code is generated. */
|
|
||||||
// "rootDir": "./", /* Specify the root folder within your source files. */
|
|
||||||
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
|
||||||
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
|
||||||
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
|
||||||
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
|
||||||
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
|
||||||
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
|
||||||
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
|
||||||
// "resolveJsonModule": true, /* Enable importing .json files. */
|
|
||||||
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
|
||||||
|
|
||||||
/* JavaScript Support */
|
|
||||||
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
|
||||||
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
|
||||||
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
|
||||||
|
|
||||||
/* Emit */
|
|
||||||
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
|
||||||
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
|
||||||
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
|
||||||
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
|
||||||
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
|
||||||
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
|
||||||
// "removeComments": true, /* Disable emitting comments. */
|
|
||||||
// "noEmit": true, /* Disable emitting files from a compilation. */
|
|
||||||
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
|
||||||
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
|
||||||
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
|
||||||
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
|
||||||
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
|
||||||
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
|
||||||
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
|
||||||
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
|
||||||
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
|
||||||
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
|
||||||
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
|
||||||
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
|
||||||
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
|
||||||
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
|
||||||
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
|
||||||
|
|
||||||
/* Interop Constraints */
|
|
||||||
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
|
||||||
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
|
||||||
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
|
||||||
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
|
||||||
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
|
||||||
|
|
||||||
/* Type Checking */
|
|
||||||
"strict": true, /* Enable all strict type-checking options. */
|
|
||||||
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
|
||||||
"strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */
|
|
||||||
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
|
||||||
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
|
||||||
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
|
||||||
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
|
||||||
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
|
||||||
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
|
||||||
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
|
||||||
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
|
||||||
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
|
||||||
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
|
||||||
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
|
||||||
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
|
||||||
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
|
||||||
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
|
||||||
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
|
||||||
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
|
||||||
|
|
||||||
/* Completeness */
|
|
||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
34
Makefile
34
Makefile
@@ -1,44 +1,28 @@
|
|||||||
DOCKERCOMPOSEPATH=./srcs/docker-compose.yml
|
DOCKERCOMPOSEPATH=./srcs/docker-compose.yml
|
||||||
|
|
||||||
#dev allow hot reload.
|
#dev allow hot reload.
|
||||||
dev:
|
up:
|
||||||
|
@bash ./make_env.sh
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} up -d --build
|
docker compose -f ${DOCKERCOMPOSEPATH} up -d --build
|
||||||
@make start
|
@make start
|
||||||
@docker ps
|
@docker ps
|
||||||
|
|
||||||
|
|
||||||
#prod only the needed files ares presents inside the container
|
|
||||||
prod:
|
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} up -d --build prod
|
|
||||||
@make start_prod
|
|
||||||
@docker ps
|
|
||||||
|
|
||||||
start:
|
start:
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} start
|
docker compose -f ${DOCKERCOMPOSEPATH} start
|
||||||
docker logs --follow srcs-backend_dev-1
|
docker logs --follow nestjs
|
||||||
|
|
||||||
start_dev:
|
all : up
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} start dev
|
|
||||||
docker logs --follow srcs-backend_dev-1
|
|
||||||
|
|
||||||
start_prod:
|
re: down up
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} start prod
|
|
||||||
|
|
||||||
restart:stop
|
|
||||||
@make up
|
|
||||||
|
|
||||||
down:
|
down:
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} -v down
|
docker compose -f ${DOCKERCOMPOSEPATH} -v down
|
||||||
|
|
||||||
destroy:
|
destroy:
|
||||||
# rm -rf ./srcs/requirements/nestjs/api_back/node_modules/
|
- docker compose -f ${DOCKERCOMPOSEPATH} down -v --rmi all --remove-orphans
|
||||||
# rm -rf ./srcs/requirements/nestjs/api_back/dist
|
- docker ps -aq | xargs --no-run-if-empty docker rm -f
|
||||||
# rm -rf ./srcs/requirements/svelte/api_front/node_modules/
|
- docker images -aq | xargs --no-run-if-empty docker rmi -f
|
||||||
# rm -rf ./srcs/requirements/svelte/api_front/public/build
|
- docker volume ls -q | xargs --no-run-if-empty docker volume rm
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} down -v --rmi all --remove-orphans
|
|
||||||
docker ps -aq | xargs --no-run-if-empty docker rm -f
|
|
||||||
docker images -aq | xargs --no-run-if-empty docker rmi -f
|
|
||||||
docker volume ls -q | xargs --no-run-if-empty docker volume rm
|
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
docker compose -f ${DOCKERCOMPOSEPATH} stop
|
docker compose -f ${DOCKERCOMPOSEPATH} stop
|
||||||
|
|||||||
111
README.md
111
README.md
@@ -1,3 +1,7 @@
|
|||||||
|
- CONFLICT srcs/requirements/svelte/api_front/public/build/bundle.js
|
||||||
|
- CONFLICT srcs/requirements/svelte/api_front/public/build/bundle.js.map
|
||||||
|
|
||||||
|
|
||||||
### Pour lancer le docker :
|
### Pour lancer le docker :
|
||||||
|
|
||||||
- Il faut un fichier .env qu'on ne doit pas push, donc je ne le push pas.
|
- Il faut un fichier .env qu'on ne doit pas push, donc je ne le push pas.
|
||||||
@@ -6,60 +10,28 @@
|
|||||||
- Il faut le placer au même endroit que docker-compose.yml
|
- Il faut le placer au même endroit que docker-compose.yml
|
||||||
- Dans le makefile il y a un sedf pour changer l'un ou l'autre.
|
- Dans le makefile il y a un sedf pour changer l'un ou l'autre.
|
||||||
|
|
||||||
|
- also add an alias for transcendance in /etc/hosts
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### TODO List : Utilisateur édition.
|
### TODO List : Utilisateur édition.
|
||||||
|
|
||||||
- [x] Utilisateur : faire la base pour un utilisateur
|
- [x] Utilisateur : faire la base pour un utilisateur
|
||||||
- [x] Utilisateur : faire le système de requêtes amis
|
- [x] Utilisateur : faire le système de requêtes amis
|
||||||
- [ ] Utilisateur : mettre en place le système de session (voire de statut ?)
|
- [x] Utilisateur : mettre en place le système de session (voire de statut ?)
|
||||||
- [ ] Utilisateur : mettre en place le système d'avatar
|
- [x] Utilisateur : mettre en place le système d'avatar
|
||||||
- [ ] Utilisateur : mettre en place la double authentification
|
- [x] Utilisateur : mettre en place la double authentification
|
||||||
- [ ] Utilisateur : mettre en place le système d'Oauth
|
- [x] Utilisateur : mettre en place le système d'Oauth
|
||||||
- [ ] Utilisateur : mettre en place la hashage de mot de passe (avec Oauth)
|
- [x] Utilisateur : mettre en place la hashage de mot de passe (avec Oauth)
|
||||||
- [ ] Utilisateur : mettre en place le système de statut
|
- [x] Utilisateur : mettre en place le système de statut
|
||||||
- [ ] Utilisateur : mettre en place le système de stats
|
- [x] Utilisateur : mettre en place le système de stats
|
||||||
- [ ] Utilisateur : mettre en place l'historique des matches
|
- [x] Utilisateur : mettre en place l'historique des matches
|
||||||
|
|
||||||
### TODO List : Docker édition.
|
### TODO List : Docker édition.
|
||||||
|
|
||||||
- [ ] Docker : trouver un moyen simple de générer un .env. Peut-être renouveller les clé à chaque lancement.
|
- [ ] Docker : trouver un moyen simple de générer un .env. Peut-être renouveller les clé à chaque lancement.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
## questions :
|
|
||||||
|
|
||||||
- choose a secondary browser
|
|
||||||
- choose solution to rootless mode (VM, rebuild, unique root uid)
|
|
||||||
- choose 2fa method : texto ? Google Authentication ?
|
|
||||||
- when chat-room owner leaves what happens ?
|
|
||||||
- can chat-room administrators ban and mute other administrators ?
|
|
||||||
- game technologie (canvas ?)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## tasks :
|
|
||||||
|
|
||||||
#### luke
|
|
||||||
|
|
||||||
- getting started with Javascript
|
|
||||||
- getting started with Node.js/Nest.js
|
|
||||||
- pong game solo front-end (canvas ? js ?)
|
|
||||||
- add multiplayers to Pong with Node.js (without database, accounts, ...)
|
|
||||||
|
|
||||||
#### eric
|
|
||||||
|
|
||||||
- getting started with Javascript
|
|
||||||
- getting started with framework TypeScript front-end (Svelte ?)
|
|
||||||
- single-page démo, sans back-end
|
|
||||||
- chat front-end ? (tester avec des messages random locaux)
|
|
||||||
|
|
||||||
#### hugo
|
|
||||||
|
|
||||||
- getting started with Javascript
|
|
||||||
- getting started with Node.js/Nest.js
|
|
||||||
- getting started with PostgreSQL
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## instructions :
|
## instructions :
|
||||||
@@ -82,70 +54,81 @@
|
|||||||
#### security concerns :
|
#### security concerns :
|
||||||
|
|
||||||
- [ ] hash every passwords in db
|
- [ ] hash every passwords in db
|
||||||
|
|
||||||
- [ ] protection against SQL injections
|
- [ ] protection against SQL injections
|
||||||
|
|
||||||
- [ ] server-side validation of users inputs
|
- [ ] server-side validation of users inputs
|
||||||
|
|
||||||
- [ ] store credentials in local .env git-ignored
|
- [ ] store credentials in local .env git-ignored
|
||||||
|
|
||||||
#### user account :
|
#### user account :
|
||||||
|
|
||||||
- [ ] login with 42 intranet OAuth system
|
- [ ] login with 42 intranet OAuth system
|
||||||
|
|
||||||
- [ ] user can choose name, avatar, 2fa (ex texto or Google Authenticator)
|
- [ ] user can choose name, avatar, 2fa (ex texto or Google Authenticator)
|
||||||
|
|
||||||
- [ ] display user name on site
|
- [ ] display user name on site
|
||||||
|
|
||||||
- [ ] user default avatar if not chosen
|
- [ ] user default avatar if not chosen
|
||||||
|
|
||||||
- [ ] user can add friends, and see status (online/offline, in game, ...)
|
- [ ] user can add friends, and see status (online/offline, in game, ...)
|
||||||
|
|
||||||
- [ ] display stats on user profile (wins, losses, ladderm levelm achievements, ...)
|
- [ ] display stats on user profile (wins, losses, ladderm levelm achievements, ...)
|
||||||
|
|
||||||
- [ ] public match history (lvl games, ladder, ...)
|
- [ ] public match history (lvl games, ladder, ...)
|
||||||
|
|
||||||
#### chat :
|
#### chat :
|
||||||
|
|
||||||
- [ ] can create chat-rooms (public/private, password protected)
|
- [ ] can create chat-rooms (public/private, password protected)
|
||||||
|
|
||||||
- [ ] send direct messages
|
- [ ] send direct messages
|
||||||
|
|
||||||
- [ ] block other users
|
- [ ] block other users
|
||||||
|
|
||||||
- [ ] creators of chat-room are owners, untill they leave
|
- [ ] creators of chat-room are owners, untill they leave
|
||||||
|
|
||||||
- [ ] chat-room owner can set, change, remove password
|
- [ ] chat-room owner can set, change, remove password
|
||||||
|
|
||||||
- [ ] chat-room owner is administrator and can set other administrators
|
- [ ] chat-room owner is administrator and can set other administrators
|
||||||
|
|
||||||
- [ ] administrators can ban or mute for a time other users
|
- [ ] administrators can ban or mute for a time other users
|
||||||
|
|
||||||
- [ ] send game invitation in chat
|
- [ ] send game invitation in chat
|
||||||
|
|
||||||
- [ ] view user profiles from chat
|
- [ ] view user profiles from chat
|
||||||
|
|
||||||
#### game :
|
#### game :
|
||||||
|
|
||||||
- [ ] play pong with others on website
|
- [ ] play pong with others on website
|
||||||
|
|
||||||
- [ ] matchmaking system : join a queue untill automatic match
|
- [ ] matchmaking system : join a queue untill automatic match
|
||||||
|
|
||||||
- [ ] faithfull to original pong (1972)
|
- [ ] faithfull to original pong (1972)
|
||||||
|
|
||||||
- [ ] customs options (powers up, multiple maps, ...), with a default one
|
- [ ] customs options (powers up, multiple maps, ...), with a default one
|
||||||
|
|
||||||
- [ ] reponsive
|
- [ ] reponsive
|
||||||
|
|
||||||
- [ ] can watch other matchs
|
- [ ] can watch other matchs
|
||||||
|
|
||||||
|
|
||||||
---
|
---
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
|
- [routes back](https://semestriel.framapad.org/p/z5gqbq51dx-9xlo?lang=fr)
|
||||||
|
|
||||||
|
### error msg
|
||||||
|
- [rollup packages did not export](https://stackoverflow.com/questions/69768925/rollup-plugin-svelte-the-following-packages-did-not-export-their-package-json)
|
||||||
|
|
||||||
### Svelte
|
### Svelte
|
||||||
- [The Official Svelte Tutorial](https://svelte.dev/tutorial/basics)
|
- [The Official Svelte Tutorial](https://svelte.dev/tutorial/basics)
|
||||||
- SPA Svelte Article [Build a single-page application in Svelte with svelte-spa-router](https://blog.logrocket.com/build-spa-svelte-svelte-spa-router/)
|
- SPA Svelte Article [Build a single-page application in Svelte with svelte-spa-router](https://blog.logrocket.com/build-spa-svelte-svelte-spa-router/)
|
||||||
- [An excellent Svelt Tutorial video series](https://www.youtube.com/watch?v=zojEMeQGGHs&list=PL4cUxeGkcC9hlbrVO_2QFVqVPhlZmz7tO&index=2)
|
- [An excellent Svelt Tutorial video series](https://www.youtube.com/watch?v=zojEMeQGGHs&list=PL4cUxeGkcC9hlbrVO_2QFVqVPhlZmz7tO&index=2)
|
||||||
|
- to check svelte logs, do a 'docker logs --follow <container-id>'
|
||||||
|
|
||||||
|
### nestjs
|
||||||
|
- [linkedin clone angular nestjs](https://www.youtube.com/watch?v=gL3D-MIt_G8&list=PL9_OU-1M9E_ut3NA04C4eHZuuAFQOUwT0&index=1)
|
||||||
|
- [nestjs crash course](https://www.youtube.com/watch?v=vGafqCNCCSs)
|
||||||
|
|
||||||
|
### websocket
|
||||||
|
- [game networking](https://gafferongames.com/post/what_every_programmer_needs_to_know_about_game_networking/)
|
||||||
|
- [client-server game architecture](https://www.gabrielgambetta.com/client-server-game-architecture.html)
|
||||||
|
- [websocket api mozilla doc](https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API)
|
||||||
|
- [websocket rfc](https://www.rfc-editor.org/rfc/rfc6455.html)
|
||||||
|
- [ws doc npm](https://www.npmjs.com/package/ws)
|
||||||
|
- [exemple chat implementation](https://github.com/mdn/samples-server/tree/master/s/websocket-chat)
|
||||||
|
- [websocket and nginx](https://www.nginx.com/blog/websocket-nginx/)
|
||||||
|
|
||||||
|
### css
|
||||||
|
- [separation of concern](https://adamwathan.me/css-utility-classes-and-separation-of-concerns/)
|
||||||
|
- [decoupling css and html](https://www.smashingmagazine.com/2012/04/decoupling-html-from-css/)
|
||||||
|
|
||||||
|
### security
|
||||||
|
- [xss attack with innerHTML](https://gomakethings.com/a-safer-alternative-to-innerhtml-with-vanilla-js/)
|
||||||
|
- [xss attack innerHTML prevention](https://stackoverflow.com/questions/30661497/xss-prevention-and-innerhtml)
|
||||||
|
- [xss attack prevention with createTextNode](https://stackoverflow.com/questions/11654555/is-createtextnode-completely-safe-from-html-injection-xss)
|
||||||
|
- [xss attacks prevention in svelte](https://stackoverflow.com/questions/74931516/in-svete-what-to-use-instead-of-html-to-avoid-xss-attacks/74932137)
|
||||||
|
|
||||||
|
### installation
|
||||||
|
- [node and npm with nvm](https://github.com/nvm-sh/nvm)
|
||||||
|
- [docker](https://github.com/docker/docker-install)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
12
docs/transcendence_chat.drawio.html
Normal file
12
docs/transcendence_chat.drawio.html
Normal file
File diff suppressed because one or more lines are too long
BIN
docs/transcendence_en.subject.pdf
Normal file
BIN
docs/transcendence_en.subject.pdf
Normal file
Binary file not shown.
148
make_env.sh
Executable file
148
make_env.sh
Executable file
@@ -0,0 +1,148 @@
|
|||||||
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
|
ENV_FILE_DOCKER=./srcs/.env
|
||||||
|
ENV_FILE_SVELTE=./srcs/requirements/svelte/api_front/.env
|
||||||
|
NGINX_CONF_FILE=./srcs/requirements/nginx/conf/default.conf
|
||||||
|
BOLD_RED="\033[1;31m"
|
||||||
|
BOLD_GREEN="\033[1;32m"
|
||||||
|
BOLD_BLUE="\033[1;34m"
|
||||||
|
RESET="\033[0m"
|
||||||
|
|
||||||
|
# Function to generate passwords
|
||||||
|
#
|
||||||
|
function generate_password
|
||||||
|
{
|
||||||
|
# base64 alphabet is alphanumeric characters and "+", "/", "="
|
||||||
|
# https://en.wikipedia.org/wiki/Base64#Base64_table_from_RFC_4648
|
||||||
|
# we could delete them 'tr -d "+/="', but that would randomly shorten the string
|
||||||
|
echo $(openssl rand -base64 32 | tr "/" "_" );
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_env_for_svelte
|
||||||
|
{
|
||||||
|
echo -e "${BOLD_BLUE}Creating a new environment for svelte${RESET}"
|
||||||
|
grep "^WEBSITE_" "$ENV_FILE_DOCKER" > "$ENV_FILE_SVELTE"
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_nginx_conf
|
||||||
|
{
|
||||||
|
echo -e "${BOLD_BLUE}Updating the nginx conf${RESET}"
|
||||||
|
echo -e "${BOLD_RED}WARNING : If for some reason you've changed the port, you MUST change it inside the nginx conf file. It's not supposed to change.${RESET}"
|
||||||
|
HOST=$(grep "^WEBSITE_HOST" $ENV_FILE_DOCKER | cut -d "=" -f 2)
|
||||||
|
sed -i "s/server_name.*/server_name $HOST;/g" "$NGINX_CONF_FILE"
|
||||||
|
}
|
||||||
|
|
||||||
|
function make_env_for_docker_and_svelte
|
||||||
|
{
|
||||||
|
docker rm -f postgresql
|
||||||
|
docker volume rm -f srcs_data_nest_postgresql
|
||||||
|
echo -e "${BOLD_BLUE}Creating a new environment for docker${RESET}"
|
||||||
|
NODE_ENV=""
|
||||||
|
# Ask if dev or prod environment
|
||||||
|
while [ "$NODE_ENV" != "1" ] && [ "$NODE_ENV" != "2" ]; do
|
||||||
|
read -p "Enter the env configuration for nestjs : \"1\" for development OR \"2\" for production : " NODE_ENV
|
||||||
|
done
|
||||||
|
if [ "$NODE_ENV" = "1" ]; then
|
||||||
|
echo "NODE_ENV=development" > "$ENV_FILE_DOCKER"
|
||||||
|
else
|
||||||
|
echo "NODE_ENV=production" > "$ENV_FILE_DOCKER"
|
||||||
|
fi
|
||||||
|
read -p "Enter the name of the host like \"localhost\" : " PROJECT_HOST
|
||||||
|
echo "WEBSITE_HOST=$PROJECT_HOST" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "WEBSITE_PORT=8080" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "POSTGRES_USER=postgres" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "#if change postgres pswd, do make destroy" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "POSTGRES_PASSWORD=$(generate_password)" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "POSTGRES_DB=transcendance_db" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "POSTGRES_HOST=postgresql" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "POSTGRES_PORT=5432" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "REDIS_HOST=redis" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "REDIS_PORT=6379" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "REDIS_PASSWORD=$(generate_password)" >> "$ENV_FILE_DOCKER"
|
||||||
|
# Connection to 42
|
||||||
|
echo -e "${BOLD_BLUE}In the next steps, we'll need to enter the client secret and client id of the 42 api${RESET}"
|
||||||
|
read -p "Enter the client id of the 42 api : " CLIENT_ID
|
||||||
|
echo "FORTYTWO_CLIENT_ID=$CLIENT_ID" >> "$ENV_FILE_DOCKER"
|
||||||
|
read -p "Enter the client secret of the 42 api : " CLIENT_SECRET
|
||||||
|
echo "FORTYTWO_CLIENT_SECRET=$CLIENT_SECRET" >> "$ENV_FILE_DOCKER"
|
||||||
|
FT_CALLBACK="http://$PROJECT_HOST:8080/api/v2/auth/redirect"
|
||||||
|
echo "FORTYTWO_CALLBACK_URL=$FT_CALLBACK" >> "$ENV_FILE_DOCKER"
|
||||||
|
# Other configs
|
||||||
|
echo "COOKIE_SECRET=$(generate_password)" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "PORT=3000" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "TWO_FACTOR_AUTHENTICATION_APP_NAME=Transcendance" >> "$ENV_FILE_DOCKER"
|
||||||
|
echo "TICKET_FOR_PLAYING_GAME_SECRET=$(generate_password)" >> "$ENV_FILE_DOCKER"
|
||||||
|
make_env_for_svelte
|
||||||
|
update_nginx_conf
|
||||||
|
echo -e "${BOLD_GREEN}Environment created.${RESET}"
|
||||||
|
echo -e "${BOLD_GREEN}The script will exit${RESET}"
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function change_host_and_port_api
|
||||||
|
{
|
||||||
|
if [ -f "$ENV_FILE_DOCKER" ]
|
||||||
|
then
|
||||||
|
echo -e "${BOLD_BLUE}Changing the host and the port of the api${RESET}"
|
||||||
|
read -p "Enter the name of the host like \"localhost\" : " PROJECT_HOST
|
||||||
|
sed -i "s/WEBSITE_HOST=.*/WEBSITE_HOST=$PROJECT_HOST/g" "$ENV_FILE_DOCKER"
|
||||||
|
read -p "Enter the port of the api : " PROJECT_PORT
|
||||||
|
sed -i "s/WEBSITE_PORT=.*/WEBSITE_PORT=$PROJECT_PORT/g" "$ENV_FILE_DOCKER"
|
||||||
|
make_env_for_svelte
|
||||||
|
update_nginx_conf
|
||||||
|
else
|
||||||
|
echo -e "${BOLD_RED}No environment file found. We will regenerate the entire env files.${RESET}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function change_api_fields_for_42_auth_api
|
||||||
|
{
|
||||||
|
if [ -f "$ENV_FILE_DOCKER" ]
|
||||||
|
then
|
||||||
|
echo -e "${BOLD_BLUE}Changing the secret for the 42 api${RESET}"
|
||||||
|
read -p "Enter the client id of the 42 api : " CLIENT_ID
|
||||||
|
sed -i "s/FORTYTWO_CLIENT_ID=.*/FORTYTWO_CLIENT_ID=$CLIENT_ID/g" "$ENV_FILE_DOCKER"
|
||||||
|
read -p "Enter the client secret of the 42 api : " CLIENT_SECRET
|
||||||
|
sed -i "s/FORTYTWO_CLIENT_SECRET=.*/FORTYTWO_CLIENT_SECRET=$CLIENT_SECRET/g" "$ENV_FILE_DOCKER"
|
||||||
|
echo -e "${BOLD_GREEN}The fields concerning the 42 api have been changed.${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "${BOLD_RED}No environment file found. We will regenerate the entire env files.${RESET}"
|
||||||
|
make_env_for_docker_and_svelte
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function choose_options_and_process {
|
||||||
|
if [ ! -f "$ENV_FILE_DOCKER" ]; then
|
||||||
|
make_env_for_docker_and_svelte
|
||||||
|
elif [ ! -f "$ENV_FILE_SVELTE" && -f "$ENV_FILE_DOCKER" ]; then
|
||||||
|
make_env_for_svelte
|
||||||
|
fi
|
||||||
|
echo -e "${BOLD_RED}An environment already exists.${RESET}"
|
||||||
|
echo -e "${BOLD_GREEN}What do you want to do ?${RESET}"
|
||||||
|
echo -e "${BOLD_GREEN}1. Regenerate entire environment${RESET}"
|
||||||
|
echo -e "${BOLD_GREEN}2. Only change the fields about the HOSTNAME and the PORT of the API${RESET}"
|
||||||
|
echo -e "${BOLD_GREEN}3. Only change the fields concerning the 42 API (id and secret)${RESET}"
|
||||||
|
echo -e "${BOLD_GREEN}4. Exit${RESET}"
|
||||||
|
CHOICE=""
|
||||||
|
while [ "$CHOICE" != "1" ] && [ "$CHOICE" != "2" ] && [ "$CHOICE" != "3" ] && [ "$CHOICE" != "4" ]; do
|
||||||
|
read -p "Enter your choice : " CHOICE
|
||||||
|
done
|
||||||
|
if [ "$CHOICE" = "1" ]; then
|
||||||
|
make_env_for_docker_and_svelte
|
||||||
|
elif [ "$CHOICE" = "2" ]; then
|
||||||
|
change_host_and_port_api
|
||||||
|
elif [ "$CHOICE" = "3" ]; then
|
||||||
|
change_api_fields_for_42_auth_api
|
||||||
|
else
|
||||||
|
echo -e "${BOLD_GREEN}The script will exit.${RESET}"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create a new environment for docker
|
||||||
|
|
||||||
|
choose_options_and_process
|
||||||
|
|
||||||
|
echo "The environment has been created successfully. You can now wait for the docker to build the project."
|
||||||
|
|
||||||
5
package-lock.json
generated
5
package-lock.json
generated
@@ -1,6 +1,3 @@
|
|||||||
{
|
{
|
||||||
"name": "group_transcendence",
|
"lockfileVersion": 1
|
||||||
"lockfileVersion": 2,
|
|
||||||
"requires": true,
|
|
||||||
"packages": {}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
NODE_ENV=development
|
|
||||||
POSTGRES_USER=postgres
|
|
||||||
POSTGRES_PASSWORD=9pKpKEgiamxwk5P7Ggsz
|
|
||||||
POSTGRES_DB=transcendance_db
|
|
||||||
POSTGRES_HOST=postgresql
|
|
||||||
POSTGRES_PORT=5432
|
|
||||||
REDIS_HOST=redis
|
|
||||||
REDIS_PORT=6379
|
|
||||||
REDIS_PASSWORD=1a5e04138b91b3d683c708e4689454c2
|
|
||||||
@@ -1,22 +1,54 @@
|
|||||||
services:
|
services:
|
||||||
backend_dev:
|
backend_dev:
|
||||||
|
container_name: nestjs
|
||||||
build:
|
build:
|
||||||
context: ./requirements/nestjs
|
context: ./requirements/nestjs
|
||||||
target: development
|
target: development
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: "${POSTGRES_USER}"
|
||||||
|
POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
|
||||||
|
POSTGRES_DB: "${POSTGRES_DB}"
|
||||||
|
POSTGRES_HOST: "${POSTGRES_HOST}"
|
||||||
|
POSTGRES_PORT: "${POSTGRES_PORT}"
|
||||||
|
NODE_ENV: "${NODE_ENV}"
|
||||||
|
WEBSITE_HOST: "${WEBSITE_HOST}"
|
||||||
|
WEBSITE_PORT: "${WEBSITE_PORT}"
|
||||||
|
REDIS_HOST: "${REDIS_HOST}"
|
||||||
|
REDIS_PORT: "${REDIS_PORT}"
|
||||||
|
FORTYTWO_CLIENT_ID: "${FORTYTWO_CLIENT_ID}"
|
||||||
|
FORTYTWO_CLIENT_SECRET : "${FORTYTWO_CLIENT_SECRET}"
|
||||||
|
FORTYTWO_CALLBACK_URL: "${FORTYTWO_CALLBACK_URL}"
|
||||||
|
COOKIE_SECRET: "${COOKIE_SECRET}"
|
||||||
|
TWO_FACTOR_AUTHENTICATION_APP_NAME : "${TWO_FACTOR_AUTHENTICATION_APP_NAME}"
|
||||||
|
TICKET_FOR_PLAYING_GAME_SECRET : "${TICKET_FOR_PLAYING_GAME_SECRET}"
|
||||||
|
PORT: "${PORT}"
|
||||||
volumes:
|
volumes:
|
||||||
- ./requirements/nestjs/api_back/src:/usr/app/src
|
- ./requirements/nestjs/api_back/src:/usr/app/src
|
||||||
- ./requirements/nestjs/api_back/test:/usr/app/test/
|
- ./requirements/nestjs/api_back/test:/usr/app/test/
|
||||||
env_file:
|
- nestjs_photos_volume:/usr/app/src/uploads/avatars
|
||||||
- .env
|
|
||||||
environment:
|
|
||||||
NODE_ENV: "${NODE_ENV}"
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgresql
|
- postgresql
|
||||||
- redis
|
- redis
|
||||||
|
|
||||||
|
game_server:
|
||||||
|
container_name: game_server
|
||||||
|
build:
|
||||||
|
context: ./requirements/game_server
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
environment:
|
||||||
|
NODE_ENV: "${NODE_ENV}"
|
||||||
|
WEBSITE_HOST: "${WEBSITE_HOST}"
|
||||||
|
WEBSITE_PORT: "${WEBSITE_PORT}"
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "8042:8042"
|
||||||
|
depends_on:
|
||||||
|
- backend_dev
|
||||||
|
|
||||||
frontend_dev:
|
frontend_dev:
|
||||||
|
container_name: svelte
|
||||||
build:
|
build:
|
||||||
context: ./requirements/svelte
|
context: ./requirements/svelte
|
||||||
target: development
|
target: development
|
||||||
@@ -26,9 +58,9 @@ services:
|
|||||||
- ./requirements/svelte/api_front/public:/usr/app/public/
|
- ./requirements/svelte/api_front/public:/usr/app/public/
|
||||||
ports:
|
ports:
|
||||||
- "35729:35729"
|
- "35729:35729"
|
||||||
env_file:
|
|
||||||
- .env
|
|
||||||
environment:
|
environment:
|
||||||
|
WEBSITE_HOST: "${WEBSITE_HOST}"
|
||||||
|
WEBSITE_PORT: "${WEBSITE_PORT}"
|
||||||
NODE_ENV: "${NODE_ENV}"
|
NODE_ENV: "${NODE_ENV}"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
@@ -38,10 +70,12 @@ services:
|
|||||||
|
|
||||||
# t'embete pas a gerer ton propre container nginx
|
# t'embete pas a gerer ton propre container nginx
|
||||||
nginx:
|
nginx:
|
||||||
|
container_name: nginx
|
||||||
image: nginx:alpine
|
image: nginx:alpine
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
volumes:
|
volumes:
|
||||||
- ./requirements/nginx/conf/default.conf:/etc/nginx/conf.d/default.conf:ro
|
- ./requirements/nginx/conf/default.conf:/etc/nginx/conf.d/default.conf:ro
|
||||||
|
- ./requirements/nginx/conf/nginx.conf:/etc/nginx/nginx.conf:ro
|
||||||
command: [nginx-debug, "-g", "daemon off;"]
|
command: [nginx-debug, "-g", "daemon off;"]
|
||||||
ports:
|
ports:
|
||||||
- "8080:8080"
|
- "8080:8080"
|
||||||
@@ -52,7 +86,7 @@ services:
|
|||||||
- redis
|
- redis
|
||||||
|
|
||||||
postgresql:
|
postgresql:
|
||||||
container_name: nestjs_postgresql
|
container_name: postgresql
|
||||||
image: postgres
|
image: postgres
|
||||||
volumes:
|
volumes:
|
||||||
- data_nest_postgresql:/var/lib/postgresql/data
|
- data_nest_postgresql:/var/lib/postgresql/data
|
||||||
@@ -66,10 +100,8 @@ services:
|
|||||||
|
|
||||||
# Je connais pas redis, mais si t'en a besoin que a l'interieur de tes containers, je pense pas que t'as besoin d'un expose.
|
# Je connais pas redis, mais si t'en a besoin que a l'interieur de tes containers, je pense pas que t'as besoin d'un expose.
|
||||||
redis:
|
redis:
|
||||||
container_name: nestjs_redis
|
container_name: redis
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
expose:
|
|
||||||
- "6379"
|
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
environment:
|
environment:
|
||||||
REDIS_HOST: "${REDIS_HOST}"
|
REDIS_HOST: "${REDIS_HOST}"
|
||||||
@@ -77,3 +109,4 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
data_nest_postgresql:
|
data_nest_postgresql:
|
||||||
|
nestjs_photos_volume:
|
||||||
|
|||||||
15
srcs/requirements/game_server/Dockerfile
Normal file
15
srcs/requirements/game_server/Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM node:alpine AS build
|
||||||
|
|
||||||
|
WORKDIR /usr/app
|
||||||
|
|
||||||
|
COPY ./game_back ./
|
||||||
|
|
||||||
|
RUN npm install typescript
|
||||||
|
|
||||||
|
RUN npx tsc
|
||||||
|
|
||||||
|
WORKDIR /usr/app/src/server
|
||||||
|
|
||||||
|
EXPOSE 8042
|
||||||
|
|
||||||
|
CMD [ "node", "wsServer.js"]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "ft_transcendence",
|
"name": "game_back",
|
||||||
"lockfileVersion": 2,
|
"lockfileVersion": 2,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"@types/node": "^18.11.5",
|
"@types/node": "^18.11.5",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.9.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
@@ -37,9 +37,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/typescript": {
|
"node_modules/typescript": {
|
||||||
"version": "4.8.4",
|
"version": "4.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"tsc": "bin/tsc",
|
"tsc": "bin/tsc",
|
||||||
@@ -101,9 +101,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"typescript": {
|
"typescript": {
|
||||||
"version": "4.8.4",
|
"version": "4.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.4.tgz",
|
||||||
"integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==",
|
"integrity": "sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"uuid": {
|
"uuid": {
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
"@types/node": "^18.11.5",
|
"@types/node": "^18.11.5",
|
||||||
"@types/uuid": "^8.3.4",
|
"@types/uuid": "^8.3.4",
|
||||||
"@types/ws": "^8.5.3",
|
"@types/ws": "^8.5.3",
|
||||||
"typescript": "^4.8.4"
|
"typescript": "^4.9.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
@@ -2,22 +2,24 @@
|
|||||||
import { WebSocket } from "../wsServer.js";
|
import { WebSocket } from "../wsServer.js";
|
||||||
import { Racket } from "../../shared_js/class/Rectangle.js";
|
import { Racket } from "../../shared_js/class/Rectangle.js";
|
||||||
import { GameSession } from "./GameSession.js";
|
import { GameSession } from "./GameSession.js";
|
||||||
import * as ev from "./Event.js"
|
import * as ev from "../../shared_js/class/Event.js"
|
||||||
import * as en from "../enums.js"
|
import * as en from "../../shared_js/enums.js"
|
||||||
|
|
||||||
class Client {
|
export class Client {
|
||||||
socket: WebSocket;
|
socket: WebSocket;
|
||||||
id: string; // Pas indispensable si "socket" a une copie de "id"
|
id: string; // same as "socket.id"
|
||||||
isAlive: boolean = true;
|
isAlive: boolean = true;
|
||||||
gameSession: GameSession = null;
|
gameSession: GameSession = null;
|
||||||
matchOptions: en.MatchOptions = 0;
|
|
||||||
constructor(socket: WebSocket, id: string) {
|
constructor(socket: WebSocket, id: string) {
|
||||||
this.socket = socket;
|
this.socket = socket;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientPlayer extends Client {
|
export class ClientPlayer extends Client {
|
||||||
|
token: string;
|
||||||
|
username: string;
|
||||||
|
matchOptions: en.MatchOptions = 0;
|
||||||
inputBuffer: ev.EventInput = new ev.EventInput();
|
inputBuffer: ev.EventInput = new ev.EventInput();
|
||||||
lastInputId: number = 0;
|
lastInputId: number = 0;
|
||||||
racket: Racket;
|
racket: Racket;
|
||||||
@@ -27,10 +29,8 @@ class ClientPlayer extends Client {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ClientSpectator extends Client { // Wip, unused
|
export class ClientSpectator extends Client {
|
||||||
constructor(socket: WebSocket, id: string) {
|
constructor(socket: WebSocket, id: string) {
|
||||||
super(socket, id);
|
super(socket, id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {Client, ClientPlayer, ClientSpectator}
|
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
|
|
||||||
import * as c from "../constants.js"
|
import * as en from "../../shared_js/enums.js"
|
||||||
import * as en from "../enums.js"
|
|
||||||
import { GameComponents } from "../../shared_js/class/GameComponents.js";
|
import { GameComponents } from "../../shared_js/class/GameComponents.js";
|
||||||
|
|
||||||
class GameComponentsServer extends GameComponents {
|
export class GameComponentsServer extends GameComponents {
|
||||||
scoreLeft: number = 0;
|
scoreLeft: number = 0;
|
||||||
scoreRight: number = 0;
|
scoreRight: number = 0;
|
||||||
constructor(options: en.MatchOptions)
|
constructor(options: en.MatchOptions)
|
||||||
@@ -11,5 +10,3 @@ class GameComponentsServer extends GameComponents {
|
|||||||
super(options);
|
super(options);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {GameComponentsServer}
|
|
||||||
@@ -0,0 +1,302 @@
|
|||||||
|
|
||||||
|
import * as en from "../../shared_js/enums.js"
|
||||||
|
import * as ev from "../../shared_js/class/Event.js"
|
||||||
|
import * as c from "../constants.js"
|
||||||
|
import { ClientPlayer, ClientSpectator } from "./Client";
|
||||||
|
import { GameComponentsServer } from "./GameComponentsServer.js";
|
||||||
|
import { clientInputListener, clientTerminate } from "../wsServer.js";
|
||||||
|
import { random } from "../utils.js";
|
||||||
|
import { Ball } from "../../shared_js/class/Rectangle.js";
|
||||||
|
import { wallsMovements } from "../../shared_js/wallsMovement.js";
|
||||||
|
|
||||||
|
/*
|
||||||
|
multiples methods of GameSession have parameter "s: GameSession".
|
||||||
|
its used with calls to setTimeout(),
|
||||||
|
because "this" is not equal to the GameSession but to "this: Timeout"
|
||||||
|
*/
|
||||||
|
export class GameSession {
|
||||||
|
id: string; // url ?
|
||||||
|
playersMap: Map<string, ClientPlayer> = new Map();
|
||||||
|
unreadyPlayersMap: Map<string, ClientPlayer> = new Map();
|
||||||
|
spectatorsMap: Map<string, ClientSpectator> = new Map();
|
||||||
|
gameLoopInterval: NodeJS.Timer | number = 0;
|
||||||
|
playersUpdateInterval: NodeJS.Timer | number = 0;
|
||||||
|
spectatorsUpdateInterval: NodeJS.Timer | number = 0;
|
||||||
|
components: GameComponentsServer;
|
||||||
|
matchOptions: en.MatchOptions;
|
||||||
|
isPrivateMatch: boolean; // WIP: could be used to separate leaderboards for example.
|
||||||
|
matchEnded: boolean = false;
|
||||||
|
lastStateSnapshot: ev.EventGameUpdate;
|
||||||
|
|
||||||
|
actual_time: number;
|
||||||
|
last_time: number;
|
||||||
|
delta_time: number;
|
||||||
|
|
||||||
|
constructor(id: string, matchOptions: en.MatchOptions, isPrivateMatch: boolean = false) {
|
||||||
|
this.id = id;
|
||||||
|
this.matchOptions = matchOptions;
|
||||||
|
this.isPrivateMatch = isPrivateMatch;
|
||||||
|
this.components = new GameComponentsServer(this.matchOptions);
|
||||||
|
}
|
||||||
|
start() {
|
||||||
|
const gc = this.components;
|
||||||
|
setTimeout(this.resume, c.matchStartDelay, this);
|
||||||
|
|
||||||
|
let timeout = c.matchStartDelay + c.newRoundDelay;
|
||||||
|
gc.ballsArr.forEach((ball) => {
|
||||||
|
setTimeout(this._newRound, timeout, this, ball);
|
||||||
|
timeout += c.newRoundDelay*0.5;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
resume(s?: GameSession)
|
||||||
|
{
|
||||||
|
if (!s) { s = this; }
|
||||||
|
|
||||||
|
s.playersMap.forEach( (client) => {
|
||||||
|
client.socket.on("message", clientInputListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
s.actual_time = Date.now();
|
||||||
|
s.lastStateSnapshot = s._gameStateSnapshot();
|
||||||
|
s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s);
|
||||||
|
s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s);
|
||||||
|
s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s);
|
||||||
|
}
|
||||||
|
pause(s?: GameSession)
|
||||||
|
{
|
||||||
|
if (!s) { s = this; }
|
||||||
|
|
||||||
|
s.playersMap.forEach( (client) => {
|
||||||
|
client.socket.off("message", clientInputListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
clearInterval(s.gameLoopInterval);
|
||||||
|
clearInterval(s.playersUpdateInterval);
|
||||||
|
clearInterval(s.spectatorsUpdateInterval);
|
||||||
|
}
|
||||||
|
destroy(s?: GameSession)
|
||||||
|
{
|
||||||
|
if (!s) { s = this; }
|
||||||
|
|
||||||
|
s.pause();
|
||||||
|
|
||||||
|
s.spectatorsMap.forEach((client) => {
|
||||||
|
clientTerminate(client);
|
||||||
|
});
|
||||||
|
s.playersMap.forEach((client) => {
|
||||||
|
clientTerminate(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
instantInputDebug(client: ClientPlayer) {
|
||||||
|
this._handleInput(c.fixedDeltaTime, client);
|
||||||
|
}
|
||||||
|
private _handleInput(delta: number, client: ClientPlayer) {
|
||||||
|
// if (client.inputBuffer === null) {return;}
|
||||||
|
const gc = this.components;
|
||||||
|
const input = client.inputBuffer.input;
|
||||||
|
|
||||||
|
if (input === en.InputEnum.up) {
|
||||||
|
client.racket.dir.y = -1;
|
||||||
|
}
|
||||||
|
else if (input === en.InputEnum.down) {
|
||||||
|
client.racket.dir.y = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input !== en.InputEnum.noInput) {
|
||||||
|
client.racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
||||||
|
}
|
||||||
|
|
||||||
|
client.lastInputId = client.inputBuffer.id;
|
||||||
|
// client.inputBuffer = null;
|
||||||
|
}
|
||||||
|
private _gameLoop(s: GameSession) {
|
||||||
|
/* s.last_time = s.actual_time;
|
||||||
|
s.actual_time = Date.now();
|
||||||
|
s.delta_time = (s.actual_time - s.last_time) / 1000; */
|
||||||
|
s.delta_time = c.fixedDeltaTime;
|
||||||
|
|
||||||
|
// WIP, replaced by instantInputDebug() to prevent desynchro
|
||||||
|
/* s.playersMap.forEach( (client) => {
|
||||||
|
s._handleInput(s.delta_time, client);
|
||||||
|
}); */
|
||||||
|
|
||||||
|
const gc = s.components;
|
||||||
|
gc.ballsArr.forEach((ball) => {
|
||||||
|
s._ballMovement(s.delta_time, ball);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (s.matchOptions & en.MatchOptions.movingWalls) {
|
||||||
|
wallsMovements(s.delta_time, gc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private _ballMovement(delta: number, ball: Ball) {
|
||||||
|
const gc = this.components;
|
||||||
|
if (ball.ballInPlay)
|
||||||
|
{
|
||||||
|
ball.moveAndBounce(delta, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
|
||||||
|
if (ball.pos.x > c.w
|
||||||
|
|| ball.pos.x < 0 - ball.width)
|
||||||
|
{
|
||||||
|
ball.ballInPlay = false;
|
||||||
|
if (this.matchEnded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._scoreUpdate(ball);
|
||||||
|
setTimeout(this._newRound, c.newRoundDelay, this, ball);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private _scoreUpdate(ball: Ball) {
|
||||||
|
const gc = this.components;
|
||||||
|
if (ball.pos.x > c.w) {
|
||||||
|
++gc.scoreLeft;
|
||||||
|
}
|
||||||
|
else if (ball.pos.x < 0 - ball.width) {
|
||||||
|
++gc.scoreRight;
|
||||||
|
}
|
||||||
|
const scoreUpdate = new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight);
|
||||||
|
this.playersMap.forEach( (client) => {
|
||||||
|
client.socket.send(JSON.stringify(scoreUpdate));
|
||||||
|
});
|
||||||
|
this.spectatorsMap.forEach( (client) => {
|
||||||
|
client.socket.send(JSON.stringify(scoreUpdate));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _playersUpdate(s: GameSession) {
|
||||||
|
s.lastStateSnapshot = s._gameStateSnapshot();
|
||||||
|
s.playersMap.forEach( (client) => {
|
||||||
|
s.lastStateSnapshot.lastInputId = client.lastInputId;
|
||||||
|
client.socket.send(JSON.stringify(s.lastStateSnapshot));
|
||||||
|
});
|
||||||
|
s.lastStateSnapshot.lastInputId = 0;
|
||||||
|
}
|
||||||
|
private _spectatorsUpdate(s: GameSession) {
|
||||||
|
s.spectatorsMap.forEach( (client) => {
|
||||||
|
client.socket.send(JSON.stringify(s.lastStateSnapshot));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
private _gameStateSnapshot() : ev.EventGameUpdate {
|
||||||
|
const gc = this.components;
|
||||||
|
const snapshot = new ev.EventGameUpdate();
|
||||||
|
snapshot.playerLeft.y = gc.playerLeft.pos.y;
|
||||||
|
snapshot.playerRight.y = gc.playerRight.pos.y;
|
||||||
|
gc.ballsArr.forEach((ball) => {
|
||||||
|
snapshot.ballsArr.push({
|
||||||
|
x: ball.pos.x,
|
||||||
|
y: ball.pos.y,
|
||||||
|
dirX: ball.dir.x,
|
||||||
|
dirY: ball.dir.y,
|
||||||
|
speed: ball.speed
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (this.matchOptions & en.MatchOptions.movingWalls) {
|
||||||
|
snapshot.wallTop.y = gc.wallTop.pos.y;
|
||||||
|
snapshot.wallBottom.y = gc.wallBottom.pos.y;
|
||||||
|
}
|
||||||
|
return (snapshot);
|
||||||
|
}
|
||||||
|
private _newRound(s: GameSession, ball: Ball) {
|
||||||
|
if (s._checkDisconnexions()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// https://fr.wikipedia.org/wiki/Tennis_de_table#Nombre_de_manches
|
||||||
|
const gc = s.components;
|
||||||
|
const minScore = 11;// can be changed for testing
|
||||||
|
if (gc.scoreLeft >= minScore || gc.scoreRight >= minScore)
|
||||||
|
{
|
||||||
|
if (Math.abs(gc.scoreLeft - gc.scoreRight) >= 2)
|
||||||
|
{
|
||||||
|
if (gc.scoreLeft > gc.scoreRight) {
|
||||||
|
s._matchEnd(en.PlayerSide.left);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
s._matchEnd(en.PlayerSide.right);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ball.pos.x = c.w_mid;
|
||||||
|
ball.pos.y = random(c.h*0.3, c.h*0.7);
|
||||||
|
ball.speed = ball.baseSpeed;
|
||||||
|
ball.ballInPlay = true;
|
||||||
|
}
|
||||||
|
private _checkDisconnexions()
|
||||||
|
{
|
||||||
|
if (this.playersMap.size !== 2)
|
||||||
|
{
|
||||||
|
this.matchEnded = true;
|
||||||
|
if (this.playersMap.size != 0) {
|
||||||
|
this._forfeit();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// WIP: envoyer un truc à Nest ? Genre "match draw"
|
||||||
|
this.destroy();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
private _forfeit()
|
||||||
|
{
|
||||||
|
this.matchEnded = true;
|
||||||
|
console.log("Forfeit Ending");
|
||||||
|
const gc = this.components;
|
||||||
|
const luckyWinner: ClientPlayer = this.playersMap.values().next().value;
|
||||||
|
if (luckyWinner.racket === gc.playerLeft) {
|
||||||
|
this._matchEnd(en.PlayerSide.left, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this._matchEnd(en.PlayerSide.right, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async _matchEnd(winner: en.PlayerSide, forfeit_flag: boolean = false)
|
||||||
|
{
|
||||||
|
this.matchEnded = true;
|
||||||
|
const eventEnd = new ev.EventMatchEnd(winner, forfeit_flag);
|
||||||
|
this.playersMap.forEach( (client) => {
|
||||||
|
client.socket.send(JSON.stringify(eventEnd));
|
||||||
|
});
|
||||||
|
this.spectatorsMap.forEach( (client) => {
|
||||||
|
client.socket.send(JSON.stringify(eventEnd));
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: mettre à jour la route pour gerer les forfaits (actuellement le plus haut score gagne par defaut)
|
||||||
|
const gc = this.components;
|
||||||
|
console.log("================================= MATCH ENDED");
|
||||||
|
if (forfeit_flag) {
|
||||||
|
if (winner === en.PlayerSide.left)
|
||||||
|
{
|
||||||
|
gc.scoreLeft = 3
|
||||||
|
gc.scoreRight = 0
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
gc.scoreLeft = 0
|
||||||
|
gc.scoreRight = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await fetch(c.addressBackEnd + "/game/gameserver/updategame",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
gameServerIdOfTheMatch: this.id,
|
||||||
|
playerOneUsernameResult: gc.scoreLeft,
|
||||||
|
playerTwoUsernameResult: gc.scoreRight,
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(this.destroy, 15000, this);
|
||||||
|
|
||||||
|
// logs
|
||||||
|
if (winner === en.PlayerSide.left) {
|
||||||
|
console.log("Player Left WIN");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Player Right WIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
export * from "../shared_js/constants.js"
|
||||||
|
|
||||||
|
// 15ms == 1000/66.666
|
||||||
|
export const serverGameLoopIntervalMS = 15; // millisecond
|
||||||
|
export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second
|
||||||
|
|
||||||
|
// 33.333ms == 1000/30
|
||||||
|
export const playersUpdateIntervalMS = 1000/30; // millisecond
|
||||||
|
export const spectatorsUpdateIntervalMS = 1000/30; // millisecond
|
||||||
|
|
||||||
|
export const addressBackEnd = "http://backend_dev:3000/api/v2";
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
|
||||||
|
export * from "../shared_js/utils.js"
|
||||||
|
|
||||||
|
export function shortId(id: string): string {
|
||||||
|
return id.substring(0, id.indexOf("-"));
|
||||||
|
}
|
||||||
426
srcs/requirements/game_server/game_back/src/server/wsServer.ts
Normal file
426
srcs/requirements/game_server/game_back/src/server/wsServer.ts
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
|
||||||
|
import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws";
|
||||||
|
|
||||||
|
export class WebSocket extends BaseLibWebSocket {
|
||||||
|
id?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
import { IncomingMessage } from "http";
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
|
import * as en from "../shared_js/enums.js"
|
||||||
|
import * as ev from "../shared_js/class/Event.js"
|
||||||
|
import * as c from "./constants.js"
|
||||||
|
import { Client, ClientPlayer, ClientSpectator } from "./class/Client.js"
|
||||||
|
import { GameSession } from "./class/GameSession.js"
|
||||||
|
import { shortId } from "./utils.js";
|
||||||
|
|
||||||
|
const wsPort = 8042;
|
||||||
|
export const wsServer = new WebSocketServer<WebSocket>({host: "0.0.0.0", port: wsPort, path: "/pong"});
|
||||||
|
const clientsMap: Map<string, Client> = new Map; // socket.id/Client
|
||||||
|
const matchmakingMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
|
||||||
|
const privateMatchmakingMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
|
||||||
|
const gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession
|
||||||
|
|
||||||
|
wsServer.on("connection", serverConnectionListener);
|
||||||
|
wsServer.on("error", serverErrorListener);
|
||||||
|
wsServer.on("close", serverCloseListener);
|
||||||
|
|
||||||
|
|
||||||
|
function serverConnectionListener(socket: WebSocket, request: IncomingMessage)
|
||||||
|
{
|
||||||
|
const id = uuidv4();
|
||||||
|
const client = new Client(socket, id);
|
||||||
|
clientsMap.set(id, client);
|
||||||
|
socket.id = id;
|
||||||
|
|
||||||
|
socket.on("pong", function heartbeat() {
|
||||||
|
client.isAlive = true;
|
||||||
|
console.log(`client ${shortId(client.id)} is alive`);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("close", function removeClient() {
|
||||||
|
clientTerminate(client);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("error", function errorLog(this: WebSocket, err: Error) {
|
||||||
|
console.log(`error socket ${shortId(this.id)}:`);
|
||||||
|
console.log(`${err.name}: ${err.message}`);
|
||||||
|
if (err.stack) {
|
||||||
|
console.log(`err.stack: ${err.stack}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on("message", function messageLog(data: string) {
|
||||||
|
try {
|
||||||
|
const event: ev.ClientEvent = JSON.parse(data);
|
||||||
|
if (event.type === en.EventTypes.clientInput) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {}
|
||||||
|
console.log("data: " + data);
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.once("message", clientAnnounceListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function clientAnnounceListener(this: WebSocket, data: string)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const msg : ev.ClientAnnounce = JSON.parse(data);
|
||||||
|
if (msg.type === en.EventTypes.clientAnnounce)
|
||||||
|
{
|
||||||
|
// BONUS: reconnection with msg.clientId ?
|
||||||
|
if (msg.role === en.ClientRole.player)
|
||||||
|
{
|
||||||
|
const announce: ev.ClientAnnouncePlayer = <ev.ClientAnnouncePlayer>msg;
|
||||||
|
|
||||||
|
const body = {
|
||||||
|
playerOneUsername: announce.username,
|
||||||
|
playerTwoUsername: "",
|
||||||
|
gameOptions: announce.matchOptions,
|
||||||
|
isGameIsWithInvitation: announce.privateMatch,
|
||||||
|
token: announce.token,
|
||||||
|
};
|
||||||
|
if (announce.privateMatch) {
|
||||||
|
body.playerTwoUsername = announce.playerTwoUsername;
|
||||||
|
}
|
||||||
|
const response = await fetch(c.addressBackEnd + "/game/gameserver/validate",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
{
|
||||||
|
this.send(JSON.stringify( new ev.EventError((await response.json()).message) ));
|
||||||
|
clientTerminate(clientsMap.get(this.id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = clientsMap.get(this.id) as ClientPlayer;
|
||||||
|
player.matchOptions = announce.matchOptions;
|
||||||
|
player.token = announce.token;
|
||||||
|
player.username = announce.username;
|
||||||
|
this.send(JSON.stringify( new ev.EventAssignId(this.id) )); // unused
|
||||||
|
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) ));
|
||||||
|
if (announce.privateMatch) {
|
||||||
|
if (announce.isInvitedPerson) {
|
||||||
|
player.username = announce.playerTwoUsername;
|
||||||
|
}
|
||||||
|
privateMatchmaking(player);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
publicMatchmaking(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (msg.role === en.ClientRole.spectator)
|
||||||
|
{
|
||||||
|
const announce: ev.ClientAnnounceSpectator = <ev.ClientAnnounceSpectator>msg;
|
||||||
|
const gameSession = gameSessionsMap.get(announce.gameSessionId);
|
||||||
|
if (!gameSession) {
|
||||||
|
this.send(JSON.stringify( new ev.EventError("invalid gameSessionId") ));
|
||||||
|
clientTerminate(clientsMap.get(this.id));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const spectator = clientsMap.get(this.id) as ClientSpectator;
|
||||||
|
spectator.gameSession = gameSession;
|
||||||
|
gameSession.spectatorsMap.set(spectator.id, spectator);
|
||||||
|
spectator.socket.once("message", spectatorReadyConfirmationListener);
|
||||||
|
this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Invalid ClientAnnounce");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("Invalid JSON (clientAnnounceListener)");
|
||||||
|
}
|
||||||
|
this.once("message", clientAnnounceListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function publicMatchmaking(player: ClientPlayer)
|
||||||
|
{
|
||||||
|
const minPlayersNumber = 2;
|
||||||
|
const maxPlayersNumber = 2;
|
||||||
|
matchmakingMap.set(player.id, player);
|
||||||
|
const matchOptions = player.matchOptions;
|
||||||
|
|
||||||
|
const compatiblePlayers: ClientPlayer[] = [];
|
||||||
|
for (const [id, client] of matchmakingMap)
|
||||||
|
{
|
||||||
|
if (client.matchOptions === matchOptions)
|
||||||
|
{
|
||||||
|
compatiblePlayers.push(client);
|
||||||
|
if (compatiblePlayers.length === maxPlayersNumber) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compatiblePlayers.length >= minPlayersNumber) {
|
||||||
|
compatiblePlayers.forEach((client) => {
|
||||||
|
matchmakingMap.delete(client.id);
|
||||||
|
});
|
||||||
|
createGameSession(compatiblePlayers, matchOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function privateMatchmaking(player: ClientPlayer)
|
||||||
|
{
|
||||||
|
const minPlayersNumber = 2;
|
||||||
|
const maxPlayersNumber = 2;
|
||||||
|
privateMatchmakingMap.set(player.id, player);
|
||||||
|
const matchOptions = player.matchOptions;
|
||||||
|
|
||||||
|
const token = player.token;
|
||||||
|
const compatiblePlayers: ClientPlayer[] = [];
|
||||||
|
for (const [id, client] of privateMatchmakingMap)
|
||||||
|
{
|
||||||
|
if (client.token === token)
|
||||||
|
{
|
||||||
|
compatiblePlayers.push(client);
|
||||||
|
if (compatiblePlayers.length === maxPlayersNumber) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (compatiblePlayers.length >= minPlayersNumber) {
|
||||||
|
compatiblePlayers.forEach((client) => {
|
||||||
|
privateMatchmakingMap.delete(client.id);
|
||||||
|
});
|
||||||
|
createGameSession(compatiblePlayers, matchOptions);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
setTimeout(async function abortMatch() {
|
||||||
|
if (!player.gameSession)
|
||||||
|
{
|
||||||
|
if (player.socket.OPEN) {
|
||||||
|
player.socket.send(JSON.stringify( new ev.EventMatchAbort() ));
|
||||||
|
}
|
||||||
|
const response = await fetch(c.addressBackEnd + "/game/gameserver/destroysession",{
|
||||||
|
method: "POST",
|
||||||
|
headers : {"Content-Type": "application/json"},
|
||||||
|
body : JSON.stringify({
|
||||||
|
token : player.token
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.then(x => x.json())
|
||||||
|
.catch(error => console.log("ERROR : " + error));
|
||||||
|
clientTerminate(player);
|
||||||
|
}
|
||||||
|
}, 60000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function createGameSession(playersArr: ClientPlayer[], matchOptions: en.MatchOptions)
|
||||||
|
{
|
||||||
|
// const id = c.gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR
|
||||||
|
const id = uuidv4();
|
||||||
|
const gameSession = new GameSession(id, matchOptions);
|
||||||
|
gameSessionsMap.set(id, gameSession);
|
||||||
|
|
||||||
|
playersArr.forEach((client) => {
|
||||||
|
client.gameSession = gameSession;
|
||||||
|
gameSession.playersMap.set(client.id, client);
|
||||||
|
gameSession.unreadyPlayersMap.set(client.id, client);
|
||||||
|
client.socket.once("message", playerReadyConfirmationListener);
|
||||||
|
});
|
||||||
|
|
||||||
|
// REFACTORING: Not pretty, hardcoded two players.
|
||||||
|
// Could be done in gameSession maybe ?
|
||||||
|
const gameSessionPlayersIterator = gameSession.playersMap.values();
|
||||||
|
let player: ClientPlayer;
|
||||||
|
player = (<ClientPlayer>gameSessionPlayersIterator.next().value);
|
||||||
|
player.racket = gameSession.components.playerLeft;
|
||||||
|
player.socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) ));
|
||||||
|
|
||||||
|
player = (<ClientPlayer>gameSessionPlayersIterator.next().value);
|
||||||
|
player.racket = gameSession.components.playerRight;
|
||||||
|
player.socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) ));
|
||||||
|
// REFACTORING
|
||||||
|
|
||||||
|
setTimeout(function abortMatch() {
|
||||||
|
if (gameSession.unreadyPlayersMap.size !== 0)
|
||||||
|
{
|
||||||
|
gameSessionsMap.delete(gameSession.id);
|
||||||
|
gameSession.playersMap.forEach((client) => {
|
||||||
|
client.socket.send(JSON.stringify( new ev.EventMatchAbort() ));
|
||||||
|
client.gameSession = null;
|
||||||
|
clientTerminate(client);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 5000);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function playerReadyConfirmationListener(this: WebSocket, data: string)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const msg : ev.ClientEvent = JSON.parse(data);
|
||||||
|
if (msg.type === en.EventTypes.clientPlayerReady)
|
||||||
|
{
|
||||||
|
const client = clientsMap.get(this.id);
|
||||||
|
const gameSession = client.gameSession;
|
||||||
|
gameSession.unreadyPlayersMap.delete(this.id);
|
||||||
|
if (gameSession.unreadyPlayersMap.size === 0)
|
||||||
|
{
|
||||||
|
const gameSessionPlayersIterator = gameSession.playersMap.values();
|
||||||
|
const body = {
|
||||||
|
gameServerIdOfTheMatch : gameSession.id,
|
||||||
|
gameOptions: gameSession.matchOptions,
|
||||||
|
playerOneUsername: (<ClientPlayer>gameSessionPlayersIterator.next().value).username,
|
||||||
|
playerTwoUsername: (<ClientPlayer>gameSessionPlayersIterator.next().value).username,
|
||||||
|
playerOneUsernameResult : 0,
|
||||||
|
playerTwoUsernameResult : 0
|
||||||
|
};
|
||||||
|
const response = await fetch(c.addressBackEnd + "/game/gameserver/creategame",
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body)
|
||||||
|
});
|
||||||
|
if (!response.ok)
|
||||||
|
{
|
||||||
|
gameSessionsMap.delete(gameSession.id);
|
||||||
|
gameSession.playersMap.forEach((client) => {
|
||||||
|
client.socket.send(JSON.stringify( new ev.EventMatchAbort() ));
|
||||||
|
client.gameSession = null;
|
||||||
|
clientTerminate(client);
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gameSession.playersMap.forEach( (client) => {
|
||||||
|
client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
||||||
|
});
|
||||||
|
gameSession.start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Invalid playerReadyConfirmation");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("Invalid JSON (playerReadyConfirmationListener)");
|
||||||
|
}
|
||||||
|
this.once("message", playerReadyConfirmationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export function clientInputListener(this: WebSocket, data: string)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// const input: ev.ClientEvent = JSON.parse(data);
|
||||||
|
const input: ev.EventInput = JSON.parse(data);
|
||||||
|
if (input.type === en.EventTypes.clientInput)
|
||||||
|
{
|
||||||
|
const client = clientsMap.get(this.id) as ClientPlayer;
|
||||||
|
client.inputBuffer = input;
|
||||||
|
client.gameSession.instantInputDebug(client); // wip
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Invalid clientInput");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("Invalid JSON (clientInputListener)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function spectatorReadyConfirmationListener(this: WebSocket, data: string)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
const msg : ev.ClientEvent = JSON.parse(data);
|
||||||
|
if (msg.type === en.EventTypes.clientSpectatorReady)
|
||||||
|
{
|
||||||
|
const client = clientsMap.get(this.id);
|
||||||
|
const gameSession = client.gameSession;
|
||||||
|
const scoreUpdate = new ev.EventScoreUpdate(gameSession.components.scoreLeft, gameSession.components.scoreRight);
|
||||||
|
this.send(JSON.stringify(scoreUpdate));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log("Invalid spectatorReadyConfirmation");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
console.log("Invalid JSON (spectatorReadyConfirmationListener)");
|
||||||
|
}
|
||||||
|
this.once("message", spectatorReadyConfirmationListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
////////////
|
||||||
|
////////////
|
||||||
|
|
||||||
|
const pingInterval = setInterval( () => {
|
||||||
|
let deleteLog = "";
|
||||||
|
clientsMap.forEach( (client) => {
|
||||||
|
if (!client.isAlive) {
|
||||||
|
clientTerminate(client);
|
||||||
|
deleteLog += ` ${shortId(client.id)} |`;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
client.isAlive = false;
|
||||||
|
client.socket.ping();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deleteLog) {
|
||||||
|
console.log(`Disconnected:${deleteLog}`);
|
||||||
|
}
|
||||||
|
console.log("gameSessionMap size: " + gameSessionsMap.size);
|
||||||
|
console.log("clientsMap size: " + clientsMap.size);
|
||||||
|
console.log("matchmakingMap size: " + matchmakingMap.size);
|
||||||
|
console.log("privateMatchmakingMap size: " + privateMatchmakingMap.size);
|
||||||
|
console.log("");
|
||||||
|
}, 4200);
|
||||||
|
|
||||||
|
|
||||||
|
export function clientTerminate(client: Client)
|
||||||
|
{
|
||||||
|
client.socket.terminate();
|
||||||
|
if (client.gameSession)
|
||||||
|
{
|
||||||
|
client.gameSession.playersMap.delete(client.id);
|
||||||
|
client.gameSession.spectatorsMap.delete(client.id);
|
||||||
|
if (client.gameSession.playersMap.size === 0)
|
||||||
|
{
|
||||||
|
client.gameSession.destroy();
|
||||||
|
gameSessionsMap.delete(client.gameSession.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clientsMap.delete(client.id);
|
||||||
|
if (matchmakingMap.has(client.id)) {
|
||||||
|
matchmakingMap.delete(client.id);
|
||||||
|
}
|
||||||
|
else if (privateMatchmakingMap.has(client.id)) {
|
||||||
|
privateMatchmakingMap.delete(client.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function serverCloseListener()
|
||||||
|
{
|
||||||
|
clearInterval(pingInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function serverErrorListener(error: Error)
|
||||||
|
{
|
||||||
|
console.log("Error: " + JSON.stringify(error));
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
|
||||||
|
import * as en from "../enums.js"
|
||||||
|
|
||||||
|
/* From Server */
|
||||||
|
export class ServerEvent {
|
||||||
|
type: en.EventTypes;
|
||||||
|
constructor(type: en.EventTypes = 0) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventAssignId extends ServerEvent {
|
||||||
|
id: string;
|
||||||
|
constructor(id: string) {
|
||||||
|
super(en.EventTypes.assignId);
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventMatchmakingComplete extends ServerEvent {
|
||||||
|
side: en.PlayerSide;
|
||||||
|
constructor(side: en.PlayerSide) {
|
||||||
|
super(en.EventTypes.matchmakingComplete);
|
||||||
|
this.side = side;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventGameUpdate extends ServerEvent {
|
||||||
|
playerLeft = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
playerRight = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
ballsArr: {
|
||||||
|
x: number,
|
||||||
|
y: number,
|
||||||
|
dirX: number,
|
||||||
|
dirY: number,
|
||||||
|
speed: number
|
||||||
|
}[] = [];
|
||||||
|
wallTop? = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
wallBottom? = {
|
||||||
|
y: 0
|
||||||
|
};
|
||||||
|
lastInputId = 0;
|
||||||
|
constructor() { // TODO: constructor that take GameComponentsServer maybe ?
|
||||||
|
super(en.EventTypes.gameUpdate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventScoreUpdate extends ServerEvent {
|
||||||
|
scoreLeft: number;
|
||||||
|
scoreRight: number;
|
||||||
|
constructor(scoreLeft: number, scoreRight: number) {
|
||||||
|
super(en.EventTypes.scoreUpdate);
|
||||||
|
this.scoreLeft = scoreLeft;
|
||||||
|
this.scoreRight = scoreRight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventMatchEnd extends ServerEvent {
|
||||||
|
winner: en.PlayerSide;
|
||||||
|
forfeit: boolean;
|
||||||
|
constructor(winner: en.PlayerSide, forfeit = false) {
|
||||||
|
super(en.EventTypes.matchEnd);
|
||||||
|
this.winner = winner;
|
||||||
|
this.forfeit = forfeit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventMatchAbort extends ServerEvent {
|
||||||
|
constructor() {
|
||||||
|
super(en.EventTypes.matchAbort);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventError extends ServerEvent {
|
||||||
|
message: string;
|
||||||
|
constructor(message: string) {
|
||||||
|
super(en.EventTypes.error);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* From Client */
|
||||||
|
export class ClientEvent {
|
||||||
|
type: en.EventTypes; // readonly ?
|
||||||
|
constructor(type: en.EventTypes = 0) {
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClientAnnounce extends ClientEvent {
|
||||||
|
role: en.ClientRole;
|
||||||
|
constructor(role: en.ClientRole) {
|
||||||
|
super(en.EventTypes.clientAnnounce);
|
||||||
|
this.role = role;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClientAnnouncePlayer extends ClientAnnounce {
|
||||||
|
clientId: string; // unused
|
||||||
|
matchOptions: en.MatchOptions;
|
||||||
|
token: string;
|
||||||
|
username: string;
|
||||||
|
privateMatch: boolean;
|
||||||
|
playerTwoUsername?: string;
|
||||||
|
isInvitedPerson? : boolean;
|
||||||
|
constructor(matchOptions: en.MatchOptions, token: string, username: string, privateMatch: boolean = false, playerTwoUsername?: string, isInvitedPerson? : boolean) {
|
||||||
|
super(en.ClientRole.player);
|
||||||
|
this.matchOptions = matchOptions;
|
||||||
|
this.token = token;
|
||||||
|
this.username = username;
|
||||||
|
this.privateMatch = privateMatch;
|
||||||
|
if (isInvitedPerson) {
|
||||||
|
this.isInvitedPerson = isInvitedPerson;
|
||||||
|
}
|
||||||
|
if (playerTwoUsername) {
|
||||||
|
this.playerTwoUsername = playerTwoUsername;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ClientAnnounceSpectator extends ClientAnnounce {
|
||||||
|
gameSessionId: string;
|
||||||
|
constructor(gameSessionId: string) {
|
||||||
|
super(en.ClientRole.spectator);
|
||||||
|
this.gameSessionId = gameSessionId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class EventInput extends ClientEvent {
|
||||||
|
input: en.InputEnum;
|
||||||
|
id: number;
|
||||||
|
constructor(input: en.InputEnum = en.InputEnum.noInput, id: number = 0) {
|
||||||
|
super(en.EventTypes.clientInput);
|
||||||
|
this.input = input;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@ import { VectorInteger } from "./Vector.js";
|
|||||||
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
|
import { Rectangle, MovingRectangle, Racket, Ball } from "./Rectangle.js";
|
||||||
import { random } from "../utils.js";
|
import { random } from "../utils.js";
|
||||||
|
|
||||||
class GameComponents {
|
export class GameComponents {
|
||||||
wallTop: Rectangle | MovingRectangle;
|
wallTop: Rectangle | MovingRectangle;
|
||||||
wallBottom: Rectangle | MovingRectangle;
|
wallBottom: Rectangle | MovingRectangle;
|
||||||
playerLeft: Racket;
|
playerLeft: Racket;
|
||||||
@@ -61,5 +61,3 @@ class GameComponents {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {GameComponents}
|
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import { Vector, VectorInteger } from "./Vector.js";
|
import { Vector, VectorInteger } from "./Vector.js";
|
||||||
import { Component, Moving } from "./interface.js";
|
import type { Component, Moving } from "./interface.js";
|
||||||
import * as c from "../constants.js"
|
import * as c from "../constants.js"
|
||||||
|
|
||||||
class Rectangle implements Component {
|
export class Rectangle implements Component {
|
||||||
pos: VectorInteger;
|
pos: VectorInteger;
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
@@ -33,7 +33,7 @@ class Rectangle implements Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MovingRectangle extends Rectangle implements Moving {
|
export class MovingRectangle extends Rectangle implements Moving {
|
||||||
dir: Vector = new Vector(0,0);
|
dir: Vector = new Vector(0,0);
|
||||||
speed: number;
|
speed: number;
|
||||||
readonly baseSpeed: number;
|
readonly baseSpeed: number;
|
||||||
@@ -61,7 +61,7 @@ class MovingRectangle extends Rectangle implements Moving {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Racket extends MovingRectangle {
|
export class Racket extends MovingRectangle {
|
||||||
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
constructor(pos: VectorInteger, width: number, height: number, baseSpeed: number) {
|
||||||
super(pos, width, height, baseSpeed);
|
super(pos, width, height, baseSpeed);
|
||||||
}
|
}
|
||||||
@@ -72,13 +72,22 @@ class Racket extends MovingRectangle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Ball extends MovingRectangle {
|
export class Ball extends MovingRectangle {
|
||||||
readonly speedIncrease: number;
|
readonly speedIncrease: number;
|
||||||
ballInPlay: boolean = false;
|
ballInPlay: boolean = false;
|
||||||
constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number) {
|
constructor(pos: VectorInteger, size: number, baseSpeed: number, speedIncrease: number) {
|
||||||
super(pos, size, size, baseSpeed);
|
super(pos, size, size, baseSpeed);
|
||||||
this.speedIncrease = speedIncrease;
|
this.speedIncrease = speedIncrease;
|
||||||
}
|
}
|
||||||
|
moveAndBounce(delta: number, colliderArr: Rectangle[]) {
|
||||||
|
this.move(delta);
|
||||||
|
let i = colliderArr.findIndex(this.collision, this);
|
||||||
|
if (i != -1)
|
||||||
|
{
|
||||||
|
this.bounce(colliderArr[i]);
|
||||||
|
this.move(delta);
|
||||||
|
}
|
||||||
|
}
|
||||||
bounce(collider?: Rectangle) {
|
bounce(collider?: Rectangle) {
|
||||||
this._bounceAlgo(collider);
|
this._bounceAlgo(collider);
|
||||||
}
|
}
|
||||||
@@ -92,15 +101,6 @@ class Ball extends MovingRectangle {
|
|||||||
this._bounceWall();
|
this._bounceWall();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
moveAndBounce(delta: number, colliderArr: Rectangle[]) {
|
|
||||||
this.move(delta);
|
|
||||||
let i = colliderArr.findIndex(this.collision, this);
|
|
||||||
if (i != -1)
|
|
||||||
{
|
|
||||||
this.bounce(colliderArr[i]);
|
|
||||||
this.move(delta);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
protected _bounceWall() { // Should be enough for Wall
|
protected _bounceWall() { // Should be enough for Wall
|
||||||
this.dir.y = this.dir.y * -1;
|
this.dir.y = this.dir.y * -1;
|
||||||
}
|
}
|
||||||
@@ -140,5 +140,3 @@ class Ball extends MovingRectangle {
|
|||||||
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
|
// console.log(`x: ${this.dir.x}, y: ${this.dir.y}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {Rectangle, MovingRectangle, Racket, Ball}
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
|
|
||||||
class Vector {
|
export class Vector {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
constructor(x: number = 0, y: number = 0) {
|
constructor(x: number = 0, y: number = 0) {
|
||||||
@@ -16,13 +16,13 @@ class Vector {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VectorInteger extends Vector {
|
export class VectorInteger extends Vector {
|
||||||
// PLACEHOLDER
|
// PLACEHOLDER
|
||||||
// VectorInteger with set/get dont work (No draw on the screen). Why ?
|
// VectorInteger with set/get dont work (No draw on the screen). Why ?
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
class VectorInteger {
|
export class VectorInteger {
|
||||||
// private _x: number = 0;
|
// private _x: number = 0;
|
||||||
// private _y: number = 0;
|
// private _y: number = 0;
|
||||||
// constructor(x: number = 0, y: number = 0) {
|
// constructor(x: number = 0, y: number = 0) {
|
||||||
@@ -45,5 +45,3 @@ class VectorInteger {
|
|||||||
// }
|
// }
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export {Vector, VectorInteger}
|
|
||||||
@@ -1,21 +1,19 @@
|
|||||||
|
|
||||||
import { Vector, VectorInteger } from "./Vector.js";
|
import type { Vector, VectorInteger } from "./Vector.js";
|
||||||
|
|
||||||
interface Component {
|
export interface Component {
|
||||||
pos: VectorInteger;
|
pos: VectorInteger;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GraphicComponent extends Component {
|
export interface GraphicComponent extends Component {
|
||||||
ctx: CanvasRenderingContext2D;
|
ctx: CanvasRenderingContext2D;
|
||||||
color: string;
|
color: string;
|
||||||
update: () => void;
|
update: () => void;
|
||||||
clear: (pos?: VectorInteger) => void;
|
clear: (pos?: VectorInteger) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Moving {
|
export interface Moving {
|
||||||
dir: Vector;
|
dir: Vector;
|
||||||
speed: number; // pixel per second
|
speed: number; // pixel per second
|
||||||
move(delta: number): void;
|
move(delta: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export {Component, GraphicComponent, Moving}
|
|
||||||
@@ -11,8 +11,8 @@ export const pw = Math.floor(w*0.017);
|
|||||||
export const ph = pw*6;
|
export const ph = pw*6;
|
||||||
export const ballSize = pw;
|
export const ballSize = pw;
|
||||||
export const wallSize = Math.floor(w*0.01);
|
export const wallSize = Math.floor(w*0.01);
|
||||||
export const racketSpeed = Math.floor(w*0.66); // pixel per second
|
export const racketSpeed = Math.floor(w*0.60); // pixel per second
|
||||||
export const ballSpeed = Math.floor(w*0.66); // pixel per second
|
export const ballSpeed = Math.floor(w*0.55); // pixel per second
|
||||||
export const ballSpeedIncrease = Math.floor(ballSpeed*0.05); // pixel per second
|
export const ballSpeedIncrease = Math.floor(ballSpeed*0.05); // pixel per second
|
||||||
|
|
||||||
export const normalizedSpeed = false; // for consistency in speed independent of direction
|
export const normalizedSpeed = false; // for consistency in speed independent of direction
|
||||||
@@ -24,3 +24,7 @@ export const newRoundDelay = 1500; // millisecond
|
|||||||
export const multiBallsCount = 3;
|
export const multiBallsCount = 3;
|
||||||
export const movingWallPosMax = Math.floor(w*0.12);
|
export const movingWallPosMax = Math.floor(w*0.12);
|
||||||
export const movingWallSpeed = Math.floor(w*0.08);
|
export const movingWallSpeed = Math.floor(w*0.08);
|
||||||
|
|
||||||
|
|
||||||
|
export const gameSessionIdPLACEHOLDER = "match-id-test-42"; // TESTING SPECTATOR PLACEHOLDER
|
||||||
|
// for testing, force gameSession.id in wsServer.ts->createGameSession()
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
|
|
||||||
enum EventTypes {
|
export enum EventTypes {
|
||||||
// Class Implemented
|
// Class Implemented
|
||||||
gameUpdate = 1,
|
gameUpdate = 1,
|
||||||
scoreUpdate,
|
scoreUpdate,
|
||||||
matchEnd,
|
matchEnd,
|
||||||
assignId,
|
assignId,
|
||||||
matchmakingComplete,
|
matchmakingComplete,
|
||||||
|
error,
|
||||||
|
|
||||||
// Generic
|
// Generic
|
||||||
matchmakingInProgress,
|
matchmakingInProgress,
|
||||||
matchStart,
|
matchStart,
|
||||||
|
matchAbort,
|
||||||
matchNewRound, // unused
|
matchNewRound, // unused
|
||||||
matchPause, // unused
|
matchPause, // unused
|
||||||
matchResume, // unused
|
matchResume, // unused
|
||||||
@@ -17,31 +19,30 @@ enum EventTypes {
|
|||||||
// Client
|
// Client
|
||||||
clientAnnounce,
|
clientAnnounce,
|
||||||
clientPlayerReady,
|
clientPlayerReady,
|
||||||
|
clientSpectatorReady,
|
||||||
clientInput,
|
clientInput,
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enum InputEnum {
|
export enum InputEnum {
|
||||||
noInput = 0,
|
noInput = 0,
|
||||||
up = 1,
|
up = 1,
|
||||||
down,
|
down,
|
||||||
}
|
}
|
||||||
|
|
||||||
enum PlayerSide {
|
export enum PlayerSide {
|
||||||
left = 1,
|
left = 1,
|
||||||
right
|
right
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ClientRole {
|
export enum ClientRole {
|
||||||
player = 1,
|
player = 1,
|
||||||
spectator
|
spectator
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MatchOptions {
|
export enum MatchOptions {
|
||||||
// binary flags, can be mixed
|
// binary flags, can be mixed
|
||||||
noOption = 0b0,
|
noOption = 0b0,
|
||||||
multiBalls = 1 << 0,
|
multiBalls = 1 << 0,
|
||||||
movingWalls = 1 << 1
|
movingWalls = 1 << 1
|
||||||
}
|
}
|
||||||
|
|
||||||
export {EventTypes, InputEnum, PlayerSide, ClientRole, MatchOptions}
|
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
import type { MovingRectangle } from "./class/Rectangle.js";
|
||||||
|
|
||||||
|
export function random(min: number = 0, max: number = 1) {
|
||||||
|
return Math.random() * (max - min) + min;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function sleep (ms: number) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clamp(n: number, min: number, max: number) : number
|
||||||
|
{
|
||||||
|
if (n < min)
|
||||||
|
n = min;
|
||||||
|
else if (n > max)
|
||||||
|
n = max;
|
||||||
|
return (n);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Typescript hack, unused
|
||||||
|
export function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
|
||||||
|
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
|
||||||
|
return;
|
||||||
|
}
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
import * as c from "./constants.js";
|
import * as c from "./constants.js";
|
||||||
import { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
import type { MovingRectangle } from "../shared_js/class/Rectangle.js";
|
||||||
import { GameComponents } from "./class/GameComponents.js";
|
import type { GameComponents } from "./class/GameComponents.js";
|
||||||
|
|
||||||
function wallsMovements(delta: number, gc: GameComponents)
|
export function wallsMovements(delta: number, gc: GameComponents)
|
||||||
{
|
{
|
||||||
const wallTop = <MovingRectangle>gc.wallTop;
|
const wallTop = <MovingRectangle>gc.wallTop;
|
||||||
const wallBottom = <MovingRectangle>gc.wallBottom;
|
const wallBottom = <MovingRectangle>gc.wallBottom;
|
||||||
@@ -16,5 +16,3 @@ function wallsMovements(delta: number, gc: GameComponents)
|
|||||||
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
wallTop.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
||||||
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
wallBottom.moveAndCollide(delta, [gc.playerLeft, gc.playerRight]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {wallsMovements}
|
|
||||||
103
srcs/requirements/game_server/game_back/tsconfig.json
Normal file
103
srcs/requirements/game_server/game_back/tsconfig.json
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "ES6", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
"moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
// "outDir": "./", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
"strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,8 @@ FROM node:alpine AS development
|
|||||||
WORKDIR /usr/app
|
WORKDIR /usr/app
|
||||||
|
|
||||||
COPY ./api_back ./
|
COPY ./api_back ./
|
||||||
COPY ./api_back/.env ./.env
|
|
||||||
COPY ./api_back/src/uploads/avatars/default.png ./uploads/avatars/default.png
|
COPY ./api_back/src/uploads/avatars/default.png ./uploads/avatars/default.png
|
||||||
|
|
||||||
RUN npm ci
|
RUN npm i
|
||||||
|
|
||||||
|
|
||||||
CMD [ "npm", "run", "start:dev" ]
|
CMD [ "npm", "run", "start:dev" ]
|
||||||
|
|||||||
2209
srcs/requirements/nestjs/api_back/package-lock.json
generated
2209
srcs/requirements/nestjs/api_back/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -24,13 +24,13 @@
|
|||||||
"@nestjs/common": "^9.0.0",
|
"@nestjs/common": "^9.0.0",
|
||||||
"@nestjs/config": "^2.2.0",
|
"@nestjs/config": "^2.2.0",
|
||||||
"@nestjs/core": "^9.0.0",
|
"@nestjs/core": "^9.0.0",
|
||||||
"@nestjs/jwt": "^9.0.0",
|
|
||||||
"@nestjs/mapped-types": "^1.2.0",
|
"@nestjs/mapped-types": "^1.2.0",
|
||||||
"@nestjs/passport": "^9.0.0",
|
"@nestjs/passport": "^9.0.0",
|
||||||
"@nestjs/platform-express": "^9.0.0",
|
"@nestjs/platform-express": "^9.0.0",
|
||||||
|
"@nestjs/platform-socket.io": "^9.2.1",
|
||||||
"@nestjs/typeorm": "^9.0.1",
|
"@nestjs/typeorm": "^9.0.1",
|
||||||
|
"@nestjs/websockets": "^9.2.1",
|
||||||
"@types/express-session": "^1.17.5",
|
"@types/express-session": "^1.17.5",
|
||||||
"@types/redis": "^4.0.11",
|
|
||||||
"@types/validator": "^13.7.9",
|
"@types/validator": "^13.7.9",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"passport": "^0.6.0",
|
"passport": "^0.6.0",
|
||||||
"passport-42": "^1.2.6",
|
"passport-42": "^1.2.6",
|
||||||
"passport-jwt": "^4.0.0",
|
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pg": "^8.8.0",
|
"pg": "^8.8.0",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
@@ -49,6 +48,7 @@
|
|||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
|
"socket.io": "^4.5.4",
|
||||||
"typeorm": "^0.3.10",
|
"typeorm": "^0.3.10",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
@@ -59,8 +59,7 @@
|
|||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/jest": "28.1.8",
|
"@types/jest": "28.1.8",
|
||||||
"@types/multer": "^1.4.7",
|
"@types/multer": "^1.4.7",
|
||||||
"@types/node": "^16.0.0",
|
"@types/node": "^16.18.11",
|
||||||
"@types/passport-jwt": "^3.0.7",
|
|
||||||
"@types/passport-local": "^1.0.34",
|
"@types/passport-local": "^1.0.34",
|
||||||
"@types/supertest": "^2.0.11",
|
"@types/supertest": "^2.0.11",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
"@typescript-eslint/eslint-plugin": "^5.0.0",
|
||||||
|
|||||||
@@ -7,29 +7,35 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
import { FriendshipsModule } from './friendship/friendships.module';
|
import { FriendshipsModule } from './friendship/friendships.module';
|
||||||
import { AuthenticationModule } from './auth/42/authentication.module';
|
import { AuthenticationModule } from './auth/42/authentication.module';
|
||||||
import { PassportModule } from '@nestjs/passport';
|
import { PassportModule } from '@nestjs/passport';
|
||||||
// import { GameModule } from './game/game/game.module';
|
import { GameModule } from './game/game.module';
|
||||||
|
import { ChatGateway } from './chat/chat.gateway';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [UsersModule,
|
imports: [
|
||||||
AuthenticationModule,
|
UsersModule,
|
||||||
PassportModule.register({ session: true }),
|
AuthenticationModule,
|
||||||
FriendshipsModule,
|
PassportModule.register({ session: true }),
|
||||||
ConfigModule.forRoot(),
|
FriendshipsModule,
|
||||||
TypeOrmModule.forRoot({
|
GameModule,
|
||||||
type: 'postgres',
|
ConfigModule.forRoot(),
|
||||||
host: process.env.POSTGRES_HOST,
|
TypeOrmModule.forRoot({
|
||||||
port: parseInt(process.env.POSTGRES_PORT),
|
type: 'postgres',
|
||||||
username: process.env.POSTGRES_USER,
|
host: process.env.POSTGRES_HOST,
|
||||||
password: process.env.POSTGRES_PASSWORD,
|
port: parseInt(process.env.POSTGRES_PORT),
|
||||||
database: process.env.POSTGRES_DATABASE,
|
username: process.env.POSTGRES_USER,
|
||||||
autoLoadEntities: true,
|
password: process.env.POSTGRES_PASSWORD,
|
||||||
//ne pas synchroniser quand on est en prod. Trouver un moyen de set ça, sûrement
|
database: process.env.POSTGRES_DATABASE,
|
||||||
//avec une classe pour le module
|
autoLoadEntities: true,
|
||||||
synchronize: true,
|
//ne pas synchroniser quand on est en prod. Trouver un moyen de set ça, sûrement
|
||||||
}),
|
//avec une classe pour le module
|
||||||
// GameModule,
|
synchronize: true,
|
||||||
],
|
}),
|
||||||
|
// GameModule,
|
||||||
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [AppService],
|
providers: [
|
||||||
|
AppService,
|
||||||
|
ChatGateway,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export class AppModule {}
|
export class AppModule {}
|
||||||
|
|||||||
@@ -37,10 +37,10 @@ export class AuthenticationController {
|
|||||||
const user : User = request.user
|
const user : User = request.user
|
||||||
if (user.isEnabledTwoFactorAuth === false || user.isTwoFactorAuthenticated === true){
|
if (user.isEnabledTwoFactorAuth === false || user.isTwoFactorAuthenticated === true){
|
||||||
console.log('ON VA VERS PROFILE');
|
console.log('ON VA VERS PROFILE');
|
||||||
return response.status(200).redirect('http://transcendance:8080/#/profile');
|
return response.status(200).redirect('http://' + process.env.WEBSITE_HOST + ':' + process.env.WEBSITE_PORT + '/#/profile');
|
||||||
}
|
}
|
||||||
console.log('ON VA VERS 2FA')
|
console.log('ON VA VERS 2FA')
|
||||||
return response.status(200).redirect('http://transcendance:8080/#/2fa');
|
return response.status(200).redirect('http://' + process.env.WEBSITE_HOST + ':' + process.env.WEBSITE_PORT + '/#/2fa');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -83,6 +83,6 @@ export class AuthenticationController {
|
|||||||
throw new UnauthorizedException('Wrong Code.');
|
throw new UnauthorizedException('Wrong Code.');
|
||||||
await this.userService.authenticateUserWith2FA(request.user.id);
|
await this.userService.authenticateUserWith2FA(request.user.id);
|
||||||
console.log('ON REDIRIGE');
|
console.log('ON REDIRIGE');
|
||||||
return response.status(200).redirect('http://transcendance:8080/#/profile');
|
return response.status(200).redirect('http://' + process.env.WEBSITE_HOST + ':' + process.env.WEBSITE_PORT + '/#/profile');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ import { CreateUsersDto } from "src/users/dto/create-users.dto";
|
|||||||
async validate(accessToken: string, refreshToken: string, profile: Profile, callbackURL: string) {
|
async validate(accessToken: string, refreshToken: string, profile: Profile, callbackURL: string) {
|
||||||
console.log("Validate inside strategy.ts");
|
console.log("Validate inside strategy.ts");
|
||||||
console.log(profile.id, profile.username, profile.phoneNumbers[0].value, profile.emails[0].value, profile.photos[0].value);
|
console.log(profile.id, profile.username, profile.phoneNumbers[0].value, profile.emails[0].value, profile.photos[0].value);
|
||||||
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: 'default.png', isEnabledTwoFactorAuth: false , status: "connected" };
|
const userDTO: CreateUsersDto = { fortyTwoId: profile.id, username: profile.username, email: profile.emails[0].value, image_url: 'default.png', isEnabledTwoFactorAuth: false , status: "Connected" };
|
||||||
const user = await this.authenticationService.validateUser(userDTO);
|
const user = await this.authenticationService.validateUser(userDTO);
|
||||||
if (!user)
|
if (!user)
|
||||||
throw new UnauthorizedException();
|
throw new UnauthorizedException();
|
||||||
|
|||||||
44
srcs/requirements/nestjs/api_back/src/chat/chat.gateway.ts
Normal file
44
srcs/requirements/nestjs/api_back/src/chat/chat.gateway.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import {
|
||||||
|
WebSocketGateway,
|
||||||
|
SubscribeMessage,
|
||||||
|
WebSocketServer,
|
||||||
|
MessageBody,
|
||||||
|
OnGatewayConnection,
|
||||||
|
OnGatewayDisconnect,
|
||||||
|
} from '@nestjs/websockets';
|
||||||
|
import { UsersService } from 'src/users/users.service';
|
||||||
|
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
||||||
|
|
||||||
|
@WebSocketGateway(5000, {
|
||||||
|
path: '/chat',
|
||||||
|
})
|
||||||
|
|
||||||
|
export class ChatGateway
|
||||||
|
implements OnGatewayConnection, OnGatewayDisconnect
|
||||||
|
{
|
||||||
|
constructor
|
||||||
|
(
|
||||||
|
private usersService: UsersService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@WebSocketServer()
|
||||||
|
server;
|
||||||
|
|
||||||
|
// how to guard the handleConnection ?
|
||||||
|
// https://github.com/nestjs/nest/issues/882
|
||||||
|
async handleConnection(client) {
|
||||||
|
// const paginationQuery = new PaginationQueryDto();
|
||||||
|
// const users = await this.usersService.findAll(paginationQuery);
|
||||||
|
|
||||||
|
// const users = await this.usersService.findAll(client);
|
||||||
|
// const users = await this.usersService.findAll(client);
|
||||||
|
console.log('---- Client connected :', client.id);
|
||||||
|
// console.log('users :', users);
|
||||||
|
}
|
||||||
|
handleDisconnect(client) {
|
||||||
|
console.log('---- client disconnected :', client.id);
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
|
||||||
@@ -8,6 +8,12 @@ const MIME_TYPES = {
|
|||||||
'image/png': 'png'
|
'image/png': 'png'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export enum STATUS {
|
||||||
|
CONNECTED = 'Connected',
|
||||||
|
DISCONNECTED = 'Disconnected',
|
||||||
|
IN_GAME = 'In Game',
|
||||||
|
IN_POOL = 'In Pool',
|
||||||
|
}
|
||||||
|
|
||||||
export const storageForAvatar = {
|
export const storageForAvatar = {
|
||||||
storage: diskStorage({
|
storage: diskStorage({
|
||||||
@@ -18,5 +24,13 @@ export const storageForAvatar = {
|
|||||||
const extension : string = MIME_TYPES[file.mimetype];
|
const extension : string = MIME_TYPES[file.mimetype];
|
||||||
cb(null, `${filename}${extension}`);
|
cb(null, `${filename}${extension}`);
|
||||||
}
|
}
|
||||||
})
|
}),
|
||||||
|
fileFilter: (req, file, cb) => {
|
||||||
|
if (file.mimetype === 'image/png' || file.mimetype === 'image/jpg' || file.mimetype === 'image/jpeg') {
|
||||||
|
cb(null, true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
cb(null, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
import { IsBoolean, IsEmail, IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
|
||||||
export class CreateUsersDto {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
readonly username: string;
|
|
||||||
readonly fortyTwoId: string;
|
|
||||||
@IsEmail()
|
|
||||||
readonly email: string;
|
|
||||||
@IsString()
|
|
||||||
readonly image_url: string;
|
|
||||||
@IsString()
|
|
||||||
readonly status: string;
|
|
||||||
@IsBoolean()
|
|
||||||
readonly isEnabledTwoFactorAuth: boolean;
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class CreateGameDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
gameServerIdOfTheMatch : string
|
||||||
|
@IsNumber()
|
||||||
|
@IsNotEmpty()
|
||||||
|
gameOptions: number
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
playerOneUsername : string
|
||||||
|
@IsString()
|
||||||
|
playerTwoUsername : string
|
||||||
|
@IsNumber()
|
||||||
|
playerTwoUsernameResult : number
|
||||||
|
@IsNumber()
|
||||||
|
playerOneUsernameResult : number
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { IsBoolean, IsEmpty, IsInt, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||||
|
import { IsNull } from "typeorm";
|
||||||
|
|
||||||
|
export class GrantTicketDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
playerOneUsername : string
|
||||||
|
@IsString()
|
||||||
|
playerTwoUsername : string
|
||||||
|
@IsNumber()
|
||||||
|
gameOptions : number
|
||||||
|
@IsBoolean()
|
||||||
|
isGameIsWithInvitation : boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
import { OmitType } from "@nestjs/mapped-types";
|
||||||
|
import { IsBoolean, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||||
|
import { CreateGameDto } from "./createGame.dto";
|
||||||
|
|
||||||
|
export class UpdateGameDto extends OmitType(CreateGameDto, ['playerOneUsername', 'playerTwoUsername', 'gameOptions'] as const){}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { IsBase64, IsBoolean, IsEmpty, IsNotEmpty, IsNumber, IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class ValidateTicketDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
playerOneUsername : string
|
||||||
|
@IsString()
|
||||||
|
playerTwoUsername : string
|
||||||
|
@IsNumber()
|
||||||
|
gameOptions : number
|
||||||
|
@IsBoolean()
|
||||||
|
isGameIsWithInvitation : boolean
|
||||||
|
@IsBase64()
|
||||||
|
@IsNotEmpty()
|
||||||
|
token : string
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity('game')
|
||||||
|
export class Game {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
playerOneUsername: string
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
playerTwoUsername: string
|
||||||
|
|
||||||
|
@Column({default : 0, nullable : true})
|
||||||
|
playerOneUsernameResult : number
|
||||||
|
|
||||||
|
@Column({default : 0, nullable : true})
|
||||||
|
playerTwoUsernameResult : number
|
||||||
|
|
||||||
|
@Column({default : 0})
|
||||||
|
gameOptions: number
|
||||||
|
|
||||||
|
@Column({unique : true})
|
||||||
|
gameServerIdOfTheMatch: string
|
||||||
|
|
||||||
|
@Column({default: false, nullable : true}) //éric pourra trouver un meilleur mot : ongoing ?
|
||||||
|
isMatchIsFinished: boolean
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
||||||
|
|
||||||
|
@Entity('tokenGame')
|
||||||
|
export class TokenGame {
|
||||||
|
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
@Column()
|
||||||
|
playerOneUsername : string
|
||||||
|
@Column({nullable: true})
|
||||||
|
playerTwoUsername : string
|
||||||
|
@Column()
|
||||||
|
gameOptions : number
|
||||||
|
@Column()
|
||||||
|
isGameIsWithInvitation : boolean
|
||||||
|
@Column({default: 0, nullable: true})
|
||||||
|
numberOfRegisteredUser : number
|
||||||
|
@Column({default : false})
|
||||||
|
isSecondUserAcceptedRequest : boolean
|
||||||
|
@Column()
|
||||||
|
token : string
|
||||||
|
}
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Entity('gameParty')
|
|
||||||
export class gameParty {
|
|
||||||
|
|
||||||
@PrimaryGeneratedColumn()
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
playerOne: string
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
playerTwo: string
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
resultOfTheMatch: string
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
gameServerIdOfTheMatch: string
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,104 @@
|
|||||||
import { Controller } from '@nestjs/common';
|
import { Body, Controller, Get, HttpException, HttpStatus, Post, Req, Res, UseGuards } from '@nestjs/common';
|
||||||
|
import { AuthenticateGuard, TwoFactorGuard } from 'src/auth/42/guards/42guards';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { CreateGameDto } from './dto/createGame.dto';
|
||||||
|
import { GrantTicketDto } from './dto/grantTicket.dto';
|
||||||
|
import { UpdateGameDto } from './dto/updateGame.dto';
|
||||||
|
import { ValidateTicketDto } from './dto/validateTicket.dto';
|
||||||
|
import { GameService } from './game.service';
|
||||||
|
|
||||||
@Controller('game')
|
@Controller('game')
|
||||||
export class GameController {}
|
export class GameController {
|
||||||
|
constructor (private readonly gameService : GameService) { }
|
||||||
|
|
||||||
|
|
||||||
|
@Get('match/all')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
async getMatchesForSpectator()
|
||||||
|
{
|
||||||
|
return this.gameService.getMatchesForSpectator();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('ranking')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
async getRankingForAllUsers(@Req() req)
|
||||||
|
{
|
||||||
|
const currentUser : User = req.user
|
||||||
|
return this.gameService.getRankingForAllUsers(currentUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('ticket')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
async grantTicket(@Req() req, @Body() grantTicketDto : GrantTicketDto, @Res() res : Response)
|
||||||
|
{
|
||||||
|
const user : User = req.user
|
||||||
|
if (grantTicketDto.playerOneUsername != user.username)
|
||||||
|
return res.status(HttpStatus.BAD_REQUEST).json({message : 'You can\'t grant a ticket to another user'});
|
||||||
|
return this.gameService.generateToken(user, grantTicketDto, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('decline')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
async declineInvitation(@Body('token') token, @Req() req, @Res() res : Response)
|
||||||
|
{
|
||||||
|
const user : User = req.user;
|
||||||
|
return this.gameService.declineInvitation(user, token, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('accept')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
async acceptInvitation(@Body('token') token, @Req() req, @Res() res : Response)
|
||||||
|
{
|
||||||
|
const user : User = req.user;
|
||||||
|
return this.gameService.acceptInvitation(user, token, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Get('invitations')
|
||||||
|
@UseGuards(AuthenticateGuard)
|
||||||
|
@UseGuards(TwoFactorGuard)
|
||||||
|
async findInvitations(@Req() request, @Res() res : Response)
|
||||||
|
{
|
||||||
|
const user : User = request.user;
|
||||||
|
return this.gameService.findInvitations(user, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
//N'est valable que pour le game-serveur.
|
||||||
|
@Post('gameserver/validate')
|
||||||
|
async validateTicket(@Body() validateTicketDto : ValidateTicketDto, @Req() request)
|
||||||
|
{
|
||||||
|
if (await this.gameService.validateToken(validateTicketDto) === false)
|
||||||
|
return new HttpException("The token is not valid", HttpStatus.NOT_FOUND);
|
||||||
|
console.log("200 retourné côté nest")
|
||||||
|
return HttpStatus.OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('gameserver/creategame')
|
||||||
|
async createGame(@Body() creategameDto : CreateGameDto)
|
||||||
|
{
|
||||||
|
console.log("On est dans create game")
|
||||||
|
console.log(creategameDto)
|
||||||
|
return this.gameService.createGame(creategameDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('gameserver/updategame')
|
||||||
|
async updateGame(@Body() updateGameDto : UpdateGameDto)
|
||||||
|
{
|
||||||
|
console.log("On est dans update game")
|
||||||
|
console.log(updateGameDto)
|
||||||
|
return this.gameService.updateGame(updateGameDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('gameserver/destroysession')
|
||||||
|
async destroySession(@Body('token') token)
|
||||||
|
{
|
||||||
|
return this.gameService.destroySession(token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Friendship } from 'src/friendship/entities/friendship.entity';
|
||||||
|
import { FriendshipService } from 'src/friendship/friendship.service';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { UsersService } from 'src/users/users.service';
|
||||||
|
import { Game } from './entity/game.entity';
|
||||||
|
import { TokenGame } from './entity/tokenGame.entity';
|
||||||
import { GameController } from './game.controller';
|
import { GameController } from './game.controller';
|
||||||
import { GameService } from './game.service';
|
import { GameService } from './game.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [GameController],
|
imports: [TypeOrmModule.forFeature([TokenGame, User, Game, Friendship])],
|
||||||
providers: [GameService]
|
controllers: [GameController],
|
||||||
|
providers: [GameService, UsersService, FriendshipService]
|
||||||
})
|
})
|
||||||
export class GameModule {}
|
export class GameModule {}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
// import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { GameService } from './game.service';
|
// // import { GameService } from './game.service';
|
||||||
|
|
||||||
describe('GameService', () => {
|
// describe('GameService', () => {
|
||||||
let service: GameService;
|
// let service: GameService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
// beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
// const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [GameService],
|
// providers: [GameService],
|
||||||
}).compile();
|
// }).compile();
|
||||||
|
|
||||||
service = module.get<GameService>(GameService);
|
// service = module.get<GameService>(GameService);
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('should be defined', () => {
|
// it('should be defined', () => {
|
||||||
expect(service).toBeDefined();
|
// expect(service).toBeDefined();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|||||||
@@ -1,4 +1,299 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { HttpException, HttpStatus, Injectable, Res } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { createCipheriv, randomBytes, scrypt } from 'crypto';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { promisify } from 'util';
|
||||||
|
import { Response } from 'express';
|
||||||
|
import { GrantTicketDto } from './dto/grantTicket.dto';
|
||||||
|
import { Game } from './entity/game.entity';
|
||||||
|
import { ValidateTicketDto } from './dto/validateTicket.dto';
|
||||||
|
import { TokenGame } from './entity/tokenGame.entity';
|
||||||
|
import { UsersService } from 'src/users/users.service';
|
||||||
|
import { CreateGameDto } from './dto/createGame.dto';
|
||||||
|
import { UpdateGameDto } from './dto/updateGame.dto';
|
||||||
|
import { FriendshipService } from 'src/friendship/friendship.service';
|
||||||
|
import { STATUS } from 'src/common/constants/constants';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class GameService {}
|
export class GameService {
|
||||||
|
constructor (
|
||||||
|
@InjectRepository(Game)
|
||||||
|
private readonly gameRepository : Repository<Game>,
|
||||||
|
@InjectRepository(User)
|
||||||
|
private readonly userRepository : Repository<User>,
|
||||||
|
@InjectRepository(TokenGame)
|
||||||
|
private readonly tokenGameRepository : Repository<TokenGame>,
|
||||||
|
private readonly userService : UsersService,
|
||||||
|
private readonly friendShipService : FriendshipService
|
||||||
|
) { }
|
||||||
|
|
||||||
|
async getMatchesForSpectator() {
|
||||||
|
const games = await this.gameRepository.createQueryBuilder("game")
|
||||||
|
.where('game.isMatchIsFinished = :isMatchIsFinished', {isMatchIsFinished : false})
|
||||||
|
.getMany();
|
||||||
|
const gamesToReturn : Partial<Game>[] = []
|
||||||
|
for (const game of games)
|
||||||
|
{
|
||||||
|
gamesToReturn.push({gameServerIdOfTheMatch : game.gameServerIdOfTheMatch,
|
||||||
|
gameOptions : game.gameOptions, playerOneUsername : game.playerOneUsername,
|
||||||
|
playerTwoUsername : game.playerTwoUsername})
|
||||||
|
console.log("Is match is finished : " + game.isMatchIsFinished)
|
||||||
|
}
|
||||||
|
return gamesToReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRankingForAllUsers(currentUser : User) {
|
||||||
|
const users = await this.userRepository.createQueryBuilder("user")
|
||||||
|
.leftJoinAndSelect("user.stats", "stats")
|
||||||
|
.orderBy('stats.winGame', "DESC")
|
||||||
|
.getMany();
|
||||||
|
const partialUser : Partial<User>[] = []
|
||||||
|
for (const user of users)
|
||||||
|
{
|
||||||
|
if (await this.friendShipService.findIfUserIsBlockedOrHasBlocked(currentUser.id, user.id) === false)
|
||||||
|
partialUser.push({username : user.username, stats : user.stats })
|
||||||
|
}
|
||||||
|
console.log(...partialUser)
|
||||||
|
return partialUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async encryptToken(toEncrypt : string) : Promise<string> {
|
||||||
|
const iv = randomBytes(16);
|
||||||
|
const password = process.env.TICKET_FOR_PLAYING_GAME_SECRET + new Date();
|
||||||
|
const key = (await promisify(scrypt)(password, 'salt', 32)) as Buffer;
|
||||||
|
const cipher = createCipheriv('aes-256-ctr', key, iv);
|
||||||
|
const encryptedText = Buffer.concat([
|
||||||
|
cipher.update(toEncrypt),
|
||||||
|
cipher.final(),
|
||||||
|
]);
|
||||||
|
const encryptedTextToReturn = encryptedText.toString('base64');
|
||||||
|
return encryptedTextToReturn
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteToken(user : User){
|
||||||
|
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||||
|
.where('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||||
|
.orWhere('tokengame.playerOneUsername = :playerOneUsername', {playerOneUsername : user.username})
|
||||||
|
.getMany();
|
||||||
|
if (tokenGame)
|
||||||
|
return this.tokenGameRepository.remove(tokenGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateToken(user : User, grantTicketDto : GrantTicketDto, @Res() res : Response)
|
||||||
|
{
|
||||||
|
console.log(user.status);
|
||||||
|
if (user.status === STATUS.IN_POOL || user.status === STATUS.IN_GAME)
|
||||||
|
{
|
||||||
|
await this.deleteToken(user);
|
||||||
|
user.status = STATUS.CONNECTED;
|
||||||
|
this.userRepository.save(user);
|
||||||
|
}
|
||||||
|
if (grantTicketDto.isGameIsWithInvitation === true)
|
||||||
|
{
|
||||||
|
const secondUser : Partial<User> = await this.userService.findOne(grantTicketDto.playerTwoUsername)
|
||||||
|
if (!secondUser || secondUser.username === user.username)
|
||||||
|
return res.status(HttpStatus.NOT_FOUND).json({message : "User not found OR you want to play with yourself."});
|
||||||
|
const encryptedTextToReturn = await this.encryptToken(user.username + '_' + secondUser.username + '_'
|
||||||
|
+ grantTicketDto.gameOptions + '_' + grantTicketDto.isGameIsWithInvitation + '_' + new Date())
|
||||||
|
const tok = this.tokenGameRepository.create(grantTicketDto);
|
||||||
|
tok.isSecondUserAcceptedRequest = false;
|
||||||
|
tok.numberOfRegisteredUser = 0;
|
||||||
|
tok.token = encryptedTextToReturn;
|
||||||
|
this.tokenGameRepository.save(tok);
|
||||||
|
this.userService.updateStatus(user.id, "In Pool")
|
||||||
|
return res.status(HttpStatus.OK).json({ token : encryptedTextToReturn });
|
||||||
|
}
|
||||||
|
else if (grantTicketDto.isGameIsWithInvitation === false) {
|
||||||
|
const encryptedTextToReturn = await this.encryptToken(user.username + '_'
|
||||||
|
+ grantTicketDto.gameOptions + '_' + grantTicketDto.isGameIsWithInvitation + '_' + new Date())
|
||||||
|
const tok = this.tokenGameRepository.create(grantTicketDto);
|
||||||
|
tok.numberOfRegisteredUser = 0;
|
||||||
|
tok.token = encryptedTextToReturn;
|
||||||
|
this.tokenGameRepository.save(tok);
|
||||||
|
this.userService.updateStatus(user.id, "In Pool")
|
||||||
|
return res.status(HttpStatus.OK).json({ token : encryptedTextToReturn });
|
||||||
|
}
|
||||||
|
return res.status(HttpStatus.INTERNAL_SERVER_ERROR).json({message : "Internal Server Error"});
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateToken(validateTicketDto : ValidateTicketDto) {
|
||||||
|
if (validateTicketDto.isGameIsWithInvitation === true)
|
||||||
|
{
|
||||||
|
const tokenGame : TokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||||
|
.where('tokengame.playerOneUsername = :playerOneUsername', {playerOneUsername : validateTicketDto.playerOneUsername})
|
||||||
|
.andWhere('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : validateTicketDto.playerTwoUsername})
|
||||||
|
.andWhere('tokengame.gameOptions = :gameOption', {gameOption : validateTicketDto.gameOptions})
|
||||||
|
.andWhere('tokengame.isGameIsWithInvitation = :isGameIsWithInvitation', {isGameIsWithInvitation: true})
|
||||||
|
.andWhere('tokengame.isSecondUserAcceptedRequest = :choice', {choice : true})
|
||||||
|
.andWhere('tokengame.token = :token', {token : validateTicketDto.token})
|
||||||
|
.getOne();
|
||||||
|
if (tokenGame)
|
||||||
|
{
|
||||||
|
tokenGame.numberOfRegisteredUser++;
|
||||||
|
if (tokenGame.numberOfRegisteredUser === 2)
|
||||||
|
{
|
||||||
|
this.tokenGameRepository.remove(tokenGame)
|
||||||
|
const userOne : User = await this.userRepository.createQueryBuilder('user')
|
||||||
|
.where("user.username = :username", {username : tokenGame.playerOneUsername})
|
||||||
|
.getOne();
|
||||||
|
this.userService.updateStatus(userOne.id, "In Game")
|
||||||
|
const userTwo : User = await this.userRepository.createQueryBuilder('user')
|
||||||
|
.where("user.username = :username", {username : tokenGame.playerTwoUsername})
|
||||||
|
.getOne();
|
||||||
|
this.deleteToken(userOne)
|
||||||
|
this.deleteToken(userTwo)
|
||||||
|
this.userService.updateStatus(userTwo.id, "In Game")
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (validateTicketDto.isGameIsWithInvitation === false)
|
||||||
|
{
|
||||||
|
const tokenGame : TokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||||
|
.where('tokengame.playerOneUsername = :playerOneUsername', {playerOneUsername : validateTicketDto.playerOneUsername})
|
||||||
|
.andWhere('tokengame.gameOptions = :gameOption', {gameOption : validateTicketDto.gameOptions})
|
||||||
|
.andWhere('tokengame.isGameIsWithInvitation = :isGameIsWithInvitation', {isGameIsWithInvitation: false})
|
||||||
|
.andWhere('tokengame.token = :token', {token : validateTicketDto.token})
|
||||||
|
.getOne();
|
||||||
|
if (tokenGame)
|
||||||
|
{
|
||||||
|
this.tokenGameRepository.remove(tokenGame)
|
||||||
|
console.log("USERNAME : " + tokenGame.playerOneUsername)
|
||||||
|
const user : User = await this.userRepository.createQueryBuilder('user')
|
||||||
|
.where("user.username = :username", {username : tokenGame.playerOneUsername})
|
||||||
|
.getOne();
|
||||||
|
this.userService.updateStatus(user.id, "In Game")
|
||||||
|
this.deleteToken(user)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findInvitations(user : User, @Res() res : Response) {
|
||||||
|
const game = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||||
|
.where('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||||
|
.andWhere('tokengame.isGameIsWithInvitation = :invit', {invit : true})
|
||||||
|
.andWhere('tokengame.isSecondUserAcceptedRequest = :choice', {choice : false})
|
||||||
|
.getMany();
|
||||||
|
if (!game)
|
||||||
|
return res.status(HttpStatus.NOT_FOUND).send({message : "No invitation found"});
|
||||||
|
let partialGame : Partial<TokenGame>[] = [];
|
||||||
|
for (const gameToken of game) {
|
||||||
|
partialGame.push({
|
||||||
|
playerOneUsername : gameToken.playerOneUsername,
|
||||||
|
playerTwoUsername : gameToken.playerTwoUsername,
|
||||||
|
gameOptions : gameToken.gameOptions,
|
||||||
|
token : gameToken.token,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return res.status(HttpStatus.OK).json(partialGame);
|
||||||
|
}
|
||||||
|
|
||||||
|
async declineInvitation(user : User, token : string, @Res() res : Response)
|
||||||
|
{
|
||||||
|
if (user.status !== "Connected")
|
||||||
|
return res.status(HttpStatus.FORBIDDEN).json({message : "You must not be in game to decline an invitation"});
|
||||||
|
console.log("On décline l'invitation")
|
||||||
|
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||||
|
.andWhere('tokengame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||||
|
.andWhere('tokengame.token = :token', {token : token})
|
||||||
|
.getOne();
|
||||||
|
if (tokenGame)
|
||||||
|
{
|
||||||
|
this.tokenGameRepository.remove(tokenGame);
|
||||||
|
return res.status(HttpStatus.OK).json({message : "Invitation declined."});
|
||||||
|
}
|
||||||
|
return res.status(HttpStatus.NOT_FOUND).json({message : "No invitation found !"});
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroySession(token : string)
|
||||||
|
{
|
||||||
|
console.log("On détruit le token et la session qui va avec")
|
||||||
|
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokengame')
|
||||||
|
.where('tokengame.token = :token', {token : token})
|
||||||
|
.getOne();
|
||||||
|
if (tokenGame)
|
||||||
|
{
|
||||||
|
const playerOne = await this.userRepository.findOneBy({username : tokenGame.playerOneUsername})
|
||||||
|
const playerTwo = await this.userRepository.findOneBy({username : tokenGame.playerTwoUsername})
|
||||||
|
if (playerOne.status !== "Disconnected")
|
||||||
|
this.userService.updateStatus(playerOne.id, "Connected")
|
||||||
|
if (playerTwo.status !== "Disconnected")
|
||||||
|
this.userService.updateStatus(playerTwo.id, "Connected")
|
||||||
|
return this.tokenGameRepository.remove(tokenGame);
|
||||||
|
}
|
||||||
|
return new HttpException("Token not found !", HttpStatus.NOT_FOUND)
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptInvitation(user : User, token : string, @Res() res : Response)
|
||||||
|
{
|
||||||
|
if (user.status !== "Connected")
|
||||||
|
return res.status(HttpStatus.FORBIDDEN).send("")
|
||||||
|
const tokenGame = await this.tokenGameRepository.createQueryBuilder('tokenGame')
|
||||||
|
.andWhere('tokenGame.playerTwoUsername = :playerTwoUsername', {playerTwoUsername : user.username})
|
||||||
|
.andWhere('tokenGame.token = :token', {token : token})
|
||||||
|
.getOne();
|
||||||
|
if (tokenGame)
|
||||||
|
{
|
||||||
|
tokenGame.isSecondUserAcceptedRequest = true;
|
||||||
|
this.tokenGameRepository.save(tokenGame)
|
||||||
|
return res.status(HttpStatus.OK).json({message : "Invitation accepted."});
|
||||||
|
}
|
||||||
|
return res.status(HttpStatus.NOT_FOUND).json({message : "No invitation found !"});
|
||||||
|
}
|
||||||
|
|
||||||
|
async createGame(creategameDto : CreateGameDto)
|
||||||
|
{
|
||||||
|
if (creategameDto.playerOneUsername === "" || creategameDto.playerTwoUsername === ""
|
||||||
|
|| creategameDto.playerOneUsername === creategameDto.playerTwoUsername)
|
||||||
|
return HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
const game = this.gameRepository.create(creategameDto)
|
||||||
|
game.isMatchIsFinished = false;
|
||||||
|
this.gameRepository.save(game);
|
||||||
|
if (!game)
|
||||||
|
return HttpStatus.INTERNAL_SERVER_ERROR
|
||||||
|
console.log("200 retourné pour la création de partie")
|
||||||
|
return HttpStatus.OK
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateGame(updateGameDto : UpdateGameDto) {
|
||||||
|
console.log("Updata game" + updateGameDto)
|
||||||
|
const game = await this.gameRepository.createQueryBuilder('game')
|
||||||
|
.where("game.gameServerIdOfTheMatch = :gameServerIdOfTheMatch", {gameServerIdOfTheMatch : updateGameDto.gameServerIdOfTheMatch})
|
||||||
|
.getOne();
|
||||||
|
if (!game)
|
||||||
|
throw new HttpException(`The game could not be updated.`,HttpStatus.NOT_FOUND);
|
||||||
|
game.isMatchIsFinished = true;
|
||||||
|
game.playerOneUsernameResult = updateGameDto.playerOneUsernameResult
|
||||||
|
game.playerTwoUsernameResult = updateGameDto.playerTwoUsernameResult
|
||||||
|
this.gameRepository.save(game);
|
||||||
|
console.log("On a sauvegardé la partie. Game :")
|
||||||
|
console.log(game)
|
||||||
|
const playerOne = await this.userRepository.findOneBy({username : game.playerOneUsername})
|
||||||
|
const playerTwo = await this.userRepository.findOneBy({username : game.playerTwoUsername})
|
||||||
|
if (!playerOne || !playerTwo)
|
||||||
|
return new HttpException("Internal Server Error. Impossible to update the database", HttpStatus.INTERNAL_SERVER_ERROR);
|
||||||
|
if (game.playerOneUsernameResult === game.playerTwoUsernameResult)
|
||||||
|
{
|
||||||
|
this.userService.incrementDraws(playerOne.id)
|
||||||
|
this.userService.incrementDraws(playerTwo.id)
|
||||||
|
}
|
||||||
|
else if (game.playerOneUsernameResult < game.playerTwoUsernameResult)
|
||||||
|
{
|
||||||
|
this.userService.incrementDefeats(playerOne.id)
|
||||||
|
this.userService.incrementVictories(playerTwo.id)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
this.userService.incrementVictories(playerOne.id)
|
||||||
|
this.userService.incrementDefeats(playerTwo.id)
|
||||||
|
}
|
||||||
|
this.userService.updateStatus(playerOne.id, "Connected")
|
||||||
|
this.userService.updateStatus(playerTwo.id, "Connected")
|
||||||
|
return HttpStatus.OK
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import * as connectRedis from 'connect-redis';
|
|||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule, { cors: true });
|
const app = await NestFactory.create(AppModule, { cors: true });
|
||||||
const port = process.env.PORT || 3001;
|
const port = process.env.PORT || 3000;
|
||||||
const client = redis.createClient(
|
const client = redis.createClient(
|
||||||
{
|
{
|
||||||
socket: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT) },
|
socket: { host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT) },
|
||||||
@@ -50,6 +50,6 @@ async function bootstrap() {
|
|||||||
);
|
);
|
||||||
app.use(passport.initialize());
|
app.use(passport.initialize());
|
||||||
app.use(passport.session());
|
app.use(passport.session());
|
||||||
await app.listen(port, () => { console.log(`Listening on port ${port}`); });
|
await app.listen(port, () => { console.log(`Listening on port ${process.env.WEBSITE_HOST}:${process.env.WEBSITE_PORT}}`); });
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// et de les mettre comme optionnelles. De plus on peut hériter
|
// et de les mettre comme optionnelles. De plus on peut hériter
|
||||||
// des décorateurs de la classe parente (par exemple @IsString()).
|
// des décorateurs de la classe parente (par exemple @IsString()).
|
||||||
|
|
||||||
import { OmitType, PartialType } from "@nestjs/mapped-types";
|
import { OmitType } from "@nestjs/mapped-types";
|
||||||
import { CreateUsersDto } from "./create-users.dto";
|
import { CreateUsersDto } from "./create-users.dto";
|
||||||
|
|
||||||
export class UpdateUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email', 'image_url', 'status'] as const){}
|
export class UpdateUsersDto extends OmitType(CreateUsersDto, ['fortyTwoId', 'email', 'image_url', 'status'] as const){}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class User {
|
|||||||
@Column({ nullable: true })
|
@Column({ nullable: true })
|
||||||
phone: string;
|
phone: string;
|
||||||
|
|
||||||
@Column({ default: 'disconnected' })
|
@Column({ default: 'Disconnected' })
|
||||||
status: string;
|
status: string;
|
||||||
|
|
||||||
// @Column()
|
// @Column()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {
|
import {
|
||||||
Body, Controller, Delete, Get, NotFoundException, Param, Patch, Post, Query, Redirect, Req, Res, UploadedFile, UseGuards, UseInterceptors
|
Body, Controller, Delete, Get, NotFoundException,HttpStatus, Param, Patch, Post, Query, Redirect, Req, Res, UploadedFile, UseGuards, UseInterceptors
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
@@ -84,12 +84,9 @@ export class UsersController {
|
|||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
@Patch()
|
@Patch()
|
||||||
async update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto, @Res() response : Response) {
|
async update(@Req() req, @Body(new ValidationPipe()) usersUpdateDto: UpdateUsersDto, @Res() response : Response) {
|
||||||
console.log("DANS PATCH USERS");
|
|
||||||
const user = await this.usersService.update(req.user.id, usersUpdateDto);
|
const user = await this.usersService.update(req.user.id, usersUpdateDto);
|
||||||
// const user : User = req.user;
|
|
||||||
if (user.isEnabledTwoFactorAuth === false && user.isTwoFactorAuthenticated === true)
|
if (user.isEnabledTwoFactorAuth === false && user.isTwoFactorAuthenticated === true)
|
||||||
this.usersService.setIsTwoFactorAuthenticatedWhenLogout(user.id);
|
this.usersService.setIsTwoFactorAuthenticatedWhenLogout(user.id);
|
||||||
console.log ("Enbale 2FA " + user.isEnabledTwoFactorAuth + " Is authenticated " + user.isTwoFactorAuthenticated);
|
|
||||||
if (user.isEnabledTwoFactorAuth === true && user.isTwoFactorAuthenticated === false)
|
if (user.isEnabledTwoFactorAuth === true && user.isTwoFactorAuthenticated === false)
|
||||||
{
|
{
|
||||||
response.status(201).send('2FA redirect')
|
response.status(201).send('2FA redirect')
|
||||||
@@ -111,9 +108,14 @@ export class UsersController {
|
|||||||
@UseGuards(TwoFactorGuard)
|
@UseGuards(TwoFactorGuard)
|
||||||
@Post('avatar')
|
@Post('avatar')
|
||||||
@UseInterceptors(FileInterceptor('file', storageForAvatar))
|
@UseInterceptors(FileInterceptor('file', storageForAvatar))
|
||||||
uploadAvatar(@UploadedFile() file, @Req() request){
|
uploadAvatar(@UploadedFile() file, @Req() request, @Res() res){
|
||||||
const user : User = request.user;
|
const user : User = request.user;
|
||||||
this.usersService.updateAvatar(user.id, file.filename);
|
if (file)
|
||||||
|
{
|
||||||
|
this.usersService.updateAvatar(user.id, file.filename);
|
||||||
|
return res.status(HttpStatus.OK).json({message : "Avatar updated"});
|
||||||
|
}
|
||||||
|
return res.status(HttpStatus.UNSUPPORTED_MEDIA_TYPE).json({message : "Unsupported media type. Please use a valid image file."});
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET http://transcendance:8080/user/avatar
|
// GET http://transcendance:8080/user/avatar
|
||||||
|
|||||||
@@ -4,12 +4,9 @@ import { User } from './entities/user.entity';
|
|||||||
import { Repository, Not } from 'typeorm';
|
import { Repository, Not } from 'typeorm';
|
||||||
import { CreateUsersDto } from './dto/create-users.dto';
|
import { CreateUsersDto } from './dto/create-users.dto';
|
||||||
import { UpdateUsersDto } from './dto/update-users.dto';
|
import { UpdateUsersDto } from './dto/update-users.dto';
|
||||||
import { Friendship } from '../friendship/entities/friendship.entity';
|
|
||||||
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
import { PaginationQueryDto } from 'src/common/dto/pagination-query.dto';
|
||||||
import { UserStats } from './entities/userStat.entities';
|
import { UserStats } from './entities/userStat.entities';
|
||||||
import { FriendshipService } from 'src/friendship/friendship.service';
|
import { FriendshipService } from 'src/friendship/friendship.service';
|
||||||
import { stringify } from 'querystring';
|
|
||||||
|
|
||||||
// On va devoir sûrement trouver un moyen plus simple pour passer l'id, sûrement via des pipes
|
// On va devoir sûrement trouver un moyen plus simple pour passer l'id, sûrement via des pipes
|
||||||
// ou des interceptors, mais pour l'instant on va faire comme ça.
|
// ou des interceptors, mais pour l'instant on va faire comme ça.
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -86,6 +83,28 @@ export class UsersService {
|
|||||||
return partialUsers;
|
return partialUsers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// async findAbsolutelyAll() {
|
||||||
|
// // const otherUsers = await this.userRepository.find({where: {id: Not(+currentUser.id)}, order: {username: "ASC"}, skip: offset, take: limit,});
|
||||||
|
// const otherUsers = await this.userRepository.find({order: {username: "ASC"}});
|
||||||
|
|
||||||
|
// let partialUsers : Partial<User>[] = [];
|
||||||
|
|
||||||
|
// for (const otherUser of otherUsers) {
|
||||||
|
// console.log('other user: ')
|
||||||
|
// console.log({...otherUser})
|
||||||
|
// let tmp = await this.friendshipService.findIfUserIsBlockedOrHasBlocked(currentUser.id, otherUser.id);
|
||||||
|
// console.log('user.services findIF Blocked... : ')
|
||||||
|
// console.log(tmp)
|
||||||
|
// if (tmp === false) {
|
||||||
|
// // if (await this.friendshipService.findIfUserIsBlockedOrHasBlocked(currentUser.id, otherUser.id) === false) {
|
||||||
|
// partialUsers.push({username: otherUser.username, image_url: otherUser.image_url, status: otherUser.status, stats: otherUser.stats});
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// console.log('user.services findAll, partialUsers:')
|
||||||
|
// console.log({...partialUsers})
|
||||||
|
// return partialUsers;
|
||||||
|
// }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
async create(createUserDto: CreateUsersDto) {
|
async create(createUserDto: CreateUsersDto) {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
server {
|
server {
|
||||||
listen 8080 default_server;
|
listen 8080;
|
||||||
listen [::]:8080 default_server;
|
listen [::]:8080;
|
||||||
server_name transcendance;
|
server_name localhost;
|
||||||
|
|
||||||
location /api/v2 {
|
location /api/v2 {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
@@ -10,6 +10,28 @@ server {
|
|||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
proxy_pass http://backend_dev:3000;
|
proxy_pass http://backend_dev:3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /chat {
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection "Upgrade";
|
||||||
|
proxy_pass http://backend_dev:5000/chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/v2/game/gameserver {
|
||||||
|
deny all;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /pong {
|
||||||
|
proxy_pass http://game_server:8042/pong;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
}
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
@@ -20,10 +42,9 @@ server {
|
|||||||
}
|
}
|
||||||
|
|
||||||
server {
|
server {
|
||||||
|
|
||||||
listen 35729 default_server;
|
listen 35729 default_server;
|
||||||
listen [::]:35729 default_server;
|
listen [::]:35729 default_server;
|
||||||
server_name transcendance;
|
server_name localhost;
|
||||||
location / {
|
location / {
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|||||||
37
srcs/requirements/nginx/conf/nginx.conf
Normal file
37
srcs/requirements/nginx/conf/nginx.conf
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
|
||||||
|
user nginx;
|
||||||
|
worker_processes auto;
|
||||||
|
|
||||||
|
error_log /var/log/nginx/error.log notice;
|
||||||
|
pid /var/run/nginx.pid;
|
||||||
|
|
||||||
|
|
||||||
|
events {
|
||||||
|
worker_connections 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
http {
|
||||||
|
include /etc/nginx/mime.types;
|
||||||
|
default_type application/octet-stream;
|
||||||
|
|
||||||
|
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
||||||
|
'$status $body_bytes_sent "$http_referer" '
|
||||||
|
'"$http_user_agent" "$http_x_forwarded_for"';
|
||||||
|
|
||||||
|
access_log /var/log/nginx/access.log main;
|
||||||
|
|
||||||
|
sendfile on;
|
||||||
|
#tcp_nopush on;
|
||||||
|
|
||||||
|
keepalive_timeout 65;
|
||||||
|
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
#gzip on;
|
||||||
|
|
||||||
|
include /etc/nginx/conf.d/*.conf;
|
||||||
|
}
|
||||||
@@ -8,3 +8,4 @@
|
|||||||
!api_front/*.json
|
!api_front/*.json
|
||||||
!api_front/*.html
|
!api_front/*.html
|
||||||
!api_front/*.lock
|
!api_front/*.lock
|
||||||
|
!api_front/.env
|
||||||
|
|||||||
@@ -1,107 +0,0 @@
|
|||||||
# This repo is no longer maintained. Consider using `npm init vite` and selecting the `svelte` option or — if you want a full-fledged app framework and don't mind using pre-1.0 software — use [SvelteKit](https://kit.svelte.dev), the official application framework for Svelte.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# svelte app
|
|
||||||
|
|
||||||
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
|
|
||||||
|
|
||||||
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npx degit sveltejs/template svelte-app
|
|
||||||
cd svelte-app
|
|
||||||
```
|
|
||||||
|
|
||||||
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
|
|
||||||
|
|
||||||
|
|
||||||
## Get started
|
|
||||||
|
|
||||||
Install the dependencies...
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd svelte-app
|
|
||||||
npm install
|
|
||||||
```
|
|
||||||
|
|
||||||
...then start [Rollup](https://rollupjs.org):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
|
||||||
|
|
||||||
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
|
|
||||||
|
|
||||||
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
|
|
||||||
|
|
||||||
## Building and running in production mode
|
|
||||||
|
|
||||||
To create an optimised version of the app:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
```
|
|
||||||
|
|
||||||
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
|
|
||||||
|
|
||||||
|
|
||||||
## Single-page app mode
|
|
||||||
|
|
||||||
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
|
|
||||||
|
|
||||||
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
|
|
||||||
|
|
||||||
```js
|
|
||||||
"start": "sirv public --single"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Using TypeScript
|
|
||||||
|
|
||||||
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node scripts/setupTypeScript.js
|
|
||||||
```
|
|
||||||
|
|
||||||
Or remove the script via:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
rm scripts/setupTypeScript.js
|
|
||||||
```
|
|
||||||
|
|
||||||
If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
|
|
||||||
|
|
||||||
## Deploying to the web
|
|
||||||
|
|
||||||
### With [Vercel](https://vercel.com)
|
|
||||||
|
|
||||||
Install `vercel` if you haven't already:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g vercel
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, from within your project folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd public
|
|
||||||
vercel deploy --name my-project
|
|
||||||
```
|
|
||||||
|
|
||||||
### With [surge](https://surge.sh/)
|
|
||||||
|
|
||||||
Install `surge` if you haven't already:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm install -g surge
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, from within your project folder:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run build
|
|
||||||
surge public my-project.surge.sh
|
|
||||||
```
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
// routing
|
|
||||||
// may not need {link} here
|
|
||||||
import Router, { link } from "svelte-spa-router";
|
|
||||||
import { routes } from "../routes.js";
|
|
||||||
|
|
||||||
import LoginPage from "./LoginPage.svelte";
|
|
||||||
import UserPage from "../UserPage.svelte";
|
|
||||||
import NotFound from "../pages/NotFound.svelte";
|
|
||||||
|
|
||||||
// Ideally fuck all this shit in the long run
|
|
||||||
let pages = ['login', 'user', 'account'];
|
|
||||||
// make this a $: currentPage ?
|
|
||||||
let currentPage = 'booba';
|
|
||||||
// prolly change this? yea idk...
|
|
||||||
// let userIndex;
|
|
||||||
// tmp for testing
|
|
||||||
let userId = 0;
|
|
||||||
// horrible naming... can be HOME or ACCOUNT
|
|
||||||
let currentType = 'account';
|
|
||||||
|
|
||||||
// this page should handle the SPA history management...
|
|
||||||
|
|
||||||
// set to false later for actual security
|
|
||||||
let loggedIn = true;
|
|
||||||
|
|
||||||
// not sure if this is how i want to do this...
|
|
||||||
// might do differently cuz URL route manangement...
|
|
||||||
const handleLogin = () => {
|
|
||||||
currentPage = 'user';
|
|
||||||
loggedIn = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// I don't know if i should but i'm tempted to put this here...
|
|
||||||
|
|
||||||
// import axios from 'axios';
|
|
||||||
// import { onMount } from 'svelte';
|
|
||||||
// import { push } from 'svelte-spa-router';
|
|
||||||
|
|
||||||
// let user = {logedIn: false};
|
|
||||||
|
|
||||||
// onMount(async () => {
|
|
||||||
// // console.log('PROFIL SVELTE');
|
|
||||||
// const {data} = await axios.get('http://transcendance:8080/api/v2/user');
|
|
||||||
// if (data)
|
|
||||||
// user.logedIn = true;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// $: submit = async() => {
|
|
||||||
// window.location.href = 'http://transcendance:8080/api/v2/auth';
|
|
||||||
// }
|
|
||||||
|
|
||||||
// $: logout = async() => {
|
|
||||||
// await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
|
||||||
// user.logedIn = false;
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- eventually we will do this with routes, but for now use a prop -->
|
|
||||||
<!-- {#if currentPage === 'login'}
|
|
||||||
<LoginPage {pages} {currentPage} {userId} on:loggedIn={handleLogin}/>
|
|
||||||
{:else if currentPage === 'user'}
|
|
||||||
<UserPage {pages} {currentPage} {userId} {currentType}/>
|
|
||||||
{:else}
|
|
||||||
<NotFound />
|
|
||||||
{/if} -->
|
|
||||||
<Router {routes}/>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
/* doesn't work... */
|
|
||||||
/* body{
|
|
||||||
background: bisque;
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,167 +0,0 @@
|
|||||||
<!-- <script lang="ts"> -->
|
|
||||||
<script>
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
let canvas;
|
|
||||||
|
|
||||||
// $: scaleRatio = window.innerWidth / 10;
|
|
||||||
$: scaleRatio = 30;
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// we're invoking JS methods of the canvas element
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
ctx.width = window.innerWidth;
|
|
||||||
ctx.height = window.innerHeight;
|
|
||||||
|
|
||||||
// ctx.beginPath();
|
|
||||||
// ctx.moveTo(50,50);
|
|
||||||
// ctx.lineTo(70,70);
|
|
||||||
// ctx.stroke();
|
|
||||||
|
|
||||||
// attempting to import an image
|
|
||||||
const img = new Image();
|
|
||||||
// we may have to call an onMount or something to make sure the image has loaded...
|
|
||||||
// doing it in JS for now, ideally in Svelte later..
|
|
||||||
// img.addEventListener('load', () => {
|
|
||||||
// // execute drawImage statements here
|
|
||||||
// }, false);
|
|
||||||
img.src = 'img/potato_logo.png'; // seems like this does need to be above onload()
|
|
||||||
img.onload = () => {
|
|
||||||
// ctx.drawImage(img, 0, 0, img.width * (ctx.width / 15), img.height * (ctx.height / 15));
|
|
||||||
// ctx.drawImage(img, 0, 0, ctx.width / 15, ctx.height / 15);
|
|
||||||
|
|
||||||
// i think i don't want this cuz it'll get in the way?
|
|
||||||
// ctx.drawImage(img, 0, 0, img.width / scaleRatio, img.height / scaleRatio);
|
|
||||||
|
|
||||||
// it would seem you need to redraw the images when you change the Window size, it's not automatically responsive
|
|
||||||
};
|
|
||||||
|
|
||||||
let startX = 200;
|
|
||||||
let startY = 200;
|
|
||||||
let dx = 3;
|
|
||||||
let dy = -1;
|
|
||||||
|
|
||||||
// Time for some math
|
|
||||||
// lets say i want 6 rows
|
|
||||||
// ok so we're gonna draw all the potatos at the same time and each of them gets animated, like i have it now
|
|
||||||
// 6 in a row so # of rows aka x = width * 6/height
|
|
||||||
|
|
||||||
function Potato(x, y) {
|
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
// ctx.drawImage(img, x, y, img.width / scaleRatio, img.height / scaleRatio);
|
|
||||||
|
|
||||||
this.draw = function() {
|
|
||||||
ctx.drawImage(img, x, y, img.width / scaleRatio, img.height / scaleRatio);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.animate = function() {
|
|
||||||
this.x += dx;
|
|
||||||
this.y += dy;
|
|
||||||
|
|
||||||
this.draw();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// let spacing = canvas.width * ((6 + 1) / canvas.height);
|
|
||||||
let spacing = 70;
|
|
||||||
let Potatos = [];
|
|
||||||
//check the math...
|
|
||||||
// for (let i = 0; i < 6 * spacing; i++) {
|
|
||||||
for (let i = 0; i < 2; i++) {
|
|
||||||
// for (let j = 0; j < 6; j++) {
|
|
||||||
for (let j = 0; i < 2; j++) {
|
|
||||||
Potatos.push(new Potato(spacing + (i * spacing), spacing + (j * spacing)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// now i'm trying to move 1 potato
|
|
||||||
let frame = requestAnimationFrame(loop);
|
|
||||||
// function loop(t) {
|
|
||||||
// ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
// frame = requestAnimationFrame(loop);
|
|
||||||
// ctx.drawImage(img, startX, startY, img.width / scaleRatio, img.height / scaleRatio);
|
|
||||||
// startX += dx;
|
|
||||||
// startY += dy;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// new Potato(70, 70).animate();
|
|
||||||
|
|
||||||
|
|
||||||
function loop(t) {
|
|
||||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
||||||
frame = requestAnimationFrame(loop);
|
|
||||||
for (let i = 0; i < Potatos.length; i++) {
|
|
||||||
Potatos[i].animate();
|
|
||||||
}
|
|
||||||
// Potatos[0].animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
loop();
|
|
||||||
|
|
||||||
// Lets try again with a single loop
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// THis shit makes the cool Gradient
|
|
||||||
// let frame = requestAnimationFrame(loop);
|
|
||||||
// function loop(t) {
|
|
||||||
// frame = requestAnimationFrame(loop);
|
|
||||||
|
|
||||||
// // const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
|
||||||
|
|
||||||
// for (let p = 0; p < imageData.data.length; p += 4) {
|
|
||||||
// const i = p / 4;
|
|
||||||
// const x = i % canvas.width;
|
|
||||||
// const y = i / canvas.width >>> 0;
|
|
||||||
|
|
||||||
// const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000));
|
|
||||||
// const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
|
|
||||||
// const b = 128;
|
|
||||||
|
|
||||||
// imageData.data[p + 0] = r;
|
|
||||||
// imageData.data[p + 1] = g;
|
|
||||||
// imageData.data[p + 2] = b;
|
|
||||||
// imageData.data[p + 3] = 255;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ctx.putImageData(imageData, 0, 0);
|
|
||||||
// }
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
cancelAnimationFrame(frame);
|
|
||||||
// prolly something else...
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<canvas
|
|
||||||
bind:this={canvas}
|
|
||||||
width={window.innerWidth}
|
|
||||||
height={window.innerHeight}
|
|
||||||
></canvas>
|
|
||||||
<!-- widht and height were 32, trying stuff -->
|
|
||||||
|
|
||||||
<!-- I don't have the /svelte-logo-mask.svg, i guess i'll go get something -->
|
|
||||||
<style>
|
|
||||||
canvas {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background-color: #666;
|
|
||||||
|
|
||||||
|
|
||||||
/* testing something */
|
|
||||||
/* -webkit-mask-image: radial-gradient(circle, black 50%, rgba(0, 0, 0, 0.5) 50%);
|
|
||||||
mask-image: radial-gradient(circle, black 50%, rgba(0, 0, 0, 0.5) 50%); */
|
|
||||||
/* Holy shit that worked! i got a mask */
|
|
||||||
/* it also works without a mask! i have a canvas! */
|
|
||||||
|
|
||||||
/* -webkit-mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat; */
|
|
||||||
/* -webkit-mask: url(img/cartoon_potato3.jpg) 50% 50% no-repeat; */
|
|
||||||
/* mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat; */
|
|
||||||
/* mask: url(img/cartoon_potato3.jpg) 50% 50% no-repeat; */
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { onMount, setContext } from "svelte";
|
|
||||||
|
|
||||||
let canvas;
|
|
||||||
|
|
||||||
// what do i want?
|
|
||||||
// lets start with an image of my potato
|
|
||||||
// then get it to move
|
|
||||||
// then have many displayed in an offset grid
|
|
||||||
|
|
||||||
const drawFunctions = [];
|
|
||||||
|
|
||||||
|
|
||||||
setContext('canvas', {
|
|
||||||
register(drawFn) {
|
|
||||||
drawFunctions.push(drawFn);
|
|
||||||
},
|
|
||||||
unregister(drawFn) {
|
|
||||||
drawFunctions.splice(drawFunctions.indexOf(drawFn), 1);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
// not sure what this does...
|
|
||||||
const ctx = canvas.getContext('2d');
|
|
||||||
|
|
||||||
// no idea what this does...
|
|
||||||
function update() {
|
|
||||||
|
|
||||||
ctx.clearRect()
|
|
||||||
drawFunctions.forEach(drawFn => {
|
|
||||||
drawFn(ctx);
|
|
||||||
});
|
|
||||||
|
|
||||||
frameId = requestAnimationFrame(update);
|
|
||||||
}
|
|
||||||
|
|
||||||
let frameId = requestAnimationFrame(update);
|
|
||||||
return () => {
|
|
||||||
cancelAnimationFrame(update);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<canvas bind:this={canvas} />
|
|
||||||
@@ -1,112 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
import { location } from 'svelte-spa-router';
|
|
||||||
import GenerateUserDisplay from './GenerateUserDisplay.svelte';
|
|
||||||
|
|
||||||
// using location won't work cuz i do a fetch but i don't change the page fragment, so means nothing to location...
|
|
||||||
|
|
||||||
// this is how you access /:first for example
|
|
||||||
// export let params = {}
|
|
||||||
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
|
|
||||||
|
|
||||||
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
|
|
||||||
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
|
|
||||||
// will have to coordinate with Back, will know more once the Game stats are in the back
|
|
||||||
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
|
|
||||||
// not sure if that's what i want...
|
|
||||||
|
|
||||||
|
|
||||||
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
|
|
||||||
// why bother storing that shit in the back...
|
|
||||||
// maybe i need a Rank.svelte component
|
|
||||||
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
|
|
||||||
|
|
||||||
export let aUsername;
|
|
||||||
|
|
||||||
let user;
|
|
||||||
let rank = '';
|
|
||||||
let avatar;
|
|
||||||
|
|
||||||
// i think i don't need to do this once i sort out the {wrap} conditions: in theory i could pass values to the Route
|
|
||||||
// once the async authentication check is done
|
|
||||||
onMount( async() => {
|
|
||||||
console.log('Display aUser username: '+ aUsername)
|
|
||||||
// http://transcendance:8080/api/v2/user?username=NomDuUserATrouver
|
|
||||||
user = await fetch(`http://transcendance:8080/api/v2/user?username=${aUsername}`)
|
|
||||||
.then( (x) => x.json() );
|
|
||||||
|
|
||||||
console.log('Display a user: ' + user.username)
|
|
||||||
|
|
||||||
|
|
||||||
// console.log('profile display did my fetch')
|
|
||||||
// should i be updating the userStore or is that unnecessary?
|
|
||||||
|
|
||||||
if (user.loseGame > user.winGame) {
|
|
||||||
rank = 'Bitch Ass Loser!'
|
|
||||||
} else if (user.loseGame === user.winGame) {
|
|
||||||
rank = 'Fine i guess...'
|
|
||||||
} else {
|
|
||||||
rank = 'Yea you da Boss!'
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
|
|
||||||
.then(response => {return response.blob()})
|
|
||||||
.then(data => {
|
|
||||||
const url = URL.createObjectURL(data);
|
|
||||||
avatar = url;
|
|
||||||
});
|
|
||||||
|
|
||||||
// tmp
|
|
||||||
// console.log('mounted Profile Display')
|
|
||||||
// console.log(user);
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Glittery Stars and such for Rank
|
|
||||||
|
|
||||||
let index = 0, interval = 1000;
|
|
||||||
|
|
||||||
const rand = (min, max) =>
|
|
||||||
Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
|
|
||||||
// it's unhappy that "star" isn't typeset, no idea what to do about it...
|
|
||||||
const animate = (star) => {
|
|
||||||
// the if seems to have fixed the type issue
|
|
||||||
if (star) {
|
|
||||||
star.style.setProperty("--star-left", `${rand(-10, 100)}%`);
|
|
||||||
star.style.setProperty("--star-top", `${rand(-40, 80)}%`);
|
|
||||||
|
|
||||||
star.style.animation = "none";
|
|
||||||
star.offsetHeight;
|
|
||||||
star.style.animation = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the part i invented, it was kinda a fucking nightmare...
|
|
||||||
let stars = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
setTimeout(() => {
|
|
||||||
animate(stars[i]);
|
|
||||||
|
|
||||||
setInterval(() => animate(stars[i]), 1000);
|
|
||||||
}, index++ * (interval / 3))
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{#if user !== undefined}
|
|
||||||
<GenerateUserDisplay {user}/>
|
|
||||||
{:else}
|
|
||||||
<h2>Sorry</h2>
|
|
||||||
<div>Failed to load user {aUsername}</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
// might rename...
|
|
||||||
// no idea what i'm doing...
|
|
||||||
|
|
||||||
import { getContext, onMount } from 'svelte';
|
|
||||||
|
|
||||||
// here you have to export the vars you need to draw this shit...
|
|
||||||
|
|
||||||
const { register, unregister } = getContext('canvas');
|
|
||||||
|
|
||||||
onMount(() => {
|
|
||||||
register(draw);
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
unregister(draw);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function draw(ctx) {
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.fillStyle = fill;
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
@@ -1,124 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
// Fucking having a header that can change size, i don't really want the larger one
|
|
||||||
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let types: string[] = ['home', 'regular', 'none'];
|
|
||||||
// let currentType: string = 'regular';
|
|
||||||
export let currentType = 'regular';
|
|
||||||
// apparently Regular is the only one i use...
|
|
||||||
|
|
||||||
let handleClickHome = () => {
|
|
||||||
dispatch('clickedHome');
|
|
||||||
};
|
|
||||||
|
|
||||||
let handleClickLogout = () => {
|
|
||||||
dispatch('clickedLogout');
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Make it so you can have a Big Home page header and a regular header or no header -->
|
|
||||||
<!-- So far my CSS is super Gross, i guess i'll get Hugo to help me with it -->
|
|
||||||
|
|
||||||
<header class={currentType}>
|
|
||||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
|
||||||
<img class={currentType} src="/img/potato_logo.png" alt="Potato Pong Logo" on:click={handleClickHome}>
|
|
||||||
<!-- {#if currentType === 'home'} -->
|
|
||||||
<h1 class={currentType}>Potato Pong</h1>
|
|
||||||
<!-- {/if} -->
|
|
||||||
<nav class={currentType}>
|
|
||||||
<!-- <a href=""></a> -->
|
|
||||||
<!-- i might change these to links rather than buttons, i kinda hate the buttons -->
|
|
||||||
<button>My Stats</button>
|
|
||||||
<button>Stream</button>
|
|
||||||
<button on:click={handleClickLogout}>Log Out</button>
|
|
||||||
</nav>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* See "possible_fonts.css" for more font options... */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Bondi';
|
|
||||||
src:url('/fonts/Bondi.ttf.woff') format('woff'),
|
|
||||||
url('/fonts/Bondi.ttf.svg#Bondi') format('svg'),
|
|
||||||
url('/fonts/Bondi.ttf.eot'),
|
|
||||||
url('/fonts/Bondi.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* There is a bunch of unncessary shit in here... why so many flex grids, why is everything the same class? just seemed easier but... */
|
|
||||||
|
|
||||||
|
|
||||||
header{
|
|
||||||
/* background: #f7f7f7; */
|
|
||||||
background: #618174;
|
|
||||||
/* padding: 20px; */
|
|
||||||
margin: 0;
|
|
||||||
/* does nothing so far... */
|
|
||||||
/* display: flex; */
|
|
||||||
}
|
|
||||||
|
|
||||||
header.home{
|
|
||||||
/* position: sticky; */
|
|
||||||
}
|
|
||||||
header.regular{
|
|
||||||
/* for some reason this doesn't do shit! */
|
|
||||||
position: sticky;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Headers */
|
|
||||||
h1{
|
|
||||||
font-family: 'Bondi';
|
|
||||||
}
|
|
||||||
h1.home {
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
/* max-width: 100px; */
|
|
||||||
}
|
|
||||||
h1.regular{
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
/* max-width: 40px; */
|
|
||||||
/* this helped with the weird extra space under the image... */
|
|
||||||
display: flex;
|
|
||||||
justify-self: center;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Images */
|
|
||||||
img.home{
|
|
||||||
/* text-align: center; */
|
|
||||||
/* get the image squarely in the middle... */
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 100px;
|
|
||||||
}
|
|
||||||
img.regular{
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 40px;
|
|
||||||
padding: 7px 20px;
|
|
||||||
justify-self: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav{
|
|
||||||
display: flex;
|
|
||||||
justify-content: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav button{
|
|
||||||
margin: 7px 20px;
|
|
||||||
/* padding: 5px; */
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .none{
|
|
||||||
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
let types: string[] = ['home', 'regular', 'none'];
|
|
||||||
// let currentType: string = 'regular';
|
|
||||||
export let currentType = 'home';
|
|
||||||
|
|
||||||
let handleClick = () => {
|
|
||||||
dispatch('clickedHome');
|
|
||||||
};
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Make it so you can have a Big Home page header and a regular header or no header -->
|
|
||||||
<!-- So far my CSS is super Gross, i guess i'll get Hugo to help me with it -->
|
|
||||||
|
|
||||||
<header class={currentType}>
|
|
||||||
<h1 class={currentType}>
|
|
||||||
<img class={currentType} src="/img/potato_logo.png" alt="Potato Pong Logo" on:click={handleClick}>
|
|
||||||
</h1>
|
|
||||||
<!-- {#if currentType === 'home'} -->
|
|
||||||
<h1 class={currentType}>Potato Pong</h1>
|
|
||||||
<!-- {/if} -->
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* See "possible_fonts.css" for more font options... */
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Bondi';
|
|
||||||
src:url('/fonts/Bondi.ttf.woff') format('woff'),
|
|
||||||
url('/fonts/Bondi.ttf.svg#Bondi') format('svg'),
|
|
||||||
url('/fonts/Bondi.ttf.eot'),
|
|
||||||
url('/fonts/Bondi.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* There is a bunch of unncessary shit in here... why so many flex grids, why is everything the same class? just seemed easier but... */
|
|
||||||
|
|
||||||
|
|
||||||
header{
|
|
||||||
/* background: #f7f7f7; */
|
|
||||||
background: #618174;
|
|
||||||
/* padding: 20px; */
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Bondi';
|
|
||||||
/* does nothing so far... */
|
|
||||||
/* display: flex; */
|
|
||||||
}
|
|
||||||
|
|
||||||
header.home{
|
|
||||||
/* position: sticky; */
|
|
||||||
}
|
|
||||||
header.regular{
|
|
||||||
position: sticky;
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr 1fr;
|
|
||||||
}
|
|
||||||
header.regular > h1:first-child{
|
|
||||||
justify-self: left;
|
|
||||||
}
|
|
||||||
header.regular > h1:nth-child(2){
|
|
||||||
justify-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Headers */
|
|
||||||
h1.home {
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
/* max-width: 100px; */
|
|
||||||
}
|
|
||||||
h1.regular{
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
/* max-width: 40px; */
|
|
||||||
/* this helped with the weird extra space under the image... */
|
|
||||||
display: flex;
|
|
||||||
justify-self: center;
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Images */
|
|
||||||
h1 img.home{
|
|
||||||
/* text-align: center; */
|
|
||||||
/* get the image squarely in the middle... */
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 100px;
|
|
||||||
}
|
|
||||||
h1 img.regular{
|
|
||||||
cursor: pointer;
|
|
||||||
max-width: 40px;
|
|
||||||
padding: 7px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* .none{
|
|
||||||
|
|
||||||
} */
|
|
||||||
</style>
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
// Now called LoginPage
|
|
||||||
|
|
||||||
// import Header from "./Header.svelte";
|
|
||||||
import Footer from "../components/Footer.svelte";
|
|
||||||
import Login from "./Login.svelte";
|
|
||||||
import Tabs from "../shared/Tabs.svelte"
|
|
||||||
import Card from "../pieces/Card.svelte"
|
|
||||||
// tmp
|
|
||||||
let login = { username: '', password: ''};
|
|
||||||
// let's us track any errors in a submited form
|
|
||||||
let errors = { username: '', password: ''};
|
|
||||||
let valid:boolean = false;
|
|
||||||
const loginHandler = () => {
|
|
||||||
console.log('hi');
|
|
||||||
};
|
|
||||||
const createAccountHandler = () => {
|
|
||||||
console.log('hi');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Tabs
|
|
||||||
let items: string[] = ['Login', 'Create Account'];
|
|
||||||
let activeItem: string = 'Login';
|
|
||||||
|
|
||||||
const tabChange = (e) => {
|
|
||||||
activeItem = e.detail;
|
|
||||||
};
|
|
||||||
|
|
||||||
// TMP for switching page, down the line this will be down by modifying url
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- New New Approach -->
|
|
||||||
<!-- if i were to do all this with CSS Grids how would i do it? -->
|
|
||||||
<!-- Things i want -->
|
|
||||||
<!-- A title button, some nav buttons, a giant dope canvas and words over it -->
|
|
||||||
<!-- not sure if i want: login and create account -->
|
|
||||||
<!-- let's start with just the canvas -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- New aproach -->
|
|
||||||
<!-- just make the html in order you can move it around all you want into special Compoenents later -->
|
|
||||||
|
|
||||||
<!-- Ok i think all of this needs to go in a Home Page Component -->
|
|
||||||
<!-- and then i make another master component for the main page once you're logged in, no idea what that should look like -->
|
|
||||||
|
|
||||||
<!-- what if i kept the special canvas header in here and made another generic header for the rest of the site as a component -->
|
|
||||||
|
|
||||||
<header class="banner">
|
|
||||||
<!-- top left corner, sticky -->
|
|
||||||
<h1>Potato Pong</h1>
|
|
||||||
<!-- top right but it takes you down the page -->
|
|
||||||
<h2>Login</h2>
|
|
||||||
|
|
||||||
<!-- all this used to be in Welcome Section -->
|
|
||||||
<!-- the amazing backround! not sure yet if it should scroll with us or be the size of the View Port... -->
|
|
||||||
<!-- <canvas></canvas> -->
|
|
||||||
<!-- i think maybe the canvas needs to be in the header -->
|
|
||||||
<!-- using an image for now as a placehodler for the canvase -->
|
|
||||||
<img src="/img/tmp_mario_banner.png" alt="tmp Mario banner">
|
|
||||||
<div class="welcome">
|
|
||||||
<h2>Welcome to <br><span>Potato Pong</span></h2>
|
|
||||||
</div>
|
|
||||||
<!-- I want some sort of arrow pointing down and blinking to indicate you should scroll -->
|
|
||||||
</header>
|
|
||||||
<!-- <section class="banner"> -->
|
|
||||||
<section class="welcome">
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<!-- no nav on home page -->
|
|
||||||
<section class="register">
|
|
||||||
|
|
||||||
<!-- i could have a toggle tab to login or create a new account -->
|
|
||||||
|
|
||||||
<Tabs items={items} {activeItem} on:tabChange={tabChange}/>
|
|
||||||
{#if activeItem === 'Login'}
|
|
||||||
<div class="card">
|
|
||||||
<Card>
|
|
||||||
<h2>Login</h2>
|
|
||||||
<form on:submit|preventDefault={loginHandler}>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
|
||||||
<div class="error">{ errors.username }</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
|
||||||
<div class="error">{ errors.password }</div>
|
|
||||||
</div>
|
|
||||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
|
||||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
|
||||||
<button>Login</button>
|
|
||||||
</form>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
{:else if activeItem = 'Create Account'}
|
|
||||||
<!-- Create Account -->
|
|
||||||
<div class="card">
|
|
||||||
<Card>
|
|
||||||
<h3>Create Account</h3>
|
|
||||||
<form on:submit|preventDefault={createAccountHandler}>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
|
||||||
<div class="error">{ errors.username }</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
|
||||||
<div class="error">{ errors.password }</div>
|
|
||||||
</div>
|
|
||||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
|
||||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
|
||||||
<button>Login</button>
|
|
||||||
</form>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<!-- below this i could say, this is where i might have put an explanation of what you can do on this page but fuck you i didn't -->
|
|
||||||
<!-- or maybe in the end i will, something like: Fun, Game, Colors, enjoy Friendship, or don't, it's your choice! -->
|
|
||||||
<Footer />
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* hearder stuff */
|
|
||||||
|
|
||||||
/* Clearly i have yet to master floating stuff... */
|
|
||||||
/* i need to put box-sizing in here somewhere for the login... */
|
|
||||||
|
|
||||||
/* starting again with CSS Grid */
|
|
||||||
/* .banner{
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
.banner img{
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.banner h1{
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
.banner h2{
|
|
||||||
position: absolute;
|
|
||||||
left: 90%;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
.banner .welcome{
|
|
||||||
background-color: #feb614;
|
|
||||||
color:white;
|
|
||||||
padding: 30px;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
}
|
|
||||||
.banner .welcome h2{
|
|
||||||
font-size: 58px;
|
|
||||||
}
|
|
||||||
.banner .welcome h2 span{
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.register{
|
|
||||||
|
|
||||||
} */
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { now } from "svelte/internal";
|
|
||||||
import Card from "../pieces/Card.svelte";
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
// import UserStore from './stores/UserStore';
|
|
||||||
|
|
||||||
// prolly Typescript-ify
|
|
||||||
//let fields{question:string, answerA:string, answerB: string} = { question: '', answerA: '', answerB: ''};
|
|
||||||
let login = { username: '', password: ''};
|
|
||||||
// let's us track any errors in a submited form
|
|
||||||
let errors = { username: '', password: ''};
|
|
||||||
let valid:boolean = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
const loginHandler = () => {
|
|
||||||
valid = true;
|
|
||||||
|
|
||||||
if (login.username !== $UserStore.username) {
|
|
||||||
valid = false;
|
|
||||||
errors.username = "wrong example username."
|
|
||||||
} else {
|
|
||||||
// reseting the value of errors
|
|
||||||
errors.username = "";
|
|
||||||
}
|
|
||||||
if (login.password !== $UserStore.password) {
|
|
||||||
valid = false;
|
|
||||||
errors.password = "wrong example password."
|
|
||||||
} else {
|
|
||||||
// reseting the value of errors
|
|
||||||
errors.password = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
$UserStore.loggedIn = true;
|
|
||||||
$UserStore.status = 'online';
|
|
||||||
|
|
||||||
dispatch('login');
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="login">
|
|
||||||
<Card>
|
|
||||||
|
|
||||||
<form on:submit|preventDefault={loginHandler}>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
|
||||||
<div class="error">{ errors.username }</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
|
||||||
<div class="error">{ errors.password }</div>
|
|
||||||
</div>
|
|
||||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
|
||||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
|
||||||
<button>Login</button>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
form{
|
|
||||||
width: 200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.form-field{
|
|
||||||
margin: 18px auto;
|
|
||||||
}
|
|
||||||
input{
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 6px;
|
|
||||||
}
|
|
||||||
.login{
|
|
||||||
/* display: grid;
|
|
||||||
grid-template-columns: 1fr; */
|
|
||||||
max-width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error{
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #d91b42;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,414 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
// import Header from "./Header.svelte";
|
|
||||||
import Footer from "../components/Footer.svelte";
|
|
||||||
import Tabs from "../shared/Tabs.svelte";
|
|
||||||
import Card from "../pieces/Card.svelte";
|
|
||||||
import Canvas from "../pieces/Canvas.svelte";
|
|
||||||
import ScrollTo from "../shared/ScrollTo.svelte";
|
|
||||||
import UserStore from "./UserStore.js";
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import {push} from "svelte-spa-router";
|
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
|
||||||
// Tabs
|
|
||||||
let items: string[] = ['Login', 'Create Account'];
|
|
||||||
let activeItem: string = 'Login';
|
|
||||||
|
|
||||||
const tabChange = (e) => {
|
|
||||||
activeItem = e.detail;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
import axios from 'axios';
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
let user = {logedIn: false};
|
|
||||||
|
|
||||||
onMount(async () => {
|
|
||||||
// console.log('PROFIL SVELTE');
|
|
||||||
const {data} = await axios.get('http://transcendance:8080/api/v2/user');
|
|
||||||
if (data)
|
|
||||||
user.logedIn = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
const submit = async() => {
|
|
||||||
document.body.scrollIntoView();
|
|
||||||
push
|
|
||||||
window.location.href = 'http://transcendance:8080/api/v2/auth';
|
|
||||||
}
|
|
||||||
|
|
||||||
const logout = async() => {
|
|
||||||
await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
|
||||||
user.logedIn = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// for toLogin
|
|
||||||
|
|
||||||
let bottomHalf;
|
|
||||||
// console.log(bottomHalf);
|
|
||||||
// const element = document.body;
|
|
||||||
|
|
||||||
|
|
||||||
// in theory this could be a Store, but for now this will do
|
|
||||||
// also in future we'll do this with urls
|
|
||||||
export let pages;
|
|
||||||
export let currentPage;
|
|
||||||
// this shit has overstayed it's welcome, fuck having userId all over the place
|
|
||||||
export let userId;
|
|
||||||
|
|
||||||
|
|
||||||
// maybe we put this in the login Component?
|
|
||||||
// tmp
|
|
||||||
let login = { username: '', password: ''};
|
|
||||||
// let's us track any errors in a submited form
|
|
||||||
let errors = { username: '', password: ''};
|
|
||||||
let valid:boolean = false;
|
|
||||||
|
|
||||||
const loginHandler = () => {
|
|
||||||
console.log('hi from loginHandler');
|
|
||||||
|
|
||||||
|
|
||||||
//
|
|
||||||
// Basic Checks
|
|
||||||
//
|
|
||||||
valid = false;
|
|
||||||
|
|
||||||
// checkin Username
|
|
||||||
if (login.username.length < 1)
|
|
||||||
{
|
|
||||||
valid = false;
|
|
||||||
errors.username = 'please enter a username';
|
|
||||||
} else {
|
|
||||||
valid = true;
|
|
||||||
errors.username = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Checking Password
|
|
||||||
if (login.password.length < 1)
|
|
||||||
{
|
|
||||||
valid = false;
|
|
||||||
errors.password = 'please enter your password';
|
|
||||||
} else {
|
|
||||||
valid = true;
|
|
||||||
errors.password = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Advanded Checks
|
|
||||||
//
|
|
||||||
|
|
||||||
// Comparing to UserStore
|
|
||||||
let users;
|
|
||||||
const unsubscribe = UserStore.subscribe(objs => {
|
|
||||||
users = objs;
|
|
||||||
console.log('subscribed');
|
|
||||||
});
|
|
||||||
|
|
||||||
// could i do $users.length ? doesn't look like it...
|
|
||||||
// let len = $users.length;
|
|
||||||
|
|
||||||
// userId = 0;
|
|
||||||
// userId = users.filter(user => user.username === login.username);
|
|
||||||
// this shit returns an array, it would be nice if it weren't an array
|
|
||||||
// let user = users.filter(user => user.username === login.username);
|
|
||||||
let user = users.find(user => user.username === login.username);
|
|
||||||
|
|
||||||
console.log(user);
|
|
||||||
// console.log(user.password);
|
|
||||||
|
|
||||||
|
|
||||||
// all this shit is a bit wordy... maybe a better way to handle this stuff?
|
|
||||||
|
|
||||||
// if (userIndex > users.length) {
|
|
||||||
if (!user) {
|
|
||||||
valid = false;
|
|
||||||
errors.username = 'user not found';
|
|
||||||
// something better?
|
|
||||||
} else {
|
|
||||||
valid = true;
|
|
||||||
errors.username = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// if (users[userIndex].password !== login.password) {
|
|
||||||
if (user && user.password !== login.password) {
|
|
||||||
valid = false;
|
|
||||||
errors.password = 'Wrong Password';
|
|
||||||
// Maybe clear the fields?
|
|
||||||
} else {
|
|
||||||
valid = true;
|
|
||||||
errors.password = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Validation
|
|
||||||
//
|
|
||||||
unsubscribe();
|
|
||||||
|
|
||||||
if (valid) {
|
|
||||||
// yea don't modify this here...
|
|
||||||
currentPage = 'user';
|
|
||||||
// something like: indicate that userIndex is the one we want...
|
|
||||||
console.log('valid Credentials');
|
|
||||||
|
|
||||||
// making sure we start at the top of the page, way less jarring
|
|
||||||
// leave for now just in case...
|
|
||||||
document.body.scrollIntoView();
|
|
||||||
|
|
||||||
// may not actually want a dispatch?
|
|
||||||
// pass data userIndex?
|
|
||||||
// dispatch('loggedIn');
|
|
||||||
// from svelte-spa-router
|
|
||||||
push("/user");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//
|
|
||||||
const createAccountHandler = () => {
|
|
||||||
console.log('hi from accunt handler');
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
<header class="grid-container">
|
|
||||||
|
|
||||||
<!-- <div on:mouseenter={enter} on:mouseleave={leave} class:active > -->
|
|
||||||
<h1>Potato Pong</h1>
|
|
||||||
<!-- </div> -->
|
|
||||||
<nav>
|
|
||||||
<!-- placeholder links -->
|
|
||||||
<a href="/">Somewhere</a>
|
|
||||||
<!-- <a href="/">SomewhereElse</a> -->
|
|
||||||
{#if !user.logedIn}
|
|
||||||
<ScrollTo element={bottomHalf}/>
|
|
||||||
{:else}
|
|
||||||
<div class="logout" on:click={logout}>Log Out</div>
|
|
||||||
{/if}
|
|
||||||
<!-- one of these will be login and it will scroll you down to the login part -->
|
|
||||||
</nav>
|
|
||||||
<h2>
|
|
||||||
<div>Welcome to</div>
|
|
||||||
<div>Potato Pong</div>
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<!-- here i want a flashing arrow pointing down to the login part -->
|
|
||||||
|
|
||||||
</header>
|
|
||||||
<!-- <Canvas class=".canvas2"/> -->
|
|
||||||
<Canvas/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<section class="register" bind:this={bottomHalf}>
|
|
||||||
|
|
||||||
<!-- My beautiful tabs are useless now, whatever, kill your darlings... -->
|
|
||||||
<!-- i could have a toggle tab to login or create a new account -->
|
|
||||||
<!-- This shit is kinda unnecessary cuz there is no Create Account option... -->
|
|
||||||
|
|
||||||
<Tabs items={items} {activeItem} on:tabChange={tabChange}/>
|
|
||||||
{#if activeItem === 'Login'}
|
|
||||||
<div class="card">
|
|
||||||
<Card>
|
|
||||||
<h2>Login</h2>
|
|
||||||
<form on:submit|preventDefault={loginHandler}>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
|
||||||
<div class="error">{ errors.username }</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
|
||||||
<div class="error">{ errors.password }</div>
|
|
||||||
</div>
|
|
||||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
|
||||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
|
||||||
<button>Login</button>
|
|
||||||
</form>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
{:else if activeItem = 'Create Account'}
|
|
||||||
<!-- Create Account -->
|
|
||||||
<div class="card">
|
|
||||||
<Card>
|
|
||||||
<h2>Create Account</h2>
|
|
||||||
<form on:submit|preventDefault={createAccountHandler}>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="text" id="username" placeholder="username" bind:value={login.username}>
|
|
||||||
<div class="error">{ errors.username }</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-field">
|
|
||||||
<input type="password" id="password" placeholder="password" bind:value={login.password}>
|
|
||||||
<div class="error">{ errors.password }</div>
|
|
||||||
</div>
|
|
||||||
<!-- type="" but flat={} cuz type is a string but flat is a bool-->
|
|
||||||
<!-- <Button type="secondary" flat={true}>Add Poll</Button> -->
|
|
||||||
<button>Login</button>
|
|
||||||
</form>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
|
|
||||||
</section>
|
|
||||||
<!-- below this i could say, this is where i might have put an explanation of what you can do on this page but fuck you i didn't -->
|
|
||||||
<!-- or maybe in the end i will, something like: Fun, Game, Colors, enjoy Friendship, or don't, it's your choice! -->
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
|
|
||||||
<!-- </div> -->
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* currently useless */
|
|
||||||
/* No styles get applied to the canvas from here, maybe i should move them to the Canvas Component... */
|
|
||||||
/* tho tbh, why bother, i'm gonna change it anyway... */
|
|
||||||
.canvas{
|
|
||||||
/* grid-column: 1 / 13;
|
|
||||||
grid-row: 1 / 3; */
|
|
||||||
/* don't rely on Z-Index!!!! */
|
|
||||||
z-index: -1;
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
/* Tmp? */
|
|
||||||
/* background-color: #666; */
|
|
||||||
|
|
||||||
/* somehow this got rid of they annoying white space under the canvas */
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas2{
|
|
||||||
z-index: -1;
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
/* Tmp? */
|
|
||||||
/* background-color: #666; */
|
|
||||||
|
|
||||||
/* somehow this got rid of they annoying white space under the canvas */
|
|
||||||
padding-bottom: 0;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* .canvas .grid-container{ */
|
|
||||||
.grid-container{
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
|
|
||||||
|
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
white-space: nowrap;
|
|
||||||
/* padding-bottom: 0; */
|
|
||||||
margin-bottom: 0px;
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 20px 40px;
|
|
||||||
|
|
||||||
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(12, 1fr);
|
|
||||||
grid-template-rows: 1fr 1fr 1fr 1fr 1fr;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1, header nav a{
|
|
||||||
/* tmp ? well i kinda like it */
|
|
||||||
color: bisque;
|
|
||||||
}
|
|
||||||
header h1{
|
|
||||||
grid-column: 1 / 7;
|
|
||||||
grid-row: 1;
|
|
||||||
/* grid-column: span 6; */
|
|
||||||
/* tmp? */
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid bisque;
|
|
||||||
}
|
|
||||||
header nav{
|
|
||||||
/* make it a flexbox? */
|
|
||||||
grid-column: 7 / 13;
|
|
||||||
grid-row: 1;
|
|
||||||
justify-self: end;
|
|
||||||
/* tmp? */
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid bisque;
|
|
||||||
}
|
|
||||||
header nav a{
|
|
||||||
margin-left: 10px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* testing */
|
|
||||||
header nav a:hover{
|
|
||||||
font-weight: bold;
|
|
||||||
background-color: blue;
|
|
||||||
}
|
|
||||||
header h2:hover{
|
|
||||||
background: blue;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
header h2{
|
|
||||||
grid-row: 3;
|
|
||||||
grid-column: 5 / span 4;
|
|
||||||
justify-self: center;
|
|
||||||
/* tmp */
|
|
||||||
border: 1px solid black;
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
header h2 div{
|
|
||||||
font-size: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the login / register part */
|
|
||||||
|
|
||||||
/* What do i want?
|
|
||||||
I want it to be the same size as a full screen so you don't see the canvas at all anymore */
|
|
||||||
|
|
||||||
/* doesn't work... */
|
|
||||||
/* body{
|
|
||||||
background: bisque;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.bottom-half{
|
|
||||||
/* doesn't quite work... */
|
|
||||||
background: bisque;
|
|
||||||
/* also doesn't work... */
|
|
||||||
/* height: 1vw; */
|
|
||||||
|
|
||||||
/* testing */
|
|
||||||
/* position: absolute; */
|
|
||||||
}
|
|
||||||
|
|
||||||
section.register{
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error{
|
|
||||||
font-size: 0.8em;
|
|
||||||
font-weight: bold;
|
|
||||||
color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,305 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
|
|
||||||
import { location } from 'svelte-spa-router';
|
|
||||||
// this is how you access /:first for example
|
|
||||||
// export let params = {}
|
|
||||||
// <p>Your name is: <b>{params.first}</b> <b>{#if params.last}{params.last}{/if}</b></p>
|
|
||||||
|
|
||||||
// If i export these vars, maybe as an nice tidy object, i could pass whatever i like to them
|
|
||||||
// The current user, some other user, whatever, and thus reuse this Componente for the user and their friends or whatever
|
|
||||||
// will have to coordinate with Back, will know more once the Game stats are in the back
|
|
||||||
// wait maybe this won't work, cuz like it's still going through a route, i would have to update a Store Var each time...
|
|
||||||
// not sure if that's what i want...
|
|
||||||
|
|
||||||
|
|
||||||
// maybe the rank is determined dynamically just in the front based on win loss ratio or something no one cares about
|
|
||||||
// why bother storing that shit in the back...
|
|
||||||
// maybe i need a Rank.svelte component
|
|
||||||
// ohhh i could make above a certain rank glitter! like that CSS tutorial showed me!
|
|
||||||
|
|
||||||
let user;
|
|
||||||
let rank = '';
|
|
||||||
let avatar;
|
|
||||||
|
|
||||||
// i think i don't need to do this once i sort out the {wrap} conditions: in theory i could pass values to the Route
|
|
||||||
// once the async authentication check is done
|
|
||||||
onMount( async() => {
|
|
||||||
// console.log('mounting profile display')
|
|
||||||
user = await fetch('http://transcendance:8080/api/v2/user')
|
|
||||||
.then( (x) => x.json() );
|
|
||||||
|
|
||||||
// console.log('profile display did my fetch')
|
|
||||||
// should i be updating the userStore or is that unnecessary?
|
|
||||||
|
|
||||||
if (user.loseGame > user.winGame) {
|
|
||||||
rank = 'Bitch Ass Loser!'
|
|
||||||
} else if (user.loseGame === user.winGame) {
|
|
||||||
rank = 'Fine i guess...'
|
|
||||||
} else {
|
|
||||||
rank = 'Yea you da Boss!'
|
|
||||||
}
|
|
||||||
|
|
||||||
await fetch("http://transcendance:8080/api/v2/user/avatar", {method: "GET"})
|
|
||||||
.then(response => {return response.blob()})
|
|
||||||
.then(data => {
|
|
||||||
const url = URL.createObjectURL(data);
|
|
||||||
avatar = url;
|
|
||||||
});
|
|
||||||
|
|
||||||
// tmp
|
|
||||||
// console.log('mounted Profile Display')
|
|
||||||
// console.log(user);
|
|
||||||
|
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
// Glittery Stars and such for Rank
|
|
||||||
|
|
||||||
let index = 0, interval = 1000;
|
|
||||||
|
|
||||||
const rand = (min, max) =>
|
|
||||||
Math.floor(Math.random() * (max - min + 1)) + min;
|
|
||||||
|
|
||||||
// it's unhappy that "star" isn't typeset, no idea what to do about it...
|
|
||||||
const animate = (star) => {
|
|
||||||
// the if seems to have fixed the type issue
|
|
||||||
if (star) {
|
|
||||||
star.style.setProperty("--star-left", `${rand(-10, 100)}%`);
|
|
||||||
star.style.setProperty("--star-top", `${rand(-40, 80)}%`);
|
|
||||||
|
|
||||||
star.style.animation = "none";
|
|
||||||
star.offsetHeight;
|
|
||||||
star.style.animation = "";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the part i invented, it was kinda a fucking nightmare...
|
|
||||||
let stars = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
setTimeout(() => {
|
|
||||||
animate(stars[i]);
|
|
||||||
|
|
||||||
setInterval(() => animate(stars[i]), 1000);
|
|
||||||
}, index++ * (interval / 3))
|
|
||||||
}
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- is this if excessive? -->
|
|
||||||
<div class="outer">
|
|
||||||
{#if user !== undefined}
|
|
||||||
<main>
|
|
||||||
<!-- <img class="icon" src="img/default_user_icon.png" alt="default user icon"> -->
|
|
||||||
<!-- <img class="icon" src="{user.image_url}" alt="default user icon"> -->
|
|
||||||
<img class="avatar" src="{avatar}" alt="default user icon">
|
|
||||||
<div class="username">{user.username}</div>
|
|
||||||
<div class="rank">Rank:
|
|
||||||
<span class="glitter">
|
|
||||||
<span bind:this={stars[0]} class="glitter-star">
|
|
||||||
<svg viewBox="0 0 512 512">
|
|
||||||
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span bind:this={stars[1]} class="glitter-star">
|
|
||||||
<svg viewBox="0 0 512 512">
|
|
||||||
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span bind:this={stars[2]} class="glitter-star">
|
|
||||||
<svg viewBox="0 0 512 512">
|
|
||||||
<path d="M512 255.1c0 11.34-7.406 20.86-18.44 23.64l-171.3 42.78l-42.78 171.1C276.7 504.6 267.2 512 255.9 512s-20.84-7.406-23.62-18.44l-42.66-171.2L18.47 279.6C7.406 276.8 0 267.3 0 255.1c0-11.34 7.406-20.83 18.44-23.61l171.2-42.78l42.78-171.1C235.2 7.406 244.7 0 256 0s20.84 7.406 23.62 18.44l42.78 171.2l171.2 42.78C504.6 235.2 512 244.6 512 255.1z" />
|
|
||||||
</svg>
|
|
||||||
</span>
|
|
||||||
<span class="glitter-text">{rank}</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<section class="main-stats">
|
|
||||||
<h4>Match Statistics</h4>
|
|
||||||
<p>Total: {user.stats.totalGame}</p>
|
|
||||||
<p>Victories: {user.stats.winGame}</p>
|
|
||||||
<p>Losses: {user.stats.loseGame}</p>
|
|
||||||
<p>Draws: {user.stats.drawGame}</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
<div>testing when there's tons of stuff</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
div.outer{
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 40px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The main part */
|
|
||||||
main{
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 40px auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Normal CSS stuff */
|
|
||||||
.avatar{
|
|
||||||
max-width: 150px;
|
|
||||||
/* padding: 5px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The variable rich section */
|
|
||||||
section.main-stats{
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 40px auto;
|
|
||||||
text-align: center;
|
|
||||||
/* i think i want to use a grid? */
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
/* not sure about this, maybe top should be larger? */
|
|
||||||
grid-template-rows: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the stuff in the grid*/
|
|
||||||
section.main-stats h4{
|
|
||||||
grid-column: 1 / span 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.username{
|
|
||||||
font-size: 1.5em;
|
|
||||||
font-weight: bold;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.rank {
|
|
||||||
/* color: black; */
|
|
||||||
font-size: 1.2em;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* Glittery Star Stuff */
|
|
||||||
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--purple: rgb(123, 31, 162);
|
|
||||||
--violet: rgb(103, 58, 183);
|
|
||||||
--pink: rgb(244, 143, 177);
|
|
||||||
/* make shit gold? */
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes background-pan {
|
|
||||||
from {
|
|
||||||
background-position: 0% center;
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
background-position: -200% center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes scale {
|
|
||||||
from, to {
|
|
||||||
transform: scale(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rotate {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
to {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div > .glitter {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
div > .glitter > .glitter-star {
|
|
||||||
--size: clamp(20px, 1.5vw, 30px);
|
|
||||||
|
|
||||||
animation: scale 700ms ease forwards;
|
|
||||||
display: block;
|
|
||||||
height: var(--size);
|
|
||||||
left: var(--star-left);
|
|
||||||
position: absolute;
|
|
||||||
top: var(--star-top);
|
|
||||||
width: var(--size);
|
|
||||||
}
|
|
||||||
|
|
||||||
div > .glitter > .glitter-star > svg {
|
|
||||||
animation: rotate 1000ms linear infinite;
|
|
||||||
display: block;
|
|
||||||
opacity: 0.7;
|
|
||||||
}
|
|
||||||
|
|
||||||
div > .glitter > .glitter-star > svg > path {
|
|
||||||
fill: var(--violet);
|
|
||||||
}
|
|
||||||
|
|
||||||
div > .glitter > .glitter-text {
|
|
||||||
animation: background-pan 3s linear infinite;
|
|
||||||
/* background-image: linear-gradient( */
|
|
||||||
background: linear-gradient(
|
|
||||||
to right,
|
|
||||||
var(--purple),
|
|
||||||
var(--violet),
|
|
||||||
var(--pink),
|
|
||||||
var(--purple)
|
|
||||||
);
|
|
||||||
background-size: 200%;
|
|
||||||
|
|
||||||
/* Keep these for Safari and chrome */
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
|
|
||||||
/* These are for Firefox */
|
|
||||||
background-clip: text;
|
|
||||||
color: transparent;
|
|
||||||
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,137 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
|
||||||
import { loginStatus } from '../stores/loginStatusStore';
|
|
||||||
|
|
||||||
// Cherif's code
|
|
||||||
|
|
||||||
let qrCodeImg;
|
|
||||||
let qrCode = "";
|
|
||||||
let wrongCode = "";
|
|
||||||
let maxTry = 3;
|
|
||||||
// const fetchQrCodeImg = (async() => {
|
|
||||||
// await fetch("http://transcendance:8080/api/v2/auth/2fa/generate",
|
|
||||||
// {
|
|
||||||
// method: 'POST',
|
|
||||||
// })
|
|
||||||
// .then(response => {return response.blob()})
|
|
||||||
// .then(blob => {
|
|
||||||
// const url = URL.createObjectURL(blob);
|
|
||||||
// qrCodeImg = url;
|
|
||||||
// });
|
|
||||||
// })()
|
|
||||||
|
|
||||||
// $: submit = async() => {
|
|
||||||
// const response = await fetch("http://transcendance:8080/api/v2/auth/2fa/turn-on",
|
|
||||||
// {
|
|
||||||
// method : 'POST',
|
|
||||||
// headers : {
|
|
||||||
// "Content-Type": "application/json",
|
|
||||||
// },
|
|
||||||
// body : JSON.stringify({
|
|
||||||
// "twoFaCode" : qrCode,
|
|
||||||
// }),
|
|
||||||
// });
|
|
||||||
// if (response.status === 401)
|
|
||||||
// {
|
|
||||||
// qrCode = "";
|
|
||||||
// wrongCode = `Wrong code, please try again. You have ${maxTry} before end session`;
|
|
||||||
// maxTry--;
|
|
||||||
// }
|
|
||||||
// if (maxTry === 0)
|
|
||||||
// {
|
|
||||||
// await fetch("http://transcendance:8080/auth/logout",
|
|
||||||
// {
|
|
||||||
// method : 'POST',
|
|
||||||
// })
|
|
||||||
// .then(response => response.json())
|
|
||||||
// .then(push("/login"));
|
|
||||||
// }
|
|
||||||
// if (response.status === 200)
|
|
||||||
// {
|
|
||||||
// push("/");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// My code
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
let auth;
|
|
||||||
// we're expecting secret and otpauth
|
|
||||||
|
|
||||||
onMount( async() => {
|
|
||||||
// auth = await fetch('http://transcendance:8080/api/v2/auth/2fa/generate', {
|
|
||||||
// method: 'POST'
|
|
||||||
// })
|
|
||||||
// .then((resp) => resp.json());
|
|
||||||
// console.log(auth.secret);
|
|
||||||
|
|
||||||
await fetch("http://transcendance:8080/api/v2/auth/2fa/generate", {
|
|
||||||
method: 'POST',
|
|
||||||
})
|
|
||||||
.then(response => {return response.blob()})
|
|
||||||
.then(blob => {
|
|
||||||
const url = URL.createObjectURL(blob);
|
|
||||||
qrCodeImg = url;
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
// testing loginStatus Custom Store
|
|
||||||
|
|
||||||
const toggleTFA = () => {
|
|
||||||
loginStatus.toggleTFA();
|
|
||||||
console.log($loginStatus.tfa);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// testing
|
|
||||||
|
|
||||||
let auth2
|
|
||||||
const TFA = async() => {
|
|
||||||
// ok no idea what goes in here...
|
|
||||||
auth2 = await fetch('http://transcendance:8080/api/v2/auth/2fa/generate', {
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
// .then((resp) => resp.json());
|
|
||||||
|
|
||||||
// console.log(auth2.secret);
|
|
||||||
console.log(auth2);
|
|
||||||
};
|
|
||||||
|
|
||||||
// if ($loginStatus.tfa && $loginStatus.fortyTwo)
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<h1>2FA Test</h1>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button on:click={TFA}>TFA</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
{#if auth2}
|
|
||||||
<p>{auth2.json()}</p>
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<img src={qrCodeImg} alt="A QRCodeImg you must scan with google authenticator" id="qrcodeImg" />
|
|
||||||
|
|
||||||
|
|
||||||
<!-- <p>FortyTwo: {$loginStatus.fortyTwo}</p>
|
|
||||||
<p>TFA: {$loginStatus.tfa}</p>
|
|
||||||
<p>isLogged: {loginStatus.isLogged}</p>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<button on:click={toggleTFA}>toggleTFA</button>
|
|
||||||
</div> -->
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,149 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
|
|
||||||
// The User Page can have several Flavors ?
|
|
||||||
// like a HomePage vibe, and an AccountPage vibe or whatever, but we'll still be serving this page just with diff props
|
|
||||||
|
|
||||||
import UserStore from "./UserStore";
|
|
||||||
import Header from "../components/Header.svelte";
|
|
||||||
import Footer from "../components/Footer.svelte";
|
|
||||||
import { createEventDispatcher } from "svelte";
|
|
||||||
import { loginStatus } from '../stores/loginStatusStore';
|
|
||||||
import { push } from "svelte-spa-router";
|
|
||||||
|
|
||||||
let dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
// i fucking hate these vars, the will have to go
|
|
||||||
export let pages;
|
|
||||||
export let currentPage;
|
|
||||||
export let userId;
|
|
||||||
|
|
||||||
// This shit is so redundant...
|
|
||||||
let types = ['home', 'account']
|
|
||||||
export let currentType = 'account';
|
|
||||||
|
|
||||||
// this is also stupid...
|
|
||||||
let sidebar = true;
|
|
||||||
|
|
||||||
|
|
||||||
// would i prefer to forward this?
|
|
||||||
let clickedHome = () => {
|
|
||||||
console.log('clicked home');
|
|
||||||
// do something...
|
|
||||||
currentType = 'home';
|
|
||||||
};
|
|
||||||
|
|
||||||
let clickedLogout = async() => {
|
|
||||||
console.log('clicked logout');
|
|
||||||
await fetch('http://transcendance:8080/api/v2/auth/logout',);
|
|
||||||
// $loginStatus = false;
|
|
||||||
// maybe use replace() ?
|
|
||||||
push('/');
|
|
||||||
};
|
|
||||||
|
|
||||||
// All the variables that will eventually be replaced by the real values
|
|
||||||
|
|
||||||
let username = 'Username';
|
|
||||||
let games = { total: 7, won: 4, lost: 3};
|
|
||||||
let rank = 'gold or whatever the fuck who cares...';
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- remove ={clickedHome} if you want to forward the event to App.svelte-->
|
|
||||||
<!-- god this is some gross code... -->
|
|
||||||
<Header on:clickedHome={clickedHome} currentType="{currentType === 'home' ? 'home' : 'regular'}" on:clickedLogout={clickedLogout}/>
|
|
||||||
|
|
||||||
<!-- The Wave -->
|
|
||||||
<!-- <div class="spacer layer1"></div> -->
|
|
||||||
|
|
||||||
<!-- this is the thing that will let me offset -->
|
|
||||||
<div class='{sidebar ? "main-grid" : "none"}'>
|
|
||||||
{#if sidebar}
|
|
||||||
<section class="sidebar">
|
|
||||||
<p>i am a sidebar</p>
|
|
||||||
</section>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<main class:offset={sidebar}>
|
|
||||||
<!-- what the fuck do we even want in here? messges, about the user, STATISTICS!!! -->
|
|
||||||
<!-- <div>some stuff goes here</div> -->
|
|
||||||
<img class="icon" src="img/default_user_icon.png" alt="default user icon">
|
|
||||||
<div>{username}</div>
|
|
||||||
<div>Rank: {rank}</div>
|
|
||||||
<section class="main-stats">
|
|
||||||
<h4>Match Statistics</h4>
|
|
||||||
<p>Total: {games.total}</p>
|
|
||||||
<p>Victories: {games.won}</p>
|
|
||||||
<p>Losses: {games.lost}</p>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Footer />
|
|
||||||
|
|
||||||
|
|
||||||
<style>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* from Haikei */
|
|
||||||
/* for any Haikei image */
|
|
||||||
.spacer{
|
|
||||||
aspect-ratio: 900/300;
|
|
||||||
width: 100%;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: center;
|
|
||||||
background-size: cover;
|
|
||||||
}
|
|
||||||
/* the specific image we use, you need both classes */
|
|
||||||
.layer1{
|
|
||||||
background-image: url('/img/wave-haikei.svg');
|
|
||||||
}
|
|
||||||
|
|
||||||
div.main-grid{
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(12, 1fr);
|
|
||||||
/* max-height: calc(100vh - 30vh); */
|
|
||||||
height: 85vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.sidebar{
|
|
||||||
grid-column: 1 / span 2;
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The main part */
|
|
||||||
main{
|
|
||||||
max-width: 960px;
|
|
||||||
margin: 40px auto;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
main.offset{
|
|
||||||
grid-column: 3 / span 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Normal CSS stuff */
|
|
||||||
.icon{
|
|
||||||
max-width: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The variable rich section */
|
|
||||||
section.main-stats{
|
|
||||||
max-width: 600px;
|
|
||||||
margin: 40px auto;
|
|
||||||
text-align: center;
|
|
||||||
/* i think i want to use a grid? */
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(3, 1fr);
|
|
||||||
/* not sure about this, maybe top should be larger? */
|
|
||||||
grid-template-rows: repeat(3, 1fr);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* the stuff in the grid*/
|
|
||||||
section.main-stats h4{
|
|
||||||
grid-column: 1 / span 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
</style>
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { writable } from "svelte/store";
|
|
||||||
|
|
||||||
// ok yea this doesn't make a lot of sense, what am i trying to do?
|
|
||||||
// have an array of objects that are all the users?
|
|
||||||
// or an object that is the one user?
|
|
||||||
// For now as a placeholder i'll have one user in one obj
|
|
||||||
|
|
||||||
// should it not be a const? yea seems like it
|
|
||||||
// export const users = writable(
|
|
||||||
const UserStore = writable(
|
|
||||||
[{
|
|
||||||
// this is an example user
|
|
||||||
id: 1,
|
|
||||||
username: 'chaboi',
|
|
||||||
email: 'nope@fu.com',
|
|
||||||
// surely there's a better way to do this!
|
|
||||||
password: '1234',
|
|
||||||
// maybe an object listing friends' usernames?
|
|
||||||
friends: 0,
|
|
||||||
loggedIn: false,
|
|
||||||
// i imagine the user uploading their Avatare and it being put somehwere in the DB which could be referenced like a URL
|
|
||||||
// so if this field is empty then use the default avatar
|
|
||||||
avatar: '',
|
|
||||||
// online, offline, gaming
|
|
||||||
status: 'offline',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 2,
|
|
||||||
username: 'itsame',
|
|
||||||
email: 'mario@nintendo.com',
|
|
||||||
// surely there's a better way to do this!
|
|
||||||
password: '1234',
|
|
||||||
// maybe an object listing friends' usernames?
|
|
||||||
friends: 0,
|
|
||||||
loggedIn: false,
|
|
||||||
// i imagine the user uploading their Avatare and it being put somehwere in the DB which could be referenced like a URL
|
|
||||||
// so if this field is empty then use the default avatar
|
|
||||||
avatar: '',
|
|
||||||
// online, offline, gaming
|
|
||||||
status: 'offline',
|
|
||||||
}]
|
|
||||||
);
|
|
||||||
|
|
||||||
export default UserStore;
|
|
||||||
// export default users;
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
import { writable } from "svelte/store";
|
|
||||||
|
|
||||||
// an alternative way of doing things where i have a svelte store connected to localStorage
|
|
||||||
|
|
||||||
// do in need to adapt this to work with 2fa?
|
|
||||||
|
|
||||||
let _user = localStorage.getItem('42User');
|
|
||||||
|
|
||||||
// turns out a simple store is actually the easiest :)
|
|
||||||
// export const userStore = writable(_user ? JSON.parse(_user) : null); // we start with no user, but go get one if one exists
|
|
||||||
// export const userStore = writable(null);
|
|
||||||
|
|
||||||
// ok so this will happen no matter what, basically we are telling it what to do if the store containing the user changes
|
|
||||||
userStore.subscribe((value) => {
|
|
||||||
if (value)
|
|
||||||
localStorage.setItem('42User', JSON.stringify(value));
|
|
||||||
else
|
|
||||||
localStorage.removeItem('42User'); // for logout
|
|
||||||
});
|
|
||||||
|
|
||||||
export const userLogout = () => userStore.set(null);
|
|
||||||
|
|
||||||
|
|
||||||
// export const tmpStore = userStore
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
import { writable } from "svelte/store";
|
|
||||||
|
|
||||||
// This is a "Custom Store" see that chapter in the Svelte Tutorial, should be fine
|
|
||||||
// NVM this is definitely overkill
|
|
||||||
// function createLogin() {
|
|
||||||
// const { subscribe, update } = writable(false);
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// subscribe,
|
|
||||||
// login: () => update(s => s = true),
|
|
||||||
// logout: () => update(s => s = false),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// export const loginStatus = createLogin();
|
|
||||||
|
|
||||||
// export const loginStatus = writable({
|
|
||||||
// 42: false,
|
|
||||||
// tfa: false,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// function createLoginStatus() {
|
|
||||||
|
|
||||||
// //ok it really hated all this
|
|
||||||
|
|
||||||
// // const store = writable({
|
|
||||||
// // fortyTwo: false,
|
|
||||||
// // tfa: false,
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // return {
|
|
||||||
// // ...store,
|
|
||||||
// // subscribe,
|
|
||||||
// // // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
|
||||||
// // toggle42: () => store.update( fortyTwo => !fortyTwo ),
|
|
||||||
// // // toggleTFA: () => update( l => l.tfa = !l.tfa ),
|
|
||||||
// // toggleTFA: () => store.update( tfa => !tfa ),
|
|
||||||
// // isLogged: () => store.fortyTwo && store.tfa,
|
|
||||||
// // // isLogged: this.fortyTwo && this.tfa,
|
|
||||||
// // // it really doesn't like "this."
|
|
||||||
// // // isLogged: () => (this.tfa && this.fortyTwo),
|
|
||||||
// // // this. ? or (l) => l.tfa ... ?
|
|
||||||
// // }
|
|
||||||
|
|
||||||
|
|
||||||
// // doesn't seem to work...
|
|
||||||
// const { subscribe, update } = writable({
|
|
||||||
// fortyTwo: false,
|
|
||||||
// tfa: false,
|
|
||||||
// });
|
|
||||||
|
|
||||||
// return {
|
|
||||||
// subscribe,
|
|
||||||
// // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
|
||||||
// toggle42: () => update( fortyTwo => !fortyTwo ),
|
|
||||||
// // toggleTFA: () => update( l => l.tfa = !l.tfa ),
|
|
||||||
// toggleTFA: () => update( tfa => !tfa ),
|
|
||||||
// // isLogged: () => fortyTwo && tfa,
|
|
||||||
// // isLogged: this.fortyTwo && this.tfa,
|
|
||||||
// // it really doesn't like "this."
|
|
||||||
// // isLogged: () => (this.tfa && this.fortyTwo),
|
|
||||||
// // this. ? or (l) => l.tfa ... ?
|
|
||||||
// isLogged() {
|
|
||||||
// return fortyTwo && tfa;
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // possible other way of doing this
|
|
||||||
|
|
||||||
// // const store = writable({
|
|
||||||
// // fortyTwo: false,
|
|
||||||
// // tfa: false,
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// // return {
|
|
||||||
// // ...store,
|
|
||||||
// // subscribe,
|
|
||||||
// // // toggle42: () => update( l => l.fortyTwo = !l.fortyTwo ),
|
|
||||||
// // toggle42: () => store.update( l.fortyTwo => !l.fortyTwo ),
|
|
||||||
// // toggleTFA: () => store.update( l => l.tfa = !l.tfa ),
|
|
||||||
// // isLogged: store.fortyTwo && store.tfa,
|
|
||||||
// // // isLogged: () => (this.tfa && this.fortyTwo),
|
|
||||||
// // // this. ? or (l) => l.tfa ... ?
|
|
||||||
// // }
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
function createLoginStatus() {
|
|
||||||
const { subscribe, update } = writable({
|
|
||||||
fortyTwo: false,
|
|
||||||
tfa: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
function toggle42() {
|
|
||||||
update( (old) => ({...old, fortyTwo: !old.fortyTwo}) );
|
|
||||||
};
|
|
||||||
|
|
||||||
function toggleTFA() {
|
|
||||||
// update( () => {
|
|
||||||
// self.tfa = !self.tfa;
|
|
||||||
// return self;
|
|
||||||
// })
|
|
||||||
// console.log("testing");
|
|
||||||
update( (old) => ({...old, tfa: !old.tfa}) );
|
|
||||||
};
|
|
||||||
|
|
||||||
function isLogged() {
|
|
||||||
// return (l) => {l.fortyTwo && l.tfa};
|
|
||||||
// return self.fortyTwo && self.tfa;
|
|
||||||
// return fortyTwo && tfa;
|
|
||||||
};
|
|
||||||
|
|
||||||
return { subscribe, update, toggle42, toggleTFA, isLogged };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const loginStatus = createLoginStatus();
|
|
||||||
|
|
||||||
// OK let's try a totally new approach
|
|
||||||
|
|
||||||
// const _loginStatus = writable({
|
|
||||||
// fortyTwo: false,
|
|
||||||
// tfa: false,
|
|
||||||
// })
|
|
||||||
|
|
||||||
// export const loginStatus = {
|
|
||||||
// subscribe: _loginStatus.subscribe,
|
|
||||||
// set: _loginStatus.set,
|
|
||||||
// update: _loginStatus.update,
|
|
||||||
// toggle42: () =>
|
|
||||||
// }
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
@font-face {
|
|
||||||
font-family: 'Monocode-Regular-Demo';
|
|
||||||
src:url('/fonts/Monocode-Regular-Demo.ttf.woff') format('woff'),
|
|
||||||
url('Monocode-Regular-Demo.ttf.svg#Monocode-Regular-Demo') format('svg'),
|
|
||||||
url('Monocode-Regular-Demo.ttf.eot'),
|
|
||||||
url('Monocode-Regular-Demo.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Air-Conditioner';
|
|
||||||
src:url('/fonts/Air-Conditioner.ttf.woff') format('woff'),
|
|
||||||
url('Air-Conditioner.ttf.svg#Air-Conditioner') format('svg'),
|
|
||||||
url('Air-Conditioner.ttf.eot'),
|
|
||||||
url('Air-Conditioner.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: '1968-Odyssey-3D';
|
|
||||||
src:url('/fonts/1968-Odyssey-3D.ttf.woff') format('woff'),
|
|
||||||
url('1968-Odyssey-3D.ttf.svg#1968-Odyssey-3D') format('svg'),
|
|
||||||
url('1968-Odyssey-3D.ttf.eot'),
|
|
||||||
url('1968-Odyssey-3D.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: '1968-Odyssey-Gradient';
|
|
||||||
src:url('/fonts/1968-Odyssey-Gradient.ttf.woff') format('woff'),
|
|
||||||
url('1968-Odyssey-Gradient.ttf.svg#1968-Odyssey-Gradient') format('svg'),
|
|
||||||
url('1968-Odyssey-Gradient.ttf.eot'),
|
|
||||||
url('1968-Odyssey-Gradient.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'AddFatMan';
|
|
||||||
src:url('/fonts/AddFatMan.ttf.woff') format('woff'),
|
|
||||||
url('/fonts/AddFatMan.ttf.svg#AddFatMan') format('svg'),
|
|
||||||
url('/fonts/AddFatMan.ttf.eot'),
|
|
||||||
url('/fonts/AddFatMan.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: 'Bondi';
|
|
||||||
src:url('/fonts/Bondi.ttf.woff') format('woff'),
|
|
||||||
url('/fonts/Bondi.ttf.svg#Bondi') format('svg'),
|
|
||||||
url('/fonts/Bondi.ttf.eot'),
|
|
||||||
url('/fonts/Bondi.ttf.eot?#iefix') format('embedded-opentype');
|
|
||||||
font-weight: normal;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
@@ -1,177 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import NotFound from "../src/pages/NotFound.svelte";
|
|
||||||
import ProfilePage from "../src/pages/profile/ProfilePage.svelte";
|
|
||||||
import SplashPage from "../src/pages/SplashPage.svelte";
|
|
||||||
import TwoFactorAuthentication from '../src/pages/TwoFactorAuthentication.svelte';
|
|
||||||
import UnauthorizedAccessPage from '../src/pages/UnauthorizedAccessPage.svelte';
|
|
||||||
import { wrap } from 'svelte-spa-router/wrap'
|
|
||||||
import { get } from 'svelte/store';
|
|
||||||
|
|
||||||
import TestPage from '../src/pages/TmpTestPage.svelte';
|
|
||||||
import { userStore, userLogout } from "../src/stores/loginStatusStore";
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// "/article/:title": Article, // this is how you would do parameters!
|
|
||||||
// "/": LoginPage,
|
|
||||||
|
|
||||||
// TMP not using this cuz need to work out how to authentical both 42 and 2FA from the backend
|
|
||||||
|
|
||||||
// export const primaryRoutes = {
|
|
||||||
// '/': SplashPage,
|
|
||||||
// // '/2fa': TwoFactorAuthentication,
|
|
||||||
// '/2fa': wrap({
|
|
||||||
// component: TwoFactorAuthentication,
|
|
||||||
// conditions: [
|
|
||||||
// (detail) => {
|
|
||||||
// // let loggedIn;
|
|
||||||
// // loginStatus.subscribe(value => {
|
|
||||||
// // loggedIn = value;
|
|
||||||
// // });
|
|
||||||
|
|
||||||
// const { fortyTwo, tfa } = get(loginStatus);
|
|
||||||
|
|
||||||
// console.log('condition in /2fa');
|
|
||||||
// // return (loginStatus.fortyTwo && loginStatus.tfa);
|
|
||||||
// // console.log($loginStatus.fortyTwo)
|
|
||||||
// console.log(fortyTwo);
|
|
||||||
// console.log(tfa);
|
|
||||||
// return true;
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }),
|
|
||||||
// '/profile': wrap({
|
|
||||||
// component: ProfilePage,
|
|
||||||
// conditions: [
|
|
||||||
// (detail) => {
|
|
||||||
// const { fortyTwo, tfa } = get(loginStatus);
|
|
||||||
// // console.log(fortyTwo);
|
|
||||||
// // console.log(tfa);
|
|
||||||
// // return true;
|
|
||||||
// return (fortyTwo && tfa);
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }),
|
|
||||||
// '/profile/*': wrap({
|
|
||||||
// component: ProfilePage,
|
|
||||||
// conditions: [
|
|
||||||
// (detail) => {
|
|
||||||
// const { fortyTwo, tfa } = get(loginStatus);
|
|
||||||
// // console.log(fortyTwo);
|
|
||||||
// // console.log(tfa);
|
|
||||||
// // return true;
|
|
||||||
// return (fortyTwo && tfa);
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }),
|
|
||||||
// '/profile': wrap({
|
|
||||||
// // Use a dynamically-loaded component for this
|
|
||||||
// asyncComponent: () => import('./ProfilePage.svelte'),
|
|
||||||
// // Adding one pre-condition that's an async function
|
|
||||||
// conditions: [
|
|
||||||
// async (detail) => {
|
|
||||||
// // Make a network request, which are async operations
|
|
||||||
// const response = await fetch('http://transcendance:8080/api/v2/user')
|
|
||||||
// const data = await response.json()
|
|
||||||
// // Return true to continue loading the component, or false otherwise
|
|
||||||
// if (data.isAdmin) {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// else {
|
|
||||||
// return false
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }),
|
|
||||||
// '/unauthorized-access': UnauthorizedAccessPage,
|
|
||||||
// '*': NotFound
|
|
||||||
// };
|
|
||||||
|
|
||||||
export const primaryRoutes = {
|
|
||||||
"/": SplashPage,
|
|
||||||
'/test': wrap({
|
|
||||||
component: TestPage,
|
|
||||||
conditions: [
|
|
||||||
(detail) => {
|
|
||||||
// const user = get(userStore); // seems like get(store) is not an option
|
|
||||||
// // const user = userStore;
|
|
||||||
// // console.log(fortyTwo);
|
|
||||||
// // console.log(tfa);
|
|
||||||
// console.log('in /test what is in user')
|
|
||||||
// console.log(user)
|
|
||||||
|
|
||||||
// you moron $userStore is a Svelte Abreviation, this is .JS, duh
|
|
||||||
// let user = $userStore;
|
|
||||||
let user;
|
|
||||||
const unsub = userStore.subscribe(value => {
|
|
||||||
user = value;
|
|
||||||
});
|
|
||||||
console.log('in /test what is in userStore directly')
|
|
||||||
console.log(user)
|
|
||||||
|
|
||||||
// return true;
|
|
||||||
// obvi this doesn't work cuz skips to true after no user...
|
|
||||||
// you gotta make the condition the true and the everything else false
|
|
||||||
// if (user && user.statusCode && user.statusCode === 403)
|
|
||||||
// if (user !== null) {
|
|
||||||
if (user && user.username) {
|
|
||||||
unsub();
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
unsub();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}),
|
|
||||||
// '/test': wrap({
|
|
||||||
// component: TestPage,
|
|
||||||
// conditions: [
|
|
||||||
// async(detail) => {
|
|
||||||
// // THIS SHIT TOTALLY WORKS
|
|
||||||
// const user = await fetch('http://transcendance:8080/api/v2/user')
|
|
||||||
// .then((resp) => resp.json())
|
|
||||||
|
|
||||||
// console.log('in /test what is in user')
|
|
||||||
// console.log(user)
|
|
||||||
|
|
||||||
// if (user && user.username)
|
|
||||||
// return true;
|
|
||||||
// else
|
|
||||||
// return false;
|
|
||||||
// }
|
|
||||||
// ]
|
|
||||||
// }),
|
|
||||||
'/2fa': TwoFactorAuthentication,
|
|
||||||
"/profile": ProfilePage,
|
|
||||||
"/profile/*": ProfilePage,
|
|
||||||
'/unauthorized-access': UnauthorizedAccessPage,
|
|
||||||
"*": NotFound
|
|
||||||
};
|
|
||||||
|
|
||||||
// export const primaryRoutes = {
|
|
||||||
// "/": SplashPage,
|
|
||||||
// "/profile": ProfilePage,
|
|
||||||
// "/game": GamePage,
|
|
||||||
// "/chat": ChatPage,
|
|
||||||
// "*": NotFound
|
|
||||||
// };
|
|
||||||
|
|
||||||
|
|
||||||
// i might need to add /profile/* and such to make the nested routers work
|
|
||||||
|
|
||||||
// ok maybe these need to be in their own files?
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// export const gameRoutes = {
|
|
||||||
// "/": GamePage,
|
|
||||||
// "*": NotFound
|
|
||||||
// };
|
|
||||||
|
|
||||||
// export const chatRoutes = {
|
|
||||||
// "/": ChatPage,
|
|
||||||
// "*": NotFound
|
|
||||||
// };
|
|
||||||
|
|
||||||
</script>
|
|
||||||
1308
srcs/requirements/svelte/api_front/package-lock.json
generated
1308
srcs/requirements/svelte/api_front/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -13,6 +13,7 @@
|
|||||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||||
"@rollup/plugin-typescript": "^8.0.0",
|
"@rollup/plugin-typescript": "^8.0.0",
|
||||||
"@tsconfig/svelte": "^2.0.0",
|
"@tsconfig/svelte": "^2.0.0",
|
||||||
|
"@types/node": "^18.11.18",
|
||||||
"rollup": "^2.3.4",
|
"rollup": "^2.3.4",
|
||||||
"rollup-plugin-css-only": "^3.1.0",
|
"rollup-plugin-css-only": "^3.1.0",
|
||||||
"rollup-plugin-livereload": "^2.0.0",
|
"rollup-plugin-livereload": "^2.0.0",
|
||||||
@@ -25,7 +26,10 @@
|
|||||||
"typescript": "^4.0.0"
|
"typescript": "^4.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@rollup/plugin-replace": "^5.0.2",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
"sirv-cli": "^2.0.0",
|
"sirv-cli": "^2.0.0",
|
||||||
|
"socket.io-client": "^4.5.4",
|
||||||
"svelte-spa-router": "^3.3.0"
|
"svelte-spa-router": "^3.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
23467
srcs/requirements/svelte/api_front/public/build/bundle.js
Normal file
23467
srcs/requirements/svelte/api_front/public/build/bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
BIN
srcs/requirements/svelte/api_front/public/favicon.ico
Normal file
BIN
srcs/requirements/svelte/api_front/public/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.1 KiB |
@@ -1,16 +0,0 @@
|
|||||||
|
|
||||||
import * as c from "./constants.js"
|
|
||||||
|
|
||||||
export const soundPongArr: HTMLAudioElement[] = [];
|
|
||||||
export const soundRoblox = new Audio("http://localhost:8080/sound/roblox-oof.ogg");
|
|
||||||
|
|
||||||
export function initAudio(muteFlag: boolean)
|
|
||||||
{
|
|
||||||
for (let i = 0; i <= 32; i++) {
|
|
||||||
soundPongArr.push(new Audio("http://localhost:8080/sound/pong/"+i+".ogg"));
|
|
||||||
soundPongArr[i].volume = c.soundPongVolume;
|
|
||||||
soundPongArr[i].muted = muteFlag;
|
|
||||||
}
|
|
||||||
soundRoblox.volume = c.soundRobloxVolume;
|
|
||||||
soundRoblox.muted = muteFlag;
|
|
||||||
}
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
|
|
||||||
import * as en from "../enums.js"
|
|
||||||
|
|
||||||
/* From Server */
|
|
||||||
class ServerEvent {
|
|
||||||
type: en.EventTypes;
|
|
||||||
constructor(type: en.EventTypes = 0) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventAssignId extends ServerEvent {
|
|
||||||
id: string;
|
|
||||||
constructor(id: string) {
|
|
||||||
super(en.EventTypes.assignId);
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventMatchmakingComplete extends ServerEvent {
|
|
||||||
side: en.PlayerSide;
|
|
||||||
constructor(side: en.PlayerSide) {
|
|
||||||
super(en.EventTypes.matchmakingComplete);
|
|
||||||
this.side = side;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventGameUpdate extends ServerEvent {
|
|
||||||
playerLeft = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
playerRight = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
ballsArr: {
|
|
||||||
x: number,
|
|
||||||
y: number,
|
|
||||||
dirX: number,
|
|
||||||
dirY: number,
|
|
||||||
speed: number
|
|
||||||
}[] = [];
|
|
||||||
wallTop? = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
wallBottom? = {
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
lastInputId = 0;
|
|
||||||
constructor() { // TODO: constructor that take GameComponentsServer maybe ?
|
|
||||||
super(en.EventTypes.gameUpdate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventScoreUpdate extends ServerEvent {
|
|
||||||
scoreLeft: number;
|
|
||||||
scoreRight: number;
|
|
||||||
constructor(scoreLeft: number, scoreRight: number) {
|
|
||||||
super(en.EventTypes.scoreUpdate);
|
|
||||||
this.scoreLeft = scoreLeft;
|
|
||||||
this.scoreRight = scoreRight;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventMatchEnd extends ServerEvent {
|
|
||||||
winner: en.PlayerSide;
|
|
||||||
constructor(winner: en.PlayerSide) {
|
|
||||||
super(en.EventTypes.matchEnd);
|
|
||||||
this.winner = winner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* From Client */
|
|
||||||
class ClientEvent {
|
|
||||||
type: en.EventTypes; // readonly ?
|
|
||||||
constructor(type: en.EventTypes = 0) {
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ClientAnnounce extends ClientEvent {
|
|
||||||
role: en.ClientRole;
|
|
||||||
clientId: string;
|
|
||||||
matchOptions: en.MatchOptions;
|
|
||||||
constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") {
|
|
||||||
super(en.EventTypes.clientAnnounce);
|
|
||||||
this.role = role;
|
|
||||||
this.clientId = clientId;
|
|
||||||
this.matchOptions = matchOptions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class EventInput extends ClientEvent {
|
|
||||||
input: en.InputEnum;
|
|
||||||
id: number;
|
|
||||||
constructor(input: en.InputEnum = en.InputEnum.noInput, id: number = 0) {
|
|
||||||
super(en.EventTypes.clientInput);
|
|
||||||
this.input = input;
|
|
||||||
this.id = id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
ServerEvent, EventAssignId, EventMatchmakingComplete,
|
|
||||||
EventGameUpdate, EventScoreUpdate, EventMatchEnd,
|
|
||||||
ClientEvent, ClientAnnounce, EventInput
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
import * as c from "./constants.js";
|
|
||||||
import * as en from "../shared_js/enums.js"
|
|
||||||
import { gc, matchOptions, clientInfo } from "./global.js";
|
|
||||||
import { wallsMovements } from "../shared_js/wallsMovement.js";
|
|
||||||
|
|
||||||
let actual_time: number = Date.now();
|
|
||||||
let last_time: number;
|
|
||||||
let delta_time: number;
|
|
||||||
|
|
||||||
function gameLoop()
|
|
||||||
{
|
|
||||||
/* last_time = actual_time;
|
|
||||||
actual_time = Date.now();
|
|
||||||
delta_time = (actual_time - last_time) / 1000; */
|
|
||||||
|
|
||||||
delta_time = c.fixedDeltaTime;
|
|
||||||
// console.log(`delta_gameLoop: ${delta_time}`);
|
|
||||||
|
|
||||||
// interpolation
|
|
||||||
// console.log(`dir.y: ${clientInfo.opponent.dir.y}, pos.y: ${clientInfo.opponent.pos.y}, opponentNextPos.y: ${clientInfo.opponentNextPos.y}`);
|
|
||||||
if (clientInfo.opponent.dir.y != 0 ) {
|
|
||||||
opponentInterpolation(delta_time);
|
|
||||||
}
|
|
||||||
|
|
||||||
// client prediction
|
|
||||||
gc.ballsArr.forEach((ball) => {
|
|
||||||
ball.moveAndBounce(delta_time, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (matchOptions & en.MatchOptions.movingWalls) {
|
|
||||||
wallsMovements(delta_time, gc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function opponentInterpolation(delta: number)
|
|
||||||
{
|
|
||||||
// interpolation
|
|
||||||
clientInfo.opponent.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]);
|
|
||||||
|
|
||||||
if ((clientInfo.opponent.dir.y > 0 && clientInfo.opponent.pos.y > clientInfo.opponentNextPos.y)
|
|
||||||
|| (clientInfo.opponent.dir.y < 0 && clientInfo.opponent.pos.y < clientInfo.opponentNextPos.y))
|
|
||||||
{
|
|
||||||
clientInfo.opponent.dir.y = 0;
|
|
||||||
clientInfo.opponent.pos.y = clientInfo.opponentNextPos.y;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export {gameLoop}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
|
|
||||||
export {pong, gc, matchOptions} from "./pong.js"
|
|
||||||
export {socket, clientInfo} from "./ws.js"
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
|
|
||||||
initDom();
|
|
||||||
function initDom() {
|
|
||||||
document.getElementById("play_pong_button").addEventListener("click", init);
|
|
||||||
}
|
|
||||||
|
|
||||||
import * as c from "./constants.js"
|
|
||||||
import * as en from "../shared_js/enums.js"
|
|
||||||
import { GameArea } from "./class/GameArea.js";
|
|
||||||
import { GameComponentsClient } from "./class/GameComponentsClient.js";
|
|
||||||
import { handleInput } from "./handleInput.js";
|
|
||||||
// import { sendLoop } from "./handleInput.js";
|
|
||||||
import { gameLoop } from "./gameLoop.js"
|
|
||||||
import { drawLoop } from "./draw.js";
|
|
||||||
import { countdown } from "./utils.js";
|
|
||||||
import { initWebSocket } from "./ws.js";
|
|
||||||
import { initAudio } from "./audio.js";
|
|
||||||
|
|
||||||
|
|
||||||
/* Keys
|
|
||||||
Racket: W/S OR Up/Down
|
|
||||||
Grid On-Off: G
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */
|
|
||||||
export let pong: GameArea;
|
|
||||||
export let gc: GameComponentsClient;
|
|
||||||
export let matchOptions: en.MatchOptions = en.MatchOptions.noOption;
|
|
||||||
|
|
||||||
function init()
|
|
||||||
{
|
|
||||||
console.log("multi_balls:"+(<HTMLInputElement>document.getElementById("multi_balls")).checked);
|
|
||||||
console.log("moving_walls:"+(<HTMLInputElement>document.getElementById("moving_walls")).checked);
|
|
||||||
console.log("sound_on:"+(<HTMLInputElement>document.getElementById("sound_on")).checked);
|
|
||||||
|
|
||||||
let soundMutedFlag = false;
|
|
||||||
if ( (<HTMLInputElement>document.getElementById("sound_off")).checked ) {
|
|
||||||
soundMutedFlag = true;
|
|
||||||
}
|
|
||||||
initAudio(soundMutedFlag);
|
|
||||||
|
|
||||||
if ( (<HTMLInputElement>document.getElementById("multi_balls")).checked ) {
|
|
||||||
matchOptions |= en.MatchOptions.multiBalls;
|
|
||||||
}
|
|
||||||
if ( (<HTMLInputElement>document.getElementById("moving_walls")).checked ) {
|
|
||||||
matchOptions |= en.MatchOptions.movingWalls;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById("div_game_options").hidden = true;
|
|
||||||
|
|
||||||
pong = new GameArea();
|
|
||||||
gc = new GameComponentsClient(matchOptions, pong.ctx);
|
|
||||||
initWebSocket(matchOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchmaking()
|
|
||||||
{
|
|
||||||
console.log("Searching an opponent...");
|
|
||||||
gc.text1.clear();
|
|
||||||
gc.text1.pos.assign(c.w/5, c.h_mid);
|
|
||||||
gc.text1.text = "Searching...";
|
|
||||||
gc.text1.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function matchmakingComplete()
|
|
||||||
{
|
|
||||||
console.log("Match Found !");
|
|
||||||
gc.text1.clear();
|
|
||||||
gc.text1.pos.assign(c.w/8, c.h_mid);
|
|
||||||
gc.text1.text = "Match Found !";
|
|
||||||
gc.text1.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
function startGame() {
|
|
||||||
gc.text1.pos.assign(c.w_mid, c.h_mid+c.h/4);
|
|
||||||
countdown(c.matchStartDelay/1000, (count: number) => {
|
|
||||||
gc.text1.clear();
|
|
||||||
gc.text1.text = `${count}`;
|
|
||||||
gc.text1.update();
|
|
||||||
}, resumeGame);
|
|
||||||
}
|
|
||||||
|
|
||||||
function resumeGame()
|
|
||||||
{
|
|
||||||
gc.text1.text = "";
|
|
||||||
window.addEventListener('keydown', function (e) {
|
|
||||||
pong.addKey(e.key);
|
|
||||||
});
|
|
||||||
window.addEventListener('keyup', function (e) {
|
|
||||||
pong.deleteKey(e.key);
|
|
||||||
});
|
|
||||||
pong.handleInputInterval = window.setInterval(handleInput, c.handleInputIntervalMS);
|
|
||||||
// pong.handleInputInterval = window.setInterval(sendLoop, c.sendLoopIntervalMS);
|
|
||||||
pong.gameLoopInterval = window.setInterval(gameLoop, c.gameLoopIntervalMS);
|
|
||||||
pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
export {matchmaking, matchmakingComplete, startGame}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
|
|
||||||
import { MovingRectangle } from "./class/Rectangle.js";
|
|
||||||
|
|
||||||
function random(min: number = 0, max: number = 1) {
|
|
||||||
return Math.random() * (max - min) + min;
|
|
||||||
}
|
|
||||||
|
|
||||||
function sleep (ms: number) {
|
|
||||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
||||||
}
|
|
||||||
|
|
||||||
function clamp(n: number, min: number, max: number) : number
|
|
||||||
{
|
|
||||||
if (n < min)
|
|
||||||
n = min;
|
|
||||||
else if (n > max)
|
|
||||||
n = max;
|
|
||||||
return (n);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typescript hack, unused
|
|
||||||
function assertMovingRectangle(value: unknown): asserts value is MovingRectangle {
|
|
||||||
// if (value !== MovingRectangle) throw new Error("Not a MovingRectangle");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
export {random, sleep, clamp, assertMovingRectangle}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
|
||||||
html, body {
|
html, body {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -4,9 +4,9 @@
|
|||||||
<meta charset='utf-8'>
|
<meta charset='utf-8'>
|
||||||
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
<meta name='viewport' content='width=device-width,initial-scale=1'>
|
||||||
|
|
||||||
<title>Svelte app</title>
|
<title>Potato Pong</title>
|
||||||
|
|
||||||
<link rel='icon' type='image/png' href='/favicon.png'>
|
<link rel='icon' type='image/x-icon' href='/favicon.ico'>
|
||||||
<link rel='stylesheet' href='/global.css'>
|
<link rel='stylesheet' href='/global.css'>
|
||||||
<link rel='stylesheet' href='/build/bundle.css'>
|
<link rel='stylesheet' href='/build/bundle.css'>
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user