registration.showNotification is not a function - javascript

I'm using serviceworker-webpack-plugin to create a service worker in my reactjs apps.
I've followed the example to register the service worker in the main thread. I've learnt that Html5 Notification doesn't work on Android chrome, so I used registration.showNotification('Title', { body: 'Body.'}); instead of new Notification('...') to push notifications. But when I tested it on the desktop chrome, it throws this error
registration.showNotification is not a function
Is the registration.showNotification only available on Android chrome but not on the desktop?
public componentDidMount(){
if ('serviceWorker' in navigator &&
(window.location.protocol === 'https:' || window.location.hostname === 'localhost')
) {
const registration = runtime.register();
registerEvents(registration, {
onInstalled: () => {
registration.showNotification('Title', { body: 'Body.'});
}
})
} else {
console.log('serviceWorker not available')
}
}

runtime.register() returns a JavaScript Promise, which is why you are getting a not a function error because Promises don't have a showNotification() method.
Instead, you'd have to chain a .then() callback to it in order to get the actual registration object (or use async/await, which is also cool).
runtime.register().then(registration => {
registration.showNotification(...);
})

Below solution worked for me. Can try.
The main root cause of .showNotification() not firing is service worker. Service worker is not getting registered. So it wont call registration.showNotification() method.
Add service-worker.js file to your project root directory
You can download service-worker.js file from Link
Use below code to register service worker and fire registration.showNotification() method.
const messaging = firebase.messaging();
messaging.onMessage(function (payload) {
console.log("Message received. ", payload);
NotisElem.innerHTML = NotisElem.innerHTML + JSON.stringify(payload);
//foreground notifications
if ('serviceWorker' in navigator) {
navigator.serviceWorker
.register('./service-worker.js', { scope: './' })
.then(function (registration) {
console.log("Service Worker Registered");
setTimeout(() => {
registration.showNotification(payload.data.title, {
body: payload.data.body,
data: payload.data.link
});
registration.update();
}, 100);
})
.catch(function (err) {
console.log("Service Worker Failed to Register", err);
})
}
});

Related

How to get FCM token?

I am trying to get FCM token in react js application.
First thing i tried is to use messaging.useServiceWorker(registration) then use messaging.getToken() and it's working fine on localhost for firefox and google chrome, but on an HTTPS live server it works fine on firefox but in chrome it throws an error: DOMException: Failed to execute 'subscribe' on 'PushManager': Subscription failed - no active Service Worker.
I saw firebase docs and found that messaging.useServiceWorker is deprecated now and I have to use messaging.getToken({ serviceWorkerRegistration }) instead but it throws an error: FirebaseError: Messaging: We are unable to register the default service worker. Failed to register a ServiceWorker for scope ('http://localhost:3000/firebase-cloud-messaging-push-scope') with script ('http://localhost:3000/firebase-messaging-sw.js'): The script has an unsupported MIME type ('text/html'). (messaging/failed-service-worker-registration).
Notes
firebase-messaging-sw.js File is under the public directory.
firebase-messaging-sw.js File is empty.
This how I register the service worker:
export const registerServiceWorker = () => {
if ("serviceWorker" in navigator) {
return new Promise((resolve, reject) => {
navigator.serviceWorker
.register(process.env.PUBLIC_URL + "/firebase-messaging-sw.js")
.then(function (registration) {
console.log("[registration]", registration)
// messaging.useServiceWorker(registration)
resolve(registration);
})
.catch(function (err) {
console.log("[ERROR registration]: ", err)
reject(null);
});
});
} else {
console.log("SERVICE WORKER NOT IN THE BROWSER")
}
};
What should I do to get FCM token in a write way?
I have found a solution for this issue here is my code:
class Firebase {
constructor() {
if (firebase.apps.length) return;
firebase.initializeApp(config);
this.auth = firebase.auth();
this.messaging = firebase.messaging();
navigator.serviceWorker.getRegistrations().then((registrations) => {
if (registrations.length) {
[this.registration] = registrations;
return;
}
navigator.serviceWorker
.register("/firebase-message-sw.js")
.then((registration) => {
this.registration = registration;
});
});
}
async askNotificationPermission() {
try {
const token = await this.messaging.getToken({
serviceWorkerRegistration: this.registration,
});
return token;
} catch (error) {
console.error("[FIREBASE ERROR]: ", error);
return null;
}
}
}
And I am firing askNotificationPermission function with a click action.

How to respond to fetch event for a navigation in service worker?

I have created a SPA (Single Page Application) with Create-React-App. I added a service worker to it so that the SPA could load when there is no network connection. The service worker successfully caches all the resources but fails respond when there is no network connection. I've tried many things but just couldn't get it to serve the assets to get offline capabilities. It gives the following error message:
Uncaught (in promise) TypeError: Failed to fetch
My service worker registers successfully and also caches assets successfully. Service Worker code:
const thingsToCache = [
'index.html',
'static/js/1.cb2fedf5.chunk.js',
'static/js/main.5e7fdc75.chunk.js',
'static/js/runtime~main.229c360f.js',
'static/css/main.ca6d346b.chunk.css',
'static/media/roboto.18d44f79.ttf',
'static/media/comfortaa.7d0400b7.ttf',
];
this.addEventListener('install', event => {
event.waitUntil(
caches.open('v1').then(cache => {
return cache.addAll(thingsToCache);
})
);
});
this.addEventListener('fetch', event => {
//respond to fetch requests here
caches
.match(event.request)
.then(cachedRes => {
if (cachedRes) {
event.respondWith(cachedRes);
} else {
throw new Error('No match found in cache!');
}
})
.catch(() => {
return fetch(event.request);
});
});
If you need the assets, then here's the link:
https://github.com/Twaha-Rahman/pwa-problem-assets
Thanks for all of your help!
You have an error in your fetch event listener. You need to call event.respondWith instead of event.waitUntil and it has to be at the top level. See below for a slighly amended version.
this.addEventListener('fetch', event => {
event.respondWith(
caches
.match(event.request)
.then(cachedRes => {
if (cachedRes) {
return cachedRes;
} else {
throw new Error('No match found in cache!');
}
})
.catch(() => {
return fetch(event.request);
});
)
});
More details: https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers

active serviceWorker is not sending message to waiting serviceWorker

What am I trying to do?
I am creating a progressive web application (PWA) and in order to send upgrade the app correctly, I am working on a step where the user is notified and once the user says "upgrade", the serviceWorker calls skipWaiting()
What have I done so far?
I am following up a nice article https://redfin.engineering/how-to-fix-the-refresh-button-when-using-service-workers-a8e27af6df68 to achieve this
In order to remove the complexity, I am only testing sending of messages between serviceWorkers to see how skipWaiting works. I am using create-react-app (v"react": "^16.5.2",) which comes with workbox bundled as plugin.
My current registerServiceWorker.js looks like
// 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 (process.env.NODE_ENV === 'production' && '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}/service-worker.js`;
if (!isLocalhost) {
// Is not local host. Just register service worker
registerValidSW(swUrl);
} else {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
}
});
window.addEventListener('message', messageEvent => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (messageEvent.data === 'skipWaiting') {
console.log('skipWaiting');
return navigator.serviceWorker.getRegistration(swUrl)
.then(registration => registration.skipWaiting());
}
console.log(`message=${messageEvent.data}`);
});
let refreshingPage;
navigator.serviceWorker.addEventListener('controllerchange', () => {
console.log('refreshing page now');
if (refreshingPage) return;
refreshingPage = true;
window.location.reload();
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
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.');
navigator.serviceWorker.controller.postMessage('skipWaiting');
} 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();
});
}
}
As you see, when a new serviceWorker is installed, I am sending a postMessage as
console.log('>> New content is available; please refresh.');
navigator.serviceWorker.controller.postMessage('skipWaiting');
and I am expecting the message to be handled with the function
window.addEventListener('message', messageEvent => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (messageEvent.data === 'skipWaiting') {
console.log('skipWaiting');
return navigator.serviceWorker.getRegistration(swUrl)
.then(registration => registration.skipWaiting());
}
console.log(`message=${messageEvent.data}`);
});
Then, I deploy my changes so that this serviceWorker is ready.
Then, I make changes to my application (index.html) and now when I deploy, I see multiple messages being logged, but none with skipWaiting
message=
registerServiceWorker.js:53 message=!_{"h":"I0_1548194465894"}
registerServiceWorker.js:53 message=!_{"s":"/I0_1548194465894::_g_rpcReady","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":1,"a":[null],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[1,[null]],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[2,[null]],"g":false}
registerServiceWorker.js:53 message=!_{"s":"/I0_1548194465894::_g_restyleMe","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":2,"a":[{"setHideOnLeave":false}],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[4,[null]],"g":false}
registerServiceWorker.js:53 message=!_{"s":"/I0_1548194465894::authEvent","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":3,"a":[{"type":"authEvent","authEvent":{"type":"unknown","eventId":null,"urlResponse":null,"sessionId":null,"postBody":null,"tenantId":null,"error":{"code":"auth/no-auth-event","message":"An internal error has occurred."}}}],"g":false}
registerServiceWorker.js:53 message=!_{"s":"__cb","f":"I0_1548194465894","r":"I0_1548194465894","t":"32296067","c":null,"a":[3,[true]],"g":false}
What am I doing wrong here?
There a few things wrong
navigator.serviceWorker.controller.postMessage('skipWaiting');
This is sending a message from window to worker, but you're waiting for the message on the window
controller -> is not the worker instance. postMessage() would send the message to that worker.
You don't want to post to that worker though - it's your current active worker, while you want to notify the newly installed worker that haven't taken over yet
Even if you manage to run the code in the message event handler it won't do
if (messageEvent.data === 'skipWaiting') {
console.log('skipWaiting');
return navigator.serviceWorker.getRegistration(swUrl)
.then(registration => registration.skipWaiting());
}
There's no registration.skipWaiting() method. We call skipWaiting inside the service-worker.js code
In your case you would add an event listener for message inside the worker code and intercept the "Skip Waiting" message there
worker code
self.addEventListener('message', (event) => {
if (event.data == 'skipWaiting') self.skipWaiting();
});
window code (changes only)
Send a message to the now installed worker to skip waiting and take over
console.log('>> New content is available; please refresh.');
installingWorker.postMessage('skipWaiting');

How do I use service workers with an htaccess protected directory?

I'm trying some basic service workers. The service worker itself will work normally the first time the service worker is registered. The problem I always get is once the person revisits the website in the future (e.g. the following day) and tries to access a .htaccess/.htpasswd protected directory. Instead of getting the dialog box, as normal, they go straight to a 401 error.
This is how I am registering the service worker in script tags in the HTML.
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.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);
});
});
}
I have tried a couple of different methods in the sw.js itself and every time I get the same error. This is one from the Google airhorner example, I believe...
self.addEventListener('install', e => {
const timeStamp = Date.now();
e.waitUntil(
caches.open('somename').then(cache => {
return cache.addAll([
`/`,
`/index.html`,
`/css/tour-2.css`
])
.then(() => self.skipWaiting());
})
);
});
self.addEventListener('activate', event => {
event.waitUntil(self.clients.claim());
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request, {ignoreSearch: true}).then(response => {
return response || fetch(event.request);
})
);
});
Does anyone know if it is possible to use service workers with websites with .htaccess protected directories?
Thanks.
One way to cure is:
self.addEventListener('fetch', event => {
// Exclude admin panel.
if (0 === event.request.url.indexOf("https://www.my-site.com/my-protected-area")) {
return;
}
It should help.
Source: TIV.NET

Server worker being registered twice

I'm using FCM web notification service, when I am calling the register function:
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js').then(function(registration) {
// Registration was successful
console.log('ServiceWorker registration successful with scope: ', registration.scope);
}).catch(function(err) {
// registration failed :(
console.log('ServiceWorker registration failed: ', err);
});
});
}
The service worker is registered twice, one because of this function, and one by the FCM script. This is my service worker code:
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/3.5.2/firebase-messaging.js');
'messagingSenderId': '<my senderid>'
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
self.addEventListener('notificationclick', function (event) {
event.notification.close();
var promise = new Promise(function (resolve) {
setTimeout(resolve, 1000);
}).then(function () {
return clients.openWindow(payload.data.locator);
});
event.waitUntil(promise);
});
var notificationTitle = payload.data.title;
var notificationOptions = {
body: payload.data.body,
icon: payload.data.icon
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
One more thing, when I send test notifications, and I click the first message and it opens the URL correctly, but in the same instance of Chrome, all other messages I click open the URL of the first message. This problem does not happen on Firefox, just Chrome. I am using chrome version 55
With the firebase messaging SDK you don't need to call register.
If you call register, you can make the SDK use your service worker by calling useServiceWorker() (See: https://firebase.google.com/docs/reference/js/firebase.messaging.Messaging#useServiceWorker)
if ('serviceWorker' in navigator) {
window.addEventListener('load', function() {
navigator.serviceWorker.register('/sw.js')
.then(function(registration) {
messaging.useServiceWorker(registration);
});
});
}
The reason the SDK registers the service worker for you is that it sets a scope that will prevent it from interfering with any other service workers you might have.
Regarding your second issue, are you sending different URLs or are they the same URLs?

Categories

Resources