IE 11 retrieving old version of the object with web sockets via Stompjs within ReactJS with Spring Boot/Spring Data REST
<p>I originally thought I could not get <code>web sockets</code> working under IE 11.  Now after adding a whole lot of debugging details I have noticed that the sockets are being called correctly. </p>

<p>The problem is that somehow IE is returning old data.   I notice that after updating an object in the application with IE 11 that whenever it goes to retrieve that object again it will not get the latest copy.  The ETag in the HTTP Header is set to the original version of the object not the updated one.</p>

<p>Here is my example:</p>

<p>1) Go to the main application page <a href="http://localhost:8081" rel="nofollow">http://localhost:8081</a>
This page loads the user page by default.  This page will use <code>ReactJS</code> to load the <code>User</code> objects from my <code>Spring</code> application.<br>
2) Update the user's description, from <code>UpdateNow</code> to <code>changed1</code>.<br>
3) This trigges an update and the websocket to update the user object in the database.  </p>

<p><strong>Problem</strong>
When I reload the page in IE it shows the old data.  Even by closing the browser and opening a new one it continues to show the old data.  When enabling IE debugging I can see the ETag in the header is set to the original not the updated version.</p>

<pre><code>    componentDidMount() {
    log("calling componentDidMount");
    this.loadFromServer(this.state.pageSize);
</code></pre>

<p>In IE the following is returned:</p>

<pre><code>loadFromServer for type users
Opening Web Socket...
loadFromServer::: objectCollection:
 [object Object]{entity: Object {...}, headers: Object {...}, raw: XMLHttpRequest {...}.....
loadFromServer::: objects:
</code></pre>

<p>Here is where the old data is loaded inside the objects array for the first user:</p>

<pre><code> firstName: Aaron
 lastName: Smith
 description: UpdateNow (this is old in the db it is currently set to changed1)
</code></pre>

<p>If we do the same in Chrome the above steps are the same but we get the current User object</p>

<pre><code>description: "changed1"
firstName    : "Aaron"
lastName    :    "Smith"
</code></pre>

<p>I can further tell that my IE data is incorrect because when I try to make a change to that user I will get an 412 precondition failed status code when I go to do a HTTP PUT for a update for this user telling me the copy is stale.</p>

<p><strong>Problem 2</strong>
I can update the object in Chrome for a User.  When I do this I can see IE update its version of the objects in the console but it continues to retrieve the older version and not the new version of the object.</p>

<p><strong>Question</strong></p>

<p>Why is IE getting this old data?  It looks like it is somehow cached within it.  I have turned off history in the browser, I have deleted everything and even closed and reopened the browser but it continually retrieves the old data.  Even when I do CTRL-F5 it will keep the old data.</p>

<p>Another odd thing I notice is that I can not see the Network Body Request/Response from within IE using the console debug.  It comes up as <code>This resource has no response payload data</code> even though I can see the headers get a HTTP 200 when retrieving the user at <a href="http://localhost:8081/api/users/1" rel="nofollow">http://localhost:8081/api/users/1</a>.  I can view the headers and they show me that the ETag is out of date for IE using the original ETag and not the new one.</p>

<p>If I view the request and response in another client it looks fine.  I'm wanting to know how/why IE is somehow continuing to access the old data.</p>

<p>Thank you</p>

<p><strong>Code/Setup:</strong></p>

<p>app.js </p>

<pre><code>const stompClient = require('./websocket-listener');
</code></pre>

<p>..... (other functions)</p>

<pre><code>loadFromServer(pageSize, newObjectType) {
    console.log("loadFromServer for type " + this.state.objectType);

    var currentObjectType = this.state.objectType;

    // check to see if we are refreshing the page for a new object type
    if (newObjectType != null) {
      log("setting new objectType of " + newObjectType);
      // set the new object type for the page and refresh the results with that tyupe
      currentObjectType = newObjectType;
    }

    follow(client, root, [
        {rel: currentObjectType, params: {size: pageSize}}]
    ).then(objectCollection =&gt; {
        console.log("loadFromServer::: objectCollection:")
        console.log(objectCollection)
        return client({
          method: 'GET',
          path: objectCollection.entity._links.profile.href,
          headers: {'Accept': 'application/schema+json'}
        }).then(schema =&gt; {
          this.schema = schema.entity;
          this.links = objectCollection.entity._links;
          log("loadFromServer::: schema:")
          log(schema)
          return objectCollection;
        });
    }).then(objectCollection =&gt; {
      this.page = objectCollection.entity.page;
      log("loadFromServer::: objectCollection 2:")
      log(objectCollection)
      return this.getObjectMap(currentObjectType, objectCollection);
    }).then(objectPromises =&gt; {
      log("loadFromServer::: objectPromises:")
      log(objectPromises)
      return when.all(objectPromises);
    }).done(objects =&gt; {
      console.log("loadFromServer::: objects:")
      console.log(objects)
      this.setState({
        page: this.page,
        objects: objects,
        attributes: Object.keys(this.schema.properties),
        pageSize: pageSize,
        links: this.links,
        objectType: currentObjectType
      });
    });
  }


componentDidMount() {
    log("calling componentDidMount");
    this.loadFromServer(this.state.pageSize);

    // TODO add web socket callbacks for all entity types
    stompClient.register([
      {route: '/topic/newUser', callback: this.refreshAndGoToLastPage},
      {route: '/topic/updateUser', callback: this.refreshCurrentPage},
      {route: '/topic/deleteUser', callback: this.refreshCurrentPage},
      {route: '/topic/newTestCase', callback: this.refreshAndGoToLastPage},
      {route: '/topic/updateTestCase', callback: this.refreshCurrentPage},
      {route: '/topic/deleteTestCase', callback: this.refreshCurrentPage},
      {route: '/topic/newTestSuite', callback: this.refreshAndGoToLastPage},
      {route: '/topic/updateTestSuite', callback: this.refreshCurrentPage},
      {route: '/topic/deleteTestSuite', callback: this.refreshCurrentPage}
    ]);
  }
</code></pre>

<p>client.js </p>

<pre><code>'use strict';

var rest = require('rest');
var defaultRequest = require('rest/interceptor/defaultRequest');
var mime = require('rest/interceptor/mime');
var uriTemplateInterceptor = require('./api/uriTemplateInterceptor');
var errorCode = require('rest/interceptor/errorCode');
var baseRegistry = require('rest/mime/registry');

var registry = baseRegistry.child();

registry.register('text/uri-list', require('./api/uriListConverter'));
registry.register('application/hal+json', require('rest/mime/type/application/hal'));

module.exports = rest
    .wrap(mime, { registry: registry })
    .wrap(uriTemplateInterceptor)
    .wrap(errorCode)
    .wrap(defaultRequest, { headers: { 'Accept': 'application/hal+json' }});
</code></pre>

<p>Web socket listner.js</p>

<pre><code>'use strict';

var SockJS = require('sockjs-client'); // &lt;1&gt;
require('stompjs'); // &lt;2&gt;

function register(registrations) {
  var socket = SockJS('/mcbserver'); // &lt;3&gt;
  var stompClient = Stomp.over(socket);
  stompClient.connect({}, function(frame) {
    registrations.forEach(function (registration) { // &lt;4&gt;
      stompClient.subscribe(registration.route, registration.callback);
    });
  });
}

module.exports.register = register;
</code></pre>

<p>package.json</p>

<pre><code>"dependencies": {
    "babel-core": "^6.8.0",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "babel-preset-stage-2": "^6.5.0",
    "react": "^15.0.1",
    "react-dom": "^15.0.1",
    "rest": "^1.3.2",
    "sockjs-client": "^1.0.3",
    "stompjs": "^2.3.3",
    "webpack": "^1.13.0",
    "when": "^3.7.7",
    "react-bootstrap": "^0.29.3",
    "jquery":  "^2.2.3"

  },
</code></pre>

<p>WebSocketConfiguration.java</p>

<pre><code>` 
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Component
@EnableWebSocketMessageBroker
public class WebSocketConfiguration extends AbstractWebSocketMessageBrokerConfigurer {

    static final String MESSAGE_PREFIX = "/topic";

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/mcbserver").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.enableSimpleBroker(MESSAGE_PREFIX);
        registry.setApplicationDestinationPrefixes("/app");
    }
}
</code></pre>

<p>EventHandler.java</p>

<pre><code>@Component
@RepositoryEventHandler(User.class)
public class EventHandler {

    private final SimpMessagingTemplate websocket;

    private final EntityLinks entityLinks;

    @Autowired
    public EventHandler(SimpMessagingTemplate websocket, EntityLinks entityLinks) {
        this.websocket = websocket;
        this.entityLinks = entityLinks;
    }

    @HandleAfterCreate
    public void newUser(User user) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/newUser", getPath(user));
    }

    @HandleAfterDelete
    public void deleteUser(User user) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/deleteUser", getPath(user));
    }

    @HandleAfterSave
    public void updateUser(User user) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/updateUser", getPath(user));
    }

    @HandleAfterCreate
    public void newTestCase(TestCase testCase) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/newTestCase", getPath(testCase));
    }

    @HandleAfterDelete
    public void deleteTestCase(TestCase testCase) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/deleteTestCase", getPath(testCase));
    }

    @HandleAfterSave
    public void updateTestCase(TestCase testCase) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/updateTestCase", getPath(testCase));
    }

    @HandleAfterCreate
    public void newTestSuite(TestSuite testSuite) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/newTestSuite", getPath(testSuite));
    }

    @HandleAfterDelete
    public void deleteTestSuite(TestSuite testSuite) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/deleteTestSuite", getPath(testSuite));
    }

    @HandleAfterSave
    public void updateTestSuite(TestSuite testSuite) {
        this.websocket.convertAndSend(MESSAGE_PREFIX + "/updateTestSuite", getPath(testSuite));
    }

    /**
     * Take an {@link User} and get the URI using Spring Data REST's
     * {@link EntityLinks}.
     *
     * @param user
     */
    private String getPath(User user) {
        return this.entityLinks.linkForSingleResource(user.getClass(), user.getId()).toUri().getPath();
    }

    /**
     * Take an {@link TestCase} and get the URI using Spring Data REST's
     * {@link EntityLinks}.
     *
     * @param user
     */
    private String getPath(TestCase testCase) {
        return this.entityLinks.linkForSingleResource(testCase.getClass(), testCase.getId()).toUri().getPath();
    }

    /**
     * Take an {@link TestSuite} and get the URI using Spring Data REST's
     * {@link EntityLinks}.
     *
     * @param user
     */
    private String getPath(TestSuite testSuite) {
        return this.entityLinks.linkForSingleResource(testSuite.getClass(), testSuite.getId()).toUri().getPath();
    }

}
</code></pre>