Search code examples
browserleafletxmlhttprequestesri-leafletcacheapi

How to intercept esri-leaflet xhr request and then check CacheStorage API, if cache exist return cached feature layer data rather than send real xhr


My web app uses esri-leaflet package, it loads geojson via xhr rather than fetch from ArcGIS server. I am trying to use the following code to intercept xhr requests, however, it doesn't work, and the browser doesn't render the cache data, I am not sure what's going on...

The code is running fine, save data to browser's cache storage, and then retrieve data successfully, but no matter how I configurate the xhr response, it seems browser never treat the fake xhr call success, and don't feed back the cached data.

const cache = await caches.open('my-cache');

(function() {
  var origOpen = XMLHttpRequest.prototype.open;
  var origSend = XMLHttpRequest.prototype.send;
  var targeturl, rs;
    
  XMLHttpRequest.prototype.send = async function() {
    var self = this;
    // Check for cached response before sending the request
    const cachedResponse = await caches.match(targeturl, {
      //ignoreSearch: true, //true will ignore querystring
      ignoreMethod: true,
      ignoreVary: true
    });

    if (cachedResponse) {
      // Use cached response
      rs = await cachedResponse.json();
      Object.defineProperty(self, 'status', {
            writable: true,
            configurable: true
        });
      Object.defineProperty(self, 'readyState', {
            writable: true,
            configurable: true
        });
      Object.defineProperty(self, 'responseText', {
            writable: true,
            configurable: true
        });
      Object.defineProperty(self, 'responseURL', {
            writable: true,
            configurable: true
        });
      Object.defineProperty(self, 'response', {
            writable: true,
            configurable: true
        });
      self.status = 200; // Set appropriate status code
      self.readyState = 4;
      self.responseText = JSON.stringify(rs); // Access cached response text
      self.response = JSON.stringify(rs);
      self.responseURL = targeturl;   
      self.dispatchEvent(new Event('loadstart'));
      self.dispatchEvent(new Event('load'));
      self.dispatchEvent(new Event('loadend'));
      
    } else {
      // Send the actual XHR request
      origSend.apply(self, arguments);
    }
  };

  XMLHttpRequest.prototype.open = function(method, url) {
    targeturl = url;  
    var self = this;

    // Corrected event listener logic
    self.addEventListener('loadstart', async (e) => {
      console.log('request started!');
      //try set response again as not working
      e.target.status = 200; // Set appropriate status code
      e.target.readyState = 4;
      e.target.responseText = JSON.stringify(rs); // Access cached response text
      console.log(e)
    });

    self.addEventListener('load', async (e) => {
      console.log('request completed!', e, rs);

      // Add response to cache if not already cached
      const cacheKeys = await cache.keys();
      const hit = cacheKeys.find((key) => key.url === url);
      if (!hit) {
        await cache.add(url); // Cache the actual response
      }
    });

    origOpen.apply(self, arguments);
  };
})();

Solution

  • After look at the esri-leaflet source code, I worked out the featureLayer render logic triggered by readystatechange event.

    the above code all correct, just need to add

    self.dispatchEvent(new Event('readystatechange'));