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

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