Hello guys,
In this article we are going to create a group chat app that can also be private.
Socket.io
Socket.io is the main package that we are going to use for our web app.
Socket.IO is a library that enables real-time, bidirectional and event-based communication between the browser and the server.
It consists of:
- a Node.js server
- a Javascript client library for the browser (which can be also run from Node.js)
So lets create a server for our web app:
Initialize a new node.js project:
npm init
Install express:
npm install --save express
Install socket.io:
npm install --save socket.io
Install ejs for server side rendering:
npm install --save ejs
The server code:
//Loading dependencies & initializing express | |
var express = require("express"); | |
var app = express(); | |
var http = require("http"); | |
var socketIO = require("socket.io"); | |
app.use(express.static("public")); | |
//in order to render a html page for our app | |
app.get("/", function (req, res) { | |
res.render("index.ejs"); | |
}); | |
var server = http.createServer(app); | |
server.listen(process.env.PORT || 3000); | |
var io = socketIO(server); | |
io.sockets.on("connection", function (socket) { | |
//Defining Socket Connections | |
socket.on("message", function (message, room) { | |
// for a real app, would be room-only (not broadcast) | |
socket.to(room).emit("message", message, room); | |
}); | |
//every new client that will join the chat room will send this event to the server | |
//and the server will respond acordingly | |
socket.on("create or join", function (room, clientName) { | |
var clientsInRoom = io.sockets.adapter.rooms.get(room); | |
var numClients = clientsInRoom ? clientsInRoom.size : 0; | |
if (numClients === 0) { | |
socket.join(room); | |
socket.emit("created", room, socket.id); | |
} else { | |
//this message ("join") will be received only by the previous clients since the last client has not joined the room yet | |
//send to the clients the new client that will join the room | |
io.sockets.in(room).emit("join", room, clientName, socket.id); //this client name is the name of the last client who wants to join | |
socket.join(room); | |
//this message will be received by all cleints after the join of the last client | |
io.sockets.in(room).emit("ready"); | |
} | |
}); | |
//send this info(previous) as a private message to the user that is specified in the parameters(socketid) | |
socket.on("previousclients", (previous,socketid) => { | |
// to individual socketid (private message) | |
io.to(socketid).emit("beforeyou",previous); | |
}); | |
socket.on("bye", function () { | |
console.log("received bye"); | |
}); | |
}); |
We use express framework for server, also in order to render a html page we use ejs.
The server handles the messaging between clients. It is the middleware for the message delivering.
The client code:
//Defining some global utility variables | |
var clientName = "user_" + Math.floor(Math.random() * 1000 + 1); | |
var remoteclients = []; | |
var isInitiator = false; | |
document.getElementById("yourname").innerHTML = "You: " + clientName; | |
// Prompting for room name: | |
// var room = prompt('Enter room name:'); | |
//setting test room | |
var room = "test"; | |
//Initializing socket.io | |
var socket = io.connect(); | |
//Ask server to add in the room if room name is provided by the user | |
if (room !== "") { | |
// socket.emit('create or join', room); | |
// console.log('Attempted to create or join room', room); | |
} | |
//Defining socket events | |
//Event - Client has created the room i.e. is the first member of the room | |
socket.on("created", function (room) { | |
console.log("Created room " + room); | |
isInitiator = true; | |
}); | |
//Event - Another client tries to join room | |
//this message is received only by the client that connected first | |
//when the second peer is connected | |
socket.on("join", function (room, client, socketid) { | |
console.log("Another peer made a request to join room ", room, " whith name :", client); | |
sendmessagebutton.disabled = false; | |
//if the client is the creator send to the client that just connected the array of the other users names | |
if (isInitiator) { | |
beforeclients = remoteclients.slice(); //or [...remoteclients] | |
beforeclients.push(clientName); | |
socket.emit("previousclients", beforeclients, socketid); | |
} | |
remoteclients.push(client); | |
document.getElementById("remotename").innerHTML = remoteclients; | |
}); | |
//this message will be received by the last client that will connect to the room | |
//and has the names of all the other users before him, this information is provided by the creator of the room | |
//as he has all the names of every next client joins the room | |
socket.on("beforeyou", (previous) => { | |
remoteclients = previous; | |
document.getElementById("remotename").innerHTML = remoteclients; | |
sendmessagebutton.disabled = false; | |
}); | |
//Event - server asks to log a message | |
socket.on("log", function (array) { | |
console.log.apply(console, array); | |
}); | |
socket.on("message", function (message, room) { | |
viewmsgtoelement(document.getElementById("messagesent"), message); | |
}); | |
//Function to send message in a room | |
function sendMessage(message, room) { | |
console.log("Client sending message: ", message, room); | |
socket.emit("message", message, room); | |
} | |
//Sending bye if user closes the window | |
window.onbeforeunload = function () { | |
sendMessage("bye", room); | |
}; | |
var connectbutton = document.getElementById("connectbutton"); | |
if (connectbutton) { | |
connectbutton.addEventListener("click", () => { | |
if (connectbutton.innerHTML !== "Connected") { | |
socket.emit("create or join", room, clientName); | |
} | |
connectbutton.innerHTML = "Connected"; | |
//connection logic | |
}); | |
} | |
let messagetexted = ""; | |
//DOM elements | |
var messageinput = document.getElementById("messagearea"); | |
if (messageinput) { | |
//Tip: This event is similar to the onchange event. | |
//The difference is that the oninput event occurs immediately | |
// after the value of an element has changed, while onchange occurs | |
//when the element loses focus, after the content has been changed. | |
//The other difference is that the onchange event also works on <select> elements. | |
messageinput.addEventListener("input", (event) => { | |
messagetexted = event.target.value; | |
}); | |
} | |
var sendmessagebutton = document.getElementById("sendmessage"); | |
if (sendmessagebutton) { | |
sendmessagebutton.disabled = true; | |
sendmessagebutton.addEventListener("click", () => { | |
var themessage = "<p>" + clientName + ":" + messagetexted + "</p>"; | |
viewmsgtoelement(document.getElementById("messagesent"), themessage); | |
sendMessage(themessage, room); | |
messageinput.value = ""; | |
messagetexted = ""; | |
}); | |
} | |
function viewmsgtoelement(element, message) { | |
element.innerHTML += "\n" + message; | |
} |
Lets explain the client code
First we define the room name. Here the room name is set to test for simplicity reasons but it can be dynamic. The room name is the name that will need every client in order to join the chat.
"created" event, will be received from the first user that will join the room and this client will flagged as the initiator of the room.
"create or join" is the event that every user that joins the room will call.
"join" event will received from all clients when a new one enters the room and will inform them about his information (in our situation only his name)
Also in this event the initator will send an event only for he new user that joined the room to update his list of other clients in the room."beforeyou" event will received only by the last user that will join the room to update his list of the users already existing in the room.
"message" event is the main event that every user will receive after the connection in the room is established
The view:
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8" /> | |
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> | |
<title>Document</title> | |
</head> | |
<body> | |
<h1>Send messages peer to peer</h1> | |
<!-- These paragraphs will be initialized when the connection has been established --> | |
<p id="yourname"></p> | |
<p>Other users:</p> | |
<p id="remotename"></p> | |
<button id="connectbutton">Connect with peer</button> | |
<textarea id="messagearea" name="message" rows="5" cols="50"></textarea> | |
<button id="sendmessage">Send message</button> | |
<div id="messagesent"></div> | |
<!-- Import SocketIO for signalling --> | |
<script src="/socket.io/socket.io.js"></script> | |
<script src="js/main.js"></script> | |
</body> | |
</html> |
The full code on github with more detailed comments:
github
Top comments (0)