Warning!

This page applies to an older version of Orbited (Version 0.5) For info on the current version see the main documentation.


How to Write an IRC Client in JavaScript

This tutorial covers the protocol handling portion of writing an IRC client. Orbited comes with irc.js, a more complete IRC implementation.

Orbited 0.5 introduces TCPSocket, a socket interface for JavaScript. This allows JavaScript code to connect to network servers. IRC is a common, text-based protocol that is popular for chat. In this tutorial, we will write an IRC client in JavaScript much as we would write one in any other language.

Let's start by creating our files, IRC.html and IRC.js.

IRC.html

We need some simple HTML to provide input and output while we write the client.

<!DOCTYPE html>
<html>
<head>
    <script src="http://example.com:8000/static/TCPSocket.js"></script>
    <script src="IRC.js"></script>
    <script>
        // Use IRC client here (See below)
    </script>
</head>
<body>
    <div id="output"></div>
    <input type=text id=""><button onclick="post()">Send</button>
    <button onclick="client.connect(hostname,port)">Connect</button>
</body>
</html>

The first script tag loads the TCPSocket object from Orbited. (Be sure to replace example.com with your own address.) We will use TCPSocket to connect to a remote server. The Orbited daemon serves as the bridge between scripts in the browser and the target TCP server.

In the third script tag, where we will use the IRC client, we need to set a few connection parameters. We will also initialize the connection and hook it up to callbacks.

    <script>
        var hostname = "irc.freenode.net"
        var channel = "#orbited.tutorial"
        var port = 6667
        nickname = ""

        var client = new IRCClient()

        client.onopen = function() {
            nickname = prompt("Please enter a user name")
            client.nick(nickname)
            client.ident(nickname, "8 *", "Tutorial User")
            client.join(channel)
        }
        client.onnickInUse = function() {
            nickname = prompt("That name was taken. Please try another")
            client.nick(nickname)
            client.ident(nickname, "8 *", "Tutorial User")
            client.join(channel)
        }
        client.onmessage = function(sender, place, msg) {
            if (place == channel) {
                var nick = sender.slice(0, sender.search("!"))
                print_output(nick + ": " + msg)
            }
        }

This script hardcodes the username, irc server, and channel to join when the connection opens. You will probably want to dynamically set these parameters.

It is useful to define a print function that displays text on the page as it is received and another to send messages from the input box.

        var print_output = function(s) {
            var output = document.getElementById("output")

            // make output HTML safe
            s = s.replace("&", "&amp;", "g")
                     .replace("<", "&lt;", "g")
                     .replace(" ", "&nbsp;", "g")

            output.innerHTML += s + "<br>"
            output.scrollTop = output.scrollHeight
        }
        
        var send_message = function() {
            var input = document.getElementById("input")
            print_output(nickname + ": " + input.value)
            client.privmsg(channel, input.value)
        }
    </script>

IRC.js

At this point we can write the IRCClient object that makes this all happen.

IRCClient = function() {
    var self = this
    var conn = null
    var buffer = ""
    var ENDL = "\r\n"

We should make callback functions for users of this client to set in their code.

    self.onconnect = function() {/* Do nothing in default callbacks*/}
    self.onnickInUse = function() {}
    self.onmessage = function() {}
    self.onclose = function() {}

In a full-featured IRC client, you would want to have callbacks for other events in the [protocol]. In the interest of brevity, this tutorial only includes a few.

Our client object also needs public methods for performing various actions

    self.connect = function(hostname, port) {
        conn = new TCPSocket(hostname, port)
        conn.onopen = conn_opened
        conn.onclose = conn_closed
        conn.onread = conn_read
    }
    self.close = function() {
        conn.close()
        self.onclose()
    }
    self.ident = function(nickname, modes, real_name) {
        send("USER", nickname + " " + modes + " :" + real_name) 
    }
    self.nick = function(nickname) {
        send("NICK", nickname)
    }
    self.join = function(channel) {
        send("JOIN", channel)
    }
    self.quit = function(reason) {
        send("QUIT", ":" + reason)
        conn.close()
    }
    self.privmsg = function(destination, message) {
        send('PRIVMSG', destination + ' :' + message)
    }

As with the callbacks, there are many other methods you may want to implement in a complete client.

In self.connect, we made a TCPSocket and set several callbacks. We need to implement functions with those names in our client.

    var conn_opened = function() {
        self.onopen()
    }
    var conn_closed = function() {
        self.onclose()
    }
    var conn_read = function(data) {
        buffer += data
        parse_buffer()
    }

Finally, we need a few functions for internal use.

    var send = function(type, payload) {
        conn.send(type + " " + payload + ENDL)
    }
    var parse_buffer= function() {
        var msgs = buffer.split(ENDL)
        buffer = msgs[msgs.length-1]
        for (var i=0; i<msgs.length-1; i++)
            dispatch(msgs[i])
    }
    var parse_message = function(s) {        
        var msg = {}
        msg.prefix = ""
        if (s[0] == ":") {
            var first_space = s.search(" ")
            msg.prefix = s.slice(0, first_space).slice(1)
            s = s.slice(first_space + 1)
        }
        if (s.search(':') != -1) {
            var i = s.search(":")
            var payload = s.slice(i+1)
            s = s.slice(0,i-1)
            msg.args = s.split(' ')
            msg.args.push(payload)
        } else {
            msg.args = s.split(' ')
        }
        msg.type = msg.args.shift()
        return msg
    }
    var dispatch = function(line) {
        msg = parse_message(line)
        switch(msg.type) {
            case "PRIVMSG":
                self.onmessage(msg.prefix, msg.args[0], msg.args.slice(1).join("[]"))
                break
            case "433":
                //ERR_NICKNAMEINUSE
                console.log("nick taken")
                self.onnickInUse()
                break
            case "PING":
                send("PONG", ":" + msg.args)
                break
            default:
                break
        }
    }

} // End of IRCClient

Configuring Orbited

Assuming you have Orbited installed and working properly, you already have Freenode allowed in the access control list. If you want to connect to another server, you will need to configure access control.

Conclusion

At this point you should have a working client for the IRC protocol. You can use this technique to write clients for other protocols.

The complete code for this tutorial is attached here:

Attachments