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('/')
}))
}
Related
I'm writing my first react app with create-react-app but encountered a problem when setting up pwa.
I was trying to show a snackbar on new service-worker registration, but i won't get it working even with the template code.
Below is my service-worker.js under ./src/ (the same as the one in cra template)
/* eslint-disable no-restricted-globals */
// This service worker can be customized!
// See https://developers.google.com/web/tools/workbox/modules
// for the list of available Workbox modules, or add any other
// code you'd like.
// You can also remove this file if you'd prefer not to use a
// service worker, and the Workbox build step will be skipped.
import { clientsClaim } from 'workbox-core';
import { ExpirationPlugin } from 'workbox-expiration';
import { precacheAndRoute, createHandlerBoundToURL } from 'workbox-precaching';
import { registerRoute } from 'workbox-routing';
import { StaleWhileRevalidate } from 'workbox-strategies';
clientsClaim();
// Precache all of the assets generated by your build process.
// Their URLs are injected into the manifest variable below.
// This variable must be present somewhere in your service worker file,
// even if you decide not to use precaching. See https://cra.link/PWA
precacheAndRoute(self.__WB_MANIFEST);
// Set up App Shell-style routing, so that all navigation requests
// are fulfilled with your index.html shell. Learn more at
// https://developers.google.com/web/fundamentals/architecture/app-shell
const fileExtensionRegexp = new RegExp('/[^/?]+\\.[^/]+$');
registerRoute(
// Return false to exempt requests from being fulfilled by index.html.
({ request, url }) => {
// If this isn't a navigation, skip.
if (request.mode !== 'navigate') {
return false;
} // If this is a URL that starts with /_, skip.
if (url.pathname.startsWith('/_')) {
return false;
} // If this looks like a URL for a resource, because it contains // a file extension, skip.
if (url.pathname.match(fileExtensionRegexp)) {
return false;
} // Return true to signal that we want to use the handler.
return true;
},
createHandlerBoundToURL(process.env.PUBLIC_URL + '/index.html')
);
// An example runtime caching route for requests that aren't handled by the
// precache, in this case same-origin .png requests like those from in public/
registerRoute(
// Add in any other file extensions or routing criteria as needed.
({ url }) => url.origin === self.location.origin && url.pathname.endsWith('.png'), // Customize this strategy as needed, e.g., by changing to CacheFirst.
new StaleWhileRevalidate({
cacheName: 'images',
plugins: [
// Ensure that once this runtime cache reaches a maximum size the
// least-recently used images are removed.
new ExpirationPlugin({ maxEntries: 50 }),
],
})
);
// This allows the web app to trigger skipWaiting via
// registration.waiting.postMessage({type: 'SKIP_WAITING'})
self.addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
self.skipWaiting();
}
});
// Any other custom service worker logic can go here.
And here is my serviceWorkerRegistration.js under ./src/utils. Also nearly the same as the originally shipped one w/ location change and one console.log msg modification (I'm sure ive correctly imported it as the onSuccess callback was functional)
// This optional code is used to register a service worker.
// register() is not called by default.
// 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 subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://cra.link/PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
);
export function register(config) {
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.href);
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/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://cra.link/PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available! Click the pop-up notification to update it!'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} 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.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.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, config);
}
})
.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();
})
.catch((error) => {
console.error(error.message);
});
}
}
I'm registering sw with a useEffect hook:
useEffect(() => {
registerSW({
onSuccess: () => setShowSuccess(true),
onUpdate: sw => {
console.log('onUpdate triggered')
setShowReload(true)
setSW(sw)
},
});
}, []);
The onSuccess callback was working perfectly but the problem was, the onUpdate callback simply wont be triggered. Also the console.log msg in serviceWorkerRegistration itself('New content is available! Click the pop-up notification to update it!') won't appear.
My guess was that some browser apis have changed and the onupdatefound api will no longer work. But I'm not sure if it was because my app call sw to register on every page load, and at that time, the service worker registered is already the new sw.js so that there will not be an update.
I've checked my previous project and found that i was listening on navigator.serviceWorker's controllerchange. I'm not sure which method is correct and how can i implement this feature in react. And im curious about when the onUpdate callback is called and if i was using it correctly.
Thanks in advance!
After some console.log based debugging, i found that this bug was caused by sw registration module loaded after window.onload, causing the template code no longer useful as the registerValidSW function will only be triggered when window.onload target is reached.
I've modified the window.onload part to this to resolve this issue:
const callRegisterValidSW = () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service worker.'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
};
if (document.readyState === "complete") {
callRegisterValidSW();
} else {
window.addEventListener('load', callRegisterValidSW);
}
If someone here sees the maintainer of https://github.com/cra-template/pwa/, we may add some comment around this part in the shipped swRegistration function.
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');
}
}
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');
With Firebase Cloud Messaging (for web), how can I generate the notification that appears when the webpage is closed or in the background, but when I'm actually focused on the webpage?
It's my understanding that messaging.onMessage(...) is where I handle incoming messages when the page is in focus, but I can't seem to find documentation on how I could still create the notification pop-ups as though the page were in the background.
Thanks for your time!
handle incoming messges by Notification API
messaging.onMessage(function(payload) {
const notificationTitle = payload.notification.title;
const notificationOptions = {
body: payload.notification.body,
icon: payload.notification.icon,
};
if (!("Notification" in window)) {
console.log("This browser does not support system notifications");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification(notificationTitle,notificationOptions);
notification.onclick = function(event) {
event.preventDefault(); // prevent the browser from focusing the Notification's tab
window.open(payload.notification.click_action , '_blank');
notification.close();
}
}
});
Notification is deprecated.
send message to service worker
messaging.onMessage(function(payload) {
local_registration.active.postMessage(payload);
}
receive message and show push from sw.js
self.addEventListener('notificationclick', function(event) {
console.log('[firebase-messaging-sw.js] Received notificationclick event ', event);
var click_action = event.notification.data;
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 == click_action && 'focus' in client)
return client.focus();
}
if (clients.openWindow)
return clients.openWindow(click_action);
}));
});
const showMessage = function(payload){
console.log('showMessage', payload);
const notificationTitle = payload.data.title;
const notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
image: payload.data.image,
click_action: payload.data.click_action,
data:payload.data.click_action
};
return self.registration.showNotification(notificationTitle,notificationOptions);
}
messaging.setBackgroundMessageHandler(showMessage);
self.addEventListener('message', function (evt) {
console.log("self",self);
showMessage( evt.data );
})
Since Notification is deprecated in Android, you should use Firebase serviceWorker registration to show the notification.
As of Feb 2020, it looks like Firebase registers its serviceWorker with the '/firebase-cloud-messaging-push-scope' scope (as can be seen in chrome devtools -> Application -> Service Workers)
To use that, you can do:
messaging.onMessage(function(payload) {
console.log("onMessage: ", payload);
navigator.serviceWorker.getRegistration('/firebase-cloud-messaging-push-scope').then(registration => {
registration.showNotification(
payload.notification.title,
payload.notification
)
});
});
More clean approach would be:
messaging.onMessage(payload => {
const {title, ...options} = payload.notification;
navigator.serviceWorker.ready.then(registration => {
registration.showNotification(title, options);
});
});
Am using FCM to handle notifications, it works fine up until when I need to update my UI from the firebase-messaging-sw.js when my web app is in the background.
My first question is: is it possible to update my web app UI in the background (When user is not focused on the web app) through a service worker
Secondly, if so, how? because I tried a couple of things and its not working, obviously am doing something wrong and when it does work, my web app is in the foreground. What am I doing wrong?
My codes are below.
my-firebase-service-sw.js
// [START initialize_firebase_in_sw]
// Give the service worker access to Firebase Messaging.
// Note that you can only use Firebase Messaging here, other Firebase
libraries
// are not available in the service worker.
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-app.js');
importScripts('https://www.gstatic.com/firebasejs/4.1.1/firebase-messaging.js');
// My Custom Service Worker Codes
var CACHE_NAME = 'assembly-v0.1.3.1';
var urlsToCache = [
'/',
'lib/vendors/bower_components/animate.css/animate.min.css',
'lib/vendors/bower_components/sweetalert/dist/sweetalert.css',
'lib/css/app_1.min.css',
'lib/css/app_2.min.css',
'lib/css/design.css'
];
var myserviceWorker;
var servicePort;
// Install Service Worker
self.addEventListener('install', function (event) {
console.log('installing...');
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function (cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
console.log('installed...');
});
// Service Worker Active
self.addEventListener('activate', function (event) {
console.log('activated!');
// here you can run cache management
var cacheWhitelist = [CACHE_NAME];
event.waitUntil(
caches.keys().then(function (cacheNames) {
return Promise.all(
cacheNames.map(function (cacheName) {
if (cacheWhitelist.indexOf(cacheName) === -1) {
return caches.delete(cacheName);
}
})
);
})
);
});
self.addEventListener('fetch', function (event) {
event.respondWith(
caches.match(event.request)
.then(function (response) {
// Cache hit - return response
if (response) {
return response;
}
// IMPORTANT: Clone the request. A request is a stream and
// can only be consumed once. Since we are consuming this
// once by cache and once by the browser for fetch, we need
// to clone the 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;
}
// IMPORTANT: Clone the response. A response is a stream
// and because we want the browser to consume the response
// as well as the cache consuming the response, we need
// to clone it so we have two streams.
var responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(function (cache) {
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
});
self.addEventListener('message', function (event) {
console.log("SW Received Message: " + event.data);
// servicePort = event;
event.ports[0].postMessage("SW Replying Test Testing 4567!");
});
myserviceWorker = self;
// Initialize the Firebase app in the service worker by passing in the
// messagingSenderId.
firebase.initializeApp({
'messagingSenderId': '393093818386'
});
// Retrieve an instance of Firebase Messaging so that it can handle background
// messages.
const messaging = firebase.messaging();
// [END initialize_firebase_in_sw]
// If you would like to customize notifications that are received in the
// background (Web app is closed or not in browser focus) then you should
// implement this optional method.
// [START background_handler]
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
// send to client
console.log('Sending data to notification');
try {
myserviceWorker.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
console.log('sending to client ' + client);
client.postMessage({
"msg": "401",
"dta": payload.data
});
})
});
} catch (e) {
console.log(e);
}
const notificationTitle = payload.data.title;;
const notificationOptions = {
body: payload.data.body,
icon: payload.data.icon,
click_action: "value"
};
return self.registration.showNotification(notificationTitle,
notificationOptions);
});
// [END background_handler]
In my main javascript file, which receives the payload. it receives it when the application is in the foreground. My major concern and problem is receiving payload when the application is in the background, all activities on foreground works just fine.
It is possible to update the UI even your website is opening but unfocused.
Just add enable option includeUncontrolled when you get all client list.
Example:
messaging.setBackgroundMessageHandler(function (payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
self.clients.matchAll({includeUncontrolled: true}).then(function (clients) {
console.log(clients);
//you can see your main window client in this list.
clients.forEach(function(client) {
client.postMessage('YOUR_MESSAGE_HERE');
})
})
});
In your main page, just add listener for message from service worker.
Ex:
navigator.serviceWorker.addEventListener('message', function (event) {
console.log('event listener', event);
});
See Clients.matchAll() for more details.