From: Carl Worth Date: Sun, 3 May 2020 22:05:23 +0000 (-0700) Subject: Add an API for server-sent events at `/events` X-Git-Url: https://git.cworth.org/git?a=commitdiff_plain;h=a7af56552e2a36e0a069f0aec5c1a9e9b0407e6e;p=empires-server Add an API for server-sent events at `/events` So far there are two event types being sent here: players: An event reporting the complete list of registered players player-register: An event reporting a new player has registered I'm not 100% sure these are all formatted correctly yet. I'll need to implement a client to listen to these events and then check that. --- diff --git a/server.js b/server.js index f0bc78e..bbc5d8a 100644 --- a/server.js +++ b/server.js @@ -9,15 +9,23 @@ class Game { constructor() { this._players = []; this.next_player_id = 1; + this.clients = []; + this.next_client_id = 1; } add_player(name, character) { - this._players.push({id: this.next_player_id, + const new_player = {id: this.next_player_id, name: name, character: character, captures: [], - }) + }; + this._players.push(new_player); this.next_player_id++; + /* The syntax here is using an anonymous function to create a new + object from new_player with just the subset of fields that we + want. */ + const player_string = JSON.stringify((({id, name}) => ({id, name}))(new_player)); + this.broadcast("player-register", player_string); } remove_player(id) { @@ -60,6 +68,24 @@ class Game { get players() { return this._players.map(player => ({id: player.id, name: player.name })); } + + add_client(response) { + const id = this.next_client_id; + this.clients.push({id: id, + response: response}); + this.next_client_id++; + + return id; + } + + remove_client(id) { + this.clients = this.clients.filter(client => client.id !== id); + } + + /* Send an event to all clients */ + broadcast(type, data) { + this.clients.forEach(client => client.response.write(`event: ${type}\ndata: ${data}\n\n`)); + } } const game = new Game(); @@ -67,6 +93,32 @@ const game = new Game(); app.use(body_parser.urlencoded({ extended: false })); app.use(body_parser.json()); +function handle_events(request, response) { + /* These headers will keep the connection open so we can stream events. */ + const headers = { + "Content-type": "text/event-stream", + "Connection": "keep-alive", + "Cache-Control": "no-cache" + }; + response.writeHead(200, headers); + + /* Now that a client has connected, first we need to stream all of + * the existing players (if any). */ + if (game._players.length > 0) { + const players_json = JSON.stringify(game.players); + const players_data = `event: players\ndata: ${players_json}\n\n`; + response.write(players_data); + } + + /* Add this new client to our list of clients. */ + const id = game.add_client(response); + + /* And queue up cleanup to be tirggered on client close. */ + request.on('close', () => { + game.remove_client(id); + }); +} + app.get('/', (request, response) => { response.send('Hello World!'); }); @@ -113,6 +165,8 @@ app.get('/players', (request, response) => { response.send(game.players); }); +app.get('/events', handle_events); + app.listen(3000, function () { console.log('Example app listening on port 3000!'); });