Salesforce Streaming API and IE 11

The Salesforce Streaming API is a wonderful tool that can help you create UIs that stay up to date with your data in real time. You also might find that it mysteriously doesn’t work in IE11, which Salesforce still officially supports on the platform.

Problem 1: Salesforce documentation is horribly out of date

The official documentation has you installing CometD version 2.2 (from 2011) against Salesforce API version 24. This version is way to old to work with IE. I found a blog post from 2014 that determined you need at least 2.9

In addition, as of Summer 17, Salesforce only supports CometD 3.0.9 or later so I recommend using that or the latest version 3.1.1 from download.cometd.org as I did.

Problem 2: CometD folder structure has changed

  • jQuery is no longer included in the CometD download. Grab the latest jQuery from jquery.com. You need at least 3.1.0 but I used 3.2.1
  • json2.js is no longer included in the CometD download. Theoretically this is no longer needed as it is only for IE8 and below and SF only supports IE11 so ignore it.
  • For CometD you only need two files but they need to be extracted first. Extract the CometD download then go to cometd-javascript/jquery/target right click the .war file and “open with” an archive utility. You need js/cometd/cometd.js and js/jquery/jquery.cometd.js from the resulting files

Problem 3: CometD defaults to WebSockets

Salesforce doesn’t support WebSockets. CometD is supposed to detect this failure and fall back to https, however, it does so by attampting the WebSocket connection, catching the failure, and re-trying with https. Very inefficient and also means that the example code from the Streaming API Developer Guide will not work as is. There are two fixes to make sure you have a robust connection, only one is required but the combination builds in some efficiency and reliability so I highly reccoment implementing both fixes.

Fix 1

Add the line

$.cometd.websocketEnabled = false;

before calling $.cometd.init to disable WebSockets right off the bat. This will prevent CometD from even attempting the pointless WebSockets call.

Fix 2

Do your subscribes only after the handshake has succeeded by using callbacks instead of syncronous calls like the Salesforce documented example has you do. To do this, you need to split the legacy init call into configure and handshake with a callback.

  • $.comet.init should simply change to $.cometd.configure, all parameters can stay exactly as they are.
  • Wrap your subscribe call the handshake callback
 $.cometd.handshake(function(handshakeReply) { 
    if(handshakeReply.successful) { 
      SUBSCRIBE GOES HERE 
    }
  });

Problem 4: VisualForce overrides XMLHttpRequest with IServerXMLHTTPRequest2 in IE only

This is a problem because IServerXMLHTTPRequest2 is missing attributes used by the jQuery $.ajax calls.

This one took me forever to find as I had to first use IE debugger with breakpoints to even find out that the object substitution was taking place and then IServerXMLHTTPRequest2 + anything in a search engine returns no results. Finally, I found this forum post. Luckily the fix is easy, just add the following code block before making any $.CometD calls

/* Override the jquery xhr/XMLHttpRequest generation
    This is needed to support IE 11 in VisualForce due to Salesforce 
    overriding XMLHttpRequest and breaking the ajax call
    SOURCE: https://developer.salesforce.com/forums/?id=906F0000000AjLtIAK
    */
  $.ajaxSetup({
    xhr: function() {
      try {
        var request = null;
        if(Sarissa.originalXMLHttpRequest) {
            request = new Sarissa.originalXMLHttpRequest();
        } else if (window.XMLHttpRequest) {
            request = new XMLHttpRequest();
        }
        return request;
      } catch ( e ) {}
    }
  });

Putting it all together

Lets take the Salesforce example

$(document).ready(function() {
    // Connect to the CometD endpoint
    $.cometd.init({
       url: window.location.protocol+'//'+window.location.hostname+'/cometd/24.0/',
       requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}'}
   });

   // Subscribe to a topic. JSON-encoded update will be returned
   // in the callback
   $.cometd.subscribe('/topic/InvoiceStatementUpdates', function(message) {
       $('#content').append('<p>Notification: ' +
            'Channel: ' + JSON.stringify(message.channel) + '<br>' +
            'Record name: ' + JSON.stringify(message.data.sobject.Name) +
            '<br>' + 'ID: ' + JSON.stringify(message.data.sobject.Id) + 
            '<br>' + 'Event type: ' + JSON.stringify(message.data.event.type)+
            '<br>' + 'Created: ' + JSON.stringify(message.data.event.createdDate) + 
            '</p>');
    });
});

This would become

$(document).ready(function() {
  /* Override the jquery xhr/XMLHttpRequest generation
      This is needed to support IE 11 in VisualForce due to Salesforce 
      overriding XMLHttpRequest and breaking the ajax call
      SOURCE: https://developer.salesforce.com/forums/?id=906F0000000AjLtIAK
      */
    $.ajaxSetup({
      xhr: function() {
        try {
          var request = null;
          if(Sarissa.originalXMLHttpRequest) {
              request = new Sarissa.originalXMLHttpRequest();
          } else if (window.XMLHttpRequest) {
              request = new XMLHttpRequest();
          }
          return request;
        } catch ( e ) {}
      }
    });

    //Don't use WebSockets as they aren't supported
    $.cometd.websocketEnabled = false;

    // Configure to the CometD endpoint
    $.cometd.configure({
       url: window.location.protocol+'//'+window.location.hostname+'/cometd/24.0/',
       requestHeaders: { Authorization: 'OAuth {!$Api.Session_ID}'}
    });

    // Subscribe in the handshake callback only if successfull
    $.cometd.handshake(function(handshakeReply) { 
      if(handshakeReply.successful) { 
        $.cometd.subscribe('/topic/InvoiceStatementUpdates', function(message) {
          $('#content').append('<p>Notification: ' +
              'Channel: ' + JSON.stringify(message.channel) + '<br>' +
              'Record name: ' + JSON.stringify(message.data.sobject.Name) +
              '<br>' + 'ID: ' + JSON.stringify(message.data.sobject.Id) + 
              '<br>' + 'Event type: ' + JSON.stringify(message.data.event.type)+
              '<br>' + 'Created: ' + JSON.stringify(message.data.event.createdDate) + 
              '</p>');
        });
      }
    });
});

I hope this saves some of you some huge headaches when using the Streaming API with IE11.

#blog #mavens