Source: fetchStrategies.js

/**
 * Copyright (c) 2017, Oracle and/or its affiliates.
 * All rights reserved.
 */

define(['./persistenceManager', './persistenceUtils', './impl/defaultCacheHandler','./impl/logger'],
  function (persistenceManager, persistenceUtils, cacheHandler, logger) {
    'use strict';

    /**
     * @class fetchStrategies
     * @classdesc Contains out of the box Fetch Strategies which includes Cache First
     * and Cache If Offline fetch strategies.
     *
     * The Cache First strategy always fetches
     * from the local cache first regardless of offline status and returns the
     * cached response. In addition, when online, a fetch is also made out to the server
     * and the serverResponseCallback (if supplied) is called with the server response.
     *
     * The Cache If Offline strategy will fetch from the server when online and will
     * fetch from the cache if offline.
     * @export
     * @hideconstructor
     */

    /**
     * Returns the Cache First fetch strategy
     * @method
     * @name getCacheFirstStrategy
     * @memberof fetchStrategies
     * @static
     * @param {{serverResponseCallback: Function}=} options Options
     * <ul>
     * <li>options.serverResponseCallback The callback which will be called when the server responds. The callback should return a Promise which resolves when complete.</li>
     * <li>options.backgroundFetch Whether to do background fetching to the server when online. 'disabled' will disable background fetching, 'enabled' will enable it. The default is 'enabled'.</li>
     * </ul>
     * @return {Function} Returns the Cache First fetch strategy which conforms
     * to the Fetch Strategy API.
     */
    function getCacheFirstStrategy(options) {
      options = options || {};
      var serverResponseCallback = options['serverResponseCallback'];
      var backgroundFetch = options['backgroundFetch'];
      var isBackgroundFetchDisabled = backgroundFetch == 'disabled' ? true : false;
      
      if (isBackgroundFetchDisabled) {
        serverResponseCallback = null;
      }
      
      if (!serverResponseCallback && !isBackgroundFetchDisabled) {
        // dummy callback just so that the local cache is updated
        serverResponseCallback = function(request, response) {
          return Promise.resolve(response);
        };
      }

      return function (request, options) {
        logger.log("Offline Persistence Toolkit fetchStrategies: Processing CacheFirstStrategy");
        if (serverResponseCallback) {
          var wrappedServerResponseCallback = function (request, response) {
            var endpointKey = persistenceUtils.buildEndpointKey(request);
            cacheHandler.registerEndpointOptions(endpointKey, options);
            var localVars = {};
            return persistenceUtils._cloneResponse(response, {url: request.url}).then(function(responseClone) {
              return serverResponseCallback(request, responseClone);
            }).then(function (resolvedResponse) {
              localVars.resolvedResponse = resolvedResponse;
              return persistenceManager.getCache().hasMatch(request);
            }).then(function (matchExist) {
              var responseClone = localVars.resolvedResponse.clone();
              if (matchExist) {
                if (localVars.resolvedResponse != null &&
                  !persistenceUtils.isCachedResponse(localVars.resolvedResponse) &&
                  (request.method == 'GET' ||
                    request.method == 'HEAD')) {
                  return persistenceManager.getCache().put(request, localVars.resolvedResponse).then(function () {
                    return responseClone;
                  });
                } else {
                  return responseClone;
                }
              } else {
                return responseClone;
              }
            }).finally(function() {
              cacheHandler.unregisterEndpointOptions(endpointKey);
            });
          }
        }
        return _fetchFromCacheOrServerIfEmpty(request, options, wrappedServerResponseCallback);
      };
    };

    /**
     * Returns the Cache If Offline Fetch Strategy
     * @method
     * @name getCacheIfOfflineStrategy
     * @memberof fetchStrategies
     * @static
     * @return {Function} Returns the Cache If Offline fetch strategy which conforms
     * to the Fetch Strategy API.
     */
    function getCacheIfOfflineStrategy() {
      return function (request, options) {
        logger.log("Offline Persistence Toolkit fetchStrategies: Processing CacheIfOfflineStrategy");
        if (persistenceManager.isOnline()) {
          return persistenceManager.browserFetch(request).then(function (response) {
            // check for response.ok. That indicates HTTP status in the 200-299 range
            if (response.ok) {
              return persistenceUtils._cloneResponse(response, {url: request.url});
            } else {
              return _handleResponseNotOk(request, response, options);
            }
          }, function (err) {
            // As per MDN, fetch will reject when there is a network error
            // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch#Checking_that_the_fetch_was_successful
            // in that case we do want to fetch from cache.
            logger.log(err);
            return _fetchFromCacheOrServerIfEmpty(request, options);
          });
        } else {
          return _fetchFromCacheOrServerIfEmpty(request, options);
        }
      }
    };

    function _handleResponseNotOk(request, response, options) {
      // for 300-499 range, we should not fetch from cache.
      // 300-399 are redirect errors
      // 400-499 are client errors which should be handled by the client
      if (response.status < 500) {
        logger.log("Offline Persistence Toolkit fetchStrategies: Response is not ok");
        return Promise.resolve(response);
      } else {
        // 500-599 are server errors so we can fetch from cache
        return _fetchFromCacheOrServerIfEmpty(request, options);
      }
    };

    function _checkCacheForMatch(request) {
      return persistenceManager.getCache().match(request);
    };

    function _fetchFromCacheOrServerIfEmpty(request, options, serverResponseCallback) {
      return new Promise(function (resolve, reject) {
        logger.log("Offline Persistence Toolkit fetchStrategies: Process queryParams for Request");
        _processQueryParams(request, options).then(function (queryResponse) {
          if (!queryResponse) {
            logger.log("Offline Persistence Toolkit fetchStrategies: Response for queryParams is not null");
            _checkCacheForMatch(request).then(function (cachedResponse) {
              if (cachedResponse) {
                logger.log("Offline Persistence Toolkit fetchStrategies: Cached Response is not null");
                resolve(cachedResponse);
                _fetchForServerResponseCallback(request, serverResponseCallback);
              } else {
                logger.log("Offline Persistence Toolkit fetchStrategies: Cached Response is null");
                persistenceManager.browserFetch(request).then(function (response) {
                  var responseClone = response.clone();
                  resolve(responseClone);

                  if (serverResponseCallback) {
                    serverResponseCallback(request, response);
                  }
                  return;
                }, function (err) {
                  var init = {'status': 503, 'statusText': 'No cached response exists'};
                  resolve(new Response(null, init));
                });
              }
            });
          } else {
            resolve(queryResponse.clone());
            _fetchForServerResponseCallback(request, serverResponseCallback);
          }
        });
      });
    };

    function _fetchForServerResponseCallback(request, serverResponseCallback) {
      if (serverResponseCallback) {
        // we don't need to do any error handling.
        // At this point we've already resolved with a response.
        // This fetch is only for the server response callback which
        // may not occur if there is no connectivity or some other issue.
        logger.log("Offline Persistence Toolkit fetchStrategies: Fetch for ServerResponseCallback");
        persistenceManager.browserFetch(request).then(function (response) {
          persistenceUtils._cloneResponse(response, {url: request.url}).then(function(responseClone) {
            serverResponseCallback(request, responseClone);
          });
        });
      }
    };

    function _processQueryParams(request, options) {
      // this is a helper function for processing URL query params
      var queryHandler = _getQueryHandler(options);

      if (queryHandler == null) {
        // if a queryHandler was not specified
        return Promise.resolve();
      } else {
        return queryHandler(request, options);
      }
    };

    function _getQueryHandler(options) {
      // this is a helper function for processing URL query params
      var queryHandler = null;

      if (options['queryHandler'] != null) {
        queryHandler = options['queryHandler'];
      }

      return queryHandler;
    };

    return {'getCacheFirstStrategy': getCacheFirstStrategy,
      'getCacheIfOfflineStrategy': getCacheIfOfflineStrategy};
  });