Receive notifications 24/7 via service-worker.js in Chrome - javascript

I've been doing push notifications,
when I register the page then test it via curl commands, i received the notification!
However after a while (maybe 1-2 minutes), when I close the tab that the push notifications scope has been registered, then test the notifications again, i cant receive the notifications. This usually happens more in google Chrome in mobiles.
The workaround I did in here is that i need to go to the page of the scoped page first, then when I test the notification again, it now works. Though i cant have this because I need the clients to receive notifications without being on the site all the time.
Here are my push event in service worker
self.addEventListener('push', function(event) {
var SOME_API_ENDPOINT = "https://somewhre.com/push_notification_api/service_worker_endpoint";
event.waitUntil(
fetch(SOME_API_ENDPOINT, {
method: 'post',
headers: {
"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"
},
body: 'subscription_id=' + subscriptionId
}).then(function(response) {
return response.json().then(function(data) {
var title = data.notification.title;
var message = data.notification.message;
var icon = base + "assets/images/logo_notification.png";
var notificationTag = data.notification.url;
// var notificationTag = 'https://google.com'; //data.notification.tag;
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: notificationTag
});
});
})
);
});
How do i make my service workers 24/7 even though i am not in the page of the scope in the registration of SW?
Do I need to use eventListeners 'install', 'activate' and 'fetch'?

Service workers appear to persist but really don't - a new instance spins up in response to events. This will be the case when restarting on mobile to handle a push event, but there are also suggestions that busy service workers should allow multiple instances in parallel.
Your subscriptionId is a global parameter, so it may well be undefined by the time your push event fires.
To fix this you need to retain the subscriptionId in some kind of storage (mayber IndexedDB).
I also think in order for a service worker to persist it needs to have an install event and that event needs to succeed.

Related

Push API: Mobile browsers don't receive push notifications anytime. Only when SW is running

I am developing a PWA with node.js.
It is installable and it runs nicely.
Lately I tried to implement Push Notifications like explained here:
https://developer.mozilla.org/en-US/docs/Web/Progressive_web_apps/Re-engageable_Notifications_Push
This is the express route to trigger a notification for all subscriptions (only for testing purposes)
app.get('/notifyall/', function(req, res) {
subscriptions.forEach(subObject => {
webPush.sendNotification(subObject.sub, "notify All");
})
res.send("sent");
});
This is the eventhandler in the serviceworker
self.addEventListener('push', function(event) {
const payload = event.data ? event.data.text() : 'no payload';
console.debug("sw push event");
event.waitUntil(
self.registration.showNotification('ServiceWorker Cookbook', {
body: payload,
renotify: true,
tag: 'test'
})
);
});
this is how I register a subscription to the push service
sub = navigator.serviceWorker.ready
.then(function(registration)
{
return registration.pushManager.getSubscription()
.then(async function(subscription)
{
console.debug("pwasetup: getSubscription");
if (subscription) {
console.debug("sub found");
sub = subscription;
return subscription;
}
const response = await fetch('/vapidPublicKey/');
const vapidPublicKey = await response.text();
// Chrome doesn't accept the base64-encoded (string) vapidPublicKey yet
const convertedVapidKey = frontendfuncs.urlBase64ToUint8Array(vapidPublicKey);
var newSub = registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: convertedVapidKey
});
console.debug("sub: " + JSON.stringify(newSub));
return newSub;
});
})
.then(function(subscription)
{
// Send the subscription details to the server using the Fetch API.
console.debug("sub: " + JSON.stringify(subscription));
Notification.requestPermission(function(result)
{
if(result === "granted")
{
fetch('/register/', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
subscription: subscription
}),
});
}
})
});
});
I tested receiving push notifications with three different mobile browsers on my samsung galaxy S10+ in Chrome, Firefox and Internet (Samsung).
(Of course in the installed/A2HS version of my app. The push notification was triggerd by the code in the /notifyall/ route, mentioned above)
When the app is open, or the sw still in running state, the notifcation is received and displayed without any problems.
But after that things become less easy in Chrome for Android and Samsung Internet:
Chrome:
Firing a notification like 2 minutes after app being closed, the notification will not be displayed, till I open the PWA again. (Bonus question: Why after 2 minutes? I thougt chrome closes sw after 30 seconds)
Internet (Samsung):
After the sw is stopped, the notification arrives on display unlock. Not earlier.
Firefox:
Seems to get the push notification at any time and displays it, as it should.
Does anyone know, what here the problems are in Chrome and Samsung Internet?
I just want to notify my users anytime. Not at certain circumstances...
Thanks!
Problem solved. Easier than it sounds...
Since the PWA needs to be installed/A2HS to use the feature of receiving notifications at any time, I tried to give all the needed settings to the PWA itself (notifications allowed, background optimization disabled, etc.).
Therefore I never thought of, disabling background optimization for the browser-main-app too, which is necessary to wake up the serviceworker of the PWA.
All three browsers work now as intended.

Refresh page after load on cache-first Service Worker

I'm currently considering adding service workers to a Web app I'm building.
This app is, essentially, a collection manager. You can CRUD items of various types and they are usually tightly linked together (e.g. A hasMany B hasMany C).
sw-toolbox offers a toolbox.fastest handler which goes to the cache and then to the network (in 99% of the cases, cache will be faster), updating the cache in the background. What I'm wondering is how you can be notified that there's a new version of the page available. My intent is to show the cached version and, then, if the network fetch got a newer version, to suggest to the user to refresh the page in order to see the latest edits. I saw something in a YouTube video a while ago but the presenter gives no clue of how to deal with this.
Is that possible? Is there some event handler or promise that I could bind to the request so that I know when the newer version is retrieved? I would then post a message to the page to show a notification.
If not, I know I can use toolbox.networkFirst along with a reasonable timeout to make the pages available even on Lie-Fi, but it's not as good.
I just stumbled accross the Mozilla Service Worker Cookbooj, which includes more or less what I wanted: https://serviceworke.rs/strategy-cache-update-and-refresh.html
Here are the relevant parts (not my code: copied here for convenience).
Fetch methods for the worker
// On fetch, use cache but update the entry with the latest contents from the server.
self.addEventListener('fetch', function(evt) {
console.log('The service worker is serving the asset.');
// You can use respondWith() to answer ASAP…
evt.respondWith(fromCache(evt.request));
// ...and waitUntil() to prevent the worker to be killed until the cache is updated.
evt.waitUntil(
update(evt.request)
// Finally, send a message to the client to inform it about the resource is up to date.
.then(refresh)
);
});
// Open the cache where the assets were stored and search for the requested resource. Notice that in case of no matching, the promise still resolves but it does with undefined as value.
function fromCache(request) {
return caches.open(CACHE).then(function (cache) {
return cache.match(request);
});
}
// Update consists in opening the cache, performing a network request and storing the new response data.
function update(request) {
return caches.open(CACHE).then(function (cache) {
return fetch(request).then(function (response) {
return cache.put(request, response.clone()).then(function () {
return response;
});
});
});
}
// Sends a message to the clients.
function refresh(response) {
return self.clients.matchAll().then(function (clients) {
clients.forEach(function (client) {
// Encode which resource has been updated. By including the ETag the client can check if the content has changed.
var message = {
type: 'refresh',
url: response.url,
// Notice not all servers return the ETag header. If this is not provided you should use other cache headers or rely on your own means to check if the content has changed.
eTag: response.headers.get('ETag')
};
// Tell the client about the update.
client.postMessage(JSON.stringify(message));
});
});
}
Handling of the "resource was updated" message
navigator.serviceWorker.onmessage = function (evt) {
var message = JSON.parse(evt.data);
var isRefresh = message.type === 'refresh';
var isAsset = message.url.includes('asset');
var lastETag = localStorage.currentETag;
// ETag header usually contains the hash of the resource so it is a very effective way of check for fresh content.
var isNew = lastETag !== message.eTag;
if (isRefresh && isAsset && isNew) {
// Escape the first time (when there is no ETag yet)
if (lastETag) {
// Inform the user about the update.
notice.hidden = false;
}
//For teaching purposes, although this information is in the offline cache and it could be retrieved from the service worker, keeping track of the header in the localStorage keeps the implementation simple.
localStorage.currentETag = message.eTag;
}
};

Using service worker in angularjs

How we can get event fired in service worker in our angularjs app.
Here is sample code which is working and showing notification in chrome/firefox
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
// console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
console.log(event.data);
console.log(event.data.json());
console.log(typeof (event.data.json()));
console.log(event);
window.dispatchEvent( new Event('dataisthere') );
const title = 'YummZ';
const options = {
body: 'Message Received \n ' + event.data.json().message,
icon: 'images/icon.png',
// badge: 'images/badge.png',
data : event.data.json()
};
event.waitUntil(self.registration.showNotification(title, options));
});
I tried to dispatch a window event but i got error window is undefined
when service worker get push notification, i need to notify my angular app to perform action.
NOTE: NEW TO SERVICE WORKER
Have a read on this. Havent tested it yet but I think the general idea for the solution you are looking for is there. The title says How to Send Messages Between Service Workers and Clients, so if you manage to bridge that gap, you can pretty much tell your service worker to do whatever you want.

Updating a ServiceWorker only for new clients

I'm trying to make the update of my ServiceWorker work.
I have an old service worker, some windows are under it's control. Now there is a new version of the ServiceWorker. It get installed properly. But it does not get activated for new pages. My aim is to keep the old one for old pages and the new for every new tab/pages viewed.
Any idea how to accomplish this ?
Edit:
Is how I check that the the new service worker is not updated:
I have a sendMessage method:
sendMessage(message): Promise {
const channel = new MessageChannel();
const p1 = channel.port1;
const result = new Promise(resolve => {
p1.onmessage = ({ data }) => {
resolve(data);
};
});
serviceWorker.controller.postMessage(message, [channel.port2]);
return result;
}
Then I use it to check on page start
this.ready = this.sendMessage('version')
.then(version => {
if (version !== process.env.BUILD_NUMBER) {
console.log('$$ Version mismatch, reinstalling service worker');
throw new Error('build mismatch');
}
return serviceWorker.controller;
});
And I answer in the ServiceWorker
self.addEventListener('message', ({ data, ports }) => {
const client = ports[0];
if (data === 'version') {
client.postMessage(process.env.BUILD_NUMBER);
}
});
Edit
Adding:
event.waitUntil(self.clients.claim());
helps but it activate the ServiceWorker also for old clients.
Thanks
Calling self.skipWaiting() within a service worker's install event handler will cause the newly installed service worker to immediately activate, even if there's an existing, older service worker that's currently active for other clients. Without self.skipWaiting(), the newly installed service worker will remain in a waiting state until all other clients of the previous service worker have been closed.
Calling self.clients.claim() from inside the activate event handler is sometimes used as well, but only if you want the newly activated service worker to take control of already-open clients that are either uncontrolled, or controlled by an older version of the service worker. In your case, it sounds like you do not want that behavior, so you shouldn't call self.clients.claim().

does service worker request, response from server continuously?

I'm using server send event to display a notification.I have created a service worker and i used EventSource to connect with the server (in my case i used a servlet. ) once i run the project. everything is working fine.
but the contents inside the event execute countiously. I want to know why?
my other question is
once i close the tab. it stops sending notification. service worker is nunning and server also running. but why it stops?
this is my service worker code.
var eventSource = new EventSource("HelloServ");
//MyDiv1 is a custom event
eventSource.addEventListener("MyDiv1",function(event){
console.log("data from down" , event.data);
var title = event.data;
//below notification is displaying continuously. why ?
var notification = new Notification(title, {
icon: 'http://cdn.sstatic.net/stackexchange/img/logos/so/so-icon.png',
body: event.data,
});
notification.onclick = function () {
window.open("http://ageofthecustomer.com/wp-content/uploads/2014/03/success.jpg");
};
console.log("down");
});
this is my servlet code;
response.setContentType("text/event-stream");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
String upVote = "my up vote";
writer.write("id:1\n");
writer.write("event:myid\n");
writer.write("data: "+ upVote +"\n");
writer.write("data: "+"new data 2\n\n");
System.out.println("servlet "+ i);
writer.flush();
i++;
writer.close();
Service workers have a limited lifetime, you shouldn't use things like web sockets or server sent events.
Push notifications are implemented in a different way.
In your page, you need to subscribe the user for push notifications. The subscription is an endpoint URL (and a set of keys, if you plan to use payloads). Once the user is subscribed, you need to send the subscription information to your server.
The server will send a push notification to the user via a POST request to the endpoint URL.
The service worker will be awakened when a push notification arrives, its 'push' event handler is going to be executed.
A simple example (for more complex ones, take a look at the ServiceWorker Cookbook).
Page
// Register a Service Worker.
navigator.serviceWorker.register('service-worker.js')
.then(function(registration) {
// Use the PushManager to get the user's subscription to the push service.
return registration.pushManager.getSubscription()
.then(function(subscription) {
// If a subscription was found, return it.
if (subscription) {
return subscription;
}
// Otherwise, subscribe the user (userVisibleOnly allows to
// specify that you don't plan to send notifications that
// don't have a visible effect for the user).
return registration.pushManager.subscribe({
userVisibleOnly: true
});
});
}).then(function(subscription) {
// subscription.endpoint is the endpoint URL that you want to
// send to the server (e.g. via the Fetch API or via
// XMLHTTPRequest).
console.log(subscription.endpoint);
// Here's an example with the Fetch API:
fetch('./register', {
method: 'post',
headers: {
'Content-type': 'application/json'
},
body: JSON.stringify({
endpoint: subscription.endpoint,
}),
});
});
Service Worker
// Register event listener for the 'push' event.
self.addEventListener('push', function(event) {
// Keep the service worker alive until the notification is created.
event.waitUntil(
self.registration.showNotification('Title', {
body: 'Body',
})
);
});
Server
In the server, simply send a POST request to the endpoint URL.
For example, with curl:
curl -X POST [endpointURL]
Or, if you're using Node.js, you can use the web-push library (https://github.com/marco-c/web-push):
var webPush = require('web-push');
webPush.sendNotification(req.query.endpoint, req.query.ttl);
In Java, you could use this class (https://github.com/marco-c/java-web-push) that hides the details of the implementation and the differences between the protocols in the current versions of Firefox and Chrome (differences destined to disappear since Chrome is going to use the Web Push protocol soon).
Here's a "manual" example with a push service that implements the Web Push protocol (currently only works with Firefox):
URL url = new URL(endpointURL);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setDoOutput(true);
conn.setRequestMethod("POST");
OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
writer.write("");
writer.flush();
String line;
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
writer.close();
reader.close();

Categories

Resources