Good day.
In this article I will discuss how to implement a system for monitoring user activity using Node.js and Socket.IO. It looks like this:
It was written for the ERP-system, in which the interaction of workers is important, in particular, to avoid situations when two operators will edit one product.
Task
So, for the server on Node.js (express, connect) it was necessary to implement this monitoring system, which would inform about the activity of users in real time (i.e., the user who left the system would immediately disappear from the “
Users online ” list, switched to another page - disappeared from "
This page is being viewed ", and the one who entered, accordingly, appeared in the lists).
At once I will make a reservation that due to the closedness of the system from the rest of the world, authentication is a mandatory start.
')
Well, before you start - those who are not very familiar with the basics of Socket.IO I advise you to look at the
typical implementation of the chat .
Customer
The client part in this case is implemented quite simply, so I will not focus on external beauty. I will only mention that the code below is included in the base template, i.e. present on every download page.
Here is a list of what we need: elements with id = "sockstat" (to display the connection status), with id = "alsohere" (for the list of those who view the page), id = "online" (for the list of all those who are online ), of course, such a thing
<script type="text/javascript" src="/js/lib/jquery.js"></script> <script type="text/javascript" src="/socket.io/lib/socket.io.js"></script>
,
and a small auxiliary function that makes elements of a bulleted list from an array:
function lister(arr) { var s = ''; $.each(arr, function(key, value) { s += '<li><b>' + value.name + '</b> (login: ' + value.login + ', id: ' + value.user_id + ')</li>'; }); return s; }
Now everything is ready. The mechanism is as follows:
$(document).ready(function() { var socket = io.connect("http://localhost:8080");
Leaving the page (following the link, for example), the socket connection with the server is first broken, and only then a new page is rendered and given to the client, resuming the connection. Therefore, in order to leave the page, the message about the gap in vain for a split second was not displayed, and this delay was made in half a second.
A little trick, but the user will always be able to confidently say: “There were no breaks!” Until the server really falls.
It seems simple? And so it is.
Server: surface
In the code of the project file being launched, in index.js, in addition to any pieces of type
app.configure();
We connect our handwritten sockets.js, which is responsible for working with the sockets necessary in our business:
require('./sockets');
In addition to actually processing the events, it contains the authorization function - which gives permission to create a connection for those who are suitable according to certain criteria. In our case, those who previously logged in.
In general, it looks
like this (note
sio.set ('authorization' ...) ).
And now the algorithm itself. For starters - in Russian.
There will be 2 labels here:
- online - a list of all users online
- alsohere - a list of users on the same page as the client
The steps in the algorithm are also 2 — handling the new connection and processing the message that the client has disconnected.
As soon as it connects to us (the server), i.e. passes authorization, enters the page and reports its location -
socket.emit("iamhere", { location: document.URL } );
customer, we must:
- remember who went and where (add it online)
- send updated to all active online
- send out to everyone viewing the same page - alsohere updated
When the client disconnects -
- remember who came out and where (remove it from online)
- send updated to all active online
- send to viewers the page from which the client has just left, updated alsohere
Is it logical We continue.
Server: a little deeper
To implement the above, I decided not to specifically load the socket.js file and rendered the “bring-bring” functions into a separate file - auth.js.
It is designed like this:
var auth = function () { "use strict";
The main socket.js block is:
sio.sockets.on('connection', function (socket) { var hs = socket.handshake; auth.addActiveUser({ login: hs.session.user, name: hs.session.username, id: hs.session.user_id });
Voila
Magic disclosure
In the auth module, all this data about users — about their connections and pages, sessions — is stored as an object in the private variable __activeUsers.
For each user, a field is created - __activeUsers [login], containing the following fields:
- name - username - not to be confused with the login ("vasya" / "Vasily Ivanovich")
- user_id - internal user id - like the previous field, is used for subsequent transfer to the client, for example, to generate links "/ users / user_id"
- locations is an array of objects consisting of two fields: path is the path to the open page and id is the id of the connection to the socket
Accordingly, the auth module exposes only public methods that work with the above variable. They are f-AI, consisting of
for ,
for..in ,
push 'her and
splice ' s
enumeration :
- addActiveUser - adds new user data to __activeUsers
- addPageActiveUser - adds a new open page to the user ( __activeUsers [sLogin] .locations.push ({path: sPath, id: sPath_id}); )
- getListActiveUser - returns a list of active users ( for (i in __activeUsers) {list.push (...
- getListByPageActiveUser - returns a list of users browsing the same page along the passed path
- getListByPageConnection - returns a list of user connections that refer to the same page to the passed page address
- getPageByIdConnection - by the connection id returns the address of the page (leaving the page the client sends us only the id, not the path)
- removeActiveUser - decrements the number of open pages to the user
- removePageActiveUser - removes a closed page from the list of open
By connecting to the server and successfully authenticating, the user gets a
socket.handshake with a unique id for that user.
Opening a new page, a unique
socket.id for this page is
brought to this connection. That is, 1 user with 10 open pages is 1 handshake.id and 10 different socket.id.
And then the matter of technology - by manipulating this data, we can always tell who and what we are viewing / editing using the above described data structure.
And the whole "complexity" of the implementation is to iterate over the object fields and arrays, taking the right one. And we can do it :)
So now you can look at the above piece of code again and everything will become clear.
I hope the principle of work you understand where any id will not confuse, where you need to insert timeouts to smooth the information transferred to the client; Well, with the implementation for your specific task problems should arise.
Good luck.
PS Opera (tested on v.11.62) when closing pages, unlike FF and chrome, does not bother sending a client disconnect message to the server. Therefore, users who have disconnected / left the page will be listed in the active list for a few more seconds, until the server automatically turns them off after a timeout.