1
\$\begingroup\$

I am an experienced Java programmer, but fairly new to JavaScript.

I am creating a chat library in JavaScript, and have it working, but want to know if I am going about this correctly, and following correct JavaScript coding standards.

I have a SDK object, LiveChatConnection class, and LiveChatListener and Credentials interfaces (I know classes and interfaces do not exist in JavaScript, but this seems to be how to create them).

To declare methods I am using

this.myMethod = function() {...};

I have seen other libraries put methods on the class prototype

MyClass.prototype.myMethod = function() {...}

or declare them like,

myMethod : function() {...}

Not sure which is best.

So my SDK will be packaged in a sdk.js file on my website, then users should be able to import and use it in their web pages.

Not sure if my code makes sense from a usage perspective, or if I'm missing anything else.

var SDK = {};

function Credentials() {
    this.host = "";
    this.app = "";
    this.url = "";
    this.applicationId = "";
}


/**
 * Listener interface for a LiveChatConnection.
 * This gives asynchronous notification when a channel receives a message, or notice.
 */
function LiveChatListener() {
    /**
     * A user message was received from the channel.
     */
    this.message = function(message) {};

    /**
     * An informational message was received from the channel.
     * Such as a new user joined, private request, etc.
     */ 
    this.info = function(message) {};

    /**
     * An error message was received from the channel.
     * This could be an access error, or message failure.
     */ 
    this.error = function(message) {};

    /**
     * Notification that the connection was closed.
     */
    this.closed = function() {};

    /**
     * The channels users changed (user joined, left, etc.)
     * This contains a comma separated values (CSV) list of the current channel users.
     * It can be passed to the SDKConnection.getUsers() API to obtain the UserConfig info for the users.
     */
    this.updateUsers = function(usersCSV) {};

    /**
     * The channels users changed (user joined, left, etc.)
     * This contains a HTML list of the current channel users.
     * It can be inserted into an HTML document to display the users.
     */
    this.updateUsersXML = function(usersXML) {};
}

/**
 * Connection class for a Live Chat, or chatroom connection.
 * A live chat connection is different than an SDKConnection as it is asynchronous,
 * and uses web sockets for communication.
 */
function LiveChatConnection(credentials) {
    this.debug = false;
    this.channel = null;
    this.user = null;
    this.credentials = credentials;
    this.socket = null;
    this.listener = null;
    this.keepAlive = false;
    this.keepAliveInterval = null;

    /**
     * Connection to the live chat server channel.
     * Validate the user credentials.
     * This call is asynchronous, any error or success with be sent as a separate message to the listener.
     */
    this.connect = function(channel, user) {
        if (this.credentials == null) {
            throw "Mising credentials";
        }
        this.channel = channel;
        this.user = user;
        var host = "ws://" + this.credentials.host + this.credentials.app + "/live/chat";
        if ('WebSocket' in window) {
            this.socket = new WebSocket(host);
        } else if ('MozWebSocket' in window) {
            this.socket = new MozWebSocket(host);
        } else {
            this.socket = new WebSocket(host);
            //throw 'Error: WebSocket is not supported by this browser.';
        }

        this.listener.connection = this;
        var self = this;

        this.socket.onopen = function () {
            if (self.user == null) {
                self.socket.send("connect " + self.channel.id + " " + self.credentials.applicationId);
            } else {
                self.socket.send(
                        "connect " + self.channel.id + " " + self.user.user + " " + self.user.token + " " + self.credentials.applicationId);                        
            }
            self.setKeepAlive(this.keepAlive);
        };

        this.socket.onclose = function () {
            self.listener.message("Info: Closed");
            self.listener.closed();
        };

        this.socket.onmessage = function (message) {
            user = "";
            data = message.data;
            text = data;
            index = text.indexOf(':');
            if (index != -1) {
                user = text.substring(0, index);
                data = text.substring(index + 2, text.length);
            }
            if (user == "Online-xml") {
                self.listener.updateUsersXML(data);
                return;
            }
            if (user == "Online") {
                self.listener.updateUsers(data);
                return;
            }

            if (self.keepAlive && user == "Info" && text.contains("pong")) {
                return;
            }
            if (user == "Info") {
                self.listener.info(text);
                return;
            }
            if (user == "Error") {
                self.listener.error(text);
                return;
            }
            self.listener.message(text);
        };
    };

    /**
     * Sent a text message to the channel.
     * This call is asynchronous, any error or success with be sent as a separate message to the listener.
     * Note, the listener will receive its own messages.
     */
    this.sendMessage = function(message) {
        this.checkSocket();
        this.socket.send(message);
    };

    /**
     * Accept a private request.
     * This is also used by an operator to accept the top of the waiting queue.
     * This can also be used by a user to chat with the channel bot.
     * This call is asynchronous, any error or success with be sent as a separate message to the listener.
     */
    this.accept = function() {
        this.checkSocket();
        this.socket.send("accept");
    };

    /**
     * Test the connection.
     * A pong message will be returned, this message will not be broadcast to the channel.
     * This call is asynchronous, any error or success with be sent as a separate message to the listener.
     */
    this.ping = function() {
        this.checkSocket();
        this.socket.send("ping");
    };

    /**
     * Exit from the current private channel.
     * This call is asynchronous, any error or success with be sent as a separate message to the listener.
     */
    this.exit = function() {
        this.checkSocket();
        this.socket.send("exit");
    };

    /**
     * Request a private chat session with a user.
     * This call is asynchronous, any error or success with be sent as a separate message to the listener.
     */
    this.pvt = function(user) {
        this.checkSocket();
        this.socket.send("pvt: " + user);
    };

    /**
     * Disconnect from the channel.
     */
    this.disconnect = function() {
        this.setKeepAlive(false);
        if (this.socket != null) {
            this.socket.disconnect();
        }
    };

    this.checkSocket = function() {
        if (this.socket == null) {
            throw "Not connected";
        }
    };

    this.toggleKeepAlive = function() {
        setKeepAlive(!this.keepAlive);
    }

    this.setKeepAlive = function(keepAlive) {
        this.keepAlive = keepAlive;
        if (!keepAlive && this.keepAliveInterval != null) {
            clearInterval(this.keepAliveInterval);
        } else if (keepAlive && this.keepAliveInterval == null) {
            this.keepAliveInterval = setInterval(
                    function() {
                        this.ping()
                    },
                    600000);
        }
    }
}

SDK.chime = function() {
    var sound = new Audio('chime.wav');
    sound.play();
}

SDK.url = "/botlibre/rest/botlibre";
SDK.tts = function(text) {
    try {
        var url = SDK.url + '/form-speak?&text=';
        url = url + encodeURIComponent(text);

        var request = new XMLHttpRequest();
        request.onreadystatechange = function() {
            if (request.readyState != 4) return;
            if (request.status != 200) {
                console.log('Error: Speech web request failed');
                return;
            }
            var audio = new Audio(request.responseText);
            audio.mediaGroup = 'voice';
            audio.play();
        }

        request.open('GET', url, true);
        request.send();
    } catch (error) {
        console.log('Error: Speech web request failed');
    }
}
\$\endgroup\$

migrated from stackoverflow.com Oct 2 '14 at 19:35

This question came from our site for professional and enthusiast programmers.

1
\$\begingroup\$

For adding methods, the preferred form is MyClass.prototype.myMethod = function() {...};

Imagine if you created a thousand instances of MyClass, you would be defining myMethod a thousand times as well, which is not necessary. By putting the method on the prototype, it is only defined once, though every instance of MyClass can still access it.

Also, it's good to be mindful of which methods need to be public, and which can be private. For example:

var MyClass = (function(){

    var myClass = function(){};
    var privateMethod = function(){ return 1; };
    myClass.prototype.publicMethod = function(){ return (privateMethod() + 1); };

    return myClass;
})();

In the above example, privateMethod is defined locally, so that it doesn't clutter the global namespace, and also doesn't clutter the object properties of a MyClass instance, yet it is still available to be used by publicMethod.

This pattern uses an immediately invoked function expression (IIFE) for defining the class, which is common in javascript library development.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy