Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

xmpp.js 17KB

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