# Node.js & Socket.io chat app with GeoLocation and User Agent support

Source: https://tpiros.dev/blog/node-jssocket-io-chat-app-with-geolocation-and-user-agent-support

If you've been reading this blog, you'll know I've previously written some posts about a Node.js/Socket.io chat application. I've had great feedback on those. I think what makes them work is that I documented my learning curve with these technologies, which helps people who are equally new to Node.js/Socket.io.

I keep updating the application and the code behind it. Today it's time for another round. After a few hours of work, I've added new features, refactored some code, and made the app look better (I still can't call myself a designer).

Let's walk through these changes, starting with the backend code.

I've started using the wonderful [underscorejs.org](http://underscorejs.org/) library for functional programming support in JavaScript. It's especially useful for operations on arrays and collections. Before, I was using hand-rolled code (or code grabbed from other places) to manipulate collections. With Underscore.js, things got simpler and cleaner. Consider this:

```js
Array.prototype.contains = function (k, callback) {
  var self = this;
  return (function check(i) {
    if (i >= self.length) {
      return callback(false);
    }
    if (self[i] === k) {
      return callback(true);
    }
    return process.nextTick(check.bind(null, i + 1));
  })(0);
};
```

That `contains` function checks whether an array holds a particular element. Here's how I used it before:

```js
room.people.contains(socket.id, function (found) {
  if (found) {
    //do something
  }
});
```

With Underscore.js, I can scrap the custom `contains` function and use its built-in version. The whole thing becomes cleaner when searching for a particular socket ID in the room list:

```js
if (_.contains(room.people, socket.id)) {
  //do something
}
```

Finding a particular element in the people collection using my old method looked like this:

```js
for (var key in people) {
  if (people[key].name === name) {
    exists = true;
    break;
  }
}
```

With Underscore.js, much simpler:

```js
_.find(people, function (key, value) {
  if (key.name === name) return (exists = true);
});
```

I'm mostly using `find`, `findWhere`, `contains`, `count`, and `without`. My personal favourite is `findWhere`, which collapsed a complicated set of `for` and `if` statements into two lines. When a client disconnects, I remove the socket ID from a `sockets` array like this:

```js
var o = _.findWhere(sockets, { id: socket.id });
sockets = _.without(sockets, o);
```

The code first finds the right element inside the `sockets` array using `id` as the parameter. Then `without` returns a copy of the array with that element removed.

Another feature I've added to this release: [User Agent](http://en.wikipedia.org/wiki/User_agent) detection. Every online user now has either a mobile or PC icon next to their username, showing what type of device they're connected from. I'm using `navigator.userAgent` on the client side:

```js
var device = 'desktop';
if (
  navigator.userAgent.match(
    /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i
  )
) {
  device = 'mobile';
}
```

This string gets sent to the backend, and the `device` value is stored as a key in the `people` collection. To swap the string for an icon, I'm using the [Font Awesome](http://fontawesome.io) library:

```js
$('#people').append(
  '<li class="list-group-item"><span>' +
    obj.name +
    '</span> <i class="fa fa-' +
    obj.device +
    '"></i></li>'
);
```

Here `obj.name` is the username and `obj.device` is the device identifier.

But that wasn't enough. How nice would it be to see where users are logging in from? Let's bolt on GeoLocation. Using the standard HTML5 GeoLocation API and Yahoo's Geolookup, I put together the following:

```js
if (navigator.geolocation) { //get lat lon of user
    navigator.geolocation.getCurrentPosition(positionSuccess, positionError, { enableHighAccuracy: true });
  } else {
    $("#errors").show();
    $("#errors").append("Your browser is ancient and it doesn't support GeoLocation.");
  }
  function positionError(e) {
    console.log(e);
  }

  function positionSuccess(position) {
    var lat = position.coords.latitude;
    var lon = position.coords.longitude;
    //consult the yahoo service
    $.ajax({
      type: "GET",
      url: "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20geo.placefinder%20where%20text%3D%22"+lat+"%2C"+lon+"%22%20and%20gflags%3D%22R%22&format=json",
      dataType: "json",
        success: function(data) {
        socket.emit("countryUpdate", {country: data.query.results.Result.countrycode});
      }
    });
  }
});
```

Once Yahoo's service returns data, I emit a message to my socket server. I add a new key to the people collection (`country`) and re-broadcast the people list with this extra key:

```js
socket.on('countryUpdate', function (data) {
  country = data.country.toLowerCase();
  people[socket.id].country = country;
  io.sockets.emit('update-people', { people: people, count: sizePeople });
});
```

Then I replace the `country` string with a flag icon using the [Flag Stripes](http://flag-sprites.com/) library.

I've also added the whisper function against each user. Click the link and the chat message input box automatically fills with the right format for whispering to that particular user.

All the code is up to date on [GitHub](https://github.com/tpiros/advanced-chat). Check the [setup instructions](https://github.com/tpiros/advanced-chat/blob/master/README.md#setup-and-configuration) as well.
