Firefox not opening window in service worker for Push message - javascript

Firebase Cloud Messaging
I have everything setup, the Push messages are received fine and when I click on it, it opens new window... but only in Chrome, in Firefox it is not opened.
I have specifically allowed popups, but didn't make any difference.
I was just debugging for 1 hour
self.addEventListener('notificationclick', function(e) {
console.log("This is printed to console fine");
clients.openWindow('https://example.com'); // this makes no error, nothing
});
Any ideas?
Works in Firefox 47.0
Doesn't work in Firefox Quantum 60
Subscribed a bug.

I also struggled with this for a while...
To anyone else having the problem, I wanted to add something:
You can still use firebase.messaging() but you have to make sure you put it AFTER the event listener.
To make this clear:
This does NOT work (clients.openWindow does nothing):
const messaging = firebase.messaging();
// Add an event listener to handle notification clicks
self.addEventListener('notificationclick', function (event) {
if (event.action === 'close') {
event.notification.close();
} else if (event.notification.data.target_url && '' !== event.notification.data.target_url.trim()) {
clients.openWindow(event.notification.data.target_url);
}
});
messaging.setBackgroundMessageHandler(function (payload) {
// ... add custom notification handling ...
});
This does work (clients.openWindow opens link as expected):
// Add an event listener to handle notification clicks
self.addEventListener('notificationclick', function (event) {
if (event.action === 'close') {
event.notification.close();
} else if (event.notification.data.target_url && '' !== event.notification.data.target_url.trim()) {
clients.openWindow(event.notification.data.target_url);
}
});
const messaging = firebase.messaging();
messaging.setBackgroundMessageHandler(function (payload) {
// ... add custom notification handling ...
});
Still not sure of the underlying reason. I also suspect that the messaging() messes up the click event and makes Firefox refuse to open a window because it considers that the user took no direct action at the notification.
But at least I have a workaround and can keep going.
Hope that helps.

I removed from the service worker:
const messaging = firebase.messaging();
And it is now working.
This is just nuts.

Found out the reason of this.
The Firebase.messaging was the culprit ONLY if you send the messaging payload (from server) as such:
{
"notification": {
//...
}
}
The existence of notification property will prevent the notificationclick from propagating due to some reason in the firebase sdk.
You can send the following instead
{
"data": {
//...
}
}

Related

Check connection loss in JavaScript on the browser [duplicate]

This question already has answers here:
navigator.onLine not always working
(2 answers)
Closed last month.
I referred to this Detect the Internet connection is offline? question to find out how to check if the browser is offline or online.
So I used window.navigator.onLine to find that out.
The problem is that, no matter what I do, window.navigator.onLine is always true.
I am using brave browser, but I'm not sure if that's related to the issue, it's chromium based.
I'm on Ubuntu Linux, desktop.
I just want to detect when the browser becomes offline to show a small message "connection lost".
In react the code looks as follows:
const online = window.navigator.onLine
useEffect(() => {
if (online) return
console.log("Connection lost!")
}, [online])
try to toggle your wifi on and on to see the console logs
Here's a stack blitz instance to try it out, it's a pretty small code, (Click me)
The property sends updates whenever the browser's ability to connect to the network changes. The update occurs when the user follows links or when a script requests a remote page.
https://developer.mozilla.org/en-US/docs/Web/API/Navigator/onLine
So the value won't update unless you make a request of some sort.
There are also some implementation specific notes on the same url.
In Chrome and Safari, if the browser is not able to connect to a local area network (LAN) or a router, it is offline; all other conditions return true.
In other words, if there is any sort of network access it will be true, even if you are not connected to the internet.
So the best way to check this is probably to just make a request to an API endpoint or other resource that is only available while online and base your status on if the request was successful or not. Since in most cases just being "online" isn't worth much if your API is inaccessible this would probably provide better information to your users as well.
Need to use the event listener for this: window.addEventListener('online', () => { ...});.
Inside the callback for listener, do setState to check online off-line toggle.
here is small hook i created in reactjs to handle online offline states:
import { useEffect, useState } from 'react';
// toastr alert messages
import { showOffline, showOnline } from 'utils/alerts';
const useNetworkStatus = () => {
const [state, setState] = useState(true);
async function isOnline() {
// if its offline and to check if window?.navigator is supported.
if (!window?.navigator?.onLine) {
setState(false);
return false;
}
// Failover case:
// navigator.onLine cannot be trusted: there's situation where you appear to be online (connect to a network with no internet)
// but still cannot access the internet.
// So to fix: we request to our own origin to avoid CORS errors
const url = new URL(window.location.origin);
// with random value to prevent cached responses
url.searchParams.set('rand', Date.now());
try {
const response = await fetch(url.toString(), { method: 'HEAD' });
setState(true);
return response.ok;
} catch {
setState(false);
return false;
}
}
useEffect(() => {
const setOnlineOnVisibleChange = async () => {
// if its page is visible and state was offline
if (!document?.hidden && !state) {
if (await isOnline()) showOnline();
}
};
// on visiting the page again if the state is offline and network is online, then show online alert
if ('hidden' in document)
document.addEventListener('visibilitychange', setOnlineOnVisibleChange, false);
return () => document.removeEventListener('visibilitychange', setOnlineOnVisibleChange, false);
}, [state]);
useEffect(() => {
async function changeStatus() {
if (await isOnline()) showOnline();
else showOffline();
}
// Listen for the page to be finished loading
window.addEventListener('load', () => {
// if its offline
if (!isOnline()) showOffline();
});
window.addEventListener('online', changeStatus);
window.addEventListener('offline', changeStatus);
return () => {
window.removeEventListener('online', changeStatus);
window.removeEventListener('offline', changeStatus);
};
}, []);
};
export default useNetworkStatus;

Javascript/jQuery beforeunload and unload not firing

I have a page where I want my script to let the server know that the user went offline when the browser/tab/page is closed.
$(document).ready(function() {
// Inform the server that the user went online
setStatus('online');
// Inform the server that the user went offline
$(window).on('beforeunload', function() {
setStatus('offline');
});
// Inform the server that the user went offline
$(window).on('unload', function() {
setStatus('offline');
});
});
async function setStatus(status) {
let { data } = await axios.post('/status', { status: status });
}
The part where I have setStatus('online'); works but I can never seem to set the status to offline when I close the page/tab/browser.
Looking around, I found this really nice answer - I just had to put this on my server.js file:
peer.oniceconnectionstatechange = function() {
if(peer.iceConnectionState == 'disconnected') {
console.log('Disconnected');
}
}
It does take a few seconds before it registers that the peer has dropped but at least it's always firing and that's reliable enough for me.

Push event not being triggered in Service worker

So I have managed to get push notifications to work. The problem is that whilst they appear on my screen, they don't seem to be triggering the push event in my serviceworker. I am trying to console.log something once the event is triggered but it's not working. Does anyone know what could be causing this problem? (Service worker is registered properly).
self.addEventListener('push', function (e) {
console.log('hello world');
if (!(self.Notification && self.Notification.permission ===
'granted')) {
//notifications aren't supported or permission not granted!
return;
}
if (e.data) {
var msg = e.data.json();
console.log(msg)
e.waitUntil(self.registration.showNotification(msg.title, {
body: msg.body,
icon: msg.icon,
actions: msg.actions
}));
}
});
After writing this I discovered that the push event works correctly on my phone but not on my laptop.

WebRTC Events in Firefox

I have a working WebRTC connection in Chrome. It uses 1 data channel as part of a chat application.
I want to also support Firefox thus I need to change some not supported events:
For the RTCPeerConnection as well as for the DataChannel.
The changes to the data channel worked as expected:
//chrome implenetation
dc.onopen = this.conncectionStats.bind(this);
dc.onmessage = onMessage;
// chrome and firefox
dc.addEventListener('open', (event) => {
this.conncectionStats.bind(this)
});
dc.addEventListener('message', (event) => {
onMessage(event)
});
However, the problem arises when changing the PeerConnection:
// chrome implenetation
pc.onconnectionstatechange = this.onConnectionStateChange.bind(this);
// chrome and firefox
pc.addEventListener('onconnectionstatechange', (event) => {
console.log("onconnectionstatechange fired")
this.onConnectionStateChange.bind(this);
})
The event is never occuring. Any ideas why this is the case?
The event should be correct, but on the other hand, the documentation is missing on MDN Web Docs.
You should use WebRTC adapter so that unsupported events will be shimmed for you:
https://github.com/webrtc/adapter
I am using it on my webpages, and onconnectionstatechange fires fine in Firefox:
...
pc.onconnectionstatechange = onConnStateChange;
...
function onConnStateChange(event) {
if (pc.connectionState === "failed") {
Terminate();
alert("Connection failed; playback stopped");
}
}

How to logout my application when I closed the window?

In my chat application i am having the logout button and it works fine.
Now I need to logout the application when I closed the browser window also..How can I achieve this...
Thanks in advance...
There is no exact way to do this with the clientside. There is no event that is fired when the page is exited. It should be done with the Session End event on the server.
You can try to use onbeforeunload or unload, but race conditions will prevent that from happening. AND they do not fire for browsers crashing, lost internet connection, etc.
I dealt with this issue recently in my angularJS app - The main issue was that I don't want to log you out if you refresh, but I do want to if you close the tab.. Ajax requests with onbeforeunload/onunload aren't guaranteed to wait for response, so here is my solution:
I set a sessionStorage cookie on login that is just a bool - set to true when I get login response
sessionStorage.setItem('activeSession', 'true');
Obviously, on logout, we set this flag to false
Either when controller initializes or using window.onload (in my app.js file) - I check for this activeSession bool.. if it is false, I have this small if statement - where if conditions are met I call my logout method ONLOAD instead of onunload
var activeSession = sessionStorage.activeSession;
if (sessionStorage.loggedOutOnAuth) {
console.log('Logged out due to expiry already')
}
else if (!activeSession) {
sessionStorage.loggedOutOnAuth = true;
_logout()
}
Basically, the "loggedOutAuth" bool let's me know that I just expired you on page load due to the absence of an activeSession in sessionStorage so you don't get stuck in a loop
This was a great solution for me since I didn't want to implement a heartbeat/websocket
Add your logout code to the on onunload event.
window.onunload = function () {
//logout code here...
}
In JQuery you can use the .unload() function. Remember that you don't have much time so you may send the Ajax request but the result may not reach the client.
Another trick is to open a small new window and handle the logout there.
window.open("logout url","log out","height=10,width=10,location=no,menubar=no,status=no,titlebar=no,toolbar=no",true);
If you want to disable closing the window (or at least warn the user), you can use this code:
window.onbeforeunload = function(event) {
//if you return anything but null, it will warn the user.
//optionally you can return a string which most browsers show to the user as the warning message.
return true;
}
Another trick is to keep pinging the client every few seconds. If no reply comes back, assume the user has closed the window, browser has crashed or there is a network issue that ended the chat session anyway. On the client side, if you don't receive this ping package, you can assume that network connection or server has a problem and you can show the logout warning (and optionally let the user login again).
Some websites are using the following script to detect whether window is closed or not.
if(window.screenTop > 10000)
alert("Window is closed");
else
alert("Window stillOpen");
You need to add the correct action instead of alert()
also take a look HERE - I think this is somthing you need to detect the window closing
I got the Solution by,
window.onunload = function () {
//logout code here...
}
Thanks for all who supported me...
Another approach is some sort of "keepalive": the browser page "pings" the server with a small ajax request every minute or so. If the server doesn't get the regular pings, the session is closed and can no longer be used.
As an optimization, the pings can be skipped if we have made another request to the server in the interim.
Advantages:
still works with multiple windows open
no problem with F5 / refresh
can provides some usage statistics to the server
Disadvantages:
when the window is closed, there is a delay before the user is logged out
uses a little network bandwidth
additional load on the server
users might have concerns about the page constantly "phoning home"
more difficult to implement
I've never actually done this in a web app, and not sure if I would; just putting it out there as an alternative. It seems like a good option for a chat app, where the server does need to know if you are still there.
Rather than polling / pinging, another possibility is to keep a "long running request" open while the page is open. A chat app needs some such socket to receive new messages and notifications. If the page is closed, the socket is closed too, and the server can notice that it has been closed. It then waits a brief time for the client to establish a new socket, and if it doesn't we assume the page is closed and delete the session. This would require some slightly unusual software on the server.
I was with this problem here and I come with a different solution:
checkSessionTime();
$interval(checkSessionTime, 2000);
function checkSessionTime() {
var now = (new Date()).getTime();
if (!$localStorage.lastPing) {
$localStorage.lastPing = now;
}
if ($localStorage.lastPing < now - 5000) {
$localStorage.lastPing = undefined;
AuthService.logout();
} else {
$localStorage.lastPing = now;
}
}
I like this solution cause it doesnt add overhead pinging the server nor rely on the window unload event. This code was put inside the $app.run.
I am using angular with a JWT auth, this way to me to log out just mean to get rid of the auth token. However, if you need to finish up the session server-side you can just build the Auth service to do one ping when finishing the session instead of keep pinging to maitain session alive.
This solutionsolves my case cause my intetion is just to prevent unwanted users to access someones account when they closed the tab and went away from the PC.
After lots of search I wrote the below customized code in javascript and server side code for session kill in c#.
The below code is extended in case of same website is open in multiple tabs so the session is alive till one tab of website is open
//Define global varible
var isCloseWindow = false;
//Jquery page load function to register the events
$(function () {
//function for onbeforeuload
window.onbeforeunload = ConfirmLeave;
//function for onload
window.onload = ConfirmEnter;
//mouseover for div which spans the whole browser client area
$("div").on('mouseover', (function () {
//for postback from the page make isCloseWindow global varible to false
isCloseWindow = false;
}));
//mouseout event
$("div").on('mouseout', (function () {
//for event raised from tabclose,browserclose etc. the page make isCloseWindow global varible to false
isCloseWindow = true;
}));
});
//Key board events to track the browser tab or browser closed by ctrl+w or alt+f4 key combination
$(document).keydown(function (e) {
if (e.key.toUpperCase() == "CONTROL") {
debugger;
isCloseWindow = true;
}
else if (e.key.toUpperCase() == "ALT") {
debugger;
isCloseWindow = true;
}
else {
debugger;
isCloseWindow = false;
}
});
function ConfirmEnter(event) {
if (localStorage.getItem("IsPostBack") == null || localStorage.getItem("IsPostBack") == "N") {
if (localStorage.getItem("tabCounter") == null || Number(localStorage.getItem("tabCounter")) == 0) {
//cookie is not present
localStorage.setItem('tabCounter', 1);
} else {
localStorage.setItem('tabCounter', Number(localStorage.getItem('tabCounter')) + 1);
}
}
localStorage.setItem("IsPostBack", "N");
}
function ConfirmLeave(event) {
if (event.target.activeElement.innerText == "LOGOUT") {
localStorage.setItem('tabCounter', 0);
localStorage.setItem("IsPostBack", "N");
} else {
localStorage.setItem("IsPostBack", "Y");
}
if ((Number(localStorage.getItem('tabCounter')) == 1 && isCloseWindow == true)) {
localStorage.setItem('tabCounter', 0);
localStorage.setItem("IsPostBack", "N");
**Call Web Method Kill_Session using jquery ajax call**
} else if (Number(localStorage.getItem('tabCounter')) > 1 && isCloseWindow == true) {
localStorage.setItem('tabCounter', Number(localStorage.getItem('tabCounter')) - 1);
}
}
//C# server side WebMethod
[WebMethod]
public static void Kill_Session()
{
HttpContext.Current.Session.Abandon();
}
For this issue I tried 2 solutions: window.onbeforeunload event and sessionStorage
Since window.onbeforeunload is not only for closing the browser but also redirect, tab refresh, new tab, it was not a robust solution. Also there are cases which the event does not happen: closing the browser through the command line, shutting down the computer
I switched to using sessionStorage. When the user logs in I set a sessionStorage variable to 'true'; when the application is loaded I would check to see if this variable is there, otherwise I would force the user to log in. However I need to share the sessionStorage variable across tabs so that a user is not forced to log in when they open a new tab in the same browser instance, I was able to do this by leveraging the storage event; a great example of this can be found here
tabOrBrowserStillAliveInterval;
constructor() {
// system should logout if the browser or last opened tab was closed (in 15sec after closing)
if (this.wasBrowserOrTabClosedAfterSignin()) {
this.logOut();
}
// every 15sec update browserOrTabActiveTimestamp property with new timestamp
this.setBrowserOrTabActiveTimestamp(new Date());
this.tabOrBrowserStillAliveInterval = setInterval(() => {
this.setBrowserOrTabActiveTimestamp(new Date());
}, 15000);
}
signin() {
// ...
this.setBrowserOrTabActiveTimestamp(new Date());
}
setBrowserOrTabActiveTimestamp(timeStamp: Date) {
localStorage.setItem(
'browserOrTabActiveSessionTimestamp',
`${timeStamp.getTime()}`
);
}
wasBrowserOrTabClosedAfterSignin(): boolean {
const value = localStorage.getItem('browserOrTabActiveSessionTimestamp');
const lastTrackedTimeStampWhenAppWasAlive = value
? new Date(Number(value))
: null;
const currentTimestamp = new Date();
const differenceInSec = moment(currentTimestamp).diff(
moment(lastTrackedTimeStampWhenAppWasAlive),
'seconds'
);
// if difference between current timestamp and last tracked timestamp when app was alive
// is more than 15sec (if user close browser or all opened *your app* tabs more than 15sec ago)
return !!lastTrackedTimeStampWhenAppWasAlive && differenceInSec > 15;
}
How it works:
If the user closes the browser or closes all opened your app tabs then after a 15sec timeout - logout will be triggered.
it works with multiple windows open
no additional load on the server
no problem with F5 / refresh
Browser limitations are the reason why we need 15sec timeout before logout. Since browsers cannot distinguish such cases: browser close, close of a tab, and tab refresh. All these actions are considered by the browser as the same action. So 15sec timeout is like a workaround to catch only the browser close or close of all the opened your app tabs (and skip refresh/F5).
I posted this originally here but I will repost here for continuity.
There have been updates to the browser to better tack the user when leaving the app. The event 'visibilitychange' lets you tack when a page is being hidden from another tab or being closed. You can track the document visibility state. The property document.visibilityState will return the current state. You will need to track the sign in and out but its closer to the goal.
This is supported by more newer browser but safari (as we know) never conforms to standards. You can use 'pageshow' and 'pagehide' to work in safari.
You can even use new API's like sendBeacon to send a one way request to the server when the tab is being closed and shouldn't expect a response.
I build a quick port of a class I use to track this. I had to remove some calls in the framework so it might be buggy however this should get you started.
export class UserLoginStatus
{
/**
* This will add the events and sign the user in.
*/
constructor()
{
this.addEvents();
this.signIn();
}
/**
* This will check if the browser is safari.
*
* #returns {bool}
*/
isSafari()
{
if(navigator && /Safari/.test(navigator.userAgent) && /Chrome/.test(navigator.userAgent))
{
return (/Google Inc/.test(navigator.vendor) === false);
}
return false;
}
/**
* This will setup the events array by browser.
*
* #returns {array}
*/
setupEvents()
{
let events = [
['visibilitychange', document, () =>
{
if (document.visibilityState === 'visible')
{
this.signIn();
return;
}
this.signOut();
}]
];
// we need to setup events for safari
if(this.isSafari())
{
events.push(['pageshow', window, (e) =>
{
if(e.persisted === false)
{
this.signIn();
}
}]);
events.push(['pagehide', window, (e) =>
{
if(e.persisted === false)
{
this.signOut();
}
}]);
}
return events;
}
/**
* This will add the events.
*/
addEvents()
{
let events = this.setupEvents();
if(!events || events.length < 1)
{
return;
}
for(var i = 0, length = events.length; i < length; i++)
{
var event = events[i];
if(!event)
{
continue;
}
event[1].addEventListener(event[0], event[3]);
}
}
/**
*
* #param {string} url
* #param {string} params
*/
async fetch(url, params)
{
await fetch(url,
{
method: 'POST',
body: JSON.stringify(params)
});
}
/**
* This will sign in the user.
*/
signIn()
{
// user is the app
const url = '/auth/login';
let params = 'userId=' + data.userId;
this.fetch(url, params);
}
/**
* This will sign out the user.
*/
signOut()
{
// user is leaving the app
const url = '/auth/logout';
let params = 'userId=' + data.userId;
if(!('sendBeacon' in window.navigator))
{
// normal ajax request here
this.fetch(url, params);
return;
}
// use a beacon for a more modern request the does not return a response
navigator.sendBeacon(url, new URLSearchParams(params));
}
}

Categories