Server-side WebSocket — Create a Connect 4 online training
In this chapter we will implement server-side WebSockets logic to manage game synchronization between players. For this we will rely on the package @fastify/websocket
which will allow easy integration with what has already been done.
To manage our games and our connections we will use a repository system to abstract the game fetching and creation logic.
- the
ConnectionRepository
will memorize our connections by organizing them by player id using a Mapnew Map<Player["id"], Map<GameId, SocketStream>>
- the
GameRepository
will take care of saving the different parts of the playersnew Map<GameId, Machine>
Once these 2 classes have been created, they can easily be used during connections.
const connections = new ConnectionRepository()
const games = new GameRepository(connections)
fastify.register(FastifyWebsocket)
fastify.register(async (f) => {
f.get('/ws', {websocket: true}, (connection, req) => {
const query = req.query as Record<string, string>
const playerId = query.id ?? ''
const signature = query.signature ?? ''
const playerName = query.name || 'John Doe'
const gameId = query.gameId
if (!gameId) {
connection.end()
f.log.error('Pas de gameId')
return;
}
if (!verify(playerId, signature)) {
f.log.error(`Erreur d'authentification`)
connection.socket.send(JSON.stringify({
type: 'error', code: ServerErrors.AuthError
}))
return;
}
const game = games.find(gameId) ?? games.create(gameId)
connections.persist(playerId, gameId, connection)
game.send(GameModel.events.join(playerId, playerName))
publishMachine(game.state, connection)
connection.socket.on('message', (rawMessage) => {
const message = JSON.parse(rawMessage.toLocaleString())
if (message.type === 'gameUpdate') {
game.send(message.event)
}
})
connection.socket.on('close', () => {
connections.remove(playerId, gameId)
game.send(GameModel.events.leave(playerId))
games.clean(gameId)
})
})
})