diff --git a/README.md b/README.md index d6764bb14ede2b5be0a652b3489d12c00fcc0850..a4d9d50fc59a8a4758c67f70f6a00a0cd8fdea07 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ You can contact me via: | 3 | [Promises](notes/promises/promises.md) | [`make`](specs/make/make.md) | 2020-04-24 | | 4-5 | [Streams](notes/streams/streams.md) | [`grep`](specs/grep/grep.md) | 2020-05-08 | | 6 | [HTTP](notes/http/http.md) | [Wiki Game](specs/wiki-game/wiki-game.md) | 2020-05-15 | -| 7 | WebSockets | Chat server **OR** Chat client | 2020-05-22 | +| 7 | [WebSockets](notes/websockets/websockets.md) | [Chat client](specs/chat/client.md) **OR** [Chat server](specs/chat/server.md) | 2020-05-22 | | 8-9 | `async`-`await` | MiniVC | 2020-06-05 | ## JavaScript reference diff --git a/notes/websockets/pixel-art.html b/notes/websockets/pixel-art.html new file mode 100644 index 0000000000000000000000000000000000000000..9e1fa26e33656cad9ab73449904b314ccebe8f16 --- /dev/null +++ b/notes/websockets/pixel-art.html @@ -0,0 +1,109 @@ +<html> + <head> + <style> + #grid-canvas { + border-collapse: collapse; + } + #grid-canvas td { + width: 20px; + height: 20px; + padding: 0; + border: 1px solid black; + } + </style> + <script> + const GRID_WIDTH = 20 + const GRID_HEIGHT = 20 + + // The <input type='color'> used to select the drawing color + let colorInput + // The <table> used to display the pixel art + let grid + // The WebSocket + let socket + // Rectangles are drawn by clicking and dragging. + // These store the row and column of the cell that was clicked. + let startRow + let startCol + + // Sends a `set-color` message with the current color. + // Called when the page loads and whenever `colorInput` changes. + function sendColor() { + const message = {type: 'set-color', color: colorInput.value} + socket.send(JSON.stringify(message)) + } + + // Sends a `draw` message to create a rectangle + // from `startRow` and `startCol` to the given row and column + function sendRectangle(row, col) { + const message = { + type: 'draw', + // Using Math.min() and Math.abs() + // since the rectangle could have been drawn in any direction + row: Math.min(startRow, row), + col: Math.min(startCol, col), + rows: Math.abs(startRow - row) + 1, + cols: Math.abs(startCol - col) + 1 + } + socket.send(JSON.stringify(message)) + } + + // Adds the table cell elements to `grid` and attaches + // `mousedown` and `mouseup` event handlers to them + function makeGrid() { + // Build a grid of <tr> and <td> elements, like in Minesweeper + for (let row = 0; row < GRID_HEIGHT; row++) { + const gridRow = document.createElement('tr') + for (let col = 0; col < GRID_WIDTH; col++) { + const cell = document.createElement('td') + // Handles the click of a click-and-drag + cell.onmousedown = () => { + startRow = row + startCol = col + } + // Handles the release of a click-and-drag + cell.onmouseup = () => sendRectangle(row, col) + gridRow.append(cell) + } + grid.append(gridRow) + } + } + + // Handles a `draw` message from the server. + // `{color, ..., cols}` unpacks the message's fields. + function draw({color, row, col, rows, cols}) { + // For each cell in the rectangle, update its background color + for (let i = 0; i < rows; i++) { + const gridRow = grid.children[row + i] + for (let j = 0; j < cols; j++) { + const gridCell = gridRow.children[col + j] + gridCell.style.background = color + } + } + } + + // We need to run code that accesses HTML elements after + // the rest of the HTML is parsed (signaled by the `load` event) + window.onload = () => { + // Retrieve HTML elements + colorInput = document.getElementById('color') + grid = document.getElementById('grid-canvas') + + colorInput.onchange = sendColor + makeGrid() + + // Create a WebSocket connection + socket = new WebSocket('ws://localhost') + // As soon as socket opens, send the initial color (black) + socket.onopen = sendColor + // Handles `draw` messages from the server. + // `event.data` is the JSON string received. + socket.onmessage = event => draw(JSON.parse(event.data)) + } + </script> + </head> + <body> + <input type='color' id='color' /> + <table id='grid-canvas'></table> + </body> +</html> diff --git a/notes/websockets/pixel-art.js b/notes/websockets/pixel-art.js new file mode 100644 index 0000000000000000000000000000000000000000..034bcc5fb8351460c08ac8438cf394159e5086fc --- /dev/null +++ b/notes/websockets/pixel-art.js @@ -0,0 +1,47 @@ +const WebSocket = require('ws') + +// The currently connected WebSockets +const sockets = new Set() +// The array of `draw` messages sent out. When a new client connects, +// we replay these messages to initialize the pixel grid. +const drawJSONs = [] + +// Create a WebSocket server on port 80 (the default) +new WebSocket.Server({port: 80}) + // The 'connection' event handler is called for new connections + .on('connection', socket => { + // Send all previously drawn rectangles to the client + for (const drawJSON of drawJSONs) socket.send(drawJSON) + + // Register the WebSocket to receive updates to the drawing + sockets.add(socket) + + // Each WebSocket has a `color` variable storing its current paint color + let color + socket + // The 'message' event handler receives each message from the client + .on('message', json => { + // `json` stores the (JSON string) contents of the message, + // which we turn back into an object + const message = JSON.parse(json) + if (message.type === 'set-color') { + // Handle `set-color` by updating the client's color + color = message.color + } + else if (message.type === 'draw') { + // Handle `draw` by constructing a `draw` message to send. + // We add the `color` field to the message from the client. + const drawMessage = {...message, color} + const drawJSON = JSON.stringify(drawMessage) + // Send the message to all connected WebSockets + for (const socket of sockets) socket.send(drawJSON) + // And add it to the history of `draw` messages + drawJSONs.push(drawJSON) + } + }) + // The 'close' event handler is called when the client disconnects + .on('close', () => { + // Unregister the socket so it stops receiving updates + sockets.delete(socket) + }) + }) diff --git a/notes/websockets/websockets.md b/notes/websockets/websockets.md new file mode 100644 index 0000000000000000000000000000000000000000..d644e8a61593be7a21beeab35174f7c58355ea28 --- /dev/null +++ b/notes/websockets/websockets.md @@ -0,0 +1,241 @@ +# WebSockets + +WebSocket is a modern protocol used on the Web in tandem with HTTP. +HTTP and WebSocket are both built on top of the same lower-level protocol, [TCP](https://en.wikipedia.org/wiki/Transmission_Control_Protocol). +However, WebSocket is a thinner abstraction over TCP and exposes more of TCP's functionality. +This set of notes discusses reasons to use WebSocket over HTTP and demonstrates how to use WebSockets in an application. + +## Limitations of HTTP + +The main drawback of HTTP in Web applications is its request-response nature. +As discussed [last time](../http/http.md), all HTTP connections to a server are initiated by the client. +Often this makes sense: a browser knows when its user wants to load a page or ask the server to do something. + +But sometimes, a server has new data to send to the client. +For example, an online game must tell the browser when the game state changes so it can be displayed. +An email/messaging application wants to notify the browser of a new message. +With HTTP, this requires the browser to periodically request updates, which is both slow and wasteful. +For example, if the browser checks for updates each minute, it can take a whole minute to learn about new data, and most requests may return no new data. + +WebSockets solve this by leveraging the *bidirectionality* of TCP. +A WebSocket connection (or just "WebSocket") allows the client and server to send messages to each other at any time. +There are no "requests" or "responses"; the server can send data without the client asking for it, and the server doesn't have to respond to data from the client. + +WebSockets are also *persistent*: many messages can be sent across the same WebSocket. +(HTTP uses a new connection for every request.) +This means a server can remember which WebSocket belongs to which user. +WebSockets also avoid the HTTP headers required for every HTTP request, which reduces the amount of data sent and the time until it arrives (the "latency"). + +## Example: collaborative pixel art + +We are going to build a WebSocket application that allows multiple users to paint a grid of pixels. +Each client can change its paint color and draw rectangles with its current color. + +The first step in building a WebSocket application is designing the API used to communicate with the server. +(This is the same as with HTTP; see the [HTTP notes on JSON APIs](../http/http.md#apis) for a refresher.) +Our API has two types of JSON messages to the server: +- `set-color`: changes the client's painting color. + The JSON has the form + ```json + {"type": "set-color", "color": "#rrggbb"} + ``` +- `draw`: fills a rectangle with the current color. + The client specifies the top-left row and column and the number of rows and columns in the rectangle. + The JSON has the form + ```json + { + "type": "draw", + "row": number, + "col": number, + "rows": number, + "cols": number + } + ``` +And one message to the client: +- `draw`: tells the client that a rectangle was drawn. + The JSON has the form + ```json + { + "type": "draw", + "color": "#rrggbb", + "row": number, + "col": number, + "rows": number, + "cols": number + } + ``` + +Next, we explain how to build the client and server around this WebSocket API. +It's a lot of code to read, so I recommend focusing on the part (either the client or server) you plan to implement on this week's project. + +### The client + +The client opens the WebSocket to the server using the builtin `WebSocket` constructor. +The server will run on the same computer, so the client connects to `localhost`. +(WebSocket URLs use `ws://`, or `wss://` for secure WebSockets.) +```js +socket = new WebSocket('ws://localhost') +``` + +When the socket connects, the client sends a `set-color` message with the initial drawing color. +We also want to send `set-color` whenever a new color is selected. +The HTML uses an `<input type='color'>` which conveniently gives us a color in the `#rrggbb` format. +```js +function sendColor() { + // Build the message we want to send, then convert it to a JSON string + const message = {type: 'set-color', color: colorInput.value} + socket.send(JSON.stringify(message)) +} + +// When the socket opens, call `sendColor()` +socket.onopen = sendColor +// When `colorInput`'s value changes, call `sendColor()` +colorInput.onchange = sendColor +``` + +Rectangles can be drawn by clicking and dragging, and this should send a `draw` message. +We use the `mousedown` event to capture clicking on a table cell and `mouseup` to capture releasing on a different table cell. +```js +// During a drag, these variables store +// the row and column of the cell that was clicked +let startRow +let startCol + +for (let row = 0; row < GRID_HEIGHT; row++) { + for (let col = 0; col < GRID_WIDTH; col++) { + const cell = document.createElement('td') + cell.onmousedown = () => { + startRow = row + startCol = col + } + cell.onmouseup = () => sendRectangle(row, col) + } +} + +function sendRectangle(row, col) { + const message = { + type: 'draw', + // Using Math.min() and Math.abs() + // since the rectangle could have been drawn in any direction + row: Math.min(startRow, row), + col: Math.min(startCol, col), + rows: Math.abs(startRow - row) + 1, + cols: Math.abs(startCol - col) + 1 + } + socket.send(JSON.stringify(message)) +} +``` + +And finally, we need to handle messages *from* the server. +This requires setting the `message` event handler on the socket. +```js +// Handles `draw` messages from the server. +// `event.data` is the JSON string received. +socket.onmessage = event => draw(JSON.parse(event.data)) + +// The curly braces around the arguments are shorthand for +// extracting these fields from the message object +function draw({color, row, col, rows, cols}) { + // For each cell in the rectangle, update its background color + for (let i = 0; i < rows; i++) { + const gridRow = grid.children[row + i] + for (let j = 0; j < cols; j++) { + const gridCell = gridRow.children[col + j] + gridCell.style.background = color + } + } +} +``` + +### The server + +In Node.js, you can use the npm package [`ws`](https://www.npmjs.com/package/ws) to create WebSocket servers. +You can install this package by running `npm install ws` in a terminal. +```js +const WebSocket = require('ws') + +// Make a WebSocket server and listen on port 80 (the default) +new WebSocket.Server({port: 80}) + // The 'connection' event handler is called for new connections + .on('connection', socket => { + // ... send and receive messages over the WebSocket + }) +``` + +This `socket` is very similar to a `WebSocket` object in the browser. + +The server needs to store the color selected by each `socket`, and update it whenever `set-color` is received: +```js +let color +socket.on('message', json => { + // `json` stores the (JSON string) contents of the message, + // which we turn back into an object + const message = JSON.parse(json) + if (message.type === 'set-color') { + color = message.color + } +}) +``` + +When a `draw` message is received, we need to send `draw` messages to all connected sockets. +This requires us to store the set of connected WebSockets. +```js +const sockets = new Set() + +new WebSocket.Server({port: 80}) + .on('connection', socket => { + // Register the WebSocket to receive updates to the drawing + sockets.add(socket) + + // When the client disconnects, stop sending it updates + socket.on('close', () => { + sockets.delete(socket) + }) + + // ... + }) +``` + +Now we can handle `draw` messages, including sending new clients the previous `draw` messages: +```js +// The array of `draw` messages previously sent +const drawJSONs = [] + +new WebSocket.Server({port: 80}) + .on('connection', socket => { + let color + socket.on('message', json => { + const message = JSON.parse(json) + if (message.type === 'draw') { + // Handle `draw` by constructing a `draw` message to send. + // We add the `color` field to the message from the client. + const drawMessage = {...message, color} + const drawJSON = JSON.stringify(drawMessage) + // Send the message to all connected WebSockets + for (const socket of sockets) socket.send(drawJSON) + // And add it to the history of `draw` messages + drawJSONs.push(drawJSON) + } + }) + + // When a client connects, send it all previously drawn rectangles + for (const drawJSON of drawJSONs) socket.send(drawJSON) + + // ... + }) +``` + +The full code for the client is in [`pixel-art.html`](pixel-art.html), and the server code is in [`pixel-art.js`](pixel-art.js). +I recommend playing around with it (try opening it in multiple tabs) and `console.log()`ing the messages received by the client/server to see what's being sent over the WebSockets! + +## *Aside*: WebSocket requests *are* HTTP requests + +Astute readers may have noticed that our WebSocket server listens on port 80, the same as the default port for HTTP. +That is because WebSocket is an extension of the HTTP protocol! +When a browser opens a WebSocket to `ws://some-url.com/some-path`, it actually makes a HTTP `GET` request to path `/some-path` on `some-url.com`. +The browser sends the [HTTP header](../http/http.md#aside-headers) `Upgrade: websocket` to request a WebSocket connection. +The server sends back a [status code](../http/http.md#aside-status-codes) of `101 Switching Protocols` and the headers `Connection: Upgrade` and `Upgrade: websocket` to accept the connection. +Then, unlike a normal HTTP request, the server leaves the connection open and both the client and server start communicating using the WebSocket protocol. + +This allows the same server to serve both HTTP and WebSocket requests, which is very useful in practice. +Most websites using WebSockets rely primarily on HTTP for clients to request webpages and make API calls, but use WebSockets when they need to push data to clients. diff --git a/specs/chat/api.md b/specs/chat/api.md new file mode 100644 index 0000000000000000000000000000000000000000..b86797e70957c22a6cd69e3df1df3e49ae762a56 --- /dev/null +++ b/specs/chat/api.md @@ -0,0 +1,92 @@ +# Chat API + +The chat server and client communicate over a WebSocket connection. +When a user logs in to the chat application, the client opens a WebSocket to the server that lasts until the page is closed. +Messages can be sent in either direction over the WebSocket at any time. +Whether you choose to implement the client or the server, you will need to implement both message directions. + +All messages are sent as JSON strings. +The field `type` indicates which type of message it is. +For simplicity, the API doesn't have any way to signal that a message was invalid (e.g. a `message` sent to a user that doesn't exist). + +## Concepts + +**Users**: +To log into the chat application, you provide your username. +(For simplicity, there is no password mechanism.) +The server stores all the usernames that have ever logged on. +You can chat with any user even if they aren't currently connected; they just won't see your messages until they connect. + +**Chat messages**: +A chat message is a piece of text sent between two users, from the "sender" to the "recipient". +Each message has a string ID so read receipts can refer to it. +These IDs are picked by senders and should be approximately unique (a good choice would be the combination of the sender's username and the time it was sent—see [`Date.now()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now)). + +**Read receipts**: +When a chat message is read by the recipient, they respond with a "read receipt". +(A message is counted as read if the recipient selects the sender on the chat page after receiving the message.) +Before a read receipt has been received, the sender shows the message with a grey background; afterwards, it has a blue background. + +## WebSocket messages from client to server + +- `login`: this message tells the server the client's username. + This must be the first message sent on the WebSocket. + It is a JSON string with this format: + ```json + {"type": "login", "user": string} + ``` +- `message`: this message asks the server to send a chat message to another user. + The client must specify the message ID, the recipient's username, and the string to send. + It is a JSON string with this format: + ```json + { + "type": "message", + "message": { + "id": string, + "user": string, + "content": string + } + } + ``` +- `read`: this message asks the server to send a read receipt for a previous message. + The client must specify the message ID. + It is a JSON string with this format: + ```json + {"type": "read", "id": string} + ``` + +## WebSocket messages from server to client + +- `users`: this message tells the client the usernames of all users who have ever logged on (including this client). + The usernames are in no particular order. + The server sends this message to each client as soon as the client sends a `user` message. + It is a JSON string with this format: + ```json + {"type": "users", "users": string[]} + ``` +- `message`: this message tells the client that they have received a message. + The server includes the message ID, the sender's username, and the string that was sent. + If the recipient is logged in, this message is delivered as soon as the server receives `message`; otherwise, it is sent whenever the recipient logs in. + It is a JSON string with this format: + ```json + { + "type": "message", + "message": { + "id": string, + "user": string, + "content": string + } + } + ``` +- `read`: this message is a read receipt and contains the message ID that was read. + If the sender is not logged in when the recipient responds with a read recipient, the sender doesn't receive the read receipt. + It is a JSON string with this format: + ```json + {"type": "read", "id": string} + ``` +- `new-user`: this message is sent to all connected clients when a new user logs on for the first time. + The message contains the new user's username. + It is a JSON string with this format: + ```json + {"type": "new-user", "user": string} + ``` diff --git a/specs/chat/chat-client.mp4 b/specs/chat/chat-client.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..a15d6ecea32b38a155cb7b052431ec985ed75171 Binary files /dev/null and b/specs/chat/chat-client.mp4 differ diff --git a/specs/chat/chrome-websockets.png b/specs/chat/chrome-websockets.png new file mode 100644 index 0000000000000000000000000000000000000000..d97fb2d871ed0d2a207270f02f9ec7b93cb82ba5 Binary files /dev/null and b/specs/chat/chrome-websockets.png differ diff --git a/specs/chat/client.md b/specs/chat/client.md new file mode 100644 index 0000000000000000000000000000000000000000..a549e140370b128fb0891550a0b60a857931af89 --- /dev/null +++ b/specs/chat/client.md @@ -0,0 +1,95 @@ +# Recommended JavaScript reading + +Communicating with the server requires constructing and destructing JSON objects. +JavaScript has shorthand notation that you may find useful for packing variables into objects, and for "destructuring" objects into variables. +There are examples under "Objects" in the [builtin types](../../notes/js/js.md#types) notes and under "destructuring" in the [variables](../../notes/js/js.md#variables) section. + +# Chat client + +This week, you will implement a chat application built on WebSockets. +You should write **either** the [Node.js server](server.md) **or** the browser-side chat client. +You're welcome to write both (or find another student who implemented the other half) and put them together, but this is not required. + +This [video](chat-client.mp4) shows 3 clients using the chat app. +The server waits half a second before processing each message to simulate a slow network. + +## WebSockets + +A chat app can benefit from both the bidirectionality and persistence of WebSockets: +- Since the chat server can send messages to the client without the client having to request them, new messages can be delivered as soon as possible +- Since the connections to the server stay open, there is less overhead in sending a message, which reduces latency and bandwidth use + +For these reasons, the chat app communicates entirely over WebSockets. + +## API + +The JSON API that is used to communicate over the WebSockets is documented in the [Chat API writeup](api.md). + +## Codebase + +Like on Minesweeper, an `index.html` with the HTML layout of the chat client is provided. +It uses [Bootstrap](https://getbootstrap.com), a popular style framework for webpages. +You are welcome to change the page structure and style as long as it supports all the required functionality. +There is a small bit of additional CSS in `style.css`. + +One change is that `client.js` runs *before* the elements are defined, which is typical for webpages. +This means, for example, that `document.getElementById('login')` won't work until the rest of the page is loaded. +Any code that depends on page elements should be put inside the `window.onload` callback, e.g.: +```js +window.onload = () => { + const loginButton = document.getElementById('login') + // ... +} +``` + +## Requirements + +When the page is opened, the user should be asked to enter their username. +Once the user clicks the login button, the client should open a WebSocket to the server and send `login`. +The login screen should also be hidden and the users/messages screen shown instead. +(You can add/remove the class `hidden` to hide/show elements.) + +Users should be shown in sorted order on the left side of the screen. +(Arrays have a `.sort()` method that sorts them.) +The user list is a [Boostrap "list group"](https://getbootstrap.com/docs/4.4/components/list-group); you can add an entry to it by appending a `<li class='list-group-item'>username</li>`. +(An element's text can be set using `element.innerText = username`.) +When `new-user` is received, the new username should be added to the user list. +Clicking on a user should select them in the chat screen, showing all the messages that have been sent to and received from them. +Only messages sent or received since the chat app was opened need to be shown. + +When the send button is clicked, the message entered in the input should be sent to the selected user using a `message` API message. +The sent message should be shown in the chat. +The input should be cleared so a new message can be typed (you can set an input's value using `element.value = ...`). + +The list of messages is also a list group, so you can display messages by adding list group items to it. +Received messages should appear on the left side and sent messages should appear on the right (use the Bootstrap class `text-right` to align text to the right.) +When a message is sent, it should initially show a grey background (use the class `list-group-item-secondary`). +Once a read receipt is received, the message should turn blue (use the class `active`). +To be able to refer to a message element, you can set its `id` to the message ID with `element.id = messageID` and use `document.getElementById(messageID)` to select it. + +When `message` is received, if the sender is currently selected in the chat window, the received message should be shown and a `read` for the message should be sent. +If the sender is not selected, a count of the number of unread messages should be shown next to the sender's username. +You can use a [Bootstrap badge](https://getbootstrap.com/docs/4.4/components/badge) for this: `<span class='badge badge-danger'>unread_count</span>`. +When the sender is selected, `read` should be sent for all unread messages and the unread count should disappear. + +When `read` is received, the corresponding message should turn blue. +Note that a `read` can be received for a message that was sent in a previous chat session; in this case, it can be ignored. + +## Testing + +My implementation of the chat server is running on my website so your client can connect to it. +It uses secure WebSockets; its URL is `wss://calebsander.com:8443`. + +It's not hard to make it crash by sending invalid messages. +It will restart automatically if it crashes, but please don't flood it with bad messages; it is a shared resource for the entire class. +The server also restarts at the start of every hour so you can try it with fresh data. + +Most browsers have a debugging window that shows what messages are being sent across the WebSocket. +For example, in Google Chrome: + + +## Going further + +The chat client you've written allows people to use the chat app, but the WebSocket API can also be leveraged for chat bots. +You can write a chat bot that logs in with a fixed username and responds to any messages it receives. +For example, users could send the bot city names and it could use a weather API to send the current weather there. diff --git a/specs/chat/server.md b/specs/chat/server.md new file mode 100644 index 0000000000000000000000000000000000000000..927e2605e929a71062659c9b6cc98e2b3e3e63b6 --- /dev/null +++ b/specs/chat/server.md @@ -0,0 +1,72 @@ +# Recommended JavaScript reading + +Communicating with the server requires constructing and destructing JSON objects. +JavaScript has shorthand notation that you may find useful for packing variables into objects, and for "destructuring" objects into variables. +There are examples under "Objects" in the [builtin types](../../notes/js/js.md#types) notes and under "destructuring" in the [variables](../../notes/js/js.md#variables) section. + +You will have to install the [`ws` package](https://www.npmjs.com/package/ws) from npm this week to use WebSockets in Node.js. +The [npm section](../../notes/js/js.md#installing-npm-packages) of `js.md` has information on how to install npm packages. +You can also use `npm install --save` to automatically add the dependency to the `package.json` file. + +# Chat server + +This week, you will implement a chat application built on WebSockets. +You should write **either** the [browser-side chat client](client.md) **or** the Node.js server. +You're welcome to write both (or find another student who implemented the other half) and put them together, but this is not required. +There are standalone tests provided for the server's WebSocket interface, so you don't need a browser client to test it. + +This [video](chat-client.mp4) shows 3 clients using the chat app. +The server waits half a second before processing each message to simulate a slow network. + +## WebSockets + +A chat app can benefit from both the bidirectionality and persistence of WebSockets: +- Since the chat server can send messages to the client without the client having to request them, new messages can be delivered as soon as possible +- Since the connections to the server stay open, there is less overhead in sending a message, which reduces latency and bandwidth use + +For these reasons, the chat app communicates entirely over WebSockets. +The server will be run using the command `node server.js port`. +It should host a WebSocket server on the specified port and handle all incoming connections. + +## API + +The JSON API that is used to communicate over the WebSockets is documented in the [Chat API writeup](api.md). +For simplicity, you can assume that the messages sent from the clients to the server conform to the API. +A real server would need to handle any malformed request without crashing, corrupting data, etc. + +## Requirements + +The requirements of the server are mostly explained in the API document, but here is a complete list: +- When a client sends `login`, the server should send `users`, followed by `message` for each chat message sent to that user which has not yet been read. + If this user connected for the first time, the server should send `new-user` to all connected clients. +- When a client sends `message`, the server should send a corresponding `message` to the recipient if the recipient is currently connected. + Otherwise, the chat message should be delivered when the recipient next connects. +- When a client sends `read`, the server should send a corresponding `read` to the sender of the message if they are connected. + If the sender is no longer connected, no `read` needs to be sent. +- `users` should include all users which have ever logged in, not just the currently connected users +- All data on the server should be stored in memory (i.e. as JavaScript values). + This means that if the server crashes or needs to be restarted, all users and messages will be lost. + Real servers prevent this by storing data in files or databases; you'll do this on the MiniVC project. +- The server should only store the messages that are still unread. + As soon as a read receipt is received for the message, the server should discard the message to save memory. +- If a client sends `login` with the same username as an existing connection, the server should assume the old connection became disconnected and send all future messages to the new connection + +## `ws` + +Node.js doesn't have a builtin library for WebSockets. +Luckily, there's a package for that. +Node.js has one of the best package managers, [npm](https://www.npmjs.com)! +Most modern JavaScript projects (including browser-side ones) use some of the over 1 million open-source packages on npm. + +You should install the [`ws` package](https://www.npmjs.com/package/ws) so that your server can `require('ws')`. +Make sure to include `ws` in the `dependencies` section of your `package.json` so that others can install the necessary dependencies for your project by running `npm install`. + +This only scratches the surface of what npm can do; I think npm's super cool, so please ask me if you want to know more about it! + +## Going further + +This chat app supports the core functionality of a real chat app like Facebook Messenger. +One feature you could add is chat groups: +- Users should be able to create new groups, add other users to groups they belong to, and leave groups +- Clients should be able to send messages to a group or an individual user. + If the client sends to a group, all users in the group should receive the message and send read receipts when it's read.