I'm fiddling around with service workers and want to use sw-toolbox which has a way to support express-style routing. However, when I import it with any version of these lines:
importScripts('node_modules/sw-toolbox/sw-toolbox.js');
importScripts('../node_modules/sw-toolbox/sw-toolbox.js');
importScripts('/node_modules/sw-toolbox/sw-toolbox.js');
I get the following error:
A bad HTTP response code (404) was received when fetching the script.
:3000/node_modules/sw-toolbox/sw-toolbox.js Failed to load resource: net::ERR_INVALID_RESPONSE
Here's my service worker code so far:
(global => {
'use strict';
//Load the sw-toolbox library
importScripts('node_modules/sw-toolbox/sw-toolbox.js');
//Ensure that our service worker takes control of the page asap
global.addEventListener('install', event => event.waitUntil(global.skipWaiting()));
global.addEventListener('activate', event => event.waitUntil(global.clients.claim()));
})(self);
What am I doing wrong?
I'm not sure if this is right, as I didn't find any reference to this in the tutorials on sw-toolbox online, but I found a workaround to get it to import.
Apparently service workers don't work like module.import, that is, relative to the calling code directory. So I added this script in my server:
//serve the sw-toolbox
server.get('/sw-toolbox.js', (req, res) => {
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('content-type', 'text/javascript');
let file = path.join(__dirname, 'node_modules', 'sw-toolbox', 'sw-toolbox.js');
res.sendFile(file);
});
And call it from the service worker thusly:
importScripts('/sw-toolbox.js');
Can anyone explain to me why this works and importScripts doesn't?
Related
I am trying to add a serviceworker to an existing React app with this filesystem layout:
Filesystem
Basically a bit of initialization code is stored in the public folder, and all code of importance is in the src folder. In the serviceWorker.js file, I made an array of filenames to cache and call that array in the 'install' event listener, and if I check DevTools I can see that the filenames are present in the cache: when I preview the data in Chrome DevTools however, I see that the code inside the cached files is all from index.html. In fact, I can add anything I want to the filename array and I will find it in cached storage only to find that it is storing the index.html code. It seems like no matter what file I try to add to the cache, only index.html gets loaded.
ServiceWorker.js:
let CACHE_NAME = "MG-cache-v2";
const urlsToCache = [
'/',
'/index.html',
'/src/App.js',
'/monkey'
];
self.addEventListener('install', function (event) {
//perform install steps
event.waitUntil(
caches.open(CACHE_NAME).then(function (cache) {
console.log('Opened MG_Cache');
return cache.addAll(urlsToCache);
}).catch(function (error) {
console.error("Error loading cache files: ", error);
})
);
self.skipWaiting();
});
self.addEventListener('fetch', function (event) {
event.respondWith(caches.match(event.request).then(function (response) {
if (response) {
return response;
}
return fetch(event.request);
})
);
});
self.addEventListener('activate', (event) => {
event.waitUntil(async function () {
const cacheNames = await caches.keys();
await Promise.all(
cacheNames.filter((cacheName) => {
//Return true if you want to remove this cache,
//but remember that caches are shared across the whole origin
return;
}).map(cacheName => caches.delete(cacheName))
);
})
})
Portion of index.html:
<script>
if ('serviceWorker' in navigator)
{
window.addEventListener('load', function () {
navigator.serviceWorker.register('serviceWorker.js').then(function (registration) {
// Registration was successful
console.log("ServiceWorker registration successful with scope: ", registration.scope);
}, function (err) {
// registration failed :
(console.log('ServiceWorker registration failed: ', err));
});
});
}
</script>
Google Devtools Preview:
All files are the same
I have tried a variety of naming strategies in the filename array but all have ended with the same result. At this point I'm at a complete loss.
EDIT: While this does not solve my problem, I found an answer to another problem that gives a little guidance. It seems like the server never finds the file I request and thus returns index.html. I tried placing the serviceWorker.js file in the src folder and moving the service worker registration to App.js and got an error:
`DOMException: Failed to register a ServiceWorker for scope ('http://localhost:3000/src/') with script ('http://localhost:3000/src/serviceWorker.js'): The script has an unsupported MIME type ('text/html'). `
This suggests that the server somehow doesn't have access to the src folder, only public. Any idea why that may be?
An important piece of information I left out it that I'm using Create-React-App. Because of the enforced layout of the filesystem, the serviceWorker must be placed in the public folder: at the same time, the scope of service workers by default is the folder that they are placed in. According to this answer, changing the scope of the service worker to be a level above the folder that it is in requires adding to the HTTP header response of the service worker (not entirely sure what that means), and doing something like that assumes you have some form of a local server set up. Alas, thus far I have just been using npm start to test my app and pushing onto nsmp to make the site live, thus have negleted to do any form of server implementation myself (I know, not very smart of me).
My hotfix was to create a new temporary app with the npx create-react-app my-app --template cra-template-pwa command, copy all files pertaining to service workers from that app (serviceWorkerRegistration.js, service-worker.js, index.js, potentially setupTests.js), and paste them into my app. Then I could simply follow this tutorial. Now my site works offline.
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.
Our server is setup as follows, using react, graphql, mongo and express:
public
index.html
service.js
src
assets
client (has 2 client side js files)
components (for react)
game.jsx
server (graphql server)
server.js
I need to register a service worker so that I can send push notification to players; the call is from game.jsx (the one that gets loaded when I want the serviceWorker to be registered):
const swreg = await navigator.serviceworker.register('service.js');
This causes a get request to ourURL.com/service.js (hence why I have service.js under public, as that's where it's served)
This is fine and dandy, but then I keep getting import errors in service.js:
Uncaught SyntaxError: Unexpected token {
this is the offending code in service.js:
import { saveSubscription } from "src/queries/queries";
Where saveSubscription is a graphql mutation call, and is defined in src/queries/queries.js.
I have tried other forms of importing, but they give me a syntax error of somekind. Googling told me that I need a type="module" tag, which obviously does not apply to this case.
How can I solve this problem? Thanks!
I fixed it... sort of.
I removed the import line, and instead used a fetch within the function.
fetch(url, {
method: "POST", // get can't have body
'Content-Type': 'application/graphql',
body: JSON.stringify({graphQLQuery})
});
I'm using workbox-webpack-plugin to register service worker.
My frontend app is react-redux app configured with webpack. If you visit app url, you can always see login view.
My plugin inside webpack.config.js:
new InjectManifest({
swSrc: path.join('src', 'service-worker.js')
})
Service worker:
workbox.skipWaiting();
workbox.clientsClaim();
workbox.precaching.precacheAndRoute(self.__precacheManifest);
My service worker caches all my splitted routes. But that doesn't matter - even if they all are cached, when user without connection visits my app, he cannot login. That's why I need a way to check if user is in offline mode, and instead of returning login, return 'offline.html' page.
I found out that my env.config.js file (which contains API URLS and is requested on login page) is not cached, so I think it would be easy to catch error while not getting this file. So I added following in my service worker:
workbox.routing.registerRoute(
new RegExp('/env.config.js'),
({event}) => {
return networkFirstHandler.handle({event})
.catch(() => caches.match('/offline.html'));
}
);
But it doesn't return offline.html in browser. It seems like 'offline.html' file is returned instead of 'env.config.js' file.
How to accomplish this? I'm new to workbox plugin and it would be great to see some suggestions.
importScripts("/precache-manifest.81b400bbc7dc89de30f4854961b64d1d.js", "https://storage.googleapis.com/workbox-cdn/releases/3.4.1/workbox-sw.js");
workbox.skipWaiting();
workbox.clientsClaim();
const STATIC_FILES = [
'/env.config.js',
];
self.__precacheManifest = STATIC_FILES.concat(self.__precacheManifest || []);
workbox.precaching.precacheAndRoute(self.__precacheManifest);
Update - since I decided to cache env.config.js file I'm only getting API error while using app offline. Maybe this API call (which returns error because of no connection) is a good trigger to display offline page? I think it is, but I still don't know.
When I try something like this:
workbox.routing.registerRoute(
new RegExp(API_REGEX_GOES_HERE),
({event}) => {
return networkFirstHandler.handle({event})
.catch(() => caches.match('/offline.html'));
}
);
The "offline.html" page will be returned instead of API request. So it will not be displayed like a page...
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.