PWA: Chrome warning "Service worker does not have the 'fetch' handler" - google-chrome

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.

Related

Workbox advanced recipes example with Gulp

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.

Service-Worker, "TypeError:Request failed at <anonymous>"

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.

Getting chrome.getAuthToken to work inside a script

I'm building a Chrome extension that retrieves data from a user's Google Drive and inserts it into an arbitrary page. I've gotten the OAuth to work, but I can't seem to get access to the token (which I can see is set via chrome://identity-internals).
The issue here is that when the Chrome extension nav bar button is clicked, I fire a request to execute test.js. test.js apparently has no concept of chrome.identity, but it needs that information to make an XHR request. I've tried
Storing the auth token in localStorage so that test.js can retrieve it (no luck)
Nesting the test.js inside the AuthToken request (not sure how to actually pass the variable into the file and retrieve it).
Are there any ideas?
Thank you in advance!
chrome.browserAction.onClicked.addListener(function (tab) {
chrome.identity.getAuthToken({ 'interactive': true }, function(token) {
// This works
alert(token);
// This doesn't work
localStorage.setItem("authtoken", token);
});
chrome.tabs.executeScript(tab.id, {
// This file needs access to the authtoken
// but within test.js, chrome.identity is undefined.
"file": "test.js"
}, function () {
});
});
localStorage (effectively it's window.localStorage) is stored per origin (scheme + hostname + port number), and extensions have their own one in privileged components that can access restricted chrome.* API (some are listed as exceptions in content scripts docs), namely popup and background/event page, options, and other pages with a URL like chrome-extension://abc..... (abc... is an extension ID).
localStorage of a web page belongs to its own origin such as https://www.google.com.
Content scripts run in the context of web page, so they can't access extension's localStorage directly. They see localStorage of their web page's origin only.
Solution 1: use another executeScript to set a variable that will be used by the content script injected from a file:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.identity.getAuthToken({interactive: true}, function(token) {
chrome.tabs.executeScript(tab.id, {
code: 'var token=' + JSON.stringify(token) + ';'
}, function() {
chrome.tabs.executeScript(tab.id, {file: "test.js"}, function() {
});
});
});
});
JSON-serialization is used in order not to bother escaping special characters and be able to pass objects.
Solution 2: use messaging API to pass data once the content script is injected:
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.identity.getAuthToken({interactive: true}, function(token) {
chrome.tabs.executeScript(tab.id, {file: "test.js"}, function() {
chrome.tabs.sendMessage(tab.id, {token: token});
});
});
});
content script:
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
if (msg.token) {
document.getElementById('token').textContent = msg.token;
//nowYouCanProcessToken(msg.token);
}
});
Solution 3: use chrome.storage API accessible both from a content script and the abovementioned privileged parts of an extension.
chrome.browserAction.onClicked.addListener(function(tab) {
chrome.identity.getAuthToken({interactive: true}, function(token) {
chrome.storage.local.set({token: token}, function() {
chrome.tabs.executeScript(tab.id, {file: "test.js"}, function() {
});
});
});
});
content script:
chrome.storage.local.get('token', function(data) {
if (data.token) {
document.getElementById('token').textContent = data.token;
//nowYouCanProcessToken(data.token);
}
});

How to tell Service Worker which files to cache

What I’m doing
I’m building a Service Worker component. I want it to have:
A single worker.js file containing the Service Worker implementation.
I want to be able to tell the worker which files to cache, as well as the name of the cache.
Why? I want to require this module in several projects and I don’t want any of them to modify the worker file. The worker should be able to receive a list of paths to cache.
What I’ve tried
I tried passing a configuration object to the register method but it didn’t work. The worker didn't receive the object.
Taking advantage of the postMessage API which is available on the worker I did this:
In the registration of the worker, I’ve sent a message to the worker containing an object with the routes to cache.
Inside the worker, I’ve subscribed the worker to this message and used the paths to create the cache.
See it for yourself
Registration of the worker in the main thread
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/worker.js').then(function(reg) {
navigator.serviceWorker.controller.postMessage({
'hello': 'world',
cacheName: 'v1',
urlsToCache: [
"/index.html"
]
});
}, function(err) {
console.log('ಠ_ಠ Nope.', err);
});
}
The worker file
'use strict';
var cacheName,
urlsToCache;
importScripts('/node_modules/serviceworker-cache-polyfill/index.js');
self.addEventListener('message', function (evt) {
cacheName = evt.data.cacheName;
urlsToCache = evt.data.urlsToCache;
});
self.addEventListener('install', function(event) {
setTimeout(function(){
event.waitUntil(
caches.open(cacheName)
.then(function(cache) {
console.log('Opened cache:', cache);
return cache.addAll(urlsToCache);
})
);
}, 2000);
});
What is wrong with this?
I had to delay the opening of the cache by using a setTimeout, which is wrong, ugly and unreliable.
What are you trying to achieve, man? ಠ_ಠ
I want to find a way to tell the worker to wait until the message containing the paths to cache arrives.
Link to my repo
Thanks in advance.
I've sent you a Pull Request: https://github.com/cristianelias/serviceworker_component/pull/1. Basically what I did is use the cache object from the page itself as follow:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/worker.js').then(function(reg) {
caches.open('pages').then(function(pages){
return pages.add('test.html');
}).then(function(){
console.log('cached!');
});
}, function(err) {
console.log('ಠ_ಠ Nope.', err);
});
}
And instructed the SW to only perform a cache match:
'use strict';
self.addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request).then(function(match){
return match || fetch(event.request);
})
);
});
It works as expected on Chrome 45, I don't know on older versions since moving the cache object to the page is quite a new thing.

Stop chrome extension from executing based on condition

Chrome extension that I'm developing requires users to authenticate with Gmail account.
However, if a user doesn't want to authorize, I've stopped the authorization window from appearing. However, some background scripts seem to be running. How do I make sure that the extension stops functioning completely?
You must initiate your extension in a callback called when the user is authenticated. For example, using oAuth2:
function onAuthorized() {
var url = 'https://www.googleapis.com/oauth2/v1/userinfo';
var request = {
'method': 'GET',
'parameters': {
'alt': 'json'
}
};
// Declare the callback
oauth.sendSignedRequest(url, callback, request);
};
and the callback:
function callback(resp, xhr) {
// ... Process text response ...
}).done(function (data) {
// Your used is authenticated...
// ==>Init your extension HERE
});
}
A background page with "persistent": false will be unloaded after a few seconds of inactivity. Stop doing work and the right thing will happen.