Source: jsxc.lib.xmpp.js

  1. /**
  2. * Handle XMPP stuff.
  3. *
  4. * @namespace jsxc.xmpp
  5. */
  6. jsxc.xmpp = {
  7. conn: null, // connection
  8. /**
  9. * Create new connection or attach to old
  10. *
  11. * @name login
  12. * @memberOf jsxc.xmpp
  13. * @private
  14. */
  15. /**
  16. * Create new connection with given parameters.
  17. *
  18. * @name login^2
  19. * @param {string} jid
  20. * @param {string} password
  21. * @memberOf jsxc.xmpp
  22. * @private
  23. */
  24. /**
  25. * Attach connection with given parameters.
  26. *
  27. * @name login^3
  28. * @param {string} jid
  29. * @param {string} sid
  30. * @param {string} rid
  31. * @memberOf jsxc.xmpp
  32. * @private
  33. */
  34. login: function() {
  35. if (jsxc.xmpp.conn && jsxc.xmpp.conn.authenticated) {
  36. jsxc.debug('Connection already authenticated.');
  37. return;
  38. }
  39. var jid = null,
  40. password = null,
  41. sid = null,
  42. rid = null;
  43. switch (arguments.length) {
  44. case 2:
  45. jid = arguments[0];
  46. password = arguments[1];
  47. break;
  48. case 3:
  49. jid = arguments[0];
  50. sid = arguments[1];
  51. rid = arguments[2];
  52. break;
  53. default:
  54. sid = jsxc.storage.getItem('sid');
  55. rid = jsxc.storage.getItem('rid');
  56. if (sid !== null && rid !== null) {
  57. jid = jsxc.storage.getItem('jid');
  58. } else {
  59. sid = jsxc.options.xmpp.sid || null;
  60. rid = jsxc.options.xmpp.rid || null;
  61. jid = jsxc.options.xmpp.jid;
  62. }
  63. }
  64. if (!jid) {
  65. jsxc.warn('Jid required for login');
  66. return;
  67. }
  68. if (!jsxc.bid) {
  69. jsxc.bid = jsxc.jidToBid(jid);
  70. }
  71. var url = jsxc.options.get('xmpp').url;
  72. if (!url) {
  73. jsxc.warn('xmpp.url required for login');
  74. return;
  75. }
  76. if (!(jsxc.xmpp.conn && jsxc.xmpp.conn.connected)) {
  77. // Register eventlistener
  78. $(document).on('connected.jsxc', jsxc.xmpp.connected);
  79. $(document).on('attached.jsxc', jsxc.xmpp.attached);
  80. $(document).on('disconnected.jsxc', jsxc.xmpp.disconnected);
  81. $(document).on('connfail.jsxc', jsxc.xmpp.onConnfail);
  82. $(document).on('authfail.jsxc', jsxc.xmpp.onAuthFail);
  83. Strophe.addNamespace('RECEIPTS', 'urn:xmpp:receipts');
  84. Strophe.addNamespace('VERSION', 'jabber:iq:version');
  85. }
  86. // Create new connection (no login)
  87. jsxc.xmpp.conn = new Strophe.Connection(url);
  88. if (jsxc.storage.getItem('debug') === true) {
  89. jsxc.xmpp.conn.xmlInput = function(data) {
  90. console.log('<', data);
  91. };
  92. jsxc.xmpp.conn.xmlOutput = function(data) {
  93. console.log('>', data);
  94. };
  95. }
  96. jsxc.xmpp.conn.nextValidRid = jsxc.xmpp.onRidChange;
  97. var callback = function(status, condition) {
  98. jsxc.debug(Object.getOwnPropertyNames(Strophe.Status)[status] + ': ' + condition);
  99. switch (status) {
  100. case Strophe.Status.CONNECTING:
  101. $(document).trigger('connecting.jsxc');
  102. break;
  103. case Strophe.Status.CONNECTED:
  104. jsxc.bid = jsxc.jidToBid(jsxc.xmpp.conn.jid.toLowerCase());
  105. $(document).trigger('connected.jsxc');
  106. break;
  107. case Strophe.Status.ATTACHED:
  108. $(document).trigger('attached.jsxc');
  109. break;
  110. case Strophe.Status.DISCONNECTED:
  111. $(document).trigger('disconnected.jsxc');
  112. break;
  113. case Strophe.Status.CONNFAIL:
  114. $(document).trigger('connfail.jsxc');
  115. break;
  116. case Strophe.Status.AUTHFAIL:
  117. $(document).trigger('authfail.jsxc');
  118. break;
  119. }
  120. };
  121. if (jsxc.xmpp.conn.caps) {
  122. jsxc.xmpp.conn.caps.node = 'http://jsxc.org/';
  123. }
  124. jsxc.changeState(jsxc.CONST.STATE.ESTABLISHING);
  125. if (sid && rid) {
  126. jsxc.debug('Try to attach');
  127. jsxc.debug('SID: ' + sid);
  128. jsxc.xmpp.conn.attach(jid, sid, rid, callback);
  129. } else {
  130. jsxc.debug('New connection');
  131. if (jsxc.xmpp.conn.caps) {
  132. // Add system handler, because user handler isn't called before
  133. // we are authenticated
  134. // @REVIEW this could maybe retrieved from jsxc.xmpp.conn.features
  135. jsxc.xmpp.conn._addSysHandler(function(stanza) {
  136. var from = jsxc.xmpp.conn.domain,
  137. c = stanza.querySelector('c'),
  138. ver = c.getAttribute('ver'),
  139. node = c.getAttribute('node');
  140. var _jidNodeIndex = JSON.parse(localStorage.getItem('strophe.caps._jidNodeIndex')) || {};
  141. jsxc.xmpp.conn.caps._jidVerIndex[from] = ver;
  142. _jidNodeIndex[from] = node;
  143. localStorage.setItem('strophe.caps._jidVerIndex', JSON.stringify(jsxc.xmpp.conn.caps._jidVerIndex));
  144. localStorage.setItem('strophe.caps._jidNodeIndex', JSON.stringify(_jidNodeIndex));
  145. }, Strophe.NS.CAPS);
  146. }
  147. jsxc.xmpp.conn.connect(jid, password || jsxc.options.xmpp.password, callback);
  148. }
  149. },
  150. /**
  151. * Logs user out of his xmpp session and does some clean up.
  152. *
  153. * @param {boolean} complete If set to false, roster will not be removed
  154. * @returns {Boolean}
  155. */
  156. logout: function(complete) {
  157. jsxc.storage.setUserItem('forcedLogout', true);
  158. jsxc.triggeredFromElement = (typeof complete === 'boolean') ? complete : true;
  159. if (!jsxc.master) {
  160. // instruct master
  161. jsxc.storage.removeItem('sid');
  162. // jsxc.xmpp.disconnected is called if master deletes alive after logout
  163. return true;
  164. }
  165. // REVIEW: this should maybe moved to xmpp.disconnected
  166. // clean up
  167. jsxc.storage.removeUserItem('windowlist');
  168. jsxc.storage.removeUserItem('unreadMsg');
  169. if (jsxc.gui.favicon) {
  170. jsxc.gui.favicon.badge(0);
  171. }
  172. // Hide dropdown menu
  173. $('body').click();
  174. if (!jsxc.xmpp.conn || !jsxc.xmpp.conn.authenticated) {
  175. return true;
  176. }
  177. // restore all otr objects
  178. $.each(jsxc.storage.getUserItem('otrlist') || {}, function(i, val) {
  179. jsxc.otr.create(val);
  180. });
  181. var numOtr = Object.keys(jsxc.otr.objects || {}).length + 1;
  182. var disReady = function() {
  183. if (--numOtr <= 0) {
  184. jsxc.xmpp.conn.flush();
  185. setTimeout(function() {
  186. jsxc.xmpp.conn.disconnect();
  187. }, 600);
  188. }
  189. };
  190. // end all private conversations
  191. $.each(jsxc.otr.objects || {}, function(key, obj) {
  192. if (obj.msgstate === OTR.CONST.MSGSTATE_ENCRYPTED) {
  193. obj.endOtr.call(obj, function() {
  194. obj.init.call(obj);
  195. jsxc.otr.backup(key);
  196. disReady();
  197. });
  198. } else {
  199. disReady();
  200. }
  201. });
  202. disReady();
  203. // Trigger real logout in jsxc.xmpp.disconnected()
  204. return false;
  205. },
  206. /**
  207. * Triggered if connection is established
  208. *
  209. * @private
  210. */
  211. connected: function() {
  212. jsxc.xmpp.conn.pause();
  213. jsxc.xmpp.initNewConnection();
  214. jsxc.xmpp.saveSessionParameter();
  215. var rosterVerSupport = $(jsxc.xmpp.conn.features).find('[xmlns="urn:xmpp:features:rosterver"]').length > 0;
  216. jsxc.storage.setUserItem('rosterVerSupport', rosterVerSupport);
  217. jsxc.storage.removeUserItem('forcedLogout');
  218. if (jsxc.options.loginForm.triggered) {
  219. switch (jsxc.options.loginForm.onConnected || 'submit') {
  220. case 'submit':
  221. jsxc.submitLoginForm();
  222. return;
  223. case false:
  224. return;
  225. }
  226. }
  227. // start chat
  228. jsxc.gui.dialog.close();
  229. jsxc.xmpp.conn.resume();
  230. jsxc.onMaster();
  231. jsxc.changeState(jsxc.CONST.STATE.READY);
  232. $(document).trigger('attached.jsxc');
  233. },
  234. /**
  235. * Triggered if connection is attached
  236. *
  237. * @private
  238. */
  239. attached: function() {
  240. $('#jsxc_roster').removeClass('jsxc_noConnection');
  241. Strophe.addNamespace('VERSION', 'jabber:iq:version');
  242. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onRosterChanged, 'jabber:iq:roster', 'iq', 'set');
  243. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onChatMessage, null, 'message', 'chat');
  244. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onErrorMessage, null, 'message', 'error');
  245. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onHeadlineMessage, null, 'message', 'headline');
  246. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onReceived, null, 'message');
  247. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onPresence, null, 'presence');
  248. jsxc.xmpp.conn.addHandler(jsxc.xmpp.onVersionRequest, Strophe.NS.VERSION, 'iq', 'get');
  249. jsxc.gui.init();
  250. var caps = jsxc.xmpp.conn.caps;
  251. var domain = jsxc.xmpp.conn.domain;
  252. if (caps) {
  253. var conditionalEnable = function() {};
  254. if (jsxc.options.get('carbons').enable) {
  255. conditionalEnable = function() {
  256. if (jsxc.xmpp.conn.caps.hasFeatureByJid(domain, jsxc.CONST.NS.CARBONS)) {
  257. jsxc.xmpp.carbons.enable();
  258. }
  259. };
  260. $(document).on('caps.strophe', function onCaps(ev, from) {
  261. if (from !== domain) {
  262. return;
  263. }
  264. conditionalEnable();
  265. $(document).off('caps.strophe', onCaps);
  266. });
  267. }
  268. if (typeof caps._knownCapabilities[caps._jidVerIndex[domain]] === 'undefined') {
  269. var _jidNodeIndex = JSON.parse(localStorage.getItem('strophe.caps._jidNodeIndex')) || {};
  270. jsxc.debug('Request server capabilities');
  271. caps._requestCapabilities(jsxc.xmpp.conn.domain, _jidNodeIndex[domain], caps._jidVerIndex[domain]);
  272. } else {
  273. // We know server caps
  274. conditionalEnable();
  275. }
  276. }
  277. var rosterLoaded = jsxc.storage.getUserItem('rosterLoaded');
  278. // Only load roaster if necessary
  279. if (rosterLoaded !== jsxc.xmpp.conn._proto.sid) {
  280. // in order to not overide existing presence information, we send
  281. // pres first after roster is ready
  282. $(document).one('cloaded.roster.jsxc', jsxc.xmpp.sendPres);
  283. $('#jsxc_roster > p:first').remove();
  284. var queryAttr = {
  285. xmlns: 'jabber:iq:roster'
  286. };
  287. if (jsxc.storage.getUserItem('rosterVerSupport')) {
  288. // @TODO check if we really cached the roster
  289. queryAttr.ver = jsxc.storage.getUserItem('rosterVer') || '';
  290. }
  291. var iq = $iq({
  292. type: 'get'
  293. }).c('query', queryAttr);
  294. jsxc.xmpp.conn.sendIQ(iq, jsxc.xmpp.onRoster);
  295. } else {
  296. jsxc.xmpp.sendPres();
  297. if (!jsxc.restoreCompleted) {
  298. jsxc.gui.restore();
  299. }
  300. }
  301. jsxc.xmpp.saveSessionParameter();
  302. jsxc.masterActions();
  303. jsxc.changeState(jsxc.CONST.STATE.READY);
  304. },
  305. saveSessionParameter: function() {
  306. var nomJid = Strophe.getBareJidFromJid(jsxc.xmpp.conn.jid).toLowerCase() + '/' + Strophe.getResourceFromJid(jsxc.xmpp.conn.jid);
  307. // Save sid and jid
  308. jsxc.storage.setItem('sid', jsxc.xmpp.conn._proto.sid);
  309. jsxc.storage.setItem('jid', nomJid);
  310. },
  311. initNewConnection: function() {
  312. jsxc.storage.removeUserItem('windowlist');
  313. jsxc.storage.removeUserItem('own');
  314. jsxc.storage.removeUserItem('avatar', 'own');
  315. jsxc.storage.removeUserItem('otrlist');
  316. jsxc.storage.removeUserItem('unreadMsg');
  317. jsxc.storage.removeUserItem('features');
  318. // reset user options
  319. jsxc.storage.removeUserElement('options', 'RTCPeerConfig');
  320. },
  321. /**
  322. * Sends presence stanza to server.
  323. */
  324. sendPres: function() {
  325. // disco stuff
  326. if (jsxc.xmpp.conn.disco) {
  327. jsxc.xmpp.conn.disco.addIdentity('client', 'web', 'JSXC');
  328. jsxc.xmpp.conn.disco.addFeature(Strophe.NS.DISCO_INFO);
  329. jsxc.xmpp.conn.disco.addFeature(Strophe.NS.RECEIPTS);
  330. jsxc.xmpp.conn.disco.addFeature(Strophe.NS.VERSION);
  331. }
  332. // create presence stanza
  333. var pres = $pres();
  334. if (jsxc.xmpp.conn.caps) {
  335. // attach caps
  336. pres.c('c', jsxc.xmpp.conn.caps.generateCapsAttrs()).up();
  337. }
  338. var presState = jsxc.storage.getUserItem('presence') || 'online';
  339. if (presState !== 'online') {
  340. pres.c('show').t(presState).up();
  341. }
  342. var priority = jsxc.options.get('priority');
  343. if (priority && typeof priority[presState] !== 'undefined' && parseInt(priority[presState]) !== 0) {
  344. pres.c('priority').t(priority[presState]).up();
  345. }
  346. jsxc.debug('Send presence', pres.toString());
  347. jsxc.xmpp.conn.send(pres);
  348. if (!jsxc.storage.getUserItem('features')) {
  349. jsxc.xmpp.conn.flush();
  350. var barJid = Strophe.getBareJidFromJid(jsxc.xmpp.conn.jid);
  351. jsxc.xmpp.conn.disco.info(barJid, undefined, function(stanza) {
  352. var features = $(stanza).find('feature').map(function() {
  353. return $(this).attr('var');
  354. });
  355. jsxc.storage.setUserItem('features', features.toArray());
  356. $(document).trigger('features.jsxc');
  357. });
  358. } else {
  359. $(document).trigger('features.jsxc');
  360. }
  361. },
  362. /**
  363. * Triggered if lost connection
  364. *
  365. * @private
  366. */
  367. disconnected: function() {
  368. jsxc.debug('disconnected');
  369. //jsxc.storage.removeItem('jid');
  370. jsxc.storage.removeItem('sid');
  371. jsxc.storage.removeItem('rid');
  372. jsxc.storage.removeItem('hidden');
  373. jsxc.storage.removeUserItem('avatar', 'own');
  374. jsxc.storage.removeUserItem('otrlist');
  375. jsxc.storage.removeUserItem('features');
  376. $(document).off('connected.jsxc', jsxc.xmpp.connected);
  377. $(document).off('attached.jsxc', jsxc.xmpp.attached);
  378. $(document).off('disconnected.jsxc', jsxc.xmpp.disconnected);
  379. $(document).off('connfail.jsxc', jsxc.xmpp.onConnfail);
  380. $(document).off('authfail.jsxc', jsxc.xmpp.onAuthFail);
  381. jsxc.xmpp.conn = null;
  382. $('#jsxc_windowList').remove();
  383. if (jsxc.triggeredFromElement) {
  384. $(document).trigger('toggle.roster.jsxc', ['hidden', 0]);
  385. jsxc.gui.roster.ready = false;
  386. $('#jsxc_roster').remove();
  387. // REVIEW: logoutElement without href attribute?
  388. if (jsxc.triggeredFromLogout) {
  389. window.location = jsxc.options.logoutElement.attr('href');
  390. }
  391. } else {
  392. jsxc.gui.roster.noConnection();
  393. }
  394. window.clearInterval(jsxc.keepaliveInterval);
  395. jsxc.role_allocation = false;
  396. jsxc.master = false;
  397. jsxc.storage.removeItem('alive');
  398. jsxc.changeState(jsxc.CONST.STATE.SUSPEND);
  399. },
  400. /**
  401. * Triggered on connection fault
  402. *
  403. * @param {String} condition information why we lost the connection
  404. * @private
  405. */
  406. onConnfail: function(ev, condition) {
  407. jsxc.debug('XMPP connection failed: ' + condition);
  408. if (jsxc.options.loginForm.triggered) {
  409. jsxc.submitLoginForm();
  410. }
  411. },
  412. /**
  413. * Triggered on auth fail.
  414. *
  415. * @private
  416. */
  417. onAuthFail: function() {
  418. if (jsxc.options.loginForm.triggered) {
  419. switch (jsxc.options.loginForm.onAuthFail || 'ask') {
  420. case 'ask':
  421. jsxc.gui.showAuthFail();
  422. break;
  423. case 'submit':
  424. jsxc.submitLoginForm();
  425. break;
  426. case 'quiet':
  427. case false:
  428. return;
  429. }
  430. }
  431. },
  432. /**
  433. * Triggered on initial roster load
  434. *
  435. * @param {dom} iq
  436. * @private
  437. */
  438. onRoster: function(iq) {
  439. jsxc.debug('Load roster', iq);
  440. jsxc.storage.setUserItem('rosterLoaded', jsxc.xmpp.conn._proto.sid);
  441. if ($(iq).find('query').length === 0) {
  442. jsxc.debug('Use cached roster');
  443. jsxc.restoreRoster();
  444. return;
  445. }
  446. var buddies = [];
  447. $(iq).find('item').each(function() {
  448. var jid = $(this).attr('jid');
  449. var name = $(this).attr('name') || jid;
  450. var bid = jsxc.jidToBid(jid);
  451. var sub = $(this).attr('subscription');
  452. buddies.push(bid);
  453. jsxc.storage.removeUserItem('res', bid);
  454. jsxc.storage.saveBuddy(bid, {
  455. jid: jid,
  456. name: name,
  457. status: 0,
  458. sub: sub,
  459. res: [],
  460. rnd: Math.random() // force storage event
  461. });
  462. jsxc.gui.roster.add(bid);
  463. });
  464. if (buddies.length === 0) {
  465. jsxc.gui.roster.empty();
  466. }
  467. jsxc.storage.setUserItem('buddylist', buddies);
  468. if ($(iq).find('query').attr('ver')) {
  469. jsxc.storage.setUserItem('rosterVer', $(iq).find('query').attr('ver'));
  470. }
  471. // load bookmarks
  472. jsxc.xmpp.bookmarks.load();
  473. jsxc.gui.roster.loaded = true;
  474. jsxc.debug('Roster loaded');
  475. $(document).trigger('cloaded.roster.jsxc');
  476. jsxc.changeUIState(jsxc.CONST.UISTATE.READY);
  477. },
  478. /**
  479. * Triggerd on roster changes
  480. *
  481. * @param {dom} iq
  482. * @returns {Boolean} True to preserve handler
  483. * @private
  484. */
  485. onRosterChanged: function(iq) {
  486. var iqSender = $(iq).attr('from');
  487. var ownBareJid = Strophe.getBareJidFromJid(jsxc.xmpp.conn.jid);
  488. if (iqSender && iqSender !== ownBareJid) {
  489. return true;
  490. }
  491. jsxc.debug('onRosterChanged', iq);
  492. // @REVIEW there should be only one item, according to RFC6121
  493. // https://xmpp.org/rfcs/rfc6121.html#roster-syntax-actions-push
  494. $(iq).find('item').each(function() {
  495. var jid = $(this).attr('jid');
  496. var name = $(this).attr('name') || jid;
  497. var bid = jsxc.jidToBid(jid);
  498. var sub = $(this).attr('subscription');
  499. // var ask = $(this).attr('ask');
  500. if (sub === 'remove') {
  501. jsxc.gui.roster.purge(bid);
  502. } else {
  503. var bl = jsxc.storage.getUserItem('buddylist');
  504. if (bl.indexOf(bid) < 0) {
  505. bl.push(bid); // (INFO) push returns the new length
  506. jsxc.storage.setUserItem('buddylist', bl);
  507. }
  508. var temp = jsxc.storage.saveBuddy(bid, {
  509. jid: jid,
  510. name: name,
  511. sub: sub
  512. });
  513. if (temp === 'updated') {
  514. jsxc.gui.update(bid);
  515. jsxc.gui.roster.reorder(bid);
  516. } else {
  517. jsxc.gui.roster.add(bid);
  518. }
  519. }
  520. // Remove pending friendship request from notice list
  521. if (sub === 'from' || sub === 'both') {
  522. var notices = jsxc.storage.getUserItem('notices');
  523. var noticeKey = null,
  524. notice;
  525. for (noticeKey in notices) {
  526. notice = notices[noticeKey];
  527. if (notice.fnName === 'gui.showApproveDialog' && notice.fnParams[0] === jid) {
  528. jsxc.debug('Remove notice with key ' + noticeKey);
  529. jsxc.notice.remove(noticeKey);
  530. }
  531. }
  532. }
  533. });
  534. if ($(iq).find('query').attr('ver')) {
  535. jsxc.storage.setUserItem('rosterVer', $(iq).find('query').attr('ver'));
  536. }
  537. if (!jsxc.storage.getUserItem('buddylist') || jsxc.storage.getUserItem('buddylist').length === 0) {
  538. jsxc.gui.roster.empty();
  539. } else {
  540. $('#jsxc_roster > p:first').remove();
  541. }
  542. // preserve handler
  543. return true;
  544. },
  545. /**
  546. * Triggered on incoming presence stanzas
  547. *
  548. * @param {dom} presence
  549. * @private
  550. */
  551. onPresence: function(presence) {
  552. /*
  553. * <presence xmlns='jabber:client' type='unavailable' from='' to=''/>
  554. *
  555. * <presence xmlns='jabber:client' from='' to=''> <priority>5</priority>
  556. * <c xmlns='http://jabber.org/protocol/caps'
  557. * node='http://psi-im.org/caps' ver='caps-b75d8d2b25' ext='ca cs
  558. * ep-notify-2 html'/> </presence>
  559. *
  560. * <presence xmlns='jabber:client' from='' to=''> <show>chat</show>
  561. * <status></status> <priority>5</priority> <c
  562. * xmlns='http://jabber.org/protocol/caps' node='http://psi-im.org/caps'
  563. * ver='caps-b75d8d2b25' ext='ca cs ep-notify-2 html'/> </presence>
  564. */
  565. jsxc.debug('onPresence', presence);
  566. var ptype = $(presence).attr('type');
  567. var from = $(presence).attr('from');
  568. var jid = Strophe.getBareJidFromJid(from).toLowerCase();
  569. var r = Strophe.getResourceFromJid(from);
  570. var bid = jsxc.jidToBid(jid);
  571. var data = jsxc.storage.getUserItem('buddy', bid) || {};
  572. var res = jsxc.storage.getUserItem('res', bid) || {};
  573. var status = null;
  574. var xVCard = $(presence).find('x[xmlns="vcard-temp:x:update"]');
  575. if (jid === Strophe.getBareJidFromJid(jsxc.storage.getItem("jid"))) {
  576. return true;
  577. }
  578. if (ptype === 'error') {
  579. $(document).trigger('error.presence.jsxc', [from, presence]);
  580. var error = $(presence).find('error');
  581. //@TODO display error message
  582. jsxc.error('[XMPP] ' + error.attr('code') + ' ' + error.find(">:first-child").prop('tagName'));
  583. return true;
  584. }
  585. // incoming friendship request
  586. if (ptype === 'subscribe') {
  587. var bl = jsxc.storage.getUserItem('buddylist');
  588. if (bl.indexOf(bid) > -1) {
  589. jsxc.debug('Auto approve contact request, because he is already in our contact list.');
  590. jsxc.xmpp.resFriendReq(jid, true);
  591. if (data.sub !== 'to') {
  592. jsxc.xmpp.addBuddy(jid, data.name);
  593. }
  594. return true;
  595. }
  596. jsxc.storage.setUserItem('friendReq', {
  597. jid: jid,
  598. approve: -1
  599. });
  600. jsxc.notice.add({
  601. msg: $.t('Friendship_request'),
  602. description: $.t('from') + ' ' + jid,
  603. type: 'contact'
  604. }, 'gui.showApproveDialog', [jid]);
  605. return true;
  606. } else if (ptype === 'unavailable' || ptype === 'unsubscribed') {
  607. status = jsxc.CONST.STATUS.indexOf('offline');
  608. } else {
  609. var show = $(presence).find('show').text();
  610. if (show === '') {
  611. status = jsxc.CONST.STATUS.indexOf('online');
  612. } else {
  613. status = jsxc.CONST.STATUS.indexOf(show);
  614. }
  615. }
  616. if (status === 0) {
  617. delete res[r];
  618. } else if (r) {
  619. res[r] = status;
  620. }
  621. var maxVal = [];
  622. var max = 0,
  623. prop = null;
  624. for (prop in res) {
  625. if (res.hasOwnProperty(prop)) {
  626. if (max <= res[prop]) {
  627. if (max !== res[prop]) {
  628. maxVal = [];
  629. max = res[prop];
  630. }
  631. maxVal.push(prop);
  632. }
  633. }
  634. }
  635. if (data.status === 0 && max > 0) {
  636. // buddy has come online
  637. jsxc.notification.notify({
  638. title: data.name,
  639. msg: $.t('has_come_online'),
  640. source: bid
  641. });
  642. }
  643. if (data.type !== 'groupchat') {
  644. data.status = max;
  645. }
  646. data.res = maxVal;
  647. data.jid = jid;
  648. // Looking for avatar
  649. if (xVCard.length > 0 && data.type !== 'groupchat') {
  650. var photo = xVCard.find('photo');
  651. if (photo.length > 0 && photo.text() !== data.avatar) {
  652. jsxc.storage.removeUserItem('avatar', data.avatar);
  653. data.avatar = photo.text();
  654. }
  655. }
  656. // Reset jid
  657. if (jsxc.gui.window.get(bid).length > 0) {
  658. jsxc.gui.window.get(bid).data('jid', jid);
  659. }
  660. jsxc.storage.setUserItem('buddy', bid, data);
  661. jsxc.storage.setUserItem('res', bid, res);
  662. jsxc.debug('Presence (' + from + '): ' + jsxc.CONST.STATUS[status]);
  663. jsxc.gui.update(bid);
  664. jsxc.gui.roster.reorder(bid);
  665. $(document).trigger('presence.jsxc', [from, status, presence]);
  666. // preserve handler
  667. return true;
  668. },
  669. /**
  670. * Triggered on incoming message stanzas
  671. *
  672. * @param {dom} presence
  673. * @returns {Boolean}
  674. * @private
  675. */
  676. onChatMessage: function(stanza) {
  677. var forwarded = $(stanza).find('forwarded[xmlns="' + jsxc.CONST.NS.FORWARD + '"]');
  678. var message, carbon;
  679. var originalSender = $(stanza).attr('from');
  680. if (forwarded.length > 0) {
  681. message = forwarded.find('> message');
  682. forwarded = true;
  683. carbon = $(stanza).find('> [xmlns="' + jsxc.CONST.NS.CARBONS + '"]');
  684. if (carbon.length === 0) {
  685. carbon = false;
  686. } else if (originalSender !== Strophe.getBareJidFromJid(jsxc.xmpp.conn.jid)) {
  687. // ignore this carbon copy
  688. return true;
  689. }
  690. jsxc.debug('Incoming forwarded message', message);
  691. } else {
  692. message = stanza;
  693. forwarded = false;
  694. carbon = false;
  695. jsxc.debug('Incoming message', message);
  696. }
  697. var body = $(message).find('body:first').text();
  698. var htmlBody = $(message).find('body[xmlns="' + Strophe.NS.XHTML + '"]');
  699. if (!body || (body.match(/\?OTR/i) && forwarded)) {
  700. return true;
  701. }
  702. var type = $(message).attr('type');
  703. var from = $(message).attr('from');
  704. var mid = $(message).attr('id');
  705. var bid;
  706. var delay = $(message).find('delay[xmlns="urn:xmpp:delay"]');
  707. var stamp = (delay.length > 0) ? new Date(delay.attr('stamp')) : new Date();
  708. stamp = stamp.getTime();
  709. if (carbon) {
  710. var direction = (carbon.prop("tagName") === 'sent') ? jsxc.Message.OUT : jsxc.Message.IN;
  711. bid = jsxc.jidToBid((direction === 'out') ? $(message).attr('to') : from);
  712. jsxc.gui.window.postMessage({
  713. bid: bid,
  714. direction: direction,
  715. msg: body,
  716. encrypted: false,
  717. forwarded: forwarded,
  718. stamp: stamp
  719. });
  720. return true;
  721. } else if (forwarded) {
  722. // Someone forwarded a message to us
  723. body = from + ' ' + $.t('to') + ' ' + $(stanza).attr('to') + '"' + body + '"';
  724. from = $(stanza).attr('from');
  725. }
  726. var jid = Strophe.getBareJidFromJid(from);
  727. bid = jsxc.jidToBid(jid);
  728. var data = jsxc.storage.getUserItem('buddy', bid);
  729. var request = $(message).find("request[xmlns='urn:xmpp:receipts']");
  730. if (data === null) {
  731. // jid not in roster
  732. var chat = jsxc.storage.getUserItem('chat', bid) || [];
  733. if (chat.length === 0) {
  734. jsxc.notice.add({
  735. msg: $.t('Unknown_sender'),
  736. description: $.t('You_received_a_message_from_an_unknown_sender_') + ' (' + bid + ').'
  737. }, 'gui.showUnknownSender', [bid]);
  738. }
  739. var msg = jsxc.removeHTML(body);
  740. msg = jsxc.escapeHTML(msg);
  741. var messageObj = new jsxc.Message({
  742. bid: bid,
  743. msg: msg,
  744. direction: jsxc.Message.IN,
  745. encrypted: false,
  746. forwarded: forwarded,
  747. stamp: stamp
  748. });
  749. messageObj.save();
  750. return true;
  751. }
  752. var win = jsxc.gui.window.init(bid);
  753. // If we now the full jid, we use it
  754. if (type === 'chat') {
  755. win.data('jid', from);
  756. jsxc.storage.updateUserItem('buddy', bid, {
  757. jid: from
  758. });
  759. }
  760. $(document).trigger('message.jsxc', [from, body]);
  761. // create related otr object
  762. if (jsxc.master && !jsxc.otr.objects[bid]) {
  763. jsxc.otr.create(bid);
  764. }
  765. if (!forwarded && mid !== null && request.length && data !== null && (data.sub === 'both' || data.sub === 'from') && type === 'chat') {
  766. // Send received according to XEP-0184
  767. jsxc.xmpp.conn.send($msg({
  768. to: from
  769. }).c('received', {
  770. xmlns: 'urn:xmpp:receipts',
  771. id: mid
  772. }));
  773. }
  774. var attachment;
  775. if (htmlBody.length === 1) {
  776. var httpUploadElement = htmlBody.find('a[data-type][data-name][data-size]');
  777. if (httpUploadElement.length === 1) {
  778. // deprecated syntax @since 3.2.1
  779. attachment = {
  780. type: httpUploadElement.attr('data-type'),
  781. name: httpUploadElement.attr('data-name'),
  782. size: httpUploadElement.attr('data-size'),
  783. };
  784. if (httpUploadElement.attr('data-thumbnail') && httpUploadElement.attr('data-thumbnail').match(/^\s*data:[a-z]+\/[a-z0-9-+.*]+;base64,[a-z0-9=+/]+$/i)) {
  785. attachment.thumbnail = httpUploadElement.attr('data-thumbnail');
  786. }
  787. if (httpUploadElement.attr('href') && httpUploadElement.attr('href').match(/^https:\/\//)) {
  788. attachment.data = httpUploadElement.attr('href');
  789. body = null;
  790. }
  791. if (!attachment.type.match(/^[a-z]+\/[a-z0-9-+.*]+$/i) || !attachment.name.match(/^[\s\w.,-]+$/i) || !attachment.size.match(/^\d+$/i)) {
  792. attachment = undefined;
  793. jsxc.warn('Invalid file type, name or size.');
  794. }
  795. } else if (htmlBody.find('>a').length === 1) {
  796. var linkElement = htmlBody.find('>a');
  797. var metaString = '';
  798. var thumbnail;
  799. if (linkElement.find('>img').length === 1) {
  800. var imgElement = linkElement.find('>img');
  801. var src = imgElement.attr('src') || '';
  802. var altString = imgElement.attr('alt') || '';
  803. metaString = altString.replace(/^Preview:/, '');
  804. if (src.match(/^\s*data:[a-z]+\/[a-z0-9-+.*]+;base64,[a-z0-9=+/]+$/i)) {
  805. thumbnail = src;
  806. }
  807. } else {
  808. metaString = linkElement.text();
  809. }
  810. var metaMatch = metaString.match(/^([a-z]+\/[a-z0-9-+.*]+)\|(\d+)\|([\s\w.,-]+)/);
  811. if (metaMatch) {
  812. attachment = {
  813. type: metaMatch[1],
  814. size: metaMatch[2],
  815. name: metaMatch[3],
  816. };
  817. if (thumbnail) {
  818. attachment.thumbnail = thumbnail;
  819. }
  820. if (linkElement.attr('href') && linkElement.attr('href').match(/^https?:\/\//)) {
  821. attachment.data = linkElement.attr('href');
  822. body = null;
  823. }
  824. } else {
  825. jsxc.warn('Invalid file type, name or size.');
  826. }
  827. }
  828. }
  829. if (jsxc.otr.objects.hasOwnProperty(bid) && body) {
  830. // @TODO check for file upload url after decryption
  831. jsxc.otr.objects[bid].receiveMsg(body, {
  832. _uid: mid,
  833. stamp: stamp,
  834. forwarded: forwarded,
  835. attachment: attachment
  836. });
  837. } else {
  838. jsxc.gui.window.postMessage({
  839. _uid: mid,
  840. bid: bid,
  841. direction: jsxc.Message.IN,
  842. msg: body,
  843. encrypted: false,
  844. forwarded: forwarded,
  845. stamp: stamp,
  846. attachment: attachment
  847. });
  848. }
  849. // preserve handler
  850. return true;
  851. },
  852. onErrorMessage: function(message) {
  853. var bid = jsxc.jidToBid($(message).attr('from'));
  854. if (jsxc.gui.window.get(bid).length === 0 || !$(message).attr('id')) {
  855. return true;
  856. }
  857. if ($(message).find('item-not-found').length > 0) {
  858. jsxc.gui.window.postMessage({
  859. bid: bid,
  860. direction: jsxc.Message.SYS,
  861. msg: $.t('message_not_send_item-not-found')
  862. });
  863. } else if ($(message).find('forbidden').length > 0) {
  864. jsxc.gui.window.postMessage({
  865. bid: bid,
  866. direction: jsxc.Message.SYS,
  867. msg: $.t('message_not_send_forbidden')
  868. });
  869. } else if ($(message).find('not-acceptable').length > 0) {
  870. jsxc.gui.window.postMessage({
  871. bid: bid,
  872. direction: jsxc.Message.SYS,
  873. msg: $.t('message_not_send_not-acceptable')
  874. });
  875. } else if ($(message).find('remote-server-not-found').length > 0) {
  876. jsxc.gui.window.postMessage({
  877. bid: bid,
  878. direction: jsxc.Message.SYS,
  879. msg: $.t('message_not_send_remote-server-not-found')
  880. });
  881. } else if ($(message).find('service-unavailable').length > 0) {
  882. if ($(message).find('[xmlns="' + Strophe.NS.CHATSTATES + '"]').length === 0) {
  883. jsxc.gui.window.postMessage({
  884. bid: bid,
  885. direction: jsxc.Message.SYS,
  886. msg: $.t('message_not_send_resource-unavailable')
  887. });
  888. }
  889. } else {
  890. jsxc.gui.window.postMessage({
  891. bid: bid,
  892. direction: jsxc.Message.SYS,
  893. msg: $.t('message_not_send')
  894. });
  895. }
  896. jsxc.debug('error message for ' + bid, $(message).find('error')[0]);
  897. return true;
  898. },
  899. /**
  900. * Process message stanzas of type headline.
  901. *
  902. * @param {String} stanza Message stanza of type headline
  903. * @return {Boolean}
  904. */
  905. onHeadlineMessage: function(stanza) {
  906. stanza = $(stanza);
  907. var from = stanza.attr('from');
  908. var domain = Strophe.getDomainFromJid(from);
  909. if (domain !== from) {
  910. if (!jsxc.storage.getUserItem('buddy', jsxc.jidToBid(from))) {
  911. return true;
  912. }
  913. } else if (domain !== Strophe.getDomainFromJid(jsxc.xmpp.conn.jid)) {
  914. return true;
  915. }
  916. var subject = stanza.find('subject:first').text() || $.t('Notification');
  917. var body = stanza.find('body:first').text();
  918. jsxc.notice.add({
  919. msg: subject,
  920. description: body,
  921. type: (domain === from) ? 'announcement' : null
  922. }, 'gui.showNotification', [subject, body, from]);
  923. return true;
  924. },
  925. /**
  926. * Respond to version request (XEP-0092).
  927. */
  928. onVersionRequest: function(stanza) {
  929. stanza = $(stanza);
  930. var from = stanza.attr('from');
  931. var id = stanza.attr('id');
  932. var iq = $iq({
  933. type: 'result',
  934. to: from,
  935. id: id
  936. }).c('query', {
  937. xmlns: Strophe.NS.VERSION
  938. }).c('name').t('JSXC').up()
  939. .c('version').t(jsxc.version);
  940. jsxc.xmpp.conn.sendIQ(iq);
  941. return true;
  942. },
  943. /**
  944. * Triggerd if the rid changed
  945. *
  946. * @param {integer} rid next valid request id
  947. * @private
  948. */
  949. onRidChange: function(rid) {
  950. jsxc.storage.setItem('rid', rid);
  951. },
  952. /**
  953. * response to friendship request
  954. *
  955. * @param {string} from jid from original friendship req
  956. * @param {boolean} approve
  957. */
  958. resFriendReq: function(from, approve) {
  959. if (jsxc.master) {
  960. jsxc.xmpp.conn.send($pres({
  961. to: from,
  962. type: (approve) ? 'subscribed' : 'unsubscribed'
  963. }));
  964. jsxc.storage.removeUserItem('friendReq');
  965. jsxc.gui.dialog.close();
  966. } else {
  967. jsxc.storage.updateUserItem('friendReq', 'approve', approve);
  968. }
  969. },
  970. /**
  971. * Add buddy to my friends
  972. *
  973. * @param {string} username jid
  974. * @param {string} alias
  975. */
  976. addBuddy: function(username, alias) {
  977. var bid = jsxc.jidToBid(username);
  978. if (jsxc.master) {
  979. // add buddy to roster (trigger onRosterChanged)
  980. var iq = $iq({
  981. type: 'set'
  982. }).c('query', {
  983. xmlns: 'jabber:iq:roster'
  984. }).c('item', {
  985. jid: username,
  986. name: alias || ''
  987. });
  988. jsxc.xmpp.conn.sendIQ(iq);
  989. // send subscription request to buddy (trigger onRosterChanged)
  990. jsxc.xmpp.conn.send($pres({
  991. to: username,
  992. type: 'subscribe'
  993. }));
  994. jsxc.storage.removeUserItem('add', bid);
  995. } else {
  996. jsxc.storage.setUserItem('add', bid, {
  997. username: username,
  998. alias: alias || null
  999. });
  1000. }
  1001. },
  1002. /**
  1003. * Remove buddy from my friends
  1004. *
  1005. * @param {type} jid
  1006. */
  1007. removeBuddy: function(jid) {
  1008. var bid = jsxc.jidToBid(jid);
  1009. // Shortcut to remove buddy from roster and cancle all subscriptions
  1010. var iq = $iq({
  1011. type: 'set'
  1012. }).c('query', {
  1013. xmlns: 'jabber:iq:roster'
  1014. }).c('item', {
  1015. jid: Strophe.getBareJidFromJid(jid),
  1016. subscription: 'remove'
  1017. });
  1018. jsxc.xmpp.conn.sendIQ(iq);
  1019. jsxc.gui.roster.purge(bid);
  1020. },
  1021. onReceived: function(stanza) {
  1022. var received = $(stanza).find("received[xmlns='urn:xmpp:receipts']");
  1023. if (received.length) {
  1024. var receivedId = received.attr('id');
  1025. var message = new jsxc.Message(receivedId);
  1026. message.received();
  1027. }
  1028. return true;
  1029. },
  1030. /**
  1031. * Public function to send message.
  1032. *
  1033. * @memberOf jsxc.xmpp
  1034. * @param bid css jid of user
  1035. * @param msg message
  1036. * @param uid unique id
  1037. */
  1038. sendMessage: function(message) {
  1039. var bid = message.bid;
  1040. var msg = message.msg;
  1041. var mucRoomNames = (jsxc.xmpp.conn.muc && jsxc.xmpp.conn.muc.roomNames) ? jsxc.xmpp.conn.muc.roomNames : [];
  1042. var isMucBid = mucRoomNames.indexOf(bid) >= 0;
  1043. if (jsxc.otr.objects.hasOwnProperty(bid) && !isMucBid) {
  1044. jsxc.otr.objects[bid].sendMsg(msg, message);
  1045. } else {
  1046. jsxc.xmpp._sendMessage(jsxc.gui.window.get(bid).data('jid'), msg, message);
  1047. }
  1048. },
  1049. /**
  1050. * Create message stanza and send it.
  1051. *
  1052. * @memberOf jsxc.xmpp
  1053. * @param jid Jabber id
  1054. * @param msg Message
  1055. * @param uid unique id
  1056. * @private
  1057. */
  1058. _sendMessage: function(jid, msg, message) {
  1059. // @TODO put jid into message object
  1060. var data = jsxc.storage.getUserItem('buddy', jsxc.jidToBid(jid)) || {};
  1061. var isBar = (Strophe.getBareJidFromJid(jid) === jid);
  1062. var type = data.type || 'chat';
  1063. message = message || {};
  1064. var xmlMsg = $msg({
  1065. to: jid,
  1066. type: type,
  1067. id: message._uid
  1068. });
  1069. if (message.type === jsxc.Message.HTML && msg === message.msg && message.htmlMsg) {
  1070. xmlMsg.c('body').t(msg);
  1071. xmlMsg.up().c('html', {
  1072. xmlns: Strophe.NS.XHTML_IM
  1073. }).c('body', {
  1074. xmlns: Strophe.NS.XHTML
  1075. }).h(message.htmlMsg).up();
  1076. } else {
  1077. xmlMsg.c('body').t(msg);
  1078. }
  1079. if (jsxc.xmpp.carbons.enabled && msg.match(/^\?OTR/)) {
  1080. xmlMsg.up().c("private", {
  1081. xmlns: jsxc.CONST.NS.CARBONS
  1082. });
  1083. }
  1084. if (msg.match(/^\?OTR/)) {
  1085. xmlMsg.up().c("no-permanent-store", {
  1086. xmlns: jsxc.CONST.NS.HINTS
  1087. });
  1088. }
  1089. if (type === 'chat' && (isBar || jsxc.xmpp.conn.caps.hasFeatureByJid(jid, Strophe.NS.RECEIPTS))) {
  1090. // Add request according to XEP-0184
  1091. xmlMsg.up().c('request', {
  1092. xmlns: 'urn:xmpp:receipts'
  1093. });
  1094. }
  1095. if (jsxc.xmpp.conn.chatstates && !jsxc.xmpp.chatState.isDisabled()) {
  1096. // send active event (XEP-0085)
  1097. xmlMsg.up().c('active', {
  1098. xmlns: Strophe.NS.CHATSTATES
  1099. });
  1100. }
  1101. jsxc.xmpp.conn.send(xmlMsg);
  1102. },
  1103. /**
  1104. * This function loads a vcard.
  1105. *
  1106. * @memberOf jsxc.xmpp
  1107. * @param bid
  1108. * @param cb
  1109. * @param error_cb
  1110. */
  1111. loadVcard: function(bid, cb, error_cb) {
  1112. if (jsxc.master) {
  1113. jsxc.xmpp.conn.vcard.get(cb, bid, error_cb);
  1114. } else {
  1115. jsxc.storage.setUserItem('vcard', bid, 'request:' + (new Date()).getTime());
  1116. $(document).one('loaded.vcard.jsxc', function(ev, result) {
  1117. if (result && result.state === 'success') {
  1118. cb($(result.data).get(0));
  1119. } else {
  1120. error_cb();
  1121. }
  1122. });
  1123. }
  1124. },
  1125. /**
  1126. * Retrieves capabilities.
  1127. *
  1128. * @memberOf jsxc.xmpp
  1129. * @param jid
  1130. * @returns List of known capabilities
  1131. */
  1132. getCapabilitiesByJid: function(jid) {
  1133. if (jsxc.xmpp.conn) {
  1134. return jsxc.xmpp.conn.caps.getCapabilitiesByJid(jid);
  1135. }
  1136. var jidVerIndex = JSON.parse(localStorage.getItem('strophe.caps._jidVerIndex')) || {};
  1137. var knownCapabilities = JSON.parse(localStorage.getItem('strophe.caps._knownCapabilities')) || {};
  1138. if (jidVerIndex[jid]) {
  1139. return knownCapabilities[jidVerIndex[jid]];
  1140. }
  1141. return null;
  1142. },
  1143. /**
  1144. * Test if jid has given features
  1145. *
  1146. * @param {string} jid Jabber id
  1147. * @param {string[]} feature Single feature or list of features
  1148. * @param {Function} cb Called with the result as first param.
  1149. * @return {boolean} True, if jid has all given features. Null, if we do not know it currently.
  1150. */
  1151. hasFeatureByJid: function(jid, feature, cb) {
  1152. var conn = jsxc.xmpp.conn;
  1153. cb = cb || function() {};
  1154. if (!feature) {
  1155. return false;
  1156. }
  1157. if (!$.isArray(feature)) {
  1158. feature = $.makeArray(feature);
  1159. }
  1160. var check = function(knownCapabilities) {
  1161. if (!knownCapabilities) {
  1162. return null;
  1163. }
  1164. var i;
  1165. for (i = 0; i < feature.length; i++) {
  1166. if (knownCapabilities['features'].indexOf(feature[i]) < 0) {
  1167. return false;
  1168. }
  1169. }
  1170. return true;
  1171. };
  1172. if (conn.caps._jidVerIndex[jid] && conn.caps._knownCapabilities[conn.caps._jidVerIndex[jid]]) {
  1173. var hasFeature = check(conn.caps._knownCapabilities[conn.caps._jidVerIndex[jid]]);
  1174. cb(hasFeature);
  1175. return hasFeature;
  1176. }
  1177. $(document).on('strophe.caps', function(ev, j, capabilities) {
  1178. if (j === jid) {
  1179. cb(check(capabilities));
  1180. $(document).off(ev);
  1181. }
  1182. });
  1183. return null;
  1184. }
  1185. };
  1186. /**
  1187. * Handle carbons (XEP-0280);
  1188. *
  1189. * @namespace jsxc.xmpp.carbons
  1190. */
  1191. jsxc.xmpp.carbons = {
  1192. enabled: false,
  1193. /**
  1194. * Enable carbons.
  1195. *
  1196. * @memberOf jsxc.xmpp.carbons
  1197. * @param cb callback
  1198. */
  1199. enable: function(cb) {
  1200. var iq = $iq({
  1201. type: 'set'
  1202. }).c('enable', {
  1203. xmlns: jsxc.CONST.NS.CARBONS
  1204. });
  1205. jsxc.xmpp.conn.sendIQ(iq, function() {
  1206. jsxc.xmpp.carbons.enabled = true;
  1207. jsxc.debug('Carbons enabled');
  1208. if (cb) {
  1209. cb.call(this);
  1210. }
  1211. }, function(stanza) {
  1212. jsxc.warn('Could not enable carbons', stanza);
  1213. });
  1214. },
  1215. /**
  1216. * Disable carbons.
  1217. *
  1218. * @memberOf jsxc.xmpp.carbons
  1219. * @param cb callback
  1220. */
  1221. disable: function(cb) {
  1222. var iq = $iq({
  1223. type: 'set'
  1224. }).c('disable', {
  1225. xmlns: jsxc.CONST.NS.CARBONS
  1226. });
  1227. jsxc.xmpp.conn.sendIQ(iq, function() {
  1228. jsxc.xmpp.carbons.enabled = false;
  1229. jsxc.debug('Carbons disabled');
  1230. if (cb) {
  1231. cb.call(this);
  1232. }
  1233. }, function(stanza) {
  1234. jsxc.warn('Could not disable carbons', stanza);
  1235. });
  1236. },
  1237. /**
  1238. * Enable/Disable carbons depending on options key.
  1239. *
  1240. * @memberOf jsxc.xmpp.carbons
  1241. * @param err error message
  1242. */
  1243. refresh: function(err) {
  1244. if (err === false) {
  1245. return;
  1246. }
  1247. if (jsxc.options.get('carbons').enable) {
  1248. return jsxc.xmpp.carbons.enable();
  1249. }
  1250. return jsxc.xmpp.carbons.disable();
  1251. }
  1252. };