diff --git a/Clients/JavaScript.js b/Clients/JavaScript.js index 8371ffb..d9a31d9 100644 --- a/Clients/JavaScript.js +++ b/Clients/JavaScript.js @@ -5,123 +5,112 @@ var APE = { frequency: 0, scripts: [] }, - Client: function(core) { - if(core) this.core = core; + if (core) this.core = core; } -} +}; + APE.Client.prototype.eventProxy = []; + APE.Client.prototype.fireEvent = function(type, args, delay) { this.core.fireEvent(type, args, delay); -} +}; APE.Client.prototype.addEvent = function(type, fn, internal) { var newFn = fn.bind(this), ret = this; - if(this.core == undefined){ + if (this.core == undefined) { this.eventProxy.push([type, fn, internal]); - }else{ + } else { var ret = this.core.addEvent(type, newFn, internal); this.core.$originalEvents[type] = this.core.$originalEvents[type] || []; this.core.$originalEvents[type][fn] = newFn; } return ret; -} +}; + APE.Client.prototype.removeEvent = function(type, fn) { return this.core.removeEvent(type, fn); -} +}; APE.Client.prototype.onRaw = function(type, fn, internal) { - this.addEvent('raw_' + type.toLowerCase(), fn, internal); -} + this.addEvent('raw_' + type.toLowerCase(), fn, internal); +}; APE.Client.prototype.onCmd = function(type, fn, internal) { - this.addEvent('cmd_' + type.toLowerCase(), fn, internal); -} + this.addEvent('cmd_' + type.toLowerCase(), fn, internal); +}; APE.Client.prototype.onError = function(type, fn, internal) { - this.addEvent('error_' + type, fn, internal); -} + this.addEvent('error_' + type, fn, internal); +}; APE.Client.prototype.cookie = {}; -APE.Client.prototype.cookie.write = function (name, value) { - document.cookie = name + "=" + encodeURIComponent(value) + "; domain=" + document.domain; -} +APE.Client.prototype.cookie.write = function(name, value) { + document.cookie = name + '=' + encodeURIComponent(value) + '; domain=' + document.domain; +}; -APE.Client.prototype.cookie.read = function (name) { - var nameEQ = name + "="; +APE.Client.prototype.cookie.read = function(name) { + var nameEQ = name + '='; var ca = document.cookie.split(';'); - for(var i=0;i < ca.length;i++) { + for (var i = 0; i < ca.length; i++) { var c = ca[i]; - while (c.charAt(0)==' ') c = c.substring(1,c.length); - if (c.indexOf(nameEQ) == 0){ - return decodeURIComponent(c.substring(nameEQ.length,c.length)); + while (c.charAt(0) == ' ') c = c.substring(1, c.length); + if (c.indexOf(nameEQ) == 0) { + return decodeURIComponent(c.substring(nameEQ.length, c.length)); } } return null; -} - -APE.Client.prototype.load = function(config){ +}; +APE.Client.prototype.load = function(config) { config = config || {}; - config.transport = config.transport || APE.Config.transport || 0; config.frequency = config.frequency || 0; config.domain = config.domain || APE.Config.domain || document.domain; config.scripts = config.scripts || APE.Config.scripts; config.server = config.server || APE.Config.server; config.secure = config.sercure || APE.Config.secure; - - config.init = function(core){ + config.init = function(core) { this.core = core; - for(var i = 0; i < this.eventProxy.length; i++){ + for (var i = 0; i < this.eventProxy.length; i++) { this.addEvent.apply(this, this.eventProxy[i]); } }.bind(this); - //set document.domain if (config.transport != 2) { if (config.domain != 'auto') document.domain = config.domain; if (config.domain == 'auto') document.domain = document.domain; } - //Get APE cookie var cookie = this.cookie.read('APE_Cookie'); var tmp = eval('(' + cookie + ')'); - if (tmp) { - config.frequency = tmp.frequency+1; + config.frequency = tmp.frequency + 1; } else { cookie = '{"frequency":0}'; } - - var reg = new RegExp('"frequency":([ 0-9]+)' , "g") + var reg = new RegExp('"frequency":([ 0-9]+)', 'g'); cookie = cookie.replace(reg, '"frequency":' + config.frequency); this.cookie.write('APE_Cookie', cookie); - var iframe = document.createElement('iframe'); - iframe.setAttribute('id','ape_' + config.identifier); + iframe.setAttribute('id', 'ape_' + config.identifier); iframe.style.display = 'none'; iframe.style.position = 'absolute'; iframe.style.left = '-300px'; iframe.style.top = '-300px'; - - document.body.insertBefore(iframe,document.body.childNodes[0]); - + document.body.insertBefore(iframe, document.body.childNodes[0]); var initFn = function() { iframe.contentWindow.APE.init(config); - } - + }; if (iframe.addEventListener) { iframe.addEventListener('load', initFn, false); } else if (iframe.attachEvent) { iframe.attachEvent('onload', initFn); } - if (config.transport == 2) { var doc = iframe.contentDocument; if (!doc) doc = iframe.contentWindow.document;//For IE - //If the content of the iframe is created in DOM, the status bar will always load... //using document.write() is the only way to avoid status bar loading with JSONP doc.open(); @@ -133,35 +122,35 @@ APE.Client.prototype.load = function(config){ doc.write(theHtml); doc.close(); } else { - iframe.setAttribute('src',(config.secure ? 'https': 'http') + '://' + config.frequency + '.' + config.server + '/?[{"cmd":"script","params":{"domain":"' + document.domain +'","scripts":["' + config.scripts.join('","') + '"]}}]'); - if (navigator.product == 'Gecko') { - //Firefox fix, see bug #356558 + iframe.setAttribute('src', (config.secure ? 'https' : 'http') + '://' + config.frequency + '.' + config.server + '/?[{"cmd":"script","params":{"domain":"' + document.domain + '","scripts":["' + config.scripts.join('","') + '"]}}]'); + if (navigator.product == 'Gecko') { + //Firefox fix, see bug #356558 // https://bugzilla.mozilla.org/show_bug.cgi?id=356558 iframe.contentWindow.location.href = iframe.getAttribute('src'); } } - -} +}; if (Function.prototype.bind == null) { Function.prototype.bind = function(bind, args) { return this.create({'bind': bind, 'arguments': args}); - } + }; } + if (Function.prototype.create == null) { Function.prototype.create = function(options) { var self = this; options = options || {}; - return function(){ + return function() { var args = options.arguments || arguments; - if(args && !args.length){ + if (args && !args.length) { args = [args]; } - var returns = function(){ + var returns = function() { return self.apply(options.bind || null, args); }; return returns(); }; - } + }; } diff --git a/Clients/MooTools.js b/Clients/MooTools.js index 573d9f7..65f535f 100755 --- a/Clients/MooTools.js +++ b/Clients/MooTools.js @@ -1,3 +1,14 @@ +/** + * APE_JSF + * @fileOverview APE JSF + * http://www.ape-project.org/ + * Weelya + * @author Nicolas Trani (efyx) + * @author Anthony Catel (paraboul) + * @author Florian Gasquez (Fy-) + * @author John Chavarria (psi) + */ + var APE = { Config: { identifier: 'ape', @@ -6,18 +17,91 @@ var APE = { scripts: [] } }; +/** +* Client +* +* @name APE.Client +* @class +* @public +* @constructor +* +* @param {object} [core] Core object +* @returns {void} +* +* @property {Cookie} cookie Cookie object +* +* @see APE.Core +* @see APE.client +*/ APE.Client = new Class({ - eventProxy: [], - - fireEvent: function(type, args, delay){ + /** + * Fire a event manually. + *

Executes all events of the specified type present on the APE core and Pipes.

+ * + * @name APE.fireEvent + * @function + * @public + * + * @param {string} eventName Event to launch (eg 'load'). + * @param {Array|object} args The arguments for the appropiate event callbacks + * @param {integer} [delay] Delay in ms before the event should be fired + * @returns {APE} + * + * @example + * //ape var is a reference to APE instance + * //fire the event "myEvent" with arguments "argument1" and "argument2" and delay it of 1sec + * ape.fireEvent('myEvent', ['argument1', 'argument2'], 1000); + * + * @see APE.fireEvent + * @see APE.addEvent + * @see APE.Pipe.addEvent + * @see APE.Pipe.fireGlobalEvent + */ + fireEvent: function(type, args, delay) { return this.core.fireEvent(type, args, delay); }, - - addEvent: function(type, fn, internal){ + /** + * Intercept an event. + * + * @name APE.addEvent + * @function + * @public + * + * @param {string} eventName Event to listen to. + * @param {function} fn The function that will be executed upon this named event + * @param {boolean} [internal] Flag to hide the function + * @returns {APE} + * + * @example + * //Initialize client + * client = new APE.Client(); + * client.load({ + * 'domain': 'yourdomain.com', + * 'server': 'ape.yourdomain.com', + * 'baseUrl': 'yourdomain.com/APEJavaScript' + * }); + * //Load event is fired when the client is loaded + * client.addEvent('load', function() { + * console.log('APE is loaded'); + * //Ok my client is loaded, now i can send data to my APE server + * //Connect to the APE server + * client.core.start(); + * }); + * //Init event is fired when the client is connected to APE server + * client.addEvent('init', function() { + * console.log('APE is connected'); + * }); + * + * @see APE.addEvent + * @see APE.fireEvent + * @see APE.Pipe.addEvent + * @see APE.removeEvent + */ + addEvent: function(type, fn, internal) { var newFn = fn.bind(this), ret = this; - if(!$defined(this.core)) this.eventProxy.push([type, fn, internal]); + if (!$defined(this.core)) this.eventProxy.push([type, fn, internal]); else { ret = this.core.addEvent(type, newFn, internal); this.core.$originalEvents[type] = this.core.$originalEvents[type] || []; @@ -25,52 +109,59 @@ APE.Client = new Class({ } return ret; }, - onRaw: function(type, fn, internal) { - return this.addEvent('raw_' + type.toLowerCase(), fn, internal); + return this.addEvent('raw_' + type.toLowerCase(), fn, internal); }, - + /** + * Stop watching a event + * + * @name APE.removeEvent + * @function + * @public + * + * @param {string} eventName The name of the event (e.g. 'login', 'data'); + * @param {function} [fn] For internal functions? + * @returns {APE} + * + * @example + * //ape var is a reference to APE instance + * //Intercept login raw. + * ape.onRaw('level10', function(param) { + * ape.removeEvent('level9'); + * }); + * @see APE.addEvent + */ removeEvent: function(type, fn) { return this.core.removeEvent(type, fn); }, - onCmd: function(type, fn, internal) { - return this.addEvent('cmd_' + type.toLowerCase(), fn, internal); + return this.addEvent('cmd_' + type.toLowerCase(), fn, internal); }, - onError: function(type, fn, internal) { - return this.addEvent('error_' + type, fn, internal); + return this.addEvent('error_' + type, fn, internal); }, - - load: function(config){ + load: function(config) { config = $merge({}, APE.Config, config); - // Init function called by core to init core variable - config.init = function(core){ + config.init = function(core) { this.core = core; - for(var i = 0; i < this.eventProxy.length; i++){ + for (var i = 0; i < this.eventProxy.length; i++) { this.addEvent.apply(this, this.eventProxy[i]); } }.bind(this); - //set document.domain if (config.transport != 2) { if (config.domain != 'auto') document.domain = config.domain; if (config.domain == 'auto') document.domain = document.domain; } - var tmp = JSON.decode(Cookie.read('APE_Cookie'), {'domain': document.domain}); - - if(tmp) { + if (tmp) { config.frequency = tmp.frequency.toInt(); } else { tmp = {'frequency': 0}; } - tmp.frequency = config.frequency + 1; - Cookie.write('APE_Cookie', JSON.encode(tmp), {'domain': document.domain}); - var iframe = new Element('iframe', { id: 'ape_' + config.identifier, styles: { @@ -80,16 +171,13 @@ APE.Client = new Class({ top: -300 } }).inject(document.body); - - iframe.addEvent('load', function() { + iframe.addEvent('load', function() { if (!iframe.contentWindow.APE) setTimeout(iframe.onload, 100);//Sometimes IE fire the onload event, but the iframe is not loaded -_- else iframe.contentWindow.APE.init(config); }); - if (config.transport == 2) {//Special case for JSONP var doc = iframe.contentDocument; if (!doc) doc = iframe.contentWindow.document; - //If the content of the iframe is created in DOM, the status bar will always load... //using document.write() is the only way to avoid status bar loading with JSONP doc.open(); @@ -100,15 +188,13 @@ APE.Client = new Class({ theHtml += ''; doc.write(theHtml); doc.close(); - } else { + } else { iframe.set('src', (config.secure ? 'https' : 'http') + '://' + config.frequency + '.' + config.server + '/?[{"cmd":"script","params":{"domain":"' + document.domain + '","scripts":["' + config.scripts.join('","') + '"]}}]'); - if (Browser.Engine.gecko) { - // Firefox fix, see bug  #356558 + if (Browser.Engine.gecko) { + // Firefox fix, see bug  #356558 // https://bugzilla.mozilla.org/show_bug.cgi?id=356558 iframe.contentWindow.location.href = iframe.get('src'); } - } - + } } - }); diff --git a/Demos/Chat/demo.js b/Demos/Chat/demo.js index 688ab1b..9205078 100755 --- a/Demos/Chat/demo.js +++ b/Demos/Chat/demo.js @@ -1,29 +1,21 @@ APE.Chat = new Class({ - - Extends: APE.Client, - + Extends: APE.Client, Implements: Options, - - options:{ + options: { container: null, logs_limit: 10, container: document.body }, - - initialize: function(options){ + initialize: function(options) { this.setOptions(options); - this.els = {}; this.currentPipe = null; this.logging = true; - this.onRaw('data', this.rawData); this.onCmd('send', this.cmdSend); this.onError('004', this.reset); - this.onError('006', this.promptName); this.onError('007', this.promptName); - this.addEvent('load', this.start); this.addEvent('ready', this.createChat); this.addEvent('uniPipeCreate', this.setPipeName); @@ -32,47 +24,41 @@ APE.Chat = new Class({ this.addEvent('userJoin', this.createUser); this.addEvent('userLeft', this.deleteUser); }, - - promptName: function(errorRaw){ + promptName: function(errorRaw) { this.els.namePrompt = {}; - this.els.namePrompt.div = new Element('form',{'class':'ape_name_prompt','text':'Choose a nickname : '}).inject(this.options.container) - this.els.namePrompt.div.addEvent('submit',function(ev){ + this.els.namePrompt.div = new Element('form', {'class': 'ape_name_prompt', 'text': 'Choose a nickname : '}).inject(this.options.container); + this.els.namePrompt.div.addEvent('submit', function(ev) { ev.stop(); this.options.name = this.els.namePrompt.input.get('value'); this.els.namePrompt.div.dispose(); - this.start() + this.start(); }.bindWithEvent(this)); - this.els.namePrompt.input = new Element('input',{'class':'text'}).inject(this.els.namePrompt.div); - new Element('input',{'class':'submit','type':'submit','value':'GO!'}).inject(this.els.namePrompt.div) + this.els.namePrompt.input = new Element('input', {'class': 'text'}).inject(this.els.namePrompt.div); + new Element('input', {'class': 'submit', 'type': 'submit', 'value': 'GO!'}).inject(this.els.namePrompt.div); var error; if (errorRaw) { if (errorRaw.data.code == 007) error = 'This nick is already in use'; if (errorRaw.data.code == 006) error = 'Bad nick, a nick must contain a-z 0-9 characters'; if (error) { - new Element('div', {'styles':{'padding-top': 5, 'font-weight': 'bold'},'text': error}).inject(this.els.namePrompt.div); + new Element('div', {'styles': {'padding-top': 5, 'font-weight': 'bold'}, 'text': error}).inject(this.els.namePrompt.div); } } }, - - start: function(){ + start: function() { //If name is not set & it's not a session restore ask user for his nickname - if(!this.options.name && !this.core.options.restore){ + if (!this.options.name && !this.core.options.restore) { this.promptName(); - }else{ + } else { var opt = {'sendStack': false, 'request': 'stack'}; - - this.core.start({'name':this.options.name}, opt); - + this.core.start({'name': this.options.name}, opt); if (this.core.options.restore) { this.core.getSession('currentPipe', function(resp) { this.setCurrentPipe(resp.data.sessions.currentPipe); }.bind(this), opt); } - this.core.request.stack.send(); } }, - setPipeName: function(pipe, options) { if (options.name) { pipe.name = options.name; @@ -84,14 +70,12 @@ APE.Chat = new Class({ pipe.name = options.pipe.properties.name; } }, - - getCurrentPipe: function(){ + getCurrentPipe: function() { return this.currentPipe; }, - - setCurrentPipe: function(pubid, save){ + setCurrentPipe: function(pubid, save) { save = !save; - if (this.currentPipe){ + if (this.currentPipe) { this.currentPipe.els.tab.addClass('unactive'); this.currentPipe.els.container.addClass('ape_none'); } @@ -100,161 +84,139 @@ APE.Chat = new Class({ this.currentPipe.els.tab.removeClass('unactive'); this.currentPipe.els.container.removeClass('ape_none'); this.scrollMsg(this.currentPipe); - if (save) this.core.setSession({'currentPipe':this.currentPipe.getPubid()}); + if (save) this.core.setSession({'currentPipe': this.currentPipe.getPubid()}); return this.currentPipe; }, - - cmdSend: function(data, pipe){ + cmdSend: function(data, pipe) { this.writeMessage(pipe, data.msg, this.core.user); }, - - rawData: function(raw, pipe){ + rawData: function(raw, pipe) { this.writeMessage(pipe, raw.data.msg, raw.data.from); }, - - parseMessage: function(message){ + parseMessage: function(message) { return decodeURIComponent(message); }, - - notify: function(pipe){ + notify: function(pipe) { pipe.els.tab.addClass('new_message'); }, - - scrollMsg: function(pipe){ + scrollMsg: function(pipe) { var scrollSize = pipe.els.message.getScrollSize(); - pipe.els.message.scrollTo(0,scrollSize.y); + pipe.els.message.scrollTo(0, scrollSize.y); }, - - writeMessage: function(pipe, message, from){ + writeMessage: function(pipe, message, from) { //Append message to last message - if(pipe.lastMsg && pipe.lastMsg.from.pubid == from.pubid){ + if (pipe.lastMsg && pipe.lastMsg.from.pubid == from.pubid) { var cnt = pipe.lastMsg.el; - }else{//Create new one + } else {//Create new one //Create message container - var msg = new Element('div',{'class':'ape_message_container'}); - var cnt = new Element('div',{'class':'msg_top'}).inject(msg); + var msg = new Element('div', {'class': 'ape_message_container'}); + var cnt = new Element('div', {'class': 'msg_top'}).inject(msg); if (from) { - new Element('div',{'class':'ape_user','text':from.properties.name}).inject(msg,'top'); + new Element('div', {'class': 'ape_user', 'text': from.properties.name}).inject(msg, 'top'); } - new Element('div',{'class':'msg_bot'}).inject(msg); + new Element('div', {'class': 'msg_bot'}).inject(msg); msg.inject(pipe.els.message); } - new Element('div',{ - 'text':this.parseMessage(message), - 'class':'ape_message' + new Element('div', { + 'text': this.parseMessage(message), + 'class': 'ape_message' }).inject(cnt); - this.scrollMsg(pipe); - - pipe.lastMsg = {from:from,el:cnt}; - - //notify - if(this.getCurrentPipe().getPubid()!=pipe.getPubid()){ + pipe.lastMsg = {from: from, el: cnt}; + //notify + if (this.getCurrentPipe().getPubid() != pipe.getPubid()) { this.notify(pipe); } }, - - createUser: function(user, pipe){ - user.el = new Element('div',{ - 'class':'ape_user' + createUser: function(user, pipe) { + user.el = new Element('div', { + 'class': 'ape_user' }).inject(pipe.els.users); - new Element('a',{ - 'text':user.properties.name, - 'href':'javascript:void(0)', + new Element('a', { + 'text': user.properties.name, + 'href': 'javascript:void(0)', 'events': { 'click': - function(ev,user){ - this.core.getPipe(user.pubid) + function(ev, user) { + this.core.getPipe(user.pubid); this.setCurrentPipe(user.pubid); - }.bindWithEvent(this,[user]) + }.bindWithEvent(this, [user]) } - }).inject(user.el,'inside'); + }).inject(user.el, 'inside'); }, - - deleteUser: function(user, pipe){ + deleteUser: function(user, pipe) { user.el.dispose(); }, - - createPipe: function(pipe, options){ + createPipe: function(pipe, options) { var tmp; - //Define some pipe variables to handle logging and pipe elements pipe.els = {}; pipe.logs = new Array(); - //Container - pipe.els.container = new Element('div',{ - 'class':'ape_pipe ape_none' + pipe.els.container = new Element('div', { + 'class': 'ape_pipe ape_none' }).inject(this.els.pipeContainer); - //Message container - pipe.els.message = new Element('div',{'class':'ape_messages'}).inject(pipe.els.container,'inside'); - - //If pipe has a users list - if(pipe.users){ - pipe.els.usersRight = new Element('div',{ - 'class':'users_right' + pipe.els.message = new Element('div', {'class': 'ape_messages'}).inject(pipe.els.container, 'inside'); + //If pipe has a users list + if (pipe.users) { + pipe.els.usersRight = new Element('div', { + 'class': 'users_right' }).inject(pipe.els.container); - pipe.els.users = new Element('div',{ - 'class':'ape_users_list' - }).inject(pipe.els.usersRight);; + pipe.els.users = new Element('div', { + 'class': 'ape_users_list' + }).inject(pipe.els.usersRight); } //Add tab - pipe.els.tab = new Element('div',{ - 'class':'ape_tab unactive' + pipe.els.tab = new Element('div', { + 'class': 'ape_tab unactive' }).inject(this.els.tabs); - - tmp = new Element('a',{ - 'text':pipe.name, - 'href':'javascript:void(0)', - 'events':{ - 'click':function(pipe){ - this.setCurrentPipe(pipe.getPubid()) - }.bind(this,[pipe]) + tmp = new Element('a', { + 'text': pipe.name, + 'href': 'javascript:void(0)', + 'events': { + 'click': function(pipe) { + this.setCurrentPipe(pipe.getPubid()); + }.bind(this, [pipe]) } }).inject(pipe.els.tab); - //Hide other pipe and show this one this.setCurrentPipe(pipe.getPubid()); }, - - createChat: function(){ - this.els.pipeContainer = new Element('div',{'id':'ape_container'}); + createChat: function() { + this.els.pipeContainer = new Element('div', {'id': 'ape_container'}); this.els.pipeContainer.inject(this.options.container); - - this.els.more = new Element('div',{'id':'more'}).inject(this.options.container,'after'); - this.els.tabs = new Element('div',{'id':'tabbox_container'}).inject(this.els.more); - this.els.sendboxContainer = new Element('div',{'id':'ape_sendbox_container'}).inject(this.els.more); - - this.els.sendBox = new Element('div',{'id':'ape_sendbox'}).inject(this.els.sendboxContainer,'bottom'); - this.els.sendboxForm = new Element('form',{ - 'events':{ - 'submit':function(ev){ + this.els.more = new Element('div', {'id': 'more'}).inject(this.options.container, 'after'); + this.els.tabs = new Element('div', {'id': 'tabbox_container'}).inject(this.els.more); + this.els.sendboxContainer = new Element('div', {'id': 'ape_sendbox_container'}).inject(this.els.more); + this.els.sendBox = new Element('div', {'id': 'ape_sendbox'}).inject(this.els.sendboxContainer, 'bottom'); + this.els.sendboxForm = new Element('form', { + 'events': { + 'submit': function(ev) { ev.stop(); var val = this.els.sendbox.get('value'); - if(val!=''){ + if (val != '') { this.getCurrentPipe().send(val); - this.els.sendbox.set('value',''); + this.els.sendbox.set('value', ''); } }.bindWithEvent(this) } }).inject(this.els.sendBox); - this.els.sendbox = new Element('input',{ - 'type':'text', - 'id':'sendbox_input', - 'autocomplete':'off' + this.els.sendbox = new Element('input', { + 'type': 'text', + 'id': 'sendbox_input', + 'autocomplete': 'off' }).inject(this.els.sendboxForm); - this.els.send_button = new Element('input',{ - 'type':'button', - 'id':'sendbox_button', - 'value':'' + this.els.send_button = new Element('input', { + 'type': 'button', + 'id': 'sendbox_button', + 'value': '' }).inject(this.els.sendboxForm); }, - - reset: function(){ + reset: function() { this.core.clearSession(); - if(this.els.pipeContainer){ + if (this.els.pipeContainer) { this.els.pipeContainer.dispose(); this.els.more.dispose(); } diff --git a/Demos/Controller/demo.css b/Demos/Controller/demo.css new file mode 100644 index 0000000..a1c2602 --- /dev/null +++ b/Demos/Controller/demo.css @@ -0,0 +1,22 @@ +body { + margin: 10px; + padding: 0; + font-family: Tahoma; + font-size: 12px; +} + +div#apeControllerDemo { + margin: 10px 0; + padding: 10px; + border: 1px solid #e2e2e2; + background: #f2f2f2; +} + +div#apeControllerDemo div.message { + padding: 5px 0; + border-bottom: 1px solid #bbb; +} + +div#apeControllerDemo div.message:last-child { + border-bottom: 0; +} diff --git a/Demos/Controller/demo.html b/Demos/Controller/demo.html index 6441a9a..f07bdd4 100755 --- a/Demos/Controller/demo.html +++ b/Demos/Controller/demo.html @@ -2,43 +2,17 @@ APE Controller Test - - + + -
- In order to try this demo, you need: +
+ In order to try this demo, you need:
  • Inlinepush have to be loaded on the APE server
  • Set your APE server address in the controller/test.php
Load this page and go to test.php and you will see a message appear in the element down below: -
-
+
diff --git a/Demos/Controller/demo.js b/Demos/Controller/demo.js index 18f54cb..e411098 100755 --- a/Demos/Controller/demo.js +++ b/Demos/Controller/demo.js @@ -1,30 +1,23 @@ APE.Controller = new Class({ - Extends: APE.Client, - Implements: Options, - options: { container: null }, - - initialize: function(options){ + initialize: function(options) { this.setOptions(options); this.container = $(this.options.container) || document.body; - this.onRaw('postmsg', this.onMsg); - this.addEvent('load',this.start); + this.addEvent('load', this.start); }, - - start: function(core){ + + start: function(core) { this.core.start({'name': $time().toString()}); }, - - onMsg: function(raw){ + onMsg: function(raw) { new Element('div', { 'class': 'message', html: decodeURIComponent(raw.data.message) }).inject(this.container); } - }); diff --git a/Demos/Controller/test.php b/Demos/Controller/test.php index a0c2edd..e7c0a9a 100644 --- a/Demos/Controller/test.php +++ b/Demos/Controller/test.php @@ -8,20 +8,20 @@ 'Hey, how are you doing?', ); -$cmd = array(array( - 'cmd' => 'inlinepush', - 'params' => array( - 'password' => $APEPassword, - 'raw' => 'postmsg', - 'channel' => 'testchannel', - 'data' => array( //Note: data can't be a string - 'message' => $messages[array_rand($messages)] - ) - ) -)); +$cmd = array(array( + 'cmd' => 'inlinepush', + 'params' => array( + 'password' => $APEPassword, + 'raw' => 'postmsg', + 'channel' => 'testchannel', + 'data' => array( //Note: data can't be a string + 'message' => $messages[array_rand($messages)] + ) + ) +)); var_dump($APEserver.rawurlencode(json_encode($cmd))); -$data = file_get_contents($APEserver.rawurlencode(json_encode($cmd))); +$data = file_get_contents($APEserver.rawurlencode(json_encode($cmd))); $data = json_decode($data); if ($data[0]->data->value == 'ok') { diff --git a/Demos/Move/demo.html b/Demos/Move/demo.html index 6b71770..f178ecb 100755 --- a/Demos/Move/demo.html +++ b/Demos/Move/demo.html @@ -1,8 +1,7 @@ - - - + + @@ -12,24 +11,21 @@
diff --git a/Demos/Move/move.js b/Demos/Move/move.js index cc4457a..31a437e 100755 --- a/Demos/Move/move.js +++ b/Demos/Move/move.js @@ -1,179 +1,151 @@ APE.Move = new Class({ - Extends: APE.Client, - + Extends: APE.Client, Implements: Options, - options: { container: document.body }, - - initialize: function(options){ - + initialize: function(options) { this.setOptions(options); - this.addEvent('ready', this.initPlayground); this.addEvent('userJoin', this.createUser); - this.addEvent('multiPipeCreate', function(pipe, options){ + this.addEvent('multiPipeCreate', function(pipe, options) { this.pipe = pipe; }); this.addEvent('userLeft', this.deleteUser); - - this.onRaw('positions',this.rawPositions); - this.onRaw('data',this.rawData); - + this.onRaw('positions', this.rawPositions); + this.onRaw('data', this.rawData); this.onCmd('send', this.cmdSend); - - this.onError('004',this.reset); + this.onError('004', this.reset); }, - - deleteUser: function(user, pipe){ + deleteUser: function(user, pipe) { user.element.dispose(); }, - cmdSend: function(param, pipe) { this.writeMessage(pipe, param.msg, this.core.user); }, - - rawData: function(raw, pipe){ + rawData: function(raw, pipe) { this.writeMessage(pipe, raw.data.msg, raw.data.from); }, - - rawPositions: function(raw, pipe){ - this.movePoint(raw.data.from, raw.data.x,raw.data.y); + rawPositions: function(raw, pipe) { + this.movePoint(raw.data.from, raw.data.x, raw.data.y); }, - - parseMessage: function(message){ + parseMessage: function(message) { return decodeURIComponent(message); }, - - writeMessage: function(pipe,message,sender){ + writeMessage: function(pipe, message, sender) { //Define sender sender = this.pipe.getUser(sender.pubid); - //destroy old message sender.element.getElements('.ape_message_container').destroy(); - //Create message container - var msg = new Element('div',{'opacity':'0','class':'ape_message_container'}); - var cnt = new Element('div',{'class':'msg_top'}).inject(msg); - + var msg = new Element('div', {'opacity': '0', 'class': 'ape_message_container'}); + var cnt = new Element('div', {'class': 'msg_top'}).inject(msg); //Add message - new Element('div',{ - 'text':this.parseMessage(message), - 'class':'ape_message' + new Element('div', { + 'text': this.parseMessage(message), + 'class': 'ape_message' }).inject(cnt); - new Element('div',{'class':'msg_bot'}).inject(cnt); - + new Element('div', {'class': 'msg_bot'}).inject(cnt); //Inject message msg.inject(sender.element); - //Show message - var fx = msg.morph({'opacity':'1'}); - + var fx = msg.morph({'opacity': '1'}); //Delete old message - (function(el){ - $(el).morph({'opacity':'0'}); - (function(){ + (function(el) { + $(el).morph({'opacity': '0'}); + (function() { $(this).dispose(); - }).delay(300,el); - }).delay(3000,this,msg); + }).delay(300, el); + }).delay(3000, this, msg); }, - - createUser: function(user, pipe){ - if(user.properties.x){ + createUser: function(user, pipe) { + if (user.properties.x) { var x = user.properties.x; var y = user.properties.y; - }else{ + } else { var x = 8; var y = 8; } var pos = this.element.getCoordinates(); - x = x.toInt()+pos.left; - y = y.toInt()+pos.top; - user.element = new Element('div',{ - 'class':'demo_box_container', - 'styles':{ - 'left':x+'px', - 'top':y+'px' + x = x.toInt() + pos.left; + y = y.toInt() + pos.top; + user.element = new Element('div', { + 'class': 'demo_box_container', + 'styles': { + 'left': x + 'px', + 'top': y + 'px' } - }).inject(this.element,'inside'); - new Element('div',{ - 'class':'user', - 'styles':{ - 'background-color':'rgb('+this.userColor(user.properties.name)+')' + }).inject(this.element, 'inside'); + new Element('div', { + 'class': 'user', + 'styles': { + 'background-color': 'rgb(' this.userColor(user.properties.name) + ')' } - }).inject(user.element,'inside'); - var span = new Element('span',{ - 'text':user.properties.name - }).inject(user.element,'inside'); + }).inject(user.element, 'inside'); + var span = new Element('span', { + 'text': user.properties.name + }).inject(user.element, 'inside'); if (user.pubid == this.core.user.pubid) { span.addClass('you'); span.set('text', 'You'); if (!this.core.options.restore) { var offset = this.element.getPosition(); - this.sendpos($random(0, 640) + offset.x, $random(0,300) + offset.y); + this.sendpos($random(0, 640) + offset.x, $random(0, 300) + offset.y); } } }, - - userColor: function(nickname){ - var color = new Array(0,0,0); - var i=0; - while(i<3 && ipos.width) x=pos.width-10; - if(y<0) y = 10; - if(y>pos.height) y = pos.height-10; - return {'x':x,'y':y}; + x = x - pos.left - 36; + y = y - pos.top - 46; + if (x < 0) x = 10; + if (x > pos.width) x = pos.width - 10; + if (y < 0) y = 10; + if (y > pos.height) y = pos.height - 10; + return {'x': x, 'y': y}; }, - - movePoint:function(user,x,y){ - var user = this.pipe.getUser(user.pubid); + movePoint: function(user, x, y) { + var user = this.pipe.getUser(user.pubid); var el = user.element; - var fx = el.retrieve('fx'); - - if(!fx){ - fx = new Fx.Morph(el,{'duration':300,'fps':100}); - el.store('fx',fx); + var fx = el.retrieve('fx'); + if (!fx) { + fx = new Fx.Morph(el, {'duration': 300, 'fps': 100}); + el.store('fx', fx); } el.retrieve('fx').cancel(); - pos = this.element.getCoordinates(); - x = x.toInt(); - y = y.toInt() + y = y.toInt(); //Save position in user properties user.properties.x = x; user.properties.y = y; - fx.start({'left':pos.left+x,'top':pos.top+y}); + fx.start({'left': pos.left + x, 'top': pos.top + y}); }, - - initPlayground: function(){ + initPlayground: function() { this.element = this.options.container; this.els = {}; - this.els.move_box = new Element('div',{'class':'move_box'}).inject(this.element); - this.els.move_box.addEvent('mousedown',function(ev){ + this.els.move_box = new Element('div', {'class': 'move_box'}).inject(this.element); + this.els.move_box.addEvent('mousedown', function(ev) { ev.stop(); - this.sendpos(ev.page.x,ev.page.y); + this.sendpos(ev.page.x, ev.page.y); }.bindWithEvent(this)); - var el1 = new Element('div', {'id': 'moveOverlay', 'styles': {'opacity': 0.5}}).inject(this.element,'top'); - var el2 = new Element('div', {'id': 'moveOverlay', 'styles': {'background': 'none', 'z-index':6}, 'text': 'Click on the grey area to move your ball'}).inject(this.element,'top'); + var el1 = new Element('div', {'id': 'moveOverlay', 'styles': {'opacity': 0.5}}).inject(this.element, 'top'); + var el2 = new Element('div', {'id': 'moveOverlay', 'styles': {'background': 'none', 'z-index': 6}, 'text': 'Click on the grey area to move your ball'}).inject(this.element, 'top'); var clear = function() { el1.fade('out'); el2.fade('out'); @@ -181,52 +153,47 @@ APE.Move = new Class({ el1.dispose(); el2.dispose(); }); - } - + }; el2.addEvent('click', function() { el1.destroy(); el2.destroy(); }); clear.delay(1500); - this.els.more = new Element('div',{'id':'more'}).inject(this.element,'inside'); - - this.els.sendboxContainer = new Element('div',{'id':'ape_sendbox_container'}).inject(this.els.more); - - this.els.sendBox = new Element('div',{'text':'Say : ','id':'ape_sendbox'}).inject(this.els.sendboxContainer,'bottom'); - this.els.sendbox = new Element('input',{ - 'type':'text', - 'id':'sendbox_input', - 'autocomplete':'off', - 'events':{ - 'keypress':function(ev){ - if(ev.code == 13){ + this.els.more = new Element('div', {'id': 'more'}).inject(this.element, 'inside'); + this.els.sendboxContainer = new Element('div', {'id': 'ape_sendbox_container'}).inject(this.els.more); + this.els.sendBox = new Element('div', {'text': 'Say : ', 'id': 'ape_sendbox'}).inject(this.els.sendboxContainer, 'bottom'); + this.els.sendbox = new Element('input', { + 'type': 'text', + 'id': 'sendbox_input', + 'autocomplete': 'off', + 'events': { + 'keypress': function(ev) { + if (ev.code == 13) { var val = this.els.sendbox.get('value'); - if(val!=''){ + if (val != '') { this.pipe.send(val); - $(ev.target).set('value',''); + $(ev.target).set('value', ''); } } }.bind(this) } }).inject(this.els.sendBox); - this.els.sendButton = new Element('input',{ - 'type':'button', - 'id':'sendbox_button', - 'value':'Send', + this.els.sendButton = new Element('input', { + 'type': 'button', + 'id': 'sendbox_button', + 'value': 'Send', 'events': { - - 'click': function(){ + 'click': function() { var val = this.els.sendbox.get('value'); - if(val!=''){ + if (val != '') { this.pipe.send(val); - $(ev.target).set('value',''); + $(ev.target).set('value', ''); } }.bind(this) } }).inject(this.els.sendBox); }, - - reset: function(){ + reset: function() { this.core.clearSession(); if (this.element) { this.element.empty(); diff --git a/Demos/Shoutbox/demo.html b/Demos/Shoutbox/demo.html index aee8aa7..d35f66c 100755 --- a/Demos/Shoutbox/demo.html +++ b/Demos/Shoutbox/demo.html @@ -1,6 +1,6 @@ - + @@ -12,8 +12,8 @@ diff --git a/Demos/Shoutbox/demo.js b/Demos/Shoutbox/demo.js index 14400f9..83f6650 100755 --- a/Demos/Shoutbox/demo.js +++ b/Demos/Shoutbox/demo.js @@ -1,117 +1,93 @@ APE.Shoutbox = new Class({ - //This class must implement APE_Client to intercept events Extends: APE.Client, - //Constructor - initialize: function(container){ - + initialize: function(container) { this.els = {}; - //Start the shoutbox once ape is loaded this.addEvent('load', this.start); - //Shoutbox container this.els.container = $(container); - //Catch pipeCreate events when you join a channel this.addEvent('multiPipeCreate', this.createShoutbox); - //Catch message sending - this.onCmd('send',this.cmdSend); - + this.onCmd('send', this.cmdSend); //Catch message reception - this.onRaw('data',this.rawData); + this.onRaw('data', this.rawData); }, - start: function() { - //Ask the user for his nickname - if(!this.core.options.restore){ - var nickname = prompt('Your nickname') - }else{ + //Ask the user for his nickname + if (!this.core.options.restore) { + var nickname = prompt('Your nickname'); + } else { var nickname = null; } //Call start method from core to start connection to APE server this.core.start({'name': nickname}); }, - /*** * Create the shoutbox */ - createShoutbox:function(pipe, options){ + createShoutbox: function(pipe, options) { /*** * Définition d'une variables de class content l'objet pipe * Il serra utilisez pour envoyer les message */ this.pipe = pipe; - //Création de la shoutbox - this.els.shoutbox = new Element('div', {'id': 'shoutbox'}).inject(this.els.container); - + this.els.shoutbox = new Element('div', {'id': 'shoutbox'}).inject(this.els.container); //Ajout d'un élement pour contenir les messages reçus this.els.shoutbox_msg = new Element('div', {'id': 'shoutbox_msg'}).inject(this.els.shoutbox); - //Ajout du formulaire d'envoie de message this.els.shoutboxForm = new Element('form').inject(this.els.shoutbox); this.els.shoutboxInput = new Element('input', {'type': 'text', 'class': 'text'}).inject(this.els.shoutboxForm); //Input pour le texte - new Element('input', {'class': 'submit','type': 'submit','value': 'Envoyer'}).inject(this.els.shoutboxForm); //Bouton envoyer - - //Lorsque le formulaire est soumis, le message est envoyé + new Element('input', {'class': 'submit', 'type': 'submit', 'value': 'Envoyer'}).inject(this.els.shoutboxForm); //Bouton envoyer + //Lorsque le formulaire est soumis, le message est envoyé this.els.shoutboxForm.addEvent('submit', this.postMessage.bindWithEvent(this)); }, - /*** * Intercepte la commande send et écrits le message dans la shoutbox */ - cmdSend: function(param,pipe) { + cmdSend: function(param, pipe) { this.writeMessage(param.msg, this.core.user.properties.name); }, - /*** * Intercepte le raw data et écrits le message dans la shoutbox */ - rawData: function(raw, pipe){ + rawData: function(raw, pipe) { this.writeMessage(raw.data.msg, raw.data.from.properties.name); }, - /*** * Écrit un message dans la shoutbox */ - writeMessage: function(message, senderNickname){ + writeMessage: function(message, senderNickname) { //Création d'un élement pour contenir le message - var container = new Element('div',{'class': 'msg_container'}).inject(this.els.shoutbox_msg, 'top'); - + var container = new Element('div', {'class': 'msg_container'}).inject(this.els.shoutbox_msg, 'top'); //Ajout du pseudo - new Element('span',{ + new Element('span', { 'class': 'pseudo', - 'text': senderNickname+' : ' + 'text': senderNickname + ' : ' }).inject(container); - //Ajout du msg - new Element('span',{ + new Element('span', { 'class': 'msg', 'text': unescape(message) //les messages reçu du serveur sont echapé }).inject(container); - }, /*** * Envoie un message */ - postMessage: function(ev){ + postMessage: function(ev) { //Empèche le formulaire d'être soumis ev.stop(); - //Récupération du contenu de l'input var msg = this.els.shoutboxInput.get('value'); - //Enlève les espaces en début et fin de chaine msg = msg.trim(); - //Si le message n'est pas vide, il est envoyé - if(msg!=''){ + if (msg != '') { //Envoie du message a l'aide de la méthode send() de l'objet pipe this.pipe.send(msg); - //Effacement de l'input this.els.shoutboxInput.set('value', ''); } diff --git a/Demos/TCPSockets/chat.css b/Demos/TCPSockets/chat.css index 4dfbcc0..cfb7c2c 100755 --- a/Demos/TCPSockets/chat.css +++ b/Demos/TCPSockets/chat.css @@ -1,119 +1,133 @@ #signin_popup { - width:220pt; - padding:2em; - padding-bottom:3em; - text-align:center; - background-color: #A4C9D6; - color:#0E3541; - border: 5pt solid #195263; - position:absolute; - top:80pt; - left:100pt; - z-index:2; + width:220pt; + padding:2em; + padding-bottom:3em; + text-align:center; + background-color: #A4C9D6; + color:#0E3541; + border: 5pt solid #195263; + position:absolute; + top:80pt; + left:100pt; + z-index:2; } + #nickname { - width:90pt; + width:90pt; } + #absorb_clicks { - position:absolute; - top:0; - left:0; - width:300pt; - height:200pt; + position:absolute; + top:0; + left:0; + width:300pt; + height:200pt; } .chatbox { - width:490pt; - font: 100% Verdana, sans-serif; - background-color:#24758E; - padding:5pt; - position:relative; + width:490pt; + font: 100% Verdana, sans-serif; + background-color:#24758E; + padding:5pt; + position:relative; } + .chatbox .left_column { - width: 360pt; + width: 360pt; } + .chatbox .chathistory { - width:100%; - height:217pt; - overflow: auto; - background-color: white; + width:100%; + height:217pt; + overflow: auto; + background-color: white; } + .chatbox .chathistory div { - background-color:#ffffee; - padding:.2em; - padding-left:1.5em; - text-indent: -1em; - border-bottom: 1px dotted #ccc; + background-color:#ffffee; + padding:.2em; + padding-left:1.5em; + text-indent: -1em; + border-bottom: 1px dotted #ccc; } + .chatbox .chathistory .message .user { - width:5em; - font-weight: bold; - color:#DD7200; - padding-right:.2em; - text-align:right; + width:5em; + font-weight: bold; + color:#DD7200; + padding-right:.2em; + text-align:right; } + .chatbox .chathistory .message.self .user{ - color: #B42600; + color: #B42600; } + .chatbox .chathistory .message.mentioned{ - background-color: #d5c5ff; + background-color: #d5c5ff; } + .chatbox .chathistory .message.private{ - background-color: #ccc; + background-color: #ccc; } + .chatbox .chathistory .message.ctcp { - background-color: #f0f; + background-color: #f0f; } + .chatbox .chathistory .action, .chatbox .chathistory .action .user { - color: #D68850; - font-style:italic; + color: #D68850; + font-style:italic; } + .chatbox .chathistory .action.self, .chatbox .chathistory .action.self .user { - color: #A94C3B; - font-style:italic; + color: #A94C3B; + font-style:italic; } + .chatbox .chathistory .informative { - font-size: 85%; - font-style: italic; - color:#999; - text-align:center; - padding:.3em; + font-size: 85%; + font-style: italic; + color:#999; + text-align:center; + padding:.3em; } .chatbox .chatbox_input { - width:100%; - height:30pt; - margin:0; - margin-top:3pt; - padding:0; - border:0; - font-family: Verdana, sans-serif; + width:100%; + height:30pt; + margin:0; + margin-top:3pt; + padding:0; + border:0; + font-family: Verdana, sans-serif; } .chatbox .user_list { - float:right; - position: absolute; - padding:0; - margin:0; - right:5pt; - top:5pt; - background-color:white; - width:127pt; - height:250pt; - overflow:auto; + float:right; + position: absolute; + padding:0; + margin:0; + right:5pt; + top:5pt; + background-color:white; + width:127pt; + height:250pt; + overflow:auto; } + .chatbox .user_list .user_entry{ - padding:.2em; - padding-left:8pt; - border-bottom: 1px dotted #ccc; + padding:.2em; + padding-left:8pt; + border-bottom: 1px dotted #ccc; } #events { - visibility:hidden; - border:0; - width:0; - height:0; -/* display:none;*/ -} \ No newline at end of file + visibility:hidden; + border:0; + width:0; + height:0; +/* display:none;*/ +} diff --git a/Demos/TCPSockets/chat.js b/Demos/TCPSockets/chat.js index ee1e04f..f556f3e 100755 --- a/Demos/TCPSockets/chat.js +++ b/Demos/TCPSockets/chat.js @@ -1,360 +1,322 @@ // TODO refactor this code into a UI and non-UI part. // This code is from http://orbited.org -var CHANNEL = "#ape-project" -var IRC_SERVER = 'irc.freenode.net' -var IRC_PORT = '6667' - +var CHANNEL = '#ape-project'; +var IRC_SERVER = 'irc.freenode.net'; +var IRC_PORT = '6667'; var nickname = null; var users = {}; - var irc = new IRCClient(); -var log = getIrcLogger("Chat"); - -var connect = function () { - $('#signin_popup, #absorb_clicks').hide(); - $('#chatbox_input').focus(); - $('#chatbox_input').val('') - - nickname = $("#nickname").val(); - - function parseName(identity) { - // TODO remove privileges from name head. - return identity.split("!", 1)[0]; - } - irc.onACTION = function(command) { - var messagediv = $('
'); - var sender = parseName(command.prefix); - messagediv.addClass("action"); - var target = command.args[0]; - var message = command.args.slice(1).join(" ") - - if (sender == nickname) - messagediv.addClass("self"); - - if (isSubstring(nickname, message)) - messagediv.addClass("mentioned"); - - messagediv.html('' + sender + ' ' + - sanitize(message)) - .appendTo("#chathistory"); - scrollDown(); - } - irc.onCTCP = function(command) { - var messagediv = $('
'); - messagediv.addClass("ctcp"); - var message = command.args.slice(1).join(" ") - var sender = parseName(command.prefix); - messagediv. - html('' + sender + '(CTCP): ' + sanitize(message)). - appendTo("#chathistory"); - scrollDown(); - } - irc.onPRIVMSG = function(command) { - var sender = parseName(command.prefix); - var target = command.args[0]; - var message = command.args[1]; - - var messagediv = $('
'); - - if (message.charCodeAt(0) == 1) { - // This is an CTCP request. - - // NB: the embedded CTCP requests is: - // \x01[ ]+\x01 - // eg: - // when we type "/me waves", we receive the following message: - // \x01ACTION waves\x01 - // eg: - // after we connect, the freenode networks sends: - // \x01VERSION\x01 - // See http://www.invlogic.com/irc/ctcp.html - // NB: CTCP draft talks about quoting but it does not actually - // define it. - var args = message.slice(1, message.length - 1).split(' '); - var request = args.shift(); - if (request == "ACTION") { - message = args.join(" "); - messagediv.addClass("action"); - } else { - message = request + " " + args.join(" "); - messagediv.addClass("ctcp"); - } - } - - if (sender == nickname) - messagediv.addClass("self"); - - if (target == nickname) - messagediv.addClass("private"); - - if (isSubstring(nickname, message)) - messagediv.addClass("mentioned"); - - messagediv.html('' + sender + ': ' + - sanitize(message)) - .appendTo("#chathistory"); - scrollDown(); - }; - irc.onTOPIC = function(command) { - // See http://tools.ietf.org/html/rfc2812#section-3.2.4 - // Args: [ ] - - var channel = command.args[0]; - if (channel != CHANNEL) - return; - - var topic = command.args[1]; - - var user = parseName(command.prefix); - $("
"). - html('' + user + ' changed the topic to: ' + sanitize(topic)). - appendTo("#chathistory"); - }; -irc.onerror = function(command) { - var responseCode = parseInt(command.type); - if (responseCode == 431 || responseCode == 432 || responseCode == 433) { - // 431 ERR_NONICKNAMEGIVEN - // 432 ERR_ERRONEUSNICKNAME - // 433 ERR_NICKNAMEINUSE - nickname += '_' - irc.nick(nickname) - irc.join(CHANNEL) - } -} - - - irc.onresponse = function(command) { - var responseCode = parseInt(command.type); - - if (responseCode == 332) { - // """ - // 331 RPL_NOTOPIC - // :No topic is set - // 332 RPL_TOPIC - // : - // - // When sending a TOPIC message to determine the - // channel topic, one of two replies is sent. If - // the topic is set, RPL_TOPIC is sent back else - // RPL_NOTOPIC. - // """ -- rfc2812 - - var channel = command.args[1]; - if (channel != CHANNEL) - return; - - var topic = command.args[2]; - - $("
"). - html("Channel topic is: " + sanitize(topic)). - appendTo("#chathistory"); - } else if (responseCode == 353) { - // 353 is the code for RPL_NAMEREPLY. - - // The args are: - // - // """ - // ( "=" / "*" / "@" ) - // :[ "@" / "+" ] *( " " [ "@" / "+" ] ) - // - // - "@" is used for secret channels, "*" for private - // channels, and "=" for others (public channels). - // """ -- rfc2812 - - var channel = command.args[2]; - if (channel != CHANNEL) - return; - - var partialUserList = command.args[3].split(' '); - for (var i = 0, l = partialUserList.length; i < l; ++i) { - var name = $.trim(partialUserList[i]); - if (name == "" || users[name]) - continue; - addName(name); - } - } else if (responseCode == 366) { - // 366 is the code for RPL_ENDOFNAMES. - - fillUserList(); - - $("
"). - html("Joined " + CHANNEL + " channel."). - appendTo("#chathistory"); - scrollDown(); - } - }; - - - irc.onopen = function() { - irc.nick(nickname); - irc.ident(nickname, '8 *', nickname); - irc.join(CHANNEL); - - // Once we have joined, don't leave without warning the user - window.onbeforeunload = function() { - return 'Are you sure you want to quit and lose the chat connection?'; - }; - $(window).unload(function() { - hardquit(); -// quit(); - }) - } - irc.onclose = function() { - log.debug("closed..."); - }; - irc.onNICK = function(command) { - // See http://tools.ietf.org/html/rfc2812#section-3.1.2 - var previousNick = parseName(command.prefix); - var newNick = command.args[0]; - userRenamed(previousNick, newNick); - }; - // TODO implement onNOTICE... - irc.onJOIN = function(command) { - var joiner = parseName(command.prefix); - - addName(joiner); - fillUserList(); - - $("
") - .html("" + joiner + ' has joined ' + CHANNEL) - .appendTo("#chathistory"); - scrollDown(); - } - irc.onPART = function(command) { - var leaver = parseName(command.prefix); - var message = command.args.join(" "); - - $("
") - .html("" + leaver + ' left ' + CHANNEL + - (message ? ' (“' + sanitize(message) + '”)' : '')) - .appendTo("#chathistory"); - scrollDown(); - - removeName(leaver); - } - - irc.onQUIT = function(command) { - var quitter = parseName(command.prefix); - var message = command.args.join(" "); +var log = getIrcLogger('Chat'); + +var connect = function() { + $('#signin_popup, #absorb_clicks').hide(); + $('#chatbox_input').focus(); + $('#chatbox_input').val(''); + nickname = $('#nickname').val(); + function parseName(identity) { + // TODO remove privileges from name head. + return identity.split('!', 1)[0]; + }; + irc.onACTION = function(command) { + var messagediv = $('
'); + var sender = parseName(command.prefix); + messagediv.addClass('action'); + var target = command.args[0]; + var message = command.args.slice(1).join(' '); + if (sender == nickname) + messagediv.addClass('self'); + if (isSubstring(nickname, message)) + messagediv.addClass('mentioned'); + messagediv.html('' + sender + ' ' + + sanitize(message)) + .appendTo('#chathistory'); + scrollDown(); + }; + irc.onCTCP = function(command) { + var messagediv = $('
'); + messagediv.addClass('ctcp'); + var message = command.args.slice(1).join(' '); + var sender = parseName(command.prefix); + messagediv. + html('' + sender + '(CTCP): ' + sanitize(message)). + appendTo('#chathistory'); + scrollDown(); + }; + irc.onPRIVMSG = function(command) { + var sender = parseName(command.prefix); + var target = command.args[0]; + var message = command.args[1]; + var messagediv = $('
'); + if (message.charCodeAt(0) == 1) { + // This is an CTCP request. + // NB: the embedded CTCP requests is: + // \x01[ ]+\x01 + // eg: + // when we type "/me waves", we receive the following message: + // \x01ACTION waves\x01 + // eg: + // after we connect, the freenode networks sends: + // \x01VERSION\x01 + // See http://www.invlogic.com/irc/ctcp.html + // NB: CTCP draft talks about quoting but it does not actually + // define it. + var args = message.slice(1, message.length - 1).split(' '); + var request = args.shift(); + if (request == 'ACTION') { + message = args.join(' '); + messagediv.addClass('action'); + } else { + message = request + ' ' + args.join(' '); + messagediv.addClass('ctcp'); + } + } + if (sender == nickname) + messagediv.addClass('self'); + if (target == nickname) + messagediv.addClass('private'); + if (isSubstring(nickname, message)) + messagediv.addClass('mentioned'); + messagediv.html('' + sender + ': ' + + sanitize(message)) + .appendTo('#chathistory'); + scrollDown(); + }; + irc.onTOPIC = function(command) { + // See http://tools.ietf.org/html/rfc2812#section-3.2.4 + // Args: [ ] + var channel = command.args[0]; + if (channel != CHANNEL) + return; + var topic = command.args[1]; + var user = parseName(command.prefix); + $("
"). + html('' + user + ' changed the topic to: ' + sanitize(topic)). + appendTo('#chathistory'); + }; + irc.onerror = function(command) { + var responseCode = parseInt(command.type); + if (responseCode == 431 || responseCode == 432 || responseCode == 433) { + // 431 ERR_NONICKNAMEGIVEN + // 432 ERR_ERRONEUSNICKNAME + // 433 ERR_NICKNAMEINUSE + nickname += '_'; + irc.nick(nickname); + irc.join(CHANNEL); + } + }; + irc.onresponse = function(command) { + var responseCode = parseInt(command.type); + if (responseCode == 332) { + // """ + // 331 RPL_NOTOPIC + // :No topic is set + // 332 RPL_TOPIC + // : + // + // When sending a TOPIC message to determine the + // channel topic, one of two replies is sent. If + // the topic is set, RPL_TOPIC is sent back else + // RPL_NOTOPIC. + // """ -- rfc2812 + var channel = command.args[1]; + if (channel != CHANNEL) + return; + var topic = command.args[2]; + $('
'). + html('Channel topic is: ' + sanitize(topic)). + appendTo('#chathistory'); + } else if (responseCode == 353) { + // 353 is the code for RPL_NAMEREPLY. + // The args are: + // + // """ + // ( "=" / "*" / "@" ) + // :[ "@" / "+" ] *( " " [ "@" / "+" ] ) + // + // - "@" is used for secret channels, "*" for private + // channels, and "=" for others (public channels). + // """ -- rfc2812 + var channel = command.args[2]; + if (channel != CHANNEL) + return; + var partialUserList = command.args[3].split(' '); + for (var i = 0, l = partialUserList.length; i < l; ++i) { + var name = $.trim(partialUserList[i]); + if (name == '' || users[name]) + continue; + addName(name); + } + } else if (responseCode == 366) { + // 366 is the code for RPL_ENDOFNAMES. + fillUserList(); + $('
'). + html('Joined ' + CHANNEL + ' channel.'). + appendTo('#chathistory'); + scrollDown(); + } + }; + irc.onopen = function() { + irc.nick(nickname); + irc.ident(nickname, '8 *', nickname); + irc.join(CHANNEL); + // Once we have joined, don't leave without warning the user + window.onbeforeunload = function() { + return 'Are you sure you want to quit and lose the chat connection?'; + }; + $(window).unload(function() { + hardquit(); +// quit(); + }); + }; + irc.onclose = function() { + log.debug('closed...'); + }; + irc.onNICK = function(command) { + // See http://tools.ietf.org/html/rfc2812#section-3.1.2 + var previousNick = parseName(command.prefix); + var newNick = command.args[0]; + userRenamed(previousNick, newNick); + }; + // TODO implement onNOTICE... + irc.onJOIN = function(command) { + var joiner = parseName(command.prefix); + addName(joiner); + fillUserList(); + $('
') + .html('' + joiner + ' has joined ' + CHANNEL) + .appendTo('#chathistory'); + scrollDown(); + }; + irc.onPART = function(command) { + var leaver = parseName(command.prefix); + var message = command.args.join(' '); + $('
') + .html('' + leaver + ' left ' + CHANNEL + + (message ? ' (“' + sanitize(message) + '”)' : '')) + .appendTo('#chathistory'); + scrollDown(); + removeName(leaver); + }; + irc.onQUIT = function(command) { + var quitter = parseName(command.prefix); + var message = command.args.join(' '); + $('
') + .html('' + quitter + ' quit' + + (message ? ' (“' + sanitize(message) + '”)' : '')) + .appendTo('#chathistory'); + scrollDown(); + removeName(quitter); + }; + irc.connect(IRC_SERVER, IRC_PORT); +}; - $("
") - .html("" + quitter + ' quit' + - (message ? ' (“' + sanitize(message) + '”)' : '')) - .appendTo("#chathistory"); - scrollDown(); - - removeName(quitter); - } - - irc.connect(IRC_SERVER, IRC_PORT); +var chat = function() { + msg = $('#chatbox_input').val(); + irc.privmsg(CHANNEL, msg); + $('#chatbox_input').val(''); + // the IRC server will not echo our message back, so simulate a send. + irc.onPRIVMSG({prefix: nickname, type: 'PRIVMSG', args: [CHANNEL, msg]}); }; -var chat = function () { - msg = $('#chatbox_input').val(); - irc.privmsg(CHANNEL, msg); - $('#chatbox_input').val(''); - // the IRC server will not echo our message back, so simulate a send. - irc.onPRIVMSG({prefix:nickname,type:'PRIVMSG',args:[CHANNEL, msg]}); -} var hardquit = function() { - irc.reset(); -} -var quit = function () { - // XXX sometimes the quit reason is not seen when we quit... - // probably because the browser is immediately closed - // without waiting for socket flush? - irc.quit(); -} + irc.reset(); +}; + +var quit = function() { + // XXX sometimes the quit reason is not seen when we quit... + // probably because the browser is immediately closed + // without waiting for socket flush? + irc.quit(); +}; // TODO enable the nick change code. // function nickChanged(newnick) { -// userRenamed(nickname, newnick); -// nickname = newnick; +// userRenamed(nickname, newnick); +// nickname = newnick; // } function userRenamed(oldname, newname) { - $("
") - .html("" + oldname + " is now known as " + - "" + newname + "") - .appendTo("#chathistory"); - scrollDown(); - - $(".user_list .user#user_" + oldname) - .attr("id", "user_" + newname) - .html(newname); + $('
') + .html('' + oldname + ' is now known as ' + + '' + newname + '') + .appendTo('#chathistory'); + scrollDown(); + $('.user_list .user#user_' + oldname) + .attr('id', 'user_' + newname) + .html(newname); } -var user_priviledges = function (name) { - var privs_symbols = {"@":"admin", "%":"subadmin", "+":"voice"}; - if (name[0] in privs_symbols) { - return privs_symbols[name[0]]; - } else { - return ""; - }; +var user_priviledges = function(name) { + var privs_symbols = {'@': 'admin', '%': 'subadmin', '+': 'voice'}; + if (name[0] in privs_symbols) { + return privs_symbols[name[0]]; + } else { + return ''; + } }; -var addName = function (name) { - // XXX this MUST remove the nick privileges from name (eg: because - // these prefixes are not sent when the user changed his nick, - // etc). - var priviledges = user_priviledges(name); - users[name] = {"privs":priviledges}; - $('
' + name + '
') - .addClass(priviledges) - .appendTo("#user_list"); +var addName = function(name) { + // XXX this MUST remove the nick privileges from name (eg: because + // these prefixes are not sent when the user changed his nick, + // etc). + var priviledges = user_priviledges(name); + users[name] = {'privs': priviledges}; + $('
' + name + '
') + .addClass(priviledges) + .appendTo('#user_list'); }; -var removeName = function (name) { - delete users[name]; - $(".user_list #user_" + name).remove() +var removeName = function(name) { + delete users[name]; + $('.user_list #user_' + name).remove(); }; -var fillUserList = function () { - $('.user_list').empty(); - //alert("test"); - var list = []; - for (var user in users) { - list.push(user); - }; - list.sort() - for (var i=0; i < list.length; i++) { - var user = list[i]; - privs = users[user]['privs']; - $('
' + user + '
') - .addClass(privs) - .appendTo("#user_list"); - }; +var fillUserList = function() { + $('.user_list').empty(); + //alert("test"); + var list = []; + for (var user in users) { + list.push(user); + } + list.sort(); + for (var i = 0; i < list.length; i++) { + var user = list[i]; + privs = users[user]['privs']; + $('
' + user + '
') + .addClass(privs) + .appendTo('#user_list'); + } }; -var scrollDown = function () { - var box = document.getElementById('chathistory'); - box.scrollTop = box.scrollHeight; -} +var scrollDown = function() { + var box = document.getElementById('chathistory'); + box.scrollTop = box.scrollHeight; +}; -var isSubstring = function (sub, str) { - // case insensitive substring test - return str.toLowerCase().indexOf(sub.toLowerCase()) >= 0; +var isSubstring = function(sub, str) { + // case insensitive substring test + return str.toLowerCase().indexOf(sub.toLowerCase()) >= 0; }; var sanitize = (function(str) { - // See http://bigdingus.com/2007/12/29/html-escaping-in-javascript/ - var MAP = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; - var repl = function(c) { return MAP[c]; }; - return function(s) { - return s.replace(/[&<>'"]/g, repl); - }; + // See http://bigdingus.com/2007/12/29/html-escaping-in-javascript/ + var MAP = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''' + }; + var repl = function(c) { return MAP[c]; }; + return function(s) { + return s.replace(/[&<>'"]/g, repl); + }; })(); // function infoMessage(message) { -// $("
") -// .html(message) -// .appendTo("#chathistory"); -// scrollDown(); +// $("
") +// .html(message) +// .appendTo("#chathistory"); +// scrollDown(); // }; diff --git a/Demos/TCPSockets/demo.html b/Demos/TCPSockets/demo.html index 30f3697..252a8d4 100755 --- a/Demos/TCPSockets/demo.html +++ b/Demos/TCPSockets/demo.html @@ -1,6 +1,6 @@ - + @@ -10,21 +10,17 @@
-
-

Sign in to the #ape-project chat room.

- - -
-
-
- -
-
- -
-
-
+
+

Sign in to the #ape-project chat room.

+ + +
+
+
+
+ +
+
+


diff --git a/Demos/TCPSockets/irc2.js b/Demos/TCPSockets/irc2.js index bcdefe1..1824ea3 100755 --- a/Demos/TCPSockets/irc2.js +++ b/Demos/TCPSockets/irc2.js @@ -3,23 +3,23 @@ * Orbited 0.5+ required * * Methods: - * connect(hostname, port) - * ident(nickname, modes, real_name) - * join(channel) - * names(channel) - * part(channel) - * quit(reason) - * privmsg(destination, message) + * connect(hostname, port) + * ident(nickname, modes, real_name) + * join(channel) + * names(channel) + * part(channel) + * quit(reason) + * privmsg(destination, message) * * Callbacks: - * Built-in callbacks are onconnect(), onerror(), onresponse(), and onclose() - * onerror and onreply are passed numerical reponse codes, see: - * http://www.irchelp.org/irchelp/rfc/chapter6.html for a list of IRC response - * codes. + * Built-in callbacks are onconnect(), onerror(), onresponse(), and onclose() + * onerror and onreply are passed numerical reponse codes, see: + * http://www.irchelp.org/irchelp/rfc/chapter6.html for a list of IRC response + * codes. * - * To add callbacks for IRC actions, for instance PRIVMSG, - * set onPRIVMSG = function(command) {...you code here...} - * See the included IRC demo (/static/demos/irc) for example usage + * To add callbacks for IRC actions, for instance PRIVMSG, + * set onPRIVMSG = function(command) {...you code here...} + * See the included IRC demo (/static/demos/irc) for example usage * * Frank Salim (frank.salim@gmail.com) * ©2008 The Orbited Project @@ -29,196 +29,188 @@ IRC_DEBUG = false; if (IRC_DEBUG && typeof(Orbited)) { - var getIrcLogger = function(name) { - var logger = Orbited.getLogger(name); - if (!("dir" in logger)) { - logger.dir = function() {}; - } - return logger; - } -} -else if (IRC_DEBUG && typeof(console)) { - var getIrcLogger = function(name) { - return { - debug: function() { - var args = Array.prototype.slice.call(arguments); - args.unshift(name, ": "); - console.debug.apply(console, args); - }, - dir: function() { - console.debug(name, ":"); - console.dir.apply(console, arguments); - } - }; - }; -} -else { - var getIrcLogger = function(name) { - return { - debug: function() {}, - dir: function() {} - }; - }; + var getIrcLogger = function(name) { + var logger = Orbited.getLogger(name); + if (!('dir' in logger)) { + logger.dir = function() {}; + } + return logger; + }; +}else if (IRC_DEBUG && typeof(console)) { + var getIrcLogger = function(name) { + return { + debug: function() { + var args = Array.prototype.slice.call(arguments); + args.unshift(name, ': '); + console.debug.apply(console, args); + }, + dir: function() { + console.debug(name, ':'); + console.dir.apply(console, arguments); + } + }; + }; +} else { + var getIrcLogger = function(name) { + return { + debug: function() {}, + dir: function() {} + }; + }; } IRCClient = function() { - var log = getIrcLogger("IRCClient"); - var self = this - var conn = null - var buffer = "" - var ENDL = "\r\n" - - self.onopen = function() {}; - self.onconnect = function() {} // Do nothing in default callbacks - self.onclose = function() {} - self.onerror = function(command) {} - self.onresponse = function(command) {} // used for numerical replies - - self.connect = function(hostname, port) { - log.debug("connect"); - conn = self._createTransport(); - conn.onopen = conn_opened - conn.onclose = conn_closed - conn.onread = conn_read - conn.open(hostname, port) - // TODO set onerror. - } - self._createTransport = function() { - return new TCPSocket(); - }; - self.close = function(code) { - log.debug("close: "+code); - conn.close(); - conn.onopen = null; - conn.onclose = null; - conn.onread = null; - self.onclose(code); - } - 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.names = function(channel) { - send("NAMES", channel) - } - self.part = function(channel, reason) { - send("PART", channel + " :" + reason) - } - self.quit = function(reason) { - var reason = reason || "leaving"; - send("QUIT", ":" + reason) - conn.close() - } - self.reset = function() { - conn.reset(); - } - self.action = function(destination, message) { - send('PRIVMSG', destination + ' :\01ACTION ' + message + '\01') - } - - self.privmsg = function(destination, message) { - send('PRIVMSG', destination + ' :' + message) - } - - // Socket Callbacks - var conn_opened = function() { - self.onopen() - } - var conn_closed = function(code) { - self.onclose(code) - } - var conn_read = function(data) { - log.debug("data:"); - log.debug(data); - buffer += data - parse_buffer() - } - - // Internal Functions - var send = function(type, payload) { - log.debug("send: " + payload); - conn.send(type + " " + payload + ENDL); - }; - var parse_buffer= function() { - var commands = buffer.split(ENDL); - buffer = commands[commands.length-1]; - for (var i = 0, l = commands.length - 1; i < l; ++i) { - var line = commands[i]; - if (line.length > 0) - dispatch(line); - } - }; - var parse_command = function(s) { - // See http://tools.ietf.org/html/rfc2812#section-2.3 - - // all the arguments are split by a single space character until - // the first ":" character. the ":" marks the start of the last - // trailing argument which can contain embeded space characters. - var i = s.indexOf(" :"); - if (i >= 0) { - var args = s.slice(0, i).split(' '); - args.push(s.slice(i + 2)); - } else { - var args = s.split(' '); - } - - // extract the prefix (if there is one). - if (args[0].charAt(0) == ":") { - var prefix = args.shift().slice(1); - } else { - var prefix = null; - } + var log = getIrcLogger('IRCClient'); + var self = this; + var conn = null; + var buffer = ''; + var ENDL = '\r\n'; + self.onopen = function() {}; + self.onconnect = function() {};// Do nothing in default callbacks + self.onclose = function() {}; + self.onerror = function(command) {}; + self.onresponse = function(command) {}; // used for numerical replies + self.connect = function(hostname, port) { + log.debug('connect'); + conn = self._createTransport(); + conn.onopen = conn_opened; + conn.onclose = conn_closed; + conn.onread = conn_read; + conn.open(hostname, port); + // TODO set onerror. + }; + self._createTransport = function() { + return new TCPSocket(); + }; + self.close = function(code) { + log.debug('close: ' + code); + conn.close(); + conn.onopen = null; + conn.onclose = null; + conn.onread = null; + self.onclose(code); + }; + 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.names = function(channel) { + send('NAMES', channel); + }; + self.part = function(channel, reason) { + send('PART', channel + ' :' + reason); + }; + self.quit = function(reason) { + var reason = reason || 'leaving'; + send('QUIT', ':' + reason); + conn.close(); + }; + self.reset = function() { + conn.reset(); + }; + self.action = function(destination, message) { + send('PRIVMSG', destination + ' :\01ACTION ' + message + '\01'); + }; + self.privmsg = function(destination, message) { + send('PRIVMSG', destination + ' :' + message); + }; + // Socket Callbacks + var conn_opened = function() { + self.onopen(); + }; + var conn_closed = function(code) { + self.onclose(code); + }; + var conn_read = function(data) { + log.debug('data:'); + log.debug(data); + buffer += data; + parse_buffer(); + }; + // Internal Functions + var send = function(type, payload) { + log.debug('send: ' + payload); + conn.send(type + ' ' + payload + ENDL); + }; + var parse_buffer = function() { + var commands = buffer.split(ENDL); + buffer = commands[commands.length - 1]; + for (var i = 0, l = commands.length - 1; i < l; ++i) { + var line = commands[i]; + if (line.length > 0) + dispatch(line); + } + }; + var parse_command = function(s) { + // See http://tools.ietf.org/html/rfc2812#section-2.3 - var command = { - prefix: prefix, - type: args.shift(), - args: args - }; - log.debug("command:"); - log.dir(command); - return command; - }; - var dispatch = function(line) { - command = parse_command(line); - if (command.type == "PING") { - send("PONG", ":" + command.args) - } - if (!isNaN(parseInt(command.type))) { - var error_code = parseInt(command.type) - if (error_code > 400) - return self.onerror(command) - else - return self.onresponse(command) - } - if (command.type == "PRIVMSG") { - msg = command.args[1] - if (msg.charCodeAt(0) == 1 && msg.charCodeAt(msg.length-1) == 1) { - var args = [command.args[0]] - var newargs = msg.slice(1, msg.length - 1).split(' ') - if (newargs[0] == 'ACTION') { - command.type = newargs.shift() - } - else { - command.type = 'CTCP' - } + // all the arguments are split by a single space character until + // the first ":" character. the ":" marks the start of the last + // trailing argument which can contain embeded space characters. + var i = s.indexOf(' : '); + if (i >= 0) { + var args = s.slice(0, i).split(' '); + args.push(s.slice(i + 2)); + } else { + var args = s.split(' '); + } - for (var i = 0; i < newargs.length; ++i) { - args.push(newargs[i]) - } - command.args = args - } - } - if (typeof(self["on" + command.type]) == "function") { - // XXX the user is able to define unknown command handlers, - // but cannot send any arbitrary command - self["on" + command.type](command); - } else { - log.debug("unhandled command received: ", command.type); - } - }; + // extract the prefix (if there is one). + if (args[0].charAt(0) == ':') { + var prefix = args.shift().slice(1); + } else { + var prefix = null; + } + var command = { + prefix: prefix, + type: args.shift(), + args: args + }; + log.debug('command:'); + log.dir(command); + return command; + }; + var dispatch = function(line) { + command = parse_command(line); + if (command.type == 'PING') { + send('PONG', ':' + command.args); + } + if (! isNaN(parseInt(command.type))) { + var error_code = parseInt(command.type); + if (error_code > 400) { + return self.onerror(command); + } else { + return self.onresponse(command); + } + } + if (command.type == 'PRIVMSG') { + msg = command.args[1]; + if (msg.charCodeAt(0) == 1 && msg.charCodeAt(msg.length - 1) == 1) { + var args = [command.args[0]]; + var newargs = msg.slice(1, msg.length - 1).split(' '); + if (newargs[0] == 'ACTION') { + command.type = newargs.shift(); + } + else { + command.type = 'CTCP'; + } + for (var i = 0; i < newargs.length; ++i) { + args.push(newargs[i]); + } + command.args = args; + } + } + if (typeof(self['on' + command.type]) == 'function') { + // XXX the user is able to define unknown command handlers, + // but cannot send any arbitrary command + self['on' + command.type](command); + } else { + log.debug('unhandled command received: ', command.type); + } + }; }; diff --git a/Demos/config.js b/Demos/config.js index 2373b5d..68bd097 100644 --- a/Demos/config.js +++ b/Demos/config.js @@ -2,11 +2,11 @@ * APE JSF Setup */ -APE.Config.baseUrl = 'http://local.ape-project.org/APE_JSF'; //APE JSF -APE.Config.domain = 'ape-project.org'; +APE.Config.baseUrl = 'http://local.ape-project.org/APE_JSF'; //APE JSF +APE.Config.domain = 'ape-project.org'; APE.Config.server = 'ape.local.ape-project.org:6969'; //APE server URL -(function(){ +(function() { for (var i = 0; i < arguments.length; i++) APE.Config.scripts.push(APE.Config.baseUrl + '/Source/' + arguments[i] + '.js'); -})('mootools-core', 'Core/APE', 'Core/Events', 'Core/Core', 'Pipe/Pipe', 'Pipe/PipeProxy', 'Pipe/PipeMulti', 'Pipe/PipeSingle', 'Request/Request','Request/Request.Stack', 'Request/Request.CycledStack', 'Transport/Transport.longPolling','Transport/Transport.SSE', 'Transport/Transport.XHRStreaming', 'Transport/Transport.JSONP', 'Transport/Transport.WebSocket', 'Core/Utility', 'Core/JSON'); +})('mootools-core', 'Core/APE', 'Core/Events', 'Core/Core', 'Pipe/Pipe', 'Pipe/PipeProxy', 'Pipe/PipeMulti', 'Pipe/PipeSingle', 'Request/Request', 'Request/Request.Stack', 'Request/Request.CycledStack', 'Transport/Transport.longPolling', 'Transport/Transport.SSE', 'Transport/Transport.XHRStreaming', 'Transport/Transport.JSONP', 'Transport/Transport.WebSocket', 'Core/Utility', 'Core/JSON'); diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c910e56 --- /dev/null +++ b/Makefile @@ -0,0 +1,59 @@ +#Configure these for your situation +JSFDIR=. +CLOSURECOMPILER=/opt/closureCompiler/compiler.jar +YUICOMPRESSOR=/opt/yuicompressor-2.4.7/build/yuicompressor-2.4.7.jar + +#CONFIGJS=$(JSFDIR)/Demos/config.js + +#Some constants and variables +SRCDIR=$(JSFDIR)/Source +#CONFIGJS=$(JSFDIR)/Demos/config.js +CLIENTSDIR=$(JSFDIR)/Clients +MOOTOOLSCORECLIENT=$(CLIENTSDIR)/mootools-core.js +MOOTOOLSCORESOURCE=$(SRCDIR)/mootools-core.js +TARGETDIR=BuildTest +CATTEDDIR=uncompressed +COMPRESSEDDIR=yuiCompressor +COMPILEDDIR=closureCompiler +#The jsf dependencies +JSF=$(SRCDIR)/Core/APE.js $(SRCDIR)/Core/Events.js $(SRCDIR)/Core/Core.js $(SRCDIR)/Pipe/Pipe.js $(SRCDIR)/Pipe/PipeProxy.js $(SRCDIR)/Pipe/PipeMulti.js $(SRCDIR)/Pipe/PipeSingle.js $(SRCDIR)/Request/Request.js $(SRCDIR)/Request/Request.Stack.js $(SRCDIR)/Request/Request.CycledStack.js $(SRCDIR)/Transport/Transport.longPolling.js $(SRCDIR)/Transport/Transport.SSE.js $(SRCDIR)/Transport/Transport.XHRStreaming.js $(SRCDIR)/Transport/Transport.JSONP.js $(SRCDIR)/Transport/Transport.WebSocket.js $(SRCDIR)/Core/Utility.js $(SRCDIR)/Core/JSON.js +JSFSESSION = $(JSF) $(SRCDIR)/Core/Session.js + +#let's make all + +all: targetdirs compressedstuff + +compressedstuff: compiledstuff + java -jar $(YUICOMPRESSOR) -o $(TARGETDIR)/$(COMPRESSEDDIR)/apeClientJS.js $(TARGETDIR)/$(COMPILEDDIR)/apeClientJS.js + java -jar $(YUICOMPRESSOR) -o $(TARGETDIR)/$(COMPRESSEDDIR)/apeClientMoo.js $(TARGETDIR)/$(COMPILEDDIR)/apeClientMoo.js + java -jar $(YUICOMPRESSOR) -o $(TARGETDIR)/$(COMPRESSEDDIR)/apeCore.js $(TARGETDIR)/$(COMPILEDDIR)/apeCore.js + java -jar $(YUICOMPRESSOR) -o $(TARGETDIR)/$(COMPRESSEDDIR)/apeCoreSession.js $(TARGETDIR)/$(COMPILEDDIR)/apeCoreSession.js + + +compiledstuff: $(TARGETDIR)/$(CATTEDDIR)/apeClientJS.js $(TARGETDIR)/$(CATTEDDIR)/apeClientMoo.js $(TARGETDIR)/$(CATTEDDIR)/apeCore.js $(TARGETDIR)/$(CATTEDDIR)/apeCoreSession.js + java -jar $(CLOSURECOMPILER) --js $(TARGETDIR)/$(CATTEDDIR)/apeClientJS.js --js_output_file $(TARGETDIR)/$(COMPILEDDIR)/apeClientJS.js + java -jar $(CLOSURECOMPILER) --js $(TARGETDIR)/$(CATTEDDIR)/apeClientMoo.js --js_output_file $(TARGETDIR)/$(COMPILEDDIR)/apeClientMoo.js + java -jar $(CLOSURECOMPILER) --js $(TARGETDIR)/$(CATTEDDIR)/apeCore.js --js_output_file $(TARGETDIR)/$(COMPILEDDIR)/apeCore.js + java -jar $(CLOSURECOMPILER) --js $(TARGETDIR)/$(CATTEDDIR)/apeCoreSession.js --js_output_file $(TARGETDIR)/$(COMPILEDDIR)/apeCoreSession.js + +#concatting needs some logic +$(TARGETDIR)/$(CATTEDDIR)/apeClientJS.js: $(CLIENTSDIR)/JavaScript.js $(CONFIGJS) + cat > $@ $^ + +$(TARGETDIR)/$(CATTEDDIR)/apeClientMoo.js: $(CLIENTSDIR)/MooTools.js $(CONFIGJS) + cat > $@ $^ + +$(TARGETDIR)/$(CATTEDDIR)/apeCore.js: $(MOOTOOLSCORESOURCE) $(JSF) + cat >$@ $^ + +$(TARGETDIR)/$(CATTEDDIR)/apeCoreSession.js: $(MOOTOOLSCORESOURCE) $(JSFSESSION) + cat > $@ $^ + +#startup and cleanup +targetdirs: + mkdir -p $(TARGETDIR)/$(CATTEDDIR) $(TARGETDIR)/$(COMPRESSEDDIR) $(TARGETDIR)/$(COMPILEDDIR) + +.PHONEY: clean targetdirs + +clean: + rm -rf $(TARGETDIR) diff --git a/Source/Core/APE.js b/Source/Core/APE.js index 0d701f1..fdc4667 100755 --- a/Source/Core/APE.js +++ b/Source/Core/APE.js @@ -1,3 +1,61 @@ + +/** + * Create a new APE instance. + *

The constuctor only create a APE instance, to connect to the APE server you need to use the start(); method

+ *

If you want to use APE, do NOT start APE in this way, use APE.load() method instead.

+ * + * @name APE + * @class + * @augments-APE.Events + * @public + * + * @property {string} version Version (e.g. 1.1) + * @property {APE.Request} request Request object + * @property {APE.Request.stack } request.stack Request stack + * @property {APE.Request.cycledStack } request.cycledStack Cycled request stack + * @property {APE.Transport} transport Transport variant + * @property {APE.Client} client Client object + * @property {APE.Core} core Core object + * + * @param {object} [options] An object with ape default options + * @param {string} [options.server] APE server URL + * @param {integer} [options.pollTime=25000] Max time for a request in ms + * @param {string} [options.identifier='ape'] Identifier used to differentiate two APE instance when you are using more than one application on your website and using Session.js + * @param {integer} [options.transport=1] Transport method used by APE,

+ * 1 : Long polling
+ * 2 : XHR streaming
+ * 3 : forever iframe
+ * 4 : JSONP
+ * 6 : Websocket

+ * @param {integer} [options.frequency=0] The frequency identifier + * @returns APE An Ape instance + * + * @example + * var ape = new APE.Core({ + * 'server': 'ape.yourdomain.com' + * }); + * @example + * var ape = new APE.Core({ + * 'server': 'ape.yourdomain.com', + * 'pollTime': 35000, //if you set pollTime to 35sec you need to set it on the server side to 55sec + * 'identifier': 'myApplicationIdentifier', + * 'transport': 2, + * 'frequency': 3 + * }); + * ape.start(); + * client.addEvent('load', function() { + * console.log(ape.user.pubid); //Show pubid of current user + * console.log(ape.user.properties); //Show properties of current user + * } + * + * @see APE.client + * @see APE.load + * @see APE.start + * @see APE.onError + * @see APE.onRaw + */ + + var APE = { 'version': '1.1', 'Request': {}, diff --git a/Source/Core/Core.js b/Source/Core/Core.js index ee55bd9..0a06088 100755 --- a/Source/Core/Core.js +++ b/Source/Core/Core.js @@ -1,5 +1,5 @@ /* - Copyright (C) 2008-2009 Weelya + Copyright (C) 2008-2009 Weelya This file is part of APE Client. APE is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -14,16 +14,16 @@ You should have received a copy of the GNU General Public License along with APE ; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - + */ -/*** ________________________________________________________ +/** ________________________________________________________ * __------__ / \ * /~ ~\ | APE, the Ajax Push Engine made with heart (and MooTools) | * | //^\\//^\| | http://www.weelya.net - http://www.ape-project.org | * /~~\ || o| |o|:~\ \ _______________________________________________________/ * | |6 ||___|_|_||:| / - * \__. / o \/' / + * \__. / o \/' / * | ( O )_/ * /~~~~\ `\ \ / * | |~~\ | ) ~------~`\ @@ -39,103 +39,116 @@ * -^-\ \ | ) * `\_______/^\______/ */ -APE.Core = new Class({ - Implements: [APE.Events, Options], +/** + * Core object, can be extended with sessions + *

To connect such an instance to a APE server user the start() method.

+ * + * @name APE.Core + * @public + * @class + * @constructor + * + * @param {object} options Options + * @param {string} options.server APE server URL (default: '') + * @param {integer} options.pollTime Max time for a request (default: 25000) + * @param {string} options.identifier Identifier is used by cookie to differentiate ape instance (default: 'ape') + * @param {integer} options.transport: Transport [0: long polling, 1 : XHRStreaming, 2: JSONP, 3 SSE / JSONP, 4 : SSE / XHR 6: websocket] (default: 0) + * @param {integer} options.frequency Frequency identifier (default: 0) + * @param {integer} options.cycledStackTime Time before send request of cycledStack (default: 350) + * @param {boolean} options.secure Use https instead of http (default:false) + * + * @property {object} user User object + * + * @see APE.start + * @see APE.ready + * @see APE.load + */ +/** +* APE.Transport namespace +* +* @name APE.Transport +* @namespace +* @augments-APE.Events +*/ +APE.Core = new Class({ + Implements: [APE.Events, Options], $originalEvents: {}, - - options:{ + options: { server: '', // APE server URL pollTime: 25000, // Max time for a request identifier: 'ape', // Identifier is used by cookie to differentiate ape instance - transport: 0, // Transport 0: long polling, 1 : XHRStreaming, 2: JSONP, 3 SSE / JSONP, 4 : SSE / XHR + transport: 0, // Transport 0: long polling, 1 : XHRStreaming, 2: JSONP, 3 SSE / JSONP, 4 : SSE / XHR 6: websocket frequency: 0, // Frequency identifier cycledStackTime: 350, //Time before send request of cycledStack secure: false }, - - initialize: function(options){ + initialize: function(options) { window.Ape = this; this.setOptions(options); - this.selectTransport(); this.request = new APE.Request(this); - - this.pipes = new $H; + this.pipes = new $H; this.users = new $H; - this.sessid = null; this.pubid = null; - this.serverUri = (this.options.secure ? 'https' : 'http') + '://' + this.options.frequency + '.' + this.options.server + '/' + this.options.transport + '/?', this.timer = null; this.status = 0; // 0 = APE is not initialized, 1 = connected, -1 = Disconnected by timeout, -2 = Disconnected by request failure this.failCounter = 0; this.pollerObserver = null; this.requestDisabled = false; - this.onRaw('login', this.rawLogin); this.onRaw('err', this.rawErr); this.onRaw('ident', this.rawIdent); this.onRaw('quit', this.rawQuit); - this.onError('003', this.clearSession); this.onError('004', this.clearSession); - //Set core var for APE.Client instance if (options.init) options.init.apply(null, [this]); - //Execute complete function of APE.Client instance if (options.complete) options.complete.apply(null, [this]); this.fireEvent('load', this); - if (this.options.connectOptions) this.start(this.options.connectOptions); }, - selectTransport: function() { - var transports = [APE.Transport.longPolling, APE.Transport.XHRStreaming, APE.Transport.JSONP,null, null, null, APE.Transport.WebSocket]; + var transports = [APE.Transport.longPolling, APE.Transport.XHRStreaming, APE.Transport.JSONP, null, null, null, APE.Transport.WebSocket]; var transport = this.options.transport; var support; - while (support !== true) { - support = transports[transport].browserSupport();//Test if browser support transport - + support = transports[transport].browserSupport();//Test if browser support transport if (support === true) { this.options.transport = transport; this.transport = new transports[transport](this); - } else transport = support;//Browser do not support transport, next loop will test with fallback transport returned by browserSupport(); + } else transport = support;//Browser does not support transport, next loop will test with fallback transport returned by browserSupport(); } }, poller: function() { if (this.pollerActive) this.check(); }, - startPoller: function() { this.pollerActive = true; }, - stopPoller: function() { $clear(this.pollerObserver); this.pollerActive = false; }, - stopRequest: function() { this.cancelRequest(); if (this.transport.streamRequest) this.transport.streamRequest.cancel(); this.requestDisabled = true; }, - parseParam: function(param) { return ($type(param) == 'object') ? Hash.getValues(param) : $splat(param); }, - cancelRequest: function() { this.transport.cancel(); }, - - /*** - * Function called when a request fail or timeout + /** + * Function called when a request fail or timeout. + * + * @fires APE.apeDisconnect */ requestFail: function(failStatus, request) { var reSendData = false; @@ -145,57 +158,48 @@ APE.Core = new Class({ this.cancelRequest(); this.stopPoller(); this.fireEvent('apeDisconnect'); - } - + } if (this.failCounter < 6) this.failCounter++; - //Cancel last request this.cancelRequest(); - - var delay = (this.failCounter*$random(300,1000)); - + var delay = (this.failCounter * $random(300, 1000)); //if (reSendData) { // this.request.send.delay(delay, this.request, queryString); //} else { this.check.delay(delay, this); //} }, - - /*** - * Parse received data from Server + /** + * Parse received data from Server. + * + * @fires APE.apeReconnect */ parseResponse: function(raws, callback) { if (raws) { - if (this.status < 0 ) { + if (this.status < 0) { this.failCounter = 0; this.status = 1; this.startPoller(); this.fireEvent('apeReconnect'); } } - var check = false; var chlCallback;//Callback on challenge - if (raws) { - raws = JSON.parse(raws); - if (!raws){ // Something went wrong, json decode failed + raws = JSON.parse(raws); + if (!raws) { // Something went wrong, json decode failed this.check(); return; } - - for (var i = 0; i < raws.length; i++){ //Read all raw + for (var i = 0; i < raws.length; i++) { //Read all raw var raw = raws[i]; - if (callback && $type(callback) == 'function') { callback.run(raw); } - this.callRaw(raw); - //Last request is finished and it's not an error if (!this.transport.running()) { - if (!raw.data.code || (raw.data.code != '006' && raw.data.code != '007' && raw.data.code != '005' && raw.data.code!= '001' && raw.data.code != '004' && raw.data.code != '003')) { + if (!raw.data.code || (raw.data.code != '006' && raw.data.code != '007' && raw.data.code != '005' && raw.data.code != '001' && raw.data.code != '004' && raw.data.code != '003')) { check = true; } } else { @@ -206,9 +210,12 @@ APE.Core = new Class({ } else if (!this.transport.running()) check = true; //No request running, request didn't respond correct JSON, something went wrong if (check) this.check(); }, - - /*** - * Fire raw event. If received raw is on a non-existing pipe, create new pipe + /** + * Fire raw event. + *

If received raw is on a non-existing pipe, create new pipe.

+ * + * @fires APE.onRaw + * @fires APE.raw_ */ callRaw: function(raw) { var args; @@ -226,9 +233,7 @@ APE.Core = new Class({ } else { args = raw; } - this.fireEvent('onRaw', args); - if (raw.data.chl) {//Execute callback on challenge var chlCallback = this.request.callbackChl.get(raw.data.chl); if (chlCallback) { @@ -236,33 +241,81 @@ APE.Core = new Class({ chlCallback.run(raw); } } - this.fireEvent('raw_' + raw.raw.toLowerCase(), args); }, - - newPipe: function(type, options){ + /** + * Create a new pipe, or find an existing one by its pubid. + *

If options is a pipe with a existing pubid, that pipe will be returned, else a new one will be created for the desired type.

+ * + * @name APE.Core.newPipe + * @function + * @public + * + * @param {string} type Can be 'uni', 'multi', 'proxy' + * @param {object} [options] Option with a pipe.pubid property + * @returns {APE.PipeSingle|APE.PipeMulti|APE.PipeProxy} + */ + newPipe: function(type, options) { if (options && options.pipe.pubid) { - var pipe = this.pipes.get(options.pipe.pubid) + var pipe = this.pipes.get(options.pipe.pubid); if (pipe) return pipe; - } - - if(type == 'uni') return new APE.PipeSingle(this, options); - if(type == 'multi') return new APE.PipeMulti(this, options); - if(type == 'proxy') return new APE.PipeProxy(this, options); + } + if (type == 'uni') return new APE.PipeSingle(this, options); + if (type == 'multi') return new APE.PipeMulti(this, options); + if (type == 'proxy') return new APE.PipeProxy(this, options); }, - getRequest: function(opt) { if (!opt.request) return this.request.send.bind(this.request); else return this.request[opt.request].add.bind(this.request[opt.request]); }, - - /*** + /** * Add a pipe to the core pipes hash */ - addPipe: function(pubid, pipe){ - return this.pipes.set(pubid, pipe); + addPipe: function(pubid, pipe) { + return this.pipes.set(pubid, pipe); }, - + /** + * Get a pipe object + * + * @name APE.getPipe + * @function + * @public + * + * @param {string} pubid The pubid of the pipe + * @returns {object} A. pipe object or null + * + * @example + * //ape var is a reference to APE instance + * //Get a pipe + * var myPipe = ape.getPipe('a852c20b3e5c9889c16fe4ac88abe98f'); + * //Send a message on the pipe + * myPipe.send('Hello world'); + * @example + * //ape var is a reference to APE instance + * //getPipe on Multi Pipe + * //ape var is a reference to APE instance + * ape.join('testChannel'); + * //This sample is just here to show you how to intercept pipe pubid in pipeCreate event + * ape.addEvent('multiPipeCreate', function(type, pipe, options) { + * //Get the pipe object + * var myPubid = ape.getPipe(pipe.getPubid()); + * //Send a message on the pipe + * myPipe.send('Hello world'); + * }); + * @example + * //ape var is a reference to APE instance + * //ape var is a reference to APE instance + * ape.join('testChannel'); + * ape.addEvent('userJoin', function(user, pipe) { + * //For performance purpose, user are not pipe in APE JSF. If you want to have a pipe from an user, use getPipe. + * //Transform all user into a pipe object + * var pipe = ape.getPipe(user.pubid); + * //Send 'Hello world' to the user + * pipe.send('Hello world'); + * }); + * + * @see APE.Pipe + */ getPipe: function(pubid) { var pipe = this.pipes.get(pubid); if (!pipe) { @@ -271,132 +324,344 @@ APE.Core = new Class({ } return pipe; }, - - /*** - * Remove a pipe from the pipe hash and fire event 'pipeDelete' + /** + * Delete a pipe from the Core. + *

Effectively this removes a pipe from the pipe hash and fire event 'pipeDelete'.

+ *

Use this function when you no longer need to use a pipe to free the memory.

+ *

You should only use this function to delete Uni Pipe (user pipe). The deleting of Multi Pipe (channel pipe) are automatically handled by the Core when you left a Multi Pipe.

+ * + * @name APE.Core.delPipe + * @function + * @public + * + * param {string} pubid The pubid of the pipe.* + * returns {APE.Pipe} The deleted pipe + * + * @examples + * //ape var is a reference to APE instance + * //Join testChannel + * ape.join('testChannel'); + * ape.addEvent('userJoin', function(user, pipe) { + * //Get the pipe from user + * var userPipe = ape.getUserPipe(user); + * //Send a message to the user + * userPipe.send('Hello world'); + * //Delete the pipe as we no longer need it + * ape.delPipe(userPipe.getPubid()); + * }); + * + * @fires APE.PipeDelete */ - delPipe: function(pubid){ + delPipe: function(pubid) { var pipe = this.pipes.get(pubid); this.pipes.erase(pubid); - this.fireEvent(pipe.type+'PipeDelete', [pipe]); + this.fireEvent(pipe.type + 'PipeDelete', [pipe]); return pipe; }, - - check: function(){ + /** + * Send a CHECK cmd to the server. + *

Note: this is automatically done and can be configured by the pollTime option parameter. + * + * @name APE.check + * @function + * @public + * + * @example + * var ape = new APE.Core({ + * 'pollTime': 35000, //if you set pollTime to 35sec you need to set it on the server side to 55sec + * 'identifier': 'myApplicationIdentifier', + * }); + * ape.start(); + * // obsessive check + * setInterval(function(){ + * ape.check(); + * }, 1000); + * + * @see APE + */ + check: function() { this.request.send('CHECK'); }, - - start: function(args, options){ - this.connect(args, options); + /** + * Initiate a connection to a APE server. + *

If there is an instance of APE present (created via APE.Core()), it can be connected to the server with the start method. The prefered way is to make the connection with APE.load() method.

+ * + * @name APE.start + * @function + * @public + * + * @param {object} args Connection arguments + * @param {object} options Connection options (e.g. 'channel') + * @returns {void} + * + * @example + * //ape var is a reference to APE instance + * ape.start(); //Send CONNECT command to APE. + * @example + * //ape var is a reference to APE instance + * //If you use APE with libape-chat you need to send your nickname in the CONNECT request + * ape.start('myNickname'); //Send CONNECT command with a nickname to APE and tries to join the optional channel + * + * @see APE.load + * @see APE.restoreStart + * @see APE.restoreEnd + * @see APE.apeDisconnect + * @see APE.apeReconnect + * @see APE.ready + */ + start: function(args, options) { + this.connect(args, options); }, - - connect: function(args, options){ + connect: function(args, options) { if (!options) options = {}; options.sessid = false; - this.request.stack.add('CONNECT', args, options); - if (this.options.channel) { - this.request.stack.add('JOIN', {"channels": this.options.channel}, options); + if (this.options.channel) { + this.request.stack.add('JOIN', {'channels': this.options.channel}, options); } if (!$defined(options.sendStack) && options.sendStack !== false) this.request.stack.send(); }, - + /** + * Join one or many channels. + * + * @name APE.join + * @function + * @public + * + * @param {string|Array} channel The channel(s) to join + * @returns {void} + * + * @example + * //ape var is a reference to APE instance + * ape.join('testchannel'); //Join channel "testchannel" + * //ape var is a reference to APE instance + * ape.join(['channel1', 'channel2']); //Join channels "channel1" and "channel2" + * + * @see APE.left + */ join: function(channel, options) { options = options || {}; options.channels = channel; this.request.send('JOIN', options); }, - - left: function(pubid){ - this.request.send('LEFT', {"channel":this.pipes.get(pubid).name}); + /** + * Leave (unsubscribe) a channel and fire the pipeDelete + * + * @name APE.left + * @function + * @public + * + * @param {string} pubid The pubid of the channel + * @returns {void} + * + * @example + * //ape var is a reference to APE instance + * //Join testchannel + * ape.join('testchannel'); + * //Intercept pipeCreate event (this event is fired when you join a channel) + * ape.addEvent('multiPipeCreate', function(pipe, options) { + * if (pipe.properties.name=='teschannel') { + * //If pipe is testchannel, left it :p + * ape.left(pipe.getPubid()); + * } + * }); + * + * @see APE.join + */ + left: function(pubid) { + this.request.send('LEFT', {'channel': this.pipes.get(pubid).name}); }, - - quit: function(){ + /** + * Exit APE + *

Sends a QUIT CMD and clears the sessions

+ * + * @name APE.quit + * @function + * @public + * + * @returns {void} + * @example + * //ape var is a reference to APE instance + * ape.quit(); + * + * @see APE.clearSession + */ + quit: function() { this.request.send('QUIT'); this.clearSession(); }, - - getPubid: function(){ + getPubid: function() { return this.pubid; }, - - getSessid:function(){ + getSessid: function() { return this.sessid; }, - + /** + * Save a sesion variable on the APE server. + *

Adds a key-value pair to the session on the APE server or replaces a previous value associated with the specified key.

+ * + * @name APE.setSession + * @function + * @public + * + * @param {string} key The key + * @param {string} value The value that will be associated with that key + * @returns {void} + * + * @example + * //ape var is a reference to APE instance + * ape.setSession({'myKey1':'myValue','myKey2':'myValue2'}); + * ape.getSession('myKey', function() { + * console.log(resp.datas.sessions.myKey1); + * }); + * @example + * //ape var is a reference to APE instance + * ape.setSession({'myKey':'["A Json array", "Value 2"]'}); + * ape.getSession('myKey', function(response) { + * //decode the received data, and use eval to dedode the data + * console.log(eval(decodeURIComponent(response.datas.sessions.myKey))); + * }); + * + * @see APE.getSession + * @see APE.clearSession + */ setSession: function(obj, option) { if (this.restoring) return; - this.request.send('SESSION', {'action': 'set', 'values': obj}, option); }, - - getSession: function(key, callback, option){ + /** + * Retrieves a value from the session on the APE server. + * + * @name APE.getSession + * @function + * @public + * + * @param {string|Array} key The key(s) to retrieve + * @param {function} fn Callback function to execute when the sessions is received. One arguments is passed to the callback function with the response of the server. + * @returns {void} + * + * @example + * //ape var is a reference to APE instance + * ape.setSession({'myKey1':'myValue','myKey2':'myValue2'}); + * ape.getSession('myKey', function(resp) { + * console.log(resp.datas.sessions.myKey); + * }); + * @example + * //ape var is a reference to APE instance + * ape.setSession({'myKey':'["A Json array", "Value 2"]'}); + * ape.getSession('myKey', function(resp) { + * //decode the received data, and use eval to dedode the data + * console.log(eval(decodeURIComponent(resp.datas.sessions.myKey))); + * }); + * + * @see APE.setSession + * @see APE.clearSession + */ + getSession: function(key, callback, option) { if (!option) option = {}; var requestOption = {}; - if (callback) { - requestOption.callback = function(resp) { - if (resp.raw == 'SESSIONS') this.apply(null, arguments) - }.bind(callback) + requestOption.callback = function(resp) { + if (resp.raw == 'SESSIONS') this.apply(null, arguments); + }.bind(callback); } requestOption.requestCallback = option.requestCallback || null; - this.getRequest(option)('SESSION', { - 'action':'get', + 'action': 'get', 'values': (($type(key) == 'array') ? key : [key]) }, requestOption); - if (option.request && option.sendStack !== false) { this.request[option.request].send(); } }, - - rawIdent: function(raw){ + rawIdent: function(raw) { this.user = raw.data.user; this.pubid = raw.data.user.pubid; this.user.pipes = new $H; this.users.set(this.pubid, this.user); }, - - rawLogin: function(param){ + /** + * @fires APE.ready + * @fires APE.init + */ + rawLogin: function(param) { this.sessid = param.data.sessid; - this.status = 1; this.startPoller(); this.fireEvent('ready'); this.fireEvent('init'); }, - - rawErr: function(err){ + /** + * @fires APE.error_ + */ + rawErr: function(err) { this.fireEvent('error_' + err.data.code, err); }, - + /** + * + * @see APE.quit + */ rawQuit: function() { this.stopRequest(); }, - - /*** - * Clear the sessions, clean timer, remove cookies, remove events + /** + * Clear the sessions, clean timer, remove cookies, remove events, stop all running requests, reset sissd and pubid + * + * @name APE.clearSession + * @function + * @public + * + * @example + * //ape var is a reference to APE instance + * //Error 004 is fired when you sent a bad sessid (this can happen if the client experience some connection issue longer than 45sec) + * ape.onError('004', function() { + * ape.clearSession(); + * ape.initialize(ape.options); //Reinitialize APE class + * }); + * + * @see APE.quit + * @see APE.getSession + * @see APE.setSession + * @fires APE.clearSession */ - clearSession:function(){ + clearSession: function() { //Clear all APE var this.sessid = null; this.pubid = null; - this.$events = {}; + this.$events = {}; this.request.chl = 1; this.status = 0; this.options.restore = false; - this.fireEvent('clearSession'); this.stopPoller(); this.cancelRequest(); } }); - -var Ape; -APE.init = function(config){ +/** + * Ape variable + * + * @name Ape + * + */ +var Ape; +/** + * Ape initialiser + * + * @name APE.init + * @function + * @public + * + * @param {object} config Configuration object + * + * @see APE.Core + * @see APE.Client + * @see APE.load + * @see APE.start + * + */ +APE.init = function(config) { //Delay of 1ms allow browser to do not show a loading message (function() { new APE.Core(config); }).delay(1); -} +}; diff --git a/Source/Core/Events.js b/Source/Core/Events.js index 702e411..46cf7bc 100755 --- a/Source/Core/Events.js +++ b/Source/Core/Events.js @@ -1,21 +1,589 @@ +/** + * Event objects + * Extends the mootools Request class + * + * @name APE.Events + * @class + * @private + */ + APE.Events = new Class({ - Extends: Events, - + /** + * Add a handler for RAW's + * + * @param {string} rawName name of the raw + * @param {function} function to be called + * @param {object} options + * @returns {APE} + */ onRaw: function(type, fn, internal) { return this.addEvent('raw_' + type.toLowerCase(), fn, internal); }, - - onCmd: function(type, fn, internal) { + /** + * Add a handler for CMD's + * + * @param {string} cmdName name of the cmd + * @param {function} function to be called + * @param {object} options + * @returns {APE} + */ + onCmd: function(type, fn, internal) { return this.addEvent('cmd_' + type.toLowerCase(), fn, internal); }, - - onError: function(type, fn, internal) { + /** + * Add a handler for Errors + * + * @param {string} errorCode Errorcode nr of the error + * @param {function} function to be called + * @param {object} options + * @returns {APE} + */ + onError: function(type, fn, internal) { return this.addEvent('error_' + type, fn, internal); }, - + /** + * Remove an event + * + * @param {string} eventname to be removed + * @param {function} function to be removed + * @returns {APE} + */ removeEvent: function(type, fn) { return Events.prototype.removeEvent.run([type, this.$originalEvents[type][fn]], this); } - }); + + +/** +* Events sent when client apeDisconnect. +*

This events is sent when the client is apeDisconnect from the server in the case of connection timeout or request failure.

+* +* @name APE.apeDisconnect +* @event +* @public +* +* @see APE.load +* @see APE.restoreStart +* @see APE.restoreEnd +* @see APE.apeDisconnect +* @see APE.apeReconnect +* @see APE.ready +*/ + +/** +* Event fired when a new Uni Pipe is created. +*

This event is fired when a new Uni Pipe is created. A new Uni Pipe is created when you call getPipe with the pubid of an user.

+* +* @name APE.uniPipeCreate +* @event +* @public +* +* @param {APE.pipe} pipe The pipe object. +* @param {object} options The options that were passed to the constructor of the pipe. +* +* @example +* //ape var is a reference to APE instance +* ape.join('testChannel'); +* ape.addEvent('userJoin', function(user, pipe) { +* //For performance purpose, user are not pipe in APE JSF. If you want to have a pipe from an user, use getPipe. +* //Transform all user into a pipe object +* var pipe = ape.getPipe(user.pubid); +* }); +* ape.addEvent('uniPipeCreate', function(pipe, options) { +* //Send 'Hello world' to the user +* pipe.send('Hello world'); +* }); +* +* @see APE.multiPipeCreate +* @see APE.proxyPipeCreate +* @see APE.pipeDelete +*/ + +/** +* Event fired when a new Multi Pipe is created. +*

A new Multi Pipe is created when you successfully joined a channel.

+* +* @name APE.multiPipeCreate +* @event +* @public +* +* @param {APE.pipe} pipe The pipe object. +* @param {object} options The options that were passed to the constructor of the pipe. +* +* @example +* //ape var is a reference to APE instance +* ape.join('testChannel'); +* //Intercept multiPipeCreate event +* ape.addEvent('multiPipeCreate', function(pipe, options) { +* console.log('A new Multi pipe is created. Pipe object : ', pipe); +* }); +* +* @see APE.uniPipeCreate +* @see APE.proxyPipeCreate +* @see APE.pipeDelete +* @see APE.PipeMulti.getUser +* @see APE.PipeMulti.left +* @see APE.PipeMulti.users +*/ + +/** +* Event fired when a new Proxy Pipe is created. +*

A new Proxy Pïpe is created in two cases :

+*
    +*
  • If you use TCP socket interface, the Proxy Pipe is created when the socket is initialized.
  • +*
  • When you explicitly create a new Proxy Pipe with new APE.ProxyPipe();

+* +* @name APE.proxyPipeCreate +* @event +* @public +* +* @param {APE.pipe} pipe The pipe object. +* @param {object} options The options that were passed to the constructor of the pipe. +* +* @see APE.uniPipeCreate +* @see APE.proxyPipeCreate +* @see APE.proxyClose +* @see APE.proxyConnect +* @see APE.pipeDelete +*/ + +/** +* Event fired when a user joins a channel +*

This event is fired both on core and pipe the user joined

+* +* @name APE.userJoin +* @event +* @public +* +* @param {object} user The user that joined +* @param {APE.Pipe} pipe The pipe that is involved +* +* @example +* //ape var is a reference to APE instance +* ape.addEvent('userJoin', function (user, pipe) { +* //An user join the channel +* console.log('the user ' + user.properties.name + ' join the channel ' + pipe.properties.name); +* }); +* @example +* //ape var is a reference to APE instance +* //Join two channel +* ape.join(['channel1', 'channel2']); +* ape.addEvent('pipeCreate', function(type, pipe, options) { +* //Attach userJoin event only to pipe "channel1" +* if (type == 'multi' && pipe.properties.name =='channel1') { +* pipe.addEvent('userJoin', function(user, pipe) { +* console.log('New user on channel1'); +* }); +* } +* }); +* +* @see APE.userLeft +* @see APE.MultiPipe.left +* @see APE.Pipe.join +*/ + +/** +* Event fired when a user left a channel +*

This event is fired both on core and pipe the user joined

+* +* @name APE.userLeft +* @event +* @public +* +* @param {object} user The user that joined +* @param {APE.Pipe} pipe The pipe that is involved +* +* @example +* //ape var is a reference to APE instance +* ape.addEvent('userJoin', function (user, pipe) { +* //An user join the channel +* console.log('the user ' + user.properties.name + ' join the channel ' + pipe.properties.name); +* }); +* @example +* //ape var is a reference to APE instance +* //Join two channel +* ape.join(['channel1', 'channel2']); +* ape.addEvent('pipeCreate', function(type, pipe, options) { +* //Attach userJoin event only to pipe "channel1" +* if (type == 'multi' && pipe.properties.name =='channel1') { +* pipe.addEvent('userJoin', function(user, pipe) { +* console.log('New user on channel1'); +* }); +* } +* }); +* +* @see APE.userJoin +* @see APE.join +*/ + +/** +* Event fired when a Multi Pipe is deleted. A pipe is deleted when you leave it. +* +* @name APE.multiPipeDelete +* @event +* @public +* +* @param {APE.Pipe} pipe The pipe object +* +* @example +* //ape var is a reference to APE instance +* //Join testchannel +* ape.join('testchannel'); +* //Intercept pipeCreate event (this event is fired when you join a channel) +* ape.addEvent('multiPipeCreate', function(pipe, options) { +* if (pipe.properties.name=='teschannel') { +* //If pipe is testchannel, left it :p +* pipe.left(pipe.getPubid());//This will fire the event multiPipeDelete +* } +* }); +* ape.addEvent('multiPipeDelete', function(pipe) { +* console.log('pipe ' + pipe.properties.name + ' have been deleted') +* }); +* @see APE.left +* @see APE.PipeDelete +*/ + +/** +* Events sent when client apeDisconnect. +*

This events is sent when the client is apeDisconnect from the server in the case of connection timeout or request fail

+* +* @name APE.apeReconnect +* @event +* @public +* +* @see APE.load +* @see APE.restoreStart +* @see APE.restoreEnd +* @see APE.apeDisconnect +* @see APE.apeReconnect +* @see APE.ready +*/ + +/** +* Event sent when a pipe is deleted +* +* @name APE.PipeDelete +* @event +* @public +* +* @param {integer} pipeType The type of the pipe (can be uni / multi / proxy) +* +* @example +* //ape var is a reference to APE instance +* //Join testchannel +* ape.join('testchannel'); +* //Intercept pipeCreate event (this event is fired when you join a channel) +* ape.addEvent('pipeCreate', function(type, pipe, options) { +* if (pipe.properties.name=='teschannel') { +* //If pipe is testchannel, left it :p +* pipe.left(pipe.getPubid()); +* } +* }); +* //Intercept the pipeDelete event +* ape.addEvent('pipeDelete', function(type, pipe) { +* console.log('you just left the pipe ', pipe); +* }); +* @see APE.left +* @see APE.multiPipeDelete +*/ + +/** +* Event sent when the client is connected to the APE server. +*

This event is sent when the client is connected to the APE server, just after the login (start(); method);

+* +* @name APE.ready +* @event +* @public +* +* @see APE.load +* @see APE.restoreStart +* @see APE.restoreEnd +* @see APE.apeDisconnect +* @see APE.apeReconnect +* @see APE.ready +*/ + +/** + * Events sent when the session is cleared. + *

This events is sent when the clearSession(); method is called.

+ * + * @name APE.clearSession + * @event + * @public + * @requires Source/Core/Session.js + */ + +/** +* Event sent when the session restore begin. +*

This event sent when the session restore has finished.

+*

The session restore is automatically handled by the JSF if you include Session.js in your APE JSF.

+* +* @name APE.restoreEnd +* @event +* @public +* @requires Source/Core/Session.js +*/ + +/** +* Event sent when the session restore begin. +*

This event sent when the session restore begin.

+*

The session restore is automatically handled by the JSF if you include Session.js in your APE JSF.

+* +* @name APE.restoreStart +* @event +* @public +* @requires Source/Core/Session.js +*/ + +/** +* Intercept an server raw (RAW) on a pipe. +*

Execute a function when a raw is received and pass to the function the data received from the server.

+*

If the received raw is related to a pipe (e.g : you receive data from a channel) the second arguments will be a pipe object.

+*

If you send custom raw and you want to intercept those on the pipe you need to add a pipe arguments to data your are sending. (see examples for more information).

+* +* @name APE.Pipe.onRaw +* @event +* @public +* +* @param {string} rawName The name of the raw (e.g. 'login', 'data'); +* @param {function} fn The function to execute upon sending such a command to the server +* @param {object} [fn.args] An object containing all data that was send to the server +* @param {object} [fn.pipe] If the request is made on a pipe (e.g : you sent data to a channel) the second argument will be a pipe object. +* @param {boolean} [internal]Flag to hide the function +* @returns {APE} +* +* +* @example +* //client side code +* /ape var is a reference to APE instance +* //Add an event when a new pipe is created +* ape.addEvent('pipeCreate', function(type, pipe, options) { +* //Test if the pipe is a "multi pipe" with name test1 +* if (type == 'multi' && pipe.name == 'test1') { +* //Add an event when a message is received on the pipe test1 +* pipe.onRaw('data', function(data, pipe) { +* console.log('data received on pipe', pipe.name, ' message : ', data.msg); + * }); +* } +* //When a new pipe is created, send hello world +* pipe.send('Hello world'); +* }); +* //Join channel test1 and test2 +* ape.join(['test1', 'test2']); +* @example +* // server side code +* var chan = APE.getChannelByName('demoChannel'); +* chan.pipe.sendRaw('testRaw', {'foo': 'bar', 'pipe': chan.pipe.toObject()}); +* //Client side code (pipe is a pipe object) +* pipe.onRaw('testRaw', function(data, pipe) { +* console.log('testRaw received on pipe'); +* }); +* +* @see APE.onRaw +* @see APE.onError +* @see APE.onCmd +* @see APE.Pipe.onCmd +*/ + +/** +* Intercept an server command (CMD) on a pipe. +*

Execute a function when a command is sent and pass to the function the arguments sent to APE server.

+* +* @name APE.Pipe.onCmd +* @event +* @public +* +* @param {string} commandName The name of the command (e.g. 'connect', 'send'); +* @param {function} fn The function to execute upon sending such a command to the server +* @param {object} [fn.args] An object containing all data that was send to the server +* @param {object} [fn.pipe] If the request is made on a pipe (e.g : you sent data to a channel) the second argument will be a pipe object. +* @param {boolean} [internal] Flag to hide the function +* @returns {APE} +* +* @example +* //ape var is a reference to APE instance +* /Add an event when a new pipe is created +* ape.addEvent('pipeCreate', function(type, pipe, options) { +* //Test if the pipe is a "multi pipe" with name test1 +* if (type == 'multi' && pipe.name == 'test1') { +* //Add an event when a message is received on the pipe test1 +* pipe.onCmd('send', function(data, pipe) { +* console.log('Sending data on pipe pipe', pipe.name, ' message : ', data.msg); +* }); +* } +* //When a new pipe is created, send hello world +* pipe.send('Hello world'); +* ); +* //Join channel test1 and test2 +* ape.join(['test1', 'test2']); +* +* @see APE.onCmd +* @see APE.onError +* @see APE.onRaw +* @see APE.Pipe.onRaw +*/ + +/** +* Event sent when the client received data from the proxy. +*

This is a global event.

+* +* @name APE.proxyRead +* @event +*/ + +/** +* Event sent when the client is connected to the proxy. +*

This is a global event.

+* +* @name APE.proxyConnect +* @event +*/ +/** +* Event sent when the proxy close the connection. +*

This is a global event.

+* +* @name APE.proxyClose +* @event +*/ + +/** +* Intercept an server raw (RAW). +*

Execute a function when a raw is received and pass to the function the data received from the server.

+*

If the received raw is related to a pipe (e.g : you receive data from a channel) the second arguments will be a pipe object.

+* +* @name APE.onRaw +* @event +* @public +* +* @param {string} rawName The name of the raw (e.g. 'login', 'data'); +* @param {function} fn The function to execute upon sending such a command to the server +* @param {object} [fn.args] An object containing all data that was send to the server +* @param {object} [fn.pipe] If the request is made on a pipe (e.g : you sent data to a channel) the second argument will be a pipe object. +* @param {boolean} [internal]Flag to hide the function +* @returns {APE} +* +* @example +* //ape var is a reference to APE instance +* //Intercept data raw (when you receive data from a channel) +* ape.onRaw('data', function(param, pipe) { +* alert('You received : ' + param.data.msg + ' on pipe ' + pipe.properties.name); +* //Send some data to the channel +* pipe.send('Hey i just received ' + param.data.msg + '!'); +* }); +* +* @see APE.Pipe.onRaw +* @see APE.onCmd +* @see APE.Pipe.onCmd +* @see APE.onError +*/ + +/** +* Intercept an server command (CMD). +*

Execute a function when a command is sent and pass to the function the arguments sent to APE server.

+*

If the sent raw is related to a pipe (e.g : you sent data to a channel) the first arguments will be a pipe object.

+* +* @name APE.onCmd +* @event +* @public +* +* @param {string} commandName The name of the command (e.g. 'connect', 'send'); +* @param {function} fn The function to execute upon sending such a command to the server +* @param {object} [fn.args] An object containing all data that was send to the server +* @param {object} [fn.pipe] If the request is made on a pipe (e.g : you sent data to a channel) the second argument will be a pipe object. +* @param {boolean} [internal] Flag to hide the function +* @returns {APE} +* +* @example +* ///client var is a reference to APE.Client instance +* //start(); method is used to connect to APE. +* client.core.start(); +* //Intercept connect command (connect command is used to initiate connection to APE server) +* client.onCmd('connect', function() { +* console.log('Connect command sent'); +* }); +* @example +* //client var is a reference to APE.Client instance +* //Intercept send command (send command is used to send data to a pipe) +* client.onCmd('send', function(pipe, data) { +* console.log('You sent ' + data.msg + ' to pipe with pubid' + pipe.getPubid()); +* }); +* //Intercept pipeCreate event (when a new pipe is created) +* client.addEvent('pipeCreate', function(type, pipe, options) { +* //send a message on the pipe ("complex" way) +* pipe.request.send('SEND', 'Hello'); +* //Sending a message could also be done more easily with this code +* pipe.send('Hello again!'); +* }); +* +* @see APE.Pipe.onCmd +* @see APE.onRaw +* @see APE.Pipe.onRaw +* @see APE.onError +*/ + +/** +* Intercept an error event. +* +* @name APE.onError +* @event +* @public +* +* @param {integer} errorCode The errorCode that was received (e.g. 004); +* @param {function} fn The function to execute upon receiving such an errorcode +* @param {boolean} [internal] Flag to hide the function +* @returns {APE} +* +* @example +* //Instantiate APE Client. +* var client = new APE.Client(); +* //Error 004 is when the user send a bad sessid +* client.onError('004', function() { +* alert('Bad sessid'); +* }); +*/ + +/** +* Load the APE client. +* +* @name APE.load +* @event +* @public +* +* @param {object} options An object with ape default options +* @param {string} [options.server] APE server URL +* @param {string} [options.domain] Your domain, eg : yourdomain.com +* @param {string} [options.identifier] defaults to : ape identifier is used to differentiate two APE instance when you are using more than one application on your website and using Session.js +* @param {Array} [options.scripts] Javascript files to load +* @param {string} [options.channel] Initial channel to connect to upon loading +* @param {object} [options.connectOptions] An object with connect arguments to send to APE server on connect. If given, as soon as APE Core is loaded, it connect to APE server with arguments given in connectOptions. +* @returns {void} +* +* @example +* //Initialize client +* client = new APE.Client(); +* client.load(); +* client.addEvent('load', function() { +* console.log('Your APE client is loaded'); +* client.core.start({'name': 'apeUser'}); +* }); +* client.addEvent('start', function() { +* console.log('You are connected to APE server. Now you can takeover the world'); +* }; +* @example +* //Initialize client +* client = new APE.Client(); +* //Load APE Core and connect to server +* client.load({ +* 'domain': 'yourdomain.com', +* 'server': 'ape.yourdomain.com', +* 'scripts': ['yourdomain.com/APE_JSF/Build/uncompressed/apeCore.js'], +* 'connectOptions': {'name': 'anotherApeUser'} +* }); +* client.addEvent('start', function() { +* console.log('You are connected to APE server. Now you can takeover the world'); +* }; +* +* @see APE.Core +* @see APE.Core.start +*/ diff --git a/Source/Core/Session.js b/Source/Core/Session.js index c772c5e..259ffd5 100755 --- a/Source/Core/Session.js +++ b/Source/Core/Session.js @@ -1,49 +1,55 @@ +/** + * Core object with sessions. + * + * @private + * @class + * @augments APE.Core + */ APE.Core = new Class({ - Extends: APE.Core, - - initialize: function(options){ + initialize: function(options) { if (this.getInstance(options.identifier).instance) options.restore = true; - this.parent(options); - //Init and save cookies if (options.restore) this.init(); - this.addEvent('uniPipeCreate', this.saveSessionPipe); this.addEvent('uniPipeDelete', this.saveSessionPipe); }, - - saveSessionPipe:function(){ + saveSessionPipe: function() { var uniPipe = []; this.pipes.each(function(pipe) { if (pipe.type == 'uni') { - uniPipe.push({'casttype':pipe.type, 'pubid':pipe.pipe.pubid, 'properties':pipe.properties}); + uniPipe.push({'casttype': pipe.type, 'pubid': pipe.pipe.pubid, 'properties': pipe.properties}); } }); - this.setSession({'uniPipe': JSON.stringify(uniPipe)}); }, - - restoreUniPipe: function(resp){ + /** + * @private + * @fires APE.restoreEnd + */ + restoreUniPipe: function(resp) { var pipes = JSON.parse(decodeURIComponent(resp.data.sessions.uniPipe)); if (pipes) { - for (var i = 0; i < pipes.length; i++){ - this.newPipe('uni',{'pipe': pipes[i]}); + for (var i = 0; i < pipes.length; i++) { + this.newPipe('uni', {'pipe': pipes[i]}); } } this.fireEvent('restoreEnd'); this.restoring = false; }, - - init: function(){ + init: function() { this.initCookie(); this.createCookie();//Create cookie if needed this.saveCookie();//Save cookie }, - - restoreCallback: function(resp){ - if (resp.raw!='ERR' && this.status == 0) { + /** + * @private + * @fires APE.init + * @fires APE.ready + */ + restoreCallback: function(resp) { + if (resp.raw != 'ERR' && this.status == 0) { this.fireEvent('init'); this.fireEvent('ready'); this.status = 1; @@ -51,28 +57,30 @@ APE.Core = new Class({ this.stopPoller(); } }, - - connect: function(args, options){ + /** + * @private + * @fires APE.restoreStart + */ + connect: function(args, options) { var cookie = this.initCookie(); if (!cookie) {//No cookie defined start a new connection - this.addEvent('init',this.init); + this.addEvent('init', this.init); this.parent(args, options); } else {//Cookie or instance exist if (!options) options = {}; if (!options.request) options.request = 'stack'; options.requestCallback = this.restoreCallback.bind(this); - this.restoring = true; this.fireEvent('restoreStart'); this.startPoller(); this.getSession('uniPipe', this.restoreUniPipe.bind(this), options); } }, - - /*** - * Read the cookie APE_Cookie and try to find the application identifier - * @param String identifier, can be used to force the identifier to find ortherwhise identifier defined in the options will be used - * @return Boolean false if application identifier isn't found or an object with the instance and the cookie + /** + * Read the cookie APE_Cookie and try to find the application identifier. + * + * @param {string} identifier, can be used to force the identifier to find ortherwhise identifier defined in the options will be used + * @return {boolean} false if application identifier isn't found or an object with the instance and the cookie */ getInstance: function(identifier) { var tmp = Cookie.read('APE_Cookie', {'domain': document.domain}); @@ -82,35 +90,32 @@ APE.Core = new Class({ //Cookie is corrupted or doest not contains instance if (!tmp || !tmp.instance) return false; //Get the instance of ape in cookie - for(var i = 0; i < tmp.instance.length; i++){ - if(tmp.instance[i] && tmp.instance[i].identifier == identifier){ + for (var i = 0; i < tmp.instance.length; i++) { + if (tmp.instance[i] && tmp.instance[i].identifier == identifier) { return {instance: tmp.instance[i], cookie: tmp}; } } - //No instance found, just return the cookie return {cookie: tmp}; }, - - removeInstance: function(identifier){ + removeInstance: function(identifier) { if (!this.cookie) return; - - for(var i = 0; i < this.cookie.instance.length; i++){ - if(this.cookie.instance[i].identifier == identifier){ - this.cookie.instance.splice(i,1); + for (var i = 0; i < this.cookie.instance.length; i++) { + if (this.cookie.instance[i].identifier == identifier) { + this.cookie.instance.splice(i, 1); return; } } }, - - /*** - * Initialize cookie and some application variable is instance is found - * set this.cookie variable - * @return boolean true if instance is found, else false + /** + * Initialize cookie and some application variable is instance is found. + * + *

set this.cookie variable.

+ * @return {boolean} true if instance is found, else false */ - initCookie: function(){ + initCookie: function() { var tmp = this.getInstance(); - if(tmp && tmp.instance){ //Cookie exist, application instance exist + if (tmp && tmp.instance) { //Cookie exist, application instance exist this.sessid = tmp.instance.sessid; this.pubid = tmp.instance.pubid; tmp.cookie.frequency = tmp.cookie.frequency.toInt() + 1; @@ -126,10 +131,10 @@ APE.Core = new Class({ return false; } }, - - /*** - * Create a cookie instance (add to the instance array of the cookie the current application) - * @param object APE_Cookie + /** + * Create a cookie instance (add to the instance array of the cookie the current application). + * + * @param {object} APE_Cookie */ createInstance: function(cookie) { cookie.instance.push({ @@ -138,12 +143,11 @@ APE.Core = new Class({ sessid: this.getSessid() }); }, - - /*** + /** * Create ape cookie if needed (but do not write it) */ - createCookie: function(){ - if(!this.cookie){ + createCookie: function() { + if (!this.cookie) { //No Cookie or no ape instance in cookie, lets create the cookie var tmp = { frequency: 1, @@ -153,19 +157,63 @@ APE.Core = new Class({ this.cookie = tmp; } }, - - saveCookie: function(){ + saveCookie: function() { //Save cookie on the parent window (this is usefull with JSONP as domain in the iframe is different than the domain in the parent window) Cookie.write('APE_Cookie', JSON.stringify(this.cookie), {'domain': document.domain}); }, - - clearSession: function(){ + clearSession: function() { this.parent(); this.removeInstance(this.options.identifier); this.saveCookie(); }, - - removeCookie: function(){ - Cookie.dispose('APE_Cookie', {domain:this.options.domain}); + removeCookie: function() { + Cookie.dispose('APE_Cookie', {domain: this.options.domain}); } }); +/** + * Cookie object + *

This is provided via mootools-core or by the JavaScript client + * + * @name Cookie + * @namespace + * + */ + +/** + * Write a variable to the cookies. + * + * @name Cookie.write + * @function + * @public + * + * @param {string} name Cookie key + * @param {string} value The value to store + * + * @example + * ///client var is a reference to APE.Client instance + * client.addEvent('start', function() { + * this.core.start({'name': Cookie.write('FOO', 'BAR')}); + * }; + * + * @see Cookie.read + */ + + +/** + * Read a variable from a the cookies. + * + * @name Cookie.read + * @function + * @public + * + * @param {string} name Cookie key + * @returns {string} The decoded cookie value or null + * + * @example + * ///client var is a reference to APE.Client instance + * client.addEvent('start', function() { + * this.core.start({'name': Cookie.read('FOO')}); + * }; + * + * @see Cookie.write + */ diff --git a/Source/Core/Utility.js b/Source/Core/Utility.js index c5c3035..fa76202 100755 --- a/Source/Core/Utility.js +++ b/Source/Core/Utility.js @@ -1,96 +1,297 @@ String.implement({ - - addSlashes: function(){ + /** + * Add slashes to a string + * @name String.addSlashes + * @function + * @public + * @requires Mootools.String + */ + addSlashes: function() { return this.replace(/("|'|\\|\0)/g, '\\$1'); }, - - stripSlashes: function(){ + /** + * Remove slashes from a string + * @name String.stripSlashes + * @function + * @public + * @requires Mootools.String + */ + stripSlashes: function() { return this.replace(/\\("|'|\\|\0)/g, '$1'); } }); - +/** + * Base 64 encode / Base 64 decode + * + * + * @name B64 + * @namespace + * @author Taken from orbited project - http://www.orbited.org + */ var B64 = new Hash({ - $p: '=', $tab: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', - - /*** - * Base 64 encode / Base 64 decode - * Taken from orbited project - http://www.orbited.org + /** + * Encode a string as a base64-encoded string + * + * @name B64.encode + * @function + * @public + * @static + * + * @param {string} string string to encode + * @returns {string} Encoded string + * + * @see B64.decode */ - encode: function(ba){ - // Encode a string as a base64-encoded string + encode: function(ba) { var s = [], l = ba.length; - var rm = l%3; + var rm = l % 3; var x = l - rm; var t; - for (var i = 0; i < x;){ - t = ba.charCodeAt(i++)<<16|ba.charCodeAt(i++)<<8|ba.charCodeAt(i++); - s.push(B64.$tab.charAt((t>>>18)&0x3f)); - s.push(B64.$tab.charAt((t>>>12)&0x3f)); - s.push(B64.$tab.charAt((t>>>6)&0x3f)); - s.push(B64.$tab.charAt(t&0x3f)); + for (var i = 0; i < x;) { + t = ba.charCodeAt(i++) << 16 | ba.charCodeAt(i++) << 8 | ba.charCodeAt(i++); + s.push(B64.$tab.charAt((t >>> 18) & 0x3f)); + s.push(B64.$tab.charAt((t >>> 12) & 0x3f)); + s.push(B64.$tab.charAt((t >>> 6) & 0x3f)); + s.push(B64.$tab.charAt(t & 0x3f)); } // deal with trailers, based on patch from Peter Wood. - switch (rm){ + switch (rm) { case 2: - t = ba.charCodeAt(i++)<<16|ba.charCodeAt(i++)<<8; - s.push(B64.$tab.charAt((t>>>18)&0x3f)); - s.push(B64.$tab.charAt((t>>>12)&0x3f)); - s.push(B64.$tab.charAt((t>>>6)&0x3f)); + t = ba.charCodeAt(i++) << 16 | ba.charCodeAt(i++) << 8; + s.push(B64.$tab.charAt((t >>> 18) & 0x3f)); + s.push(B64.$tab.charAt((t >>> 12) & 0x3f)); + s.push(B64.$tab.charAt((t >>> 6) & 0x3f)); s.push(B64.$p); break; case 1: - t = ba.charCodeAt(i++)<<16; - s.push(B64.$tab.charAt((t>>>18)&0x3f)); - s.push(B64.$tab.charAt((t>>>12)&0x3f)); + t = ba.charCodeAt(i++) << 16; + s.push(B64.$tab.charAt((t >>> 18) & 0x3f)); + s.push(B64.$tab.charAt((t >>> 12) & 0x3f)); s.push(B64.$p); s.push(B64.$p); break; } - return s.join(''); // string }, - - decode: function(str){ + /** + * Decode a string to a base64-encoded string + * + * @name B64.decode + * @function + * @public + * @static + * + * @param {string} string string to decode + * @returns {string} decoded string + * + * @see B64.encode + */ + decode: function(str) { var s = str.split(''), out = []; var l = s.length; var tl = 0; - while(s[--l] == B64.$p){ ++tl; } // strip off trailing padding - for (var i = 0; i < l;){ - var t = B64.$tab.indexOf(s[i++])<<18; - if(i <= l) t|=B64.$tab.indexOf(s[i++])<<12; - if(i <= l) t|=B64.$tab.indexOf(s[i++])<<6; - if(i <= l) t|=B64.$tab.indexOf(s[i++]); - out.push(String.fromCharCode((t>>>16)&0xff)); - out.push(String.fromCharCode((t>>>8)&0xff)); - out.push(String.fromCharCode(t&0xff)); + while (s[--l] == B64.$p) { ++tl; } // strip off trailing padding + for (var i = 0; i < l;) { + var t = B64.$tab.indexOf(s[i++]) << 18; + if (i <= l) t |= B64.$tab.indexOf(s[i++]) << 12; + if (i <= l) t |= B64.$tab.indexOf(s[i++]) << 6; + if (i <= l) t |= B64.$tab.indexOf(s[i++]); + out.push(String.fromCharCode((t >>> 16) & 0xff)); + out.push(String.fromCharCode((t >>> 8) & 0xff)); + out.push(String.fromCharCode(t & 0xff)); } // strip off trailing padding - while(tl--){ out.pop(); } + while (tl--) {out.pop(); } return out.join(''); // string } }); + +randomString = function(length, chars) { + //http://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript + //console.log(randomString(16, 'aA')); //console.log(randomString(32, '#aA')); //console.log(randomString(64, '#A!')); + var mask = ''; + var result = ''; + if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; + if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (chars.indexOf('#') > -1) mask += '0123456789'; + if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; + for (var i = length; i > 0; --i) { + result += mask[Math.round(Math.random() * (mask.length - 1))]; + } + return result; +} + +randomString = function(length, chars) { + //http://stackoverflow.com/questions/10726909/random-alpha-numeric-string-in-javascript + //console.log(randomString(16, 'aA')); //console.log(randomString(32, '#aA')); //console.log(randomString(64, '#A!')); + var mask = ''; + var result = ''; + if (chars.indexOf('a') > -1) mask += 'abcdefghijklmnopqrstuvwxyz'; + if (chars.indexOf('A') > -1) mask += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + if (chars.indexOf('#') > -1) mask += '0123456789'; + if (chars.indexOf('!') > -1) mask += '~`!@#$%^&*()_+-={}[]:";\'<>?,./|\\'; + for (var i = length; i > 0; --i) { + result += mask[Math.round(Math.random() * (mask.length - 1))]; + } + return result; +}; + +//http://www.webtoolkit.info/javascript-sha1.html +/** +* +* Secure Hash Algorithm (SHA1) +* http://www.webtoolkit.info/ +* +**/ +function SHA1(msg) { + function rotate_left(n, s) { + var t4 = (n << s) | (n >>> (32 - s)); + return t4; + }; + function lsb_hex(val) { + var str = ''; + var i; + var vh; + var vl; + for (i = 0; i <= 6; i += 2) { + vh = (val >>> (i * 4 + 4)) & 0x0f; + vl = (val >>> (i * 4)) & 0x0f; + str += vh.toString(16) + vl.toString(16); + } + return str; + }; + function cvt_hex(val) { + var str = ''; + var i; + var v; + for (i = 7; i >= 0; i--) { + v = (val >>> (i * 4)) & 0x0f; + str += v.toString(16); + } + return str; + }; + function Utf8Encode(string) { + string = string.replace(/\r\n/g, '\n'); + var utftext = ''; + for (var n = 0; n < string.length; n++) { + var c = string.charCodeAt(n); + if (c < 128) { + utftext += String.fromCharCode(c); + } else if ((c > 127) && (c < 2048)) { + utftext += String.fromCharCode((c >> 6) | 192); + utftext += String.fromCharCode((c & 63) | 128); + } else { + utftext += String.fromCharCode((c >> 12) | 224); + utftext += String.fromCharCode(((c >> 6) & 63) | 128); + utftext += String.fromCharCode((c & 63) | 128); + } + } + return utftext; + }; + var blockstart; + var i, j; + var W = new Array(80); + var H0 = 0x67452301; + var H1 = 0xEFCDAB89; + var H2 = 0x98BADCFE; + var H3 = 0x10325476; + var H4 = 0xC3D2E1F0; + var A, B, C, D, E; + var temp; + msg = Utf8Encode(msg); + var msg_len = msg.length; + var word_array = new Array(); + for (i = 0; i < msg_len - 3; i += 4) { + j = msg.charCodeAt(i) << 24 | msg.charCodeAt(i + 1) << 16 | + msg.charCodeAt(i + 2) << 8 | msg.charCodeAt(i + 3); + word_array.push(j); + } + switch (msg_len % 4) { + case 0: + i = 0x080000000; + break; + case 1: + i = msg.charCodeAt(msg_len - 1) << 24 | 0x0800000; + break; + case 2: + i = msg.charCodeAt(msg_len - 2) << 24 | msg.charCodeAt(msg_len - 1) << 16 | 0x08000; + break; + case 3: + i = msg.charCodeAt(msg_len - 3) << 24 | msg.charCodeAt(msg_len - 2) << 16 | msg.charCodeAt(msg_len - 1) << 8 | 0x80; + break; + } + word_array.push(i); + while ((word_array.length % 16) != 14) word_array.push(0); + word_array.push(msg_len >>> 29); + word_array.push((msg_len << 3) & 0x0ffffffff); + for (blockstart = 0; blockstart < word_array.length; blockstart += 16) { + for (i = 0; i < 16; i++) W[i] = word_array[blockstart + i]; + for (i = 16; i <= 79; i++) W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1); + A = H0; + B = H1; + C = H2; + D = H3; + E = H4; + for (i = 0; i <= 19; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (~ B & D)) + E + W[i] + 0x5A827999) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + for (i = 20; i <= 39; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + for (i = 40; i <= 59; i++) { + temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i] + 0x8F1BBCDC) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + for (i = 60; i <= 79; i++) { + temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6) & 0x0ffffffff; + E = D; + D = C; + C = rotate_left(B, 30); + B = A; + A = temp; + } + H0 = (H0 + A) & 0x0ffffffff; + H1 = (H1 + B) & 0x0ffffffff; + H2 = (H2 + C) & 0x0ffffffff; + H3 = (H3 + D) & 0x0ffffffff; + H4 = (H4 + E) & 0x0ffffffff; + } + var temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4); + return temp.toLowerCase(); +} + try { //Avoid showing error if window.parent.setInterval() is not working (ie : permission denied) window.parent.setInterval(); - //Override setInterval to be done outside the frame (there is some issue inside the frame with FF3 and WebKit) - if (!Browser.Engine.trident && !Browser.Engine.presto && !(Browser.Engine.gecko && Browser.Engine.version<=18)) { - setInterval = function(fn,time) { + if (!Browser.Engine.trident && ! Browser.Engine.presto && ! (Browser.Engine.gecko && Browser.Engine.version <= 18)) { + setInterval = function(fn, time) { return window.parent.setInterval(fn, time); }; - - setTimeout = function(fn,time) { + setTimeout = function(fn, time) { return window.parent.setTimeout(fn, time); }; - clearInterval = function(id) { return window.parent.clearInterval(id); }; - clearTimeout = function(id) { return window.parent.clearTimeout(id); }; } -} catch (e) {}; +} catch (e) {} diff --git a/Source/Pipe/Pipe.js b/Source/Pipe/Pipe.js index 7389b74..de7eba0 100755 --- a/Source/Pipe/Pipe.js +++ b/Source/Pipe/Pipe.js @@ -1,18 +1,96 @@ -APE.Pipe = new Class({ +/** + * Variable containing pipe options + *

This variable contains the options of a pipe (name, properties, casttype, pubid).

+ *

The options var passed to pipeCreate event contains the same data as the pipe var.

+ * + * @class Variable containing pipe options + * + * @name APE.Pipe + * @public + * @namespace + * @augments-APE.Events + * + * @see APE.PipeSingle + * @see APE.PipeMulti + * @see APE.PipeProxy + */ - Implements: APE.Events, +/** + * @name APE.Pipe.request + * @property {APE.Request} request request + * @see APE.request + */ + +APE.Pipe = new Class({ + /** + * Intercept an event. + * + * @name APE.Pipe.addEvent + * @public + * @function + * + * @param {string} eventName Event if it is send an a pipe + * @param {function} fn The function that will be executed upon this named event + * @param {boolean} [internal] Flag to hide the function + * @returns {APE} + * + * @example + * //ape var is a reference to APE instance + * //myPipe var is a reference to a pipe object + * //In this example this core will never be called as the event is only fired on the pipe + * ape.addEvent('testEvent', function() { + * console.log('Event received on core'); + * }); + * myPipe.addEvent('testEvent', function() { + * console.log('Event received on pipe'); + * }); + * //Fire the event only on the pipe + * myPipe.fireEvent('testEvent'); + * + * @see APE.addEvent + * @see APE.fireEvent + * @see APE.Pipe.fireEvent + */ - initialize: function(ape, options){ + /** + * Fire a event manually on a pipe. + *

Executes all events of the specified type present on in only this pipe.

+ * + * @name APE.Pipe.fireEvent + * @function + * @public + * + * @param {string} eventName Event to launch. + * @param {Array|object} args The arguments for the appropiate event callbacks + * @param {integer} [delay] Delay in ms before the event should be fired + * @returns {APE} + * + * @example + * //ape var is a reference to APE instance + * //myPipe var is a reference to a pipe object + * //In this example this core will never be called as the event is only fired on the pipe + * ape.addEvent('testEvent', function() { + * console.log('Event received on core'); + * }); + * myPipe.addEvent('testEvent', function() { + * console.log('Event received on pipe'); + * }); + * //Fire the event only on the pipe + * myPipe.fireEvent('testEvent'); + * + * @see APE.fireEvent + * @see APE.addEvent + * @see APE.Pipe.addEvent + * @see APE.Pipe.fireGlobalEvent + */ + Implements: APE.Events, + initialize: function(ape, options) { this.pipe = options.pipe; this.properties = options.pipe.properties; - this.ape = ape; - this.initRequestMethod(); - this.ape.addPipe(this.pipe.pubid, this); }, - initRequestMethod: function() { this.request = {}; this.request = { @@ -28,16 +106,15 @@ APE.Pipe = new Class({ send: this.ape.request.send, setTime: this.ape.request.cycledStack.setTime.bind(this.ape.request.cycledStack) }, - stack : { + stack: { add: function() { var args = this.parsePipeCmd.apply(this, arguments); this.ape.request.stack.add.apply(this.ape.request.stack, args); }.bind(this), send: this.ape.request.stack.send.bind(this.ape.request.stack) } - } + }; }, - parsePipeCmd: function() { //convert arguments to a real array to avoid a bug with firefox see bug #292215 https://bugzilla.mozilla.org/show_bug.cgi?id=292215 var args = Array.prototype.slice.call(arguments); @@ -52,20 +129,89 @@ APE.Pipe = new Class({ } return args; }, - - send: function(data){ + /** + * Shortcut to Send a message to the APE server on this pipe + * + * @name APE.Pipe.send + * @function + * @public + * + * @param {object} [params] The message content + * + * @example + * //ape var is a reference to APE instance + * //Join channel testChannel + * ape.join('testChannel'); + * ape.addEvent('pipeCreate', function(type, pipe, options) { + * //Send "Hello world" on the pipe + * pipe.send('Hello world'); + * //pipe.send is a shorcut for : + * pipe.request.send({'msg':'Hellow world'}); + * }); + * + * @see APE.Request.send + * @see APE.Request.Stack.send + * @see APE.Request.CycledStack.send + */ + send: function(data) { this.request.send('SEND', {'msg': data}); }, - - getPubid: function(){ + /** + * Return the pubid of the pipe + * + * @name APE.Pipe.getPubid + * @function + * @public + * + * @returns {string} The pubid of this pipe + * + * @example + * //ape var is a reference to APE instance + - ape.addEvent('pipeCreate', function(type, pipe, options) { + * var pubid = pipe.getPubid(); + * //pipes is hash indexed by pubid containings all pipes + * var thePipe = ape.pipes.get(pubid); + * //thePipe is the same var than the var pipe passed in arguments + * }); + */ + getPubid: function() { return this.pipe.pubid; }, - + /** + * Executes all events of the specified type present on in this pipe and in the core + *

When an event is fired on the core, it's also fired on the APE Client (as client and core share the same events).

+ * + * @name APE.Pipe.fireGlobalEvent + * @function + * @public + * + * @param {string} eventName Event to launch. + * @param {Array|object} args The arguments for the appropiate event callbacks + * @param {integer} [delay] Delay in ms before the event should be fired + * @returns {APE} + * + * @example + * //ape var is a reference to APE instance + * //myPipe var is a reference to a pipe object + * //client var is a reference to a client instance + * //Intercept event on pipe + * myPipe.addEvent('testEvent', function() { + * console.log('event received on pipe'); + * }); + * //Intercept event on core + * ape.addEvent('testEvent', function() { + * console.log('event received on core'); + * }); + * //Fire the event "testEvent" on the Pipe & Core + * myPipe.fireGlobalEvent('testEvent', ['args1', 'args2']); + * + * @see APE.fireEvent + * @see APE.Pipe.addEvent + * @see APE.Pipe.fireGlobalEvent + */ fireGlobalEvent: function(type, fn, internal) { this.fireEvent(type, fn, internal); this.ape.fireEvent(type, fn, internal); }, - fireInTheHall: this.fireGlobalEvent - }); diff --git a/Source/Pipe/PipeMulti.js b/Source/Pipe/PipeMulti.js index 9ec8247..3856de4 100644 --- a/Source/Pipe/PipeMulti.js +++ b/Source/Pipe/PipeMulti.js @@ -1,45 +1,97 @@ -APE.PipeMulti = new Class({ +/** + * PipeMulti object + * + * @name APE.PipeMulti + * @class + * @public + * @augments APE.Pipe + * + * @see APE.Pipe + * @see APE.PipeMulti + * @see APE.PipeProxy + * + * @fires APE.multiPipeCreate + */ +/** + * A MooTools hash of user on the pipe. + *

If you use this variable in the multiPipeCreate events it will be empty.

+ *

If you want to access to all user on pipe in the multiPipeCreate event use the options.users (object) (second parameter passed to multiPipeCreate event)

+ * + * @name APE.PipeMulti.users + * @public + * @property {Hash} A MooTools hash of user on the pipe. + * + * @see APE.multiPipeCreate + */ +APE.PipeMulti = new Class({ Extends: APE.Pipe, - initialize: function(core, options) { this.parent(core, options); - this.type = 'multi'; this.name = options.pipe.properties.name; - //Test if pipe have users before sending event //because this.users need to be defined if (options.users) { this.users = new $H; var users = options.users; } - this.ape.fireEvent('multiPipeCreate', [this, options]); - if (options.users) { var l = users.length; - for (var i=0; i < l; i++) { + for (var i = 0; i < l; i++) { this.addUser(users[i].pubid, users[i]); } } this.onRaw('left', this.rawLeft); this.onRaw('join', this.rawJoin); }, - rawJoin: function(raw, pipe) { this.addUser(raw.data.user.pubid, raw.data.user); }, - rawLeft: function(raw, pipe) { if (pipe.name.charAt(0) != '*') this.delUser(raw.data.user.pubid); if (raw.data.user.pubid == this.ape.user.pubid) this.ape.delPipe(pipe.pipe.pubid); }, - + /** + * Force this user to leave this pipe + * + * @name APE.PipeMulti.left + * @public + * @function + * + * @returns {void} + * + * @example + * //ape var is a reference to APE instance + * //Join testchannel + * ape.join('testchannel'); + * //Intercept pipeCreate event (this event is fired when you join a channel) + * ape.addEvent('multiPipeCreate', function(pipe, options) { + * if (pipe.properties.name=='teschannel') { + * //If pipe is testchannel, left it :p + * pipe.left(); + * } + * }); + */ left: function() { this.ape.left(this.pipe.pubid); }, - + /** + * Add another user to this pipe + * + * @name APE.PipeMulti.addUser + * @public + * @function + * + * @param {string} pubid A user's pubid + * @param {object} updatedUser An updated user object + * @returns {user} user object + * + * @see APE.PipeMulti.getUserPipe + * @see APE.PipeMulti.getUser + * @see APE.PipeMulti.delUser + */ addUser: function(pubid, updatedUser) { var user; if (!this.ape.users.has(pubid)) { @@ -50,29 +102,63 @@ APE.PipeMulti = new Class({ user = this.ape.users.get(pubid); } user.pipes.set(this.pipe.pubid, this); - var u = {'pipes':user.pipes ,'casttype': user.casttype, 'pubid': user.pubid, 'properties': updatedUser.properties}; + var u = {'pipes': user.pipes, 'casttype': user.casttype, 'pubid': user.pubid, 'properties': updatedUser.properties}; this.users.set(pubid, u); this.fireGlobalEvent('userJoin', [u, this]); return u; }, - + /** + * Add another user to this pipe + * + * @name APE.PipeMulti.delUser + * @public + * @function + * + * @param {string} pubid A user's pubid + * @returns {user} + * + * @see APE.PipeMulti.getUserPipe + * @see APE.PipeMulti.addUser + */ delUser: function(pubid) { var u = this.users.get(pubid); this.users.erase(pubid); - u.pipes.erase(this.pipe.pubid) + u.pipes.erase(this.pipe.pubid); if (u.pipes.getLength() == 0) { this.ape.users.erase(u.pubid); } this.fireGlobalEvent('userLeft', [u, this]); return u; }, - + /** + * Return an user object identified by his pubid. + * + * @name APE.PipeMulti.getUser + * @public + * @function + * + * @param {string} pubid A user's pubid + * @returns {user} An user object (with pubid and properties) or null + * + * @see APE.PipeMulti.getUserPipe + */ getUser: function(pubid) { return this.users.get(pubid); }, - + /** + * Return an user object identified by his pubid. + * + * @name APE.PipeMulti.getUserPipe + * @public + * @function + * + * @param {string|user} user A user's pubid or a user object + * @returns {pipe} An pipe to the other user + * + * @see APE.PipeMulti.getUser + */ getUserPipe: function(user) { if (typeof user == 'string') user = this.users.get(users.pubid); - return this.ape.newPipe('uni', {'pipe':user}); + return this.ape.newPipe('uni', {'pipe': user}); } }); diff --git a/Source/Pipe/PipeProxy.js b/Source/Pipe/PipeProxy.js index 871cfd0..3d253e7 100755 --- a/Source/Pipe/PipeProxy.js +++ b/Source/Pipe/PipeProxy.js @@ -1,52 +1,116 @@ +/** + * ProxyPipe object + * + * @name APE.PipeProxy + * @class + * @public + * @augments APE.Pipe + * @see APE.PipeSingle + * @see APE.PipeMulti + * @see APE.Pipe + * @fires APE.proxyPipeCreate + */ APE.PipeProxy = new Class({ - Extends: APE.Pipe, - - initialize: function(core, options){ + initialize: function(core, options) { this.core = core || window.Ape; this.ape = this.core; - this.initRequestMethod(); this.type = 'proxy'; - if (options) { this.init(options); } }, - - init: function(options){ + init: function(options) { this.pipe = options.pipe; - this.core.addPipe(this.getPubid(), this); - this.onRaw('proxy_event', this.rawProxyEvent); this.ape.fireEvent('proxyPipeCreate', [this, options]); }, - reset: function() { //close connection }, - close: function() { //close connection }, - - open: function(hostname, port){ + /** + * Open a new proxy connection to a IP:Port + *

This is actually the constructor

+ * + * @name APE.PipeProxy.open + * @function + * @public + * + * @param {string} host Hostname or ip-address + * @param {integer} port Port + * @returns {APE.PipeProxy} a APE.PipeProxy object + * + * @example + * //ape var is a reference to APE instance + * //Connect to the APE server + * ape.start(); + * //Connect to proxy when the client is connected to the server + * ape.addEvent('init', function() { + * //Connect to freenode irc + * var proxy = new APE.PipeProxy(); + * proxy.open('irc.freenode.net', 6667); + * }); + * //Intercept proxy event + * ape.addEvent('proxyConnect', function() { + * console.log('you are now connected to proxy'); + * }); + * ape.addEvent('proxyRead', function(data) { + * console.log('Receiving data', data); + * }); + */ + open: function(hostname, port) { if (this.core.status == 0) this.core.start(null, false); //Adding a callback to request response to create a new pipe if this.pipe haven't been init - this.request.stack.add('PROXY_CONNECT', {'host':hostname, 'port':port}, this.pipe ? {} : {'callback':this.callback.bind(this)}); + this.request.stack.add('PROXY_CONNECT', {'host': hostname, 'port': port}, this.pipe ? {} : {'callback': this.callback.bind(this)}); this.request.stack.send(); }, - - send: function(data){ - this.request.send('SEND', {'msg':B64.encode(data)}); + /** + * Send data to a proxy + * + * @name APE.PipeProxy.send + * @function + * @public + * + * @param {string} data The data string to send. The data wil be b64 encoded and send with a SEND CMD + * @returns {void} + * + * + * @example + * //ape var is a reference to APE instance + * ape.start(); + * var proxy; + * ape.addEvent('init', function() { + * //Connect to the APE server + * proxy = new APE.PipeProxy(); + * //Connect to ape-project website + * proxy.connect('www.ape-project.org', 80); + * }); + * //Send HTTP headers + * proxy.write("GET / HTTP1.1\r\nhost: www.ape-project.org\n\n"); + * ape.addEvent('proxyConnect', function() { + * console.log('you are now connected to proxy'); + * }); + * //Receive the page content + * ape.addEvent('proxyRead', function(data) { + * console.log('Receiving data', data); + * }); + * ape.addEvent('proxyClose', function() { + * console.log('Proxy closed the connection'); + * }); + */ + send: function(data) { + this.request.send('SEND', {'msg': B64.encode(data)}); }, - - rawProxyEvent: function(resp){ + rawProxyEvent: function(resp) { switch (resp.data.event) { case 'read': var data = B64.decode(resp.data.data); - this.fireGlobalEvent('proxyRead', data) + this.fireGlobalEvent('proxyRead', data); if (this.onread) this.onread(data); break; case 'connect': @@ -59,18 +123,14 @@ APE.PipeProxy = new Class({ break; } }, - - callback: function(raw){ + callback: function(raw) { this.init(raw.data); this.rawProxyEvent(raw); } }); - APE.Core = new Class({ - Extends: APE.Core, - - /*** + /** * This allow ape to be compatible with TCPSocket */ TCPSocket: APE.PipeProxy diff --git a/Source/Pipe/PipeSingle.js b/Source/Pipe/PipeSingle.js index e23d92b..62b9591 100755 --- a/Source/Pipe/PipeSingle.js +++ b/Source/Pipe/PipeSingle.js @@ -1,10 +1,18 @@ +/** + * @name APE.PipeSingle + * @public + * @augments APE.Pipe + * @class + * @see APE.Pipe + * @see APE.PipeMulti + * @see APE.PipeProxy + * @fires APE.uniPipeCreate + */ APE.PipeSingle = new Class({ - Extends: APE.Pipe, - - initialize: function(core, options){ + initialize: function(core, options) { this.parent(core, options); this.type = 'uni'; - this.ape.fireEvent('uniPipeCreate',[this, options]); + this.ape.fireEvent('uniPipeCreate', [this, options]); } }); diff --git a/Source/Request/Request.CycledStack.js b/Source/Request/Request.CycledStack.js index 74357d4..a19cd87 100755 --- a/Source/Request/Request.CycledStack.js +++ b/Source/Request/Request.CycledStack.js @@ -1,17 +1,75 @@ +/** +* Request.cycledStack object +* APE JSF allows you to create "cycled stacks" of request. +* These methods are very useful for example if your application must send different commands to APE server from several functions (or not) and you want to send them all at once. +* With request.cycledStack you don't have to care when the command are sent. They are automatically sent (by default each 350ms). This is verry useful for peridical stuff. Or to reduce/optimze bandwith. +* +* @name APE.Request.cycledStack +* @public +* @namespace +* @function +* @see APE.Request.stack +* @see APE.request.stack +* @see pipe.stack +*/ APE.Request.CycledStack = new Class({ initialize: function(ape) { this.ape = ape; - this.stack = []; this.reajustTime = false; - this.timer = this.send.periodical(this.ape.options.cycledStackTime, this); }, - + /** + * Add a command to cycled request stack + * + * @name APE.Request.cycledStack.add + * @function + * @public + * + * @param {Array|string} cmd If the argument is a string, it should be a CMD (e.g. 'CONNECT'). If the argument is an array: the array should contain objects with cmd, params and sessid) + * @param {object} [params] The parameters that must be send with the command + * @param {object} [options] Object with request options + * @param {boolean} [options.sessid=true] Add the sessid property to the request + * @returns {void} The request object + * + * @example + * //ape var is a reference to APE instance + * //Note : this example use the server module mouseMotion available on APE Store + * // capture mousemove event + * document.onmousemove=getMouseCoordinates; + * //Add to the cycled Stack the position of the mouse, and the time + * function getMouseCoordinates(event) { + * ev = event || window.event; + * ape.request.cycledStack.add('mouseMotion', {"x":ev.x, "y":ev.y}); + * captureMouse = false; + * } + * //Intercept mouseMotion raw, and display where the mouse was + * ape.onRaw('mouseMotion', function(data) { + * console.log('Mouse position, x : ' + data.x + ' / y : ' + data .y); + * }); + * + * @see APE.Request.send + * @see APE.Request.stack.send + * @see APE.Request.cycledStack.send + */ add: function(cmd, params, sessid) { - this.stack.push({'cmd':cmd, 'params':params, 'sessid':sessid}); + this.stack.push({'cmd': cmd, 'params': params, 'sessid': sessid}); }, - + /** + * Change cycled stack time. By default request added through request.cycledStack.add are sent each 350ms. This function allow you to ajust this time. + * + * @name APE.Request.cycledStack.setTime + * @function + * @public + * + * @param {integer} time New cycle interval in ms + * @param {boolean} [now=false] Send immediately + * + * @example + * //ape var is a reference to APE instance + * //Set cycle stack time to 100 ms + * ape.request.cycledStack.setTime(100); + */ setTime: function(time, now) { if (now) { this.send(); @@ -21,7 +79,25 @@ APE.Request.CycledStack = new Class({ } else this.reajustTime = time; }, - + /** + * Force the sending of the request stack + * + * @name APE.Request.cycledStack.send + * @function + * @public + * + * @example + * //ape var is a reference to APE instance + * //Add two request to the stack + * ape.request.cycledStack.add('testCommand', {"param1":"value"}); + * ape.request.cycledSstack.add('anotherCommand', {"param1":"value"}); + * //Imediately send the stack to APE server + * ape.request.cycledStack.send(); + * + * @see APE.Request.send + * @see APE.Request.cycledStack.add + * @see APE.Request.stack.add + */ send: function() { if (this.stack.length > 0) { this.ape.request.send(this.stack); diff --git a/Source/Request/Request.Stack.js b/Source/Request/Request.Stack.js index 72c2e64..fa18f16 100755 --- a/Source/Request/Request.Stack.js +++ b/Source/Request/Request.Stack.js @@ -1,11 +1,68 @@ +/** +* Request.stack object. +*

These methods are very useful for example if your application must send different commands to APE server from several functions and you want to send them all at once.

+* +* @name APE.Request.stack +* @public +* @function +* @namespace +* @see APE.Request.cycledStack +*/ APE.Request.Stack = new Class({ initialize: function(ape) { this.ape = ape; - this.stack =[]; + this.stack = []; }, + /** + * Add a command to request stack. + *

Send the request stack.

+ * + * @name APE.Request.stack.add + * @function + * @public + * + * @param {Array|string} cmd If the argument is a string, it should be a CMD (e.g. 'CONNECT'). If the argument is an array: the array should contain objects with cmd, params and sessid) + * @param {object} [params] The parameters that must be send with the command + * @param {object} [options] Object with request options + * @param {boolean} [options.sessid=true] Add the sessid property to the request + * @returns {void} The request object + * + * @example + * //ape var is a reference to APE instance + * //Add two request to the stack + * ape.request.stack.add('testCommand', {"param1":"value"}); + * ape.request.stack.add('anotherCommand', {"param1":"value"}); + * //Send the stack to APE server + * ape.request.stack.send(); + * + * @see APE.Request.send + * @see APE.Request.cycledStack.send + * @see APE.Request.stack.add + */ add: function(cmd, params, options) { - this.stack.push({'cmd':cmd, 'params':params, 'options': options}); + this.stack.push({'cmd': cmd, 'params': params, 'options': options}); }, + /** + * Send the request stack. + *

This method send the command in the request stack added trough request.stack.add to the APE server.

+ * + * @name APE.Request.stack.send + * @function + * @public + * + * @returns {object} The request object + * + * @example + * //ape var is a reference to APE instance + * //Add two request to the stack + * ape.request.stack.add('testCommand', {"param1":"value"}); + * ape.request.stack.add('anotherCommand', {"param1":"value"}); + * //Send the stack to APE server + * ape.request.stack.send(); + * + * @see APE.Request.cycledStack.add + * @see APE.Request.stack.send + */ send: function() { this.ape.request.send(this.stack); this.stack = []; diff --git a/Source/Request/Request.js b/Source/Request/Request.js index 4be0ccc..bf14845 100755 --- a/Source/Request/Request.js +++ b/Source/Request/Request.js @@ -1,3 +1,10 @@ +/** +* Request object +* +* @name APE.Request +* @public +* @class +*/ APE.Request = new Class({ initialize: function(ape) { this.ape = ape; @@ -5,9 +12,8 @@ APE.Request = new Class({ this.cycledStack = new APE.Request.CycledStack(ape); this.chl = 1; this.callbackChl = new $H; - //Fix presto bug (see send method) - if (Browser.Engine.presto){ + if (Browser.Engine.presto) { this.requestVar = { updated: false, args: [] @@ -15,7 +21,42 @@ APE.Request = new Class({ this.requestObserver.periodical(10, this); } }, - + /** + * Send a command to the APE server + * + * @name APE.Request.send + * @function + * @public + * + * @param {Array|string} cmd If the argument is a string, it should be a CMD (e.g. 'CONNECT'). If the argument is an array: the array should contain objects with cmd, params and sessid) + * @param {object} [params] The parameters that must be send with the command + * @param {object} [options] Object with request options + * @param {boolean} [options.sessid=true] Add the sessid property to the request + * @returns {object} The request object or undefined in Opera browser + * + * @example + * ///ape var is a reference to APE instance + * //Send a connect command to server + * ape.request.send('JOIN', {"channels":"testChannel"}); + * //Note : you can join a channel with the method join + * ape.join('testChannel'); + * //Note : testCommand is not a real APE command, it's just used here as an example + * ape.request.send('testCommand', {"param1":"value","param2":"value"}); + * @example + * //ape var is a reference to APE instance + * //This example sends a "JOIN" command, + * // and "anotherCommand" with 2 arguments without adding sessid + * ape.request.send( + * [ + * { "cmd":"JOIN", "params": { "channels": "test1"}}, + * { "cmd": "anotherCommand", "params": { "param1": "value", "param2": "value" }, "sessid": false} + * ] + * ); + * + * @see APE.Pipe.send + * @see APE.Request.stack.send + * @see APE.Request.cycledStack.send + */ send: function(cmd, params, options, noWatch) { if (this.ape.requestDisabled) return; //Opera dirty fix @@ -24,21 +65,21 @@ APE.Request = new Class({ this.requestVar.args.push([cmd, params, options]); return; } - var opt = {}; if (!options) options = {}; - opt.event = options.event || true; opt.requestCallback = options.requestCallback || null; opt.callback = options.callback; var ret = this.ape.transport.send(this.parseCmd(cmd, params, opt), opt); - $clear(this.ape.pollerObserver); this.ape.pollerObserver = this.ape.poller.delay(this.ape.options.pollTime, this.ape); - return ret; }, - + /** + * @private + * @fires APE.cmd_ + * @fires APE.onCmd + */ parseCmd: function(cmd, params, options) { var queryString = ''; var a = []; @@ -47,21 +88,15 @@ APE.Request = new Class({ var tmp, evParams; for (var i = 0; i < cmd.length; i++) { tmp = cmd[i]; - o = {}; o.cmd = tmp.cmd; o.chl = this.chl++; if (!tmp.options) tmp.options = {}; - tmp.params ? o.params = tmp.params : null; evParams = $extend({}, o.params); - - if (!$defined(tmp.options.sessid) || tmp.options.sessid !== false) o.sessid = this.ape.sessid; a.push(o); - var ev = 'cmd_' + tmp.cmd.toLowerCase(); - if (tmp.options.callback) this.callbackChl.set(o.chl, tmp.options.callback); if (tmp.options.requestCallback) options.requestCallback = tmp.options.requestCallback; if (options.event) { @@ -70,42 +105,31 @@ APE.Request = new Class({ var pipe = this.ape.getPipe(o.params.pipe); if (pipe) evParams = [evParams, pipe]; } - this.ape.fireEvent('onCmd', [tmp.cmd, evParams]); - if (pipe) pipe.fireEvent(ev, evParams); - this.ape.fireEvent(ev, evParams); } } } else { o.cmd = cmd; o.chl = this.chl++; - params ? o.params = params : null; var evParams = $extend({}, params); - - if (!$defined(options.sessid) || options.sessid !== false) o.sessid = this.ape.sessid; a.push(o); - var ev = 'cmd_' + cmd.toLowerCase(); if (options.callback) this.callbackChl.set(o.chl, options.callback); - if (options.event) { //Request is on a pipe, fire the event on the pipe - if (params && params.pipe) { + if (params && params.pipe) { var pipe = this.ape.getPipe(params.pipe); if (pipe) evParams = [evParams, pipe]; } this.ape.fireEvent('onCmd', [cmd, evParams]); - if (pipe) pipe.fireEvent(ev, evParams); - this.ape.fireEvent(ev, evParams); } } - var transport = this.ape.options.transport; return JSON.stringify(a, function(key, value) { if (typeof(value) == 'string') { @@ -116,16 +140,14 @@ APE.Request = new Class({ } else return value; }); }, - - /**** + /** * This method is only used by opera. - * Opera have a bug, when request are sent trought user action (ex : a click), opera throw a security violation when trying to make a XHR. - * The only way is to set a class var and watch when this var change + *

Opera has a bug, when request are sent through user action (ex : a click), opera throw a security violation when trying to make a XHR. The only way is to set a class var and watch when this var change

*/ - requestObserver: function(){ + requestObserver: function() { if (this.requestVar.updated) { var args = this.requestVar.args.shift(); - this.requestVar.updated = (this.requestVar.args.length>0) ? true : false; + this.requestVar.updated = (this.requestVar.args.length > 0) ? true : false; args[3] = true; //Set noWatch argument to true this.send.run(args, this); } diff --git a/Source/Transport/Transport.JSONP.js b/Source/Transport/Transport.JSONP.js index ec96a87..80f4484 100755 --- a/Source/Transport/Transport.JSONP.js +++ b/Source/Transport/Transport.JSONP.js @@ -1,41 +1,42 @@ +/** + * SSE Transport object + * + * @name APE.Transport.JSONP + * @augments APE.Transport.SSE.prototype + * @class + * @private + */ APE.Transport.JSONP = new Class({ - Implements: APE.Transport.SSE, - initialize: function(ape) { this.ape = ape; this.requestFailObserver = []; this.requests = []; - //If browser support servent sent event, switch to SSE / JSONP transport (not yet supported by APE server) //if (this.SSESupport) this.ape.options.transport = 3; - window.parent.onkeyup = function(ev) { if (ev.keyCode == 27) { this.cancel();//Escape key if (this.ape.status > 0) { - //if (!this.SSESupport) + //if (!this.SSESupport) //this.ape.request('CLOSE'); this.ape.check(); } } }.bind(this); }, - send: function(queryString, options) { //Opera has some trouble with JSONP, so opera use mix of SSE & JSONP /*if (this.SSESupport && !this.eventSource) { //SSE not yet supported by APE server this.initSSE(queryString, options, this.readSSE.bind(this)); } else { */ this.callback = options.requestCallback; - var request = document.createElement('script'); request.src = this.ape.serverUri + queryString; document.head.appendChild(request); this.requests.push(request); //Detect timeout this.requestFailObserver.push(this.ape.requestFail.delay(this.ape.options.pollTime + 10000, this.ape, [-1, request])); - if (Browser.Engine.gecko) { //Firefox hack to avoid status bar always show a loading message //Ok this hack is little bit weird but it works! @@ -47,36 +48,31 @@ APE.Transport.JSONP = new Class({ } //} }, - clearRequest: function(request) { request.parentNode.removeChild(request); //Avoid memory leaks if (request.clearAttributes) { request.clearAttributes(); - } else { + } else { for (var prop in request) delete request[prop]; } $clear(this.requestFailObserver.shift()); }, - readSSE: function(data) { this.ape.parseResponse(data, this.callback); this.callback = null; }, - read: function(resp) { $clear(this.requestFailObserver.shift()); this.clearRequest(this.requests.shift()); this.ape.parseResponse(resp, this.callback); this.callback = null; }, - cancel: function() { if (this.requests.length > 0) { this.clearRequest(this.requests.shift()); } }, - running: function() { /* if (this.SSESupport) { return this.eventSource ? true : false; @@ -84,8 +80,13 @@ APE.Transport.JSONP = new Class({ return this.requests.length > 0 ? true : false; //} } - - }); - +/** + * Check if the browser supports JSONP + * + * @name APE.Transport.JSONP.browserSupport + * @returns {boolean} + * @function + * @private + */ APE.Transport.JSONP.browserSupport = function() { return true }; diff --git a/Source/Transport/Transport.SSE.js b/Source/Transport/Transport.SSE.js index 4459d75..3a0c6ca 100755 --- a/Source/Transport/Transport.SSE.js +++ b/Source/Transport/Transport.SSE.js @@ -1,7 +1,20 @@ -/* Notice : This class in only intended to be use as an implemented class */ -APE.Request.SSE = new Class({ - SSESupport: ((typeof window.addEventStream) == 'function'), +/** + * SSE Transport abstract class + *

Notice : This class in only intended to be use as an implemented class

+ * + * @name APE.Request.SSE + * @class + * @private + */ +/** + * @name APE.Transport.SSE + * @borrows APE.Request.SSE.prototype + * @private + */ +APE.Request.SSE = new Class({ + /* Notice : This class in only intended to be use as an implemented class */ + SSESupport: ((typeof window.addEventStream) == 'function'), initSSE: function(queryString, options, readCallback) { var tmp = document.createElement('div'); document.body.appendChild(tmp); diff --git a/Source/Transport/Transport.WebSocket.js b/Source/Transport/Transport.WebSocket.js index bd8080a..5ac136d 100644 --- a/Source/Transport/Transport.WebSocket.js +++ b/Source/Transport/Transport.WebSocket.js @@ -1,16 +1,19 @@ +/** + * WebSocket Transport object + * + * @name APE.Transport.WebSocket + * @class + * @private + */ APE.Transport.WebSocket = new Class({ - stack: [], connRunning: false, - initialize: function(ape) { this.ape = ape; this.initWs(); }, - initWs: function() { - var uri = (this.ape.options.secure ? 'wss' : 'ws') + '://' + this.ape.options.frequency + '.' + this.ape.options.server + '/' + this.ape.options.transport +'/'; - + var uri = (this.ape.options.secure ? 'wss' : 'ws') + '://' + this.ape.options.frequency + '.' + this.ape.options.server + '/' + this.ape.options.transport + '/'; this.ws = ('MozWebSocket' in window ? new MozWebSocket(uri) : new WebSocket(uri)); this.connRunning = true; this.ws.onmessage = this.readWs.bind(this); @@ -18,27 +21,22 @@ APE.Transport.WebSocket = new Class({ this.ws.onclose = this.closeWs.bind(this); this.ws.onerror = this.errorWs.bind(this); }, - readWs: function(evt) { this.ape.parseResponse(evt.data, this.callback); this.callback = null; }, - openWs: function() { if (this.stack.length > 0) { for (var i = 0; i < this.stack.length; i++) this.send(this.stack[i].q, this.stack[i].options); this.stack.length = 0; } }, - closeWs: function() { this.connRunning = false; }, - errorWs: function() { this.connRunning = false; }, - send: function(queryString, options) { if (this.ws.readyState == 1) { if (options.requestCallback) this.callback = options.requestCallback; @@ -47,18 +45,23 @@ APE.Transport.WebSocket = new Class({ this.stack.push({'q': queryString, 'options': options}); } }, - running: function() { return this.connRunning; }, - cancel: function() { this.ws.close(); } - }); +/** + * Check if the browser supports WebSocket + * + * @name APE.Transport.WebSocket.browserSupport + * @returns {boolean} + * @function + * @private + */ APE.Transport.WebSocket.browserSupport = function() { if ('WebSocket' in window || 'MozWebSocket' in window) return true; else return 1;//No websocket support switch to XHRStreaming -} +}; diff --git a/Source/Transport/Transport.XHRStreaming.js b/Source/Transport/Transport.XHRStreaming.js index 6dc34f6..f4b56d6 100644 --- a/Source/Transport/Transport.XHRStreaming.js +++ b/Source/Transport/Transport.XHRStreaming.js @@ -1,27 +1,43 @@ +/** +* Request.XHRStreaming object +* Extends the mootools Request class +* +* @name Request.XHRStreaming +* @private +* @class +*/ + +/** + * Progress on a XHRStreaming request + * + * @name APE.progress + * @event + * public + */ Request.XHRStreaming = new Class({ - Extends: Request, - lastTextLength: 0, read: 0, //Contain the amout of data read - send: function(options) { //mootools set onreadystatechange after xhr.open. In webkit, this cause readyState 1 to be never fired if (Browser.Engine.webkit) this.xhr.onreadystatechange = this.onStateChange.bind(this); return this.parent(options); }, - onStateChange: function() { if (this.xhr.readyState == 1) this.dataSent = true; else if (this.xhr.readyState == 3) this.progress(this.xhr.responseText, this.xhr.responseXML); this.parent(); }, - - onProgress: function(){ + /** + * + * @private + * @fires APE.progress + * @param {unknown} arguments + */ + onProgress: function() { this.fireEvent('progress', arguments); }, - - progress: function(text, xml){ + progress: function(text, xml) { var length = text.length; this.read += length; text = text.substr(this.lastTextLength); @@ -29,27 +45,30 @@ Request.XHRStreaming = new Class({ this.onProgress(this.processScripts(text), xml); } }); + +/** + * XHRStreaming Transport object + * + * @name APE.Transport.XHRStreaming + * @augments APE.Request.SSE + * @class + * @private + */ APE.Transport.XHRStreaming = new Class({ - maxRequestSize: 100000, - Implements: APE.Request.SSE, - - initialize: function(ape){ + initialize: function(ape) { this.ape = ape; this.requestFailObserver = []; - - //If browser support servent sent event, switch to SSE / XHR transport + //If browser support servent sent event, switch to SSE / XHR transport if (this.SSESupport) this.ape.options.transport = 4; - this.streamInfo = { timeoutObserver: null, cleanClose: false, forceClose: false, callback: null - } + }; }, - send: function(queryString, options) { if (this.SSESupport && !this.eventSource) { this.initSSE(queryString, options, this.readSSE.bind(this)); @@ -58,7 +77,6 @@ APE.Transport.XHRStreaming = new Class({ if ((!this.streamRequest || !this.streamRequest.running) && !this.eventSource) { //Only one XHRstreaming request is allowed this.buffer = ''; this.request = this.doRequest(queryString, options); - if (options.requestCallback) this.streamInfo.callback = options.requestCallback; } else { //Simple XHR request var request = new Request({ @@ -71,16 +89,12 @@ APE.Transport.XHRStreaming = new Class({ }.bind(this) }).send(queryString); this.request = request; - //set up an observer to detect request timeout this.requestFailObserver.push(this.ape.requestFail.delay(this.ape.options.pollTime + 10000, this.ape, [1, request])); - } - return this.request; } }, - doRequest: function(queryString, options) { this.streamInfo.forceClose = false; @@ -97,95 +111,86 @@ APE.Transport.XHRStreaming = new Class({ } }.bind(this) }).send(queryString); - request.id = $time(); this.streamRequest = request; - //this should no longer exist //this.streamInfo.timeoutObserver = (function() { // this.streamInfo.forceClose = true; // //try to imediatly close stream // if (this.checkStream()) this.newStream(); //}).delay(1000*60, this); - return request; }, - readSSE: function(data) { this.ape.parseResponse(data, this.streamInfo.callback); this.streamInfo.callback = null; }, - - readFragment: function(text){ + readFragment: function(text) { this.streamInfo.canClose = false; - if (text == '') { - this.streamInfo.canClose = true; this.streamInfo.cleanClose = true; this.ape.parseResponse(text, this.streamInfo.callback); - this.streamInfo.callback = null; } else { text = this.buffer + text; - var group = text.split("\n\n"); + var group = text.split('\n\n'); var length = group.length; - // If group.length is gretter than 1 the fragment received complete last RAW or contains more than one RAW - if (group.length > 1) { + if (group.length > 1) { //Clear buffer this.buffer = ''; - - for (var i = 0; i < length-1; i++) { + for (var i = 0; i < length - 1; i++) { this.ape.parseResponse(group[i], this.streamInfo.callback); } - - if (group[length-1] !== '') { //Last group complete last received raw but it's not finish - this.buffer += group[length-1]; + if (group[length - 1] !== '') { //Last group complete last received raw but it's not finish + this.buffer += group[length - 1]; } else { //Received fragment is complete this.streamInfo.canClose = true; if (this.checkStream()) this.newStream(); } - //Delete callback this.streamInfo.callback = null; - } else {//Fragement received is a part of a raw - this.buffer = text; + } else {//Fragement received is a part of a raw + this.buffer = text; } } }, - running: function() { return (this.streamRequest && this.streamRequest.running) ? true : this.eventSource ? true : false; - }, - + }, checkStream: function() { return (this.streamInfo.forceClose && this.streamInfo.canClose) || (this.streamRequest && this.streamRequest.read >= this.maxRequestSize && this.streamInfo.canClose); }, - newStream: function() { // this.ape.request.send('CLOSE');//This will close the stream request $clear(this.streamInfo.timeoutObserver); this.streamRequest.cancel(); this.ape.check(); }, - - cancel: function(){ + cancel: function() { if (this.request) this.request.cancel(); - $clear(this.streamInfo.timeoutObserver); $clear(this.requestFailObserver.shift()); } }); +/** + * Check if the browser supports XHRStreaming + * + * @name APE.Transport.XHRStreaming.browserSupport + * @returns {boolean} + * @function + * @private + */ APE.Transport.XHRStreaming.browserSupport = function() { if (Browser.Features.xhr && (Browser.Engine.webkit || Browser.Engine.gecko)) { return true; - /* Not yet + /* Not yet if (Browser.Engine.presto && ((typeof window.addEventStream) == 'function')) return true; else if (window.XDomainRequest) return true; else return Browser.Engine.trident ? 0 : true; */ } else if (Browser.Features.xhr) return 0;//No XHRStreaming support switch to long polling else return 2;//No XHR Support, switch to JSONP -} +}; diff --git a/Source/Transport/Transport.longPolling.js b/Source/Transport/Transport.longPolling.js index b7a0397..8b4eafa 100755 --- a/Source/Transport/Transport.longPolling.js +++ b/Source/Transport/Transport.longPolling.js @@ -1,26 +1,36 @@ +/** +* Enhanced Request object +* Extends the mootools Request class +* +* @name Request +* @ignore +* @class +*/ Request = new Class({ - Extends: Request, - send: function(options) { //mootools set onreadystatechange after xhr.open, in webkit, this cause readyState 1 to be never fired if (Browser.Engine.webkit) this.xhr.onreadystatechange = this.onStateChange.bind(this); return this.parent(options); }, - onStateChange: function() { if (this.xhr.readyState == 1) this.dataSent = true; this.parent(); } }); +/** + * Longpolling Transport object + * + * @name APE.Transport.LongPolling + * @class + * @private + */ APE.Transport.longPolling = new Class({ - - initialize: function(ape) { + initialize: function(ape) { this.ape = ape; this.requestFailObserver = []; }, - send: function(queryString, options) { var request = new Request({ url: this.ape.serverUri, @@ -31,22 +41,24 @@ APE.Transport.longPolling = new Class({ }.bind(this) }).send(queryString); request.id = $time(); - this.request = request; - this.requestFailObserver.push(this.ape.requestFail.delay(this.ape.options.pollTime + 10000, this.ape, [-1, request])); - return request; }, - running: function() { return this.request ? this.request.running : false; }, - cancel: function() { if (this.request) this.request.cancel(); $clear(this.requestFailObserver.shift()); } }); - +/** + * Check if the browser supports longpolling + * + * @name APE.Transport.LongPolling.browserSupport + * @returns {boolean} + * @function + * @private + */ APE.Transport.longPolling.browserSupport = function() { return Browser.Features.xhr ? true : 2; }; diff --git a/Tools/Check/check.css b/Tools/Check/check.css new file mode 100644 index 0000000..49b9d22 --- /dev/null +++ b/Tools/Check/check.css @@ -0,0 +1,85 @@ +html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,button{margin:0;padding:0;border:0;outline:0;font-weight:normal;font-style:inherit;font-size:100%;font-family:inherit;vertical-align:baseline;}:focus{outline:0;}body{line-height:1;color:black;background:white;}ol,ul{list-style:none;}table{border-collapse:separate;border-spacing:0;}caption,th,td{text-align:left;font-weight:normal;}blockquote:before,blockquote:after,q:before,q:after{content:"";}blockquote,q{quotes:"""";}.clear{clear:both;}.fl{float:left;}.fr{float:right;} + +body { + background : #FFF; + font-family : "Trebuchet MS", Trebuchet, "Lucida Grande", "Helvetica", Arial, serif; + font-size : 12px; + color : #0c0c0c; + border-top:3px solid #4E7F8D; +} + +a { + color : #4E7F8D; + text-decoration : none; + font-weight : bold; +} + +a:hover { + text-decoration : underline; +} + +h1 { + font-size : 20px; + border-bottom:1px solid #CECECE; + border-top:2px solid #FFFFFF; + font-weight:bold; + text-align:center; + background:#E5EEF5; + line-height:30px; + color:#3789B3; +} + +h2 { + font-size : 15px; + font-weight:bold; + margin:15px 0px 10px 0px; +} + +#content { + width:90%; + margin:25px auto; +} + +input { + background : #6BB000; + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + text-align : center; + width : 200px; + height : 30px; + line-height : 15px; + font-size : 16px; + color : #FFF; + text-shadow : 0 1px 1px #2B4200; + cursor : pointer; + border : none; +} + +#log { + border : 1px solid #CECECE; + background : #FFF; + -moz-border-radius: 8px; + -webkit-border-radius: 8px; + padding : 10px; + overflow:scroll; +} + +.sucess { + background-image:url(accept.png)!important; +} + +.error{ + background-image:url(cross.png)!important; +} + +.output { + background:url(application.png) no-repeat left 5px; + padding-left:25px; + line-height:20px; +} + +.runningTest { + background:url(hourglass.png) no-repeat left 50%; + text-indent:25px; + line-height:20px; +} diff --git a/Tools/Check/check.js b/Tools/Check/check.js new file mode 100644 index 0000000..4b7ca6c --- /dev/null +++ b/Tools/Check/check.js @@ -0,0 +1,184 @@ +var APETest = new Class({ + Implements: Events, + actions: [], + client: null, + initialize: function() { + this.log = $('log'); + this.addEvent('testComplete', this.runTest); + this.initTest(); + this.runTest(); + }, + initTest: function() { + this.addTest('Init', function() { + this.fireEvent('testComplete', {'sucess': true}); + }.bind(this)); + this.addTest('Loading Client', function() { + var req = new Request({ + 'url': '../../Clients/MooTools.js', + 'method': 'get', + 'evalResponse': true, + 'onComplete': function(resp) { + if (window.APE) this.fireEvent('testComplete', {'sucess': true}); + else this.fireEvent('testComplete', {'error': 'Can\'t load client, check the file ' + window.location.href.replace('/Tools/Check/', '') + '/Clients/MooTools.js is available'}); + }.bind(this) + }).send(); + }.bind(this)); + this.addTest('Loading config', function() { + var req = new Request({ + 'url': '../../Demos/config.js', + 'method': 'get', + 'evalResponse': true, + 'onComplete': function() { + if (APE.Config.baseUrl) { + var confVal = ''; + for (var key in APE.Config) { + if (APE.Config.hasOwnProperty(key)) { + confVal += key + ' : ' + APE.Config[key] + '\n'; + } + } + this.fireEvent('testComplete', {'sucess': 'Config values are :
' + confVal + '
'}); + } + else { + this.fireEvent('testComplete', {'error': 'Can\'t load client, check the file ' + window.location.href.replace('/Tools/Check/', '') + '/Clients/MooTools.js is readable'}); + } + }.bind(this) + }).send(); + }.bind(this)); + this.addTest('Setting document.domain', function() { + if (APE.Config.domain == 'yourdomain.com') { + this.fireEvent('testComplete', {'error': 'APE.Config.domain has the default value. Please first edit Demos/config.js with the correct values. APE.Config.domain might be ' + window.location.hostname + ''}); + } else { + var error = false; + try { + if (APE.Config.domain == 'auto') document.domain = document.domain; + else document.domain = APE.Config.domain; + error = false; + } catch (e) { + error = true; + } + if (!error) { + this.fireEvent('testComplete', {'sucess': true}); + } else { + this.fireEvent('testComplete', {'error': "Can't set document.domain please check APE.Config.domain value it should be " + window.location.hostname}); + } + } + }.bind(this)); + this.addTest('Checking APE.Config.baseUrl', function() { + var oldAPE = $extend({}, APE); + var req = new Request({ + 'method': 'get', + 'url': APE.Config.baseUrl + '/Source/Core/APE.js', + 'onComplete': function(res) { + if (req.isSuccess()) { + var version = APE.version; + APE = oldAPE; + this.fireEvent('testComplete', {'sucess': 'APE JSF Version ' + version + ''}); + } else { + this.fireEvent('testComplete', {'error': 'Your variable APE.Config.baseUrl is wrong in Demos/config.js. Please change it to point to APE JSF directory on your webserver'}); + } + }.bind(this) + }).send(); + }.bind(this)); + this.addTest('Contacting APE Server', function() { + //Registering JSONP reader + Ape = { + transport: { + read: function(res) { + var jsonRes = JSON.decode(res)[0]; + if (jsonRes.data.domain) { + if (jsonRes.data.domain == document.domain) { + this.fireEvent('testComplete', {'sucess': 'domain = ' + jsonRes.data.domain + ''}); + } else { + this.fireEvent('testComplete', {'error': 'document.domain mismatch client is ' + document.domain + ' and server is ' + jsonRes.data.domain + ' value of domain for client and server must be the same. Try with ' + window.location.hostname + ' for both'}); + } + } else { + this.fireEvent('testComplete', {'error': 'Connection etablished but something went wrong. Server response is
' + res + '
'}); + } + $clear(timer); + }.bind(this) + } + }; + new Element('script', {'type': 'text/javascript', 'src': 'http://' + APE.Config.server + '/2/?' + encodeURIComponent('[{"cmd":"setup","params":{"domain":"' + document.domain + '"}}]')}).inject(document.body); + var timer = (function() { + this.fireEvent('testComplete', {'error': 'Can\'t contact APE Server. Please check the your APE Server is running and the folowing url is pointing to your APE server : http://' + APE.Config.server + ''}); + }).delay(1000, this); + }.bind(this)); + this.addTest('Contacting APE Server (adding frequency)', function() { + //Registering JSONP reader + Ape = { + transport: { + read: function(res) { + $clear(timer); + var jsonRes = JSON.decode(res)[0]; + if (jsonRes.data.domain) { + this.fireEvent('testComplete', {'sucess': true}); + } else { + this.fireEvent('testComplete', {'error': 'Connection etablished but something went wrong. Server response is
' + res + '
'}); + } + }.bind(this) + } + }; + new Element('script', {'type': 'text/javascript', 'src': 'http://0.' + APE.Config.server + '/2/?' + encodeURIComponent('[{"cmd":"setup","params":{"domain":"' + document.domain + '"}}]')}).inject(document.body); + var timer = (function() { + this.fireEvent('testComplete', {'error': 'Can\'t contact APE Server. Please check the folowing url is pointing to your APE server : http://0.' + APE.Config.server + ''}); + }).delay(1000, this); + }.bind(this)); + this.addTest('Initializing APE Client', function() { + this.client = new APE.Client(); + this.client.load(); + this.client.addEvent('load', function() { + $clear(timer); + this.fireEvent('testComplete', {'sucess': true}); + }.bind(this)); + var timer = (function() { + this.fireEvent('testComplete', {'error': 'Can\'t load APE JSF'}); + }).delay(4000, this); + }.bind(this)); + this.addTest('Connecting to APE Server', function() { + this.client.core.start({'name': $time().toString()}); + this.client.addEvent('ready', function() { + this.fireEvent('testComplete', {'sucess': true}); + $clear(timer); + }.bind(this)); + var timer = (function() { + this.fireEvent('testComplete', {'error': 'Can\'t login to APE server'}); + }).delay(2000, this); + }.bind(this)); + }, + runTest: function(res) { + if (res && !res.sucess && res.error) { + this.newLogResult(res.error, 'error'); + new Element('div', {'html': 'Something went wrong. If you can\'t fix it by yourself post a message on the newsgroups with the output below or join our IRC channel'}).inject(this.log); + } else { + if (res && res.sucess) this.newLogResult(res.sucess, 'sucess'); + if (this.actions.length > 0) { + var action = this.actions.shift(); + this.newLog(action.title); + action.fn.run(); + } else { + new Element('div', { + 'styles': { + 'text-align': 'center', + 'font-weight': 'bold', + 'font-size': '13px' + }, + 'text': 'All test done. Now you can play with your APE \\o/' + }).inject(this.log); + } + } + }, + addTest: function(title, fn) { + this.actions.push({'fn': fn, 'title': title}); + }, + newLog: function(title) { + new Element('div', {'class': 'runningTest', 'text': 'Running test : ' + title}).inject(this.log); + }, + newLogResult: function(res, state) { + var el = this.log.getLast(); + if (el.hasClass('runningTest')) { + if (state == 'sucess') el.addClass('sucess'); + else el.addClass('error'); + } + if (res != true) new Element('div', {'class': 'output', 'html': res}).inject(this.log); + } +}); diff --git a/Tools/Check/index.html b/Tools/Check/index.html index 4232f27..07c1a33 100644 --- a/Tools/Check/index.html +++ b/Tools/Check/index.html @@ -1,89 +1,12 @@ - - - - - - + + + + + + +

Test your APE installation

This test will help you to test your APE Client and server is correctly configured. Click on "Launch test" button to check your installation. @@ -96,220 +19,6 @@

Debug output:

- +