PageRenderTime 38ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

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