mirror of
https://github.com/jcreek/EstimationPoker.git
synced 2026-07-12 18:43:47 +00:00
refactor(*): Move server into src
This commit is contained in:
@@ -9,11 +9,11 @@
|
||||
import Modal from '../../../components/Modal.svelte';
|
||||
|
||||
import { connectToWebSocket, sendMessage, generateId } from '../estimation.js';
|
||||
import { User } from '../../../../server/classes/user';
|
||||
import { Room } from '../../../../server/classes/room';
|
||||
import { ChangeEstimateMessage } from '../../../../server/classes/messages/ChangeEstimateMessage';
|
||||
import { JoinRoomMessage } from '../../../../server/classes/messages/JoinRoomMessage';
|
||||
import { SelectEstimateMessage } from '../../../../server/classes/messages/SelectEstimateMessage';
|
||||
import { User } from '../../../server/classes/user';
|
||||
import { Room } from '../../../server/classes/room';
|
||||
import { ChangeEstimateMessage } from '../../../server/classes/messages/ChangeEstimateMessage';
|
||||
import { JoinRoomMessage } from '../../../server/classes/messages/JoinRoomMessage';
|
||||
import { SelectEstimateMessage } from '../../../server/classes/messages/SelectEstimateMessage';
|
||||
|
||||
let socket;
|
||||
let showModal = true;
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
*.js
|
||||
Vendored
+19
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch via NPM",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"run",
|
||||
"dev"
|
||||
],
|
||||
"runtimeExecutable": "npm",
|
||||
"skipFiles": [
|
||||
"<node_internals>/**"
|
||||
],
|
||||
"type": "pwa-node",
|
||||
"stopOnEntry": true,
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
import { WebSocket } from 'ws';
|
||||
import { User } from './User';
|
||||
|
||||
export class Room {
|
||||
id: string;
|
||||
users: Map<WebSocket, User>;
|
||||
|
||||
constructor(id: string) {
|
||||
this.id = id;
|
||||
this.users = new Map();
|
||||
}
|
||||
|
||||
addUser(ws: WebSocket, user: User) {
|
||||
this.users.set(ws, user);
|
||||
}
|
||||
|
||||
removeUser(ws: WebSocket) {
|
||||
this.users.delete(ws);
|
||||
}
|
||||
|
||||
getUsers() {
|
||||
return Array.from(this.users.values());
|
||||
}
|
||||
|
||||
getUserByWebSocket(ws: WebSocket) {
|
||||
return this.users.get(ws);
|
||||
}
|
||||
|
||||
broadcast(message: any) {
|
||||
this.users.forEach((user, client) => {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(JSON.stringify(message));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAllEstimates() {
|
||||
const estimates = new Map<string, number>();
|
||||
this.users.forEach((user) => {
|
||||
if (user.estimate !== null) {
|
||||
estimates.set(user.name, user.estimate);
|
||||
}
|
||||
});
|
||||
return estimates;
|
||||
}
|
||||
|
||||
clearEstimates() {
|
||||
this.users.forEach((user) => {
|
||||
user.estimate = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
export class User {
|
||||
userId: string;
|
||||
name: string;
|
||||
estimate: number | null;
|
||||
|
||||
constructor(userId: string, name: string) {
|
||||
this.userId = userId;
|
||||
this.name = name;
|
||||
this.estimate = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export class ChangeEstimateMessage {
|
||||
type: string;
|
||||
roomId: string;
|
||||
userId: string;
|
||||
estimate: number;
|
||||
|
||||
constructor(roomId: string, userId: string, estimate: number) {
|
||||
this.type = 'change-estimate';
|
||||
this.roomId = roomId;
|
||||
this.userId = userId;
|
||||
this.estimate = estimate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export class JoinRoomMessage {
|
||||
type: string;
|
||||
roomId: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
|
||||
constructor(roomId: string, userId: string, name: string) {
|
||||
this.type = 'join-room';
|
||||
this.roomId = roomId;
|
||||
this.userId = userId;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export class SelectEstimateMessage {
|
||||
type: string;
|
||||
roomId: string;
|
||||
userId: string;
|
||||
estimate: number;
|
||||
|
||||
constructor(roomId: string, userId: string, estimate: number) {
|
||||
this.type = 'select-estimate';
|
||||
this.roomId = roomId;
|
||||
this.userId = userId;
|
||||
this.estimate = estimate;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "websocket-server",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "websocket.ts",
|
||||
"scripts": {
|
||||
"ts": "tsc -p .",
|
||||
"start": "npm run ts && npm run start:server",
|
||||
"start:server": "node websocket.js",
|
||||
"dev": "tsc-watch -p tsconfig.json --onSuccess \"npm run start:server\"",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"type": "module",
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@netlify/functions": "^1.6.0",
|
||||
"@types/body-parser": "^1.19.2",
|
||||
"@types/express": "^4.17.13",
|
||||
"@types/ws": "^8.5.5",
|
||||
"body-parser": "^1.20.0",
|
||||
"express": "^4.17.3",
|
||||
"tsc-watch": "^5.0.3",
|
||||
"typescript": "^4.6.3",
|
||||
"ws": "^8.12.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ES2022",
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"skipLibCheck": true
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
import express from 'express';
|
||||
import { WebSocketServer, WebSocket } from 'ws';
|
||||
import { Room } from './classes/Room.js';
|
||||
import { User } from './classes/User.js';
|
||||
import { JoinRoomMessage } from './classes/messages/JoinRoomMessage.js';
|
||||
import { ChangeEstimateMessage } from './classes/messages/ChangeEstimateMessage.js';
|
||||
import { SelectEstimateMessage } from './classes/messages/SelectEstimateMessage.js';
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
function onSocketPreError(e: Error) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
function onSocketPostError(e: Error) {
|
||||
console.log(e);
|
||||
}
|
||||
|
||||
console.log(`Attempting to run server on port ${port}`);
|
||||
|
||||
const server = app.listen(port, () => {
|
||||
console.log(`Listening on port ${port}`);
|
||||
});
|
||||
|
||||
const wss = new WebSocketServer({ noServer: true });
|
||||
const rooms = new Set<Room>();
|
||||
|
||||
server.on('upgrade', (req, socket, head) => {
|
||||
socket.on('error', onSocketPreError);
|
||||
|
||||
// perform auth
|
||||
if (!!req.headers['BadAuth']) {
|
||||
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
||||
socket.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
wss.handleUpgrade(req, socket, head, (ws) => {
|
||||
socket.removeListener('error', onSocketPreError);
|
||||
wss.emit('connection', ws, req);
|
||||
});
|
||||
});
|
||||
|
||||
wss.on('connection', (ws: WebSocket, req) => {
|
||||
let room: Room | null = null;
|
||||
|
||||
ws.on('error', onSocketPostError);
|
||||
|
||||
ws.on('message', (message) => {
|
||||
const data = JSON.parse(message.toString());
|
||||
|
||||
if (data.type === 'join-room') {
|
||||
const joinRoomMessage: JoinRoomMessage = data as JoinRoomMessage;
|
||||
const { roomId, userId, name } = joinRoomMessage;
|
||||
room = getRoomById(roomId);
|
||||
|
||||
// Create room if it doesn't already exist
|
||||
if (!room) {
|
||||
room = new Room(roomId);
|
||||
rooms.add(room);
|
||||
}
|
||||
|
||||
// Create the user in the room
|
||||
const user = new User(userId, name);
|
||||
room.addUser(ws, user);
|
||||
|
||||
broadcastToRoom(roomId, { type: 'user-joined', message: `${name} has joined the room` });
|
||||
} else if (data.type === 'select-estimate') {
|
||||
const selectEstimateMessage: SelectEstimateMessage = data as SelectEstimateMessage;
|
||||
const { userId, estimate } = selectEstimateMessage;
|
||||
|
||||
if (room) {
|
||||
const user = room.getUserByWebSocket(ws);
|
||||
if (user) {
|
||||
user.estimate = estimate;
|
||||
broadcastToRoom(room.id, { type: 'estimate-selected', userId, estimate });
|
||||
|
||||
const allEstimates = room.getAllEstimates();
|
||||
if (allEstimates.size === room.getUsers().length) {
|
||||
broadcastToRoom(room.id, { type: 'estimation-closed', message: 'Estimation closed' });
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (data.type === 'change-estimate') {
|
||||
const changeEstimateMessage: ChangeEstimateMessage = data as ChangeEstimateMessage;
|
||||
const { userId, estimate } = changeEstimateMessage;
|
||||
|
||||
if (room) {
|
||||
const user = room.getUserByWebSocket(ws);
|
||||
if (user) {
|
||||
user.estimate = estimate;
|
||||
broadcastToRoom(room.id, { type: 'estimate-changed', userId, estimate });
|
||||
}
|
||||
|
||||
const allEstimates = room.getAllEstimates();
|
||||
if (allEstimates.size === room.getUsers().length) {
|
||||
broadcastToRoom(room.id, { type: 'estimation-closed', message: 'Estimation closed' });
|
||||
}
|
||||
}
|
||||
} else if (data.type === 'restart-estimation') {
|
||||
if (room) {
|
||||
room.clearEstimates();
|
||||
broadcastToRoom(room.id, { type: 'estimation-restarted', message: 'Estimation restarted' });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ws.on('close', () => {
|
||||
if (room) {
|
||||
const user = room.getUserByWebSocket(ws);
|
||||
if (user) {
|
||||
room.removeUser(ws);
|
||||
|
||||
if (room.getUsers().length === 0) {
|
||||
rooms.delete(room);
|
||||
broadcastToRoom(room.id, {
|
||||
type: 'room-deleted',
|
||||
message: 'Room deleted'
|
||||
});
|
||||
}
|
||||
|
||||
broadcastToRoom(room.id, {
|
||||
type: 'user-left',
|
||||
message: `${user.userId} has left the room`
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function broadcastToRoom(roomId: string, message: any) {
|
||||
const room = getRoomById(roomId);
|
||||
if (room) {
|
||||
room.broadcast(message);
|
||||
}
|
||||
}
|
||||
|
||||
function getUsersInRoom(roomId: string) {
|
||||
const room = getRoomById(roomId);
|
||||
if (room) {
|
||||
return room.getUsers();
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
function getRoomById(roomId: string) {
|
||||
for (const room of rooms) {
|
||||
if (room.id === roomId) {
|
||||
return room;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
Reference in New Issue
Block a user