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 15KB

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