├── .gitignore ├── README.md ├── package.json └── src ├── app ├── index.html └── script.js └── server └── server.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Real-time chat with µWebSockets.js 2 | 3 | ## Getting started 4 | 5 | Grab dependencies: 6 | ```sh 7 | npm i 8 | ``` 9 | 10 | Start node server: 11 | ```sh 12 | node src/server/server.js 13 | ``` 14 | 15 | Start web server: 16 | ```sh 17 | # Python 2.x 18 | python -m SimpleHTTPServer 8000 -d src/app 19 | # Python 3.x 20 | python3 -m http.server 8000 -d src/app 21 | ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "uwebsockets-nodejs", 3 | "version": "0.1.0", 4 | "description": "Example repo for my quickstart guide to µWebSockets.js + Node.js + Nginx ", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/edisonchee/uwebsockets-nodejs.git" 12 | }, 13 | "keywords": [ 14 | "uwebsockets", 15 | "websockets", 16 | "websocket", 17 | "nginx", 18 | "nodejs" 19 | ], 20 | "author": "Edison Chee ", 21 | "license": "MIT", 22 | "bugs": { 23 | "url": "https://github.com/edisonchee/uwebsockets-nodejs/issues" 24 | }, 25 | "homepage": "https://github.com/edisonchee/uwebsockets-nodejs#readme" 26 | } 27 | -------------------------------------------------------------------------------- /src/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Real-time chat with µWebSockets.js 7 | 8 | 9 |

Chat

10 | Your display name is: 11 | 12 |
13 | 14 | 15 |
16 | 17 | 18 | -------------------------------------------------------------------------------- /src/app/script.js: -------------------------------------------------------------------------------- 1 | const MESSAGE_ENUM = Object.freeze({ 2 | SELF_CONNECTED: "SELF_CONNECTED", 3 | CLIENT_CONNECTED: "CLIENT_CONNECTED", 4 | CLIENT_DISCONNECTED: "CLIENT_DISCONNECTED", 5 | CLIENT_MESSAGE: "CLIENT_MESSAGE" 6 | }) 7 | 8 | // DOM references 9 | let DOM_EL = { 10 | username: null, 11 | chatLog: null, 12 | chatInput: null, 13 | chatInputButton: null 14 | } 15 | 16 | let wsTimeout = null; 17 | 18 | window.addEventListener('DOMContentLoaded', event => { 19 | assignReferences(); 20 | attachListeners(); 21 | }); 22 | 23 | ws = new WebSocket("wss://your-domain/ws"); 24 | ws.onopen = evt => { 25 | wsTimeout = setTimeout(ping, 50000); 26 | 27 | ws.onmessage = evt => { 28 | let msg = JSON.parse(evt.data); 29 | switch (msg.type) { 30 | case MESSAGE_ENUM.CLIENT_MESSAGE: 31 | printMessage(msg); 32 | console.log(`${msg.sender} says: ${msg.body}`); 33 | break; 34 | case MESSAGE_ENUM.CLIENT_CONNECTED: 35 | logMessage(msg); 36 | console.log(`${msg.body.name} has joined the chat.`); 37 | break; 38 | case MESSAGE_ENUM.CLIENT_DISCONNECTED: 39 | logMessage(msg); 40 | console.log(`${msg.body.name} has left the chat.`); 41 | break; 42 | case MESSAGE_ENUM.SELF_CONNECTED: 43 | DOM_EL.username.innerText = `Your username is: ${msg.body.name}` 44 | break; 45 | default: 46 | console.log("Unknown message type."); 47 | } 48 | } 49 | } 50 | 51 | const sendMessage = evt => { 52 | let msg = { 53 | type: MESSAGE_ENUM.CLIENT_MESSAGE, 54 | body: DOM_EL.chatInput.value 55 | } 56 | ws.send(JSON.stringify(msg)); 57 | DOM_EL.chatInput.value = ""; 58 | } 59 | 60 | const printMessage = msg => { 61 | let listEl = document.createElement('li'); 62 | let usernameSpanEl = document.createElement('span'); 63 | let textSpanEl = document.createElement('span'); 64 | 65 | usernameSpanEl.classList.add('username'); 66 | usernameSpanEl.innerText = msg.sender; 67 | textSpanEl.classList.add('text'); 68 | textSpanEl.innerText = msg.body; 69 | 70 | listEl.appendChild(usernameSpanEl); 71 | listEl.appendChild(textSpanEl); 72 | 73 | DOM_EL.chatLog.appendChild(listEl); 74 | } 75 | 76 | const logMessage = msg => { 77 | let listEl = document.createElement('li'); 78 | let usernameSpanEl = document.createElement('span'); 79 | let textSpanEl = document.createElement('span'); 80 | 81 | usernameSpanEl.classList.add('username'); 82 | usernameSpanEl.innerText = "System"; 83 | textSpanEl.classList.add('text'); 84 | 85 | switch(msg.message_type) { 86 | case MESSAGE_ENUM.CLIENT_CONNECTED: 87 | textSpanEl.innerText = `${msg.body.name} has connected`; 88 | break; 89 | case MESSAGE_ENUM.CLIENT_DISCONNECTED: 90 | textSpanEl.innerText = `${msg.body.name} has disconnected`; 91 | break; 92 | default: 93 | console.error("Unknown message type"); 94 | } 95 | 96 | listEl.appendChild(usernameSpanEl); 97 | listEl.appendChild(textSpanEl); 98 | 99 | DOM_EL.chatLog.appendChild(listEl); 100 | } 101 | 102 | function assignReferences() { 103 | DOM_EL.username = document.getElementById("username"); 104 | DOM_EL.chatLog = document.getElementById("chat-log"); 105 | DOM_EL.chatInput = document.getElementById("chat-input"); 106 | DOM_EL.chatInputButton = document.getElementById("chat-input-button"); 107 | } 108 | 109 | function attachListeners() { 110 | DOM_EL.chatInputButton.addEventListener('click', sendMessage); 111 | DOM_EL.chatInput.addEventListener('keydown', handleKeyDown); 112 | } 113 | 114 | function handleKeyDown(evt) { 115 | evt.key === 'Enter' && 116 | DOM_EL.chatInput.value !== '' && 117 | DOM_EL.chatInput.value.trim() !== '' ? sendMessage() : ''; 118 | } -------------------------------------------------------------------------------- /src/server/server.js: -------------------------------------------------------------------------------- 1 | const uWS = require('./uws.js'); 2 | const { uuid } = require('uuidv4'); 3 | const port = 7777; 4 | 5 | const decoder = new TextDecoder('utf-8'); 6 | 7 | const MESSAGE_ENUM = Object.freeze({ 8 | SELF_CONNECTED: "SELF_CONNECTED", 9 | CLIENT_CONNECTED: "CLIENT_CONNECTED", 10 | CLIENT_DISCONNECTED: "CLIENT_DISCONNECTED", 11 | CLIENT_MESSAGE: "CLIENT_MESSAGE", 12 | PING: "PING" 13 | }) 14 | 15 | let sockets = []; 16 | 17 | const app = uWS.SSLApp({ 18 | key_file_name: "/etc/letsencrypt/live/your-domain/privkey.pem", 19 | cert_file_name: "/etc/letsencrypt/live/your-domain/cert.pem" 20 | }) 21 | .ws('/ws', { 22 | compression: 0, 23 | maxPayloadLength: 16 * 1024 * 1024, 24 | idleTimeout: 60, 25 | 26 | open: (ws, req) => { 27 | ws.id = uuid(); 28 | ws.username = createName(getRandomInt()); 29 | 30 | // subscribe to topics 31 | ws.subscribe(MESSAGE_ENUM.CLIENT_CONNECTED); 32 | ws.subscribe(MESSAGE_ENUM.CLIENT_DISCONNECTED); 33 | ws.subscribe(MESSAGE_ENUM.CLIENT_MESSAGE); 34 | 35 | sockets.push(ws); 36 | 37 | // SELF_CONNECTED sent to self only ONCE upon ws open 38 | let selfMsg = { 39 | type: MESSAGE_ENUM.SELF_CONNECTED, 40 | body: { 41 | id: ws.id, 42 | name: ws.username 43 | } 44 | } 45 | 46 | let pubMsg = { 47 | type: MESSAGE_ENUM.CLIENT_CONNECTED, 48 | body: { 49 | id: ws.id, 50 | name: ws.username 51 | } 52 | } 53 | 54 | // send to connecting socket only 55 | ws.send(JSON.stringify(selfMsg)); 56 | 57 | // send to *all* subscribed sockets 58 | app.publish(MESSAGE_ENUM.CLIENT_CONNECTED, pubMsg) 59 | }, 60 | message: (ws, message, isBinary) => { 61 | // decode message from client 62 | let clientMsg = JSON.parse(decoder.decode(message)); 63 | let serverMsg = {}; 64 | 65 | switch (clientMsg.type) { 66 | case MESSAGE_ENUM.CLIENT_MESSAGE: 67 | serverMsg = { 68 | type: MESSAGE_ENUM.CLIENT_MESSAGE, 69 | sender: ws.username, 70 | body: clientMsg.body 71 | }; 72 | 73 | app.publish(MESSAGE_ENUM.CLIENT_MESSAGE, JSON.stringify(serverMsg)); 74 | break; 75 | default: 76 | console.log("Unknown message type."); 77 | } 78 | }, 79 | close: (ws, code, message) => { 80 | /* The library guarantees proper unsubscription at close */ 81 | SOCKETS.find((socket, index) => { 82 | if (socket && socket.id === ws.id) { 83 | SOCKETS.splice(index, 1); 84 | } 85 | }); 86 | 87 | let pubMsg = { 88 | type: MESSAGE_ENUM.CLIENT_DISCONNECTED, 89 | body: { 90 | id: ws.id, 91 | name: ws.name 92 | } 93 | } 94 | 95 | app.publish(MESSAGE_ENUM.CLIENT_DISCONNECTED, JSON.stringify(pubMsg)); 96 | } 97 | }).listen(port, token => { 98 | token ? 99 | console.log(`Listening to port ${port}`) : 100 | console.log(`Failed to listen to port ${port}`); 101 | }); 102 | 103 | function getRandomInt() { 104 | return Math.floor(Math.random() * Math.floor(9999)); 105 | } 106 | 107 | function createName(randomInt) { 108 | return sockets.find(ws => ws.name === `user-${randomInt}`) ? 109 | createName(getRandomInt()) : 110 | `user-${randomInt}` 111 | } 112 | --------------------------------------------------------------------------------