Source: jsxc.lib.xmpp.mam.js

/**
 * Implements XEP-0313: Message Archive Management.
 *
 * @namespace jsxc.xmpp.mam
 * @see {@link https://xmpp.org/extensions/xep-0313.html}
 */
jsxc.xmpp.mam = {
   conn: null
};

jsxc.xmpp.mam.init = function() {
   var self = jsxc.xmpp.mam;

   self.conn = jsxc.xmpp.conn;
};

jsxc.xmpp.mam.isEnabled = function() {
   var mamOptions = jsxc.options.get('mam') || {};

   var features = jsxc.storage.getUserItem('features') || [];
   var hasFeatureMam2 = features.indexOf(Strophe.NS.MAM) >= 0;

   return hasFeatureMam2 && mamOptions.enable;
};

jsxc.xmpp.mam.nextMessages = function(bid) {
   var self = jsxc.xmpp.mam;
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};
   var lastArchiveUid = buddyData.lastArchiveUid;
   var queryId = self.conn.getUniqueId();
   var mamOptions = jsxc.options.get('mam') || {};
   var history = jsxc.storage.getUserItem('history', bid) || [];

   if (buddyData.archiveExhausted) {
      jsxc.debug('No more archived messages.');
      return;
   }

   var queryOptions = {
      queryid: queryId,
      before: lastArchiveUid || '',
      with: bid,
      onMessage: function() {
         var args = Array.from(arguments);
         args.unshift(bid);
         self.onMessage.apply(this, args);
         return true;
      },
      onComplete: function() {
         var args = Array.from(arguments);
         args.unshift(bid);
         self.onComplete.apply(this, args);
         return true;
      }
   };

   var oldestMessageId = history[history.length - 1];

   if (oldestMessageId && !lastArchiveUid) {
      var oldestMessage = new jsxc.Message(oldestMessageId);
      queryOptions.end = (new Date(oldestMessage.stamp)).toISOString();
   }

   if (mamOptions.max) {
      queryOptions.max = mamOptions.max;
   }

   self.conn.mam.query(undefined, queryOptions);
};

jsxc.xmpp.mam.onMessage = function(bid, stanza) {
   stanza = $(stanza);
   var result = stanza.find('result[xmlns="' + Strophe.NS.MAM + '"]');
   var queryId = result.attr('queryid');

   if (result.length !== 1) {
      return;
   }

   var forwarded = result.find('forwarded[xmlns="' + jsxc.CONST.NS.FORWARD + '"]');
   var message = forwarded.find('message');
   var messageId = $(message).attr('id');

   if (message.length !== 1) {
      return;
   }

   var from = message.attr('from');
   var to = message.attr('to');

   if (jsxc.jidToBid(from) !== bid && jsxc.jidToBid(to) !== bid) {
      return;
   }

   var delay = forwarded.find('delay[xmlns="urn:xmpp:delay"]');
   var stamp = (delay.length > 0) ? new Date(delay.attr('stamp')) : new Date();
   stamp = stamp.getTime();

   var body = $(message).find('body:first').text();

   if (!body || body.match(/\?OTR/i)) {
      return true;
   }

   var direction = (jsxc.jidToBid(to) === bid) ? jsxc.Message.OUT : jsxc.Message.IN;

   var win = jsxc.gui.window.get(bid);
   var textarea = win.find('.jsxc_textarea');
   if (textarea.find('[id="' + messageId + '"]').length === 0) {
      var pseudoChatElement = $('<div>');
      pseudoChatElement.attr('id', messageId.replace(/:/g, '-'));
      pseudoChatElement.attr('data-queryId', queryId);

      var lastMessage = textarea.find('[data-queryId="' + queryId + '"]').last();
      var history = jsxc.storage.getUserItem('history', bid) || [];

      if (history.indexOf(messageId) < 0) {
         if (lastMessage.length === 0) {
            textarea.prepend(pseudoChatElement);
            history.push(messageId);
         } else {
            lastMessage.after(pseudoChatElement);
            history.splice(history.indexOf(lastMessage.attr('id').replace(/-/g, ':')), 0, messageId);
         }
      }

      jsxc.storage.setUserItem('history', bid, history);
   }

   jsxc.gui.window.postMessage({
      _uid: messageId,
      bid: bid,
      direction: direction,
      msg: body,
      encrypted: false,
      forwarded: true,
      stamp: stamp
   });
};

jsxc.xmpp.mam.onComplete = function(bid, stanza) {
   stanza = $(stanza);
   var fin = stanza.find('fin[xmlns="' + Strophe.NS.MAM + '"]');
   var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};
   var win = jsxc.gui.window.get(bid);

   buddyData.archiveExhausted = fin.attr('complete') === 'true';
   buddyData.lastArchiveUid = fin.find('first').text();

   if (buddyData.archiveExhausted) {
      win.find('.jsxc_fade').removeClass('jsxc_mam-enable');
   }

   jsxc.storage.setUserItem('buddy', bid, buddyData);
};

jsxc.xmpp.mam.initWindow = function(ev, win) {
   var self = jsxc.xmpp.mam;

   if (!jsxc.xmpp.conn && jsxc.master) {
      $(document).one('attached.jsxc', function() {
         self.initWindow(null, win);
      });
      return;
   }

   if (!jsxc.master) {
      return;
   }

   $(document).on('features.jsxc', function() {
      jsxc.xmpp.mam.addLoadButton(win);
   });

   var features = jsxc.storage.getUserItem('features');
   if (features !== null) {
      // features.jsxc was already fired
      jsxc.xmpp.mam.addLoadButton(win);
   }
};

jsxc.xmpp.mam.addLoadButton = function(win) {
   if (!jsxc.xmpp.mam.isEnabled()) {
      return;
   }

   var classNameShow = 'jsxc_show';
   var classNameMamEnable = 'jsxc_mam-enable';
   var bid = win.attr('data-bid');

   var element = $('<div>');
   element.addClass('jsxc_mam-load-more');
   element.appendTo(win.find('.slimScrollDiv'));
   element.click(function() {
      jsxc.xmpp.mam.nextMessages(bid);
   });
   element.text($.t('Load_older_messages'));

   win.find('.jsxc_textarea').scroll(function() {
      var buddyData = jsxc.storage.getUserItem('buddy', bid) || {};

      if (this.scrollTop < 42 && !buddyData.archiveExhausted) {
         element.addClass(classNameShow);
      } else {
         element.removeClass(classNameShow);
      }

      if (!buddyData.archiveExhausted) {
         win.find('.jsxc_fade').addClass(classNameMamEnable);
      }
   });

   win.find('.jsxc_textarea').scroll();
};

$(document).on('attached.jsxc', jsxc.xmpp.mam.init);
$(document).on('init.window.jsxc', jsxc.xmpp.mam.initWindow);