Creating Progressive Web Applications -- How does the ServiceWorker function? - javascript

I am looking into creating progressive web applications, and I was stumped on how the service worker functions. It is my understanding that the service worker is responsible for caching resources to be used in the event that the user loses connection--but I am unsure how it does this, and can't find any resources to quite answer the question.

The question is pretty broad and thus hard to answer. You were looking for a good resource, so here is one.
In general I think your understanding of service workers is more or less correct, but you're probably not registering the correct callbacks and maybe using the wrong APIs.
A service worker can be responsible for caching (among other things), but it in general just gives you the ability to intercept http calls in a callback: self.addEventListener('fetch', (event) => {}); (this line needs to go into the service worker file and self refers to the service worker's instance). You can then do whatever you want in that callback. One very common action is to use the Cache API to use an explicitly handled cache for data:
self.addEventListener('fetch', (event) => {
event.respondWith(
//this returns the response from any cache and populates the cache with name 'v1' with the request's response from the server on a cache miss
caches.match(event.request).then((resp) => {
return resp || fetch(event.request).then((response) => {
let responseClone = response.clone();
caches.open('v1').then((cache) => {
cache.put(event.request, responseClone);
});
return response;
}).catch(() => {
return caches.match('./sw-test/gallery/myLittleVader.jpg');
})
})
);
});
For all of this, the service worker needs to be registered first, though:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('./path-to-service-worker.js')
.then((reg) => {
console.log('Registration succeeded.);
}).catch((error) => {
console.log('Registration failed with ' + error);
});
}

I have learned that the Service Worker functions by creating a local cache in the index file of my project. I then created a service worker file that stores a variable cache_name and cache_urls to store the title of the cache and the urls that I want the service worker to cache. I then bind an event listener to a function which will wait until the cache is opened and then add the urls from the cache_urls to the actual cache.
I know I am still missing some steps, but this has at least intialized a service worker in my file.

Related

Service worker do not serve cached resources

Although I see similar questions regarding the subject, there are not the same. So, I have a pwa app that is basically a simple form that users must fill out. The service worker catch the resources and serves to the app, as usual. The strategy is 'cache first, then network'. All is ok, when is onLine, BUT in offLine mode, the cached resources are not used by the app, I mean, in spite that (you can see) in the cache are the resources(fetch requests) that the app needs, it anyway try to fetch to the web and obviously because there is offLine, the fetch fail and the app crash. So, the code lines ...
caches.match(e.request)
.then( res => {
if (res ){
return res;
}
...
is not working. My question....Why???.
I will appreciate you help/comments.
You should add some more context and code, to let others better understand your situation.
Do you serve data from the cache like the following example?
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.open('mysite-dynamic').then(function(cache) {
return cache.match(event.request).then(function (response) {
return response || fetch(event.request).then(function(response) {
cache.put(event.request, response.clone());
return response;
});
});
})
);
});

Can I catch 404s in a service worker?

Basically, I have an online app that uses a htaccess file to silently redirect all requests in a given /folder/ to the same html. Then, to decide what to show the user, the page calls
var page_name = location.href.split('/').pop();
This works well online, but could I use a ServiceWorker to support this folder/file model while the page is offline? Or will I always get the page cannot be found error unless I explicitly cache the URLs?
What you describe can be accomplished using the App Shell model.
Your service worker's exact code might look a little different, and tools like Workbox can automate some of this for you, but a very basic, "vanilla" example of a service worker that accomplishes this is:
self.addEvenListener('install', (event) => {
const cacheShell = async () => {
const cache = await caches.open('my-cache');
await cache.add('/shell.html');
};
event.waitUntil(cacheShell());
});
self.addEventListener('fetch', (event) => {
// If this is a navigation request...
if (event.request.mode === 'navigate') {
// ...respond with the cached shell HTML.
event.respondWith(caches.match('/shell.html'));
return;
}
// Any other caching/response logic can go here.
});
Regardless of what the location.href value is, when this service worker is in control, the App Shell HTML will be used to fulfill all navigation requests.

Why doesn’t my service worker store all the items during the “installation” event?

This is the code for my service worker for the “installation” event:
const CACHE_NAME = "static3";
self.addEventListener("install", function (event) {
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
cache.addAll([
"/",
"/blog/",
"/links/",
"/about/",
"/contact/",
"/privacyPolicy/",
"/offline/",
"/css/CP.ttf",
"/css/CPItalic.ttf",
"/css/WS.ttf",
"/css/WSItalic.tff",
"/css/style.css",
"/css/typography.css"
]);
})
);
});
I tried several times but the service worker doesn’t store all the items in the cache. This is a screenshot from Google Chrome’s “Application” tab:
Why are only some items stored in the cache? Keep in mind that all the paths to the other files are correct.
I found the error. As you can see, I accidentally wrote "/css/WSItalic.tff" instead of "/css/WSItalic.ttf". This caused the “install” event to reject all of the items, since it stores them unless there’s even a single error in the paths. So, the “install” event rejected the files to store in the cache and only the fetch requests from the page I was viewing were stored.

How to fallback to browser's default fetch handling within event.respondWith()?

Within the service worker my fetch handler looks like this:
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request).then(function (response) {
return response || fetch(event.request); //<-- is this the browser's default fetch handling?
})
);
});
The method event.respondWith() forces me to handle all requests myself including xhr requests which is not what I like todo. I only want the cached resources to be returned if available and let the browser handle the rest using the browser's default fetch handling.
I have two issues with fetch(event.request):
Only when devtools is opened it produces an error while fetching the initial URL which is visible in the address bar https://test.de/x/#/page. It happens both on initial install and on every reload:
Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'ServiceWorkerGlobalScope': 'only-if-cached' can be set only with 'same-origin' mode`
and I don't understand why because I am not setting anything
It seems to violate the HTTP protocol because it tries to request a URL with an anchor inside:
Console: {"lineNumber":0, "message":"The FetchEvent for
\"https://test.de/x/#/page\" resulted in a network error
response: the promise was rejected.", "message_level":2, "sourceIdentifier":1, "sourceURL":""}`
How does fetch() differ from the browser's default fetch handling and are those differences the cause for those errors?
Additional information and code:
My application also leverages the good old appCache in parallel with the service worker (for backwards compatibility). I am not sure if the appcache interferes with the service worker installation on the initial page load. The rest of the code is pretty straight forward:
My index.html at https://test.de/x/#/page uses appcache and a base-href:
<html manifest="appcache" lang="de">
<head>
<base href="/x/"/>
</head>
...
Service Worker registration within the body script
window.addEventListener('load', {
navigator.serviceWorker.register('/x/sw.js')
});
Install and activate event
let MY_CACHE_ID = 'myCache_v1';
let urlsToCache = ['js/main.js'];
self.addEventListener('install', function (event) {
event.waitUntil(
caches.open(MY_CACHE_ID)
.then(function (cache) {
return cache.addAll(
urlsToCache.map(url => new Request(url,
{credentials:'include'}))
)
})
);
});
self.addEventListener('activate', function (event) {
//delete old caches
let cacheWhitelist = [MY_CACHE_ID];
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
fetch(event.request) should be really close to the default. (You can get the actual default by not calling respondWith() at all. It should mostly not be observable, but is with CSP and some referrer bits.)
Given that, I'm not sure how you're ending up with 1. That should not be possible. Unfortunately, you've not given enough information to debug what is going on.
As for 2, it passes the fragment on to the service worker, but that won't be included in the eventual network request. That matches how Fetch is defined and is done that way to give the service worker a bit of additional context that might be useful sometimes.

JS Service Worker running on pages I don't want it to run on

I just started trying to use service workers to cache files from my web server. I'm using the exact code that google suggests HERE. I uploaded the service worker script to my sites root folder and have included a link to it in the pages I want the service worker to cache files on.
I was under the impression that the service worker only caches files that are in the urlsToCache variable, and the service worker would only work on pages where the service worker script is called (linked to).
I have several pages on my site that I don't want the service worker to do anything on. However, it seems that it's still being referenced somehow. The pages in question do not contain a link to my service worker script at all. I've noticed that each time I run an AJAX command using the POST method I receive the following error in my console:
Uncaught (in promise) TypeError: Request method 'POST' is unsupported
at service-worker.js:40
at anonymous
line 40 is this snippet of code: cache.put(event.request, responseToCache);
The url my AJAX call is pointing to does not contain a link to my service worker script either.
So my question is a two parter.
1.) Does anyone understand the error message I'm receiving and know how to fix it?
2.) Why is my service worker script running on pages that don't even link to the script in the first place?
Here is the full service worker script I'm using:
var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
'assets/scripts/script1.js',
'assets/scripts/script2.js',
'assets/scripts/script3.js'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
Once a service worker is installed it runs independently of your website, means all requests will go through the fetch-eventhandler, your service worker controls also the other pages.
Not only the urlsToCache are cached, the service worker also caches responses on the fly as soon as they were fetched in the fetch-eventhandler (line 38-41)
This also leads to the answer for your first question. Your code caches all responses independent of the http method, but as the error message says http POST response cannot be cached.

Categories

Resources