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

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