Skip to content

Commit

Permalink
* add types to Socket.io Server and Socket
Browse files Browse the repository at this point in the history
  • Loading branch information
ConorMurphy21 committed Dec 27, 2023
1 parent 1f05dd8 commit 63cffa3
Show file tree
Hide file tree
Showing 12 changed files with 157 additions and 61 deletions.
6 changes: 3 additions & 3 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ const server = http.createServer(app);
* Create socket server
*/

import { Server, Socket } from 'socket.io';
const io = new Server(server, {
import { TypedServer, TypedSocket } from './types/socketServerTypes';
const io = new TypedServer(server, {
cors: {
origin: ['http://localhost:8080', 'http://localhost:5001'],
methods: ['GET', 'POST'],
Expand All @@ -60,7 +60,7 @@ const io = new Server(server, {
* Listen on socket server
*/
import { registerHandlers } from './routes/registerHandlers';
io.on('connection', (socket: Socket) => registerHandlers(io, socket));
io.on('connection', (socket: TypedSocket) => registerHandlers(io, socket));

/**
* Start room service
Expand Down
27 changes: 14 additions & 13 deletions server/src/routes/gameHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { getRoomById, Room } from '../state/rooms';
import { GameState, Responses, Stage } from '../state/gameState';
import { GameState } from '../state/gameState';
import { z } from 'zod';
import logger from '../logger/logger';
import { Server, Socket } from 'socket.io';

/*** handler validation schemas ***/
import { ApiResult, isErr, isOk, isSuccess } from '../types/result';
import { ConfigurableOptions, getConfigurableOptionsSchema } from '../state/options';
import { PollName } from '../state/pollService';
import { TypedServer, TypedSocket } from '../types/socketServerTypes';
import { Responses, Stage } from '../types/stateTypes';

const registerGameHandlers = (io: Server, socket: Socket) => {
const registerGameHandlers = (io: TypedServer, socket: TypedSocket) => {
/*** GAME STATE ENDPOINTS ***/
socket.on('setOptions', (options: ConfigurableOptions, callback?: (p: { success: boolean }) => void) => {
const room = roomIfLeader(socket.id);
Expand Down Expand Up @@ -152,7 +153,7 @@ const registerGameHandlers = (io: Server, socket: Socket) => {
const state = room.state!;
const result = state.acceptMatch(socket.id, match);
if (isSuccess(result)) {
io.to(room.name).emit('matchesFound', [{ player: socket.id, response: match }]);
io.to(room.name).emit('matchesFound', [{ player: socket.id, response: match, exact: false }]);
} else {
logger.log(result.wrap('(gameHandlers) selectMatch failed due to %1$s'));
}
Expand All @@ -174,7 +175,7 @@ const registerGameHandlers = (io: Server, socket: Socket) => {
});

// todo: change client side to accept Result type instead of this dumb ApiResult
socket.on('getResponses', (id, callback: (result: ApiResult<Responses>) => void) => {
socket.on('getResponses', (id: string, callback: (result: ApiResult<Responses>) => void) => {
const validationResult = z.object({ id: z.string(), callback: z.function() }).safeParse({ id, callback });
if (!validationResult.success) {
logger.error('(gameHandlers) getResponses attempted with invalid arguments');
Expand All @@ -197,7 +198,7 @@ const registerGameHandlers = (io: Server, socket: Socket) => {
});
};

function registerCallbacks(io: Server, room: Room) {
function registerCallbacks(io: TypedServer, room: Room) {
const state = room.state!;

state.registerStartNextPromptCb(() => {
Expand Down Expand Up @@ -226,7 +227,7 @@ function registerCallbacks(io: Server, room: Room) {
});
}

function beginPrompt(io: Server, room: Room) {
function beginPrompt(io: TypedServer, room: Room) {
const state = room.state!;
if (state.beginNewPrompt()) {
io.to(room.name).emit('beginPrompt', state.prompt);
Expand All @@ -241,7 +242,7 @@ function beginPrompt(io: Server, room: Room) {
}
}

function skipPrompt(io: Server, room: Room) {
function skipPrompt(io: TypedServer, room: Room) {
const state = room.state!;
if (state.promptTimeout) {
clearTimeout(state.promptTimeout);
Expand All @@ -250,7 +251,7 @@ function skipPrompt(io: Server, room: Room) {
beginPrompt(io, room);
}

function beginSelection(io: Server, room: Room) {
function beginSelection(io: TypedServer, room: Room) {
const state = room.state!;
if (state.beginSelection()) {
io.to(room.name).emit('nextSelection', {
Expand All @@ -262,7 +263,7 @@ function beginSelection(io: Server, room: Room) {
}
}

function continueSelection(io: Server, room: Room) {
function continueSelection(io: TypedServer, room: Room) {
const state = room.state!;
if (state.nextSelection()) {
io.to(room.name).emit('nextSelection', {
Expand All @@ -276,7 +277,7 @@ function continueSelection(io: Server, room: Room) {
}
}

function applyDisputeAction(io: Server, room: Room, action: string) {
function applyDisputeAction(io: TypedServer, room: Room, action: string) {
if (action === 'reSelect') {
io.to(room.name).emit('nextSelection', {
selector: room.state!.selectorId(),
Expand All @@ -287,7 +288,7 @@ function applyDisputeAction(io: Server, room: Room, action: string) {
}
}

function beginMatching(io: Server, room: Room) {
function beginMatching(io: TypedServer, room: Room) {
const state = room.state!;
io.to(room.name).emit('beginMatching', state.selectedResponse());
const matches = state.matches();
Expand All @@ -305,7 +306,7 @@ function roomIfLeader(id: string): Room | undefined {
return room;
}

function midgameJoin(socket: Socket, room: Room, oldId?: string) {
function midgameJoin(socket: TypedSocket, room: Room, oldId?: string) {
socket.emit('midgameConnect', room.state!.midgameConnect(socket.id, oldId));
if (room.state!.stage === Stage.Matching) {
const match = room.state!.getMatch(socket.id);
Expand Down
4 changes: 2 additions & 2 deletions server/src/routes/registerHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Server, Socket } from 'socket.io';
import { registerRoomHandlers } from './roomHandlers';
import { registerGameHandlers } from './gameHandlers';
import { TypedServer, TypedSocket } from '../types/socketServerTypes';

export function registerHandlers(io: Server, socket: Socket) {
export function registerHandlers(io: TypedServer, socket: TypedSocket) {
registerRoomHandlers(io, socket);
registerGameHandlers(io, socket);
}
17 changes: 10 additions & 7 deletions server/src/routes/roomHandlers.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import { createRoom, disconnectPlayer, getRoomById, getRoomByName, joinRoom } from '../state/rooms';
import logger from '../logger/logger';
import { midgameJoin } from './gameHandlers';
import { Server, Socket } from 'socket.io';
import { isErr } from '../types/result';
import { Stage } from '../state/gameState';
import { z } from 'zod';
import { TypedServer, TypedSocket } from '../types/socketServerTypes';
import { Stage } from '../types/stateTypes';

/*** handler validation schemas ***/
const roomSchema = z.object({
name: z.string(),
roomName: z.string(),
langs: z.array(z.string().min(2).max(5)).optional()
});
export function registerRoomHandlers(io: Server, socket: Socket): void {

export function registerRoomHandlers(io: TypedServer, socket: TypedSocket): void {
socket.onAny(() => {
// update activity
const room = getRoomById(socket.id);
Expand Down Expand Up @@ -66,7 +67,7 @@ export function registerRoomHandlers(io: Server, socket: Socket): void {
socket.emit('joinRoom', { success: true, roomName: room.name });
socket.emit('updatePlayers', { modifies: room.players, deletes: [] });
socket.to(room.name).emit('updatePlayers', {
modifies: [room.players.find((p) => p.name === name)],
modifies: [room.players.find((p) => p.name === name)!],
deletes: []
});
socket.emit('setOptions', room.state!.getOptions());
Expand All @@ -81,7 +82,7 @@ export function registerRoomHandlers(io: Server, socket: Socket): void {
});
}

function disconnect(socket: Socket): void {
function disconnect(socket: TypedSocket): void {
const roomName = getRoomById(socket.id)?.name;
disconnectPlayer(socket.id);
// remove socket from room
Expand All @@ -92,9 +93,11 @@ function disconnect(socket: Socket): void {

const room = getRoomByName(roomName);
if (room) {
const player = room.players.find((p) => p.id === socket.id);
// safe because we know this player exists
const player = room.players.find((p) => p.id === socket.id)!;
// could be modified
const leader = room.players.find((p) => p.leader);
// safe because if there was no leader then there would be no room
const leader = room.players.find((p) => p.leader)!;
socket.to(room.name).emit('updatePlayers', { modifies: [player, leader], deletes: [] });
}
}
30 changes: 2 additions & 28 deletions server/src/state/gameState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,7 @@ import logger from '../logger/logger';
import { Player as RoomPlayer, Room } from './rooms';
import { Err, Info, Ok, Result, Success, VoidResult, Warning } from '../types/result';
import { ConfigurableOptions, defaultOptions, getConfigurableOptionsSchema, Options } from './options';

export enum Stage {
Lobby,
Response,
Selection,
Matching,
EndRound
}
export enum SelectionType {
Strike = 'strike',
Sike = 'sike',
Choice = 'choice'
}
import { Match, MidgameConnectData, Responses, SelectionType, Stage } from '../types/stateTypes';

type Player = {
id: string;
Expand All @@ -31,20 +19,6 @@ type Player = {
matchingComplete: boolean; // set to true if explicitly no match was found or a match was found
};

type Match = {
player: string;
response: string;
exact: boolean;
};

export type Responses = {
id: string;
all: string[];
used: string[];
selectedStrike: string;
selectedSike: string;
};

export class GameState {
stage: Stage;
public options: Options;
Expand Down Expand Up @@ -547,7 +521,7 @@ export class GameState {
return Math.ceil((timeout._idleStart + timeout._idleTimeout) / 1000 - process.uptime());
}

midgameConnect(id: string, oldId?: string) {
midgameConnect(id: string, oldId?: string): MidgameConnectData {
let player = this.players.find((player) => player.id === oldId);
if (!player) {
logger.info('(gameState) midgame join');
Expand Down
3 changes: 2 additions & 1 deletion server/src/state/pollService.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { GameState, Stage } from './gameState';
import { GameState } from './gameState';
import { Err, Ok, Result } from '../types/result';
import { Stage } from '../types/stateTypes';

export enum PollName {
SkipPrompt = 'skipPrompt',
Expand Down
69 changes: 69 additions & 0 deletions server/src/types/socketServerTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Server, Socket } from 'socket.io';
import { ConfigurableOptions } from '../state/options';
import { PollName } from '../state/pollService';
import { Match, MidgameConnectData, Responses, SelectionType } from './stateTypes';
import { ApiResult } from './result';
import { Player } from '../state/rooms';

interface ServerToClientRoomEvents {
joinRoom(args: { error: string } | { success: boolean; roomName: string }): void;

updatePlayers(args: { modifies: Player[]; deletes: Player[] }): void;
}

interface ClientToServerRoomEvents {
createRoom(name: string, roomName: string, langs?: string[]): void;

joinRoom(name: string, roomName: string): void;
}

interface ServerToClientGameEvents {
setOptions(options: ConfigurableOptions): void;

beginPrompt(prompt: string): void;

promptResponse(response: string): void;

nextSelection(args: { selector: string; selectionType: SelectionType }): void;

setVoteCount(args: { pollName: PollName; count: number; next: boolean }): void;

selectionTypeChosen(selectionType: SelectionType): void;

beginMatching(selectedResponse: string): void;

matchesFound(matches: Match[]): void;

endRound(args: { hasNextRound: boolean }): void;

gameOver(results: { player: string; points: number }[]): void;

midgameConnect(reconnect: MidgameConnectData): void;
}

interface ClientToServerGameEvents {
setOptions(options: ConfigurableOptions, callback?: (p: { success: boolean }) => void): void;

startGame(): void;

pollVote(pollName: PollName): void;

promptResponse(response: string): void;

selectSelectionType(isStrike: boolean): void;

selectResponse(response: string): void;

selectMatch(match: string): void;

selectionComplete(): void;

getResponses(id: string, callback: (result: ApiResult<Responses>) => void): void;
}

type ServerToClientEvents = ServerToClientRoomEvents & ServerToClientGameEvents;
type ClientToServerEvents = ClientToServerRoomEvents & ClientToServerGameEvents;

export class TypedServer extends Server<ClientToServerEvents, ServerToClientEvents> {}

export class TypedSocket extends Socket<ClientToServerEvents, ServerToClientEvents> {}
43 changes: 43 additions & 0 deletions server/src/types/stateTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { ConfigurableOptions } from '../state/options';
import { PollName } from '../state/pollService';

export enum Stage {
Lobby,
Response,
Selection,
Matching,
EndRound
}

export enum SelectionType {
Strike = 'strike',
Sike = 'sike',
Choice = 'choice'
}

export type Match = {
player: string;
response: string;
exact: boolean;
};

export type Responses = {
id: string;
all: string[];
used: string[];
selectedStrike: string;
selectedSike: string;
};

export type MidgameConnectData = {
stage: Stage;
selectionType: SelectionType;
responses: Responses;
selector: string;
selectedResponse: string;
prompt: string;
options: ConfigurableOptions;
timer: number;
matches: Match[];
voteCounts: Record<PollName, { count: number; next: boolean }>;
};
3 changes: 2 additions & 1 deletion server/tests/models/autoMatch.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { assert } from 'chai';
import { GameState, SelectionType } from '../../src/state/gameState';
import { GameState } from '../../src/state/gameState';
import { Player, Room } from '../../src/state/rooms';
import { SelectionType } from '../../src/types/stateTypes';

describe('Automatch tests', () => {
const selectorId = 'selector';
Expand Down
Loading

0 comments on commit 63cffa3

Please sign in to comment.