2 Copyright (C) 2008-2009 Weelya <contact@weelya.com>
3 This file is part of APE Client.
4 APE is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
9 APE is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with APE ; if not, write to the Free Software Foundation,
16 Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 /*** ________________________________________________________
22 * /~ ~\ | APE, the Ajax Push Engine made with heart (and MooTools) |
23 * | //^\\//^\| | http://www.weelya.net - http://www.ape-project.org |
24 * /~~\ || o| |o|:~\ \ _______________________________________________________/
25 * | |6 ||___|_|_||:| /
29 * | |~~\ | ) ~------~`\
30 * /' | | | / ____ /~~~)\
42 APE.Core = new Class({
44 Implements: [APE.Events, Options],
49 server: '', // APE server URL
50 pollTime: 25000, // Max time for a request
51 identifier: 'ape', // Identifier is used by cookie to differentiate ape instance
52 transport: 0, // Transport 0: long polling, 1 : XHRStreaming, 2: JSONP, 3 SSE / JSONP, 4 : SSE / XHR
53 frequency: 0, // Frequency identifier
54 cycledStackTime: 350, //Time before send request of cycledStack
58 initialize: function(options){
60 this.setOptions(options);
62 this.selectTransport();
63 this.request = new APE.Request(this);
71 //this.serverUri = (this.options.secure ? 'https' : 'http') + '://' + this.options.frequency + '.' + this.options.server + '/' + this.options.transport + '/?',
72 this.serverUri = (this.options.secure ? 'https' : 'http') + '://' + this.options.server + '/' + this.options.transport + '/?',
74 this.status = 0; // 0 = APE is not initialized, 1 = connected, -1 = Disconnected by timeout, -2 = Disconnected by request failure
76 this.pollerObserver = null;
77 this.requestDisabled = false;
79 this.onRaw('login', this.rawLogin);
80 this.onRaw('err', this.rawErr);
81 this.onRaw('ident', this.rawIdent);
82 this.onRaw('quit', this.rawQuit);
84 this.onError('003', this.clearSession);
85 this.onError('004', this.clearSession);
87 //Set core var for APE.Client instance
88 if (options.init) options.init.apply(null, [this]);
90 //Execute complete function of APE.Client instance
91 if (options.complete) options.complete.apply(null, [this]);
92 this.fireEvent('load', this);
94 if (this.options.connectOptions) this.start(this.options.connectOptions);
97 selectTransport: function() {
98 var transports = [APE.Transport.longPolling, APE.Transport.XHRStreaming, APE.Transport.JSONP,null, null, null, APE.Transport.WebSocket];
99 var transport = this.options.transport;
102 while (support !== true) {
103 support = transports[transport].browserSupport();//Test if browser support transport
105 if (support === true) {
106 this.options.transport = transport;
107 this.transport = new transports[transport](this);
108 } else transport = support;//Browser do not support transport, next loop will test with fallback transport returned by browserSupport();
112 if (this.pollerActive) this.check();
115 startPoller: function() {
116 this.pollerActive = true;
119 stopPoller: function() {
120 $clear(this.pollerObserver);
121 this.pollerActive = false;
124 stopRequest: function() {
125 this.cancelRequest();
126 if (this.transport.streamRequest) this.transport.streamRequest.cancel();
127 this.requestDisabled = true;
130 parseParam: function(param) {
131 return ($type(param) == 'object') ? Hash.getValues(param) : $splat(param);
134 cancelRequest: function() {
135 this.transport.cancel();
139 * Function called when a request fail or timeout
141 requestFail: function(failStatus, request) {
142 var reSendData = false;
143 if (request.request && !request.request.dataSent) reSendData = true;
144 if (this.status > 0) {//APE is connected but request failed
145 this.status = failStatus;
146 this.cancelRequest();
148 this.fireEvent('apeDisconnect');
151 if (this.failCounter < 6) this.failCounter++;
153 //Cancel last request
154 this.cancelRequest();
156 var delay = (this.failCounter*$random(300,1000));
159 // this.request.send.delay(delay, this.request, queryString);
161 this.check.delay(delay, this);
166 * Parse received data from Server
168 parseResponse: function(raws, callback) {
170 if (this.status < 0 ) {
171 this.failCounter = 0;
174 this.fireEvent('apeReconnect');
179 var chlCallback;//Callback on challenge
182 raws = JSON.parse(raws);
183 if (!raws){ // Something went wrong, json decode failed
188 for (var i = 0; i < raws.length; i++){ //Read all raw
191 if (callback && $type(callback) == 'function') {
197 //Last request is finished and it's not an error
198 if (!this.transport.running()) {
199 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')) {
203 //Invalidate check if something went wrong with other raw or a new request have been launched
207 } else if (!this.transport.running()) check = true; //No request running, request didn't respond correct JSON, something went wrong
208 if (check) this.check();
212 * Fire raw event. If received raw is on a non-existing pipe, create new pipe
214 callRaw: function(raw) {
217 var pipeId = raw.data.pipe.pubid, pipe;
218 if (!this.pipes.has(pipeId)) {
219 pipe = this.newPipe(raw.data.pipe.casttype, raw.data);
221 pipe = this.pipes.get(pipeId);
225 pipe.fireEvent('raw_' + raw.raw.toLowerCase(), args);
231 this.fireEvent('onRaw', args);
233 if (raw.data.chl) {//Execute callback on challenge
234 var chlCallback = this.request.callbackChl.get(raw.data.chl);
236 this.request.callbackChl.erase(raw.data.chl);
237 chlCallback.run(raw);
241 this.fireEvent('raw_' + raw.raw.toLowerCase(), args);
244 newPipe: function(type, options){
245 if (options && options.pipe.pubid) {
246 var pipe = this.pipes.get(options.pipe.pubid)
247 if (pipe) return pipe;
250 if(type == 'uni') return new APE.PipeSingle(this, options);
251 if(type == 'multi') return new APE.PipeMulti(this, options);
252 if(type == 'proxy') return new APE.PipeProxy(this, options);
255 getRequest: function(opt) {
256 if (!opt.request) return this.request.send.bind(this.request);
257 else return this.request[opt.request].add.bind(this.request[opt.request]);
261 * Add a pipe to the core pipes hash
263 addPipe: function(pubid, pipe){
264 return this.pipes.set(pubid, pipe);
267 getPipe: function(pubid) {
268 var pipe = this.pipes.get(pubid);
270 pipe = this.users.get(pubid);
271 if (pipe) pipe = this.newPipe('uni', {'pipe': pipe});
277 * Remove a pipe from the pipe hash and fire event 'pipeDelete'
279 delPipe: function(pubid){
280 var pipe = this.pipes.get(pubid);
281 this.pipes.erase(pubid);
282 this.fireEvent(pipe.type+'PipeDelete', [pipe]);
287 this.request.send('CHECK');
290 start: function(args, options){
291 this.connect(args, options);
294 connect: function(args, options){
295 if (!options) options = {};
296 options.sessid = false;
298 this.request.stack.add('CONNECT', args, options);
299 if (this.options.channel) {
300 this.request.stack.add('JOIN', {"channels": this.options.channel}, options);
302 if (!$defined(options.sendStack) && options.sendStack !== false) this.request.stack.send();
305 join: function(channel, options) {
306 options = options || {};
307 options.channels = channel;
308 this.request.send('JOIN', options);
311 left: function(pubid){
312 this.request.send('LEFT', {"channel":this.pipes.get(pubid).name});
316 this.request.send('QUIT');
320 getPubid: function(){
324 getSessid:function(){
328 setSession: function(obj, option) {
329 if (this.restoring) return;
331 this.request.send('SESSION', {'action': 'set', 'values': obj}, option);
334 getSession: function(key, callback, option){
335 if (!option) option = {};
336 var requestOption = {};
339 requestOption.callback = function(resp) {
340 if (resp.raw == 'SESSIONS') this.apply(null, arguments)
343 requestOption.requestCallback = option.requestCallback || null;
345 this.getRequest(option)('SESSION', {
347 'values': (($type(key) == 'array') ? key : [key])
350 if (option.request && option.sendStack !== false) {
351 this.request[option.request].send();
355 rawIdent: function(raw){
356 this.user = raw.data.user;
357 this.pubid = raw.data.user.pubid;
358 this.user.pipes = new $H;
359 this.users.set(this.pubid, this.user);
362 rawLogin: function(param){
363 this.sessid = param.data.sessid;
367 this.fireEvent('ready');
368 this.fireEvent('init');
371 rawErr: function(err){
372 this.fireEvent('error_' + err.data.code, err);
375 rawQuit: function() {
380 * Clear the sessions, clean timer, remove cookies, remove events
382 clearSession:function(){
387 this.request.chl = 1;
389 this.options.restore = false;
391 this.fireEvent('clearSession');
393 this.cancelRequest();
398 APE.init = function(config){
399 //Delay of 1ms allow browser to do not show a loading message
401 new APE.Core(config);