Search code examples
javascriptreal-timerethinkdbvotingdeepstream.io

Using deepstream.io for real-time voting


I made a prototype for a real-time voting system by using deepstream.io and rethinkdb as persistence store. So far, it works, and multiple browsers can stay in sync in getting the latest number of vote counts (see screenshot below), however, one thing I don't like it is the vote count is incremented in the browser, which is sent to the deepstream.io remote server.

the JS code embedded in the client's browser:

/**
        * Login to deepstream as Frank
        */
        var ds = deepstream( 'localhost:6020' ).login({ username: 'Frank'});
        var name = 'upvote';
        var upVotes;
        var voteText = $('.streaming-prices .vote');
        var record = ds.record.getRecord(name);
        $('#upvote_btn').click(function() {
            // on button click, increment the vote counts
            // set the record stored in the rethinkdb storage
            record.set({
                count: ++upVotes
            });
        });

        record.subscribe('count', function(newVal) {
            console.info('count updated, newVal: ', newVal);
            upVotes = newVal;
            voteText.text(newVal);
        });

the server.js code:

var PermissionHandler = require( './permission-handler' );

var DeepstreamServer = require( 'deepstream.io' ),
    RdbC = require( 'deepstream.io-storage-rethinkdb' ),
    server = new DeepstreamServer();

server.set('host', '0.0.0.0');
server.set('port', 6020);
server.set( 'tcpHost', '0.0.0.0' );
server.set( 'tcpPort', '6022' );
server.set( 'permissionHandler', new PermissionHandler() );

server.set( 'storage', new RdbC({
    port: 28015,
    host: '127.0.0.1',
    splitChar: '/',
    database: 'votings',
    defaultTable: 'question'
}));

server.start();

So you can see that the js code in the client increments the vote counts directly and updates the record, which is to be sent to the deepstream.io server for updating the database. I don't like this part, because I don't want the user to possibly mess up the total vote counts. Instead, I would like the client just to send something like +1 to the server, and let the server update the total counts for persistence. I am not sure if this is possible, can someone shed some light? I appreciate it

Screenshot for voting and showing vote counts


Solution

  • Good point, allowing clients to manipulate votes might not work so well!

    I would do it using an rpc, that way in the browser you can let a trusted provider increment the record on the users behalf.

    The three things you'll need to do is:

    1. request the rpc via the browser
    2. respond to it from a rpc provider ( a trusted client that can respond to the rpc )
    3. add permissions to reject any attempts to change the record from non-providers

    Code would look something as follows:

    The JS code in browser

    /**
    * Login to deepstream as Frank
    */
    var ds = deepstream( 'localhost:6020' ).login( { username: 'Frank'} );
    
    var voteText = $('.streaming-prices .vote');
    var name = 'vote';
    var record = ds.record.getRecord( name );
    
    $('#upvote_btn').click(function() {
        // on button click, increment the vote counts
        // set the record stored in the rethinkdb storage
        ds.rpc.make( 'upvote', {}, function( error, response ){
            //notify user when upvote succesfull
        });
    });
    
    record.subscribe('count', function( newVal ) {
        console.info( 'count updated, newVal: ', newVal);
        voteText.text(newVal);
    });
    

    The rpc provider that handles upvotes

    /**
    * Login to deepstream as Frank
    */
    var ds = deepstream( 'localhost:6021' ).login( { username: 'upvote-provider' } );
    
    var name = 'vote';
    var record = ds.record.getRecord( name );
    
    ds.rpc.provide( 'upvote', function( data, response ){
        record.set( 'count', record.get( 'count' ) + 1 );
        response.send();
    });
    

    The server should also have permissions to only allow the upvote-provider to change the votes.

    canPerformAction: function( username, message, callback ) {
        /**
         * If the user is a provider, it has all permissions
         */
        if( username === 'upvote-provider' ) {
            callback( null, true );
        }
        /**
         * Otherwise the client request is valid unless it's trying to change the score
         */
         else {
            var messageObj = Deepstream.readMessage( message );
            var isAllowed = messageObj.name !== 'vote' || messageObj.isRead;
            var errorMessage = isAllowed ? null : 'Can\'t change votes via client, use \'upvote\' rpc instead';
            callback( errorMessage, isAllowed );
        }
    }