Skip to content
Pablo Tejada edited this page Apr 20, 2014 · 3 revisions

Commands

RegisterCmd

You can register a new server command with the following :

Ape.registerCmd("foocmd", true, function(params, infos) {
});

This will register a new APE Command which is called through an APE url :

/?[{"cmd":"foocmd","chl":1,"sessid":"ba162c29abfe2126329bbceaf09ae269","params":{"foo":"bar"}}]

Ape.registerCmd() requires 3 arguments.

  1. (string) The command name
  2. (bool) Does this command require the user to be logged in? (sessid needed)
  3. (function) the callback function to call

The callback function is called with 2 arguments:

  1. The first is the parameters list sent by the user (in this case {"foo":"bar"})
  2. The second is an object containing some useful information:
    • host (string) Http header : "Host :"
    • client (socket) the socket object of the client
    • chl (number) the challange number
    • ip (string) client's IP
    • user (user) User object (if logged in)
    • http (array) contains user HTTP headers

Example:

	Ape.registerCmd("foocmd", true, function(params, infos) {
		Ape.log("The user ip : ("+infos.ip+"), foo : " + params.foo);
	});

Send an error

You just need to return an array with two elements:

  • (string) error code
  • (string) error description
	Ape.registerCmd("foocmd", true, function(params, infos) {
		if (!$defined(params.john)) return 0; // send a "BAD_PARAMS" RAW to the user
		if (params.john != "doe") return ["209", "NOT_A_JOHN_DOE"];
		return 1;
	});

The user will receive:

[{"time":"1255399359","raw":"ERR","data":{"code":"209","value":"NOT_A_JOHN_DOE"}}]

RegisterHookCmd

Hook an existing command (to add some arguments, for instance)

	Ape.registerHookCmd("foocmd", function(params, infos) {
		if (!$defined(params.john)) return 0;
		return 1;
	});

As in the error example, return 0 will send a "BAD_PARAMS" RAW to the user. Since there is sometimes no user object in infos (in the case of a "non sessid" command) there is a special syntax to send error or custom raw.

Send a custom raw

You need to return a special formatted object:

  • raw ** name: (string) Raw name ** data: (object) Raw content
	Ape.registerHookCmd("foocmd", function(params, infos) {
		if (!$defined(params.john)) return 0;
		if (params.john != "doe") return ["209", "NOT_A_JOHN_DOE"];
		return {
			'raw':{'name':'CUSTOM_RAW','data':{'foo':'bar'}}
		};
	});

The user will receive:

[{"time":"1255399359","raw":"CUSTOM_RAW","data":{"foo":"bar"}}]

RAW

A RAW is a "message" that you can send to a pipe (add it to the pipe's message queue). A message is formatted like the following:

  • RAW.time (string) Current timestamp
  • RAW.raw (string) The raw name
  • RAW.data (mixed) Data sent by the server

Example:

{"time":"1255281320","raw":"FOOBAR","data":{"foo":"bar","anything":["a","b"]}}

sendRaw

Each APE entity has a pipe (users, channels, proxy, and so on).

pipe.sendRaw("CUSTOM_RAW", {"foo":"bar"});

This will send the "CUSTOM_RAW" RAW to pipe.

{"time":"1255281320","raw":"CUSTOM_RAW","data":{"foo":"bar"}}

In addition to our previous example:

	Ape.registerCmd("foocmd", true, function(params, infos) {
		Ape.log("The user ip : ("+infos.ip+"), foo : " + params.foo);
		infos.user.pipe.sendRaw("CUSTOM_RAW", {"foo":"bar"});
	});

You can retrieve a pipe by its pubid:

	Ape.registerCmd("foocmd", true, function(params, infos) {
		Ape.log("The user ip : ("+infos.ip+"), foo : " + params.foo);
		Ape.getPipe(params.pubid).sendRaw("CUSTOM_RAW", {"foo":"bar"});
	});

This will send the RAW to the pipe which has a pubid that matches params.pubid

sendResponse

With registerCmd or registerHookCmd you sometimes need to send a response. Indeed, you could use a simple infos.user.pipe.sendRaw, but the client can't handle a callback response on the command that it sent. To fill this gap, you can directly use:

	Ape.registerCmd("foocmd", true, function(params, infos) {
		infos.sendResponse('custom_raw', {'foo':'bar'});
	});

This will add the chl received from the command to the RAW.

!Events

You can listen for "special events", such as :

  • adduser When a user connects to APE
  • deluser When a user disconnects
  • beforeJoin / join / afterJoin When a user joins a channel
  • left When a user leaves a channel
  • mkchan When a new channel is created
  • rmchan When a channel is deleted.
Ape.addEvent("adduser", function(user) {
	Ape.log("New user :)");
});
Ape.addEvent("join", function(user, channel) {
	Ape.log("New user has joined the channel ("+channel.getProperty('name')+") :)");
});

Note that all objects passed to Events are persistent. This means that you can store private data inside user, channel and pipe

Ape.addEvent("adduser", function(user) {
	Ape.log("New user :)"); 
	user.foo = "bar"; //Like this.
});
Ape.addEvent("join", function(user, channel) {
	Ape.log("New user "+user.foo+" joined the channel ("+channel.getProperty('name')+") :)");
});

Sockets

APE JS Server-Side provides a complete API for both server and client sockets All sockets are non-blocking and event driven, that means you get High I/O performance!

On the client

Steps :

var socket = new Ape.sockClient(port, host, {flushlf: true});

Here we are connecting to host:port. flushlf (bool) means that onRead event is fired only when a \n is received (data is split around it) e.g. foo\nbar\n will call onRead two times with "foo" and "bar". Otherwise, onRead is fired as data comes.

socket.onConnect = function() {
	Ape.log("We are connected !");
	this.write("Hello\n");
}

This defines the callback to call when the connection is ready. this object refers to the socket itself.

socket.onRead = function(data) {
	Ape.log("Data : " + data);
}

This callback is fired when new data is ready. If flushlf was set up to true, trailing '\n' chars are not present in data

socket.onDisconnect = function() {
	Ape.log("Gone !");
}

onDisconnect is fired when the connection closes. (By the client or the server).

To close the connection, use:

socket.close()

To write data, use:

socket.write(data)

On the server

Steps:

var socket = new Ape.sockServer(port, "0.0.0.0", {flushlf: true});

This binds "0.0.0.0" (all IPs attributed to the machine) on the given port.

socket.onAccept = function(client) {
	Ape.log("New client !");
	client.write("Hello world\n");
}

onAccept is fired when a new client is connecting.

Other callbacks (same API than socketClient):

  • onRead = function(client, data){}
  • onDisconnect = function(client)
  • client.close()
  • client.write(data);

Note that you can store private data to the client objects and retrieve them on other callbacks :

socket.onAccept = function(client) {
	client.foo = "bar";
	client.write("Hello world\n");
}
socket.onRead = function(client, data) {
	Ape.log(client.foo);
}

Users

A user is created when a client sends a "CONNECT" command and succeeds to log in. You can do several actions through the Javascript user object:

  • setting/getting public or private properties
  • Sending RAW's

setProperty

This function sets a public property that is sent to each other users in relation to this user (same channels, private messages).

user.setProperty('foo', 'bar');

The foo property can now be retrieved on the client-side (JSF).

The value can be either a string, an integer or an object/Array

getProperty

This function get a public property.

var prop = user.getProperty('foo');

Private properties

The javascript user object is created when a user connects to APE and destroyed when they leave or are disconnected by the server. This object is persistent. This means that you can store anything into it and retrieve it later.

Ape.addEvent("adduser", function(user) {
	user.foo = "bar";
});
 
Ape.registerCmd("helloworld", function(params, infos) {
	Ape.log(infos.user.foo);
});

getUserByPubid

To retrieve a user by its pubid:

var user = Ape.getUserByPubid(pubid);

User pipe

Each user object has a pipe object in order to send it a RAW.

user.pipe.sendRaw("RAW", {"foo":"bar"});

join

Force a user to join a channel :

user.join('mychannel'); /* can be either a channel name or a channel object */

left

Force a user to left a channel :

user.left('mychannel'); /* can be either a channel name or a channel object */

##Channels Channel objects work just like user objects.

You can set/get private or public properties.

getChannelByName

To retrieve a channel object by its name:

var channel = Ape.getChannelByName('foochannel');
channel.setProperty('foo', 'bar');
channel.myprivate = {'my':'private'}; // Can be a string or whatever you want
channel.pipe.sendRaw('FOORAW', {'John':'Doe'});
 
Ape.addEvent('beforeJoin', function(user, channel) {
	Ape.log('My private : ' + channel.myprivate);
});

getChannelByPubId

To retrieve a channel object by its pubid :

var channel = Ape.getChannelByPubid(pubid);

mkChan Create a new channel :

var channel = Ape.mkChan('foo');

delChan Delete an existing channel :

var channel = Ape.delChan('foo'); /* Can be a string or a channel object */

Pipes

As seen before, a pipe is an object that is a kind of connector through which RAWs are sent on. Each pipe has a unique identifier, pubid, which can be retrieved using:

pipe.getProperty('pubid');

The server-side JS offers a way to create your own pipe and define its behavior :

var mypipe = new Ape.pipe();

This initiate a new pipe where users can send data via the SEND Command.

{"cmd":"SEND","chl":1,"sessid":"04ad0814f987e5f9891bffd6a73ef5a1","params":{"pipe":"6a3ae905fb508aff6f1e84458038f262","data":{"foo:"bar"}}}

Where "pipe" property is the pubid of a pipe

To handle this command, pipe objects provide a simple callback :

var mypipe = Ape.pipe();
mypipe.onSend = function(user, params) {
	Ape.log(params.data.foo);
	/* doSomething(); */
}

You can also set private/public properties on a pipe just like on a user or channel object.

  • pipe.setProperty('key', val);
  • pipe.getProperty('key');
  • pipe.myprivate

MySQL

APE server allow you to connect to your MySQL database. Connecting

var sql = new Ape.MySQL("ip:port", "user", "password", "database");

you must specify the port, by default mysql uses port 3306. For now you must specify a user and password, mysql module does not support yet connecting with a user without password. Tips : You can use the local MySQL Unix socket by giving /var/run/mysqld/mysqld.sock as hostname

Connect callback

Callback fired when connection to mysql server is sucessfuly etablished

sql.onConnect = function() {
    Ape.log('Connected to mysql server');
}

Error callback

Callback fired when a connection error occured

sql.onError = function(errorNo) {
    Ape.log('Connection Error : ' + errorNo + ' : '+ this.errorString()); 
}

Query mySql

Select request :

sql.query("SELECT * FROM table", function(res, errorNo) {
    if (errorNo) Ape.log('Request error : ' + errorNo + ' : '+ this.errorString()); 
    else {
        Ape.log('Fetching ' + res.length);
        res.each(function(data) {
            Ape.log(data.content);//data.<column name> or data[column_name]
        }); 
    }
});

Insert request :

sql.query("INSERT INTO table VALUES('a','b','c')", function(res, errorNo) {
    if (errorNo) Ape.log('Request error : ' + errorNo + ' : '+ this.errorString()); 
    else Ape.log('Inserted ' + this.getInsertId());
});

Tips : to get the last auto-incremeted value use this.getInsertId() in the callback function.

Escaping

To prevent SQL injections you must escape you input data with Ape.MySQL.escape() :

sql.query("SELECT nick FROM user WHERE login = '"+Ape.MySQL.escape(mylogin)+"'");

For now callback function is mandatory, if you don't want your request to have a callback function, use the mootools feature $empty as second argument

Utils

APE server-side JS provides these useful natives functions:

Base64

Encodes/Decodes data with MIME base64

var xxx = Ape.base64.encode('foo');
var foo = Ape.base64.decode(xxx);

SHA1

Calculate the sha1 hash of a string (or a binary).

sha1.str()

Returns a 40-character hexadecimal number.

var result = Ape.sha1.str("hello world");

You can also get a HMAC-SHA1 result by giving the secret key as second argument:

var result = Ape.sha1.str("hello world", "mysecretkey");
sha1.bin()

The sha1 digest is instead returned in raw binary format with a length of 20:

var result = Ape.sha1.bin("hello world");

(Note: You can get a HMAC_SHA1 result using it like sha1.str()).

Xorize

Apply a 'XOR' between two string (or binary) :

var result = Ape.xorize("key1", "key2");

Algorithm internally used :

for (i = 0; i < key1_len; i++) {
	returned[i] = key1[i] ^ key2[i];
}

(Note: the second argument's length must be higher than the first argument's length.)

Timers

Javascript doesn't provide any timer functions on its own. APE provides a timer API just like browsers does (with the same API as Firefox).

setTimout

Executes a function after the specified delay (milliseconds).

var timeoutID = Ape.setTimeout(func, delayms, [param1, param2, ...]);
var timeoutID = Ape.setTimeout(function(a, b) {
	Ape.log("Foo : " + a + " Bar : " + b);
}, 3000, "foo", "bar");

setInterval

Calls a function repeatedly, with a fixed time delay between each call

var timeoutID = Ape.setInterval(func, delay[, param1, param2, ...]);

clearTimeout

Stop the timer set by Ape.setTimeout() or Ape.setInterval().

Ape.clearTimeout(timeoutID)
var timeoutID = Ape.setInterval(function(a, b) {
	Ape.log("Foo : " + a + " Bar : " + b);
}, 3000, "foo", "bar");
Ape.clearTimeout(timeoutID);

include

Execute the given file in the current context.

include('./scripts/foo.js');
Ape.log('some variable set in foo.js : ' + myfoovar);

!Case study

Proxy.js :

Ape.registerCmd("PROXY_CONNECT", true, function(params, infos) {
	if (!$defined(params.host) || !$defined(params.port)) {
		return 0;
	}
	var socket = new Ape.sockClient(params.port, params.host);
	socket.chl = infos.chl;
 
	socket.onConnect = function() {
		/* "this" refers to the socket object */
		/* Create a new pipe (with a pubid) */
		var pipe = new Ape.pipe();
 
		infos.user.proxys.set(pipe.getProperty('pubid'), pipe);
 
		/* Set some private properties */
		pipe.link = socket;
		pipe.nouser = false;
		this.pipe = pipe;
 
		/* Called when an user send a "SEND" command on this pipe */
		pipe.onSend = function(user, params) {
			/* "this" refer to the pipe object */
			this.link.write(Ape.base64.decode(params.msg));
		}
 
		pipe.onDettach = function() {
			this.link.close();
		}
 
		/* Send a PROXY_EVENT raw to the user and attach the pipe */
		infos.user.pipe.sendRaw("PROXY_EVENT", {"event": "connect", "chl": this.chl}, {from: this.pipe});
	}
 
	socket.onRead = function(data) {
		infos.user.pipe.sendRaw("PROXY_EVENT", {"event": "read", "data": Ape.base64.encode(data)}, {from: this.pipe});
	}
 
	socket.onDisconnect = function(data) {
		if ($defined(this.pipe)) {
			if (!this.pipe.nouser) { /* User is not available anymore */
				infos.user.pipe.sendRaw("PROXY_EVENT", {"event": "disconnect"}, {from: this.pipe});
				infos.user.proxys.erase(this.pipe.getProperty('pubid'));
			}
			/* Destroy the pipe */
			this.pipe.destroy();
		}
	}
 
	return 1;
});
 
Ape.addEvent("deluser", function(user) {
	user.proxys.each(function(val) {
		val.nouser = true;
		val.onDettach();
	});
});
 
Ape.addEvent("adduser", function(user) {
	user.proxys = new $H;
})
Clone this wiki locally