While implementing the chrome push notification, we were fetching the latest change from our server. While doing so, the service-worker is showing an extra notification with the message
This site has been updated in the background
Already tried with the suggestion posted here
https://disqus.com/home/discussion/html5rocks/push_notifications_on_the_open_web/
But could not find anything useful till now. Is there any suggestion ?
Short Answer: You should use event.waitUntil and pass a promise to it, which returns showNotification eventually. (if you have any other nested promises, you should also return them.)
I was expriencing the same issue but after a long research I got to know that this is because delay happen between PUSH event and self.registration.showNotification(). I only missed return keyword before self.registration.showNotification()`
So you need to implement following code structure to get notification:
var APILINK = "https://xxxx.com";
self.addEventListener('push', function(event) {
event.waitUntil(
fetch(APILINK).then(function(response) {
return response.json().then(function(data) {
console.log(data);
var title = data.title;
var body = data.message;
var icon = data.image;
var tag = 'temp-tag';
var urlOpen = data.URL;
return self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag
})
});
})
);
});
Minimal senario:
self.addEventListener('push', event => {
const data = event.data.json();
event.waitUntil(
// in here we pass showNotification, but if you pass a promise, like fetch,
// then you should return showNotification inside of it. like above example.
self.registration.showNotification(data.title, {
body: data.content
})
);
});
I've run into this issue in the past. In my experience the cause is generally one of three issues:
You're not showing a notification in response to the push
message. Every time you receive a push message on the device, when
you finish handling the event a notification must be left visible on
the device. This is due to subscribing with the userVisibleOnly:
true option (although note this is not optional, and not setting it
will cause the subscription to fail.
You're not calling event.waitUntil() in response to handling the event. A promise should be passed into this function to indicate to the browser that it should wait for the promise to resolve before checking whether a notification is left showing.
For some reason you're resolving the promise passed to event.waitUntil before a notification has been shown. Note that self.registration.showNotification is a promise and async so you should be sure it has resolved before the promise passed to event.waitUntil resolves.
Generally as soon as you receive a push message from GCM (Google Cloud Messaging) you have to show a push notification in the browser. This is mentioned on the 3rd point in here:
https://developers.google.com/web/updates/2015/03/push-notificatons-on-the-open-web#what-are-the-limitations-of-push-messaging-in-chrome-42
So it might happen that somehow you are skipping the push notification though you got a push message from GCM and you are getting a push notification with some default message like "This site has been updated in the background".
This works, just copy/paste/modify. Replace the "return self.registration.showNotification()" with the below code. The first part is to handle the notification, the second part is to handle the notification's click. But don't thank me, unless you're thanking my hours of googling for answers.
Seriously though, all thanks go to Matt Gaunt over at developers.google.com
self.addEventListener('push', function(event) {
console.log('Received a push message', event);
var title = 'Yay a message.';
var body = 'We have received a push message.';
var icon = 'YOUR_ICON';
var tag = 'simple-push-demo-notification-tag';
var data = {
doge: {
wow: 'such amaze notification data'
}
};
event.waitUntil(
self.registration.showNotification(title, {
body: body,
icon: icon,
tag: tag,
data: data
})
);
});
self.addEventListener('notificationclick', function(event) {
var doge = event.notification.data.doge;
console.log(doge.wow);
});
I was trying to understand why Chrome has this requirement that the service worker must display a notification when a push notification is received. I believe the reason is that push notification service workers continue to run in the background even after a user closes the tabs for the website. So in order to prevent websites from secretly running code in the background, Chrome requires that they display some message.
What are the limitations of push messaging in Chrome?
...
You have to show a notification when you receive a push message.
...
and
Why not use Web Sockets or Server-Sent Events (EventSource)?
The advantage of using push messages is that even if your page is closed, your service worker will be woken up and be able to show a notification. Web Sockets and EventSource have their connection closed when the page or browser is closed.
If you need more things to happen at the time of receiving the push notification event, the showNotification() is returning a Promise. So you can use the classic chaining.
const itsGonnaBeLegendary = new Promise((resolve, reject) => {
self.registration.showNotification(title, options)
.then(() => {
console.log("other stuff to do");
resolve();
});
});
event.waitUntil(itsGonnaBeLegendary);
i was pushing notification twice, once in the FCM's onBackgroundMessage()
click_action: "http://localhost:3000/"
and once in self.addEventListener('notificationclick',...
event.waitUntil(clients.matchAll({
type: "window"
}).then...
so i commented click_action, ctrl+f5 to refresh browsers and now it works normal
Related
I am creating a digital signage player that uses Chrome as it's display engine. We need to be able to still muddle along if the network goes down without too much interruption.
Chrome works fine caching images, and I've set the "Exipres" header to be a month after access. I can set the player computer offline and have the app run for days with no problem. If I reboot the machine the right way (Start->Shut Down), caching still works as expected.
The issue is that when Chrome exits abnormally - Either a crash or power loss - on reboot, Chrome ignores the cache and refuses to load images. This happens if I cut power 5 minutes after it loads the page, so content is not expiring.
My guess is that Chrome is set to ignore the cache after an abnormal exit to prevent corrupted cache from continually crashing the browser. However, this behavior is not what I need.
Does anyone know of a command line arg or flag I can set to keep this from happening?
Thanks for your help.
I tried everything I could think of to make Chrome not invalidate the local cache on system failure, and came up empty. There's a few other people who had the same question, and I didn't see an answer.
Here's what I did that made this work, and if someone else is having the same problem, it might be the workaround that you need.
I added a service worker that would cache images. The code below isn't perfect yet, but should be a starting place for someone... (FYI, I learned this 5 minutes ago, so if someone wants to give me a pointer or two on how to make this more elegant, I'm all ears.)
We cache anything that has a response type of "cors" so we cache only images coming from the remote server. Note that your images must be loaded via https for this to work.
Taken (mostly) from: https://developers.google.com/web/fundamentals/getting-started/primers/service-workers
var CACHE_NAME = 'shine_cache';
var urlsToCache = [
'/'
];
self.addEventListener('install', function(event) {
// Perform install steps
event.waitUntil(
caches.open(CACHE_NAME)
.then(function(cache) {
console.log('Opened cache');
return cache.addAll(urlsToCache);
})
);
});
self.addEventListener('fetch', function(event) {
//console.log('Handling fetch event for', event.request);
if (event.request.method == 'POST') {
//console.log("Skipping POST");
event.respondWith(fetch(event.request));
return;
}
if (event.request.headers.get('Accept').indexOf('image') !== -1) {
event.respondWith(
caches.match(event.request)
.then(function(response) {
// Cache hit - return response
if (response) {
console.log("Returning from cache.", event.request);
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) {
console.log("Have a response.", response);
// Check if we received a valid response
if(!response || response.status !== 200 || response.type !== 'cors') {
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) {
console.log("Caching response", event.request);
cache.put(event.request, responseToCache);
});
return response;
}
);
})
);
}
});
I'm developing a website that supports desktop notifications. I'm utilizing GCM and Azure Notification Hub to send push messages to end users. I followed this codelab tutorial. When testing I have found that the push notifications do get displayed on the screen, but the message that I have written in the payload is not shown in the notification. So I again went through the codelab, and they have mentioned a body key in the showNotification method.
Code
self.addEventListener('push', function(event) {
console.log('Push message', event);
var title = 'Push message';
event.waitUntil(
self.registration.showNotification(title, {
body: 'The Message',
icon: 'images/icon.png',
tag: 'my-tag'
}));
});
They have hardcoded "The Message" in the showNotification function. I don't want to hardcode my message in the function since my message won't be the same always and will vary time to time. I want to know how to make the function take the message in payload and display it. Thanks in advance!
Chrome doesn't support push payloads yet, for now you could use them only in Firefox.
The solution is to request the payload from the server (using fetch) once you get a notification.
Something like this:
self.addEventListener('push', function(event) {
event.waitUntil(
fetch('./getPayload?someUniqueID=' + someUniqueID)
.then(function(response) {
return response.text();
})
.then(function(payload) {
self.registration.showNotification('Title', {
body: payload,
});
})
);
});
Obviously, the response can also be in JSON, so that you can specify everything (title, body, icon, etc.).
You can see an example here: https://serviceworke.rs/push-get-payload.html.
Another possible solution is to use the payload when it's available and use fetch otherwise. This way you can take advantage of the feature in Firefox (and in Chrome once available), and have a fallback for browsers that don't support payloads yet.
There's an example in Mercurius.
What is this bug? When sending web pushing browser Google Chrome "sometimes" gives a second message with the text: "This site has been updated in the background."
I want to make it only one message
This text I found in source Chrome
This site has been updated in the background.
github.com/scheib/chromium/blob/master/chrome/app/resources/generated_resources_en-GB.хтб
How to get rid of this message.
The way it works is a feature not a bug.
Here is an issue that explains your situation in Chrome: https://code.google.com/p/chromium/issues/detail?id=437277
And more specific code comment in Chromium code:
https://code.google.com/p/chromium/codesearch#chromium/src/chrome/browser/push_messaging/push_messaging_notification_manager.cc&rcl=1449664275&l=287
What might have happened is some of the push messages sent to the client did not result in showing a notification.
Hope that helps
The reason this often occurs is the promise returned to event.waitUntil() didn't resolve with a notification being shown.
An example that might show the default push notification:
function handlePush() {
// BAD: The fetch's promise isn't returned
fetch('/some/api')
.then(function(response) {
return response.json();
})
.then(function(data) {
// BAD: the showNotification promise isn't returned
showNotification(data.title, {body: data.body});
});
}
self.addEventListener(function(event) {
event.waitUntil(handlePush());
});
Instead you could should write this as:
function handlePush() {
// GOOD
return fetch('/some/api')
.then(function(response) {
return response.json();
})
.then(function(data) {
// GOOD
return showNotification(data.title, {body: data.body});
});
}
self.addEventListener(function(event) {
const myNotificationPromise = handlePush();
event.waitUntil(myNotificationPromise);
});
The reason this is all important is that browsers wait for the promise passed into event.waitUntil to resolve / finish so they know the service worker needs to be kept alive and running.
When the promise resolves for a push event, chrome will check that a notification has been shown and it falls into a race condition / specific circumstance as to whether Chrome shows this notification or not. Best bet is to ensure you have a correct promise chain.
I put some extra notes on promises on this post (See: 'Side Quest: Promises' https://gauntface.com/blog/2016/05/01/push-debugging-analytics)
I'm testing under Chrome Version 42.0.2311.152m and I want to implement to open a window on a notificationclick like in this example: (source: https://developer.mozilla.org/en-US/docs/Web/API/WindowClient
)
self.addEventListener('notificationclick', function(event) {
console.log('On notification click: ', event.notification.tag);
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('/');
}));
});
My filestructure is like:
https://myurl.no-ip.org/app/index.html
https://myurl.no-ip.org/app/manifest.json
https://myurl.no-ip.org/app/service-worker.js
I have the issue that I always get an
InvalidAccessError
when calling clients.openWindow('/') or clients.openWindow('https://myurl.no-ip.org/app/index.html') in the service-worker.js, I receive the error:
{code: 15,
message: "Not allowed to open a window.",
name: "InvalidAccessError"}
The "return client.focus()" line is never reached because the client.url is never just '/'.
Looking at
clients.matchAll({type: "window"})
.then(function (clientList) {
console.log(clientList[0])});
I see my current WindowClient:
{focused: false,
frameType: "top-level",
url: "https://myurl.no-ip.org/app/index.html",
visibilityState: "hidden" }
The properties 'focused' and 'visibilityState' are correct and change correctly.
By doing a manual focus call
clients.matchAll({type: "window"})
.then(function (clientList) {
clientList[0].focus()});
I receive the error:
{code: 15,
message: "Not allowed to focus a window.",
name: "InvalidAccessError"}
I think the problem is that url is not just '/'. Do you have any ideas for that?
Thank you very much!
Best regards
Andi
Your code works fine for me, so I'll explain the requirements for using openWindow / focus, and how you can avoid the "Not allowed to [open|focus] a window" error message.
clients.openWindow() and windowClient.focus() are only allowed after clicking the notification (in Chrome 47 at least), and at most one of these methods can be called, for the duration of the click handler. This behavior was specified in https://github.com/slightlyoff/ServiceWorker/issues/602.
If your openWindow / focus call is rejected with error message
"Not allowed to open a window." for openWindow
"Not allowed to focus a window." for focus
then you didn't satisfy the requirements of openWindow / focus. For example (all points also apply to focus, not just openWindow).
openWindow was called while the notification wasn't clicked.
openWindow was called after the notificationclick handler returned, and you did not call event.waitUntil with a promise.
openWindow was called after the promise passed to event.waitUntil was resolved.
The promise was not resolved, but it took "too long" (10 seconds in Chrome), so the temporary permission to call openWindow expired.
It is really necessary that openWindow / focus is called at most once, and before the notificationclick handler finishes.
As I said before, the code in the question works, so I'll show another annotated example.
// serviceworker.js
self.addEventListener('notificationclick', function(event) {
// Close notification.
event.notification.close();
// Example: Open window after 3 seconds.
// (doing so is a terrible user experience by the way, because
// the user is left wondering what happens for 3 seconds.)
var promise = new Promise(function(resolve) {
setTimeout(resolve, 3000);
}).then(function() {
// return the promise returned by openWindow, just in case.
// Opening any origin only works in Chrome 43+.
return clients.openWindow('https://example.com');
});
// Now wait for the promise to keep the permission alive.
event.waitUntil(promise);
});
index.html
<button id="show-notification-btn">Show notification</button>
<script>
navigator.serviceWorker.register('serviceworker.js');
document.getElementById('show-notification-btn').onclick = function() {
Notification.requestPermission(function(result) {
// result = 'allowed' / 'denied' / 'default'
if (result !== 'denied') {
navigator.serviceWorker.ready.then(function(registration) {
// Show notification. If the user clicks on this
// notification, then "notificationclick" is fired.
registration.showNotification('Test');
});
}
});
}
</script>
PS. Service workers are still in development, so it's worth mentioning that I've verified that the above remarks are correct in Chrome 49, and that the example works in Chrome 43+ (and opening / instead of https://example.com also works in Chrome 42).
This worked for me
You should define Promise that will fire when your operation is finished.
Example bellow shows how to return chained Promise
First Promise returns list of windows. If it's not empty we are focusing one and returning Promise.resolve() - which is resolve immediately.
If no windows found we are returning next chained Promise - first on opens new window second tries to focus it.
addEventListener('notificationclick', (event) => {
console.log('---- notification clicked ----')
console.log(event)
//using notification data to constract specific path
const data = event.notification.data
console.log(data)
let url = 'https://exmaple.com'
if(data){
url += data['business'] ?
`/business/messages/${data['respondent']}` :
`/store/${data['respondent']}/questions`
}
console.log('new window url: ' + url)
event.notification.close()
//event should wait until we done
event.waitUntil(
//do we have some windows of our app?
self.clients.matchAll({includeUncontrolled: true, type: 'window'})
.then(list=>{
console.log('total clients: '+list.length)
if(list.length === 0){
//no windows of our app. We will open new one
console.log('no clients found')
return self.clients.openWindow(url)
.then((windowClient) => {
//we should focus new window and return Promise to terminate our event
return windowClient ? windowClient.focus() : Promise.resolve()
})
}
const client = list[0]
console.log(client)
//we have a window of our app. Let's focus it and return Promise
client.focus()
console.log('--window focused---')
return Promise.resolve()
}))
})
I know hat it is possible, but I am not quite sure how to do it the 'right' way, as to ensure there are no conflicts.
I came across this question: Cannot call functions to content scripts by clicking on image . But it is so convoluted with random comments that it's hard to understand what the corrected way was.
Use case:
Html pages have a div on the page where they expect anyone using the Chrome extension to inject a picture. When users click on he picture, I want to somehow notify an event script. So I know I need to register a listener so the code inserted messages the event script.
Can I get some indication on what code to inject through the content script? I saw that sometimes injecting jquery directly is advised.
I am trying to avoid having the html page to post a message to itself so it can be intercepted. Thanks
With the help of Jquery something like this would capture the image onclick event and allow you to pass a message to a background page in the Chrome Extension:
$("img").click(function(){
var imageSrc = $(this).attr("src");
//Post to a background page in the Chrome Extension
chrome.extension.sendMessage({ cmd: "postImage", data: { imgSrc: imageSrc } }, function (response) {
return response;
});
});
Then in your background.js create a listener for the message:
chrome.extension.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.cmd == "postImage") {
var imageSrc = request.data.imgSrc;
}
});