-
From the beginning, HTTP implemented a request | reponse process.
-
In HTTP, all communications is initiated by the cient.
-
The web server responds once to the client's request, and the "conversation" ends until client send another request.
-
The HTML5 specifications also included the ability to updagrade a HTTP connection upon request of the client.
-
This connections upgrade results in the switch to a protocol that supports bidirectional communication - the websocket protocol.
-
Working with websockets natively is not terribly difficult, however, we've learned that using libraries such as
jQuery
can make us more productive developers. -
socket.io is a JavaScript library that wraps the websocket protocol an makes it easier to implement the realtime, bidirectional communication.
-
socket.io clients and servers send messages to each other
-
and both clients and servers can listen and react to those messages.
-
socket.io is all about sending and responding to messages.
-
A message is a simple string identifier that we get to define, for example:
login
, ormove-player
-
Just like when naming functions, it's best to use identifiers for our messages that reflect their purpose.
-
What sending a message, we can optionally send data that will be received by the listeners for the message. For example, when sending a
login
message, we might send the following JS object:{ email: 'user@email.com', password: 'abc123' }
-
npm i
-
npm i socket.io
touch public/javascripts/app.js touch io.js
- We don't want to unnecessarily clutter
server.js
, so we're going to put our socket.io related code in a separate module file. - socket.io, needs to attach to the http server, not the Express app.
- We don't want to unnecessarily clutter
-
In
bin/wwww
:- In an Express app scaffolded using
express-generator
, the http server lives inside of the/bin/www
file, so that is where we will require our newio.js
module and attach to the http server.
var server = http.createServer(app); const io = require('../io'); //! Load socket.io to http server below `var server = http.createServer(app);` io.attach(server); //! Attach socket.io to http server
- In an Express app scaffolded using
-
In
io.js
module://! Require Packages const io = require('socket.io')(); //! Listen for new connections from clients (socket) io.on('connection', function (socket) { console.log('Client connected to socket.io!'); }); //! Export socket.io module.exports = io;
-
In
index.ejs
:- Add the following code to the body
<header><span>realtime-circles</span><button>clear</button></header> <section id="circles"></section> <footer><ul id="players"></ul></footer> <script src="/socket.io/socket.io.js"></script> <script src="/javascripts/app.js"></script>
-
It takes quite a bit of JavaScript in the browser to connect to socket.io on the server and implement all of its goodness.
-
Lucky for us, the socket.io module on the server helps us out by creating a secret route that returns dynamically generated JavaScript for the client - hassle free!
-
The code returned to the browser is pre-configured with the server's info, etc.
-
In
views/index.ejs
:- All we need to do is load this special client configuration script, bofore the closing body tag.
// special route created by socket.io on the server <script src="/socket.io/socket.io.js"></script> <script src="/javascripts/app.js"></script> </body>
-
In
public/javascripts/app.js
:- The
socket.io.js
client script exposes anio
global function that we call to obtain our connection to the server. - Let's call it and assign the returned connection object to a variable named
socket
.
//! Get our connection to the socket.io server const socket = io(); console.log(socket);
- The
-
Server
- To accomplish our requirements, this is what we will need to do on the server:
- Listen for
add-circle
message being sent from the clients. - When an
add-circle
message is received, forward (emit
) it (along with the data received with the message) to all connected clients (including the client that sent the message to begin with).
- Listen for
- To accomplish our requirements, this is what we will need to do on the server:
-
Client
- To accomplish for
add-circle
message from the server.- Listen for
add-circle
messas from the server. - When to
add-circle
message is received, it will containa data object with the properties necessary to pass to the existingaddCircle()
function that creates circles!. - In the existing click handler,
emit
theadd-circle
message to the server, passing along an object containing theinitials
,x
,y
,dia
andrgb
properties.
- Listen for
- To accomplish for
-
Messages - Review
- The
add-circle
message is a custom event messsage that we defined based upon what made sense for this application. - Each message can be
emitted
with data. The data can be any type except for a function. Objects and arrys come in handy for sending complex rather than a single piece of primitive data.
- The
-
In
io.js
://! Require Packages const io = require('socket.io')(); //! Listen for new connections from clients (socket) io.on('connection', function (socket) { socket.on('add-circle', function (data) { io.emit('add-circle', data); }); }); //! Export socket.io module.exports = io;
- Remember,
io
represents the server andsocket
the current client. - When a client (
socket
) conects to the server, we're using theon
method to set up a listener on the server to listen messages sent from that client. - When the server receives an
add-circle
message from the client, the callback function will send the same message to all clients using the server's (io
)emit
method.
- Remember,
-
In
public/javascripts/app.js
- Listen for an
add-circle
message from the server.
//! Get our connection to the socket.io server const socket = io(); //! Listen to the server for the `add-circle` event socket.on('add-circle', function (data) { console.log(data); });
- On the client browser, we have the
socket
object representing our realtime connection to the server. - For now, we're simply logging out data received from the server.
- Now let's upate the click event listener to
emit
anadd-circle
message to the server with the data:
//! Get our connection to the socket.io server const socket = io(); const circles = document.getElementById('circles'); //! Get circles from the DOM //! Listen to the server for the `add-circle` event socket.on('add-circle', function (data) { console.log(data); }); //! Create a click eventListner to send msg to the server circles.addEventListener('click', function(evt) { socket.emit('add-circle', { initials: initials, x: evt.clientX, y: evt.clientY, dia: randomBetween(10,100), rgba: getRandomRGBA() }); });
- Listen for an
-
To recap, our code so far:
- Emit's
add-circle
messas from data to the server when a user clicks. - Receives
add-circle
message emitted from the server and console logs their data. - Before testing, edit the css file, otherwise we won't be able to click on our page
html, body { height: 100%; width: 100%; margin: 0; padding: 0; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } #circles { width: 100%; height: 100%; } #circles div { border-radius: 50%; position: absolute; } header { position: absolute; top: 10px; left: 20px; width: 100%; z-index: 999; font: lighter 20px "Courier New"; color: purple; } header button { float: right; margin-right: 30px; padding: 6px; background-color: purple; color: white; border-style: none; border-radius: 4px; } footer { position: absolute; bottom: 0; width: 100%; text-align: center; } li { list-style: none; display: inline; font: 12px Arial; color: white; border-radius: 4px; background-color: gray; padding: 4px; margin-right: 5px; }
- Emit's
-
Let's finish our
public/javascripts/app.js
to avoid erros and warnings.//! Get our connection to the socket.io server const socket = io(); let circles = document.getElementById('circles'); //! Get circles from the DOM let buttom = document.getElementsByTagName('button'); //! Listen to the server for the `add-circle` event socket.on('add-circle', function (data) { addCircle(data); }); //! Create a click eventListner to send msg to the server circles.addEventListener('click', function(evt) { console.log(evt); socket.emit('add-circle', { initials: initials, x: evt.clientX, y: evt.clientY, dia: randomBetween(10,100), rgba: getRandomRGBA() }); }); //! Pop up to input the initials function getInitials() { const input = prompt('Please enter your initials'); return input ? input.toUpperCase() : ''; }; //! Get the initial do { initials = getInitials(); } while (initials.length < 2 || initials.length > 3); //! Clear Button - eventListener on the button buttom[0].addEventListener('click', function() { socket.emit('clear-circles'); }); //! Add Circlet to the DOM function addCircle({ x, y, dia, rgba, initials }) { const el = document.createElement('div'); el.style.left = x - Math.floor(dia / 2 + 0.5) + 'px'; el.style.top = y - Math.floor(dia / 2 + 0.5) + 'px'; el.style.width = el.style.height = dia + 'px'; el.style.backgroundColor = rgba; el.style.fontSize = Math.floor(dia / 3) + 'px'; el.style.color = 'white'; el.style.textAlign = 'center'; el.style.lineHeight = dia + 'px'; el.innerHTML = initials; circles.appendChild(el); }; //! Random math to get the RGBA colors function randomBetween(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); }; //! Get random color function getRandomRGBA() { return [ 'rgba(', randomBetween(0, 255), ',', randomBetween(0, 255), ',', randomBetween(0, 255), ',', randomBetween(2, 10) / 10, ')' ].join(''); };
-
Installation
npm i
npm i socket.io
touch public/javascripts/app.js touch io.js
-
In
bin\www
:- Add after
var server = http.createServer(app);
const io = require('../io'); //! Load socket.io to http server below `var server = http.createServer(app);` io.attach(server); //! Attach socket.io to http server
- Add after
-
In
io.js
://! ,pP"Ybd .gP"Ya `7Mb,od8 `7M' `MF' .gP"Ya `7Mb,od8 //! 8I `" ,M' Yb MM' "' VA ,V ,M' Yb MM' "' //! `YMMMa. 8M"""""" MM VA ,V 8M"""""" MM //! L. I8 YM. , MM VVV YM. , MM //! M9mmmP' `Mbmmd' .JMML. W `Mbmmd' .JMML. //! Require Packages const io = require('socket.io')(); let players = {}; //! Listen for new connections from clients (socket) io.on('connection', function (socket) { //+ Client/Socket sent a msg to 'register-player' socket.on('register-player', function(initials) { players[socket.id] = initials; //- Each socket has an unique id io.emit('update-player-list', Object.values(players)); }); //+ Client/Socket sent a msg to 'add-circle' socket.on('add-circle', function (data) { io.emit('add-circle', data); }); //+ Client/Socket sent a msg to 'clear-circles' socket.on('clear-circles', function(data) { io.emit('clear-circles', data); }); //+ Client/Socket sent a msg to 'disconnect' socket.on('disconnect', function() { delete players[socket.id]; //- Delete this id from the list io.emit('update-player-list', Object.values(players)); }); }); //! Export socket.io module.exports = io;
-
In
public/javascript/app.js
://! ,, ,, //! .g8"""bgd `7MM db mm //! .dP' `M MM MM //! dM' ` MM `7MM .gP"Ya `7MMpMMMb. mmMMmm //! MM MM MM ,M' Yb MM MM MM //! MM. MM MM 8M"""""" MM MM MM //! `Mb. ,' MM MM YM. , MM MM MM //! `"bmmmd' .JMML..JMML. `Mbmmd' .JMML JMML. `Mbmo //! Get our connection to the socket.io server const socket = io(); let circles = document.getElementById('circles'); //! Get circles from the DOM let buttom = document.getElementsByTagName('button'); //! Get buttom from the DOM let playersEl = document.getElementById('players'); //! Get palyers from the DOM let initials = ''; //! Listen to the server for the `add-circle` event socket.on('add-circle', function (data) { addCircle(data); }); //! Listen to the server for the `clear-circles` event socket.on('clear-circles', function(data) { circles.innerHTML = ''; }); //! Listen to the server for the `update-players-list` event socket.on('update-player-list', function(data) { let playerList = '<li>' + data.join('</li><li>') + '</li>'; playersEl.innerHTML = playerList; }); //! Create a click eventListner to send msg to the server circles.addEventListener('click', function(evt) { console.log(evt); socket.emit('add-circle', { initials: initials, x: evt.clientX, y: evt.clientY, dia: randomBetween(10,100), rgba: getRandomRGBA() }); }); //! Pop up to input the initials function getInitials() { const input = prompt('Please enter your initials'); return input ? input.toUpperCase() : ''; }; //! Get the initial do { initials = getInitials(); } while (initials.length < 2 || initials.length > 3); //! Emit to the server a player initials socket.emit('register-player', initials); //! Clear Button - eventListener on the button buttom[0].addEventListener('click', function() { socket.emit('clear-circles'); }); //! Add Circlet to the DOM function addCircle({ x, y, dia, rgba, initials }) { const el = document.createElement('div'); el.style.left = x - Math.floor(dia / 2 + 0.5) + 'px'; el.style.top = y - Math.floor(dia / 2 + 0.5) + 'px'; el.style.width = el.style.height = dia + 'px'; el.style.backgroundColor = rgba; el.style.fontSize = Math.floor(dia / 3) + 'px'; el.style.color = 'white'; el.style.textAlign = 'center'; el.style.lineHeight = dia + 'px'; el.innerHTML = initials; circles.appendChild(el); }; //! Random math to get the RGBA colors function randomBetween(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); }; //! Get random color function getRandomRGBA() { return [ 'rgba(', randomBetween(0, 255), ',', randomBetween(0, 255), ',', randomBetween(0, 255), ',', randomBetween(2, 10) / 10, ')' ].join(''); };
-
In
views/index.ejs
:<!DOCTYPE html> <html> <head> <title><%= title %></title> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <header><span>realtime-circles</span><button>clear</button></header> <section id="circles"></section> <footer><ul id="players"></ul></footer> <script src="/socket.io/socket.io.js"></script> <script src="/javascripts/app.js"></script> </body> </html>