I have a problem with my Service Worker.
I'm currently implementing offline functionality with an offline.html site to be shown in case of network failure. I have implemented Navigation Preloads as described here: https://developers.google.com/web/updates/2017/02/navigation-preload#activating_navigation_preload
Here is my install EventListener were skipWaiting() and initialize new cache
const version = 'v.1.2.3'
const CACHE_NAME = '::static-cache'
const urlsToCache = ['index~offline.html', 'favicon-512.png']
self.addEventListener('install', function(event) {
self.skipWaiting()
event.waitUntil(
caches
.open(version + CACHE_NAME)
.then(function(cache) {
return cache.addAll(urlsToCache)
})
.then(function() {
console.log('WORKER: install completed')
})
)
})
Here is my activate EventListener were I feature-detect navigationPreload and enable it. Afterwards I check for old caches and delete them
self.addEventListener('activate', event => {
console.log('WORKER: activated')
event.waitUntil(
(async function() {
// Feature-detect
if (self.registration.navigationPreload) {
// Enable navigation preloads!
console.log('WORKER: Enable navigation preloads')
await self.registration.navigationPreload.enable()
}
})().then(
caches.keys().then(function(cacheNames) {
cacheNames.forEach(function(cacheName) {
if (cacheName !== version + CACHE_NAME) {
caches.delete(cacheName)
console.log(cacheName + ' CACHE deleted')
}
})
})
)
)
})
This is my fetch eventListener
self.addEventListener('fetch', event => {
const { request } = event
// Always bypass for range requests, due to browser bugs
if (request.headers.has('range')) return
event.respondWith(
(async function() {
// Try to get from the cache:
const cachedResponse = await caches.match(request)
if (cachedResponse) return cachedResponse
try {
const response = await event.preloadResponse
if (response) return response
// Otherwise, get from the network
return await fetch(request)
} catch (err) {
// If this was a navigation, show the offline page:
if (request.mode === 'navigate') {
console.log('Err: ',err)
console.log('Request: ', request)
return caches.match('index~offline.html')
}
// Otherwise throw
throw err
}
})()
)
})
Now my Problem:
On my local machine on localhost everything just works as it should. If network is offline the index~offline.html page is delivered to the user. If I deploy to my test server everything works as well as expected, except for a strange error-message in Chrome on normal browsing(not offline mode):
The service worker navigation preload request failed with network error: net::ERR_INTERNET_DISCONNECTED.
I logged the error and the request to get more information
Error:
DOMException: The service worker navigation preload request failed with a network error.
Request:
Its strange because somehow index.html is requested no matter which site is loaded.
Additional Information this is happening in Chrome 89, in chrome 88 everything seems fine(I checked in browserstack). I just saw there was a change in pwa offline detection in Chrome 89...
https://developer.chrome.com/blog/improved-pwa-offline-detection/
anybody has an idea what the problem might be?
Update
I rebuild the problem here so everybody can check it out: https://dreamy-leavitt-bd4f0e.netlify.app/
This error is directly caused by the improved pwa offline detection you linked to:
https://developer.chrome.com/blog/improved-pwa-offline-detection/
The browser fakes an offline context and tries to request the start_url of your manifest, e.g. the index.html specified in your https://dreamy-leavitt-bd4f0e.netlify.app/site.webmanifest
This is to make sure that your service worker is actually returning a valid 200 response in this situation, i.e. the valid cached response for your index~offline.html page.
The error you're asking about specifically is from the await event.preloadResponse part and it apparently can't be suppressed.
The await fetch call produces a similar error but that can be suppressed, just don't console.log in the catch section.
Hopefully chrome won't show this error from preload responses in future when doing offline pwa detection as it's needlessly confusing.
I have what I assumed was a pretty standard service worker at http://www.espruino.com/ide/serviceworker.js for the page http://www.espruino.com/ide
However recently, when I have "Update on reload" set in the Chrome dev console for Service Workers the website stays with its loading indicator on, and the status shows a new service worker is "Trying to Install".
Eventually I see a red 'x' by the new service worker and a '1' with a link, but that doesn't do anything or provide any tooltip. Clicking on serviceworker.js brings me to the source file with the first line highlighted in yellow, but there are no errors highlighted.
I've done the usual and checked that all files referenced by the service worker exist and they do, and I have no idea what to look at next.
Does anyone have any clues how to debug this further?
thanks!
I'm on Chrome Beta.
I updated to the newest release a magically everything works. So I guess it was a bug in Chrome or the devtools, not my code.
For those running in to this issue with the latest version of Chrome, I was able to fix it by caching each resource in its own cache. Just call caches.open for every file you want to store. You can do this because caches.match will automatically find the file in your sea of caches.
As a messy example:
self.addEventListener('install', event => {
event.waitUntil(swpromise);
var swpromise = new Promise(function(resolve,reject) {
for (var i = 0; i < resources_array.length; i++) {
addToCache(i,resources_array[i]);
}
function addToCache(index,url) {
caches.open(version+"-"+index).then(cache => cache.addAll([url])).then(function() {cacheDone()});
}
var havedone = 0;
function cacheDone() {
havedone++;
if (havedone == resources_array.length) {
resolve();
}
}
})
I used the version number and the index of the file as the cache key for each file. I then delete all of the old caches on activation of the service worker with something similar to the following code:
addEventListener('activate', e => {
e.waitUntil(caches.keys().then(keys => {
return Promise.all(keys.map(key => {
if (key.indexOf(version+"-") == -1) return caches.delete(key);
}));
}));
});
Hopefully this works for you, it did in my case.
After deleting /serviceworker.js from my root directory, Chrome still runs the service worker that I removed from my webroot. How do I uninstall the service worker from my website and Chrome so I can log back into my website?
I've tracked the issue down to Service Work's cache mechanism and I just want to remove for now until I have time to debug it. The login script that I'm using redirects to Google's servers for them to login to their Google account. But all I get from the login.php page is an ERR_FAILED message.
Removing Service Workers Programmatically:
You can remove service workers programmatically like this:
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.unregister();
}
});
Docs: getRegistrations, unregister
Removing Service Workers Through The User Interface
You can also remove service workers under the Application tab in Chrome Devtools.
You can also go to the URL: chrome://serviceworker-internals/ and unregister a serviceworker from there.
You can do this through Chrome Developer Tool as well as Programatically.
Find all running instance or service worker by typing
chrome://serviceworker-internals/
in a new tab and then select the serviceworker you want to unregister.
Open Developer Tools (F12) and Select Application. Then Either
Select Clear Storage -> Unregister service worker
or
Select Service Workers -> Choose Update on Reload
Programatically
if(window.navigator && navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations()
.then(function(registrations) {
for(let registration of registrations) {
registration.unregister();
}
});
}
In Google Chrome, you can go to Developer tools (F12) -> Application -> Service worker and unregister the service workers from the list for that specific domain.
This method is effective in development mode of a site and mostly they run on localhost which is you may need for other project's development.
FYI, in case you are using MacOS Safari browser, there is one way to forcibly unregister a service worker (steps and images for Safari 12.1):
Safari > Preferences... > Privacy > Manage Website Data…
Enter domain name (ex. 'localhost'), click "Remove"
Note: In addition to service workers, this also will erase all caches, cookies, and databases for this domain.
In addition to the already correct answers given, if you want also to delete the SW cache you can invoke the following method:
if ('caches' in window) {
caches.keys()
.then(function(keyList) {
return Promise.all(keyList.map(function(key) {
return caches.delete(key);
}));
})
}
More in this article (Paragraph: "Unregister a service worker")
Another possibility, via Browser, is by accessing the "Cache Storage" section and click on the "Clear Site Data" button:
You should detecte two API in your devices: getRegistrations and getRegistration. The service-worker not has a unique set of APIs in all platforms. For example, some browsers only have a navigator.serviceWorker.getRegistration, no navigator.serviceWorker.getRegistrations. So you should consider with both.
safely uninstall Service Worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (const registration of registrations) {
// unregister service worker
console.log('serviceWorker unregistered');
registration.unregister();
}
});
}
to detect service worker:
navigator.serviceWorker.controller
Code to for deletion of service worker:
navigator.serviceWorker.getRegistrations()
.then(registrations => {
registrations.forEach(registration => {
registration.unregister();
})
});
navigator.serviceWorker.getRegistrations().then(function(registrations) {
for(let registration of registrations) {
registration.unregister()
} })
if(window.navigator && navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations()
.then(function(registrations) {
for(let registration of registrations) {
registration.unregister();
}
});
}
if ('caches' in window) {
caches.keys()
.then(function(keyList) {
return Promise.all(keyList.map(function(key) {
return caches.delete(key);
}));
})
}
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (const registration of registrations) {
// unregister service worker
console.log('serviceWorker unregistered');
registration.unregister();
setTimeout(function(){
console.log('trying redirect do');
window.location.replace(window.location.href); // because without redirecting, first time on page load: still service worker will be available
}, 3000);
}
});
}
IF your service worker don't let you update your files. You will need to replace serviceworker file (sw.js / ServiceWorker.js) with the next code:
self.addEventListener('install', function(e) {
self.skipWaiting();
});
self.addEventListener('activate', function(e) {
self.registration.unregister()
.then(function() {
return self.clients.matchAll();
})
.then(function(clients) {
clients.forEach(client => client.navigate(client.url))
});
});
Source here
as for me , i just use a new nonexistent scope service worker to replace old one,
ServiceWorker: {
events: true,
// what range of URLs a service worker can control. Use a nonexistent path to disable ServiceWorker
scope: '/disable-service-worker/',
},
as for the app.js, i add below code to unregister old sw:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.getRegistrations().then(registrations => {
for (const registration of registrations) {
// keep only serviceWorker which scope is /disable-service-worker/, The purpose is to make serviceWorker useless
if (registration.scope.includes('/disable-service-worker/') === false) {
registration.unregister()
}
}
});
// clear cache of service worker
caches.keys().then(keyList => {
return Promise.all(
keyList.map(key => {
return caches.delete(key);
}),
);
});
}
It can also be done in Chrome through application tab:
This code is compatible with Internet Explorer:
if (navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations().then(
function(registrations) {
for (let idx in registrations) {
registrations[idx].unregister()
}
})
}
IE doesn't support 'for...of' and 'for...of' construction may lead to "'SCRIPT1004: Expected ';'" error
The other answers all add code to the live website to remove the service worker. However I didn't want to leave that live code running forever so I developed a solution that works from within the service worker itself. The steps are below, I posted more detail and explanation on my blog.
Delete the code that registers the service worker.
Replace the service worker script with the following file. The new code must be available from the same URL the previous service worker was at. If you had multiple service worker URLs in the past you should duplicate the code at all of them.
console.log("Cleanup Service Worker Starting");
caches.keys()
.then(keys =>
Promise.all(
keys.map(async key => console.log("caches.delete", key, await caches.delete(key)))))
.then(async () => {
console.log("registration.unregister", await registration.unregister());
})
.then(() => console.log("DONE"))
.catch(console.error);
This code is fairly straight forward. First it deletes all the caches, then it unregisters itself.
Users' browsers will automatically check for an updated service worker the next time they load your website, or the next event 24h after the last service worker check. This means that existing users will run this cleanup on their next visit.
If You want to unregister all of the registered service workers in Browser,
you can do it by opening ex.
Chrome: chrome://serviceworker-internals/
Brave brave://serviceworker-internals/
open DevTools > Console and paste this:
$$('.unregister').forEach(b => b.click())
Open this page: chrome://serviceworker-internals and click to unregister button.
If you want to unregister all service worker open the developer tools and run this code on above page.
document.querySelectorAll("button.unregister").forEach(item=>{ item.click()})
Open in Chrome
chrome://serviceworker-internals/?devtools
then F12 in Console
$$('.unregister').forEach(b => b.click())
Typical JavaScript loop thats compatible with everything:
navigator.serviceWorker.getRegistrations().then(function(registrations) {
var registrationslength = registrations.length;
for (var i = 0; i < registrationslength; i++) {
registrations[i].unregister();
}
})
Our chrome extension does not work correctly anymore since version 37.0.2062.103 (It used to work correctly on chrome version 36.0.1985.143).
Specifically, the debugger API has stopped working for us when we use the DOMDebugger.
See the attached code: (background.js)
chrome.tabs.onUpdated.addListener(function(tabId,changeInfo,tab){
if( changeInfo.status == "loading" && tab.active){
var debugId = {tabId:tabId};
chrome.debugger.attach(debugId, '1.0', function() {
chrome.debugger.sendCommand(debugId, 'Debugger.enable', {}, function() {
chrome.debugger.sendCommand(debugId, "DOMDebugger.setEventListenerBreakpoint", {'eventName':'click'},
function(result) {
console.log('registering click');
});
});
});
}
});
chrome.debugger.onEvent.addListener(onEvent);
function onEvent(debuggeeId, method,params) {
if(method=="Debugger.paused"){
console.log('DONE!');
}
};
The extension successfully starts the debugger. we get the yellow debugger ribbon.
We also see the 'registering click' msg in the console. the result argument is an empty object {} (line 8).
However upon clicking on a button that has a click event listener nothing happens.
It used to work without any issues.
It seems like it regressed with https://codereview.chromium.org/305753005. One needs to call "DOM.enable" for it to work now. On the Chrome side, we should implicitly enable DOM domain upon setEventListenerBreakpoint for backwards compatibility. Unfortunately it already squeezed into the stable release.
I have created browser extension/add-on for Chrome, Firefox, Safari and IE using Crossrider framework
Now since Crossrider does not provide support for Opera, I have created a native extension for the same.
My problem is how to detect if a user(using Opera), visiting our website, has the extension installed or not ?
Similar Question is answered. Is there any better option?
Or any simple way to check(crossbrowser) if an extension is installed or not so that I don't have to use Crossrider.API?
Or is there anything similar to window.navigator.plugins for extensions?
You can communicate with your page using simple content script for your domain only using window.postMessage function.
Content script code:
window.addEventListener("message", function(event) {
if (event.source !== window) return; // We only accept messages from ourselves
switch (event.data.type) {
case 'get_info': window.postMessage({type: "info", browser: 'opera'}, "*"); break;
}
}, false);
Your page code:
window.addEventListener('load', function () {
window.addEventListener("message", function (event) {
switch (event.data.type) {
case 'info': console.log(`browser is ${event.data.browser}`); break;
}
});
window.postMessage({type: "get_info"}, "*");
});