dsw
Version:
Dynamic Service Worker, offline Progressive Web Apps much easier
1,158 lines (1,027 loc) • 108 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
function getBestMatchingRX(str, expressions) {
var bestMatchingRX = void 0;
var bestMatchingGroupSize = Number.MAX_SAFE_INTEGER;
var bestMatchingGroup = void 0;
expressions.forEach(function (currentRX) {
var regex = new RegExp(currentRX.rx);
var groups = str.match(regex);
if (groups && groups.length < bestMatchingGroupSize) {
bestMatchingRX = currentRX;
bestMatchingGroupSize = groups.length;
bestMatchingGroup = groups;
}
});
return bestMatchingRX ? {
rule: bestMatchingRX,
matching: bestMatchingGroup
} : false;
}
exports.default = getBestMatchingRX;
},{}],2:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _indexeddbManager = require('./indexeddb-manager.js');
var _indexeddbManager2 = _interopRequireDefault(_indexeddbManager);
var _utils = require('./utils.js');
var _utils2 = _interopRequireDefault(_utils);
var _logger = require('./logger.js');
var _logger2 = _interopRequireDefault(_logger);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_CACHE_NAME = 'defaultDSWCached';
var CACHE_CREATED_DBNAME = 'cacheCreatedTime';
var DEFAULT_CACHE_VERSION = null;
var DSWManager = void 0,
PWASettings = void 0,
goFetch = void 0;
// finds the real size of an utf-8 string
function lengthInUtf8Bytes(str) {
// Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
var m = encodeURIComponent(str).match(/%[89ABab]/g);
return str.length + (m ? m.length : 0);
}
var parseExpiration = function parseExpiration(rule, expires) {
var duration = expires || -1;
if (typeof duration == 'string') {
// let's use a formated string to know the expiration time
var sizes = {
s: 1,
m: 60,
h: 3600,
d: 86400,
w: 604800,
M: 2592000,
Y: 31449600
};
var size = duration.slice(-1),
val = duration.slice(0, -1);
if (sizes[size]) {
duration = val * sizes[size];
} else {
_logger2.default.warn('Invalid duration ' + duration, rule);
duration = -1;
}
}
if (duration >= 0) {
return parseInt(duration, 10) * 1000;
} else {
return 0;
}
};
var cacheManager = {
setup: function setup(DSWMan, PWASet, ftch) {
PWASettings = PWASet;
DSWManager = DSWMan;
goFetch = ftch;
DEFAULT_CACHE_VERSION = PWASettings.dswVersion || '1';
_indexeddbManager2.default.setup(cacheManager);
// we will also create an IndexedDB to store the cache creationDates
// for rules that have cash expiration
_indexeddbManager2.default.create({
version: 1,
name: CACHE_CREATED_DBNAME,
key: 'url'
});
},
registeredCaches: [],
createDB: function createDB(db) {
return _indexeddbManager2.default.create(db);
},
// Delete all the unused caches for the new version of the Service Worker
deleteUnusedCaches: function deleteUnusedCaches(keepUnused) {
if (!keepUnused) {
return caches.keys().then(function (keys) {
cacheManager.registeredCaches;
return Promise.all(keys.map(function (key) {
if (cacheManager.registeredCaches.indexOf(key) < 0) {
return caches.delete(key);
}
}));
});
}
},
// this method will delete all the caches
clear: function clear(_) {
if ('window' in self) {
// if we are not in the ServiceWorkerScope, we message it
// to clear all the cache
return window.DSW.sendMessage({
clearEverythingUp: true
}, true);
} else {
// we are in the ServiceWorkerScope, and should delete everything
return caches.keys().then(function (keys) {
var cleanItUp = keys.map(function (key) {
return caches.delete(key);
});
// we will also drop the databases from IndexedDB
cleanItUp.push(_indexeddbManager2.default.clear());
return Promise.all(cleanItUp);
});
}
},
// return a name for a default rule or the name for cache using the version
// and a separator
mountCacheId: function mountCacheId(rule) {
if (typeof rule == 'string') {
return rule;
}
var cacheConf = rule ? rule.action.cache : false;
if (cacheConf) {
return (cacheConf.name || DEFAULT_CACHE_NAME) + '::' + (cacheConf.version || DEFAULT_CACHE_VERSION);
}
return DEFAULT_CACHE_NAME + '::' + DEFAULT_CACHE_VERSION;
},
register: function register(rule) {
cacheManager.registeredCaches.push(cacheManager.mountCacheId(rule));
},
addAll: function addAll(bundle) {
return new Promise(function (resolve, reject) {
// for adding a group of files or rules
// we use it as a list
if (Array.isArray(bundle)) {
bundle = {
files: bundle
};
}
var promises = [];
// then, we use the cacheManager.add with a new rule
// this way it will be able to expire.
bundle.files.map(function (file) {
promises.push(cacheManager.add(file, null, null, {
action: {
fetch: file,
cache: {
name: bundle.name,
version: bundle.version || 1,
expires: bundle.expires || false
}
}
}));
});
// once all of them have been cached, we resolve it
// or in case one or more failed, we reject it
Promise.all(promises).then(resolve).catch(reject);
});
},
// just a different method signature, for .add
put: function put(rule, request, response) {
return cacheManager.add(request, typeof rule == 'string' ? rule : cacheManager.mountCacheId(rule), response, rule);
},
add: function add(request, cacheId, response, rule) {
cacheId = cacheId || cacheManager.mountCacheId(rule);
return new Promise(function (resolve, reject) {
function addIt(response) {
if (response.status == 200 || response.type == 'opaque') {
caches.open(cacheId).then(function (cache) {
// adding to cache
var opts = response.type == 'opaque' ? { mode: 'no-cors' } : {};
if ((request.url || request).indexOf('http') !== 0) {
request = _utils2.default.fixURL(request.url || request);
}
request = _utils2.default.createRequest(request, opts);
if (request.method != 'POST') {
(function () {
var cacheData = {};
if (rule && rule.action && rule.action.cache) {
cacheData = rule.action.cache;
} else {
cacheData = {
name: cacheId,
version: cacheId.split('::')[1]
};
}
var clonedResponse = void 0;
if (response.bodyUsed) {
// sometimes, due to different flows, the
// request body may have been already used
// In this case, we use cache.add instead
// of cache.put
cache.add(request).then(function (cached) {
DSWManager.traceStep(request, 'Added to cache', { cacheData: cacheData });
}).catch(function (err) {
_logger2.default.error('Could not save into cache', err);
});
} else {
clonedResponse = response.clone();
DSWManager.traceStep(request, 'Added to cache', { cacheData: cacheData });
clonedResponse & request & cache.put(request, clonedResponse);
}
})();
}
resolve(response);
// in case it is supposed to expire
if (rule && rule.action && rule.action.cache && rule.action.cache.expires) {
// saves the current time for further validation
cacheManager.setExpiringTime(request, rule || cacheId, rule.action.cache.expires);
}
}).catch(function (err) {
_logger2.default.error('Could not save into cache', err);
resolve(response);
});
} else {
reject(response);
}
}
if (!response) {
fetch(goFetch(null, request)).then(addIt).catch(function (err) {
DSWManager.traceStep(request, 'Fetch failed');
_logger2.default.error('[ DSW ] :: Failed fetching ' + (request.url || request), err);
reject(response);
});
} else {
addIt(response);
}
});
},
setExpiringTime: function setExpiringTime(request, rule) {
var expiresAt = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
if (typeof expiresAt == 'string') {
expiresAt = parseExpiration(rule, expiresAt);
}
_indexeddbManager2.default.addOrUpdate({
url: request.url || request,
dateAdded: new Date().getTime(),
expiresAt: expiresAt
}, CACHE_CREATED_DBNAME);
},
hasExpired: function hasExpired(request) {
return new Promise(function (resolve, reject) {
_indexeddbManager2.default.find(CACHE_CREATED_DBNAME, 'url', request.url || request).then(function (r) {
if (r && new Date().getTime() > r.dateAdded + r.expiresAt) {
resolve(true);
} else {
resolve(false);
}
}).catch(function (_) {
resolve(false);
});
});
},
get: function get(rule, request, event, matching, forceFromCache) {
var treatFailure = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : true;
var actionType = Object.keys(rule.action)[0],
url = request.url || request,
pathName = new URL(url).pathname;
// requests to / should be cached by default
if (rule.action.cache !== false && (pathName == '/' || pathName.match(/^\/index\.([a-z0-9]+)/i))) {
rule.action.cache = rule.action.cache || {};
}
var opts = rule.options || {};
opts.headers = opts.headers || new Headers();
actionType = actionType.toLowerCase();
// let's allow an idb alias for indexeddb...maybe we could move it to a
// separated structure
actionType = actionType == 'idb' ? 'indexeddb' : actionType;
// cache may expire...if so, we will use this verification afterwards
var verifyCache = void 0,
urlToMatch = null;
if (rule.action.cache && rule.action.cache.expires) {
verifyCache = cacheManager.hasExpired(request);
} else {
// if it will not expire, we just use it as a resolved promise
verifyCache = Promise.resolve();
}
switch (actionType) {
case 'bypass':
{
// if it is a bypass action (no rule shall be applied, at all)
if (rule.action[actionType] == 'request') {
// it may be of type request
// and we will simple allow it to go ahead
// this also means we will NOT treat any result from it
//logger.info('Bypassing request, going for the network for', request.url);
var treatResponse = function treatResponse(response) {
if (response.status >= 200 && response.status < 300) {
DSWManager.traceStep(event.request, 'Request bypassed');
return response;
} else {
DSWManager.traceStep(event.request, 'Bypassed request failed and was ignored');
var resp = new Response(''); // ignored
return resp;
}
};
// here we will use a "raw" fetch, instead of goFetch, which would
// create a new Request and define propreties to it
return fetch(goFetch(null, event.request)).then(treatResponse).catch(treatResponse);
} else {
// or of type 'ignore' (or anything else, actually)
// and we will simply output nothing, as if ignoring both the
// request and response
DSWManager.traceStep(event.request, 'Bypassed request');
actionType = 'output';
rule.action[actionType] = '';
}
}
case 'output':
{
DSWManager.traceStep(event.request, 'Responding with string output', { output: (rule.action[actionType] + '').substring(0, 180) });
return new Response(_utils2.default.applyMatch(matching, rule.action[actionType]));
}
case 'indexeddb':
{
return new Promise(function (resolve, reject) {
// function to be used after fetching
function treatFetch(response) {
if (response && response.status == 200) {
// with success or not(saving it), we resolve it
var done = function done(err) {
if (err) {
DSWManager.traceStep(event.request, 'Could not save response into IndexedDB', { err: err });
} else {
DSWManager.traceStep(event.request, 'Response object saved into IndexedDB');
}
resolve(response);
};
// store it in the indexedDB
_indexeddbManager2.default.save(rule.name, response.clone(), request, rule).then(done).catch(done); // if failed saving, we still have the reponse to deliver
} else {
// if it failed, we can look for a fallback
url = request.url;
pathName = new URL(url).pathname;
DSWManager.traceStep(event.request, 'Fetch failed', {
url: request.url,
status: response.status,
statusText: response.statusText
});
return DSWManager.treatBadPage(response, pathName, event);
}
}
// let's look for it in our cache, and then in the database
// (we use the cache, just so we can user)
_indexeddbManager2.default.get(rule.name, request).then(function (result) {
// if we did have it in the indexedDB
if (result) {
// we use it
DSWManager.traceStep(event.request, 'Found stored in IndexedDB');
return treatFetch(result);
} else {
// if it was not stored, let's fetch it
DSWManager.traceStep(event.request, 'Will fetch', {
url: request.url,
method: request.method
});
return goFetch(rule, request, event, matching).then(treatFetch).catch(treatFetch);
}
});
});
}
case 'redirect':
case 'fetch':
{
request = DSWManager.createRedirect(rule.action.fetch || rule.action.redirect, event, matching);
url = request.url;
pathName = new URL(url).pathname;
// keep going to be treated with the cache case
}
case 'cache':
{
var cacheId = void 0;
if (event.request.cachedFrom) {
// rule.action.cache && rule.action.cache.from) {
urlToMatch = event.request.cachedFrom;
} else {
urlToMatch = null;
}
if (rule.action.cache) {
cacheId = cacheManager.mountCacheId(rule);
}
// lets verify if the cache is expired or not
return verifyCache.then(function (expired) {
var lookForCache = void 0;
if (expired && !forceFromCache) {
// in case it has expired, it resolves automatically
// with no results from cache
DSWManager.traceStep(event.request, 'Cache was expired');
lookForCache = Promise.resolve();
} else {
// if not expired, let's look for it!
lookForCache = caches.match(urlToMatch || request);
}
// look for the request in the cache
return lookForCache.then(function (result) {
// if it does not exist (cache could not be verified)
if (result && result.status != 200) {
DSWManager.traceStep(event.request, 'Not found in cache', {
url: request.url,
status: result.status,
statusText: result.statusText
});
// if it has expired in cache, failed requests for
// updates should return the previously cached data
// even if it has expired
if (expired) {
DSWManager.traceStep(event.request, 'Forcing ' + (expired ? 'expired ' : '') + 'from cache');
// the true argument flag means it should come from cache, anyways
return cacheManager.get(rule, request, event, matching, true);
}
if (treatFailure) {
// look for rules that match for the request and its status
(DSWManager.rules[result.status] || []).some(function (cur, idx) {
if (pathName.match(cur.rx)) {
// if a rule matched for the status and request
// and it tries to fetch a different source
if (cur.action.fetch || cur.action.redirect) {
DSWManager.traceStep(event.request, 'Found fallback for failure', {
rule: cur,
url: request.url
});
// problematic requests should
result = goFetch(rule, request, event, matching);
return true; // stopping the loop
}
}
});
}
// we, then, return the promise of the failed result(for it
// could not be loaded and was not in cache)
return result;
} else {
// We will return the result, if successful, or
// fetch an anternative resource(or redirect)
// and treat both success and failure with the
// same "callback"
// In case it is a redirect, we also set the header to 302
// and really change the url of the response.
if (result) {
// when it comes from a redirect, we let the browser know about it
// or else...we simply return the result itself
if (request.url == event.request.url) {
DSWManager.traceStep(event.request, 'Result found in cache', {
url: event.request.url,
cacheSource: event.request.cachedFrom || event.request.url
});
// it was successful
return result;
} else {
// it is a redirect (different urls)
DSWManager.traceStep(event.request, 'Redirecting', {
from: event.request.url,
to: request.url
}, false, { // telling the tracker that it has moved
url: request.url,
id: request.requestId,
steps: request.traceSteps,
rule: rule
});
// let's move the browser's url and return
// the appropriate header
return Response.redirect(request.url, 302);
}
} else if (actionType == 'redirect') {
// if this is supposed to redirect
DSWManager.traceStep(event.request, 'Must redirect', {
from: event.request.url,
to: request.url
}, false, { // telling the tracker that it has moved
url: request.url,
id: request.requestId,
steps: request.traceSteps,
rule: rule
});
return Response.redirect(request.url, 302);
} else {
// this is a "normal" request, let's deliver it
// but we will be using a new Request with some info
// to allow browsers to understand redirects in case
// it must be redirected later on
var treatFetch = function treatFetch(response) {
if (response.type == 'opaque') {
// if it is a opaque response, let it go!
if (rule.action.cache !== false) {
DSWManager.traceStep(event.request, 'Added to cache (opaque)', {
url: request.url
});
return cacheManager.add(_utils2.default.createRequest(request, { mode: request.mode || 'no-cors' }), cacheManager.mountCacheId(rule), response, rule);
}
return response;
}
if (!response.status) {
response.status = 404;
}
// after retrieving it, we cache it
// if it was ok
if (response.status == 200) {
DSWManager.traceStep(event.request, 'Received result OK (200)', {
url: request.url
});
// if cache is not false, it will be added to cache
if (rule.action.cache !== false) {
// let's save it into cache
DSWManager.traceStep(event.request, 'Saving into cache', {
url: request.url
});
return cacheManager.add(request, cacheManager.mountCacheId(rule), response, rule);
} else {
return response;
}
} else {
// if it had expired, but could not be retrieved
// from network, let's give its cache a chance!
DSWManager.traceStep(request, 'Failed fetching', {
url: request.url
});
if (expired) {
_logger2.default.warn('Cache for ', request.url || request, 'had expired, but the updated version could not be retrieved from the network!\n', 'Delivering the outdated cached data');
DSWManager.traceStep(event.request, 'Using expired cache', { note: 'Failed fetching, loading from cache even though it was expired' });
return cacheManager.get(rule, request, event, matching, true);
}
// otherwise...let's see if there is a fallback
// for the 404 requisition
return DSWManager.treatBadPage(response, pathName, event);
}
};
// if not in cache, let's see if we should look
// for it in the network
if (treatFailure) {
DSWManager.traceStep(event.request, 'Will fetch', {
url: request.url,
method: request.method
});
return goFetch(rule, request, event, matching).then(treatFetch).catch(treatFetch);
}
}
}
}); // end lookForCache
}); // end verifyCache
}
default:
{
// also used in fetch actions
return event;
}
}
}
};
exports.default = cacheManager;
},{"./indexeddb-manager.js":4,"./logger.js":5,"./utils.js":8}],3:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _utils = require('./utils.js');
var _utils2 = _interopRequireDefault(_utils);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var origin = location.origin;
// this function basically creates a new Request instance
// out of an existing one
function createNewRequest(tmpUrl, request, event) {
var sameOrigin = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
var mode = request.mode;
if (!mode || mode == 'navigate') {
mode = sameOrigin ? 'cors' : 'no-cors';
}
var req = new Request(tmpUrl, {
method: request.method || 'GET',
headers: request.headers || {},
mode: mode,
cache: 'default',
redirect: 'manual'
});
if (request.body) {
req.body = request.body;
}
req.requestId = (event ? event.request : request).requestId;
req.traceSteps = (event ? event.request : request).traceSteps;
return req;
}
function goFetch(rule, request, event, matching) {
var tmpUrl = rule ? rule.action.fetch || rule.action.redirect : '';
if (typeof request == 'string') {
// lets fix the url in case it is not valid (not startingh with ./ or /, or a protocol)
request = _utils2.default.fixURL(request);
request = location.origin + request;
}
if (!tmpUrl) {
tmpUrl = request.url || request;
} else {
// we also fix the tmpUrl in case it was sent
tmpUrl = _utils2.default.fixURL(tmpUrl);
tmpUrl = location.origin + tmpUrl;
}
var originalUrl = tmpUrl;
var sameOrigin = void 0;
try {
sameOrigin = new URL(tmpUrl).origin == origin;
} catch (err) {
throw new Error('The URL "' + tmpUrl + '" is not valid and could not be parsed.');
}
// if there are group variables in the matching expression
tmpUrl = _utils2.default.applyMatch(matching, tmpUrl);
// if no rule is passed
if (request && !rule) {
// we will just create a simple request to be used "anywhere"
return createNewRequest(tmpUrl, request, event, sameOrigin);
}
var actionType = Object.keys(rule.action)[0];
var opts = rule.options || {};
opts.headers = opts.headers || new Headers();
// if the cache options is === false, we force it not to be cached
if (rule.action.cache === false) {
opts.headers.append('pragma', 'no-cache');
opts.headers.append('cache-control', 'no-store,no-cache');
tmpUrl = tmpUrl + (tmpUrl.indexOf('?') > 0 ? '&' : '?') + new Date().getTime();
}
// we will create a new request to be used, based on what has been
// defined by the rule or current request
var reqConfig = {
method: opts.method || request.method,
headers: opts || request.headers,
mode: actionType == 'redirect' ? request.mode || 'same-origin' : 'cors',
redirect: actionType == 'redirect' ? 'manual' : request.redirect
};
// if the host is not the same
if (!sameOrigin) {
// we set it to an opaque request
reqConfig.mode = request.mode || 'no-cors';
}
request = new Request(tmpUrl || request.url, reqConfig);
request.requestId = (event ? event.request : request).requestId;
request.traceSteps = (event ? event.request : request).traceSteps;
if (actionType == 'redirect') {
// if this is supposed to redirect
return Response.redirect(request.url, 302);
} else {
// if this is a "normal" request, let's deliver it
// but we will be using a new Request with some info
// to allow browsers to understand redirects in case
// it must be redirected later on
return fetch(request, opts);
}
}
exports.default = goFetch;
},{"./utils.js":8}],4:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _logger = require('./logger.js');
var _logger2 = _interopRequireDefault(_logger);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_DB_NAME = 'defaultDSWDB';
var INDEXEDDB_REQ_IDS = 'indexeddb-id-request';
var dbs = {};
var cacheManager;
function getObjectStore(dbName) {
var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'readwrite';
var db = dbs[dbName],
tx = void 0;
if (db) {
tx = db.transaction(dbName, mode);
return tx.objectStore(dbName);
}
return false;
}
var indexedDBManager = {
setup: function setup(cm) {
cacheManager = cm;
},
clear: function clear() {
var dbList = [];
var _loop = function _loop(db) {
dbList.push(new Promise(function (resolve, reject) {
var req = indexedDB.deleteDatabase(db);
req.onsuccess = function () {
resolve();
};
req.onerror = function (err) {
reject();
_logger2.default.error('Could not drop indexedDB database\n', err || this.error);
};
req.onblocked = function (err) {
reject();
_logger2.default.error('Could not drop indexedDB database, it was locked\n', err || this.error);
};
}));
};
for (var db in dbs) {
_loop(db);
}
return Promise.all([].concat(dbList));
},
create: function create(config) {
return new Promise(function (resolve, reject) {
var request = indexedDB.open(config.name || DEFAULT_DB_NAME, parseInt(config.version, 10) || undefined);
function dataBaseReady(db, dbName, resolve) {
db.onversionchange = function (event) {
db.close();
//logger.log('There is a new version of the database(IndexedDB) for ' + dbName);
};
if (!dbs[dbName]) {
dbs[dbName] = db;
}
resolve(config);
}
request.onerror = function (event) {
reject('Could not open the database (indexedDB) for ' + config.name);
};
request.onupgradeneeded = function (event) {
var db = event.target.result;
var baseData = {};
if (config.key) {
baseData.keyPath = config.key;
}
if (!config.key || config.autoIncrement) {
baseData.autoIncrement = true;
}
if (config.version) {
baseData.version = config.version;
} else {
baseData.version = 1;
}
if (event.oldVersion && event.oldVersion < baseData.version) {
// in case there already is a store with that name
// with a previous version
db.deleteObjectStore(config.name);
} else if (event.oldVersion === 0) {
(function () {
// if it is the first time it is creating it
var objectStore = db.createObjectStore(config.name, baseData);
// in case there are indexes defined, we create them
if (config.indexes) {
config.indexes.forEach(function (index) {
if (typeof index == 'string') {
objectStore.createIndex(index, index, {});
} else {
objectStore.createIndex(index.name, index.path || index.name, index.options);
}
});
}
// we will also make the key, an index
objectStore.createIndex(config.key, config.key, { unique: true });
})();
}
dataBaseReady(db, config.name, resolve);
};
request.onsuccess = function (event) {
var db = event.target.result;
dataBaseReady(db, config.name, resolve);
};
});
},
get: function get(dbName, request) {
return new Promise(function (resolve, reject) {
// We will actuallly look for its IDs in cache, to use them to find
// the real, complete object in the indexedDB
caches.match(request).then(function (result) {
if (result) {
result.json().then(function (obj) {
// if the request was in cache, we now have got
// the id=value for the indexes(keys) to look for,
// in the indexedDB!
var store = getObjectStore(dbName),
index = store ? store.index(obj.key) : false,
getter = index ? index.get(obj.value) : false;
// in case we did get the content from indexedDB
// let's create a new Response out of it!
if (getter) {
getter.onsuccess = function (event) {
resolve(new Response(JSON.stringify(event.target.result), {
headers: { 'Content-Type': 'application/json' }
}));
};
getter.onerror = function (event) {
// if we did not find it (or faced a problem) in
// indexeddb, we leave it to the network
resolve();
};
} else {
// in case it failed for some reason
// we leave it and allow it to be requested
resolve();
}
});
} else {
resolve();
}
});
});
},
find: function find(dbName, key, value) {
return new Promise(function (resolve, reject) {
var store = getObjectStore(dbName);
if (store) {
var index = store.index(key),
getter = index.get(value);
getter.onsuccess = function (event) {
resolve(event.target.result);
};
getter.onerror = function (event) {
reject();
};
} else {
resolve();
}
});
},
addOrUpdate: function addOrUpdate(obj, dbName) {
return new Promise(function (resolve, reject) {
var store = getObjectStore(dbName);
if (store) {
var req = store.put(obj);
req.onsuccess = function addOrUpdateSuccess() {
resolve(obj);
};
req.onerror = function addOrUpdateError(err) {
resolve(obj);
};
} else {
resolve({});
}
});
},
save: function save(dbName, data, request, rule) {
var _this = this;
return new Promise(function (resolve, reject) {
data.json().then(function (obj) {
var store = getObjectStore(dbName),
req = void 0;
if (store) {
req = store.put(obj);
// We will use the CacheAPI to store, in cache, only the IDs for
// the given object
req.onsuccess = function () {
var tmp = {};
var key = rule.action.indexedDB.key || 'id';
tmp.key = key;
tmp.value = obj[key];
cacheManager.put(INDEXEDDB_REQ_IDS, request, new Response(JSON.stringify(tmp), {
headers: { 'Content-Type': 'application/json' }
}));
resolve();
};
req.onerror = function (event) {
reject('Failed saving to the indexedDB!\n' + this.error);
};
} else {
reject('Failed saving into indexedDB...\n' + _this.error);
}
}).catch(function (err) {
_logger2.default.error('Failed saving into indexedDB!\n', err.message || _this.error, err);
reject('Failed saving into indexedDB:\n' + _this.error);
});
});
}
};
exports.default = indexedDBManager;
},{"./logger.js":5}],5:[function(require,module,exports){
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var TYPES = {
log: '[ LG ] :: ',
info: '[ INFO ] :: ',
warn: '[ WARN ] :: ',
error: '[ FAIL ] :: ',
track: '[ STEP ] :: '
};
var logger = {
info: function info() {
var args = [].slice.call(arguments);
args.unshift('color: blue');
args.unshift('%c ' + TYPES.info);
console.info.apply(console, args);
},
log: function log() {
var args = [].slice.call(arguments);
args.unshift('color: gray');
args.unshift('%c ' + TYPES.log);
console.log.apply(console, args);
},
warn: function warn() {
var args = [].slice.call(arguments);
args.unshift('font-weight: bold; color: yellow; text-shadow: 0 0 1px black;');
args.unshift('%c ' + TYPES.warn);
console.warn.apply(console, args);
},
error: function error() {
var args = [].slice.call(arguments);
args.unshift('font-weight: bold; color: red');
args.unshift('%c ' + TYPES.error);
console.error.apply(console, args);
},
track: function track() {
var args = [].slice.call(arguments);
args.unshift('font-weight: bold');
args.unshift('%c ' + TYPES.track);
console.debug.apply(console, args);
}
};
exports.default = logger;
},{}],6:[function(require,module,exports){
(function (global){
'use strict';
Object.defineProperty(exports, "__esModule", {
value: true
});
var _logger = require('./logger.js');
var _logger2 = _interopRequireDefault(_logger);
var _bestMatchingRx = require('./best-matching-rx.js');
var _bestMatchingRx2 = _interopRequireDefault(_bestMatchingRx);
var _cacheManager = require('./cache-manager.js');
var _cacheManager2 = _interopRequireDefault(_cacheManager);
var _goFetch = require('./go-fetch.js');
var _goFetch2 = _interopRequireDefault(_goFetch);
var _strategies = require('./strategies.js');
var _strategies2 = _interopRequireDefault(_strategies);
var _utils = require('./utils.js');
var _utils2 = _interopRequireDefault(_utils);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var isInSWScope = false;
var isInTest = typeof global.it === 'function';
var preCache;
var failedAppShellFiles = [];
var ROOT_SW_SCOPE = null;
var DSW = { version: '#@!THE_DSW_VERSION_INFO!@#', build: '#@!THE_DSW_BUILD_TIMESTAMP!@#', ready: null };
var REQUEST_TIME_LIMIT = 5000;
var REGISTRATION_TIMEOUT = 12000;
var DEFAULT_NOTIF_DURATION = 6000;
var currentlyMocking = {};
var installationFailure = function installationFailure(err) {
var errMessage = 'Failed storing appshell.\n ' + failedAppShellFiles.join(',') + ' failed loading.\n';
return errMessage;
};
function getSWRoot() {
if (ROOT_SW_SCOPE) {
return ROOT_SW_SCOPE;
}
ROOT_SW_SCOPE = new URL(location.href).pathname.replace(/\/[^\/]+$/, '/');
return ROOT_SW_SCOPE;
}
// These will be used in both ServiceWorker and Client scopes
DSW.isOffline = DSW.offline = function (_) {
return !navigator.onLine;
};
DSW.isOnline = DSW.online = function (_) {
return navigator.onLine;
};
// this try/catch is used simply to figure out the current scope
try {
var SWScope = ServiceWorkerGlobalScope;
if (self instanceof ServiceWorkerGlobalScope) {
isInSWScope = true;
}
} catch (e) {/* nothing...just had to find out the scope */}
if (isInSWScope) {
(function () {
var DSWManager = {
requestId: 0,
tracking: {},
trackMoved: {},
rules: {},
addRule: function addRule(sts, rule, rx) {
this.rules[sts] = this.rules[sts] || [];
var newRule = {
name: rule.name,
rx: rx,
strategy: rule.strategy || 'offline-first',
action: rule['apply']
};
this.rules[sts].push(newRule);
// if there is a rule for cache
if (newRule.action.cache) {
// we will register it in the cacheManager
_cacheManager2.default.register(newRule);
} else {
// if it is supposed NOT to cache
if (newRule.action.cache === false) {
newRule.strategy = 'online-first';
}
}
return newRule;
},
treatBadPage: function treatBadPage(response, pathName, event) {
var result = void 0;
DSWManager.traceStep(event.request, 'Request failed', {
status: response.status,
statusText: response.statusText,
url: response.url,
type: response.type
});
(DSWManager.rules[response && response.status ? response.status : 404] || []).some(function (cur, idx) {
var matching = pathName.match(cur.rx);
if (matching) {
if (cur.action.redirect && !cur.action.fetch) {
cur.action.fetch = cur.action.redirect;
}
if (cur.action.fetch) {
DSWManager.traceStep(event.request, 'Found fallback rule', cur, false, {
url: event.request.url,
id: event.request.requestId,
steps: event.request.traceSteps
});
// not found requests should
// fetch a different resource
var req = new Request(cur.action.fetch);
req.requestId = event.request.requestId;
req.traceSteps = event.request.traceSteps;
// applyMatch
result = _cacheManager2.default.get(cur, req, event, matching);
return true; // stopping