Candidate #1: Chrome
Once I knew I was going for some type of raw tcp socket support an extensive google search was the next step. The first thing I stumbled upon was Chrome's packaged app Network Communications section. Naturally I dropped the research stage and went straight to hacking.Packaged apps deliver an experience as capable as a native app, but as safe as a web page. Just like web apps, packaged apps are written in HTML5, JavaScript, and CSS. But packaged apps look and behave like native apps, and they have native-like capabilities that are much more powerful than those available to web apps.
Getting started
I already knew from some work on my chrome plugin that developing extensions for Chrome is as easy as it gets. Throw together a manifest.json that has the correct permissions, and you're allowed to use the API.{
"manifest_version": 2,
"version": "0.1",
"name": "SchizIRC Chrome",
"app": {
"background": {
"scripts": ["main.js"]
}
},
"permissions": [{
"socket": [
"tcp-connect:*:6667",
"tcp-listen:*:6667"
]
}]
}
Main.js contains nothing fancy, It just listens for the onLaunched event and creates the main window with some predefined bounds.chrome.app.runtime.onLaunched.addListener(function() {
chrome.app.window.create('index-chrome.html', {
bounds: {
width: 980,
height: 650
}
}, function(createdWindow) {
appWindow = createdWindow.dom;
});
});
Loading a chrome app in developer mode is as easy as it gets as well:
- Visit chrome://extensions in your browser
- Ensure that the Developer mode checkbox in the top right-hand corner is checked.
- Click Load unpacked extension… to pop up a file-selection dialog.
- Find the directory where you cloned SchizIRC into and select it.
- Press 'Launch'
Note that regular CTRL+R, F5 or even window.location.reload() won't work, when developing a packaged app, but the main app window has a 'reload app' menu item in the contextmenu that does this. You will also be able to use the full Webkit Inspector from this menu.I threw together a basic layout using Divshot; Some tabs, a title, an input field and a button. This was enough to get me started on where I wanted to go. My app also got a working title: SchizIRC (because why not)
![]() |
| Divshot |
After taking all the assets offline (required for a chrome app, and needed to be done anyway for future packaging) I ended up with this directory structure:
manifest.json
index-chrome.html
main.js
index.html
css\bootstrap-combined.min.css
css\google-bootstrap.css
css\schizirc.css
js\Irc.js
js\controller.js
js\gui.js
js\vendor\mootools-more.js
js\vendor\mootools-core.js
js\vendor\mustache.js
js\vendor\bootstrap.min.js
Time to get classy
As shown above, I threw down some classes: Irc.js, Controller.js (which will server as the main kickstarter for the app, already eyeballing we're going to have several app wrappers / frameworks here that need a common entry point) and Gui.js, which will bind DOM events to my app's events. Code says more than words:/**
* Controller.js : Generic entry point for the app.
*/
Controller = new Class({
gui: false,
server: false,
config: { // some default config options i've prepared, these will be overwritten by the config in the future
server: 'irc.freenode.net',
port: '6667',
defaultChannels: '#schizirc',
nick: 'SchizoIRC',
userName: 'SchizoIRC',
realName: 'SchizoIRC v0.01',
appVersion: 'SchizoIRC v0.01',
finger: 'omnomnom',
password: null,
logging: true
},
initialize: function() {
console.log("IRC Client initted." );
this.irc = new IRC(this.config);
this.gui = new Gui();
this.irc.connect();
}
});
window.onload = function() {
window.app = new Controller();
};
User interface bindings are handled by the User Interface Class (which needs some refactoring for the future but will do for now)
/**
* Gui.js implements DOM bindings to change the user interface
* Will delegate user interaction to the app components that need it and implement the base tab view
*/
Gui = new Class({
Implements: [Events], // allow this class to fire and receive events.
initialize: function() {
window.addEvent('mousedown:relay(.nav-tabs li)', this.switchTabs); // handle all clicks on tab labels
window.addEvent('/tab/changed', this.showActiveTab); // a custom event channel fired when a tab has changed
window.addEvent('/tab/change', this.switchTab); // a custom event channel that changes a tab to $name
this.showActiveTab('home'); // always show the home tab.
},
switchTabs: function() {
$$(".nav-tabs li").removeClass('active'); // deactivate all tab labels.
$(this).addClass('active'); // activate the tab label that was clicked
window.fireEvent('/tab/changed', this.getAttribute('data-tab')); // fire our tab changed event.
},
switchTab: function(newTab) {
console.log("Change tab to: ", newTab, $$("li[data-tab="+newTab+"]"));
$$(".nav-tabs li").removeClass('active'); // hide all tab labels.
$$("li[data-tab="+newTab+"]").addClass('active'); // show our new tab
window.fireEvent('/tab/changed', newTab); // notify that the active tab has changed.
},
showActiveTab: function(name) {
$$('section[data-tab]').hide(); // hide all tab content panels
$$('section[data-tab='+name+']').show(); // show the one passed as name
$$('section[data-tab='+name+'] input[type=text]')[0].focus(); // focus the text field in the tab
}
});
Now on to the real deal, the IRC class. This will hold the connection object, the info about the server, channels, etc.
IRC = new Class({
Implements: [Options, Events],
options: { // set some default IRC options that will be overwritten with options passed from class Controller
server: 'irc.freenode.net',
port: '6667',
defaultChannels: '#schizirc',
nick: 'SchizoIRC',
userName: 'SchizoIRC',
realName: 'SchizoIRC v0.01',
appVersion: 'SchizoIRC v0.01',
finger: 'omnomnom',
autoJoinOnInvite: true
},
connection: false,
server: false,
channels: false,
initialize: function(options) {
this.setOptions(options, this.options); // merge the passed options with this.options.
if(!this.connection) { // create a new IRC Connection object that gets the current options.
this.connection = new IRC.Connection(this.options);
}
},
connect: function() {
this.connection.connect();
},
disconnect: function() {
this.connection.disconnect();
}
});

No comments:
Post a Comment