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

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