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

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