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

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