I'm desperately trying to generate a serviceworker with Gulp and the Workbox generateSW library to serve an "offline.html" page when there is no connection.
I tried to use the advanced recipe "offline page only" given in the workbox documentation, but without success (https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offline_page_only).
The idea is to write with the Gulp Workbox generateSW library, an equivalent of this vanilla function:
this.addEventListener('fetch', event => {
// request.mode = navigate isn't supported in all browsers
// so include a check for Accept: text/html header.
if (event.request.mode === 'navigate' || (event.request.method === 'GET' && event.request.headers.get('accept').includes('text/html'))) {
event.respondWith(
fetch(event.request.url).catch(error => {
// Return the offline page
return caches.match(offlineUrl);
})
);
}
else{
// Respond with everything else if we can
event.respondWith(caches.match(event.request)
.then(function (response) {
return response || fetch(event.request);
})
);
}
});
I've no problem to use the lib to cache the offline page, but i couldn't find the syntax to generate the fetch part.
Could someone help me?
Thank you very much.
Related
Is there any way to port a complete Web App (which is already responsive and fully compatible with small screens, already has touch UI controls, etc.) to Android/iOS?
My Web App is barebone HTML/JS/CSS, so is super vanilla (I don't even use jQuery).
I thought I could just smash my web app into an empty Ionic-Cordova project and be good with it, but I was wondering is there is a faster/better way to do this?
Maybe a tool or service i don't know about that takes as input a folder and pops out an android/IOS executable?
You can make a PWA (Progressive Web App).
Progressive Web Apps (PWAs) are modern, high quality applications built using web technology. PWAs offer similar capabilities to iOS/Android/desktop apps, they are reliable even in unstable network conditions, and are installable making it easier for users to find and use them.
Basically you have to add a manifest file in .json to your project root where you'll inform many things about your App like icon, name, main color, display mode (choose standalone if you want it to be like an real app) and etc...
(see it here: https://web.dev/add-manifest/) and link to your project pages:
<link rel="manifest" href="/manifest.json">
After that you have to make it installable (https://web.dev/codelab-make-installable/), to do that you will need a service-worker script in your project, you can get one here (https://glitch.com/edit/#!/make-it-installable?path=service-worker.js%3A1%3A0)
const CACHE_NAME = 'offline';
const OFFLINE_URL = 'offline.html';
self.addEventListener('install', function(event) {
console.log('[ServiceWorker] Install');
event.waitUntil((async () => {
const cache = await caches.open(CACHE_NAME);
// Setting {cache: 'reload'} in the new request will ensure that the response
// isn't fulfilled from the HTTP cache; i.e., it will be from the network.
await cache.add(new Request(OFFLINE_URL, {cache: 'reload'}));
})());
self.skipWaiting();
});
self.addEventListener('activate', (event) => {
console.log('[ServiceWorker] Activate');
event.waitUntil((async () => {
// Enable navigation preload if it's supported.
// See https://developers.google.com/web/updates/2017/02/navigation-preload
if ('navigationPreload' in self.registration) {
await self.registration.navigationPreload.enable();
}
})());
// Tell the active service worker to take control of the page immediately.
self.clients.claim();
});
self.addEventListener('fetch', function(event) {
// console.log('[Service Worker] Fetch', event.request.url);
if (event.request.mode === 'navigate') {
event.respondWith((async () => {
try {
const preloadResponse = await event.preloadResponse;
if (preloadResponse) {
return preloadResponse;
}
const networkResponse = await fetch(event.request);
return networkResponse;
} catch (error) {
console.log('[Service Worker] Fetch failed; returning offline page instead.', error);
const cache = await caches.open(CACHE_NAME);
const cachedResponse = await cache.match(OFFLINE_URL);
return cachedResponse;
}
})());
}
});
Just add and save it in .js file in your project.
After that make sure you register the service worker using that code in your project:
/* Only register a service worker if it's supported */
// Service Worker
window.addEventListener('load', () => {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js');
}
});
Now you can make your site installable via some <button> element for example:
window.addEventListener('beforeinstallprompt', (event) => {
// Get the event first
window.deferredPrompt = event;
});
document.querySelector('#buttonInstall').addEventListener('click', () => {
const promptEvent = window.deferredPrompt;
if (! promptEvent) {
return;
}
promptEvent.prompt();
promptEvent.userChoice.then((result) => {
window.deferredPrompt = null;
});
}
});
You can hide the install button when people are in your PWA this way:
if (! window.matchMedia('(display-mode: standalone)').matches) {
// hide your install button
}
Here is some important things:
Your app have to meets certain criteria to be installable, you can
see it here: https://web.dev/install-criteria/
If the install pop-up doesn't appear, it means you made something
wrong, or your manifest is broken or your script.
You can see if your manifest.json is ok in browser developer tools open it (F12), go to Application tab and go to Manifest, this will show all your manifest parameters and it will show if something is wrong too.
I recommend you to read all the links above, there is a lot more
details an explanation about PWAs
simple way to port web app to mobile app is to make a WebView app in android. then give it your web app link address
I'm currently unsuccessfully trying to make my PWA installable. I have registered a SertviceWorker and linked a manifest as well as I am listening on the beforeInstallPromt event.
My ServiceWorker is listening to any fetch event.
My problem is, that the created beforeInstall banner is just being shown on Chrome desktop but on mobile I get a warning in Chrome inspection tab "Application" in the "Manifest" section:
Installability
Service worker does not have the 'fetch' handler
You can check the message on https://dev.testapp.ga/
window.addEventListener('beforeinstallprompt', (e) => {
// Stash the event so it can be triggered later.
deferredPrompt = e;
mtShowInstallButton();
});
manifest.json
{"name":"TestApp","short_name":"TestApp","start_url":"https://testapp.ga/loginCheck","icons":[{"src":"https://testapp.ga/assets/icons/launcher-ldpi.png","sizes":"36x36","density":0.75},{"src":"https://testapp.ga/assets/icons/launcher-mdpi.png","sizes":"48x48","density":1},{"src":"https://testapp.ga/assets/icons/launcher-hdpi.png","sizes":"72x72","density":1.5},{"src":"https://testapp.ga/assets/icons/launcher-xhdpi.png","sizes":"96x96","density":2},{"src":"https://testapp.ga/assets/icons/launcher-xxhdpi.png","sizes":"144x144","density":3},{"src":"https://testapp.ga/assets/icons/launcher-xxxhdpi.png","sizes":"192x192","density":4},{"src":"https://testapp.ga/assets/icons/launcher-web.png","sizes":"512x512","density":10}],"display":"standalone","background_color":"#ffffff","theme_color":"#0288d1","orientation":"any"}
ServiceWorker:
//This array should NEVER contain any file which doesn't exist. Otherwise no single file can be cached.
var preCache=[
'/favicon.png',
'/favicon.ico',
'/assets/Bears/bear-standard.png',
'/assets/jsInclude/mathjax.js',
'/material.js',
'/main.js',
'functions.js',
'/material.css',
'/materialcolors.css',
'/user.css',
'/translations.json',
'/roboto.css',
'/sw.js',
'/'
];
//Please specify the version off your App. For every new version, any files are being refreched.
var appVersion="v0.2.1";
//Please specify all files which sould never be cached
var noCache=[
'/api/'
];
//On installation of app, all files from preCache are being stored automatically.
self.addEventListener('install', function(event) {
event.waitUntil(
caches.open(appVersion+'-offline').then(function(cache) {
return cache.addAll(preCache).then(function(){
console.log('mtSW: Given files were successfully pre-cached')
});
})
);
});
function shouldCache(url) {
//Checking if url is market as noCache
var isNoCache=noCache.includes(url.substr(8).substr(url.substr(8).indexOf("/")))||noCache.includes((url.substr(8).substr(url.substr(8).indexOf("/"))).substr(0,(url.substr(8).substr(url.substr(8).indexOf("/"))).indexOf("?")));
//Checking of hostname of request != current hostname
var isOtherHost=url.substr(8).substr(0,url.substr(8).indexOf("/"))!=location.hostname&&url.substr(7).substr(0,url.substr(7).indexOf("/"))!=location.hostname;
return((url.substr(0,4)=="http"||url.substr(0,3)=="ftp") && isNoCache==false && isOtherHost==false);
}
//If any fetch fails, it will look for the request in the cache and serve it from there first
self.addEventListener('fetch', function(event) {
//Trying to answer with "online" version if fails, using cache.
event.respondWith(
fetch(event.request).then(function (response) {
if(shouldCache(response.url)) {
console.log('mtSW: Adding file to cache: '+response.url);
caches.open(appVersion+'-offline').then(function(cache) {
cache.add(new Request(response.url));
});
}
return(response);
}).catch(function(error) {
console.log( 'mtSW: Error fetching. Serving content from cache: ' + error );
//Check to see if you have it in the cache
//Return response
//If not in the cache, then return error page
return caches.open(appVersion+'-offline').then(function (cache) {
return cache.match(event.request).then(function (matching) {
var report = !matching || matching.status == 404?Promise.reject('no-match'): matching;
return report
});
});
})
);
})
I checked the mtShowInstallButton function. It's fully working on desktop.
What does this mean? On the Desktop, I never got this warning, just when using a handheld device/emulator.
Fetch function is used to fetch JSon manifest file. Try reading google docs again.
For adding PWA in Mobile you need manifest file to be fetched which is fetched using service-worker using fetch function.
Here is the code :
fetch('examples/example.json')
.then(function(response) {
// Do stuff with the response
})
.catch(function(error) {
console.log('Looks like there was a problem: \n', error);
});
for more about fetch and manifest try this.
I hope you can help me with my problem.
Currently I build a PWA with a service-worker. It registerd successful, but something is wrong with the installation.
The "caches.open"-promise result in an error: "TypeError: Request failed at ". You can see in Chrome, that the cache is registerd, but empty.
I already checked the cache urls thousand times..
Here is my Service-worker Code
var CACHE_NAME = 'surv-cache-1';
var resourcesToCache = [
'/',
'/index.html',
'/jquery-3.2.1.min.js',
'/pouchdb.min-6.4.1.js',
'/styles/inline.css',
'/scripts/app.js'
];
self.addEventListener('install', function(event) {
event.waitUntil(
// open the app browser cache
caches.open(CACHE_NAME).then(function(cache) {
console.log("Install succesfull");
// add all app assets to the cache
return cache.addAll(resourcesToCache);
}).then(function(out){
console.log(out);
}).catch(function(err){
console.log(err);
})
);
});
self.addEventListener('fetch', function(event) {
event.respondWith(
// try to find corresponding response in the cache
caches.match(event.request)
.then(function(response) {
if (response) {
// cache hit: return cached result
return response;
}
// not found: fetch resource from the server
return fetch(event.request);
}).catch(function(err){
console.log(err);
})
);
});
And my registration code:
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('service-worker.js').then(function(registration) {
console.log('Service worker registered:'+registration.scope);
}).catch(function(e) {
console.log(e);
});
};
I didn't get it.. I hope you have an idea :)
EDIT: I think I know now why it don't work. I have a authentication for my domain, so not everybody can access it.
While my serviceworker want to caching the data, it get 401 back. So it seems to be a problem with the authentication.
Maybe someone had already the same problem?
This happens when your resourcesToCache includes something that returns a 404 response. Make sure you have everything typed correctly. Also make sure that the scope is correct. You can check your worker scope using:
if("serviceWorker" in navigator) {
navigator.serviceWorker
.register(`worker.js`)
.then(registration => {
console.log("SW scope:", registration.scope);
});
}
If your project is not in your server domain root, doing something like this might help:
//your main js
if("serviceWorker" in navigator) {
navigator.serviceWorker
.register(`${window.location.pathname}worker.js`)
.then(registration => {
//do your thing
});
}
//your worker js
let resourcesToCache = [
'./',
'./index.html',
'./jquery-3.2.1.min.js',
'./pouchdb.min-6.4.1.js',
'./styles/inline.css',
'./scripts/app.js',
];
//...
As a side-note, you should be loading your libraries (jQuery, pouchdb), from a CDN to improve performance. Those can be cached too:
let resourcesToCache = [
'./',
'./index.html',
'https://code.jquery.com/jquery-3.3.1.min.js',
'https://cdnjs.cloudflare.com/ajax/libs/pouchdb/6.4.3/pouchdb.min.js',
'./styles/inline.css',
'./scripts/app.js',
];
This happened to me when I was developing locally on a windows machine and deploying on a linux server, the problem is with the path. You need to add a '.' before your path for it to be like "./" as follows:
var resourcesToCache = [
'./',
'./index.html',
'./jquery-3.2.1.min.js',
'./pouchdb.min-6.4.1.js',
'./styles/inline.css',
'./scripts/app.js'
];
This had happened to me when i was referring a file (that don't exist in the path specified) for caching. When i updated the path to the file, things got fine.
Anyone knows what the status of Web Worker support in NodeJS is? I found a two year old implementation, node-webworkers, but it didn't run with the current build of NodeJS.
Now there is https://github.com/audreyt/node-webworker-threads which appears to be actively maintained.
Worker Threads reached stable status in 12 LTS. Usage example
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
if (isMainThread) {
module.exports = function parseJSAsync(script) {
return new Promise((resolve, reject) => {
const worker = new Worker(__filename, {
workerData: script
});
worker.on('message', resolve);
worker.on('error', reject);
worker.on('exit', (code) => {
if (code !== 0)
reject(new Error(`Worker stopped with exit code ${code}`));
});
});
};
} else {
const { parse } = require('some-js-parsing-library');
const script = workerData;
parentPort.postMessage(parse(script));
}
You can use the child processes, they solve similar problems.
You can look at the specifics of the HTML5 WebWorker source.
With a little care, you can 'redress' the WebWorker to fit as a Node.js worker, by adding a prelude that may look something like this:
const { parentPort } = require('worker_threads')
global.postMessage = function(msg){
parentPort.postMessage(msg)
}
var handler
global.addEventListener = function(kind, callback){
handler = callback
}
parentPort.on('message', msg => {
handler(msg)
})
The specific HTML5 worker added a message event handler using addEventListener, so I registered such a function in global and saved the handler. I also had to supply a postMessage implementation. Finally I registered a Node.js message handler that invokes the HTML5 handler.
Everything works perfectly. No need for any special dependency, just looking at the HTML5 worker code and identify the points where it deals with messages.
I'm just learning angularJS (using angular-seed) and I need to load my site config from a JSON feed before the rest of the site loads.
Unfortunately, using $http or $resource doesn't return the feed in time for the rest of the app to load.
What is the correct way to force the app to load this data before the rest of the app?
You have to use the Controller.resolve method. Check out Misko's (one of the core Angular developer) answer https://stackoverflow.com/a/11972028/726711
Using the resolve method broke all my unit tests... I went with this way, where settings is a service.
$q.when(settings.loadConfig()).then(function () {
console.log( settings.versionedApiUrl );
});
Then, i check if we've already loaded settings to make sure we don't request more than once.
class settings {
loadConfig = ( ):angular.IPromise<any> => {
var deferred = this.q.defer();
if( this.settingsLoaded ){
deferred.resolve({})
return deferred.promise;
}
this.http({
url:'config.json'
}).then((result) => {
if( result.data ){
this.versionedApiUrl = result.data.versionedApiUrl;
this.apiServer = result.data.apiServer;
this.widgetServiceRoot = result.data.widgetServiceRoot;
this.settingsLoaded = true;
}
deferred.resolve({});
});
return deferred.promise;
}
}