Socket.io

Executive Summary

Socket.io is a way for the server to inform the client that data has changed.

I had a front end that looked like something like Figure 1.

Figure 1

When the user clicked on "Import" the backend would get busy and change import the file. The status would be changed on the backend, and I wanted a way to show the status in the browser without doing a refresh. So, I used socket.io.

References

These are some of the references that I found useful:

Link to Reference Description
Sample application This is socket.io's sample chat application
Open Classroom Tutorial This tutorial has a good explanation with pictures of how it works
Set up for express This tutorial is a detailed and exact example of how to set the server side of socket.io. It is specifically designed for an express deployment. While I was reading it, I had some "Ah HA!" moments.
Where is that library? Once you get the module installed in the server, you access it by adding This left me wondering, how does it know where the library is? This stackoverflow question helped me answer that.
Tuts Plus This is a very thorough tutorial
Debugging These are some debugging tools explained

Installing and Code Changes

Server Side

In the server directory, you must install socket.io with this command:

npm install --save socket.io

You have to create a new server and wrap the express app in it. Before, the server/app.js code looked like this:

```var app = express();

// uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); app.use(logger('dev')); app.use(bodyParser.urlencoded({ extended: true}));


I added the config/socket.js file and changed server/app.js to be like this:

```var app = express();

// set up socket.io
var server = require('http').Server(app);
var io = require('socket.io')(server);
var socketConfig = require('./config/socket');
socketConfig.any(io);

// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.urlencoded({ extended: true}));

The config/socket.js code looked like this:

```var socketSave; var userCnt;

exports.any = function (io) { 'use strict'; var userCnt = 0; io.on('connection', function (socket) { socketSave = socket; userCnt++; console.log('users connected:' + userCnt);

socket.on('disconnect', function() {
  userCnt--;
  console.log('users connected:' + userCnt);
});

}); };

exports.socket = function () { return socketSave; };

exports.userCnt = function () { console.log('userCnt = ' + userCnt); return userCnt; };

So, this gave me a way to get the socket initialized and save it somewhere I could get at it again.

But the real trick is making the new server "listen" where the old server, app, used to listen.

Change from this:

 ```   app.listen(3000, function () {

to this:

``` server.listen(3000, function () {

When I was ready to send data to the client, I could require the config/socket file, make up the data to send, and then execute a socket.emit.  It looked like this:

```var socketConfig  = require('../../config/socket');

...

function socketEmit(doc) {
  var socket = socketConfig.socket();
  if (socket) {
    ImportStatus.filesByImportStatus (doc.importType, function (err, files) {
      if (!err) {
        var object = {};
        object.importType = doc.importType
        object.files = files;

        console.log('object = ' + JSON.stringify(object, null, 2));

        socket.emit('importStatus.update', object);
      }
      else {
        console.log('err = ' + err);
      }
    });
  }
};

You get to define the format of the data that you are going to send (object in the case above) and the event name ('importStatus.update' in the case above).

Client side

On, the client side, you must include the socket.io library. Put this in your index.html file:

```

At first, I was totally confused about how/where the client side was going to get that socket.io library.  It turns out that server that was made and configured in server/app.js:

```var server = require('http').Server(app);
var io = require('socket.io')(server);

And later, set as the server listening:

``` server.listen(<mark">3000</mark>, function () {


What that server is going to do is respond to a get on the "/socketio/socket.io.js" route.  Other stuff gets passed along to the express server, app, which our server knows about because of this line:

```var server = require('http').Server(app);

In the angular side, the client side, you can make a dinky little service that will make a socket for the client to use:

```'use strict';

angular.module('clientApp') .service('Socket', function () { this.socket = function () { return io('[url]:[port]'); }; });

In the above code, replace [url] with the base url of your website.  Replace [port] with the port you are listening on.  I was listening on 3000, so that was port.  My url was http://zakhelp-stage.zoyd.us, so my line of code looked like this:

```return io('http://zakhelp-stage.zoyd.us:3000');

Of course, whatever file you put this in needs to be included in index.html. You can inject this service into a controller and get updates from the server. Here are the pertinent parts of the controller:

```angular.module('clientApp') // make sure this is set to whatever it is in your client/scripts/app.js .controller ('AdminCtrl', [ '$scope', 'Socket', function ($scope, Socket) { ... var socket = Socket.socket();

socket.on('importStatus.update', function (data) {
  $scope.aUploadInfos.forEach(function(uploadInfo) {
    if (uploadInfo.errorParam === data.importType) {
      uploadInfo.importStatuses = data.files;
      $scope.$apply();
    }
  });
});// end Socket.on

What I was updating was buried in an array of arrays in the $scope, so I had to do $scope.$apply() to get angular to pay attention to it.


### Note about server configuration

When I first tried using it, I was getting nonsense like this in the browser console:

<span class="error">GET http://zakhelp-stage.zoyd.us:3000/socket.io/?EIO=3&transport=polling&t=LWRocpt net::ERR_CONNECTION_REFUSED</span>

The solution was a change on the nginx server configuration.  In the file "/etc/nginx/nginx.conf", these are the settings that had to be used:

```location / {
        proxy_pass http://localhost:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        }

results matching ""

    No results matching ""