Service Worker Caches to much - service-worker

I'mm working on a service worker, who should just cache some specific files.
But after implementation and refresh (yes I enabled "dumb sw after reload") there are much more files "loaded via the sw" then I added in the "files to cache" list.
I'm new with the service worker and have no idea what is wrong. I just followed some tutorials about it.
Here the code. Perhaps I am on the complete wrong way.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/worker.js').then(function(registration) {
}, function(err) {
console.log('ServiceWorker registration failed: ', err);
});
});
}
var debugMode = false;
//Variables to cache
var CACHE_NAME = 'companyname-cache-1511794947915';
var urlsToCache = [
'/typo3conf/ext/companyname/Resources/Public/css/animate.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap.min.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap-theme.css',
'/typo3conf/ext/companyname/Resources/Public/css/bootstrap-theme.min.css',
'/typo3conf/ext/companyname/Resources/Public/css/main.css',
'/typo3conf/ext/companyname/Resources/Public/css/et/override.css',
'/typo3conf/ext/companyname/Resources/Public/css/et/screen-full.css',
'/typo3conf/ext/companyname/Resources/Public/css/et/screen-full.min.css',
'/typo3conf/ext/companyname/Resources/Public/Icons/favicon.ico',
'/typo3conf/ext/companyname/Resources/Public/Icons/FaviconTouch/android-chrome-512x512.png',
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener("activate", function(event) {
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if(debugMode) {
console.log('actual cache name: %o', CACHE_NAME);
console.log('name inside the cache: %o', cacheName);
}
if ((cacheName.indexOf('companyname-cache-') !== -1) && (cacheName !== CACHE_NAME)) {
if(debugMode) {
console.log("old cache deleted");
}
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
if(debugMode) {
console.log("fetch 1");
}
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
if(!response || response.status !== 200 || response.type !== 'basic') {
if(debugMode) {
console.log("fetch 2");
}
return response;
}
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
if(debugMode) {
console.log("fetch 3");
}
return response;
}
);
})
);
});

The code in your SW works like this:
On installation, add all items in the URL LIST to the cache
On activation, check that the cache has items only for the CACHE NAME
On a fetch event: 1st check if the requested resource is in the cache and return if if found; 2nd if the requested resource was NOT in the cache, request it from the network AND put it in the cache
The last part of #3 causes the problem in your SW code – it is adding everything requested to the cache so in the future they're available from the cache.
You can see the problem in your code if you look for the last occurrence of this:
caches.open(CACHE_NAME)
That's the part of your code when something WASN'T found from the cache AND was requested from the network. The code is then putting it into the cache – that's not what you wanted.

Related

Is there a way to have my service worker intercept fetch requests coming from a client-side SvelteKit load function? (+page.ts)

I'm trying to have a service worker intercept fetch requests coming from a client-side SvelteKit load function. The network requests are being made, but the fetch event is not being triggered.
The fetch request from the load function is going to /api/allTeams, which is cached as reported by chrome devtools, but like I said, it's not getting intercepted. All the function does it fetch the data, and return it in a prop.
Also, every couple minutes I run invalidateAll(), to reload the data, and even those requests aren't being picked up by the SW.
Thanks!
--reese
src/service-worker.js:
import { build, version } from '$service-worker';
self.addEventListener('fetch', function (event) {
console.log("fetch")
event.respondWith(
fetch(event.request).catch(function () {
return caches.match(event.request);
}),
);
});
self.addEventListener('install', async function (event) {
event.waitUntil(
caches.open("ccs-" + version).then(function (cache) {
cache.add("/api/allTeams")
cache.addAll(build)
return;
}),
);
});
src/app.html:
<script>
const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) {
try {
const registration = await navigator.serviceWorker.register("/service-worker.js", {
scope: "*",
});
if (registration.installing) {
console.log("Service worker installing");
} else if (registration.waiting) {
console.log("Service worker installed");
} else if (registration.active) {
console.log("Service worker active");
}
} catch (error) {
console.error(`Registration failed with ${error}`);
}
}
};
registerServiceWorker()
</script>
src/+page.ts:
export async function load(request: Request) {
const searchQuery = new URL(request.url).searchParams.get("q")
const apiUrl = new URL(request.url)
apiUrl.pathname = "/api/allTeams"
const req = await fetch(apiUrl)
const data = await req.json()
return {data, searchQuery};
}

How to resolve `Uncaught (in promise) DOMException: Quota exceeded` error in service worker when browsing website in `Incognito mode` in Google chrome

How to resolve(or hide) Uncaught (in promise) DOMException: Quota exceeded error in service worker when browsing website in Incognito mode in Google chrome.
Every thing works fine in normal mode.
Service worker code of my progressive web app is
var version = 'v2:';
var offlineFundamentals = [
'/',
'/offline.html'
];
var updateStaticCache = function () {
return caches.open(version + 'fundamentals').then(function (cache) {
return Promise.all(offlineFundamentals.map(function (value) {
var request = new Request(value);
var url = new URL(request.url);
if (url.origin != location.origin) {
request = new Request(value, {
mode: 'no-cors'
});
}
return fetch(request).then(function (response) {
var cachedCopy = response.clone();
return cache.put(request, cachedCopy);
});
}))
})
};
var clearOldCaches = function () {
return caches.keys().then(function (keys) {
return Promise.all(keys.filter(function (key) {
return key.indexOf(version) != 0;
}).map(function (key) {
return caches.delete(key);
}));
});
};
var limitCache = function (cache, maxItems) {
cache.keys().then(function (items) {
if (items.length > maxItems) {
cache.delete(items[0]);
}
})
};
var trimCache = function (cacheName, maxItems) {
caches.open(cacheName).then(function (cache) {
cache.keys().then(function (keys) {
if (keys.length > maxItems) {
cache.delete(keys[0]).then(trimCache(cacheName, maxItems));
}
});
});
};
var hasUrlCacheExcludeMatch = function (url) {
var cacheExcludeUrls = [
"https:\/\/example.com\/user\/login",
"https:\/\/example.com\/user\/register"
];
return cacheExcludeUrls.some(path => url.includes(path));
};
self.addEventListener("install", function (event) {
event.waitUntil(updateStaticCache().then(function () {
return self.skipWaiting();
}));
});
self.addEventListener("message", function (event) {
var data = event.data;
if (data.command == "trimCache") {
trimCache(version + "pages", 80);
trimCache(version + "images", 50);
trimCache(version + "assets", 50);
}
});
self.addEventListener("fetch", function (event) {
var fetchFromNetwork = function (response) {
var cacheCopy = response.clone();
if (event.request.headers.get('Accept').indexOf('text/html') != -1) {
if (!hasUrlCacheExcludeMatch(event.request.url)) {
caches.open(version + 'pages').then(function (cache) {
cache.put(event.request, cacheCopy).then(function () {
limitCache(cache, 80);
})
});
}
} else if (event.request.headers.get('Accept').indexOf('image') != -1) {
caches.open(version + 'images').then(function (cache) {
cache.put(event.request, cacheCopy).then(function () {
limitCache(cache, 50);
});
});
} else {
caches.open(version + 'assets').then(function add(cache) {
cache.put(event.request, cacheCopy).then(function () {
limitCache(cache, 50);
});
});
}
return response;
}
var fallback = function () {
if (event.request.headers.get('Accept').indexOf('text/html') != -1) {
return caches.match(event.request).then(function (response) {
return response || caches.match('/offline.html');
})
}
}
if (event.request.method != 'GET') {
return;
}
if (event.request.headers.get('Accept').indexOf('text/html') != -1) {
event.respondWith(fetch(event.request).then(fetchFromNetwork, fallback));
return;
}
event.respondWith(caches.match(event.request).then(function (cached) {
return cached || fetch(event.request).then(fetchFromNetwork, fallback);
}))
});
self.addEventListener("activate", function (event) {
event.waitUntil(clearOldCaches().then(function () {
return self.clients.claim();
}));
});
Browsing website in Normal mode in Google chrome works fine no error occures in console.
I am not good in service worker so I am unable to resolve this issue.
This is a bit unobvious.
Quota exceed error means that your out of available space, which is 6% of total space for Chrome.
For incognito mode all storage and workers api is working, but quota space is equals zero bytes and, thus, you can not write to it.

How to exclude specific pages from caching in PWA?

I am using Progressive Web App on a website that works fine, now I want some pages should not be cached.
the urls that I want should not be cached are saved in a JavaScript variable
var cacheExclude = [
'/user/register',
'/item/new',
'/login'
];
I want when these pages are accessed offline the '/offline.html' should be shown instead.
below is my service worker that handles cache
self.addEventListener("fetch", function(event) {
//Fetch from network and cache
var fetchFromNetwork = function(response) {
var cacheCopy = response.clone();
if (event.request.headers.get('Accept').indexOf('text/html') != -1) {
caches.open(version + 'pages').then(function(cache) {
cache.put(event.request, cacheCopy).then(function() {
limitCache(cache, 25);
})
});
} else if (event.request.headers.get('Accept').indexOf('image') != -1) {
caches.open(version + 'images').then(function(cache) {
cache.put(event.request, cacheCopy).then(function() {
limitCache(cache, 10);
});
});
} else {
caches.open(version + 'assets').then(function add(cache) {
cache.put(event.request, cacheCopy);
});
}
return response;
}
//Fetch from network failed
var fallback = function() {
if (event.request.headers.get('Accept').indexOf('text/html') != -1) {
return caches.match(event.request).then(function (response) {
return response || caches.match('/offline.html');
})
}
}
//This service worker won't touch the admin area
if (event.request.url.match(/admin/)) {
return;
}
//This service worker won't touch non-get requests
if (event.request.method != 'GET') {
return;
}
//For HTML requests, look for file in network, then cache if network fails.
if (event.request.headers.get('Accept').indexOf('text/html') != -1) {
event.respondWith(fetch(event.request).then(fetchFromNetwork, fallback));
return;
}
//For non-HTML requests, look for file in cache, then network if no cache exists.
event.respondWith(
caches.match(event.request).then(function(cached) {
return cached || fetch(event.request).then(fetchFromNetwork, fallback);
})
)
});
I simply want to redirect to the offline page if any of the cacheExclude paths match?
If you want to exclude some paths, you can use Glob negation:
var cacheExclude = [
'!(*/user/register'),
'!(*/item/new'),
'!(*/login')
];
Eventually you can skip the * if there won't be anything before the matching glob expression.
However, you can also simply redirect to the offline page if any of the cacheExclude paths has a match, hence not needing to negate the Glob expression.
var cacheExclude = [
'/user/register',
'/item/new',
'/login'
];
hasUrlCacheExcludeMatch(url) {
// Note: .endsWith() does not work in IE.
return cacheExclude.some(path => url.endsWith(path));
}
self.addEventListener('fetch', function(event) {
if (event.request.method === 'GET' &&
event.request.headers.get('accept').indexOf('text/html') !== -1) {
if(hasUrlCacheExcludeMatch(event.request.url)){
const cachedResponse = await cache.match('/offline.html');
return cachedResponse;
}
// Your other code...
}
});

Using a service worker to get list of files to cache from server

I´m trying to use a service worker in an existing asp mvc app. Generally, it´s working fine: I can cache files and so on. Problem is, that there are many files to be cached and I´m trying to return an array of paths to the service worker, so that the files can be added to cache without adding them manually.
Here´s what I have so far:
Controller:
public ActionResult GetFilesToCache()
{
string[] filePaths = Directory.GetFiles(Server.MapPath(#"~\Content"), "*", SearchOption.AllDirectories);
string[] cuttedFiles = new string[filePaths.Length];
int i = 0;
foreach (var path in filePaths)
{
cuttedFiles[i] = path.Substring(path.IndexOf("Content"));
i++;
}
return Json(new { filesToCache = cuttedFiles }, JsonRequestBehavior.AllowGet);
}
This gives me a string array with entries like "Content\image1.png" etc.
Service worker:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function (cache) {
console.log('[ServiceWorker] Caching app shell');
return fetch('Home/GetFilesToCache').then(function (response) {
return response;
}).then(function (files) {
return cache.addAll(files);
});
})
);
});
The error I get is:
Uncaught (in promise) TypeError: Failed to execute 'addAll' on 'Cache': Iterator getter is not callable.
Calling the action works just fine, data is received by the service worker, but not added to cache.
In the following code:
return fetch('Home/GetFilesToCache').then(function (response) {
return response;
}).then(function (files) {
return cache.addAll(files);
});
the value of the files parameter is going to be a Response object. You want it to be the JSON deserialization of the Response object's body. You can get this by changing return response with return response.json(), leading to:
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.addAll(files);
});
Got it working with this code:
self.addEventListener('install', function(e) {
console.log('[ServiceWorker] Install');
e.waitUntil(
caches.open(cacheName).then(function (cache) {
console.log('[ServiceWorker] Caching app shell');
return fetch('Home/GetFilesToCache').then(function (response) {
return response.json();
}).then(function (files) {
var array = files.filesToCache;
return cache.addAll(array);
});
})
);
});
Notice:
Chrome only lists a part of files stored in cache, so you just have to click that little arrow to show the next page:
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.addAll(files);
});
The returned file has "filesToCache" property on "files" and also use "add" instead of "addAll".
So you need to write the following way.
return fetch('Home/GetFilesToCache').then(function(response) {
return response.json();
}).then(function(files) {
return cache.add(files.filesToCache);
});

ServiceWorker 'fetch' event not triggered

fetch event :
self.addEventListener('fetch', function(event) {
console.log("[Service Worker] fetching "+event.request);
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
return fetch(event.request);
}
)
);
});

Resources