Source: diff-sync/aerogear.diff-sync-client.js

/* AeroGear JavaScript Library
* https://github.com/aerogear/aerogear-js
* JBoss, Home of Professional Open Source
* Copyright Red Hat, Inc., and individual contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
    The AeroGear Differential Sync Client.
    @status Experimental
    @constructs AeroGear.DiffSyncClient
    @param {Object} config - A configuration
    @param {String} config.serverUrl - the url of the Differential Sync Server
    @param {Object} [config.syncEngine="AeroGear.DiffSyncEngine"] -
    @param {function} [config.onopen] - will be called when a connection to the sync server has been opened
    @param {function} [config.onclose] - will be called when a connection to the sync server has been closed
    @param {function} [config.onsync] - listens for "sync" events from the sync server
    @param {function} [config.onerror] - will be called when there are errors from the sync server
    @returns {object} diffSyncClient - The created DiffSyncClient
 */
AeroGear.DiffSyncClient = function ( config ) {
    if ( ! ( this instanceof AeroGear.DiffSyncClient ) ) {
        return new AeroGear.DiffSyncClient( config );
    }

    config = config || {};

    var ws,
        sendQueue = [],
        that = this,
        syncEngine = config.syncEngine || new AeroGear.DiffSyncEngine();

    if ( config.serverUrl === undefined ) {
        throw new Error( "'config.serverUrl' must be specified" );
    }

    /**
        Connects to the Differential Sync Server using WebSockets
    */
    this.connect = function() {
        ws = new WebSocket( config.serverUrl );
        ws.onopen = function ( e ) {
            if ( config.onopen ) {
                config.onopen.apply( this, arguments );
            }

            while ( sendQueue.length ) {
                var task = sendQueue.pop();
                if ( task.type === "add" ) {
                    send ( task.type, task.msg );
                } else {
                    that.sendEdits( task.msg );
                }
            }
        };
        ws.onmessage = function( e ) {
            var data, doc;

            try {
                data = JSON.parse( e.data );
            } catch( err ) {
                data = {};
            }

            if ( data ) {
                that._patch( data );
            }

            doc = that.getDocument( data.id );

            if( config.onsync ) {
                config.onsync.call( this, doc, e );
            }
        };
        ws.onerror = function( e ) {
            if ( config.onerror ) {
                config.onerror.apply( this, arguments );
            }
        };
        ws.onclose = function( e ) {
            if ( config.onclose ) {
                 config.onclose.apply( this, arguments);
            }
        };
    };

    // connect needs to be callable for implementing reconnect.
    this.connect();

    /**
        Disconnects from the Differential Sync Server closing it's Websocket connection
    */
    this.disconnect = function() {
        ws.close();
    };

    /**
        patch - an internal method to sync the data with the Sync Engine
        @param {Object} data - The data to be patched
    */
    this._patch = function( data ) {
        syncEngine.patch( data );
    };

    /**
        getDocument - gets the document from the Sync Engine
        @param {String} id - the id of the document to get
        @returns {Object} - The document from the sync engine
    */
    this.getDocument = function( id ) {
        return syncEngine.getDocument( id );
    };

    /**
        diff - an internal method to perform a diff with the Sync Server
        @param {Object} data - the data to perform a diff on
        @returns {Object} - An Object containing the edits from the Sync Engine
    */
    this._diff = function( data ) {
        return syncEngine.diff( data );
    };

    /**
        addDocument - Adds a document to the Sync Engine
        @param {Object} doc - a document to add to the sync engine
    */
    this.addDocument = function( doc ) {
        syncEngine.addDocument( doc );

        if ( ws.readyState === 0 ) {
            sendQueue.push( { type: "add", msg: doc } );
        } else if ( ws.readyState === 1 ) {
            send( "add", doc );
        }
    };

    /**
        sendEdits - an internal method to send the edits from the Sync Engine to the Sync Server
        @param {Object} edit - the edits to be sent to the server
    */
    this._sendEdits = function( edit ) {
        if ( ws.readyState === WebSocket.OPEN ) {
            //console.log( 'sending edits:', edit );
            ws.send( JSON.stringify( edit ) );
        } else {
            //console.log("Client is not connected. Add edit to queue");
            if ( sendQueue.length === 0 ) {
                sendQueue.push( { type: "patch", msg: edit } );
            } else {
                var updated = false;
                for (var i = 0 ; i < sendQueue.length; i++ ) {
                    var task = sendQueue[i];
                    if (task.type === "patch" && task.msg.clientId === edit.clientId && task.msg.id === edit.id) {
                        for (var j = 0 ; j < edit.edits.length; j++) {
                            task.msg.edits.push( edit.edits[j] );
                        }
                        updated = true;
                    }
                }
                if ( !updated ) {
                    sendQueue.push( { type: "patch", msg: edit } );
                }
            }
        }
    };

    /**
        sync - performs the Sync process
        @param {Object} data - the Data to be sync'd with the server
    */
    this.sync = function( data ) {
        var edits = that._diff( data );
        that._sendEdits( edits );
    };

    /**
        removeDoc
        TODO
    */
    this.removeDoc = function( doc ) {
        throw new Error( "Method Not Yet Implemented" );
    };

    /**
        fetch - fetch a document from the Sync Server.  Will perform a sync on it
        @param {String} docId - the id of a document to fetch from the Server
    */
    this.fetch = function( docId ) {
        var doc, edits, task;

        if ( sendQueue.length === 0 ) {
            doc = syncEngine.getDocument( docId );
            that.sync( doc );
        } else {
            while ( sendQueue.length ) {
                task = sendQueue.shift();
                if ( task.type === "add" ) {
                    send ( task.type, task.msg );
                } else {
                    that._sendEdits( task.msg );
                }
            }
        }
    };

    /**
        send
        @param {String} msgType
        @param {Object} doc
    */
    var send = function ( msgType, doc ) {
        var json = { msgType: msgType, id: doc.id, clientId: doc.clientId, content: doc.content };
        //console.log ( 'sending ' + JSON.stringify ( json ) );
        ws.send( JSON.stringify ( json ) );
    };
};