Service Worker not working on offline mode on IOS 12 - ios

I'm trying to make a PWA to work on both online/offline modes on both iOS/Android OS, so far I already got my service worker ready and working on android, is service offline page correctly, but on IOS it's not working at all, I can add my PWA to home screen and it works all IndexedDB methods I have, but once I go offline, it tells me Safari doesn't have internet connection, I think there might be a problem on install method of my service worker, but I have no clue how to check that on iphone since I'm developing on linux, here's my service worker code which work ok on android but not on IOS.
(function() {
'use strict';
var filesToCache = [
'/app/inicio',
'/app/mapa',
'/app/visita',
'/app/sincronizacion',
'/static/base/bs4/js/jquery-3.1.1.min.js',
'/static/base/bs4/css/heineken-bs.min.css',
'/static/base/bs4/js/bootstrap.min.js',
'/static/base/bs/js/bootstrap-switch.min.js',
'/static/base/fontawesome-5.3.1/js/all.min.js',
'/static/base/js/chainedfk.js',
'/static/base/multiselect/js/jquery.multi-select.js',
'/static/base/js/common.js',
'/static/base/js/jquery.numeric.min.js',
'/static/base/bs4/js/popper.min.js',
'/static/base/bs/css/bootstrap-switch.min.css',
'/static/base/imgs/favicon/favicon.ico',
'/static/base/imgs/favicon/ms-icon-144x144.png',
'/static/base/leaflet-1.3.4/leaflet.css',
'/static/base/leaflet-1.3.4/leaflet.js',
'/static/base/bs4/css/map-styles.css',
'/static/base/leaflet-usermarker-master/src/leaflet.usermarker.js',
'/static/base/leaflet-usermarker-master/src/leaflet.usermarker.css',
'/static/base/Leaflet.markercluster-master/dist/leaflet.markercluster-src.js',
'/static/base/Leaflet.markercluster-master/dist/MarkerCluster.css',
'/static/base/leaflet-draw/src/leaflet.draw.css',
'/static/base/leaflet-draw/src/Leaflet.draw.js',
'/static/base/drag/jquery.sortable.min.js',
'/static/base/date-time/js/moment-locale.min.js',
'/static/base/date-time/js/bootstrap-datetimepicker.min.js',
'/static/base/date-time/css/bootstrap-datetimepicker.min.css',
'/static/base/select-picker/css/select-picker.css',
'/static/base/select-picker/js/select-picker.min.js',
'/static/base/js/turf.min.js',
'/static/base/js/util_v1.js',
'/static/base/js/util_db_v1.js',
'/static/base/localForage/localforage.min.js',
'/static/base/localForage/localforage-getitems.js',
'/static/base/localForage/localforage-setitems.js',
'/static/base/localForage/localforage-startswith.js',
'/static/base/js/progressbar.js',
'/static/base/js/util_db_v1.js',
'/static/base/js/moment.min.js',
'/static/base/imgs/puntos/rojo.png',
'/static/base/imgs/puntos/verde.png',
'/static/base/imgs/puntos/amarillo.png',
'/static/base/imgs/puntos/azul.png',
'/static/base/leaflet.offline/dist/bundle.js',
'/static/base/imgs/favicon/favicon-16x16.png',
'/static/base/imgs/favicon/favicon-32x32.png',
'/static/base/imgs/favicon/favicon-96x96.png',
'/static/base/imgs/favicon/apple-icon-57x57.png',
'/static/base/imgs/favicon/apple-icon-60x60.png',
'/static/base/imgs/favicon/apple-icon-72x72.png',
'/static/base/imgs/favicon/apple-icon-76x76.png',
'/static/base/imgs/favicon/apple-icon-114x114.png',
'/static/base/imgs/favicon/apple-icon-120x120.png',
'/static/base/imgs/favicon/apple-icon-144x144.png',
'/static/base/imgs/favicon/apple-icon-152x152.png',
'/static/base/imgs/favicon/apple-icon-180x180.png',
'/static/base/imgs/favicon/android-icon-192x192.png',
];
var staticCacheName = 'pages-cache-v5';
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(staticCacheName)
.then(function(cache) {
return cache.addAll(filesToCache);
})
);
});
self.addEventListener('fetch', function(event) {
if (event.request.url.match( '^.*(\/app\/).*$' ) || event.request.url.match( '^.*(\/app/inicio\/).*$' ) || event.request.url.match( '^.*(\/app/sincronizacion\/).*$' ) || event.request.url.match( '^.*(\/app/mapa\/).*$' ) || event.request.url.match( '^.*(\/app/visita\/).*$' ) || event.request.url.match( '^.*(\/static\/).*$' )){
event.respondWith(
caches.match(event.request).then(function(response) {
if (response) {
return response;
}
return fetch(event.request).then(function(response) {
if (response.status === 404) {
return caches.match('internet-error.html');
}
return caches.open(staticCacheName).then(function(cache) {
if (event.request.url.indexOf('test') < 0) {
cache.put(event.request.url, response.clone());
}
return response;
});
});
}).catch(function(error) {
//console.log('Error, ', error);
return caches.match('internet-error.html');
})
);
}else{
false
}
});
self.addEventListener('activate', function(event) {
console.log('Activando service worker...');
var cacheWhitelist = [staticCacheName];
event.waitUntil(
caches.keys().then(function(cacheNames) {
return Promise.all(
cacheNames.map(function(cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
console.log("Eliminando"+cacheName);
return caches.delete(cacheName);
}
})
);
})
);
});
})();
and this is the script that activates such service worker on index.html
{% if browser_mobile %}
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register("{% url 'sw.js' %}")
.then(function(registration){
console.log('Registré el Service Worker: Paso a redirección',registration.scope);
if (window.matchMedia('(display-mode: standalone)').matches) {
var url = "{% url 'autentificacion:inicio' %}";
window.location = url
}else{
console.log("This is NOT! running as standalone.");
}
})
.catch(function(err){
console.log('Falló la instalación del Service Worker: ', err);
});
});
}
{% else %}
Is there something wrong with this service worker? is there a way to debug IOS service worker install? similar to chrome-dev tools?

Related

FetchEvent.respondWith received an error: Returned response is null

I created a pwa site which is working totally fine in android devices both online and offline. But it is throwing error FetchEvent.respondWith received an error: Returned response is null when tried to load on IOS device with safari 13.0 if the mobile device is offline.
Here is my code snippet from service_worker.js
// install event
self.addEventListener('install', evt => {
evt.waitUntil(
caches.open(staticCacheName).then((cache) => {
return cache.addAll(assets);
})
);
});
// activate event
self.addEventListener('activate', (e) => {
e.waitUntil(
caches.keys().then((keyList) => {
return Promise.all(keyList.map((key) => {
if(key !== staticCacheName) {
return caches.delete(key);
}
}));
})
);
});
//fetch event
self.addEventListener('fetch', function(event) {
event.respondWith(
fetch(event.request).catch(function() {
return caches.match(event.request,{ignoreSearch: true});
})
);
});
Please, help me find the solution.

Why are my service workers not working offline

Everything seems to be right and the files are being cached but it just doesn't work offline. Am I missing something obvious?
the cache.addAll did not want to work with my const FILES_TO_CACHE but do work when I put them in directly. Thus the repeated code.
Here is my service worker file:
const FILES_TO_CACHE = [
"/",
"/index.html",
"/style.css",
"/db.js",
"/index.js",
"/manifest.webmanifest"
];
const CACHE_NAME = "static-cache-v2";
const DATA_CACHE_NAME = "data-cache-v1";
// install
self.addEventListener("install", function(evt) {
evt.waitUntil(
caches.open(CACHE_NAME).then(cache => {
console.log("Your files were pre-cached successfully!");
return cache.addAll([
"/",
"/index.html",
"/style.css",
"/db.js",
"/index.js",
"/manifest.webmanifest"
]);
})
);
self.skipWaiting();
});
// activate
self.addEventListener("activate", function(evt) {
console.log("activated");
evt.waitUntil(
caches.keys().then(keyList => {
return Promise.all(
keyList.map(key => {
if (key !== CACHE_NAME && key !== DATA_CACHE_NAME) {
console.log("Removing old cache data", key);
return caches.delete(key);
}
})
).catch(err => console.log(err));
})
);
self.clients.claim();
});
// fetch
self.addEventListener("fetch", function(evt) {
console.log("fetched", evt.request.url);
if (evt.request.url.includes("/api/")) {
evt.respondWith(
caches
.open(FILES_TO_CACHE)
.then(cache => {
return fetch(evt.request)
.then(response => {
// If the response was good, clone it and store it in the cache.
if (response.status === 200) {
cache.put(evt.request.url, response.clone());
}
return response;
})
.catch(err => {
// Network request failed, try to get it from the cache.
return cache.match(evt.request);
});
})
.catch(err => console.log(err))
);
return;
}
});
link in html:
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service-worker.js").then(function() {
console.log("Service Worker Registered");
});
}
</script>
I also have my manifest linked in the HTML file.
Thank you in advance for any help you can provide!
If you look at the last line of code here:
// fetch
self.addEventListener("fetch", function(evt) {
console.log("fetched", evt.request.url);
if (evt.request.url.includes("/api/")) {
you see that there's a very simple mistake – your Service Worker is ONLY responding to requests that start with "/api/". If they don't, the SW doesn't touch them. Thus only "/api/" calls work offline (which doesn't make any sense :-), apis being mostly dynamic, right?).
(It is possible that there's another bug in the code of course, but this is a good point to start making changes.)

Service worker registers on every sub-URL

I have a site with a service worker and it registers every time a new sub-URL is visited. Is this the expected behaviour?
Example. My root URL is mysite.com. The SW registers OK when I visit that URL. When I visit mysite.com/subpage, it registers as a new service worker for that URL too.
Please tell me if I am doing something wrong or if this is the way the SWs work.
My service worker recache the resources everytime it installs, so I'm guessing it is recaching everything everytime the user visits a sub-URL for the first time. Isn't that not recommended?
This is my SW.
var staticCacheName = "bspev-v" + new Date().getTime();
var filesToCache = [
'/',
'/offline',
'/css/app.css',
'/js/app.js',
'/js/rutas.js',
// iconos app
'/images/icons/icon-72x72.png',
'/images/icons/icon-96x96.png',
'/images/icons/icon-128x128.png',
'/images/icons/icon-144x144.png',
'/images/icons/icon-152x152.png',
'/images/icons/icon-192x192.png',
'/images/icons/icon-384x384.png',
'/images/icons/icon-512x512.png',
'/favicon.ico',
// imagenes app
'/images/logotexto.png',
'/images/offline.png',
// ajax
'/api/prestamos/pendientes',
//fuentes app
'https://fonts.googleapis.com/css?family=Archivo+Narrow&display=swap',
'https://fonts.gstatic.com/s/archivonarrow/v11/tss0ApVBdCYD5Q7hcxTE1ArZ0bbwiXw.woff2',
// vue
'/js/vue.js',
'/js/vue.min.js',
// fontawesome
'/css/fa-all.css',
'/webfonts/fa-solid-900.woff2'
];
// Cache on install
self.addEventListener("install", event => {
this.skipWaiting();
event.waitUntil(
caches.open(staticCacheName)
.then(cache => {
console.log('Service Worker instalado.');
return cache.addAll(filesToCache);
})
)
});
// Clear cache on activate
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames
.filter(cacheName => (cacheName.startsWith("bspev-")))
.filter(cacheName => (cacheName !== staticCacheName))
.map(cacheName => caches.delete(cacheName))
);
})
);
});
// Serve from Cache
self.addEventListener("fetch", event => {
const requestURL = new URL(event.request.url);
//Handle api calls
if (/\/api\//.test(requestURL.pathname)) {
event.respondWith(
fetch(event.request).then(response => {
event.waitUntil(
caches.open(staticCacheName).then(cache => {
cache.put(event.request, response.clone());
})
);
return response.clone();
}).catch(function() {
return caches.match(event.request);
})
);
} else {
event.respondWith(
caches.match(event.request)
.then(response => {
return response || fetch(event.request);
})
.catch(() => {
return caches.match('offline');
})
);
}
});
This is the SW registration
<script type="text/javascript">
// Initialize the service worker
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/serviceworker.js', {
scope: '.'
}).then(function (registration) {
// Registration was successful
console.log('Laravel PWA: ServiceWorker registration successful with scope: ', registration.scope);
}, function (err) {
// registration failed :(
console.log('Laravel PWA: ServiceWorker registration failed: ', err);
});
} else { console.log('no serviceWorker in navigator') }
</script>

Service Worker caching eventsource, xhr

I have a service worker running and registered - it adds a bunch of files to the "static cache" - as files are initiated / requested - they are dynamically added to the cache - it's running and working fine - however, I notice that the live data in the html is not being updated - I used Dev tools to investigate further and noticed that eventsource and xhr were being cached by the service worker - however fetch requests aren't. has anyone else come across this / found a solution?
var CACHE_STATIC_NAME = 'static-v1';
var CACHE_DYNAMIC_NAME = 'dynamic-v1';
self.addEventListener('install', function(event) {
console.log('[Service Worker] Installing Service Worker ...', event);
event.waitUntil(
caches.open(CACHE_STATIC_NAME)
.then(function(cache) {
console.log('[Service Worker] Precaching App Shell');
cache.addAll([
'index.html',
'CSS/style.css',
'CSS/style-responsive.css',
'CSS/default-theme.css',
'fonts/fontawesome-webfont.woff',
'IMAGES/company.png',
'IMAGES/icon.png',
'IMAGES/bg.png',
'IMAGES/header.png',
'CSS/Icons/Android/48.png',
'app.js' ]);
})
);
});
self.addEventListener('activate', function(event) {
console.log('[Service Worker] Activating Service Worker ....', event);
event.waitUntil(
caches.keys()
.then(function(keyList) {
return Promise.all(keyList.map(function(key) {
if (key !== CACHE_STATIC_NAME && key !== CACHE_DYNAMIC_NAME) {
console.log('[Service Worker] Removing old cache.', key);
return caches.delete(key);
}
}));
})
);
return self.clients.claim();
});
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
if (response) {
return response;
} else {
return fetch(event.request)
.then(function(res) {
return caches.open(CACHE_DYNAMIC_NAME)
.then(function(cache) {
cache.put(event.request.url, res.clone());
return res;
})
})
.catch(function(err) {
});
}
})
);
});

Service Worker w offline.html Backup Page

I can't get the offline.html page to display. I keep getting the The FetchEvent for "https://my-domain.com" resulted in a network error response: a redirected response was used for a request whose redirect mode is not "follow".
Here's the snippet of my service-worker.js which should return the offline.html when the network is unavailable.
self.addEventListener('fetch', function(event) {
if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
if(event.request.url.includes("my-domain.com")){
console.log(event.request);
event.respondWith(
caches.match(event.request).then(function(resp) {
return resp || fetch(event.request).then(function(response) {
let responseClone = response.clone();
caches.open(CACHE_NAME).then(function(cache) {
cache.put(event.request, responseClone);
});
return response;
});
}).catch(function() {
return caches.match("/offline.html");
})
);
}
}
});
Below is the console.log of my network request (page refresh when offline)
Request {method: "GET", url: "https://my-domain.com", headers: Headers, destination: "unknown", referrer: "", …}
bodyUsed:false
cache:"no-cache"
credentials:"include"
destination:"unknown"
headers:Headers {}
integrity:""
keepalive:false
method:"GET"
mode:"navigate"
redirect:"manual"
referrer:""
referrerPolicy:"no-referrer-when-downgrade"
signal:AbortSignal {aborted: false, onabort: null}
url:"https://my-domain.com"
__proto__:Request
I got this working / found the fix. It was related to a redirected response security issue in the browser. From the Chromium Bugs Blog, Response.redirected and a new security restriction.
Solution: To avoid this failure, you have 2 options.
You can either change the install event handler to store the response generated from res.body:
self.oninstall = evt => {
evt.waitUntil(
caches.open('cache_name')
.then(cache => {
return fetch('/')
.then(response => cache.put('/', new Response(response.body));
}));
};
Or change both handlers to store the non-redirected response by setting redirect mode to ‘manual’:
self.oninstall = function (evt) {
evt.waitUntil(caches.open('cache_name').then(function (cache) {
return Promise.all(['/', '/index.html'].map(function (url) {
return fetch(new Request(url, { redirect: 'manual' })).then(function (res) {
return cache.put(url, res);
});
}));
}));
};
self.onfetch = function (evt) {
var url = new URL(evt.request.url);
if (url.pathname != '/' && url.pathname != '/index.html') return;
evt.respondWith(caches.match(evt.request, { cacheName: 'cache_name' }));
};

Resources