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.

xmpp.js 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. /* global $, APP, config, Strophe*/
  2. var Moderator = require("./moderator");
  3. var EventEmitter = require("events");
  4. var Recording = require("./recording");
  5. var SDP = require("./SDP");
  6. var Settings = require("../settings/Settings");
  7. var Pako = require("pako");
  8. var StreamEventTypes = require("../../service/RTC/StreamEventTypes");
  9. var UIEvents = require("../../service/UI/UIEvents");
  10. var XMPPEvents = require("../../service/xmpp/XMPPEvents");
  11. var eventEmitter = new EventEmitter();
  12. var connection = null;
  13. var authenticatedUser = false;
  14. function connect(jid, password) {
  15. connection = XMPP.createConnection();
  16. Moderator.setConnection(connection);
  17. if (connection.disco) {
  18. // for chrome, add multistream cap
  19. }
  20. connection.jingle.pc_constraints = APP.RTC.getPCConstraints();
  21. if (config.useIPv6) {
  22. // https://code.google.com/p/webrtc/issues/detail?id=2828
  23. if (!connection.jingle.pc_constraints.optional)
  24. connection.jingle.pc_constraints.optional = [];
  25. connection.jingle.pc_constraints.optional.push({googIPv6: true});
  26. }
  27. // Include user info in MUC presence
  28. var settings = Settings.getSettings();
  29. if (settings.email) {
  30. connection.emuc.addEmailToPresence(settings.email);
  31. }
  32. if (settings.uid) {
  33. connection.emuc.addUserIdToPresence(settings.uid);
  34. }
  35. if (settings.displayName) {
  36. connection.emuc.addDisplayNameToPresence(settings.displayName);
  37. }
  38. var anonymousConnectionFailed = false;
  39. connection.connect(jid, password, function (status, msg) {
  40. console.log('Strophe status changed to',
  41. Strophe.getStatusString(status));
  42. if (status === Strophe.Status.CONNECTED) {
  43. if (config.useStunTurn) {
  44. connection.jingle.getStunAndTurnCredentials();
  45. }
  46. console.info("My Jabber ID: " + connection.jid);
  47. if(password)
  48. authenticatedUser = true;
  49. maybeDoJoin();
  50. } else if (status === Strophe.Status.CONNFAIL) {
  51. if(msg === 'x-strophe-bad-non-anon-jid') {
  52. anonymousConnectionFailed = true;
  53. }
  54. } else if (status === Strophe.Status.DISCONNECTED) {
  55. if(anonymousConnectionFailed) {
  56. // prompt user for username and password
  57. XMPP.promptLogin();
  58. }
  59. } else if (status === Strophe.Status.AUTHFAIL) {
  60. // wrong password or username, prompt user
  61. XMPP.promptLogin();
  62. }
  63. });
  64. }
  65. function maybeDoJoin() {
  66. if (connection && connection.connected &&
  67. Strophe.getResourceFromJid(connection.jid)
  68. && (APP.RTC.localAudio || APP.RTC.localVideo)) {
  69. // .connected is true while connecting?
  70. doJoin();
  71. }
  72. }
  73. function doJoin() {
  74. var roomName = APP.UI.generateRoomName();
  75. Moderator.allocateConferenceFocus(
  76. roomName, APP.UI.checkForNicknameAndJoin);
  77. }
  78. function initStrophePlugins()
  79. {
  80. require("./strophe.emuc")(XMPP, eventEmitter);
  81. require("./strophe.jingle")(XMPP, eventEmitter);
  82. require("./strophe.moderate")(XMPP);
  83. require("./strophe.util")();
  84. require("./strophe.rayo")();
  85. require("./strophe.logger")();
  86. }
  87. function registerListeners() {
  88. APP.RTC.addStreamListener(maybeDoJoin,
  89. StreamEventTypes.EVENT_TYPE_LOCAL_CREATED);
  90. APP.UI.addListener(UIEvents.NICKNAME_CHANGED, function (nickname) {
  91. XMPP.addToPresence("displayName", nickname);
  92. });
  93. }
  94. function setupEvents() {
  95. $(window).bind('beforeunload', function () {
  96. if (connection && connection.connected) {
  97. // ensure signout
  98. $.ajax({
  99. type: 'POST',
  100. url: config.bosh,
  101. async: false,
  102. cache: false,
  103. contentType: 'application/xml',
  104. data: "<body rid='" + (connection.rid || connection._proto.rid)
  105. + "' xmlns='http://jabber.org/protocol/httpbind' sid='"
  106. + (connection.sid || connection._proto.sid)
  107. + "' type='terminate'>" +
  108. "<presence xmlns='jabber:client' type='unavailable'/>" +
  109. "</body>",
  110. success: function (data) {
  111. console.log('signed out');
  112. console.log(data);
  113. },
  114. error: function (XMLHttpRequest, textStatus, errorThrown) {
  115. console.log('signout error',
  116. textStatus + ' (' + errorThrown + ')');
  117. }
  118. });
  119. }
  120. XMPP.disposeConference(true);
  121. });
  122. }
  123. var XMPP = {
  124. sessionTerminated: false,
  125. /**
  126. * XMPP connection status
  127. */
  128. Status: Strophe.Status,
  129. /**
  130. * Remembers if we were muted by the focus.
  131. * @type {boolean}
  132. */
  133. forceMuted: false,
  134. start: function () {
  135. setupEvents();
  136. initStrophePlugins();
  137. registerListeners();
  138. Moderator.init(this, eventEmitter);
  139. var configDomain = config.hosts.anonymousdomain || config.hosts.domain;
  140. // Force authenticated domain if room is appended with '?login=true'
  141. if (config.hosts.anonymousdomain &&
  142. window.location.search.indexOf("login=true") !== -1) {
  143. configDomain = config.hosts.domain;
  144. }
  145. var jid = configDomain || window.location.hostname;
  146. connect(jid, null);
  147. },
  148. createConnection: function () {
  149. var bosh = config.bosh || '/http-bind';
  150. return new Strophe.Connection(bosh);
  151. },
  152. getStatusString: function (status) {
  153. return Strophe.getStatusString(status);
  154. },
  155. promptLogin: function () {
  156. // FIXME: re-use LoginDialog which supports retries
  157. APP.UI.showLoginPopup(connect);
  158. },
  159. joinRoom: function(roomName, useNicks, nick)
  160. {
  161. var roomjid;
  162. roomjid = roomName;
  163. if (useNicks) {
  164. if (nick) {
  165. roomjid += '/' + nick;
  166. } else {
  167. roomjid += '/' + Strophe.getNodeFromJid(connection.jid);
  168. }
  169. } else {
  170. var tmpJid = Strophe.getNodeFromJid(connection.jid);
  171. if(!authenticatedUser)
  172. tmpJid = tmpJid.substr(0, 8);
  173. roomjid += '/' + tmpJid;
  174. }
  175. connection.emuc.doJoin(roomjid);
  176. },
  177. myJid: function () {
  178. if(!connection)
  179. return null;
  180. return connection.emuc.myroomjid;
  181. },
  182. myResource: function () {
  183. if(!connection || ! connection.emuc.myroomjid)
  184. return null;
  185. return Strophe.getResourceFromJid(connection.emuc.myroomjid);
  186. },
  187. disposeConference: function (onUnload) {
  188. eventEmitter.emit(XMPPEvents.DISPOSE_CONFERENCE, onUnload);
  189. var handler = connection.jingle.activecall;
  190. if (handler && handler.peerconnection) {
  191. // FIXME: probably removing streams is not required and close() should
  192. // be enough
  193. if (APP.RTC.localAudio) {
  194. handler.peerconnection.removeStream(
  195. APP.RTC.localAudio.getOriginalStream(), onUnload);
  196. }
  197. if (APP.RTC.localVideo) {
  198. handler.peerconnection.removeStream(
  199. APP.RTC.localVideo.getOriginalStream(), onUnload);
  200. }
  201. handler.peerconnection.close();
  202. }
  203. connection.jingle.activecall = null;
  204. if(!onUnload)
  205. {
  206. this.sessionTerminated = true;
  207. connection.emuc.doLeave();
  208. }
  209. },
  210. addListener: function(type, listener)
  211. {
  212. eventEmitter.on(type, listener);
  213. },
  214. removeListener: function (type, listener) {
  215. eventEmitter.removeListener(type, listener);
  216. },
  217. allocateConferenceFocus: function(roomName, callback) {
  218. Moderator.allocateConferenceFocus(roomName, callback);
  219. },
  220. getLoginUrl: function (roomName, callback) {
  221. Moderator.getLoginUrl(roomName, callback);
  222. },
  223. getPopupLoginUrl: function (roomName, callback) {
  224. Moderator.getPopupLoginUrl(roomName, callback);
  225. },
  226. isModerator: function () {
  227. return Moderator.isModerator();
  228. },
  229. isSipGatewayEnabled: function () {
  230. return Moderator.isSipGatewayEnabled();
  231. },
  232. isExternalAuthEnabled: function () {
  233. return Moderator.isExternalAuthEnabled();
  234. },
  235. switchStreams: function (stream, oldStream, callback) {
  236. if (connection && connection.jingle.activecall) {
  237. // FIXME: will block switchInProgress on true value in case of exception
  238. connection.jingle.activecall.switchStreams(stream, oldStream, callback);
  239. } else {
  240. // We are done immediately
  241. console.warn("No conference handler or conference not started yet");
  242. callback();
  243. }
  244. },
  245. sendVideoInfoPresence: function (mute) {
  246. connection.emuc.addVideoInfoToPresence(mute);
  247. connection.emuc.sendPresence();
  248. },
  249. setVideoMute: function (mute, callback, options) {
  250. if(!connection)
  251. return;
  252. var self = this;
  253. var localCallback = function (mute) {
  254. self.sendVideoInfoPresence(mute);
  255. return callback(mute);
  256. };
  257. if(connection.jingle.activecall)
  258. {
  259. connection.jingle.activecall.setVideoMute(
  260. mute, localCallback, options);
  261. }
  262. else {
  263. localCallback(mute);
  264. }
  265. },
  266. setAudioMute: function (mute, callback) {
  267. if (!(connection && APP.RTC.localAudio)) {
  268. return false;
  269. }
  270. if (this.forceMuted && !mute) {
  271. console.info("Asking focus for unmute");
  272. connection.moderate.setMute(connection.emuc.myroomjid, mute);
  273. // FIXME: wait for result before resetting muted status
  274. this.forceMuted = false;
  275. }
  276. if (mute == APP.RTC.localAudio.isMuted()) {
  277. // Nothing to do
  278. return true;
  279. }
  280. // It is not clear what is the right way to handle multiple tracks.
  281. // So at least make sure that they are all muted or all unmuted and
  282. // that we send presence just once.
  283. APP.RTC.localAudio.mute();
  284. // isMuted is the opposite of audioEnabled
  285. connection.emuc.addAudioInfoToPresence(mute);
  286. connection.emuc.sendPresence();
  287. callback();
  288. return true;
  289. },
  290. // Really mute video, i.e. dont even send black frames
  291. muteVideo: function (pc, unmute) {
  292. // FIXME: this probably needs another of those lovely state safeguards...
  293. // which checks for iceconn == connected and sigstate == stable
  294. pc.setRemoteDescription(pc.remoteDescription,
  295. function () {
  296. pc.createAnswer(
  297. function (answer) {
  298. var sdp = new SDP(answer.sdp);
  299. if (sdp.media.length > 1) {
  300. if (unmute)
  301. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  302. else
  303. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  304. sdp.raw = sdp.session + sdp.media.join('');
  305. answer.sdp = sdp.raw;
  306. }
  307. pc.setLocalDescription(answer,
  308. function () {
  309. console.log('mute SLD ok');
  310. },
  311. function (error) {
  312. console.log('mute SLD error');
  313. APP.UI.messageHandler.showError("dialog.error",
  314. "dialog.SLDFailure");
  315. }
  316. );
  317. },
  318. function (error) {
  319. console.log(error);
  320. APP.UI.messageHandler.showError();
  321. }
  322. );
  323. },
  324. function (error) {
  325. console.log('muteVideo SRD error');
  326. APP.UI.messageHandler.showError("dialog.error",
  327. "dialog.SRDFailure");
  328. }
  329. );
  330. },
  331. toggleRecording: function (tokenEmptyCallback,
  332. startingCallback, startedCallback) {
  333. Recording.toggleRecording(tokenEmptyCallback,
  334. startingCallback, startedCallback, connection);
  335. },
  336. addToPresence: function (name, value, dontSend) {
  337. switch (name)
  338. {
  339. case "displayName":
  340. connection.emuc.addDisplayNameToPresence(value);
  341. break;
  342. case "etherpad":
  343. connection.emuc.addEtherpadToPresence(value);
  344. break;
  345. case "prezi":
  346. connection.emuc.addPreziToPresence(value, 0);
  347. break;
  348. case "preziSlide":
  349. connection.emuc.addCurrentSlideToPresence(value);
  350. break;
  351. case "connectionQuality":
  352. connection.emuc.addConnectionInfoToPresence(value);
  353. break;
  354. case "email":
  355. connection.emuc.addEmailToPresence(value);
  356. break;
  357. default :
  358. console.log("Unknown tag for presence: " + name);
  359. return;
  360. }
  361. if (!dontSend)
  362. connection.emuc.sendPresence();
  363. },
  364. /**
  365. * Sends 'data' as a log message to the focus. Returns true iff a message
  366. * was sent.
  367. * @param data
  368. * @returns {boolean} true iff a message was sent.
  369. */
  370. sendLogs: function (data) {
  371. if(!connection.emuc.focusMucJid)
  372. return false;
  373. var deflate = true;
  374. var content = JSON.stringify(data);
  375. if (deflate) {
  376. content = String.fromCharCode.apply(null, Pako.deflateRaw(content));
  377. }
  378. content = Base64.encode(content);
  379. // XEP-0337-ish
  380. var message = $msg({to: connection.emuc.focusMucJid, type: 'normal'});
  381. message.c('log', { xmlns: 'urn:xmpp:eventlog',
  382. id: 'PeerConnectionStats'});
  383. message.c('message').t(content).up();
  384. if (deflate) {
  385. message.c('tag', {name: "deflated", value: "true"}).up();
  386. }
  387. message.up();
  388. connection.send(message);
  389. return true;
  390. },
  391. populateData: function () {
  392. var data = {};
  393. if (connection.jingle) {
  394. data = connection.jingle.populateData();
  395. }
  396. return data;
  397. },
  398. getLogger: function () {
  399. if(connection.logger)
  400. return connection.logger.log;
  401. return null;
  402. },
  403. getPrezi: function () {
  404. return connection.emuc.getPrezi(this.myJid());
  405. },
  406. removePreziFromPresence: function () {
  407. connection.emuc.removePreziFromPresence();
  408. connection.emuc.sendPresence();
  409. },
  410. sendChatMessage: function (message, nickname) {
  411. connection.emuc.sendMessage(message, nickname);
  412. },
  413. setSubject: function (topic) {
  414. connection.emuc.setSubject(topic);
  415. },
  416. lockRoom: function (key, onSuccess, onError, onNotSupported) {
  417. connection.emuc.lockRoom(key, onSuccess, onError, onNotSupported);
  418. },
  419. dial: function (to, from, roomName,roomPass) {
  420. connection.rayo.dial(to, from, roomName,roomPass);
  421. },
  422. setMute: function (jid, mute) {
  423. connection.moderate.setMute(jid, mute);
  424. },
  425. eject: function (jid) {
  426. connection.moderate.eject(jid);
  427. },
  428. logout: function (callback) {
  429. Moderator.logout(callback);
  430. },
  431. findJidFromResource: function (resource) {
  432. return connection.emuc.findJidFromResource(resource);
  433. },
  434. getMembers: function () {
  435. return connection.emuc.members;
  436. },
  437. getJidFromSSRC: function (ssrc) {
  438. if(!connection)
  439. return null;
  440. return connection.emuc.ssrc2jid[ssrc];
  441. },
  442. getMUCJoined: function () {
  443. return connection.emuc.joined;
  444. },
  445. getSessions: function () {
  446. return connection.jingle.sessions;
  447. },
  448. removeStream: function (stream) {
  449. if(!connection || !connection.jingle.activecall ||
  450. !connection.jingle.activecall.peerconnection)
  451. return;
  452. connection.jingle.activecall.peerconnection.removeStream(stream);
  453. }
  454. };
  455. module.exports = XMPP;