add some http and websocket protocol shit

This commit is contained in:
Elijah R 2024-08-09 23:05:48 -04:00
parent 3341770b6e
commit c96d18dd70
12 changed files with 272 additions and 6 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@
.pnp.*
node_modules/
**/dist/
.parcel-cache/

View file

@ -3,5 +3,9 @@
"packageManager": "yarn@4.3.1",
"devDependencies": {
"typescript": "^5.5.4"
}
},
"scripts": {
"build": "tsc"
},
"main": "dist/index.js"
}

View file

@ -1,3 +1,5 @@
export * from './board/Board.js';
export * from './board/Space.js';
export * from './board/Property.js';
export * from './protocol/protocol.js';

View file

@ -0,0 +1,34 @@
export enum ProtocolOperation {
// Bidirectional
KeepAlive = 'nop',
// Client -> Server
Login = 'login',
// Server -> Client
Init = 'init',
}
export enum GameState {
Lobby = 'lobby',
InProgress = 'inprogress',
Ended = 'ended',
}
export interface ProtocolMessage {
op: ProtocolOperation;
}
export interface LoginMessage extends ProtocolMessage {
op: ProtocolOperation.Login;
username: string;
}
export interface InitMessage extends ProtocolMessage {
op: ProtocolOperation.Init;
gameState: GameState;
username: string;
players: {
username: string;
money: number;
isBankrupt: boolean;
};
}

View file

@ -5,5 +5,12 @@
"server",
"webapp",
"common"
]
],
"scripts": {
"format": "prettier --write **/*.{ts,html,scss}",
"build": "yarn workspaces foreach -p -A run build"
},
"devDependencies": {
"prettier": "^3.3.3"
}
}

View file

@ -3,14 +3,17 @@
"packageManager": "yarn@4.3.1",
"devDependencies": {
"@types/node": "^22.1.0",
"@types/ws": "^8.5.12",
"typescript": "^5.5.4"
},
"dependencies": {
"@fastify/cors": "^9.0.1",
"@fastify/websocket": "^10.0.1",
"fastify": "^4.28.1",
"pino": "^9.3.2"
},
"scripts": {
"build": "tsc"
}
},
"type": "module"
}

45
server/src/game.ts Normal file
View file

@ -0,0 +1,45 @@
import { Player } from "./game/player.js";
import { GameState, LoginMessage, ProtocolMessage, ProtocolOperation } from "@ntptg/common";
export class Game {
private id: string;
private state: GameState;
private players: Player[];
constructor(id: string) {
this.id = id;
this.state = GameState.Lobby;
this.players = [];
}
startGame() {
this.state = GameState.InProgress;
}
returnToLobby() {
this.state = GameState.Lobby;
}
addPlayer(player: Player) {
this.players.push(player);
player.on('message', msg => this.handleMessage(player, msg));
}
private handleMessage(player: Player, msg: ProtocolMessage) {
switch (msg.op) {
case ProtocolOperation.Login: {
let loginMsg = msg as LoginMessage;
if (!loginMsg.username) return;
let username = loginMsg.username;
let i = 1;
while (this.players.some(p => p.getUsername() === username)) {
username = loginMsg.username + i++;
}
player.setUsername(username);
break;
}
}
}
}

30
server/src/game/player.ts Normal file
View file

@ -0,0 +1,30 @@
import EventEmitter from 'events';
import { WebSocket } from 'ws';
import { ProtocolMessage } from '@ntptg/common';
export interface Player {
on(event: 'message', listener: (msg: ProtocolMessage) => void): this;
}
export class Player extends EventEmitter {
private username: string | null;
private socket: WebSocket;
money: number;
isBankrupt: boolean;
constructor(socket: WebSocket) {
super();
this.username = null;
this.socket = socket;
this.money = 0;
this.isBankrupt = false;
}
getUsername() {
return this.username;
}
setUsername(username: string) {
this.username = username;
}
}

View file

@ -1,6 +1,69 @@
import fastifyWebsocket from "@fastify/websocket";
import fastify from "fastify";
import pino from "pino";
import { Game } from "./game.js";
import { randstr } from "./util.js";
import fastifyCors from "@fastify/cors";
import { Player } from "./game/player.js";
let logger = pino({
const logger = pino({
name: "NTPTG"
});
const games = new Map<string, Game>();
const app = fastify({
logger: logger.child({ module: "fastify" })
});
app.register(fastifyCors, {
origin: true,
methods: ["GET", "POST"]
});
app.register(fastifyWebsocket);
app.register(async app => {
app.route({
method: 'GET',
url: '/api/game/:gameid',
websocket: true,
preValidation: async (req, res) => {
const { gameid } = req.params as { gameid: string };
if (!games.has(gameid)) {
res.status(400).send({ error: "Game not found" });
return;
}
},
handler: async (req, res) => {
res.code(426);
return { error: "This endpoint only accepts WebSocket connections" };
},
wsHandler: async (socket, req) => {
const { gameid } = req.params as { gameid: string };
const game = games.get(gameid)!;
game.addPlayer(new Player(socket));
}
});
});
app.post('/api/create', async (req, res) => {
if (req.headers['content-type'] !== 'application/json') {
res.status(400);
return { error: "Invalid Content-Type" };
}
let gameid;
do {
gameid = await randstr(8);
} while (games.has(gameid));
games.set(gameid, new Game(gameid));
res.status(201);
return { id: gameid };
});
let port = parseInt(process.env["NTPTG_PORT"]!);
if (isNaN(port)) port = 3000;
app.listen({
host: process.env["NTPTG_HOST"] || "127.0.0.1",
port
});

20
server/src/util.ts Normal file
View file

@ -0,0 +1,20 @@
import * as crypto from 'crypto';
export function randint(min: number, max: number): Promise<number> {
return new Promise((res, rej) => crypto.randomBytes(4, (err, buf) => {
if (err) {
rej(err);
return;
}
const random = buf.readUInt32BE(0) / 0x100000000;
res(Math.floor(min + random * (max - min)));
}));
}
export async function randstr(length: number): Promise<string> {
let buf = Buffer.alloc(length);
for (let i = 0; i < length; i++) {
buf.writeUInt8(await randint(65, 91), i);
}
return buf.toString('ascii');
}

View file

@ -49,7 +49,7 @@
// "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. */
"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. */

View file

@ -45,6 +45,16 @@ __metadata:
languageName: node
linkType: hard
"@fastify/cors@npm:^9.0.1":
version: 9.0.1
resolution: "@fastify/cors@npm:9.0.1"
dependencies:
fastify-plugin: "npm:^4.0.0"
mnemonist: "npm:0.39.6"
checksum: 10c0/4db9d3d02edbca741c8ed053819bf3b235ecd70e07c640ed91ba0fc1ee2dc8abedbbffeb79ae1a38ccbf59832e414cad90a554ee44227d0811d5a2d062940611
languageName: node
linkType: hard
"@fastify/error@npm:^3.3.0, @fastify/error@npm:^3.4.0":
version: 3.4.1
resolution: "@fastify/error@npm:3.4.1"
@ -240,8 +250,10 @@ __metadata:
version: 0.0.0-use.local
resolution: "@ntptg/server@workspace:server"
dependencies:
"@fastify/cors": "npm:^9.0.1"
"@fastify/websocket": "npm:^10.0.1"
"@types/node": "npm:^22.1.0"
"@types/ws": "npm:^8.5.12"
fastify: "npm:^4.28.1"
pino: "npm:^9.3.2"
typescript: "npm:^5.5.4"
@ -1247,6 +1259,15 @@ __metadata:
languageName: node
linkType: hard
"@types/node@npm:*":
version: 22.2.0
resolution: "@types/node@npm:22.2.0"
dependencies:
undici-types: "npm:~6.13.0"
checksum: 10c0/c17900b34faecfec204f72970bd658d0c217aaf739c1bf7690c969465b6b26b77a8be1cd9ba735aadbd1dd20b5c3e4f406ec33528bf7c6eec90744886c5d5608
languageName: node
linkType: hard
"@types/node@npm:^22.1.0":
version: 22.1.0
resolution: "@types/node@npm:22.1.0"
@ -1256,6 +1277,15 @@ __metadata:
languageName: node
linkType: hard
"@types/ws@npm:^8.5.12":
version: 8.5.12
resolution: "@types/ws@npm:8.5.12"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/3fd77c9e4e05c24ce42bfc7647f7506b08c40a40fe2aea236ef6d4e96fc7cb4006a81ed1b28ec9c457e177a74a72924f4768b7b4652680b42dfd52bc380e15f9
languageName: node
linkType: hard
"abbrev@npm:^2.0.0":
version: 2.0.0
resolution: "abbrev@npm:2.0.0"
@ -2684,6 +2714,15 @@ __metadata:
languageName: node
linkType: hard
"mnemonist@npm:0.39.6":
version: 0.39.6
resolution: "mnemonist@npm:0.39.6"
dependencies:
obliterator: "npm:^2.0.1"
checksum: 10c0/a538945ea547976136ee6e16f224c0a50983143619941f6c4d2c82159e36eb6f8ee93d69d3a1267038fc5b16f88e2d43390023de10dfb145fa15c5e2befa1cdf
languageName: node
linkType: hard
"ms@npm:2.1.2":
version: 2.1.2
resolution: "ms@npm:2.1.2"
@ -2835,6 +2874,8 @@ __metadata:
"ntptg@workspace:.":
version: 0.0.0-use.local
resolution: "ntptg@workspace:."
dependencies:
prettier: "npm:^3.3.3"
languageName: unknown
linkType: soft
@ -2845,6 +2886,13 @@ __metadata:
languageName: node
linkType: hard
"obliterator@npm:^2.0.1":
version: 2.0.4
resolution: "obliterator@npm:2.0.4"
checksum: 10c0/ff2c10d4de7d62cd1d588b4d18dfc42f246c9e3a259f60d5716f7f88e5b3a3f79856b3207db96ec9a836a01d0958a21c15afa62a3f4e73a1e0b75f2c2f6bab40
languageName: node
linkType: hard
"on-exit-leak-free@npm:^2.1.0":
version: 2.1.2
resolution: "on-exit-leak-free@npm:2.1.2"
@ -3042,6 +3090,15 @@ __metadata:
languageName: node
linkType: hard
"prettier@npm:^3.3.3":
version: 3.3.3
resolution: "prettier@npm:3.3.3"
bin:
prettier: bin/prettier.cjs
checksum: 10c0/b85828b08e7505716324e4245549b9205c0cacb25342a030ba8885aba2039a115dbcf75a0b7ca3b37bc9d101ee61fab8113fc69ca3359f2a226f1ecc07ad2e26
languageName: node
linkType: hard
"proc-log@npm:^4.1.0, proc-log@npm:^4.2.0":
version: 4.2.0
resolution: "proc-log@npm:4.2.0"