I am using a ServiceWorker to implement user notifications. When the user first visits the site and approves the notification, the ServiceWorker is registered and subscribed to:
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('/js/sw.js').then(function(reg) {
if(/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())){
reg.pushManager.subscribe({
userVisibleOnly: true
}).then(function(sub) {
console.log('endpoint:', sub.endpoint);
endpoint = sub.endpoint;
fetch(MY_API+encodeURIComponent(endpoint), {
credentials: 'include'
})
});
}
}).catch(function(err) {
console.log(':^(', err);
});
}
On the very first visit, this failes with:
Uncaught (in promise) DOMException: Subscription failed - no active Service Worker
From the second visit on, everything is OK because the ServiceWorker is already active at that time. Looks like this is a timing issue. How can I be sure the ServiceWorker has been registered successfully and is active before I try to subscribe to it?
I tried using navigator.serviceWorker.ready as suggested below:
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('/js/sw.js').then(function(sreg) {
console.log(':^)', sreg);
navigator.serviceWorker.ready.then(function(reg) {
if(/chrom(e|ium)/.test(navigator.userAgent.toLowerCase())){
reg.pushManager.subscribe({
userVisibleOnly: true
}).then(function(sub) {
console.log('endpoint:', sub.endpoint);
endpoint = sub.endpoint;
fetch("https://www.wettfreun.de/?page=fetch&s=1&endpoint="+encodeURIComponent(endpoint), {credentials: 'include'})
});
}
});
}).catch(function(err) {
console.log(':^(', err);
});
}
Now the part inside navigator.serviceWorker.ready.then() is never called.
You can use ServiceWorkerContainer.ready.
Example from MDN:
function subscribe() {
var subscribeButton = document.querySelector('.js-subscribe-button');
subscribeButton.disabled = false;
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe()
.then(function(subscription) {
// The subscription was successful
subscribeButton.disabled = true;
return sendSubscriptionToServer(subscription);
})
.catch(function(error) {
if (Notification.permission === 'denied') {
console.log('Permission for Notifications was denied');
subscribeButton.disabled = true;
} else {
console.log('Unable to subscribe to push.', error);
subscribeButton.disabled = false;
}
});
});
}
Related
I have react app created with CRA. I want to have custom SW. I am using workbox-build with injectManifest to create my SW.
But I am still stuck with waiting to activate =/ and Uncaught SyntaxError: Unexpected token '<'. Or..
If I use sw-template2.js .. I can skip waiting.. but I still get Uncaught SyntaxError: Unexpected token '<' and white page and I need to manually refresh my page to see content. Any suggestions how to smoothly update my page with that SW please?
My registerServiceWorker.js:
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read .
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if ('serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/sw.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
// checkValidServiceWorker(swUrl);
registerValidSW(swUrl);
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
console.log('register ' + swUrl);
navigator.serviceWorker.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker) {
installingWorker.onstatechange = () => {
console.log('state ' + installingWorker.state);
if (installingWorker.state === 'installed') {
console.log('installed ' + navigator.serviceWorker.controller);
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
try {
console.log('active start');
let worker = registration.waiting
if (worker) {
console.log('worker start');
worker.postMessage({ type: 'SKIP_WAITING' });
console.log('worker finish');
}
registration.active.postMessage({ type: 'SKIP_WAITING' });
console.log('active finish');
} catch (e) {
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
}
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}
My sw-template.js
if (typeof importScripts === 'function') {
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.3/workbox-sw.js');
/* global workbox */
if (workbox) {
console.log('Workbox is loaded');
self.addEventListener('message', (event) => {
console.log(`The client sent me a message: ${event.data}`);
if (event.data && event.data.type === 'SKIP_WAITING') {
console.log('skipWaiting');
self.skipWaiting();
}
});
workbox.core.clientsClaim();
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
/* custom cache rules*/
workbox.routing.registerRoute(
new workbox.routing.NavigationRoute(
workbox.precaching.createHandlerBoundToURL("/index.html"),
{
denylist: [/^\/_/, /\/[^/?]+\.[^/]+$/],
}
))
workbox.googleAnalytics.initialize()
workbox.routing.registerRoute(
/.*\.(?:png|jpg|jpeg|svg|gif)/,
new workbox.strategies.CacheFirst({
cacheName: 'images',
})
);
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}
sw-template2.js
if (typeof importScripts === 'function') {
importScripts('https://storage.googleapis.com/workbox-cdn/releases/5.1.3/workbox-sw.js');
/* global workbox */
if (workbox) {
console.log('Workbox is loaded');
//self.addEventListener('message', (event) => {
// console.log(`The client sent me a message: ${event.data}`);
// if (event.data && event.data.type === 'SKIP_WAITING') {
// console.log('skipWaiting');
// self.skipWaiting();
// }
//});
self.addEventListener('install', function (event) {
console.log('skipWaiting');
self.skipWaiting();
});
workbox.core.clientsClaim();
/* injection point for manifest files. */
workbox.precaching.precacheAndRoute(self.__WB_MANIFEST);
/* custom cache rules*/
workbox.routing.registerRoute(
new workbox.routing.NavigationRoute(
workbox.precaching.createHandlerBoundToURL("/index.html"),
{
denylist: [/^\/_/, /\/[^/?]+\.[^/]+$/],
}
))
workbox.googleAnalytics.initialize()
workbox.routing.registerRoute(
/.*\.(?:png|jpg|jpeg|svg|gif)/,
new workbox.strategies.CacheFirst({
cacheName: 'images',
})
);
} else {
console.log('Workbox could not be loaded. No Offline support');
}
}
This is my first PWA app with laravel. This code is working,it gets registered well, but if I do a change in the code, for example in the HTML, it is not getting update, and the console is not throwing errors, and I dont know why.
I'm using this code to call the service-worker.js
if ('serviceWorker' in navigator ) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/service-worker.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}, function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
And this is the code of the sw.js
var cache_name = 'SW_CACHE';
var urlsToCache = [
'/',
'/register'
];
self.addEventListener('install', function(event) {
event.waitUntil(precache());
});
addEventListener('fetch', function(event) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
return response;
}
var fetchRequest = event.request.clone();
return fetch(fetchRequest).then(
function(response) {
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
var responseToCache = response.clone();
caches.open(cache_name)
.then(function(cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
var fromCache = function (request) {
return caches.open(cache_name).then(function (cache) {
cache.match(request).then(function (matching) {
return matching || Promise.resolve('no-match');
});
});
}
var update = function (request) {
return caches.open(cache_name).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response);
});
});
}
var precache = function() {
return caches.open(cache_name).then(function (cache) {
return cache.addAll(urlsToCache);
});
}
Y also used skipWaiting(); method inner Install method, but it crash my app and have to unload the sw from chrome://serviceworker-internals/
This is what service worker lifecycle suppose to work: a new service worker won't take place, unless:
The window or tabs controlled by the older service worker are closed and reopened
'Update on reload' option is checked in Chrome devtools
Here is an official tutorial explained it well: The Service Worker Lifecycle
Service worker will always use the existing worker. Two thinks you can do is in chrome there is an option to set update on load
Goto InspectorWindow (f12) -> application -> and check update on reload.
if you want immediate update you can choose the network first cache approach. which will take the latest from server always and use the cache only in offline mode. see the link for more information
How API is getting cached effectively, Using Service worker in Angular 5 using Angular CLI
I'm trying to use a service worker to do the following tasks:
Cache a set of pages.
Return a page from the cache if requested - if the page is not in the cache return from the server.
If there is no response from either the cache or the server return a custom offline page.
I have the following code:
this.addEventListener('install', function(event) {
console.log('Service Worker: Installing');
event.waitUntil(
caches.open('v1').then(function(cache) {
console.log('Service Worker: Caching Files');
return cache.addAll([
'/assets/logo.svg',
'/assets/app.css',
'/assets/app.js',
'/assets/common.js',
'/offline.html'
]);
})
);
});
this.addEventListener('fetch', function(event) {
if (event.request.method != "GET" || event.request.mode == "cors" || event.request.url.startsWith('chrome-extension')) return;
console.log('Event to be passed', event);
event.respondWith(
caches.open('v1').then(function(cache) {
return cache.match(event.request).then(function(response) {
if (response) {
console.log('Service Worker: Returning Cached Response', response);
return response;
} else {
fetch(event.request).then(function(response) {
console.log('Service Worker: Returning Response from Server', response);
cache.put(event.request, response.clone());
return response;
}).catch((error) => {
console.log('Fetch failed; returning offline page instead.', error);
return caches.match('/offline.html');
});
}
});
})
);
});
this.addEventListener('activate', function activator(event) {
console.log('Service Worker: Activating', event);
event.waitUntil(
caches.keys().then(function(keys) {
return Promise.all(keys
.filter(function(key) {
return key.indexOf('v1') !== 0;
})
.map(function(key) {
return caches.delete(key);
})
);
})
);
});
The issue I have is that even when offline I get a 200 response for my request, even though I'm offline and not actually retrieving the file.
I understand this is one of the pre-requisites for a PWA to always return a 200 response, but how can I return an offline page if my response always returns as 200.
Any help, advice or resources on service workers would be useful.
Cheers.
you must handle all events, you can't just "return" if nothing special should happen.
this.addEventListener('fetch', function(event) {
if (event.request.method != "GET" || event.request.mode == "cors" || event.request.url.startsWith('chrome-extension')){
event.respondWith(fetch(event.request));
return;
}
....
then you must also return your fetch event, otherwise you break the promise chain.
} else {
return fetch(event.request).then(function(response) {
I am trying to learn from code lab google but I got an error while implementing it.
here is my code.
Registration of service worker
if ('serviceWorker' in navigator && 'PushManager' in window) {
console.log('Service Worker and Push is supported');
navigator.serviceWorker.register('sw.js')
.then(function(swReg) {
console.log('Service Worker is registered', swReg);
swRegistration = swReg;
})
.catch(function(error) {
console.error('Service Worker Error', error);
});
} else {
console.warn('Push messaging is not supported');
pushButton.textContent = 'Push Not Supported';
}
in this code snippet I got error
function subscribeUser() {
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: applicationServerKey
})
.then(function(subscription) {
console.log('User is subscribed.');
updateSubscriptionOnServer(subscription);
isSubscribed = true;
updateBtn();
})
.catch(function(err) {
console.log('Failed to subscribe the user: ', err);
updateBtn();
});
}
console.log('Failed to subscribe the user: ', err);
here I got error : Failed to subscribe the user: DOMException: Subscription failed - no active Service Worker
If anyone is still wondering... using async/await:
// Registering Service Worker
const serviceWorkerRegistration = await navigator.serviceWorker.register('./sw.js', { scope:'./' })
.catch((err) => { return console.log('[Service Worker] Registration Error:', err) })
console.log('[Service Worker] Registered. Scope:', serviceWorkerRegistration.scope)
await navigator.serviceWorker.ready; // Here's the waiting
// Registering push
const subscription = await serviceWorkerRegistration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: urlBase64ToUint8Array(publicVapidKey)
})
.catch((err) => { return console.log('[Web Push] Registration Error:', err) });
console.log('[Web Push] Registered');
In Chrome Inspect / Application / Service Works uncheck bypass from network
Worked for me!
I am following a guide on how to implement the chrome push notification and I am trying to implement it in a Meteor app as a package.
Because I am unable to include the manifest.json I am getting "Registration failed - no sender id provided" or "Registration failed - permission denied". So how can I include this file in my project?
The manifest.json looks like this:
{
"permissions": [ "gcm" ],
"name": "push",
"short_name": "push notification",
"display": "standalone",
"gcm_sender_id": "0000000000000"
}
I have tried including it with the package.js like:
api.addAssets('manifest.json', 'client');
And also put the required variables (gcm_sender_id) in settings.json and starting meteor with meteor --settings settings.json but nothing works.
My service worker registration starts with calling Cpn.serviceWorkerRegistration:
Cpn = {};
Cpn.serviceWorkerRegistration = function () {
console.log("serviceWorkerRegistration called");
subscribe();
console.log(navigator);
// Check that service workers are supported, if so, progressively
// enhance and add push messaging support, otherwise continue without it.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(initialiseState);
} else {
console.warn('Service workers aren\'t supported in this browser.');
}
}
// Once the service worker is registered set the initial state
initialiseState = function () {
console.log("initialiseState");
// Are Notifications supported in the service worker?
if (!('showNotification' in ServiceWorkerRegistration.prototype)) {
console.warn('Notifications aren\'t supported.');
return;
}
// Check the current Notification permission.
// If its denied, it's a permanent block until the
// user changes the permission
if (Notification.permission === 'denied') {
console.warn('The user has blocked notifications.');
return;
}
// Check if push messaging is supported
if (!('PushManager' in window)) {
console.warn('Push messaging isn\'t supported.');
return;
}
// We need the service worker registration to check for a subscription
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
// Do we already have a push message subscription?
serviceWorkerRegistration.pushManager.getSubscription()
.then(function (subscription) {
if (!subscription) {
return;
}
// Keep your server in sync with the latest subscriptionId
sendSubscriptionToServer(subscription);
})
.catch(function (err) {
console.warn('Error during getSubscription()', err);
});
});
}
function subscribe() {
navigator.serviceWorker.ready.then(function (serviceWorkerRegistration) {
serviceWorkerRegistration.pushManager.subscribe()
.then(function (subscription) {
// The subscription was successful
// TODO: Send the subscription.endpoint to your server
// and save it to send a push message at a later date
return sendSubscriptionToServer(subscription);
})
.catch(function (e) {
if (Notification.permission === 'denied') {
// The user denied the notification permission which
// means we failed to subscribe and the user will need
// to manually change the notification permission to
// subscribe to push messages
console.warn('Permission for Notifications was denied');
} else {
// A problem occurred with the subscription; common reasons
// include network errors, and lacking gcm_sender_id and/or
// gcm_user_visible_only in the manifest.
console.error('Unable to subscribe to push.', e);
}
});
});
}
And the service worker looks like this:
self.addEventListener('push', showNotification)
self.addEventListener('notificationclick', closeNotificationAndOpenWindow)
function showNotification(event) {
console.log('Received a push message', event)
var title = 'Yay a message.'
var body = 'We have received a push message.'
var icon = '/images/icon-192x192.png'
var tag = 'simple-push-demo-notification-tag'
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
)
}
function closeNotificationAndOpenWindow(event) {
console.log('On notification click: ', event.notification.tag)
// Android doesn’t close the notification when you click on it
// See: http://crbug.com/463146
event.notification.close()
// This looks to see if the current is already open and
// focuses if it is
event.waitUntil(clients.matchAll({
type: "window"
}).then(function (clientList) {
for (var i = 0; i < clientList.length; i++) {
var client = clientList[i]
if (client.url == '/' && 'focus' in client)
return client.focus()
}
if (clients.openWindow)
return clients.openWindow('/')
}))
}