Linking Socket.IO to Qooxdoo

Qooxdoo is a great framework, but sometimes you need to add extra content to it, and this is not so simple.

Here is a basic class and idea to link Socket.IO client API to Qooxdoo framework :

Before playing

Before doing anything with Socket.IO, you first need to include thooses files into your project.
Start getting Node.JS, and installing Socket.IO using « npm install socket.io ». This should install in node_module (in node.js root folder) socket.io folder. Inside this node_module, you should find the client API of Socket.IO here :
%NODE_ROOT%/node_modules/socket.io/node_modules/socket.io-client/dist

The dist folder should contains 4 files :

  • socket.io.js
  • socket.io.min.js
  • WebSocketMain.swf
  • WebSocketMainInsecure.swf

Regarding what you want, you need only 3 files : choose between the socket.io.js and the min version. I will prefer here the minimal but it does not change so much things after all.

I didn’t find the good way to put thooses files in the qooxdoo main project, but this should do the trick :
On resource folder, create a script folder, create inside this folder a « socket » (what you want for this one) folder. Put 3 files into this one.

Now you have the socket.io resource folder on your Qooxdoo app. But it’s not enough for now.

On the config.json file, you need to force the system to copy it, I use here the « build-script » which is the remapped default build folder :

"jobs":{
	"build-script":{
		"copy-files" :{
			"files":[
				"./script/socket/"
			],
			"source" : "./source/resource/",
			"target"  : "./build/"
		},
		"add-script":[{
			"uri":"script/socket/socketio.js"
		}]
(...)

The add-script is the most important part, it force the system to load the script as part of the qooxdoo launch. The script will be launched around Application.main start (I didn’t check exactly but before it’s too early and at the end of main it’s available).

Now you can play with Socket.IO over Qooxdoo, here is a basic class to deal with inside Qooxdoo system (under LGPLv3, like all sources I post here) :

/**
 * This class is a direct link with socket.io.
 */
qx.Class.define("myapp.api.WebSocket",{
	extend : qx.core.Object,

	//Socket.io events
	events :{
		/** socket.io connect event */
		"connect"			: "qx.event.type.Event",
		/** socket.io connecting event */
		"connecting"		: "qx.event.type.Data",
		/** socket.io connect_failed event */
		"connect_failed"	: "qx.event.type.Event",
		/** socket.io message event */
		"message"			: "qx.event.type.Data",
		/** socket.io close event */
		"close"				: "qx.event.type.Data",
		/** socket.io disconnect event */
		"disconnect"		: "qx.event.type.Event",
		/** socket.io reconnect event */
		"reconnect"			: "qx.event.type.Data",
		/** socket.io reconnecting event */
		"reconnecting"		: "qx.event.type.Data",
		/** socket.io reconnect_failed event */
		"reconnect_failed"	: "qx.event.type.Event",
		/** socket.io error event */
		"error"				: "qx.event.type.Data"
	},

	properties:{
		/**
		 * The url used to connect to socket.io
		 */
		url:{
			nullable:	false,
			init:		"http://"+window.location.hostname+"/",
			check:		"String"
		},
		/** The port used to connect */
		port:{
			nullable:	false,
			init:		8080,
			check:		"Number"
		},
		/** The namespace (socket.io namespace), can be empty */
		namespace:{
			nullable:	true,
			init:		"",
			check:		"String"
		},
		/** The socket (socket.io), can be null */
		socket:{
			nullable:	true,
			init:		null,
			check:		"Object"
		},
		/** Parameter for socket.io indicating if we should reconnect or not */
		reconnect:{
			nullable:	true,
			init:		true,
			check:		"Boolean"
		},
		connectTimeout:{
			nullable:	true,
			init:		10000,
			check:		"Number"
		},
		/** Reconnection delay for socket.io. */
		reconnectionDelay:{
			nullable:	false,
			init:		500,
			check:		"Number"
		},
		/** Max reconnection attemps */
		maxReconnectionAttemps:{
			nullable:	false,
			init:		1000,
			check:		"Number"
		}
	},

	/** Constructor
	 *
	 * @param namespace {string ? null} The namespace to connect on
	*/
	construct: function(namespace){
		this.base(arguments);
		if(namespace !== null){
			this.setNamespace(namespace);
		}
		this.__name = [];
	},

	members:{
		//The name store an array of events
		__name : null,

		/**
		 * Trying to using socket.io to connect and plug every event from socket.io to qooxdoo one
		*/
		connect:function(){
			if(this.getSocket() != null){
				this.getSocket().removeAllListeners();
				this.getSocket().disconnect();
			}
			this.setSocket(io.connect(this.getUrl()+this.getNamespace(), {
				'port': this.getPort(),
				'reconnect': this.getReconnect(),
				'connect timeout' : this.getConnectTimeout(),
				'reconnection delay': this.getReconnectionDelay(),
				'max reconnection attempts': this.getMaxReconnectionAttemps(),
				'force new connection':true
			}));

			this.on("connect",			function(){		this.fireEvent("connect");					}, this);
			this.on("connecting",		function(e){	this.fireDataEvent("connecting", e);		}, this);
			this.on("connect_failed",	function(){		this.fireEvent("connect_failed");			}, this);
			this.on("message",			function(e){	this.fireDataEvent("message", e);			}, this);
			this.on("close",			function(e){	this.fireDataEvent("close", e);				}, this);
			this.on("disconnect",		function(){		this.fireEvent("disconnect");				}, this);
			this.on("reconnect",		function(e){	this.fireDataEvent("reconnect", e);			}, this);
			this.on("reconnecting",		function(e){	this.fireDataEvent("reconnecting", e);		}, this);
			this.on("reconnect_failed",	function(){		this.fireEvent("reconnect_failed");			}, this);
			this.on("error",			function(e){	this.fireDataEvent("error", e);				}, this);
		},

		/**
		 * Emit an event using socket.io
		 *
		 * @param name {string} The event name to send to Node.JS
		 * @param jsonObject {object} The JSON object to send to socket.io as parameters
		*/
		emit:function(name, jsonObject){
			this.getSocket().emit(name, jsonObject);
		},

		/**
		 * Connect and event from socket.io like qooxdoo event
		 *
		 * @param name {string} The event name to watch
		 * @param fn {function} The function wich will catch event response
		 * @param that {mixed} A link to this
		*/
		on:function(name, fn, that){
			this.__name.push(name);
			if(typeof(that) !== "undefined" && that !== null){
				this.getSocket().on(name, qx.lang.Function.bind(fn, that));
			}else{
				this.getSocket().on(name, fn);
			}
		}
	},

	/**
	 * Destructor
	 */
	destruct : function(){
		if(this.getSocket() != null){
			//Deleting listeners
			if(this.__name !== null && this.__name.length >= 1){
				for(var i=0; i<this.__name.length; ++i){
					this.getSocket().removeAllListeners(this.__name[i]);
				}
			}
			this.__name = null;

			this.removeAllBindings();

			//Disconnecting socket.io
			try {
				this.getSocket().socket.disconnect();
			} catch(e) {}

			try {
				this.getSocket().disconnect();
			} catch(e) {}

			this.getSocket().removeAllListeners("connect");
			this.getSocket().removeAllListeners("connecting");
			this.getSocket().removeAllListeners("connect_failed");
			this.getSocket().removeAllListeners("message");
			this.getSocket().removeAllListeners("close");
			this.getSocket().removeAllListeners("disconnect");
			this.getSocket().removeAllListeners("reconnect");
			this.getSocket().removeAllListeners("reconnecting");
			this.getSocket().removeAllListeners("reconnect_failed");
			this.getSocket().removeAllListeners("error");
		}
	}
});

This class can be used like this :

var ws = new myapp.api.WebSocket();
//Here you should customize port, hostname, ...

//Connect with previous setted properties
ws.connect();

ws.emit("achannel", "hello");
ws.on("achannel", function(result){
  console.log(result);
}, this);

Simple as this !

Publicités

Un commentaire

  1. Very nice article. Concerning placing the socket.io script files, it is simpler and a bit more idiomatic to locate them as true resources of the app, eg. under a path like ‘source/resource/myapp/socket’, where they are included under the app’s proper namespace. Then, add a generator hint to the top of the class file of myapp.api.Websocket:

    /*
    #asset(myapp/socket/*)
    */

    This will save you the extra « copy-files » entry in config.json, as declared resources are automatically copied to the build version.

    The « add-script » key is a good way to make the script available, which can now be given the value « resource/myapp/socket/socket.io.js ». This URI will now work for both ‘source’ and ‘build’ versions of the app (so you can e.g. put it in an own job definition and « extend » source-script and build-script with it).

    Scripts loaded with « add-script » are loaded ahead of all normal qooxdoo class definitions, whether they are framework or application classes (so it’s way before Application.main start).

    Consider making a qooxdoo contribution out of this. I’m sure many would be interested.

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s

%d blogueurs aiment cette page :