You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
10 KiB

  1. (function(OCA, OC) {
  2. 'use strict';
  3. OCA.SpreedMe = OCA.SpreedMe || {};
  4. function SignalingBase() {
  5. this.sessionId = '';
  6. this.currentCallToken = null;
  7. this.handlers = {};
  8. }
  9. SignalingBase.prototype.on = function(ev, handler) {
  10. if (!this.handlers.hasOwnProperty(ev)) {
  11. this.handlers[ev] = [handler];
  12. } else {
  13. this.handlers[ev].push(handler);
  14. }
  15. };
  16. SignalingBase.prototype.emit = function(/*ev, data*/) {
  17. // Override in subclasses.
  18. };
  19. SignalingBase.prototype._trigger = function(ev, args) {
  20. var handlers = this.handlers[ev];
  21. if (!handlers) {
  22. return;
  23. }
  24. handlers = handlers.slice(0);
  25. for (var i = 0, len = handlers.length; i < len; i++) {
  26. var handler = handlers[i];
  27. handler.apply(handler, args);
  28. }
  29. };
  30. SignalingBase.prototype.getSessionid = function() {
  31. return this.sessionId;
  32. };
  33. SignalingBase.prototype.disconnect = function() {
  34. this.sessionId = '';
  35. this.currentCallToken = null;
  36. };
  37. SignalingBase.prototype.emit = function(ev, data) {
  38. switch (ev) {
  39. case 'join':
  40. var callback = arguments[2];
  41. var token = data;
  42. this.joinCall(token, callback);
  43. break;
  44. case 'leave':
  45. this.leaveCurrentCall();
  46. break;
  47. case 'message':
  48. this.sendCallMessage(data);
  49. break;
  50. }
  51. };
  52. SignalingBase.prototype.leaveCurrentCall = function() {
  53. if (this.currentCallToken) {
  54. this.leaveCall(this.currentCallToken);
  55. this.currentCallToken = null;
  56. }
  57. };
  58. SignalingBase.prototype.leaveAllCalls = function() {
  59. // Override if necessary.
  60. };
  61. SignalingBase.prototype.setRoomCollection = function(rooms) {
  62. this.roomCollection = rooms;
  63. return this.syncRooms();
  64. };
  65. SignalingBase.prototype.syncRooms = function() {
  66. var defer = $.Deferred();
  67. if (this.roomCollection && oc_current_user) {
  68. this.roomCollection.fetch({
  69. success: function(data) {
  70. defer.resolve(data);
  71. }
  72. });
  73. } else {
  74. defer.resolve([]);
  75. }
  76. return defer;
  77. };
  78. // Connection to the internal signaling server provided by the app.
  79. function InternalSignaling() {
  80. SignalingBase.prototype.constructor.apply(this, arguments);
  81. this.spreedArrayConnection = [];
  82. this.pingFails = 0;
  83. this.pingInterval = null;
  84. this.isSendingMessages = false;
  85. this.sendInterval = window.setInterval(function(){
  86. this.sendPendingMessages();
  87. }.bind(this), 500);
  88. }
  89. InternalSignaling.prototype = new SignalingBase();
  90. InternalSignaling.prototype.constructor = InternalSignaling;
  91. InternalSignaling.prototype.disconnect = function() {
  92. this.spreedArrayConnection = [];
  93. if (this.source) {
  94. this.source.close();
  95. this.source = null;
  96. }
  97. if (this.sendInterval) {
  98. window.clearInterval(this.sendInterval);
  99. this.sendInterval = null;
  100. }
  101. if (this.pingInterval) {
  102. window.clearInterval(this.pingInterval);
  103. this.pingInterval = null;
  104. }
  105. if (this.roomPoller) {
  106. window.clearInterval(this.roomPoller);
  107. this.roomPoller = null;
  108. }
  109. SignalingBase.prototype.disconnect.apply(this, arguments);
  110. };
  111. InternalSignaling.prototype.on = function(ev/*, handler*/) {
  112. SignalingBase.prototype.on.apply(this, arguments);
  113. switch (ev) {
  114. case 'connect':
  115. // A connection is established if we can perform a request
  116. // through it.
  117. this._sendMessageWithCallback(ev);
  118. break;
  119. case 'stunservers':
  120. case 'turnservers':
  121. // Values are not pushed by the server but have to be explicitly
  122. // requested.
  123. this._sendMessageWithCallback(ev);
  124. break;
  125. }
  126. };
  127. InternalSignaling.prototype._sendMessageWithCallback = function(ev) {
  128. var message = [{
  129. ev: ev
  130. }];
  131. this._sendMessages(message).done(function(result) {
  132. this._trigger(ev, [result.ocs.data]);
  133. }.bind(this)).fail(function(/*xhr, textStatus, errorThrown*/) {
  134. console.log('Sending signaling message with callback has failed.');
  135. // TODO: Add error handling
  136. });
  137. };
  138. InternalSignaling.prototype._sendMessages = function(messages) {
  139. var defer = $.Deferred();
  140. $.ajax({
  141. url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'signaling',
  142. type: 'POST',
  143. data: {messages: JSON.stringify(messages)},
  144. beforeSend: function (request) {
  145. request.setRequestHeader('Accept', 'application/json');
  146. },
  147. success: function (result) {
  148. defer.resolve(result);
  149. },
  150. error: function (xhr, textStatus, errorThrown) {
  151. defer.reject(xhr, textStatus, errorThrown);
  152. }
  153. });
  154. return defer;
  155. };
  156. InternalSignaling.prototype.joinCall = function(token, callback, password) {
  157. $.ajax({
  158. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  159. type: 'POST',
  160. beforeSend: function (request) {
  161. request.setRequestHeader('Accept', 'application/json');
  162. },
  163. data: {
  164. password: password
  165. },
  166. success: function (result) {
  167. console.log("Joined", result);
  168. this.sessionId = result.ocs.data.sessionId;
  169. this.currentCallToken = token;
  170. this._startPingCall();
  171. this._startPullingMessages();
  172. // We send an empty call description to simplewebrtc since
  173. // usersChanged (webrtc.js) will create/remove peer connections
  174. // with call participants
  175. var callDescription = {'clients': {}};
  176. callback('', callDescription);
  177. }.bind(this),
  178. error: function (result) {
  179. if (result.status === 404 || result.status === 503) {
  180. // Room not found or maintenance mode
  181. OC.redirect(OC.generateUrl('apps/spreed'));
  182. }
  183. if (result.status === 403) {
  184. // This should not happen anymore since we ask for the password before
  185. // even trying to join the call, but let's keep it for now.
  186. OC.dialogs.prompt(
  187. t('spreed', 'Please enter the password for this call'),
  188. t('spreed','Password required'),
  189. function (result, password) {
  190. if (result && password !== '') {
  191. this.joinCall(token, callback, password);
  192. }
  193. }.bind(this),
  194. true,
  195. t('spreed','Password'),
  196. true
  197. ).then(function() {
  198. var $dialog = $('.oc-dialog:visible');
  199. $dialog.find('.ui-icon').remove();
  200. var $buttons = $dialog.find('button');
  201. $buttons.eq(0).text(t('core', 'Cancel'));
  202. $buttons.eq(1).text(t('core', 'Submit'));
  203. });
  204. }
  205. }.bind(this)
  206. });
  207. };
  208. InternalSignaling.prototype.leaveCall = function(token) {
  209. if (token === this.currentCallToken) {
  210. this._stopPingCall();
  211. this._closeEventSource();
  212. }
  213. $.ajax({
  214. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  215. method: 'DELETE',
  216. async: false
  217. });
  218. };
  219. InternalSignaling.prototype.sendCallMessage = function(data) {
  220. if(data.type === 'answer') {
  221. console.log("ANSWER", data);
  222. } else if(data.type === 'offer') {
  223. console.log("OFFER", data);
  224. }
  225. this.spreedArrayConnection.push({
  226. ev: "message",
  227. fn: JSON.stringify(data),
  228. sessionId: this.sessionId
  229. });
  230. };
  231. InternalSignaling.prototype.setRoomCollection = function(/*rooms*/) {
  232. this._pollForRoomChanges();
  233. return SignalingBase.prototype.setRoomCollection.apply(this, arguments);
  234. };
  235. InternalSignaling.prototype._pollForRoomChanges = function() {
  236. if (this.roomPoller) {
  237. window.clearInterval(this.roomPoller);
  238. }
  239. this.roomPoller = window.setInterval(function() {
  240. this.syncRooms();
  241. }.bind(this), 10000);
  242. };
  243. /**
  244. * @private
  245. */
  246. InternalSignaling.prototype._getCallPeers = function(token) {
  247. var defer = $.Deferred();
  248. $.ajax({
  249. beforeSend: function (request) {
  250. request.setRequestHeader('Accept', 'application/json');
  251. },
  252. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  253. success: function (result) {
  254. var peers = result.ocs.data;
  255. defer.resolve(peers);
  256. }
  257. });
  258. return defer;
  259. };
  260. /**
  261. * @private
  262. */
  263. InternalSignaling.prototype._startPullingMessages = function() {
  264. // Connect to the messages endpoint and pull for new messages
  265. $.ajax({
  266. url: OC.linkToOCS('apps/spreed/api/v1', 2) + 'signaling',
  267. type: 'GET',
  268. dataType: 'json',
  269. beforeSend: function (request) {
  270. request.setRequestHeader('Accept', 'application/json');
  271. },
  272. success: function (result) {
  273. $.each(result.ocs.data, function(id, message) {
  274. switch(message.type) {
  275. case "usersInRoom":
  276. this._trigger('usersInRoom', [message.data]);
  277. break;
  278. case "message":
  279. if (typeof(message.data) === 'string') {
  280. message.data = JSON.parse(message.data);
  281. }
  282. this._trigger('message', [message.data]);
  283. break;
  284. default:
  285. console.log('Unknown Signaling Message');
  286. break;
  287. }
  288. }.bind(this));
  289. this._startPullingMessages();
  290. }.bind(this),
  291. error: function (/*jqXHR, textStatus, errorThrown*/) {
  292. //Retry to pull messages after 5 seconds
  293. window.setTimeout(function() {
  294. this._startPullingMessages();
  295. }.bind(this), 5000);
  296. }.bind(this)
  297. });
  298. };
  299. /**
  300. * @private
  301. */
  302. InternalSignaling.prototype._closeEventSource = function() {
  303. if (this.source) {
  304. this.source.close();
  305. this.source = null;
  306. }
  307. };
  308. /**
  309. * @private
  310. */
  311. InternalSignaling.prototype.sendPendingMessages = function() {
  312. if (!this.spreedArrayConnection.length || this.isSendingMessages) {
  313. return;
  314. }
  315. var pendingMessagesLength = this.spreedArrayConnection.length;
  316. this.isSendingMessages = true;
  317. this._sendMessages(this.spreedArrayConnection).done(function(/*result*/) {
  318. this.spreedArrayConnection.splice(0, pendingMessagesLength);
  319. this.isSendingMessages = false;
  320. }.bind(this)).fail(function(/*xhr, textStatus, errorThrown*/) {
  321. console.log('Sending pending signaling messages has failed.');
  322. this.isSendingMessages = false;
  323. }.bind(this));
  324. };
  325. /**
  326. * @private
  327. */
  328. InternalSignaling.prototype._startPingCall = function() {
  329. this._pingCall();
  330. // Send a ping to the server all 5 seconds to ensure that the connection
  331. // is still alive.
  332. this.pingInterval = window.setInterval(function() {
  333. this._pingCall();
  334. }.bind(this), 5000);
  335. };
  336. /**
  337. * @private
  338. */
  339. InternalSignaling.prototype._stopPingCall = function() {
  340. if (this.pingInterval) {
  341. window.clearInterval(this.pingInterval);
  342. this.pingInterval = null;
  343. }
  344. };
  345. /**
  346. * @private
  347. */
  348. InternalSignaling.prototype._pingCall = function() {
  349. if (!this.currentCallToken) {
  350. return;
  351. }
  352. $.ajax({
  353. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + this.currentCallToken + '/ping',
  354. method: 'POST'
  355. }).done(function() {
  356. this.pingFails = 0;
  357. }.bind(this)).fail(function(xhr) {
  358. // If there is an error when pinging, retry for 3 times.
  359. if (xhr.status !== 404 && this.pingFails < 3) {
  360. this.pingFails++;
  361. return;
  362. }
  363. OCA.SpreedMe.Calls.leaveCurrentCall(false);
  364. }.bind(this));
  365. };
  366. OCA.SpreedMe.createSignalingConnection = function() {
  367. // TODO(fancycode): Create different type of signaling connection
  368. // depending on configuration.
  369. return new InternalSignaling();
  370. };
  371. })(OCA, OC);