Source: jsxc.lib.xmpp.httpUpload.js

/**
 * Implements Http File Upload (XEP-0363)
 *
 * @namespace jsxc.xmpp.httpUpload
 * @see {@link http://xmpp.org/extensions/xep-0363.html}
 */
jsxc.xmpp.httpUpload = {
   conn: null,

   ready: false,

   CONST: {
      NS: {
         HTTPUPLOAD: 'urn:xmpp:http:upload'
      }
   }
};

/**
 * Set up http file upload.
 *
 * @memberOf jsxc.xmpp.httpUpload
 * @param  {Object} o options
 */
jsxc.xmpp.httpUpload.init = function(o) {
   var self = jsxc.xmpp.httpUpload;
   self.conn = jsxc.xmpp.conn;

   var fileTransferOptions = jsxc.options.get('fileTransfer') || {};
   var options = o || jsxc.options.get('httpUpload');

   if (!fileTransferOptions.httpUpload.enable) {
      jsxc.debug('http upload disabled');

      jsxc.options.set('httpUpload', false);

      return;
   }

   if (options && options.server) {
      self.ready = true;

      return;
   }

   var caps = jsxc.xmpp.conn.caps;
   var domain = jsxc.xmpp.conn.domain;

   if (!caps || !domain || typeof caps._knownCapabilities[caps._jidVerIndex[domain]] === 'undefined') {
      jsxc.debug('Waiting for server capabilities');

      $(document).on('caps.strophe', function onCaps(ev, from) {

         if (from !== domain) {
            return;
         }

         self.init();

         $(document).off('caps.strophe', onCaps);
      });

      return;
   }

   self.discoverUploadService();
};

/**
 * Discover upload service for http upload.
 *
 * @memberOf jsxc.xmpp.httpUpload
 */
jsxc.xmpp.httpUpload.discoverUploadService = function() {
   var self = jsxc.xmpp.httpUpload;
   var domain = self.conn.domain;

   jsxc.debug('discover http upload service');

   if (jsxc.xmpp.conn.caps.hasFeatureByJid(domain, self.CONST.NS.HTTPUPLOAD)) {
      self.queryItemForUploadService(domain);
   }

   self.conn.disco.items(domain, null, function(items) {
      $(items).find('item').each(function() {
         var jid = $(this).attr('jid');

         if (self.ready) {
            // abort, because we already found a service
            return false;
         }

         self.queryItemForUploadService(jid);
      });
   });
};

/**
 * Query item for upload service.
 *
 * @param {String} jid
 * @param {Function} cb Callback on success
 * @memberOf jsxc.xmpp.httpUpload
 */
jsxc.xmpp.httpUpload.queryItemForUploadService = function(jid, cb) {
   var self = jsxc.xmpp.httpUpload;

   jsxc.debug('query ' + jid + ' for upload service');

   self.conn.disco.info(jid, null, function(info) {
      var httpUploadFeature = $(info).find('feature[var="' + self.CONST.NS.HTTPUPLOAD + '"]');
      var httpUploadMaxSize = $(info).find('field[var="max-file-size"]');

      if (httpUploadFeature.length > 0) {
         jsxc.debug('http upload service found on ' + jid);

         jsxc.options.set('httpUpload', {
            server: jid,
            name: $(info).find('identity').attr('name'),
            maxSize: parseInt(httpUploadMaxSize.text()) || -1
         });

         self.ready = true;

         if (typeof cb === 'function') {
            cb.call(info);
         }
      }
   });
};

/**
 * Upload file and send link to peer.
 *
 * @memberOf jsxc.xmpp.httpUpload
 * @param  {File} file
 * @param  {Message} message Preview message
 */
jsxc.xmpp.httpUpload.sendFile = function(file, message) {
   jsxc.debug('Send file via http upload');

   var self = jsxc.xmpp.httpUpload;

   // even if the link is encrypted the file isn't
   message.encrypted = false;

   self.requestSlot(file, function(data) {
      if (!data) {
         // general error
         jsxc.warn('Unknown error occured. Please check the debug log.');
      } else if (data.error) {
         // specific error
         jsxc.warn('The xmpp server responded with an error of the type "' + data.error.type + '"');

         message.getDOM().remove();

         jsxc.gui.window.postMessage({
            bid: message.bid,
            direction: jsxc.Message.SYS,
            msg: data.error.text
         });

         message.delete();
      } else if (data.get && data.put) {
         jsxc.debug('slot received, start upload to ' + data.put);

         self.uploadFile(data.put, file, message, function() {
            var attachment = message.attachment;
            var metaString = attachment.type + '|' + attachment.size + '|' + attachment.name;
            var a = $('<a>');
            a.attr('href', data.get);

            attachment.data = data.get;

            if (attachment.thumbnail) {
               var img = $('<img>');
               img.attr('alt', 'Preview:' + metaString);
               img.attr('src', attachment.thumbnail);
               a.prepend(img);
            } else {
               a.text(metaString);
            }

            message.msg = data.get;
            message.htmlMsg = $('<span>').append(a).html();
            message.type = jsxc.Message.HTML;
            jsxc.gui.window.postMessage(message);
         });
      }
   });
};

/**
 * Upload the given file to the given url.
 *
 * @memberOf jsxc.xmpp.httpUpload
 * @param  {String} url upload url
 * @param  {File} file
 * @param  {Message} message preview message
 * @param  {Function} success_cb callback on successful transition
 */
jsxc.xmpp.httpUpload.uploadFile = function(url, file, message, success_cb) {
   $.ajax({
      url: url,
      type: 'PUT',
      contentType: 'application/octet-stream',
      data: file,
      processData: false,
      xhr: function() {
         var xhr = $.ajaxSettings.xhr();

         // track upload progress
         xhr.upload.onprogress = function(ev) {
            if (ev.lengthComputable) {
               jsxc.gui.window.updateProgress(message, ev.loaded, ev.total);
            }
         };
         return xhr;
      },
      success: function() {
         jsxc.debug('file successful uploaded');

         // In case that upload progress is not available, inform user
         jsxc.gui.window.updateProgress(message, 1, 1);

         if (success_cb) {
            success_cb();
         }
      },
      error: function() {
         jsxc.warn('error while uploading file to ' + url);

         message.error = 'Could not upload file';
         jsxc.gui.window.postMessage(message);
      }
   });
};

/**
 * Request upload slot.
 *
 * @memberOf jsxc.xmpp.httpUpload
 * @param  {File} file
 * @param  {Function} cb Callback after finished request
 */
jsxc.xmpp.httpUpload.requestSlot = function(file, cb) {
   var self = jsxc.xmpp.httpUpload;
   var options = jsxc.options.get('httpUpload');

   if (!options || !options.server) {
      jsxc.warn('could not request upload slot, because I am not aware of a server or http upload is disabled');

      return;
   }

   var iq = $iq({
         to: options.server,
         type: 'get'
      }).c('request', {
         xmlns: self.CONST.NS.HTTPUPLOAD
      }).c('filename').t(file.name)
      .up()
      .c('size').t(file.size);

   self.conn.sendIQ(iq, function(stanza) {
      self.successfulRequestSlotCB(stanza, cb);
   }, function(stanza) {
      self.failedRequestSlotCB(stanza, cb);
   });
};

/**
 * Process successful response to slot request.
 *
 * @memberOf jsxc.xmpp.httpUpload
 * @param {String} stanza
 * @param {Function} cb
 */
jsxc.xmpp.httpUpload.successfulRequestSlotCB = function(stanza, cb) {
   var self = jsxc.xmpp.httpUpload;
   var slot = $(stanza).find('slot[xmlns="' + self.CONST.NS.HTTPUPLOAD + '"]');

   if (slot.length > 0) {
      var put = slot.find('put').text();
      var get = slot.find('get').text();

      cb({
         put: put,
         get: get
      });
   } else {
      self.failedRequestSlotCB(stanza, cb);
   }
};

/**
 * Process failed response to slot request.
 *
 * @memberOf jsxc.xmpp.httpUpload
 * @param  {String} stanza
 * @param  {Function} cb
 */
jsxc.xmpp.httpUpload.failedRequestSlotCB = function(stanza, cb) {
   if ($(stanza).find('error').length <= 0) {
      jsxc.warn('response does not contain a slot element');

      cb();

      return;
   }

   var error = {
      type: $(stanza).find('error').attr('type') || 'unknown',
      text: $(stanza).find('error text').text()
   };

   if ($(stanza).find('error not-acceptable')) {
      error.reason = 'not-acceptable';
   } else if ($(stanza).find('error resource-constraint')) {
      error.reason = 'resource-constraint';
   } else if ($(stanza).find('error not-allowed')) {
      error.reason = 'not-allowed';
   }

   cb({
      error: error
   });
};

$(document).on('stateUIChange.jsxc', function(ev, state) {
   if (state === jsxc.CONST.UISTATE.INITIATING) {
      jsxc.xmpp.httpUpload.init();
   }
});