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("&", "&", "g") .replace("<", "<", "g") .replace(" ", " ", "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: