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.

1219 lines
33 KiB

  1. /** @global console */
  2. (function(OCA, OC, $) {
  3. 'use strict';
  4. OCA.Talk = OCA.Talk || {};
  5. OCA.Talk.Signaling = {
  6. Base: {},
  7. Internal: {},
  8. Standalone: {},
  9. createConnection: function() {
  10. var settings = $("#app #signaling-settings").text();
  11. if (settings) {
  12. settings = JSON.parse(settings);
  13. } else {
  14. settings = {};
  15. }
  16. var urls = settings.server;
  17. if (urls && urls.length) {
  18. return new OCA.Talk.Signaling.Standalone(settings, urls);
  19. } else {
  20. return new OCA.Talk.Signaling.Internal(settings);
  21. }
  22. }
  23. };
  24. function Base(settings) {
  25. this.settings = settings;
  26. this.sessionId = '';
  27. this.currentRoomToken = null;
  28. this.currentCallToken = null;
  29. this.handlers = {};
  30. this.features = {};
  31. this.pendingChatRequests = [];
  32. this._lastChatMessagesFetch = null;
  33. }
  34. OCA.Talk.Signaling.Base = Base;
  35. OCA.Talk.Signaling.Base.prototype.on = function(ev, handler) {
  36. if (!this.handlers.hasOwnProperty(ev)) {
  37. this.handlers[ev] = [handler];
  38. } else {
  39. this.handlers[ev].push(handler);
  40. }
  41. switch (ev) {
  42. case 'stunservers':
  43. case 'turnservers':
  44. var servers = this.settings[ev] || [];
  45. if (servers.length) {
  46. // The caller expects the handler to be called when the data
  47. // is available, so defer to simulate a delayed response.
  48. _.defer(function() {
  49. handler(servers);
  50. });
  51. }
  52. break;
  53. }
  54. };
  55. OCA.Talk.Signaling.Base.prototype.off = function(ev, handler) {
  56. if (!this.handlers.hasOwnProperty(ev)) {
  57. return;
  58. }
  59. var pos = this.handlers[ev].indexOf(handler);
  60. while (pos !== -1) {
  61. this.handlers[ev].splice(pos, 1);
  62. pos = this.handlers[ev].indexOf(handler);
  63. }
  64. };
  65. OCA.Talk.Signaling.Base.prototype._trigger = function(ev, args) {
  66. var handlers = this.handlers[ev];
  67. if (!handlers) {
  68. return;
  69. }
  70. handlers = handlers.slice(0);
  71. for (var i = 0, len = handlers.length; i < len; i++) {
  72. var handler = handlers[i];
  73. handler.apply(handler, args);
  74. }
  75. };
  76. OCA.Talk.Signaling.Base.prototype.getSessionid = function() {
  77. return this.sessionId;
  78. };
  79. OCA.Talk.Signaling.Base.prototype.disconnect = function() {
  80. this.sessionId = '';
  81. this.currentCallToken = null;
  82. };
  83. OCA.Talk.Signaling.Base.prototype.hasFeature = function(feature) {
  84. return this.features && this.features[feature];
  85. };
  86. OCA.Talk.Signaling.Base.prototype.emit = function(ev, data) {
  87. switch (ev) {
  88. case 'joinRoom':
  89. this.joinRoom(data);
  90. break;
  91. case 'joinCall':
  92. this.joinCall(data, arguments[2]);
  93. break;
  94. case 'leaveRoom':
  95. this.leaveCurrentRoom();
  96. break;
  97. case 'leaveCall':
  98. this.leaveCurrentCall();
  99. break;
  100. case 'message':
  101. this.sendCallMessage(data);
  102. break;
  103. }
  104. };
  105. OCA.Talk.Signaling.Base.prototype.leaveCurrentRoom = function() {
  106. if (this.currentRoomToken) {
  107. this.leaveRoom(this.currentRoomToken);
  108. this.currentRoomToken = null;
  109. }
  110. };
  111. OCA.Talk.Signaling.Base.prototype.leaveCurrentCall = function() {
  112. if (this.currentCallToken) {
  113. this.leaveCall(this.currentCallToken);
  114. this.currentCallToken = null;
  115. }
  116. };
  117. OCA.Talk.Signaling.Base.prototype.setRoomCollection = function(rooms) {
  118. this.roomCollection = rooms;
  119. return this.syncRooms();
  120. };
  121. /**
  122. * Sets a single room to be synced.
  123. *
  124. * If there is a RoomCollection set the synchronization will be performed on
  125. * the RoomCollection instead and the given room will be ignored; setting a
  126. * single room is intended to be used only on public pages.
  127. *
  128. * @param OCA.SpreedMe.Models.Room room the room to sync.
  129. */
  130. OCA.Talk.Signaling.Base.prototype.setRoom = function(room) {
  131. this.room = room;
  132. return this.syncRooms();
  133. };
  134. OCA.Talk.Signaling.Base.prototype.syncRooms = function() {
  135. var defer = $.Deferred();
  136. if (this.roomCollection && OC.getCurrentUser().uid) {
  137. this.roomCollection.fetch({
  138. success: function(data) {
  139. defer.resolve(data);
  140. }
  141. });
  142. } else if (this.room) {
  143. this.room.fetch({
  144. success: function(data) {
  145. defer.resolve(data);
  146. }
  147. });
  148. } else {
  149. defer.resolve([]);
  150. }
  151. return defer;
  152. };
  153. OCA.Talk.Signaling.Base.prototype.joinRoom = function(token, password) {
  154. $.ajax({
  155. url: OC.linkToOCS('apps/spreed/api/v1/room', 2) + token + '/participants/active',
  156. type: 'POST',
  157. beforeSend: function (request) {
  158. request.setRequestHeader('Accept', 'application/json');
  159. },
  160. data: {
  161. password: password
  162. },
  163. success: function (result) {
  164. console.log("Joined", result);
  165. this.currentRoomToken = token;
  166. this._trigger('joinRoom', [token]);
  167. this._runPendingChatRequests();
  168. if (this.currentCallToken === token) {
  169. // We were in this call before, join again.
  170. this.joinCall(token);
  171. } else {
  172. this.currentCallToken = null;
  173. }
  174. this._joinRoomSuccess(token, result.ocs.data.sessionId);
  175. }.bind(this),
  176. error: function (result) {
  177. if (result.status === 404 || result.status === 503) {
  178. // Room not found or maintenance mode
  179. OC.redirect(OC.generateUrl('apps/spreed'));
  180. }
  181. if (result.status === 403) {
  182. // This should not happen anymore since we ask for the password before
  183. // even trying to join the call, but let's keep it for now.
  184. OC.dialogs.prompt(
  185. t('spreed', 'Please enter the password for this call'),
  186. t('spreed','Password required'),
  187. function (result, password) {
  188. if (result && password !== '') {
  189. this.joinRoom(token, password);
  190. }
  191. }.bind(this),
  192. true,
  193. t('spreed','Password'),
  194. true
  195. ).then(function() {
  196. var $dialog = $('.oc-dialog:visible');
  197. $dialog.find('.ui-icon').remove();
  198. var $buttons = $dialog.find('button');
  199. $buttons.eq(0).text(t('core', 'Cancel'));
  200. $buttons.eq(1).text(t('core', 'Submit'));
  201. });
  202. }
  203. }.bind(this)
  204. });
  205. };
  206. OCA.Talk.Signaling.Base.prototype._leaveRoomSuccess = function(/* token */) {
  207. // Override in subclasses if necessary.
  208. };
  209. OCA.Talk.Signaling.Base.prototype.leaveRoom = function(token) {
  210. this.leaveCurrentCall();
  211. this._trigger('leaveRoom', [token]);
  212. this._doLeaveRoom(token);
  213. $.ajax({
  214. url: OC.linkToOCS('apps/spreed/api/v1/room', 2) + token + '/participants/active',
  215. method: 'DELETE',
  216. async: false,
  217. success: function () {
  218. this._leaveRoomSuccess(token);
  219. // We left the current room.
  220. if (token === this.currentRoomToken) {
  221. this.currentRoomToken = null;
  222. }
  223. }.bind(this)
  224. });
  225. };
  226. OCA.Talk.Signaling.Base.prototype._joinCallSuccess = function(/* token */) {
  227. // Override in subclasses if necessary.
  228. };
  229. OCA.Talk.Signaling.Base.prototype.joinCall = function(token, flags) {
  230. $.ajax({
  231. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  232. type: 'POST',
  233. data: {
  234. flags: flags
  235. },
  236. beforeSend: function (request) {
  237. request.setRequestHeader('Accept', 'application/json');
  238. },
  239. success: function () {
  240. this.currentCallToken = token;
  241. this._trigger('joinCall', [token]);
  242. this._joinCallSuccess(token);
  243. }.bind(this),
  244. error: function () {
  245. // Room not found or maintenance mode
  246. OC.redirect(OC.generateUrl('apps/spreed'));
  247. }.bind(this)
  248. });
  249. };
  250. OCA.Talk.Signaling.Base.prototype._leaveCallSuccess = function(/* token */) {
  251. // Override in subclasses if necessary.
  252. };
  253. OCA.Talk.Signaling.Base.prototype.leaveCall = function(token, keepToken) {
  254. if (!token) {
  255. return;
  256. }
  257. $.ajax({
  258. url: OC.linkToOCS('apps/spreed/api/v1/call', 2) + token,
  259. method: 'DELETE',
  260. async: false,
  261. success: function () {
  262. this._trigger('leaveCall', [token]);
  263. this._leaveCallSuccess(token);
  264. // We left the current call.
  265. if (!keepToken && token === this.currentCallToken) {
  266. this.currentCallToken = null;
  267. }
  268. }.bind(this)
  269. });
  270. };
  271. OCA.Talk.Signaling.Base.prototype._runPendingChatRequests = function() {
  272. while (this.pendingChatRequests.length) {
  273. var item = this.pendingChatRequests.shift();
  274. this._doReceiveChatMessages.apply(this, item);
  275. }
  276. };
  277. OCA.Talk.Signaling.Base.prototype.receiveChatMessages = function(lastKnownMessageId) {
  278. var defer = $.Deferred();
  279. if (!this.currentRoomToken) {
  280. // Not in a room yet, defer loading of messages.
  281. this.pendingChatRequests.push([defer, lastKnownMessageId]);
  282. return defer;
  283. }
  284. return this._doReceiveChatMessages(defer, lastKnownMessageId);
  285. };
  286. OCA.Talk.Signaling.Base.prototype._getChatRequestData = function(lastKnownMessageId) {
  287. return {
  288. lastKnownMessageId: lastKnownMessageId,
  289. lookIntoFuture: 1
  290. };
  291. };
  292. OCA.Talk.Signaling.Base.prototype._doReceiveChatMessages = function(defer, lastKnownMessageId) {
  293. $.ajax({
  294. url: OC.linkToOCS('apps/spreed/api/v1/chat', 2) + this.currentRoomToken,
  295. method: 'GET',
  296. data: this._getChatRequestData(lastKnownMessageId),
  297. beforeSend: function (request) {
  298. defer.notify(request);
  299. request.setRequestHeader('Accept', 'application/json');
  300. },
  301. success: function (data, status, request) {
  302. if (status === "notmodified") {
  303. defer.resolve(null, request);
  304. } else {
  305. defer.resolve(data.ocs.data, request);
  306. }
  307. }.bind(this),
  308. error: function (result) {
  309. defer.reject(result);
  310. }
  311. });
  312. return defer;
  313. };
  314. OCA.Talk.Signaling.Base.prototype.startReceiveMessages = function() {
  315. this._waitTimeUntilRetry = 1;
  316. this.receiveMessagesAgain = true;
  317. this.lastKnownMessageId = 0;
  318. this._receiveChatMessages();
  319. };
  320. OCA.Talk.Signaling.Base.prototype.stopReceiveMessages = function() {
  321. this.receiveMessagesAgain = false;
  322. if (this._lastChatMessagesFetch !== null) {
  323. this._lastChatMessagesFetch.abort();
  324. }
  325. };
  326. OCA.Talk.Signaling.Base.prototype._receiveChatMessages = function() {
  327. if (this._lastChatMessagesFetch !== null) {
  328. // Another request is currently in progress.
  329. return;
  330. }
  331. this.receiveChatMessages(this.lastKnownMessageId)
  332. .progress(this._messagesReceiveStart.bind(this))
  333. .done(this._messagesReceiveSuccess.bind(this))
  334. .fail(this._messagesReceiveError.bind(this));
  335. };
  336. OCA.Talk.Signaling.Base.prototype._messagesReceiveStart = function(xhr) {
  337. this._lastChatMessagesFetch = xhr;
  338. };
  339. OCA.Talk.Signaling.Base.prototype._messagesReceiveSuccess = function(messages, xhr) {
  340. var lastKnownMessageId = xhr.getResponseHeader("X-Chat-Last-Given");
  341. if (lastKnownMessageId !== null) {
  342. this.lastKnownMessageId = lastKnownMessageId;
  343. }
  344. this._lastChatMessagesFetch = null;
  345. this._waitTimeUntilRetry = 1;
  346. if (this.receiveMessagesAgain) {
  347. this._receiveChatMessages();
  348. }
  349. if (messages && messages.length) {
  350. this._trigger("chatMessagesReceived", [messages]);
  351. }
  352. };
  353. OCA.Talk.Signaling.Base.prototype._retryChatLoadingOnError = function() {
  354. return this.receiveMessagesAgain;
  355. };
  356. OCA.Talk.Signaling.Base.prototype._messagesReceiveError = function(/* result */) {
  357. this._lastChatMessagesFetch = null;
  358. if (this._retryChatLoadingOnError()) {
  359. _.delay(_.bind(this._receiveChatMessages, this), this._waitTimeUntilRetry * 1000);
  360. // Increase the wait time until retry to at most 64 seconds.
  361. if (this._waitTimeUntilRetry < 64) {
  362. this._waitTimeUntilRetry *= 2;
  363. }
  364. }
  365. };
  366. // Connection to the internal signaling server provided by the app.
  367. function Internal(/*settings*/) {
  368. OCA.Talk.Signaling.Base.prototype.constructor.apply(this, arguments);
  369. this.spreedArrayConnection = [];
  370. this.pullMessagesFails = 0;
  371. this.pullMessagesRequest = null;
  372. this.isSendingMessages = false;
  373. this.sendInterval = window.setInterval(function(){
  374. this.sendPendingMessages();
  375. }.bind(this), 500);
  376. }
  377. Internal.prototype = new OCA.Talk.Signaling.Base();
  378. Internal.prototype.constructor = Internal;
  379. OCA.Talk.Signaling.Internal = Internal;
  380. OCA.Talk.Signaling.Internal.prototype.disconnect = function() {
  381. this.spreedArrayConnection = [];
  382. if (this.sendInterval) {
  383. window.clearInterval(this.sendInterval);
  384. this.sendInterval = null;
  385. }
  386. if (this.roomPoller) {
  387. window.clearInterval(this.roomPoller);
  388. this.roomPoller = null;
  389. }
  390. OCA.Talk.Signaling.Base.prototype.disconnect.apply(this, arguments);
  391. };
  392. OCA.Talk.Signaling.Internal.prototype.on = function(ev/*, handler*/) {
  393. OCA.Talk.Signaling.Base.prototype.on.apply(this, arguments);
  394. switch (ev) {
  395. case 'connect':
  396. // A connection is established if we can perform a request
  397. // through it.
  398. this._sendMessageWithCallback(ev);
  399. break;
  400. }
  401. };
  402. OCA.Talk.Signaling.Internal.prototype.forceReconnect = function(/* newSession */) {
  403. console.error("Forced reconnects are not supported with the internal signaling.");
  404. };
  405. OCA.Talk.Signaling.Internal.prototype._sendMessageWithCallback = function(ev) {
  406. var message = [{
  407. ev: ev
  408. }];
  409. this._sendMessages(message).done(function(result) {
  410. this._trigger(ev, [result.ocs.data]);
  411. }.bind(this)).fail(function(/*xhr, textStatus, errorThrown*/) {
  412. console.log('Sending signaling message with callback has failed.');
  413. // TODO: Add error handling
  414. });
  415. };
  416. OCA.Talk.Signaling.Internal.prototype._sendMessages = function(messages) {
  417. var defer = $.Deferred();
  418. $.ajax({
  419. url: OC.linkToOCS('apps/spreed/api/v1/signaling', 2) + this.currentRoomToken,
  420. type: 'POST',
  421. data: {messages: JSON.stringify(messages)},
  422. beforeSend: function (request) {
  423. request.setRequestHeader('Accept', 'application/json');
  424. },
  425. success: function (result) {
  426. defer.resolve(result);
  427. },
  428. error: function (xhr, textStatus, errorThrown) {
  429. defer.reject(xhr, textStatus, errorThrown);
  430. }
  431. });
  432. return defer;
  433. };
  434. OCA.Talk.Signaling.Internal.prototype._joinRoomSuccess = function(token, sessionId) {
  435. this.sessionId = sessionId;
  436. this._startPullingMessages();
  437. };
  438. OCA.Talk.Signaling.Internal.prototype._doLeaveRoom = function(/*token*/) {
  439. // Nothing to do anymore
  440. };
  441. OCA.Talk.Signaling.Internal.prototype.sendCallMessage = function(data) {
  442. if(data.type === 'answer') {
  443. console.log("ANSWER", data);
  444. } else if(data.type === 'offer') {
  445. console.log("OFFER", data);
  446. }
  447. this.spreedArrayConnection.push({
  448. ev: "message",
  449. fn: JSON.stringify(data),
  450. sessionId: this.sessionId
  451. });
  452. };
  453. OCA.Talk.Signaling.Internal.prototype.setRoomCollection = function(/*rooms*/) {
  454. this._pollForRoomChanges();
  455. return OCA.Talk.Signaling.Base.prototype.setRoomCollection.apply(this, arguments);
  456. };
  457. OCA.Talk.Signaling.Internal.prototype.setRoom = function(/*room*/) {
  458. this._pollForRoomChanges();
  459. return OCA.Talk.Signaling.Base.prototype.setRoom.apply(this, arguments);
  460. };
  461. OCA.Talk.Signaling.Internal.prototype._pollForRoomChanges = function() {
  462. if (this.roomPoller) {
  463. window.clearInterval(this.roomPoller);
  464. }
  465. this.roomPoller = window.setInterval(function() {
  466. this.syncRooms();
  467. }.bind(this), 10000);
  468. };
  469. /**
  470. * @private
  471. */
  472. OCA.Talk.Signaling.Internal.prototype._startPullingMessages = function() {
  473. if (!this.currentRoomToken) {
  474. return;
  475. }
  476. // Abort ongoing request
  477. if (this.pullMessagesRequest !== null) {
  478. this.pullMessagesRequest.abort();
  479. }
  480. // Connect to the messages endpoint and pull for new messages
  481. this.pullMessagesRequest =
  482. $.ajax({
  483. url: OC.linkToOCS('apps/spreed/api/v1/signaling', 2) + this.currentRoomToken,
  484. type: 'GET',
  485. dataType: 'json',
  486. beforeSend: function (request) {
  487. request.setRequestHeader('Accept', 'application/json');
  488. },
  489. success: function (result) {
  490. this.pullMessagesFails = 0;
  491. $.each(result.ocs.data, function(id, message) {
  492. switch(message.type) {
  493. case "usersInRoom":
  494. this._trigger('usersInRoom', [message.data]);
  495. this._trigger("participantListChanged");
  496. break;
  497. case "message":
  498. if (typeof(message.data) === 'string') {
  499. message.data = JSON.parse(message.data);
  500. }
  501. this._trigger('message', [message.data]);
  502. break;
  503. default:
  504. console.log('Unknown Signaling Message');
  505. break;
  506. }
  507. }.bind(this));
  508. this._startPullingMessages();
  509. }.bind(this),
  510. error: function (jqXHR, textStatus/*, errorThrown*/) {
  511. if (jqXHR.status === 0 && textStatus === 'abort') {
  512. // Request has been aborted. Ignore.
  513. } else if (this.currentRoomToken) {
  514. if (this.pullMessagesFails >= 3) {
  515. OCA.SpreedMe.app.connection.leaveCurrentRoom(false);
  516. return;
  517. }
  518. this.pullMessagesFails++;
  519. //Retry to pull messages after 5 seconds
  520. window.setTimeout(function() {
  521. this._startPullingMessages();
  522. }.bind(this), 5000);
  523. }
  524. }.bind(this)
  525. });
  526. };
  527. /**
  528. * @private
  529. */
  530. OCA.Talk.Signaling.Internal.prototype.sendPendingMessages = function() {
  531. if (!this.spreedArrayConnection.length || this.isSendingMessages) {
  532. return;
  533. }
  534. var pendingMessagesLength = this.spreedArrayConnection.length;
  535. this.isSendingMessages = true;
  536. this._sendMessages(this.spreedArrayConnection).done(function(/*result*/) {
  537. this.spreedArrayConnection.splice(0, pendingMessagesLength);
  538. this.isSendingMessages = false;
  539. }.bind(this)).fail(function(/*xhr, textStatus, errorThrown*/) {
  540. console.log('Sending pending signaling messages has failed.');
  541. this.isSendingMessages = false;
  542. }.bind(this));
  543. };
  544. function Standalone(settings, urls) {
  545. OCA.Talk.Signaling.Base.prototype.constructor.apply(this, arguments);
  546. if (typeof(urls) === "string") {
  547. urls = [urls];
  548. }
  549. // We can connect to any of the servers.
  550. var idx = Math.floor(Math.random() * urls.length);
  551. // TODO(jojo): Try other server if connection fails.
  552. var url = urls[idx];
  553. // Make sure we are using websocket urls.
  554. if (url.indexOf("https://") === 0) {
  555. url = "wss://" + url.substr(8);
  556. } else if (url.indexOf("http://") === 0) {
  557. url = "ws://" + url.substr(7);
  558. }
  559. if (url[url.length - 1] === "/") {
  560. url = url.substr(0, url.length - 1);
  561. }
  562. this.url = url + "/spreed";
  563. this.initialReconnectIntervalMs = 1000;
  564. this.maxReconnectIntervalMs = 16000;
  565. this.reconnectIntervalMs = this.initialReconnectIntervalMs;
  566. this.joinedUsers = {};
  567. this.rooms = [];
  568. this.connect();
  569. }
  570. Standalone.prototype = new OCA.Talk.Signaling.Base();
  571. Standalone.prototype.constructor = Standalone;
  572. OCA.Talk.Signaling.Standalone = Standalone;
  573. OCA.Talk.Signaling.Standalone.prototype.reconnect = function() {
  574. if (this.reconnectTimer) {
  575. return;
  576. }
  577. // Wiggle interval a little bit to prevent all clients from connecting
  578. // simultaneously in case the server connection is interrupted.
  579. var interval = this.reconnectIntervalMs - (this.reconnectIntervalMs / 2) + (this.reconnectIntervalMs * Math.random());
  580. console.log("Reconnect in", interval);
  581. this.reconnected = true;
  582. this.reconnectTimer = window.setTimeout(function() {
  583. this.reconnectTimer = null;
  584. this.connect();
  585. }.bind(this), interval);
  586. this.reconnectIntervalMs = this.reconnectIntervalMs * 2;
  587. if (this.reconnectIntervalMs > this.maxReconnectIntervalMs) {
  588. this.reconnectIntervalMs = this.maxReconnectIntervalMs;
  589. }
  590. if (this.socket) {
  591. this.socket.close();
  592. this.socket = null;
  593. }
  594. };
  595. OCA.Talk.Signaling.Standalone.prototype.connect = function() {
  596. console.log("Connecting to", this.url);
  597. this.callbacks = {};
  598. this.id = 1;
  599. this.pendingMessages = [];
  600. this.connected = false;
  601. this._forceReconnect = false;
  602. this.socket = new WebSocket(this.url);
  603. window.signalingSocket = this.socket;
  604. this.socket.onopen = function(event) {
  605. console.log("Connected", event);
  606. this.reconnectIntervalMs = this.initialReconnectIntervalMs;
  607. this.sendHello();
  608. }.bind(this);
  609. this.socket.onerror = function(event) {
  610. console.log("Error", event);
  611. this.reconnect();
  612. }.bind(this);
  613. this.socket.onclose = function(event) {
  614. console.log("Close", event);
  615. this.reconnect();
  616. }.bind(this);
  617. this.socket.onmessage = function(event) {
  618. var data = event.data;
  619. if (typeof(data) === "string") {
  620. data = JSON.parse(data);
  621. }
  622. console.log("Received", data);
  623. var id = data.id;
  624. if (id && this.callbacks.hasOwnProperty(id)) {
  625. var cb = this.callbacks[id];
  626. delete this.callbacks[id];
  627. cb(data);
  628. }
  629. switch (data.type) {
  630. case "hello":
  631. if (!id) {
  632. // Only process if not received as result of our "hello".
  633. this.helloResponseReceived(data);
  634. }
  635. break;
  636. case "room":
  637. if (this.currentRoomToken && data.room.roomid !== this.currentRoomToken) {
  638. this._trigger('roomChanged', [this.currentRoomToken, data.room.roomid]);
  639. this.joinedUsers = {};
  640. this.currentRoomToken = null;
  641. } else {
  642. // TODO(fancycode): Only fetch properties of room that was modified.
  643. this.internalSyncRooms();
  644. }
  645. break;
  646. case "event":
  647. this.processEvent(data);
  648. break;
  649. case "message":
  650. data.message.data.from = data.message.sender.sessionid;
  651. this._trigger("message", [data.message.data]);
  652. break;
  653. default:
  654. if (!id) {
  655. console.log("Ignore unknown event", data);
  656. }
  657. break;
  658. }
  659. }.bind(this);
  660. };
  661. OCA.Talk.Signaling.Standalone.prototype.sendBye = function() {
  662. if (this.connected) {
  663. this.doSend({
  664. "type": "bye",
  665. "bye": {}
  666. });
  667. }
  668. this.resumeId = null;
  669. };
  670. OCA.Talk.Signaling.Standalone.prototype.disconnect = function() {
  671. this.sendBye();
  672. if (this.socket) {
  673. this.socket.close();
  674. this.socket = null;
  675. }
  676. OCA.Talk.Signaling.Base.prototype.disconnect.apply(this, arguments);
  677. };
  678. OCA.Talk.Signaling.Standalone.prototype.forceReconnect = function(newSession) {
  679. if (!this.connected) {
  680. if (!newSession) {
  681. // Not connected, will do reconnect anyway.
  682. return;
  683. }
  684. this._forceReconnect = true;
  685. return;
  686. }
  687. this._forceReconnect = false;
  688. if (newSession) {
  689. if (this.currentCallToken) {
  690. // Mark this session as "no longer in the call".
  691. this.leaveCall(this.currentCallToken, true);
  692. }
  693. this.sendBye();
  694. }
  695. if (this.socket) {
  696. // Trigger reconnect.
  697. this.socket.close();
  698. }
  699. };
  700. OCA.Talk.Signaling.Standalone.prototype.sendCallMessage = function(data) {
  701. this.doSend({
  702. "type": "message",
  703. "message": {
  704. "recipient": {
  705. "type": "session",
  706. "sessionid": data.to
  707. },
  708. "data": data
  709. }
  710. });
  711. };
  712. OCA.Talk.Signaling.Standalone.prototype.sendRoomMessage = function(data) {
  713. if (!this.currentCallToken) {
  714. console.warn("Not in a room, not sending room message", data);
  715. return;
  716. }
  717. this.doSend({
  718. "type": "message",
  719. "message": {
  720. "recipient": {
  721. "type": "room"
  722. },
  723. "data": data
  724. }
  725. });
  726. };
  727. OCA.Talk.Signaling.Standalone.prototype.doSend = function(msg, callback) {
  728. if (!this.connected && msg.type !== "hello") {
  729. // Defer sending any messages until the hello rsponse has been
  730. // received.
  731. this.pendingMessages.push([msg, callback]);
  732. return;
  733. }
  734. if (callback) {
  735. var id = this.id++;
  736. this.callbacks[id] = callback;
  737. msg["id"] = ""+id;
  738. }
  739. console.log("Sending", msg);
  740. this.socket.send(JSON.stringify(msg));
  741. };
  742. OCA.Talk.Signaling.Standalone.prototype.sendHello = function() {
  743. var msg;
  744. if (this.resumeId) {
  745. console.log("Trying to resume session", this.sessionId);
  746. msg = {
  747. "type": "hello",
  748. "hello": {
  749. "version": "1.0",
  750. "resumeid": this.resumeId
  751. }
  752. };
  753. } else {
  754. // Already reconnected with a new session.
  755. this._forceReconnect = false;
  756. var user = OC.getCurrentUser();
  757. var url = OC.linkToOCS('apps/spreed/api/v1/signaling', 2) + 'backend';
  758. msg = {
  759. "type": "hello",
  760. "hello": {
  761. "version": "1.0",
  762. "auth": {
  763. "url": url,
  764. "params": {
  765. "userid": user.uid,
  766. "ticket": this.settings.ticket
  767. }
  768. }
  769. }
  770. };
  771. }
  772. this.doSend(msg, this.helloResponseReceived.bind(this));
  773. };
  774. OCA.Talk.Signaling.Standalone.prototype.helloResponseReceived = function(data) {
  775. console.log("Hello response received", data);
  776. if (data.type !== "hello") {
  777. if (this.resumeId) {
  778. // Resuming the session failed, reconnect as new session.
  779. this.resumeId = '';
  780. this.sendHello();
  781. return;
  782. }
  783. // TODO(fancycode): How should this be handled better?
  784. console.error("Could not connect to server", data);
  785. this.reconnect();
  786. return;
  787. }
  788. var resumedSession = !!this.resumeId;
  789. this.connected = true;
  790. if (this._forceReconnect && resumedSession) {
  791. console.log("Perform pending forced reconnect");
  792. this.forceReconnect(true);
  793. return;
  794. }
  795. this.sessionId = data.hello.sessionid;
  796. this.resumeId = data.hello.resumeid;
  797. this.features = {};
  798. var i;
  799. if (data.hello.server && data.hello.server.features) {
  800. var features = data.hello.server.features;
  801. for (i = 0; i < features.length; i++) {
  802. this.features[features[i]] = true;
  803. }
  804. }
  805. var messages = this.pendingMessages;
  806. this.pendingMessages = [];
  807. for (i = 0; i < messages.length; i++) {
  808. var msg = messages[i][0];
  809. var callback = messages[i][1];
  810. this.doSend(msg, callback);
  811. }
  812. this._trigger("connect");
  813. if (this.reconnected) {
  814. // The list of rooms might have changed while we were not connected,
  815. // so perform resync once.
  816. this.internalSyncRooms();
  817. // Load any chat messages that might have been missed.
  818. this._receiveChatMessages();
  819. }
  820. if (!resumedSession && this.currentRoomToken) {
  821. this.joinRoom(this.currentRoomToken);
  822. }
  823. };
  824. OCA.Talk.Signaling.Standalone.prototype.setRoom = function(/* room */) {
  825. OCA.Talk.Signaling.Base.prototype.setRoom.apply(this, arguments);
  826. return this.internalSyncRooms();
  827. };
  828. OCA.Talk.Signaling.Standalone.prototype.joinRoom = function(token /*, password */) {
  829. if (!this.sessionId) {
  830. // If we would join without a connection to the signaling server here,
  831. // the room would be re-joined again in the "helloResponseReceived"
  832. // callback, leading to two entries for anonymous participants.
  833. console.log("Not connected to signaling server yet, defer joining room", token);
  834. this.currentRoomToken = token;
  835. return;
  836. }
  837. return OCA.Talk.Signaling.Base.prototype.joinRoom.apply(this, arguments);
  838. };
  839. OCA.Talk.Signaling.Standalone.prototype._joinRoomSuccess = function(token, nextcloudSessionId) {
  840. if (!this.sessionId) {
  841. console.log("No hello response received yet, not joining room", token);
  842. return;
  843. }
  844. console.log("Join room", token);
  845. this.doSend({
  846. "type": "room",
  847. "room": {
  848. "roomid": token,
  849. // Pass the Nextcloud session id to the signaling server. The
  850. // session id will be passed through to Nextcloud to check if
  851. // the (Nextcloud) user is allowed to join the room.
  852. "sessionid": nextcloudSessionId,
  853. }
  854. }, function(data) {
  855. this.joinResponseReceived(data, token);
  856. }.bind(this));
  857. };
  858. OCA.Talk.Signaling.Standalone.prototype.joinCall = function(token) {
  859. if (this.signalingRoomJoined !== token) {
  860. console.log("Not joined room yet, not joining call", token);
  861. this.pendingJoinCall = token;
  862. return;
  863. }
  864. OCA.Talk.Signaling.Base.prototype.joinCall.apply(this, arguments);
  865. };
  866. OCA.Talk.Signaling.Standalone.prototype._joinCallSuccess = function(/* token */) {
  867. // Update room list to fetch modified properties.
  868. this.internalSyncRooms();
  869. };
  870. OCA.Talk.Signaling.Standalone.prototype._leaveCallSuccess = function(/* token */) {
  871. // Update room list to fetch modified properties.
  872. this.internalSyncRooms();
  873. };
  874. OCA.Talk.Signaling.Standalone.prototype.joinResponseReceived = function(data, token) {
  875. console.log("Joined", data, token);
  876. this.signalingRoomJoined = token;
  877. if (token === this.pendingJoinCall) {
  878. this.joinCall(this.pendingJoinCall);
  879. this.pendingJoinCall = null;
  880. }
  881. if (this.roomCollection) {
  882. // The list of rooms is not fetched from the server. Update ping
  883. // of joined room so it gets sorted to the top.
  884. this.roomCollection.forEach(function(room) {
  885. if (room.get('token') === token) {
  886. room.set('lastPing', (new Date()).getTime() / 1000);
  887. }
  888. });
  889. this.roomCollection.sort();
  890. }
  891. };
  892. OCA.Talk.Signaling.Standalone.prototype._doLeaveRoom = function(token) {
  893. console.log("Leave room", token);
  894. this.doSend({
  895. "type": "room",
  896. "room": {
  897. "roomid": ""
  898. }
  899. }, function(data) {
  900. console.log("Left", data);
  901. this.signalingRoomJoined = null;
  902. // Any users we previously had in the room also "left" for us.
  903. var leftUsers = _.keys(this.joinedUsers);
  904. if (leftUsers.length) {
  905. this._trigger("usersLeft", [leftUsers]);
  906. }
  907. this.joinedUsers = {};
  908. }.bind(this));
  909. };
  910. OCA.Talk.Signaling.Standalone.prototype.processEvent = function(data) {
  911. switch (data.event.target) {
  912. case "room":
  913. this.processRoomEvent(data);
  914. break;
  915. case "roomlist":
  916. this.processRoomListEvent(data);
  917. break;
  918. case "participants":
  919. this.processRoomParticipantsEvent(data);
  920. break;
  921. default:
  922. console.log("Unsupported event target", data);
  923. break;
  924. }
  925. };
  926. OCA.Talk.Signaling.Standalone.prototype.processRoomEvent = function(data) {
  927. var i;
  928. switch (data.event.type) {
  929. case "join":
  930. var joinedUsers = data.event.join || [];
  931. if (joinedUsers.length) {
  932. console.log("Users joined", joinedUsers);
  933. var leftUsers = {};
  934. if (this.reconnected) {
  935. this.reconnected = false;
  936. // The browser reconnected, some of the previous sessions
  937. // may now no longer exist.
  938. leftUsers = _.extend({}, this.joinedUsers);
  939. }
  940. for (i = 0; i < joinedUsers.length; i++) {
  941. this.joinedUsers[joinedUsers[i].sessionid] = true;
  942. delete leftUsers[joinedUsers[i].sessionid];
  943. }
  944. leftUsers = _.keys(leftUsers);
  945. if (leftUsers.length) {
  946. this._trigger("usersLeft", [leftUsers]);
  947. }
  948. this._trigger("usersJoined", [joinedUsers]);
  949. this._trigger("participantListChanged");
  950. }
  951. break;
  952. case "leave":
  953. var leftSessionIds = data.event.leave || [];
  954. if (leftSessionIds.length) {
  955. console.log("Users left", leftSessionIds);
  956. for (i = 0; i < leftSessionIds.length; i++) {
  957. delete this.joinedUsers[leftSessionIds[i]];
  958. }
  959. this._trigger("usersLeft", [leftSessionIds]);
  960. this._trigger("participantListChanged");
  961. }
  962. break;
  963. case "message":
  964. this.processRoomMessageEvent(data.event.message.data);
  965. break;
  966. default:
  967. console.log("Unknown room event", data);
  968. break;
  969. }
  970. };
  971. OCA.Talk.Signaling.Standalone.prototype.processRoomMessageEvent = function(data) {
  972. switch (data.type) {
  973. case "chat":
  974. this._receiveChatMessages();
  975. break;
  976. default:
  977. console.log("Unknown room message event", data);
  978. }
  979. };
  980. OCA.Talk.Signaling.Standalone.prototype.setRoomCollection = function(/* rooms */) {
  981. OCA.Talk.Signaling.Base.prototype.setRoomCollection.apply(this, arguments);
  982. // Retrieve initial list of rooms for this user.
  983. return this.internalSyncRooms();
  984. };
  985. OCA.Talk.Signaling.Standalone.prototype.syncRooms = function() {
  986. if (this.pending_sync) {
  987. // A sync request is already in progress, don't start another one.
  988. return this.pending_sync;
  989. }
  990. // Never manually sync rooms, will be done based on notifications
  991. // from the signaling server.
  992. var defer = $.Deferred();
  993. defer.resolve(this.rooms);
  994. return defer;
  995. };
  996. OCA.Talk.Signaling.Standalone.prototype.internalSyncRooms = function() {
  997. if (this.pending_sync) {
  998. // A sync request is already in progress, don't start another one.
  999. return this.pending_sync;
  1000. }
  1001. var defer = $.Deferred();
  1002. this.pending_sync = OCA.Talk.Signaling.Base.prototype.syncRooms.apply(this, arguments);
  1003. this.pending_sync.then(function(rooms) {
  1004. this.pending_sync = null;
  1005. this.rooms = rooms;
  1006. defer.resolve(rooms);
  1007. }.bind(this));
  1008. return defer;
  1009. };
  1010. OCA.Talk.Signaling.Standalone.prototype.processRoomListEvent = function(data) {
  1011. console.log("Room list event", data);
  1012. this.internalSyncRooms();
  1013. };
  1014. OCA.Talk.Signaling.Standalone.prototype.processRoomParticipantsEvent = function(data) {
  1015. switch (data.event.type) {
  1016. case "update":
  1017. this._trigger("usersChanged", [data.event.update.users || []]);
  1018. this._trigger("participantListChanged");
  1019. this.internalSyncRooms();
  1020. break;
  1021. default:
  1022. console.log("Unknown room participant event", data);
  1023. break;
  1024. }
  1025. };
  1026. OCA.Talk.Signaling.Standalone.prototype._getChatRequestData = function(/* lastKnownMessageId */) {
  1027. var data = OCA.Talk.Signaling.Base.prototype._getChatRequestData.apply(this, arguments);
  1028. // Don't keep connection open and wait for more messages, will be done
  1029. // through another event on the WebSocket.
  1030. data.timeout = 0;
  1031. return data;
  1032. };
  1033. OCA.Talk.Signaling.Standalone.prototype._retryChatLoadingOnError = function() {
  1034. // We don't regularly poll for changes, so need to always retry loading
  1035. // of chat messages in case of errors.
  1036. return true;
  1037. };
  1038. OCA.Talk.Signaling.Standalone.prototype.startReceiveMessages = function() {
  1039. OCA.Talk.Signaling.Base.prototype.startReceiveMessages.apply(this, arguments);
  1040. // We will be notified when to load new messages.
  1041. this.receiveMessagesAgain = false;
  1042. };
  1043. OCA.Talk.Signaling.Standalone.prototype.requestOffer = function(sessionid, roomType) {
  1044. if (!this.hasFeature("mcu")) {
  1045. console.warn("Can't request an offer without a MCU.");
  1046. return;
  1047. }
  1048. if (typeof(sessionid) !== "string") {
  1049. // Got a user object.
  1050. sessionid = sessionid.sessionId || sessionid.sessionid;
  1051. }
  1052. console.log("Request offer from", sessionid);
  1053. this.doSend({
  1054. "type": "message",
  1055. "message": {
  1056. "recipient": {
  1057. "type": "session",
  1058. "sessionid": sessionid
  1059. },
  1060. "data": {
  1061. "type": "requestoffer",
  1062. "roomType": roomType
  1063. }
  1064. }
  1065. });
  1066. };
  1067. OCA.Talk.Signaling.Standalone.prototype.sendOffer = function(sessionid, roomType) {
  1068. // TODO(jojo): This should go away and "requestOffer" should be used
  1069. // instead by peers that want an offer by the MCU. See the calling
  1070. // location for further details.
  1071. if (!this.hasFeature("mcu")) {
  1072. console.warn("Can't send an offer without a MCU.");
  1073. return;
  1074. }
  1075. if (typeof(sessionid) !== "string") {
  1076. // Got a user object.
  1077. sessionid = sessionid.sessionId || sessionid.sessionid;
  1078. }
  1079. console.log("Send offer to", sessionid);
  1080. this.doSend({
  1081. "type": "message",
  1082. "message": {
  1083. "recipient": {
  1084. "type": "session",
  1085. "sessionid": sessionid
  1086. },
  1087. "data": {
  1088. "type": "sendoffer",
  1089. "roomType": roomType
  1090. }
  1091. }
  1092. });
  1093. };
  1094. })(OCA, OC, $);