AngularJS + Socket.IO - how to update model? - node.js

So I have this basic CRUD Todo app that I've made with AngularJS. I then thought I wanted to spice it up with some Socket.IO to make it a real time web app. However, I'm having some problems getting it to work correctly.
Setup:
Yeoman (bower+grunt)
AngularJS
RequireJS
NodeJS + MongoDB
Socket.IO
My 'grunt server' runs on localhost:9000 whereas my NodeJS/MongoDB/Socket.IO server runs on localhost:4711
What works with Socket.IO at the moment is creating and deleting a todo, but I can't seem to figure out how to update my todos. Something I thought would be quite easy.
Here is the codez.
Socket.js:
var io = require('socket.io');
exports.initialize = function(server) {
io = io.listen(server);
io.sockets.on('connection', function(socket) {
socket.on('message', function(message) {
message = JSON.parse(message);
socket.get('deletedTodo', function(err, nickname) {
socket.broadcast.send(JSON.stringify(message));
socket.send(JSON.stringify(message));
});
});
socket.on('delete_todo', function(data) {
socket.set('deletedTodo', data.title, function() {
socket.emit('todo_delete', data);
socket.broadcast.emit('todoDeleted', data);
});
});
socket.on('update_todo', function(data) {
socket.broadcast.emit('todoUpdated', data);
})
});
}
And here is my client side codez.
Services.js:
/*global define*/
define(['angular'], function(angular) {
'use strict';
return angular.module('services', [])
.factory('Todo', function($resource) {
//var baseUrl = 'http://designit-todo.eu01.aws.af.cm/api/todos/:id';
var baseUrl = 'http://localhost:port/api/todos/:id';
return $resource(baseUrl,
{port: ':4711', id: '#_id'}, {
'update': {method:'PUT'},
'delete': {method:'DELETE', isArray: true}
});
})
.factory('socket', function($rootScope) {
var socket = io.connect('http://localhost:4711');
return {
on: function(eventName, callback) {
socket.on(eventName, function() {
var args = arguments;
$rootScope.$apply(function() {
callback.apply(socket, args);
})
});
},
emit: function(eventName, data, callback) {
socket.emit(eventName, data, function() {
var args = arguments;
$rootScope.$apply(function() {
if (callback) {
callback.apply(socket, args);
}
});
});
},
send: function(eventName, data, callback) {
socket.send(eventName, data, function() {
var args = arguments;
$rootScope.$apply(function() {
if (callback) {
callback.apply(socket, args);
}
});
});
}
}
});
});
Controllers.js:
/*global define*/
define(['angular', 'services/services'], function(angular) {
'use strict';
return angular.module('controllers', ['services'])
.controller('Todos', ['$scope', '$resource', 'Todo', '$location', 'socket', function($scope, $resource, Todo, $location, socket) {
// Grab all todos.
$scope.todos = Todo.query();
socket.on('message', function(data) {
// Grab all todos when new added via socket.
$scope.todos = Todo.query();
$scope.todos.push(data);
});
// Complete/update todo.
$scope.todoCompleted = function(todo) {
todo.$update();
socket.emit('update_todo', todo);
}
socket.on('todoUpdated', function(todo) {
// Update on all connected clients.
console.log(todo);
});
$scope.removeTodo = function(todo) {
var index = $scope.todos.indexOf(todo);
socket.emit("delete_todo", JSON.stringify(index));
$scope.todos.splice(index, 1);
todo.$remove();
}
socket.on('todoDeleted', function(index) {
$scope.todos.splice(index, 1);
});
}])
.controller('Single', ['$scope', '$resource', '$routeParams', 'Todo', '$timeout', '$location', function($scope, $resource, $routeParams, Todo, $timeout, $location) {
// Grab just a single todo
$scope.todo = Todo.get({ id: $routeParams.id });
// Throttle the update PUT request
var saveTimeout;
$scope.save = function() {
$timeout.cancel(saveTimeout);
saveTimeout = $timeout(function() {
// Save the todo and then update the scope
$scope.todo.$update(function(updated_todo) {
$scope.todo = updated_todo;
});
}, 1000);
};
}])
.controller('Add', ['$scope', '$resource', 'Todo', '$location', 'socket', function($scope, $resource, Todo, $location, socket) {
$scope.todo = new Todo({});
$scope.save = function() {
if ($scope.todo.title) {
$scope.todo.$save(function(data) {
console.log(data);
socket.send(JSON.stringify(data));
$location.path('/');
});
}
}
}]);
});
So my problem is within this piece of code:
// Complete/update todo.
$scope.todoCompleted = function(todo) {
todo.$update();
socket.emit('update_todo', todo);
}
socket.on('todoUpdated', function(todo) {
// Update on all connected clients.
console.log(todo);
});
When I console.log(todo), I get the todo, but I'm able to do a "todo.$update" on it. I've also tried something like:
socket.on('todoUpdated', function(todo) {
var thisTodo = Todo.get({id: todo._id});
console.log(thisTodo);
});
But as soon as I try to do thisTodo.$update() I get an error:
PUT http://localhost:4711/api/todos 404 (Not Found) :4711/api/todos:1
What am I doing wrong? And please correct me if I'm doing anything wrong within my code. I'm still fairly new to AngularJS and Socket.IO.

I found another solution, but not sure if it's the right way to do it:
socket.on('todoUpdated', function(updated_todo) {
for (var i in $scope.todos) {
if ($scope.todos[i]._id == updated_todo._id) {
$scope.todos[i] = updated_todo;
}
}
});
So in the Todos controller I have the $scope.todos which holds all todos. I then loop through all these, find a match on the id and sets the found todo to the newly updated todo.
If there's a better way to do it, please do tell me :)

Related

Backbone and Express: concatinating (duplicating) routes on res.redirect

I have an action where I need to update MongoDB entry including _id field, which requires deleting old entry and making a new one, here is server side:
exports.update = function(req, res, next){
var outcome = [];
outcome.previousId = req.params.id;
outcome.newId = req.body.name;
var getPreviousRecord = function(callback) {
req.app.db.models.AccountGroup
.findOne({ _id: req.params.id })
.lean()
.exec(function(err, accountGroups) {
if (err) {
return callback(err, null);
}
outcome.accountGroups = accountGroups;
return callback(null, 'done');
});
};
var makeNewRecord = function(callback) {
var permissions = outcome.accountGroups.permissions;
var fieldsToSet = {
_id: outcome.newId.toLowerCase(),
name: outcome.newId,
permissions: permissions
};
req.app.db.models.AccountGroup
.create(fieldsToSet, function(err, record) {
if (err) {
return callback(err, null);
}
outcome.record = record;
return callback(null, 'done');
});
};
var deletePreviousRecord = function() {
req.app.db.models.AccountGroup
.findByIdAndRemove(outcome.previousId)
.exec(function(err) {
if (err) {
return next(err);
}
res.redirect('admin/account-groups/' + outcome.newId + '/');
});
};
var asyncFinally = function(err) {
if (err) {
return next(err);
}
};
require('async').series([getPreviousRecord, makeNewRecord, deletePreviousRecord], asyncFinally);
};
It works fine, but I can't make this work normally on the front-end, it returns me both old route and a new route, for example:
PUT /admin/account-groups/customers22/admin/account-groups/Customers2233/ 404 213.749 ms - 31
where customers22 is old _id and customers2233 is new _id. If I navigate from another page to new entry it gets route normally.
On client side:
(function() {
'use strict';
app = app || {};
app.Details = Backbone.Model.extend({
idAttribute: '_id',
defaults: {
success: false,
errors: [],
errfor: {},
name: ''
},
url: function() {
return '/admin/account-groups/'+ app.mainView.model.id +'/';
},
parse: function(response) {
if (response.accountGroup) {
app.mainView.model.set(response.accountGroup);
delete response.accountGroup;
}
return response;
}
});
app.DetailsView = Backbone.View.extend({
el: '#details',
events: {
'click .btn-update': 'update'
},
template: Handlebars.compile( $('#tmpl-details').html() ),
initialize: function() {
this.model = new app.Details();
this.syncUp();
this.listenTo(app.mainView.model, 'change', this.syncUp);
this.listenTo(this.model, 'sync', this.render);
this.render();
},
syncUp: function() {
this.model.set({
_id: app.mainView.model.id,
name: app.mainView.model.get('name')
});
},
render: function() {
this.$el.html(this.template( this.model.attributes ));
for (var key in this.model.attributes) {
if (this.model.attributes.hasOwnProperty(key)) {
this.$el.find('[name="'+ key +'"]').val(this.model.attributes[key]);
}
}
},
update: function() {
this.model.save({
name: this.$el.find('[name="name"]').val()
});
}
});
app.MainView = Backbone.View.extend({
el: '.page .container',
initialize: function() {
app.mainView = this;
this.model = new app.AccountGroup( JSON.parse( unescape($('#data-record').html()) ) );
// ...
app.detailsView = new app.DetailsView();
}
});
$(document).ready(function() {
app.mainView = new app.MainView();
});
}());
It probably requires to trigger both model.save and model.destroy or prevent URL being used. Any advice on how to do it is appreciated, thank you.
Edit
Just a typo mistake here that is not related to the question, recklessly checking routes, see as cancelled
I believe the problem is here:
res.redirect('admin/account-groups/' + outcome.newId + '/');
That's a relative path so it'll be appended onto the current URL. I suspect you want something like this:
res.redirect('/admin/account-groups/' + outcome.newId + '/');

nodejs - test failing but callback being called

I have a module which I export and which has a method editHeroImage which I am trying to test using mocha, chai and sinon. The modules has two objects that are passed as arguments, connection and queries. These are mySql objects, one containing the connection to the database and the other the query strings which are defined in their separate modules. The expObj which I am exporting and trying to test is a "helper" module.
I have successfully tested other methods of this module in the same way I am trying to test this method, but, however when I run into methods which use the async module for some reason, my tests no longer behave as expected. I wonder if I am missing something in this particular case, because I have tested other modules and methods which also use async and have not come across this behaviour.
When I run the tests, it logs "HELLO!" as expected but the assertion that the callbackSpy has been called, fails.
I am losing my mind here! Please help! What is going on? Could there be contamination between test suits?
Method under test:
expObj.editHeroImage = function(connection, queries, postId, postData, callback) {
async.waterfall([
function(next) {
var qString = queries.getSinglePostById();
connection.query(qString, [postId], function(err, results) {
if (err) {
return next(err);
}
if (!results.length) {
console.log('NO POST FOUND WITH ID ' + postId);
return callback();
}
next(null, results[0].hero_image);
});
},
function(heroImageId, next) {
if (!heroImageId) {
console.log('HERO IMAGE IS NEW - NEXT TICK!');
return next();
}
// Delete resized images of hero image
var queryStr = queries.deleteResizedImages();
var resizedVals = [heroImageId];
connection.query(queryStr, resizedVals, function(err) {
if (err) {
return callback(err);
}
console.log('DELETED RESIZED IMAGES OF HERO IMAGE ' + heroImageId);
var qString = queries.updateHeroImagePath();
var values = [postData.hero_image, heroImageId];
return connection.query(qString, values, function(err, results) {
if (err) {
return next(err);
}
console.log('UPDATED HERO IMAGE ' + heroImageId + ' WITH PATH ' + postData.hero_image);
next('break');
});
});
},
function addHeroImage(next) {
var qString = queries.insertImage();
var values = [postData.hero_image, postId];
connection.query(qString, values, function(err, results) {
if (err) {
return next(err);
}
next(null, results.insertId);
});
},
function addHeroImagePathToPost(heroImageId, next) {
var qString = queries.saveHeroImageId();
var values = [heroImageId, postId];
connection.query(qString, values, function(err) {
if (err) {
return next(err);
}
next();
});
}
], function(err) {
if (err && err !== 'break') {
return callback(err);
}
console.log('HELLO!');
callback(null);
});
};
Test, with set-up:
'use strict';
var chai = require('chai');
var sinonChai = require("sinon-chai");
var proxyquire = require('proxyquire');
var sinon = require('sinon');
chai.use(sinonChai);
var expect = chai.expect;
describe('HELPERS', function() {
var testedModule,
callbackSpy,
fakeConnectionObj,
fakeQueriesObj,
fakePost,
fakeSnakeCaseObj,
queryStub,
connectionStub,
manageStub,
fakeCamelCaseObj;
beforeEach(function() {
fakePost = {};
fakeConnectionObj = {};
fakeQueriesObj = {
getPostIdFromImage: function() {},
insertResizedImages: function() {},
createPost: function() {},
getPostImages: function() {},
getPostsAlternativesImages: function() {},
getSinglePostById: function() {},
getAllImages: function() {},
insertImage: function() {},
deleteMainImage: function() {},
deleteResizedImages: function() {},
updateHeroImagePath: function() {},
saveHeroImageId: function() {}
};
afterEach(function() {
queryStub.resetBehavior();
});
fakeSnakeCaseObj = {
sub_title: '123',
hero_image: '456'
};
fakeCamelCaseObj = {
subTitle: '123',
heroImage: '456'
};
callbackSpy = sinon.spy();
queryStub = sinon.stub();
manageStub = sinon.stub();
connectionStub = {query: queryStub};
testedModule = proxyquire('./../../../../lib/modules/mySql/workers/helpers', {
'./../../../factories/notification-service': {
select: function() {
return {manageSns: manageStub};
}
}
});
});
it('edits hero image', function() {
var _post = {
id: '123',
title: 'vf',
sub_title: 'vf',
slug: 'vf',
reading_time: 4,
created_at: '123',
published_at: '123',
deleted_on: false,
hero_image: 'hero_image_path'
};
var _postId = '123';
queryStub.onCall(0).callsArgWith(2, null, [{hero_image: '55'}]);
queryStub.onCall(1).callsArgWith(2, null);
queryStub.onCall(2).callsArgWith(2, null);
testedModule.editHeroImage(connectionStub, fakeQueriesObj, _postId, _post, function() {
console.log(arguments); // --> {'0': null} as expected
callbackSpy.apply(null, arguments);
});
expect(callbackSpy).has.been.calledWith(null);
});
});
Your assertion is probably executing before your async function has returned.
There are a number of ways to ensure your async functions have finished executing. The cleanest is to format your mocha test differently.
describe('...', function () {
var callbackSpy;
before(function () {
var _post = {
id: '123',
title: 'vf',
sub_title: 'vf',
slug: 'vf',
reading_time: 4,
created_at: '123',
published_at: '123',
deleted_on: false,
hero_image: 'hero_image_path'
};
var _postId = '123';
queryStub.onCall(0).callsArgWith(2, null, [{
hero_image: '55'
}]);
queryStub.onCall(1).callsArgWith(2, null);
queryStub.onCall(2).callsArgWith(2, null);
return testedModule.editHeroImage(connectionStub, fakeQueriesObj, _postId, _post, function () {
console.log(arguments); // --> {'0': null} as expected
callbackSpy.apply(null, arguments);
});
});
it('edits hero image', function () {
expect(callbackSpy).has.been.calledWith(null);
});
});
Notice that I have wrapped your assertion in a describe block so we can use before. Your actual logic for setting up stubs and executing the class has been moved to the before block and a return added, this ensures the async function is complete before moving on to your assertions.
Your other tests may have passed, but they will also be susceptible to this and it is purely a timing issue.
Indeed #Varedis was right about it being a timing issue. However using your suggestion of wrapping the assertion in a describe bloack and using the before function to set-up the test resulted in my stubs no longer working correctly. However taking your suggestion about timing into account I managed to solve the issue by using the done callback within my test suit. By keeping the set-up I made a slight change and my tests suddenly passed:
it('edits hero image', function(done) {
var _post = {
id: '123',
title: 'vf',
sub_title: 'vf',
slug: 'vf',
reading_time: 4,
created_at: '123',
published_at: '123',
deleted_on: false,
hero_image: 'hero_image_path'
};
var _postId = '123';
queryStub.onCall(0).callsArgWith(2, null, [{hero_image: '55'}]);
queryStub.onCall(1).callsArgWith(2, null);
queryStub.onCall(2).callsArgWith(2, null);
testedModule.editHeroImage(connectionStub, fakeQueriesObj, _postId, _post, function() {
callbackSpy.apply(null, arguments);
expect(callbackSpy).has.been.calledWith(null);
expect(callbackSpy).has.not.been.calledWith('FDgdjghg');
done();
});
});

node opc-ua - how do i discover a variable in a server?

I am learning node opc-ua and have followed the example provided in the GitHub page for the sample_server.js and simple_client.js.
In the sample_server I add a variable when constructing the address space of the server such as:
//this code is in the server
addressSpace.addVariable({
componentOf: device,
nodeId: "ns=1;s=variable_1",
browseName: "MyVariable1",
dataType: "Double",
value: {
get: function () {
return new opcua.Variant({ dataType: opcua.DataType.Double, value: variable1 });
}
}
});
Here is the whole server code for reference:
var opcua = require("node-opcua");
var server = new opcua.OPCUAServer({
port: 4334, // the port of the listening socket of the server
resourcePath: "UA/MyLittleServer", // this path will be added to the endpoint resource name
buildInfo: {
productName: "MySampleServer1",
buildNumber: "7658",
buildDate: new Date(2014, 5, 2)
}
});
server.initialize(post_initialize);
function post_initialize() {
console.log("initialized");
construct_my_address_space(server, function () {
server.start(function () {
console.log("Server is now listening ... ( press CTRL+C to stop)");
console.log("port ", server.endpoints[0].port);
var endpointUrl = server.endpoints[0].endpointDescriptions()[0].endpointUrl;
console.log(" the primary server endpoint url is ", endpointUrl);
});
});
}
function construct_my_address_space(server, callback) {
var addressSpace = server.engine.addressSpace;
// declare a new object
var device = addressSpace.addObject({
organizedBy: addressSpace.rootFolder.objects,
browseName: "MyDevice"
});
// add a variable named MyVariable1 to the newly created folder "MyDevice"
var variable1 = 1;
// emulate variable1 changing every 500 ms
setInterval(function () { variable1 += 1; }, 500);
addressSpace.addVariable({
componentOf: device,
nodeId: "ns=1;s=variable_1",
browseName: "MyVariable1",
dataType: "Double",
value: {
get: function () {
return new opcua.Variant({ dataType: opcua.DataType.Double, value: variable1 });
}
}
});
callback();
}
Now, in the client i want to discover this variable by name or the full nodeId.
I am using the sample provided to browse the session to the server but in the return i only see FolderType, Objects, Types and Views and can not locate this variable anywhere.
Here is the client code:
var opcua = require("node-opcua");
var async = require("async");
var client = new opcua.OPCUAClient();
var endpointUrl = "opc.tcp://" + require("os").hostname() + ":4334/UA/MyLittleServer";
var the_session, the_subscription;
async.series([
// step 1 : connect to
function (callback) {
client.connect(endpointUrl, function (err) {
if (err) {
console.log(" cannot connect to endpoint :", endpointUrl);
} else {
console.log("connected !");
}
callback(err);
});
},
// step 2 : createSession
function (callback) {
client.createSession(function (err, session) {
if (!err) {
the_session = session;
}
callback(err);
});
},
// step 3 : browse
function (callback) {
the_session.browse("RootFolder", function (err, browse_result) {
if (!err) {
console.log('Result of browsing: ', browse_result);
browse_result[0].references.forEach(function (reference) {
console.log('browsing: ', reference.browseName);
console.log(reference);
console.log("**************************************");
});
}
callback(err);
});
},
/* step 4 : read a variable with readVariableValue
function (callback) {
the_session.readVariableValue("ns=1;s=variable_1", function (err, dataValue) {
if (!err) {
console.log(" var 1 = ", dataValue.toString());
}
callback(err);
});
},
// step 4' : read a variable with read
function (callback) {
var max_age = 0;
var nodes_to_read = [
{ nodeId: "ns=1;s=variable_1", attributeId: opcua.AttributeIds.Value }
];
the_session.read(nodes_to_read, max_age, function (err, nodes_to_read, dataValues) {
if (!err) {
console.log(" variable 1 = ", dataValues[0]);
}
callback(err);
});
},
*/
// step 5: install a subscription and install a monitored item for 10 seconds
function (callback) {
the_subscription = new opcua.ClientSubscription(the_session, {
requestedPublishingInterval: 1000,
requestedLifetimeCount: 10,
requestedMaxKeepAliveCount: 2,
maxNotificationsPerPublish: 10,
publishingEnabled: true,
priority: 10
});
the_subscription.on("started", function () {
console.log("subscription started for 2 seconds - subscriptionId=", the_subscription.subscriptionId);
}).on("keepalive", function () {
console.log("keepalive");
}).on("terminated", function () {
callback();
});
setTimeout(function () {
the_subscription.terminate();
}, 10000);
// install monitored item
var monitoredItem = the_subscription.monitor({
nodeId: opcua.resolveNodeId("ns=1;s=variable_1"),
attributeId: opcua.AttributeIds.Value
},
{
samplingInterval: 100,
discardOldest: true,
queueSize: 10
},
opcua.read_service.TimestampsToReturn.Both
);
console.log("-------------------------------------");
monitoredItem.on("changed", function (dataValue) {
console.log("variable_1 = ", dataValue.value.value);
});
},
// close session
function (callback) {
the_session.close(function (err) {
if (err) {
console.log("session closed failed ?");
} else{
console.log("session closed!");
}
callback();
});
}
],
function (err) {
if (err) {
console.log(" failure ", err);
} else {
console.log("done!");
}
client.disconnect(function () { });
});
Thanks in advance
You need to drill down from from the rootFolder and investigate the various objects until you find the variable you want to monitor, with a series of browseNode.
The best option is to use a free OPCUA client from a commercial vendor such as UAExpert or Prosys OPC UA Client to name a few.
You can also use opcua-commander: This little utility will allow you to explore the address space of the server and find the node you're looking for. ( source code available on github )

Using the node-robosmart calls

I have been using the code from github sandeepmistry/node-robosmart. Specifically I am doing this:
var async = require('async');
var RoboSmart = require('./index');
var found =0;
RoboSmart.discover(function(roboSmart) {
async.series([
function(callback) {
console.log('connect');
roboSmart.connect(callback);
},
function(callback) {
console.log('discoverServicesAndCharacteristics');
roboSmart.discoverServicesAndCharacteristics(callback);
},
function(callback) {
roboSmart.getLightName(function(lightName) {
console.log('light name = ' + lightName);
if(lightName = 'XXXX'){
found=1;
console.log(found);
}
callback();
});
},
function(callback) {
console.log('disconnect');
roboSmart.disconnect(callback);
}
],
function() {
process.exit(0);
});
});
The code works great and I am able to do other stuff. What I am having trouble with is using the RoboSmart.discoverAll(timeout, callback(devices)); // Returns array of devices discovered within command.
When I use this code:
var async = require('async');
var RoboSmart = require('./index');
var found =0;
RoboSmart.discoverAll(2000,function(devices) {
async.series([
function(callback) {
}
],
function() {
process.exit(0);
});
});
~
It Hangs. Can someone help me with using this function?

How to create online users list using webrtc and nodejs on the server end

I am using webrtc to make a audio, video and chat application where I need keep all the users in a user list in the serverside. Need help how to get this done.
Also, how can I remove users from the list when they logout from the system.
Need help to implement this.
webRTC.rtc.on('connect', function(rtc) {
//Client connected
});
webRTC.rtc.on('send answer', function(rtc) {
//answer sent
});
webRTC.rtc.on('disconnect', function(rtc) {
//Client disconnect
//console.log(webRTC);
});
webRTC.rtc.on('chat_msg', function(data, socket) {
var roomList = webRTC.rtc.rooms[data.room] || [];
for (var i = 0; i < roomList.length; i++) {
var socketId = roomList[i];
if (socketId !== socket.id) {
var soc = webRTC.rtc.getSocket(socketId);
if (soc) {
soc.send(JSON.stringify({
"eventName": "receive_chat_msg",
"data": {
"messages": data.messages,
"id": data.id,
"from": data.from,
"status": data.status,
"email": data.email
}
}), function(error) {
if (error) {
console.log(error);
}
});
}
}
}
});
As I was using webrtc.io module, so below are the methods that helped me to create the userlist and maintain the presence.
webRTC.rtc.on('join_room', function(data, socket) {
// Will get info who joined along with his socket id
}
And
webRTC.rtc.on('room_leave', function(room, socketid) {
// Will get info who left the room
}
Node.js code:
var users = {};
io.sockets.on('connection', function (socket) {
socket.emit('connect', true);
socket.on('message', function (data) {
socket.broadcast.emit('message', data);
});
socket.on('new-user', function (username) {
users[username] = username;
});
socket.on('check-presence', function (username) {
var isUserPresent = !! users[username];
socket.emit('presence', isUserPresent);
});
socket.on('remove-user', function (username) {
var user = users[username];
if (user) delete users[username];
});
});
This may also work (node.js):
var users = {};
io.sockets.on('connection', function (socket) {
var UserName;
socket.emit('connect', true);
socket.on('message', function (data) {
socket.broadcast.emit('message', data);
});
socket.on('new-user', function (username) {
users[username] = username;
UserName = username;
});
socket.on('check-presence', function (username) {
var isUserPresent = !! users[username];
socket.emit('presence', isUserPresent);
});
// removing user on "disconnect"
socket.on('disconnect', function () {
var user = users[UserName];
if (user) delete users[UserName];
});
});
For 1st case; client-side code:
var socket = io.connect();
socket.on('connect', function () {
socket.emit('new-user', 'username');
});
function removeUser() {
socket.emit('remove-user', 'username');
}
window.onbeforeunload = function () {
removeUser();
};
// if someone pressed "F5" key to refresh the page
window.onkeyup = function (e) {
if (e.keyCode == 116)
removeUser();
};
// if someone leaves via <a href>
var anchors = document.querySelectorAll('a'),
length = anchors.length;
for (var i = 0; i < length; i++) {
var a = anchors[i];
if (a.href.indexOf('#') !== 0 && a.getAttribute('target') != '_blank')
a.onclick = function () {
removeUser();
};
}
For 2nd case; client side code:
var socket = io.connect();
socket.on('connect', function () {
socket.emit('new-user', 'username');
});
You can check presence too:
socket.on('presence', isUserPresent) {
// boolean: user is present or not
});
socket.emit('check-presence', 'username');

Resources