PageRenderTime 1616ms CodeModel.GetById 293ms app.highlight 1025ms RepoModel.GetById 136ms app.codeStats 2ms

/wwwroot/lib/uce.js

http://github.com/AF83/ucengine
JavaScript | 681 lines | 536 code | 29 blank | 116 comment | 64 complexity | 9cab92fe9f05db40b8b746c35334e3e7 MD5 | raw file
  1/**
  2 * U.C.Engine library
  3 * http://ucengine.org/
  4 * (c) 2011 af83
  5 */
  6(function(g) {
  7    var VERSION = "0.6";
  8
  9    function UCEngine(baseUrl) {
 10
 11        baseUrl = baseUrl || '/api';
 12
 13        function getCollection(url, params, callback) {
 14            get(url, params, function(err, result, xhr) {
 15                if (!err) {
 16                    callback(err, result.result, xhr);
 17                } else {
 18                    callback(err, result, xhr);
 19                }
 20            });
 21        }
 22
 23        function format_api_url(url) {
 24            return baseUrl +'/' + VERSION + url;
 25        }
 26
 27        function uce_api_call(method, url, data, callback, overridedOptions) {
 28            var call_back = callback || $.noop;
 29            url = format_api_url(url);
 30            var options = {
 31                type     : method,
 32                dataType : "json",
 33                url      : url,
 34                data     : data,
 35                complete : function(xhr, textStatus) {
 36                    var response = jQuery.parseJSON(xhr.responseText);
 37                    if (xhr.status >= 400) {
 38                        call_back(xhr.status, response, xhr);
 39                    } else {
 40                        call_back(null, response, xhr);
 41                    }
 42                }
 43            };
 44            return $.ajax($.extend(options, overridedOptions || {}));
 45        }
 46
 47        function get(url, data, callback, options) {
 48            return uce_api_call("get", url, data, callback, options);
 49        }
 50
 51        function post(url, data, callback, options) {
 52            return uce_api_call("post", url, data, callback, options);
 53        }
 54
 55        function put(url, data, callback, options) {
 56            data = $.extend({"_method": "put"}, data);
 57            return uce_api_call("post", url, data, callback, options);
 58        }
 59
 60        function del(url, data, callback, options) {
 61            data = $.extend({"_method": "delete"}, data);
 62            return uce_api_call("post", url, data, callback, options);
 63        }
 64
 65        function UCEMeeting(client, meetingname, presence) {
 66            this.handlers = [];
 67            this.client = client;
 68            this.name = meetingname;
 69            this.uid = (presence || {}).user;
 70            this.sid = (presence || {}).id;
 71            this.params = {
 72                merge: function() {
 73                    var args = Array.prototype.slice.call(arguments);
 74                    args.unshift({}, {'uid': presence.user,
 75                                      'sid': presence.id});
 76                    return $.extend.apply($, args);
 77                }
 78            };
 79        }
 80
 81        UCEMeeting.prototype = {
 82            get: function(callback) {
 83                get("/meeting/" + this.name, this.params.merge(),
 84                    function(err, result, xhr) {
 85                        if (!err) {
 86                            callback(err, result.result, xhr);
 87                        } else {
 88                            callback(err, result, xhr);
 89                        }
 90                    });
 91                return this;
 92            },
 93            update: function(metadata, callback) {
 94                var params = this.params.merge({'metadata': metadata});
 95                put("/meeting/" + this.name, params,
 96                    function(err, result, xhr) {
 97                        if (!callback) {
 98                            return;
 99                        }
100                        callback(err, result, xhr);
101                    });
102            },
103            join: function(metadata, callback) {
104                post("/meeting/" + this.name + "/roster/",
105                     this.params.merge({metadata: metadata}), callback);
106                return this;
107            },
108            leave: function(callback) {
109                del("/meeting/" + this.name + "/roster/" + this.uid,
110                    this.params.merge(),
111                    callback);
112                return this;
113            },
114            getRoster: function(callback) {
115                get("/meeting/" + this.name + "/roster",
116                    this.params.merge(),
117                    function (err, result, xhr) {
118                        if (!callback) {
119                            return;
120                        }
121                        var roster = result.result;
122                        callback(err, roster, xhr);
123                    });
124            },
125
126            /**
127             * Generic Push event
128             */
129            _push: function(params, callback) {
130                post("/event/" + this.name,
131                     JSON.stringify(this.params.merge(params)),
132                     callback, {contentType: "application/json"});
133                return this;
134            },
135            /**
136             * Push event to all users in the current meeting room
137             * @param params can be a string with the type or an object
138             */
139            push: function(params, metadata, callback) {
140                // is params a string ?
141                if (params.charAt) {
142                    params = {
143                        type: params
144                    }
145                }
146                var type = params.type;
147                var parent = params.parent;
148                this._push({type: type,
149                            parent: parent,
150                            metadata: metadata},
151                           callback);
152                return this;
153            },
154            /**
155             * Push private event to the user in the current meeting room
156             */
157            pushTo: function(to, type, metadata, callback) {
158                this._push({'type': type,
159                            'to': to,
160                            'metadata': metadata},
161                           callback);
162                return this;
163            },
164
165            /**
166             * Get file upload url for this meeting
167             */
168            getFileUploadUrl: function() {
169                return format_api_url("/file/"+this.name+"?uid="+this.uid+"&sid="+ this.sid);
170            },
171
172            /**
173             * Get file download url
174             * @param String filename
175             */
176            getFileDownloadUrl: function(filename) {
177                return format_api_url("/file/"+this.name+"/"+ filename +"?uid="+this.uid+"&sid="+this.sid);
178            },
179
180            /**
181             * List files
182             */
183            listFiles: function(callback) {
184                get('/file/'+ this.name, this.params.merge(), function(err, result, xhr) {
185                    callback(err, result.result, xhr);
186                });
187            },
188
189            /**
190             * @param String id
191             * @param Function callback
192             */
193            delFile: function(id, callback) {
194                del("/file/" + this.name + "/" + id,
195                    this.params.merge(),
196                    function (err, result, xhr) {
197                        if (!callback) {
198                            return;
199                        }
200                        callback (err, result, xhr);
201                    });
202            },
203
204            /**
205             * @param Object params
206             *    search
207             *    start you can use uce.time() (mandatory)
208             *    type
209             *    from
210             * @param Function callback
211             * @param Boolean one_shot
212             * @param Array transports
213             * @return Object with a stop() method
214             */
215            waitEvents: function(params, callback, one_shot, transportsSelected) {
216                transportsSelected = transportsSelected || ['eventsource', 'longpolling'];
217                var that = this;
218                var transports = {
219                    // EventSource transport
220                    // http://dev.w3.org/html5/eventsource/
221                    eventsource : {
222                        available: window.EventSource,
223                        fun: function() {
224                            var getParams = that.params.merge({'mode': 'eventsource'}, params);
225                            var source = new EventSource(format_api_url("/live/" + that.name) +"?"+ $.param(getParams));
226                            source.onmessage = function(event) {
227                                try {
228                                    callback(null, $.parseJSON(event.data), null);
229                                } catch (e) {
230                                    // naive but it's better than nothing
231                                    if (window.console) console.error(e);
232                                }
233                            };
234                            return {
235                                close: function() {
236                                    source.close();
237                                }
238                            };
239                        }
240                    },
241                    // long polling transport
242                    longpolling: {
243                        available: true,
244                        fun: function() {
245                            function startLongPolling(p, callback) {
246                                var getParams = that.params.merge({'mode': 'longpolling'}, p);
247                                return get("/live/" + that.name, getParams, callback);
248                            }
249                            var longPolling = {
250                                aborted : false,
251                                _start : function() {
252                                    var that = this;
253                                    this.xhr = startLongPolling(params, function(err, result, xhr) {
254                                        try {
255                                            var events = result.result;
256                                            $.each(events, function(index, event) {
257                                                try {
258                                                    callback(err, event, xhr);
259                                                } catch (e) {
260                                                    // naive but it's better than nothing
261                                                    if (window.console) console.error(e);
262                                                }
263                                            });
264                                            if (events.length > 0) {
265                                                params.start = parseInt(events[events.length - 1].datetime, 10) + 1;
266                                            }
267                                        } catch (e) {
268                                            // do nothing
269                                        }
270                                        if (that.aborted === false && one_shot !== true) {
271                                            that._start(params, callback);
272                                        }
273                                    });
274                                },
275                                stop: function() {
276                                    this.aborted = true;
277                                    this.xhr.abort();
278                                }
279                            };
280                            longPolling._start();
281                            return longPolling;
282                        }
283                    }
284                };
285                for (var i = 0; i < transportsSelected.length; i++) {
286                    var transport = transports[transportsSelected[i]];
287                    if (transport.available) {
288                        return transport.fun();
289                    }
290                }
291                throw new Error("no transport available");
292            },
293
294            /**
295             * @param Object params
296             *    search
297             *    start
298             *    end
299             *    type
300             *    from
301             *    count
302             *    page
303             *    order
304             * @param Function callback
305             * @param Boolen onEachEvent
306             */
307            getEvents: function(params, callback, onEachEvent) {
308                var that = this;
309                params = this.params.merge(params);
310                get("/event/" + this.name,
311                    params,
312                    function(err, result, xhr) {
313                        if (!callback) {
314                            return;
315                        }
316                        var events = result.result;
317                        if (!onEachEvent) {
318                            callback(err, events, xhr);
319                        } else {
320                            $.each(events, function(index, event) {
321                                callback(err, event, xhr);
322                            });
323                        }
324                    });
325                return this;
326            },
327
328            /**
329             * Trigger event on the internal queue
330             * @param Object event
331             *  - type
332             */
333            trigger: function(event) {
334                $.each(this.handlers, function(i, item) {
335                    if (!item.type) {
336                        item.callback(event);
337                    } else {
338                        if (item.type == event.type)
339                            item.callback(event);
340                    }
341                });
342            },
343
344            /**
345             * Start main loop event
346             * [@param Integer start]
347             * [@param Array transport longpolling or eventsource]
348             */
349            startLoop: function(start, transports) {
350                var that = this;
351                return this.waitEvents({start: start || 0}, function(err, result, xhr) {
352                    that.trigger(result);
353                }, false, transports);
354            },
355
356            /**
357             * Start replay loop event
358             * @param Integer start offset
359             * @param Array events
360             */
361            startReplay: function(start, events, index) {
362                this._replay_current_time = start;
363                if (!index) {
364                    this._replay_events = events;
365                    index = 0;
366                }
367                var next = null;
368                while (next = events[index]) {
369                    if (next && start > next.datetime) {
370                        this.trigger(next);
371                        index++;
372                    } else {
373                        break;
374                    }
375                }
376                if (next) {
377                    this._replay_next_index = index;
378                    var that = this;
379                    var offset = 100; // each 100 milisecond
380                    this._replay_temporized = setTimeout(function() {
381                        that.startReplay(start + offset, events, index);
382                    }, offset);
383                }
384            },
385
386            getCurrentReplay: function() {
387                return this._replay_current_time;
388            },
389
390            /**
391             * Jump to a specific datetime
392             */
393            jumpToReplay: function(datetime) {
394                this.stopReplay();
395                if (datetime > this._replay_current_time) {
396                    this.startReplay(datetime, this._replay_events, this._replay_next_index);
397                } else {
398                    this.startReplay(datetime, this._replay_events);
399                }
400            },
401
402            stopReplay: function() {
403                clearTimeout(this._replay_temporized);
404            },
405
406            /**
407             * Alias of on
408             */
409            bind: function() {
410                var args = Array.prototype.slice.call(arguments);
411                return this.on.apply(this, args);
412            },
413
414            /**
415             * Bind event handler
416             * use it with startLoop
417             * [@param String type]
418             * @param Function callback
419             */
420            on: function(type, callback) {
421                if (!callback) {
422                    callback  = type;
423                    type = null;
424                }
425                this.handlers.push({type: type,
426                                    callback: callback});
427                return this;
428            },
429            /**
430             * Remove event listener
431             */
432            unbind: function(type, callback) {
433                 if (!callback) {
434                    callback  = type;
435                    type = null;
436                }
437                this.handlers = $(this.handlers).filter(function(index, handler) {
438                    return !(handler.callback == callback && handler.type == type);
439                });
440                return this;
441            },
442            /**
443             * Search event in current meeting
444             */
445            search: function(terms, options, callback) {
446                terms.location = this.name;
447                return this.client.search(terms, options, callback);
448            },
449            /**
450             * Can the user make the action in the current meeting ?
451             */
452            can: function(uid, action, object, conditions, callback) {
453                return this.client.user.can(uid, action, object, conditions, this.name, callback);
454            },
455            /**
456             *
457             */
458            canCurrentUser: function(action, object, conditions, callback) {
459                return this.can(this.uid, action, object, conditions, callback);
460            }
461        };
462
463        var _presence = null;
464        return {
465            connected : false,
466            uid: null,
467            name: null,
468            /**
469             * Create user presence
470             */
471            auth: function(uname, credential, callback) {
472                var params = {name: uname};
473                name = uname;
474                if (credential) {
475                    params.credential = credential;
476                }
477                var that = this;
478                post("/presence/", params, function(err, result, xhr) {
479                    if (err) {
480                        callback(err, result, xhr);
481                    } else {
482                        var uid = result.result.uid;
483                        var p = {"user": uid, "id": result.result.sid, "name": name};
484                        that.attachPresence(p);
485                        callback(err, p, xhr);
486                    }
487                });
488                return this;
489            },
490            /**
491             * Get user presence
492             */
493            presence: function(callback) {
494                get("/presence/" + _presence.id, {'uid': _presence.user,
495                                                  'sid': _presence.id},
496                    callback);
497                return this;
498            },
499            /**
500             * Close user presence
501             */
502            close: function(callback) {
503                del("/presence/" + _presence.id, {'uid': _presence.user,
504                                                  'sid': _presence.id},
505                    callback);
506                this.uid = null;
507                this.connected = false;
508                _presence = null;
509                return this;
510            },
511            getWaiter : function(calls_needed, callback) {
512                if(calls_needed == 0)
513                    callback();
514                var ok = true;
515                var waiter = function(){
516                    --calls_needed;
517                    if (calls_needed == 0 && ok)
518                        callback();
519                    // XXX: should we raise an error if waiter called too many times?
520                };
521                return waiter;
522            },
523            /**
524             * Attach presence to a new uce object
525             */
526            attachPresence : function(p) {
527                _presence = p;
528                this.connected = true;
529                this.uid = p.user;
530                this.name = p.name;
531                return this;
532            },
533            /**
534             * Search events
535             */
536            search: function(terms, params, callback) {
537                if (!callback) {
538                    callback = params;
539                    params = {};
540                }
541                var query = terms.query || '';
542                delete terms.query;
543                var searchTerms = [];
544                for (var i in terms) {
545                    searchTerms.push(i+":"+terms[i]);
546                }
547                searchTerms.push(query);
548                get("/search/event",
549                    $.extend({'uid': _presence.user,
550                              'sid': _presence.id,
551                              'searchTerms' : searchTerms.join(' ')}, params),
552                    function (err, result, xhr) {
553                        if (!callback) {
554                            return;
555                        }
556                        callback(err, result.result, xhr);
557                    });
558            },
559            _meetingsCache : {},
560            meeting: function(meetingname) {
561                if (!this._meetingsCache[meetingname])
562                    this._meetingsCache[meetingname] = new UCEMeeting(this, meetingname, _presence);
563                return this._meetingsCache[meetingname];
564            },
565            meetings : function(callback) {
566                getCollection("/meeting/", {'uid': _presence.user,
567                                            'sid': _presence.id}, callback);
568                return this;
569            },
570            user: {
571                register: function(name, auth, credential, metadata, callback) {
572                    post("/user/", $.extend({}, {name: name, auth: auth, credential:credential, metadata:metadata}), function(err, result, xhr) {
573                        callback(err, result, xhr);
574                    });
575                    return this;
576                },
577                registerWithPassword: function(name, credential, metadata, callback) {
578                    return this.register(name, "password", credential, metadata, callback);
579                },
580                get: function(uid, callback) {
581                    get("/user/"+ uid, $.extend({}, {'uid': _presence.user,
582                                                     'sid': _presence.id}),
583                        function(err, result, xhr) {
584                            callback(err, result, xhr);
585                        });
586                },
587                addRole: function(uid, role, location, callback) {
588                    post("/user/" + uid + "/roles", {'uid': _presence.user,
589                                                     'sid': _presence.id,
590                                                     'role': role,
591                                                     'location': location},
592                         function(err, result, xhr) {
593                             callback(err, result, xhr);
594                         });
595                },
596                delRole: function(uid, role, location, callback) {
597                    del("/user/" + uid + "/roles/" + role + "/" + location,
598                        {'uid': _presence.user,
599                         'sid': _presence.id},
600                        function(err, result, xhr) {
601                            callback(err, result, xhr);
602                        });
603
604                },
605                can: function(uid, action, object, conditions, location, callback) {
606                    get("/user/" + uid + "/can/" + action + "/" + object + "/" + location,
607                        {'conditions': conditions,
608                         'uid': _presence.user,
609                         'sid': _presence.id},
610                        function(err, result, xhr) {
611                            if (err)
612                                callback(err, result, xhr);
613                            else
614                                callback(err, result.result === "true", xhr);
615                        });
616                }
617            },
618            role: {
619                add: function(name, callback) {
620                    post("/role", {'name': name,
621                                   'uid': _presence.user,
622                                   'sid': _presence.id}, callback);
623                },
624                del: function(name, callback) {
625                    del("/role/" + name,
626                        {'uid': _presence.user,
627                         'sid': _presence.id}, callback);
628                },
629                addAccess: function(role, action, object, conditions, callback) {
630                    post("/role/" + role + "/acl",
631                         {'action': action,
632                          'object': object,
633                          'conditions': conditions,
634                          'uid': _presence.user,
635                          'sid': _presence.id}, callback);
636                },
637                delAccess: function(role, action, object, conditions, callback) {
638                    del("/role/" + role + "/acl/" + action + "/" + object,
639                        {'conditions': conditions,
640                         'uid': _presence.user,
641                         'sid': _presence.id}, callback);
642                }
643            },
644            users: {
645                get: function(callback) {
646                    getCollection("/user/", {'uid': _presence.user,
647                                             'sid': _presence.id}, callback);
648                    return this;
649                }
650            },
651            time: {
652                get: function(callback) {
653                    get("/time", {},
654                        function(err, result, xhr) {
655                            callback(err, result.result, xhr);
656                        });
657                    return this;
658                }
659            }
660        };
661    }
662    g.uce = {
663        version: VERSION,
664        createClient : function(baseUrl) {
665            return new UCEngine(baseUrl || '');
666        },
667        getWaiter : function(calls_needed, callback) {
668            if(calls_needed == 0)
669                callback();
670            var ok = true;
671            var waiter = function(){
672                --calls_needed;
673                if (calls_needed == 0 && ok)
674                    callback();
675                // XXX: should we raise an error if waiter called too many times?
676            };
677            return waiter;
678        }
679    };
680
681})(window);