Manual Moderation – multiple users

This is the final post on the manual moderation feature, where we will cover how to deal with the case of multiple users logged in moderating or viewing the same wall. The problem was that whenever two pages of the wall were open at the same time, there was no way to tell if another page was polling for tweets, so there would be duplicate tweets added to the database, which would then show up on both walls. This had to be solved as it is expected to have multiple displays or moderators for each wall.

To solve this, we needed to be able to store the user-wall id that was currently polling from the server. Not only did an open page have to check if there was another page polling, but also when the page was closed, the store would have to remove the user-wall id, so that when another open page checked it could start polling and store it’s user-wall id.

This connecting and disconnecting behavior can be detected through websocket events, and socket.io has a convenient way for us to listen to these events on the server:

[code language=”javascript”]
var server = express();
var s = http.createServer(server);
var io = require(‘socket.io’)(s);
var pollingWalls = {};
var clientIds = {};

io.on(‘connection’, function (socket) {

// Create and join UserwallId room when user opens page
socket.on(‘create’, function(userWallId) {
socket.join(userWallId);
});

// When close window, use the socket.id to remove from maps so another poll can pass
socket.on(‘disconnect’, function() {
var clientWallPolls = clientIds[socket.id];
clientWallPolls.forEach(function(wallId){
pollingWalls[wallId] = null;
})
delete clientIds[socket.id];
})

// Check duplicate, start if no one else polling
socket.on(‘checkDup’, function(data){
var clients_in_the_room = io.sockets.adapter.rooms[data.userWallId];
var isNoOneElsePolling = pollingWalls[data.userWallId] === socket.id || !pollingWalls[data.userWallId];
if(clients_in_the_room){
var result = clients_in_the_room.length === 1 || isNoOneElsePolling;
var responseEmit = ‘checkDupSuccess’+ data.userWallId+ data.socketId;
socket.emit(responseEmit, result);
}
})

// Start polling and mark poller
// Pre-cond: no one else polling / previous poller leaves
socket.on(‘addPollingWalls’, function(userWallId){
pollingWalls[userWallId] = socket.id;
var clientWalls = clientIds[socket.id];
if(clientWalls.indexOf(userWallId) === -1){
clientWalls.push(userWallId);
}
})
[/code]

I have pasted the server events(above) and client events(below) so that it is easier to view and explain:

[code language=”javascript”]
var init = function() {
// … other config
socket.emit(‘create’, $stateParams.user + $stateParams.id);
}

// Timeout that checks for multiple users on the same wall
vm.update2 = function(refreshTime) {
return $timeout(function() {
socket.emit(‘checkDup’, {userWallId:userWallId, socketId:socketId});
}, refreshTime);
};

// Event listener that polls if there are no duplicates
socket.on(‘checkDupSuccess’+userWallId+socketId, function(result){
if(result){
SearchService.initData(searchParams).then(successCb, errorCb);
socket.emit(‘addPollingWalls’, userWallId);
}
})
[/code]

In server.js, we use two objects – ‘pollingWalls‘ to map the walls that are currently polling to the userId, and ‘clientIds‘ to map each user to the walls that has the user has opened, so we do not have to traverse the whole object.

We also use socket.io rooms to check if there is no one else polling on the same wall. socket.join allows the connected user to join an existing room or create one if it does not exist. Each room is marked by their user-wall id. In the client code, (in wallDisplay.js the controller for the wall display page), the ‘create‘ event is emitted when a user opens a wall.

The other 2 event listeners on the server are to check if there are duplicate users.

Before each polling interval, in the timeout function below, the ‘checkDup‘ event is emitted, carrying with it the userwallId and the socketId. The event listener on server (as seen above) checks if there are no other users in the room with ‘io.sockets.adapter.rooms[data.userWallId]’ or nobody polling ‘var isNoOneElsePolling = pollingWalls[data.userWallId] === socket.id || !pollingWalls[data.userWallId];’. It then emits an event ‘checkDupSuccess’ carrying the boolean result to the specific ‘socket.id’ of the client that requested the check.

If the check passes then the angular ‘SearchService’ is called, and another event ‘addPollingWalls‘ is emitted to store the ‘userWallId’ and associate it with the client’s socket.id in the ‘pollingWalls‘ and ‘clientIds‘ object.

Create your own walls at: loklak-wall.herokuapp.com

 

Manual Moderation – multiple users