2

I'm working on an angular web application that must support IE10. I need to make a cross domain call to our enterprise salesforce server. In chrome (which we don't support officially, but we all develop in) that call fails because chrome makes that OPTIONS preflight call to the salesforce server, which does not support CORS.

However, IE does not make a CORS preflight, so I assumed I would have no problems making this call. But I get an "Access is Denied." error thrown from deep inside the angular code.

Further digging reveals that the specific line in angular (v1.2.21) that is failing is:

xhr.open(method, url, true); (on line 8544 if you happen to have version 1.2.21).

In looking at github, google groups, and stack overflow threads, I see that the issue might be with the way IE wants to handle cross domain requests, specifically which xhr object is being invoked to make the call.

It seems that older versions of angular had this issue, but it was addressed by adding a function prior to the xhr.open() call to retrieve the correct XMLHttpRequest object for the version of IE that is running:

var xhr = createXhr(method);

xhr.open(method, url, true);
forEach(headers, function(value, key) {
  if (isDefined(value)) {
      xhr.setRequestHeader(key, value);
  }
});

So in theory, the correct xhr object is having its .open() method called. However for me, that line throws an "Access is Denied" error.

In the links above, it seems commonly suggested that instead of using the XMLHttpRequest object for cross domain calls, you have to use XDomainRequest(). Thinking it was unlikely that the angular folks missed this, I tried it anyway, just manually altering the code in the angular.js file to return that object for our specific salesforce call:

var xhr;
if (url.indexOf("salesforce.com") > -1) {
  xhr = new XDomainRequest();
}
else {
  xhr = createXhr(method);
}

xhr.open(method, url, true);
forEach(headers, function(value, key) {
  if (isDefined(value)) {
      xhr.setRequestHeader(key, value);
  }
});

Except now the line where the code tries to call xhr.setRequestHeader(key, value) fails. Does anyone know what the problem is? I have a hard time believing angular doesn't have a way to deal with cross domain calls in IE, so I imagine I'm just missing something.

Community
  • 1
  • 1
tengen
  • 2,125
  • 3
  • 31
  • 57
  • Are you mixing http and https? – epascarello Nov 05 '14 at 18:32
  • @epascarello Yes. Our address is `http://...`, and the salesforce address is `https://...` However, if I change their address to `http` I still get the same issue. Access is denied, no call actually made to salesforce. – tengen Nov 05 '14 at 19:22
  • 2
    And that would be your problem. http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx – epascarello Nov 05 '14 at 19:50
  • @tengen looks like an general CORS issue. Without CORS support, you won't be able to send an xhr quest to said server. – Kevin B Nov 05 '14 at 19:56
  • @epascarello That link is easily the best, most readable explanation of CORS I have seen, and I've seen my fair share. Thank you! +1! I see an update in the article that says **Update: Internet Explorer 10 now supports CORS using XMLHTTPRequest which should be preferred to the now-deprecated XDomainRequest object.** That explains why the XDomainRequest is a non-issue for me in IE10. But even when I am making a GET call to HTTP (removing the issue in #7 in that article... mixed http/https requests) I still have the same issue. – tengen Nov 05 '14 at 20:59
  • @KevinB I'm reading your comment to mean that without the salesforce server providing CORS support, I'm sunk. Am I reading you wrong? The call never even gets made, so I don't believe the issue is with CORS support on their end. – tengen Nov 05 '14 at 21:01
  • That would be correct. Without CORS support on the salesforce server, your only option is to make the request servers-side rather than client-side. (unless the salesforce server supports JSONP) – Kevin B Nov 05 '14 at 21:02
  • The issue is the fact that IE will not allow http to https with the XDomainRequest. They added security and made it "too" secure. Only solution[s] would be to either make a call to http or make your code to https or make a proxy on your end. – epascarello Nov 05 '14 at 21:08
  • @epascarello well, there is still something hinky, because even hitting http-to-http I get the error, and IE10 doesn't use XDomainRequest (or rather, angular corrctly uses XMLHttpRequest). However, I believe you are right, and that link was excellent. If you want to create an answer with that link, I'll accept it. Thanks for the help! – tengen Nov 06 '14 at 05:06

1 Answers1

1

While you specifically say IE10 here, I was having this same exact problem with IE8-9. My solution was to use the cross domain object window.XDomainRequest.

function loadCaptions() {

    //Use the XDomainRequest if it is defined (IE8-10), or when angular calls .open on the xhr request will receive "access denied" error.
    var url = $scope.captionsUrl;
    if (!!window.XDomainRequest) {
        var xdr = new window.XDomainRequest();
        if (xdr) {
            xdr.open("get", url);
            xdr.send();
        }

        return;
    }


//You folks on stackoverflow can ignore this part, just left in to show how I was requesting the captions leading to "access denied"
    $http.get($scope.captionsUrl)
        .success(function (captionsJson) {
            $scope.captionsList = captionsJson;
            createCaptionsMap();
        })
        .error(function (data, status, headers, config) {
            $cbtErrors.failedToLoadCaptions($scope.captionsUrl);
        });
}

EDIT:

Here is a more complete solution that includes memory management due to a bug in XDR requests and "on success"/"on error" call backs:

function loadCaptions() {
    //Use the XDomainRequest for IE8-9, or angular get request will recieve "access denied" error.
    var url = $scope.captionsUrl;

    if (!!window.XDomainRequest) {
        var xdr = new window.XDomainRequest();
        //See explination below why global.pendingXDR is set.  Cleaning up that memory here.
       var removeXDR = function(xdr) {

            //You will need a indexOf function defined for IE8.  See http://stackoverflow.com/questions/3629183/why-doesnt-indexof-work-on-an-array-ie8.
            var index = global.pendingXDR.indexOf(xdr);
            if (index >= 0) {
                global.pendingXDR.splice(index, 1);
            }
        };

        if (xdr) {
            //bind xdr.onload before sending the request (or the event does nothing).
            xdr.onload = function(){
                removeXDR(xdr);
                $scope.captionsList = xdr.responseText;
                createCaptionsMap();
            };
            xdr.onerror = function(){
                removeXDR(xdr);
                $cbtErrors.failedToLoadCaptions($scope.captionsUrl);
            };
            xdr.open("get", url);
            xdr.send();

            //In Internet Explorer 8/9, the XDomainRequest object is incorrectly subject to garbage collection after
            //send() has been called but not yet completed. The symptoms of this bug are the Developer Tools'
            //network trace showing "Aborted" for the requests and none of the error, timeout, or success event
            //handlers being called.
            //To correctly work around this issue, ensure the XDomainRequest is stored in a global variable until
            //the request completes.
            global.pendingXDR = [];
            global.pendingXDR.push(xdr);

        }

        return;
    }


//You folks on stackoverflow can ignore this part, just left in to show how I was requesting the captions leading to "access denied"
    $http.get($scope.captionsUrl)
        .success(function (captionsJson) {
            $scope.captionsList = captionsJson;
            createCaptionsMap();
        })
        .error(function (data, status, headers, config) {
            $cbtErrors.failedToLoadCaptions($scope.captionsUrl);
        });
}
tophstar
  • 75
  • 1
  • 9
  • Can I suggest that you add internet-explorer-8 and 9 tags to this? As well an including in the title internet explorer and cors. These things would have helped me find the question much faster. – tophstar Jun 05 '15 at 17:57