initial commit
[namibia] / public / js / vendor / ape-source / Core / Core.js
1 /*
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.
8
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.
13
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
17   
18 */
19
20 /***                                                        ________________________________________________________
21  *                 __------__      /                                                                                                        \
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   ||___|_|_||:|  /
26  *            \__.  /      o  \/' / 
27  *             |   (       O   )_/
28  *    /~~~~\    `\  \         /
29  *   | |~~\ |     )  ~------~`\
30  *  /' |  | |   /     ____ /~~~)\
31  * (_/'   | | |     /'    |    ( |
32  *        | | |     \    /   __)/ \
33  *        \  \ \      \/    /' \   `\
34  *          \  \|\        /   | |\___|
35  *            \ |  \____/     | |
36  *            /^~>  \        _/ <
37  *           |  |         \       \
38  *           |  | \        \        \
39  *           -^-\  \       |        )
40  *                `\_______/^\______/
41  */
42 APE.Core = new Class({
43
44         Implements: [APE.Events, Options],
45
46         $originalEvents: {},
47
48         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
55                 secure: false
56         },
57
58         initialize: function(options){
59                 window.Ape = this;
60                 this.setOptions(options);
61
62                 this.selectTransport();
63                 this.request = new APE.Request(this);
64
65                 this.pipes = new $H; 
66                 this.users = new $H;
67
68                 this.sessid = null;
69                 this.pubid = null;
70
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 + '/?',
73                 this.timer = null;
74                 this.status = 0; // 0 = APE is not initialized, 1 = connected, -1 = Disconnected by timeout, -2 = Disconnected by request failure
75                 this.failCounter = 0;
76                 this.pollerObserver = null;
77                 this.requestDisabled = false;
78
79                 this.onRaw('login', this.rawLogin);
80                 this.onRaw('err', this.rawErr);
81                 this.onRaw('ident', this.rawIdent);
82                 this.onRaw('quit', this.rawQuit);
83
84                 this.onError('003', this.clearSession);
85                 this.onError('004', this.clearSession);
86
87                 //Set core var for APE.Client instance
88                 if (options.init) options.init.apply(null, [this]);
89
90                 //Execute complete function of APE.Client instance
91                 if (options.complete) options.complete.apply(null, [this]);
92                 this.fireEvent('load', this);
93
94                 if (this.options.connectOptions) this.start(this.options.connectOptions);
95         },
96
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;
100                 var support;
101
102                 while (support !== true) {
103                         support = transports[transport].browserSupport();//Test if browser support transport    
104
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();
109                 }
110         },
111         poller: function() {
112                 if (this.pollerActive) this.check();
113         },
114
115         startPoller: function() {
116                 this.pollerActive = true;
117         },
118
119         stopPoller: function() {
120                 $clear(this.pollerObserver);
121                 this.pollerActive = false;
122         },
123         
124         stopRequest: function() {
125                 this.cancelRequest();
126                 if (this.transport.streamRequest) this.transport.streamRequest.cancel();
127                 this.requestDisabled = true;
128         },
129         
130         parseParam: function(param) {
131                 return ($type(param) == 'object') ? Hash.getValues(param) : $splat(param);
132         },
133
134         cancelRequest: function() {
135                 this.transport.cancel();
136         },
137
138         /***
139          * Function called when a request fail or timeout
140          */
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();
147                         this.stopPoller();
148                         this.fireEvent('apeDisconnect');
149                 } 
150
151                 if (this.failCounter < 6) this.failCounter++;
152
153                 //Cancel last request
154                 this.cancelRequest();
155
156                 var delay = (this.failCounter*$random(300,1000));
157
158                 //if (reSendData) {
159                 //      this.request.send.delay(delay, this.request, queryString);
160                 //} else {
161                         this.check.delay(delay, this);
162                 //}
163         },
164
165         /***
166          * Parse received data from Server
167          */
168         parseResponse: function(raws, callback) {
169                 if (raws) {
170                         if (this.status < 0 ) {
171                                 this.failCounter = 0;
172                                 this.status = 1;
173                                 this.startPoller();
174                                 this.fireEvent('apeReconnect');
175                         }
176                 }
177
178                 var check = false;
179                 var chlCallback;//Callback on challenge
180
181                 if (raws) {
182                         raws = JSON.parse(raws); 
183                         if (!raws){ // Something went wrong, json decode failed
184                                 this.check();
185                                 return;
186                         }
187
188                         for (var i = 0; i < raws.length; i++){ //Read all raw
189                                 var raw = raws[i];
190
191                                 if (callback && $type(callback) == 'function') {
192                                         callback.run(raw);
193                                 }
194
195                                 this.callRaw(raw);
196
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')) {
200                                                 check = true;
201                                         }
202                                 } else {
203                                         //Invalidate check if something went wrong with other raw or a new request have been launched
204                                         check = false;
205                                 }
206                         }
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();
209         },
210
211         /***
212          * Fire raw event. If received raw is on a non-existing pipe, create new pipe
213          */
214         callRaw: function(raw) {
215                 var args;
216                 if (raw.data.pipe) {
217                         var pipeId = raw.data.pipe.pubid, pipe;
218                         if (!this.pipes.has(pipeId)) {
219                                 pipe = this.newPipe(raw.data.pipe.casttype, raw.data);
220                         } else {
221                                 pipe = this.pipes.get(pipeId);
222                         }
223                         if (pipe) {
224                                 args = [raw, pipe];
225                                 pipe.fireEvent('raw_' + raw.raw.toLowerCase(), args);
226                         }
227                 } else {
228                         args = raw;
229                 }
230
231                 this.fireEvent('onRaw', args);
232
233                 if (raw.data.chl) {//Execute callback on challenge
234                         var chlCallback = this.request.callbackChl.get(raw.data.chl);
235                         if (chlCallback) {
236                                 this.request.callbackChl.erase(raw.data.chl);
237                                 chlCallback.run(raw);
238                         }
239                 }
240
241                 this.fireEvent('raw_' + raw.raw.toLowerCase(), args);
242         },
243
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;
248                 } 
249
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);
253         },
254
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]);
258         },
259
260         /***
261          * Add a pipe to the core pipes hash
262          */
263         addPipe: function(pubid, pipe){
264                 return this.pipes.set(pubid, pipe); 
265         },
266
267         getPipe: function(pubid) {
268                 var pipe = this.pipes.get(pubid);
269                 if (!pipe) {
270                         pipe = this.users.get(pubid);
271                         if (pipe) pipe = this.newPipe('uni', {'pipe': pipe});
272                 }
273                 return pipe;
274         },
275
276         /***
277          * Remove a pipe from the pipe hash and fire event 'pipeDelete'
278          */
279         delPipe: function(pubid){
280                 var pipe = this.pipes.get(pubid);
281                 this.pipes.erase(pubid);
282                 this.fireEvent(pipe.type+'PipeDelete', [pipe]);
283                 return pipe;
284         },
285         
286         check: function(){
287                 this.request.send('CHECK');
288         },
289
290         start: function(args, options){
291                 this.connect(args, options); 
292         },
293
294         connect: function(args, options){
295                 if (!options) options = {};
296                 options.sessid = false;
297
298                 this.request.stack.add('CONNECT', args, options);
299                 if (this.options.channel) { 
300                         this.request.stack.add('JOIN', {"channels": this.options.channel}, options);
301                 }
302                 if (!$defined(options.sendStack) && options.sendStack !== false) this.request.stack.send();
303         },
304
305         join: function(channel, options) {
306                 options = options || {};
307                 options.channels = channel;
308                 this.request.send('JOIN', options);
309         },
310
311         left: function(pubid){
312                 this.request.send('LEFT', {"channel":this.pipes.get(pubid).name});
313         },
314
315         quit: function(){
316                 this.request.send('QUIT');
317                 this.clearSession();
318         },
319
320         getPubid: function(){
321                 return this.pubid;
322         },
323
324         getSessid:function(){
325                 return this.sessid;
326         },
327
328         setSession: function(obj, option) {
329                 if (this.restoring) return;
330
331                 this.request.send('SESSION', {'action': 'set', 'values': obj}, option);
332         },
333
334         getSession: function(key, callback, option){
335                 if (!option) option = {};
336                 var requestOption = {};
337
338                 if (callback) {
339                         requestOption.callback = function(resp) { 
340                                 if (resp.raw == 'SESSIONS') this.apply(null, arguments) 
341                         }.bind(callback)
342                 }
343                 requestOption.requestCallback = option.requestCallback || null;
344
345                 this.getRequest(option)('SESSION', {
346                                 'action':'get', 
347                                 'values': (($type(key) == 'array') ? key : [key])
348                         }, requestOption);
349
350                 if (option.request && option.sendStack !== false) {
351                         this.request[option.request].send();
352                 }
353         },
354         
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);
360         },
361
362         rawLogin: function(param){
363                 this.sessid = param.data.sessid;
364
365                 this.status = 1;
366                 this.startPoller();
367                 this.fireEvent('ready');
368                 this.fireEvent('init');
369         },
370
371         rawErr: function(err){
372                 this.fireEvent('error_' + err.data.code, err);
373         },
374
375         rawQuit: function() {
376                 this.stopRequest();
377         },
378         
379         /***
380          * Clear the sessions, clean timer, remove cookies, remove events
381          */
382         clearSession:function(){
383                 //Clear all APE var
384                 this.sessid = null;
385                 this.pubid = null;
386                 this.$events = {}; 
387                 this.request.chl = 1;
388                 this.status = 0;
389                 this.options.restore = false;
390                 
391                 this.fireEvent('clearSession');
392                 this.stopPoller();
393                 this.cancelRequest();
394         }
395 });
396
397 var Ape;  
398 APE.init = function(config){
399         //Delay of 1ms allow browser to do not show a loading message
400         (function() {
401                 new APE.Core(config);
402         }).delay(1);
403 }