Duration of Service Worker Registration for Samsung Internet Push Notifications - javascript

I'm trying to build a web application that will send push notifications to a user who subscribes to it (testing on Samsung Internet). However, I'm facing an issue where after several hours, the phone stops receiving the push notifications, and I will need to re-open the web application and re-subscribe to resume receiving push notifications. Below is the code for my service worker and its registration:
Service Worker:
var windowActive = true;
var numMessages = 0;
var sAdder;
self.addEventListener('push', function(event) {
console.log('[Service Worker] Push Received.');
// console.log(`[Service Worker] Push had this data: "${event.data.text()}"`);
console.log(windowActive);
numMessages++;
if(numMessages == 1) {
sAdder = "";
} else {
sAdder = "s";
}
var title = 'Convers8te';
var options = {
body: numMessages + ' message' + sAdder + ' received!',
icon: '/images/logo/8-icon.png',
badge: '/images/badge.png',
tag: 'c8t',
};
if(windowActive == false) {
event.waitUntil(self.registration.showNotification(title, options));
}
});
self.addEventListener('notificationclick', function(event) {
console.log('[Service Worker] Notification click Received.');
event.notification.close();
numMessages = 0;
event.waitUntil(
clients.openWindow('')
);
});
self.addEventListener('message', function (evt) {
windowActive = evt.data['windowActive'];
console.log('postMessage received', evt.data);
});
Registration:
function subscribeUser() {
const applicationServerKey = urlB64ToUint8Array(applicationServerPublicKey);
swRegistration.pushManager.subscribe({
userVisibleOnly: true,
})
.then(function(subscription) {
console.log('User is subscribed.');
updateSubscriptionOnServer(subscription);
isSubscribed = true;
updateBtn();
})
.catch(function(err) {
console.log('Failed to subscribe the user: ', err);
updateBtn();
});
}
Update subscription on server then sends the push token to the server to be used through the Firebase service.
I've tried several things such as lengthening the session duration, and testing on several other devices but it seems to stop receiving push notifications after several hours.
Also, a side question is whether it is possible for push notifications work even when the Samsung Internet explorer app is closed? Currently they only work for me when the tab is closed, but not when the entire app is closed.
Thanks for taking the time to read and help!

Related

Why doesn't my PWA postMessage() in the background?

I'm currently trying to make a postMessage() triggered from a push notification work correctly. Currently, I've got a PWA which has Firebase push notifications. When a notification is sent and clicked, the PWA is launched (if it's installed) and a postMessage() is sent.
However, the postMessage() doesn't get received by the client. If the app is in the foreground, it works as expected.
If the all is in the background though, it doesn't catch the postMessage(). I was wondering if I needed to listen to some kind of load event in my main sw.js file before sending the postMessage() - but I'm not sure.
My firebase-message-sw.js file is as follows:
messaging.onBackgroundMessage(function(payload) {
console.log('[firebase-messaging-sw.js] Received background message ', payload);
// Customize notification here
// self.addEventListener('load', (e) => {
console.log(`page has loaded | load event | firebase-messaging-sw.js`);
const channel = new BroadcastChannel('sw-messages');
channel.postMessage({
title: payload.notification.title,
body: payload.notification.body,
image: payload.notification.image,
// icon: event.notification.icon,
}, "*")
// })
})
My sw.js file is like this:
addEventListener('notificationclick', event => {
event.notification.close();
const urlToOpen = new URL("/settings", self.location.origin).href;
const promiseChain = clients.matchAll({
type: 'window',
includeUncontrolled: true
}).then((windowClients) => {
let matchingClient = null;
for (let i = 0; i < windowClients.length; i++) {
const windowClient = windowClients[i];
if (windowClient.url === urlToOpen) {
matchingClient = windowClient;
break;
}
}
if (matchingClient) {
return matchingClient.focus();
} else {
console.log(`opening new window`);
return clients.openWindow(urlToOpen);
}
});
event.waitUntil(promiseChain);
console.log("promiseChain | sw.js");
const channel = new BroadcastChannel('sw-messages');
channel.postMessage({
title: event.notification.title,
body: event.notification.body,
image: event.notification.image,
icon: event.notification.icon,
})
});
NOTE: This all works as required when the website is viewed in a browser, and not a PWA.
Does anyone has any suggestions/recommendations on what I should do?
Thanks in advance.

PeerJs close video call not firing close event

I am trying to create a one-directional video app with PeerJs. I've succesfully been able to run my own peer server and connect on a button click, but I'm unable to close the connection so that the peers can receive/establish a new connection with a new peer.
Every user will either be a host or a client, it will never be backwards. So the host can choose which client to connect to and the client will start to stream its camera feed back to the host. The closeCon() function is called with a button click.
$(document).ready(function(){
peer = new Peer('100001', {host: 'my.url', port: '9900', path: '/peerjs', key: 'peerjs', secure: true, debug: 3});
peer.on("open", function(id) {
console.log("My peer ID is: " + id);
});
video = document.getElementById('vidSrc');
})
function callTheGuy(id){
var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
getUserMedia({video: true, audio: false}, function(stream) {
window.call = peer.call(id, stream);
localStream = stream;
window.call.on('stream', function(remoteStream) {
let video = document.getElementById('vidArea');
video.srcObject = remoteStream;
video.play();
$("#videoModal").modal('show')
});
}, function(err) {
console.log('Failed to get local stream' ,err);
});
}
function closeCon(){
window.call.close();
}
This all works great, i get my video feed, no problem. Here is my client code:
peer = new Peer(serverId, {
host: "my.url",
port: "9900",
path: "/peerjs",
key: "peerjs",
debug: 3,
secure: true
});
peer.on("open", function(id) {
console.log("My peer ID is: " + id);
});
var getUserMedia =
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia;
peer.on("call", function(call) {
getUserMedia(
{ video: true, audio: false },
function(stream) {
localStream = stream;
call.answer(stream); // Answer the call with an A/V stream.
},
function(err) {
console.log("Failed to get local stream", err);
}
);
call.on("close", function() {
console.log("closing");
});
});
The issue is that when I call closeCon(), the client file is not receiving the close event. The part of
call.on("close", function() {
console.log("closing");
});
never gets fired. I'm not really sure why this is happening but unless that close event gets processed, the client stays connected to the original host and can't accept connections from subsequent host requests. Does anyone have any advice?
I ran into the issue after discovering my peerConnections continued to send stream data using chrome://webrtc-internals. I am currently using the public peerjs server. The MediaConnection does not fire the close event, but the DataConnection still class does. My particular flow waits for the remote to initiate a (data)connection and then starts a call.
I was able too close the MediaConnection by:
opening both a DataConnection and a MediaConnection to a remote peer
monitoring the MediaConnection close event
Closing all WebRTCpeerConnections after as part of the DataConnection close handler
This looks like:
function handlePeerDisconnect() {
// manually close the peer connections
for (let conns in peer.connections) {
peer.connections[conns].forEach((conn, index, array) => {
console.log(`closing ${conn.connectionId} peerConnection (${index + 1}/${array.length})`, conn.peerConnection);
conn.peerConnection.close();
// close it using peerjs methods
if (conn.close)
conn.close();
});
}
}
peer.on('connection', conn => {
let call = peer.call(peerToCall, localStream);
// peerjs bug prevents this from firing: https://github.com/peers/peerjs/issues/636
call.on('close', () => {
console.log("call close event");
handlePeerDisconnect();
});
}
// this one works
conn.on('close', () => {
console.log("conn close event");
handlePeerDisconnect();
});
});
Update on 7 Mar 2021
I found that is an issue, PeerJs hasn't fixed yet. (Issue link: https://github.com/peers/peerjs/issues/636)
You can trigger the close event in Socketio "disconnect" for a workaround solution
Server
socket.on("disconnect", (reason)=>{
socket.broadcast.emit("user-disconnected", userId);
});
Client
socket.on("user-disconnected", (userId)=>{
// remove video or add your code here
});
No, it fires the close event. but you first do as follows:
server
socket.on('disconnect', () =>{
socket.broadcast.to(roomId).emit('user-disconnected', userId);
})
client
const peers = {};
function connectToNewUser (userId, stream){
...
call.on('close', () =>{
video.remove();
})
peers[userId] = call;
}
and then in client emit the event:
socket.on('user-disconnected', userId =>{
console.log(userId);
if (peers[userId]){
peers[userId].close();
}
})

Transfer data between client and serviceWorker

I want to try run websockets in serviceWorker.
I write code for register serviceWorker:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register(
GetDataForUser.settings.service_worker + "?version=" + GetDataForUser.settings.service_worker_version,
{scope: './'}
)
.then(() => navigator.serviceWorker
.ready
.then((worker) => {
console.log(worker);
worker.sync.register('syncdata');
})
)
.catch((err) => console.log(err));
navigator.serviceWorker.addEventListener('message', event => {
console.log('++++++++++++++++++++', event.data.msg, event.data.url);
});
}
And write serviceWorker code:
var ws = null;
self.addEventListener('install', (event) => {
console.log('Install');
});
self.addEventListener('activate', (event) => {
console.log('Activate');
ws = new WebSocket('ws://local.ll:8880');
ws.onopen = function() {
console.log("Open");
};
// On message receive
ws.onmessage = function (event) {
console.log("Message receive " + event.data);
console.log(event);
event.ports[0].postMessage({'test': 'This is my response.'});
};
// On error connection
ws.onerror = function (error) {
console.log("Error " + error.message);
};
// On close connection
ws.onclose = function (event) {
if (event.wasClean) {
console.log('clean closed');
} else {
console.log('broken connection');
}
console.log('Code: ' + event.code + ' reason: ' + event.reason);
};
});
self.addEventListener('fetch', (event) => {});
self.addEventListener('message', function (evt) {
console.log('postMessage received', evt.data);
ws.send('121212');
});
I try to send data to serviceWorker next:
navigator.serviceWorker.controller.postMessage({'hello': 'world'});
But receive an error:
Uncaught TypeError: Cannot read property 'postMessage' of null
console.log(navigator.serviceWorker);
ServiceWorkerContainer {controller: null, ready: Promise, oncontrollerchange: null, onmessage: null}
Trying to send message from serviceWorker to client next:
event.ports[0].postMessage({'test': 'This is my response.'});
But event.ports is empty.
Whats wrong?
How to transfer data between client and serviceWorker.
To fix your error of controller being null, you need to add the activate event listener
self.clients.claim()
Without that, when the service worker is activated, the page is not controlled by it until the next navigation. This allows the service worker to take control of the pages as soon as it's active.
But I don't think it's your only problem. Looking at your code I think you're confusing WebSocket and MessageChannel. You seem to try to use WebSocket to send messages between client and service worker where you should be using MessageChannel for that.
For exemple if we want to send messages from the client to the SW and allow the SW to respond to those messages, we create a MessageChannel, and we send a port of this channel allong with the message for the SW to use in his response.
in the client :
var msg = new MessageChannel();
msg.port1.onmessage = function(event){
//Response received from SW
console.log(event.data);
};
// Send message to SW
navigator.serviceWorker.controller.postMessage("hello", [msg_chan.port2]);
in the SW :
self.addEventListener('message', function(event){
//Message received from client
console.log(event.data);
//Send response to client using the port that was sent with the message
event.ports[0].postMessage("world");
});
Correct and worked example, only for understanding, not for production:
https://github.com/Shkarbatov/WebSocketInServiceWorkerJS

Play a sound from a Service Worker

Is there a way to play an audio file from a service worker?
I'm trying to use io.sound library but it is a JavaScript plugin that requires window, so it doesn't work.
EDIT
As suggested by Jeff I'm trying to open a new window and post a message to that window. this is my code:
function notifyClientToPlaySound() {
idbKeyval.get('pageToOpenOnNotification')
.then(url => {
console.log("notifyClientToPlaySound", url);
clients.matchAll({
type: "window"
//includeUncontrolled: true
})
.then((windowClients) => {
console.log("notifyClientToPlaySound - windowClients", windowClients);
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
if (client.url === url && "focus" in client) {
notify({ event: "push" });
return client.focus();
}
}
//https://developer.mozilla.org/en-US/docs/Web/API/Clients/openWindow
if (clients.openWindow) {
return clients.openWindow("/")
.then(() => {
notify({ event: "push" });
});
}
})
});
}
This function is now called from event.waitUntil(..) inside self.addEventListener("push", (event) => { ... }
self.addEventListener("push", (event) => {
console.log("[serviceWorker] Push message received", event);
event.waitUntil(
idbKeyval.get('fetchNotificationDataUrl')
.then(url => {
console.log("[serviceWorker] Fetching notification data from -> " + url);
return fetch(url, {
credentials: "include"
});
})
.then(response => {
if (response.status !== 200) {
// Either show a message to the user explaining the error
// or enter a generic message and handle the
// onnotificationclick event to direct the user to a web page
console.log("[serviceWorker] Looks like there was a problem. Status Code: " + response.status);
throw new Error();
}
// Examine the text in the response
return response.json();
})
.then(data => {
if (!data) {
console.error("[serviceWorker] The API returned no data. Showing default notification", data);
//throw new Error();
showDefaultNotification({ url: "/" });
}
notifyClientToPlaySound(); <------ HERE
var title = data.Title;
var message = data.Message;
var icon = data.Icon;
var tag = data.Tag;
var url = data.Url;
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: tag,
data: {
url: url
},
requireInteraction: true
});
})
.catch(error => {
console.error("[serviceWorker] Unable to retrieve data", error);
var title = "An error occurred";
var message = "We were unable to get the information for this push message";
var icon = "/favicon.ico";
var tag = "notification-error";
return self.registration.showNotification(title, {
body: message,
icon: icon,
tag: tag,
data: {
url: "/"
},
requireInteraction: true
});
})
);
});
But when clients.openWindow is called, it returns the following exception:
Uncaught (in promise) DOMException: Not allowed to open a window.
How can I solve this?
The living specification for the Web Notifications API does reference a sound property that could be specified when showing a notification, and would theoretically allow you to play the sound of your choosing when showing a notification from a service worker.
However, while the specification references this property, as of the time of this writing, it's not supported in any browsers.
Update (Aug. '19): It looks like reference to sound has been removed from https://notifications.spec.whatwg.org/#alerting-the-user
Your best bet would be post a message along to an open window that's controlled by the current service worker, and have the window play the sound in response to the message event.
If there is no controlled client available (e.g. because your service worker has been awoken by a push event, and your site isn't currently open in a browser) then you'd have the option of opening a new window inside your notificationclick handler, which is triggered in response to a user clicking on the notification you display in your push event handler. You can then post a message to that new window.

Google Chrome Push Notification

I am implementing chrome push notification for my website users. Which I am able to do successfully.
I have two question ?
1) how to get the previous subscription id whenever i block the notification from browser setting. I have to remove the subscription id from my backend server
2) whenever i reload the website pushManager.subscribe method is running every time in which i am sending subscription id to server due to which the API is hitting every time with same subscription id
push.js
'use strict';
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('service_worker.js').then(function() {
return navigator.serviceWorker.ready;
}).then(function(reg) {
console.log('Service Worker is ready :^)', reg);
reg.pushManager.subscribe({userVisibleOnly: true}).then(function(sub) {
console.log('endpoint:',JSON.stringify(sub.endpoint));
console.log(sub.endpoint.substring('https://android.googleapis.com/gcm/send/'.length));
});
}).catch(function(error) {
console.log('Service Worker error :^(', error);
});
}
service-worker.js
'use strict';
var myurl;
console.log('Started', self);
self.addEventListener('install', function(event) {
self.skipWaiting();
console.log('Installed', event);
});
self.addEventListener('activate', function(event) {
console.log('Activated', event);
});
self.addEventListener('push', function(event) {
console.log('Push message', event);
event.waitUntil(
fetch('/notify.json').then(function(response) {
return response.json().then(function(data) {
console.log(JSON.stringify(data));
var title = data.title;
var body = data.body;
myurl=data.myurl;
return self.registration.showNotification(title, {
body: body,
icon: 'profile.png',
tag: 'notificationTag'
});
});
}).catch(function(err) {
console.error('Unable to retrieve data', err);
var title = 'An error occurred';
var body = 'We were unable to get the information for this push message';
return self.registration.showNotification(title, {
body: body,
icon: 'profile.png',
tag: 'notificationTag'
});
})
);
});
// var title = 'Vcona';
// event.waitUntil(
// self.registration.showNotification(title, {
// 'body': 'School Management',
// 'icon': 'profile.png'
// }));
self.addEventListener('notificationclick', function(event) {
console.log('Notification click: tag', event.notification.tag);
// Android doesn't close the notification when you click it
// See http://crbug.com/463146
event.notification.close();
var url = 'https://demo.innotical.com';
// Check if there's already a tab open with this URL.
// If yes: focus on the tab.
// If no: open a tab with the URL.
event.waitUntil(
clients.matchAll({
type: 'window'
})
.then(function(windowClients) {
console.log('WindowClients', windowClients);
for (var i = 0; i < windowClients.length; i++) {
var client = windowClients[i];
console.log('WindowClient', client);
if (client.url === url && 'focus' in client) {
return client.focus();
}
}
if (clients.openWindow) {
return clients.openWindow(myurl);
}
})
);
});
Best pieces of advice I can give:
Keep track of your subscription (especially what you send to your server) in indexDB. Why IndexDB?
You can update indexDB in the window and in the serviceworker. This is important as you'll first get a PushSubscription in the window, but serviceworker will dispatch pushsubscriptionchange events which you should listen for and attempt to get a new PushSubscription, if you can.
When the page loads, check indexDB for an old subscription, if it exists, compare it to getSubscription() (i.e. your current subscription). This check should include any values you need server side, for example, when browsers go from not supporting payloads, to supporting them, they go from having no keys, to suddenly having keys - so you should check if you server has these keys or not.
DO NOT USE any of the API's for GCM, this will NOT work on other browsers (Firefox, Opera, Samsung Browser + others in the future) and aren't needed.
1) You can't get previous reg id. There are to ways:
Every time you subscribe for notifications you can save it to a local chrome db(for example indexdb) and when you subscribe another time you just restore you previous reg id from this db.
When you send a notification to GCM it responds you with canonical ids and another information about correctness of reg ids, so you can remove invalid one
2) You have to check first if subscription id already exists, then subscribe if not:
if ('serviceWorker' in navigator) {
console.log('Service Worker is supported');
navigator.serviceWorker.register('service_worker.js').then(function() {
return navigator.serviceWorker.ready;
}).then(function(reg) {
console.log('Service Worker is ready :^)', reg);
reg.pushManager.getSubscription().then(function(subscription) {
if(!subscription) {
reg.pushManager.subscribe({userVisibleOnly: true}).then(function(sub) {
console.log('endpoint:',JSON.stringify(sub.endpoint));
console.log(sub.endpoint.substring('https://android.googleapis.com/gcm/send /'.length));
//send new subscription id to server
return;
});
}
});
}).catch(function(error) {
console.log('Service Worker error :^(', error);
});
}

Categories