Is it possible to open a browser window from a aservice worker? - service-worker

I would like to open a new browser window from inside a service worker, depending on the information in the http request that the service worker has intercepted, for example:
// inside service worker:
self.addEventListener('fetch', function (event) {
if (event.request.url.indexOf('trigger=') > -1) {
// OPEN A NEW BROWSER WINDOW...
event.respondWith(
new Response(JSON.stringify({ triggered: event.request.url }), {
headers: { 'Content-Type': 'application/json' },
}),
)
} else {
event.respondWith(
fetch(event.request).then(function (response) {
return response
}),
)
}
})
From what I have read, it seems that the only way to do this, is to click on a notification that is displayed as a result of having received a push message. That is to, you register a "notification click event" listener, which will allow you to pop open a new window? see here for more info.
Does anyone know if there is a way to do this without the need for any sort of push notification?

I figured it out, it's fairly simple actually:
// inside service worker:
self.addEventListener('notificationclick', e => {
e.notification.close()
clients.openWindow('http://localhost/')
})
self.addEventListener('fetch', function (event) {
if (event.request.url.indexOf('trigger=') > -1) {
self.registration.showNotification('Click here to open the app')
event.respondWith(
new Response(JSON.stringify({ triggered: event.request.url }), {
headers: { 'Content-Type': 'application/json' },
}),
)
} else {
event.respondWith(
fetch(event.request).then(function (response) {
return response
}),
)
}
})
You need to request permission to display notifications when registering the service worker of course, but otherwise it works.

Related

Service worker and payment Handlers

I created a service worker for a payment manager. When I checked the server is installed but I do not see the payment methods installed on the browser.
i added the event install fetch and load but i can't show the payment methods register in browser..
Any help please
My installer is
navigator.serviceWorker.register('sw.js').then(() => {
return navigator.serviceWorker.ready;
}).then((registration) => {
if (!registration.paymentManager) {
console.log('No payment handler capability in this browser. Is chrome://flags/#service-worker-payment-apps enabled?');
return;
}
if (!registration.paymentManager.instruments) {
return;
}
registration.paymentManager.instruments
.set('instrument-key', {
name: 'Chrome uses name and icon from the web app manifest',
enabledMethods: ['https://...'],
method: 'https://...',
})
.then(() => {
registration.paymentManager.instruments.get('instrument-key').then((instrument) => {
}).catch(...
})
My server worker
let payment_request_event;
let resolver;
let client;
const addResourcesToCache = async (resources) => {
const cache = await caches.open("v1");
await cache.addAll(resources);
};
self.addEventListener('install', event => {
event.waitUntil(
addResourcesToCache([
"/",
"/index.html",
])
);
console.log(`SW: Event fired: ${event.type}`);
console.dir(event);
});
self.addEventListener('activate', event => {
console.dir(event);
});
self.addEventListener('fetch', event => {
event.respondWith(fetch(event.request));
});
self.addEventListener('canmakepayment', e => {
console.log('canmakepayment', e);
e.respondWith(true);
});
self.addEventListener('paymentrequest', async e => {
console.log(e)
payment_request_event = e;
e.respondWith(resolver.promise);
// Open the checkout page.
try {
// Open the window and preserve the client
client = await e.openWindow('https://...html');
...

Second update service worker doesn't work

I hava a pwa with this sw.js:
const info = "versione: 1.0.0 data: 07-01-2020 12:46";
const CACHE = "pwa-offline";
const pages = [
// some files
];
self.addEventListener("install", function (event) {
//console.log("Install Event processing");
self.skipWaiting();
event.waitUntil(
caches.open(CACHE).then(function (cache) {
//console.log("Cached offline page during install");
return cache.addAll(pages);
})
);
});
self.addEventListener("activate", (event) => {
});
self.addEventListener("fetch", function (event) {
if (event.request.method !== "GET") return;
event.respondWith(
fetch(event.request)
.then(function (response) {
return fromCache(event.request);
})
.catch(function (error) {
//console.log("Network request Failed. Serving content from cache: " + error);
return fromCache(event.request);
})
);
});
async function fromCache(request) {
// Check to see if you have it in the cache
// Return response
// If not in the cache, then return error page
const cache = await caches.open(CACHE);
const matching = await cache.match(request);
if (!matching || matching.status === 404) {
return Promise.reject("no-match");
}
return matching;
}
async function updateCache(request, response) {
const cache = await caches.open(CACHE);
return cache.put(request, response);
}
and index.html with this code inside:
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("./sw.js")//, {updateViaCache: 'none'})
.then(reg => {
//console.log("Registration successful", reg);
})
.catch(e =>
console.error("Error during service worker registration:", e)
);
} else {
console.warn("Service Worker is not supported");
}
</script>
I upload the pwa in a firebase site. When I change the sw.js (e.g. changing the date of versionDate, in this site https://developers.google.com/web/fundamentals/primers/service-workers/lifecycle I read: "Your service worker is considered updated if it's byte-different to the one the browser already has. (We're extending this to include imported scripts/modules too.)") and I upload, I see the new service worker is changed. But when I make a second change in sw.js and upload, the sw.js is not changed, so I can do only an update on sw.js and so to the whole site (because the sw.js caches all files of the site during the install process).
How can I update my site any time I want?
UPDATE: I watched that the problem is in android phone in desktop is ok.
UPDATE: Now also it works on android but the update is not immadiate. I see the update after some hours.
Your service worker caches all static files on your website. Consequently, you will need to edit your sw.js file whenever you are updating your website. One common way to do this is to attach a version number to your static cache name, for example pwa-offline-v1, then bump up the version number in the sw.js file whenever you are pushing an update to the site. This creates a new cache and stores the updated static files to it. You can then add an activate event listener on the service worker to delete the former cache using a function like this.
const info = "versione: 1.0.0 data: 07-01-2020 12:46";
const CACHE = "pwa-offline-v1";
const pages = [
// some files
];
self.addEventListener("install", function (event) {
//console.log("Install Event processing");
self.skipWaiting();
event.waitUntil(
caches.open(CACHE).then(function (cache) {
//console.log("Cached offline page during install");
return cache.addAll(pages);
})
);
});
self.addEventListener("activate", (event) => {
event.waitUntil(
Promise.all(
caches.keys().then((cacheNames) => {
cacheNames
.filter(cacheName => cacheName.startsWith('pwa-offline-') && cacheName !== CACHE)
.map(cacheName => caches.delete(cacheName))
})
)
);
});

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.)

Why message listener does not work at first page load?

The problem that if I open an incognito window of chrome,
the updateLocalId() does not run. So I guess I dont receive events from sw.
But if I reload the page, everything works fine.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then((registration) => {
console.info('[main] ServiceWorker registration successful: ', registration, ' ', '๐Ÿ˜');
}, (err) => {
console.error('[main] ServiceWorker registration failed: ๐Ÿ˜ ', err);
});
navigator.serviceWorker.addEventListener('message', async function (event) {
self.updateLocalId(event.data);
});
}
From SW I send message in the next way:
function send_message_to_all_clients(msg) {
self.clients.matchAll().then(clients => {
clients.map(client => client.postMessage(msg))
});
}
Found solution here
"A page is controlled by a service worker on navigation to an origin that the service worker is registered for. So the original page load that actually initializes the service worker is not itself controlled"
Updated function of sending message from SW:
function send_message_to_all_clients(msg) {
self.clients.matchAll({includeUncontrolled: true, type: 'window'}).then(clients => {
console.log('[sync] clients', clients);
clients.map(client => client.postMessage(msg))});
}

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