Skip to content

Commit

Permalink
Merge pull request #1 from jannes-io/feature/socket-reconnection
Browse files Browse the repository at this point in the history
Feature/socket reconnection
  • Loading branch information
jannes-io authored Apr 23, 2020
2 parents 2a7544a + 103effe commit 2d26fdf
Show file tree
Hide file tree
Showing 21 changed files with 336 additions and 426 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ root = true
indent_style = space
indent_size = 2
insert_final_newline = true
end_of_line = lf
466 changes: 117 additions & 349 deletions backend/package-lock.json

Large diffs are not rendered by default.

31 changes: 9 additions & 22 deletions backend/src/Handler/gameHandler.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,10 @@
import * as R from 'ramda';
import { Socket } from 'socket.io';
import appState from '../state';
import { findGameInfo, findRoom } from '../state';
import { IClearCardData, IPlayCardData } from '../typesClient';
import { IServerRoom } from '../types';
import emitter from '../emitter';
import logger from '../logger';

const findRoom = (roomId: string) => appState.rooms.find(R.propEq('id', roomId));
const findUser = (room: IServerRoom, socket: Socket) => room.users
.find((user) => user.socket.id === socket.id);

const findGameInfo = (roomId: string, socket: Socket) => {
const room = findRoom(roomId);
if (room === undefined) {
return undefined;
}
const user = findUser(room, socket);
if (user === undefined) {
return undefined;
}

return { room, user };
};

const playCard = (socket: Socket, data: IPlayCardData) => {
const gameInfo = findGameInfo(data.roomId, socket);
if (gameInfo === undefined) {
Expand All @@ -35,9 +17,14 @@ const playCard = (socket: Socket, data: IPlayCardData) => {
user.hasCardSelected = true;
emitter.sendRoomUpdate(room);

const playerCount = room.users.filter(R.propEq('type', 'player')).length;
const selectedCardCount = room.users.filter(R.prop('hasCardSelected')).length;
if (playerCount === selectedCardCount) {
const connectedPlayers = room.users
.filter(R.prop('connected'))
.filter(R.propEq('type', 'player'));

const playersWithCards = connectedPlayers
.filter(R.prop('hasCardSelected'));

if (connectedPlayers.length === playersWithCards.length) {
emitter.sendRevealedCards(room);
}
};
Expand Down
6 changes: 4 additions & 2 deletions backend/src/Handler/roomHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import emitter, { transformRoom } from '../emitter';

const createRoom = (_: Socket, { cards }: ICreateRoomData, acknowledge: Function) => {
const room: IServerRoom = {
id: uniqid(),
id: process.env.ENV === 'dev' ? 'dev' : uniqid(),
users: [],
cards,
};
Expand All @@ -21,7 +21,7 @@ const createRoom = (_: Socket, { cards }: ICreateRoomData, acknowledge: Function
};

const joinRoom = (socket: Socket, data: IJoinRoomData, acknowledge: Function) => {
const { roomId, displayName, playerType } = data;
const { roomId, clientId, displayName, playerType } = data;
const room = appState.rooms.find(R.propEq('id', roomId));

if (room === undefined) {
Expand All @@ -32,9 +32,11 @@ const joinRoom = (socket: Socket, data: IJoinRoomData, acknowledge: Function) =>
room.users.push({
id: socket.id,
socket,
clientId,
displayName,
type: playerType,
hasCardSelected: false,
connected: true,
});

emitter.sendRoomUpdate(room);
Expand Down
32 changes: 29 additions & 3 deletions backend/src/clientMessages.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Socket } from 'socket.io';
import * as util from 'util';
import * as R from 'ramda';
import roomHandlers from './Handler/roomHandler';
import logger from './logger';
import appState from './state';
import appState, { findGameInfoBySocket } from './state';
import emitter from './emitter';
import gameHandlers from './Handler/gameHandler';

Expand Down Expand Up @@ -30,18 +32,42 @@ const clientMessages: IClientMessage[] = [{
const messageWrapper = (handler: Function, socket: Socket) => (...args: any[]) => {
handler(socket, ...args);
if (process.env.ENV === 'dev') {
logger.log(appState);
logger.log(util.inspect(appState, false, 4, true));
}
};

const attemptReconnect = (clientId: string, socket: Socket) => {
appState.rooms.forEach((room) => {
const user = room.users.find(R.propEq('clientId', clientId));
if (user !== undefined) {
user.id = socket.id;
user.socket = socket;
user.connected = true;
emitter.sendRoomUpdate(room);
}
});
};

const registerClientMessages = (socket: Socket) => {
const { _query: { clientId } } = socket.request;
logger.log(`client ${clientId} connected`);

clientMessages.forEach(({ msg, handler }) => {
socket.on(msg, messageWrapper(handler, socket));
});

attemptReconnect(clientId, socket);

socket.on('disconnect', () => {
emitter.sendUserDisconnected(socket);
logger.log('user disconnected');
setTimeout(() => {
const gameInfo = findGameInfoBySocket(socket);
if (gameInfo !== undefined) {
gameInfo.room.users = gameInfo.room.users.filter((user) => user.socket.id !== socket.id);
emitter.sendRoomUpdate(gameInfo.room);
}
}, 5 * 60 * 1000);
logger.log(`client ${clientId} disconnected`);
});
};

Expand Down
32 changes: 21 additions & 11 deletions backend/src/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import * as R from 'ramda';
import { Socket } from 'socket.io';
import { IServerRoom, IServerUser } from './types';
import { IServerRoom } from './types';
import { IClientRoom, IRevealedCard } from './typesClient';
import appState from './state';
import appState, { findGameInfoBySocket } from './state';

export const transformRoom = (room: IServerRoom): IClientRoom => ({
...room,
Expand All @@ -15,22 +15,31 @@ const sendRoomUpdate = (room: IServerRoom) => {
});
};

const sendUserReconnected = (socket: Socket) => {
const gameInfo = findGameInfoBySocket(socket);
if (gameInfo !== undefined) {
gameInfo.user.connected = true;
sendRoomUpdate(gameInfo.room);
}
};

const sendUserDisconnected = (socket: Socket) => {
const findBySocket = (user: IServerUser) => user.socket.id === socket.id;
const userRoom = appState.rooms.find(({ users }) => users.find(findBySocket));

if (userRoom !== undefined) {
userRoom.users = R.reject(findBySocket, userRoom.users);
if (userRoom.users.length > 0) {
sendRoomUpdate(userRoom);
} else if (process.env.ENV !== 'dev') {
appState.rooms = appState.rooms.filter((room) => room.id !== userRoom.id);
const gameInfo = findGameInfoBySocket(socket);
if (gameInfo !== undefined) {
gameInfo.user.connected = false;

const connectedUsers = gameInfo.room.users.filter(R.prop('connected'));
if (connectedUsers.length === 0) {
appState.rooms = appState.rooms.filter((room) => room.id !== gameInfo.room.id);
} else {
sendRoomUpdate(gameInfo.room);
}
}
};

const sendRevealedCards = (room: IServerRoom) => {
const revealedCards: IRevealedCard[] = room.users
.filter(R.prop('connected'))
.filter(R.propEq('type', 'player'))
.map(({ id, selectedCard }) => ({ userId: id, selectedCard }));

Expand All @@ -49,5 +58,6 @@ export default {
sendRoomUpdate,
sendRevealedCards,
sendClearAllCards,
sendUserReconnected,
sendUserDisconnected,
};
8 changes: 1 addition & 7 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import * as express from 'express';
import * as socketIO from 'socket.io';
import * as dotenv from 'dotenv';
import { createServer } from 'http';
import { Socket } from 'socket.io';
import logger from './logger';
import registerClientMessages from './clientMessages';

dotenv.config();
Expand All @@ -14,10 +12,6 @@ app.use(express.static('public'));
const server = createServer(app);
const io = socketIO(server);

io.on('connection', (socket: Socket) => {
logger.log('user connected');

registerClientMessages(socket);
});
io.on('connection', registerClientMessages);

server.listen(process.env.PORT || 8080);
33 changes: 32 additions & 1 deletion backend/src/state.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,39 @@
// FIXME this is really disgusting state management
import { IAppState } from './types';
import * as R from 'ramda';
import { Socket } from 'socket.io';
import { IAppState, IServerRoom, IServerUser } from './types';

const appState: IAppState = {
rooms: [],
};

export const findRoom = (roomId: string): IServerRoom | undefined => appState.rooms
.find(R.propEq('id', roomId));

export const findUser = (room: IServerRoom, socket: Socket): IServerUser | undefined => room.users
.find((user) => user.socket.id === socket.id);

export interface IGameInfo {
user: IServerUser;
room: IServerRoom;
}

export const findGameInfo = (roomId: string, socket: Socket): IGameInfo | undefined => {
const room = findRoom(roomId);
if (room === undefined) {
return undefined;
}
const user = findUser(room, socket);
if (user === undefined) {
return undefined;
}

return { room, user };
};

export const findGameInfoBySocket = (socket: Socket): IGameInfo | undefined => {
const userRoom = appState.rooms.find((room) => findUser(room, socket) !== undefined);
return userRoom !== undefined ? findGameInfo(userRoom.id, socket) : undefined;
};

export default appState;
1 change: 1 addition & 0 deletions backend/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IRoom, IUser } from './typesClient';

export interface IServerUser extends IUser {
socket: Socket;
clientId: string;
selectedCard?: string;
}

Expand Down
2 changes: 2 additions & 0 deletions backend/src/typesClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export interface IUser {
displayName: string;
type: PlayerType;
hasCardSelected: boolean;
connected: boolean;
}

export interface IRevealedCard {
Expand All @@ -27,6 +28,7 @@ export interface ICreateRoomData {

export interface IJoinRoomData {
roomId: string;
clientId: string;
displayName: string;
playerType: PlayerType;
}
Expand Down
12 changes: 11 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"@types/react-dom": "^16.9.0",
"@types/react-router": "^5.1.3",
"@types/react-router-dom": "^5.1.3",
"@types/uniqid": "^4.1.3",
"ac-react-core": "^0.1.2",
"notistack": "^0.9.7",
"ramda": "^0.26.1",
Expand All @@ -24,7 +25,8 @@
"react-router": "^5.1.2",
"react-router-dom": "^5.1.2",
"react-scripts": "3.3.0",
"socket.io-client": "^2.3.0"
"socket.io-client": "^2.3.0",
"uniqid": "^5.2.0"
},
"devDependencies": {
"@types/socket.io-client": "^1.4.32",
Expand Down
4 changes: 3 additions & 1 deletion frontend/src/Components/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ const useStyles = makeStyles((theme: Theme) => createStyles({
justifyContent: 'center',
alignItems: 'center',
minHeight: theme.spacing(25),
},
clickable: {
cursor: 'pointer',
'&:hover': {
background: theme.palette.type === 'dark' ? '#353535' : '#f1f1f1',
Expand Down Expand Up @@ -36,7 +38,7 @@ const Card: React.FC<ICardProps> = ({ card, wide, label, onSelectCard }) => {
<Paper
variant="outlined"
elevation={3}
className={classes.paper}
className={onSelectCard === undefined ? classes.paper : `${classes.paper} ${classes.clickable}`}
onClick={() => onSelectCard !== undefined && onSelectCard(card)}
>
<Typography variant="h4">
Expand Down
23 changes: 23 additions & 0 deletions frontend/src/Components/DisconnectAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React from 'react';
import { Dialog, DialogContent, DialogContentText, DialogTitle, LinearProgress } from '@material-ui/core';
import useSocket from '../Hooks/Socket';

const DisconnectAlert: React.FC = () => {
const { connected } = useSocket();

if (connected) {
return null;
}

return <Dialog open>
<DialogTitle>Connection lost!</DialogTitle>
<DialogContent>
<DialogContentText>
Oh no! We are trying to reconnect you with the game server.
</DialogContentText>
<LinearProgress />
</DialogContent>
</Dialog>;
};

export default DisconnectAlert;
2 changes: 2 additions & 0 deletions frontend/src/Components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
Theme,
Typography,
} from '@material-ui/core';
import DisconnectAlert from './DisconnectAlert';

const useStyles = makeStyles((theme: Theme) => createStyles({
paper: {
Expand All @@ -27,6 +28,7 @@ const Layout: React.FC = ({ children }) => {

return <>
<CssBaseline />
<DisconnectAlert />
<Container maxWidth="md">
<Grid container spacing={3}>
<Grid item xs={12}>
Expand Down
Loading

0 comments on commit 2d26fdf

Please sign in to comment.