-
Notifications
You must be signed in to change notification settings - Fork 104
JS quick start
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.
- (string) The command name
- (bool) Does this command require the user to be logged in? (sessid needed)
- (function) the callback function to call
The callback function is called with 2 arguments:
- The first is the parameters list sent by the user (in this case {"foo":"bar"})
- 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);
});
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"}}]
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.
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)
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;
})