Run notification script only once, even though script runs on multiple tabs - javascript

I use the following code inside a Tampermonkey script.
function scriptMain () {
setTimeout(function(){
GM_notification ( {
title: 'Refresh Business Manager', text: 'Your about to be logged out of staging, click here to refresh your login.', image: 'https://i.stack.imgur.com/geLPT.png',
onclick: () => {
console.log ("My notice was clicked.");
location.reload();
}
} );
}, 5*1000);
console.log ("Script main code ran.");
}
The code shows a notification whenever a browser tab is open for more than 5 seconds which includes a button to refresh the current browser tab. I wanna use this script to alert me every 20 minutes or so, that one of the logins in the browser is about to auto-logout.
The functionality works as expected, but if I have 5 tabs open from the site where im logged in, I will get 5 notifications when the page is about to run out. I would love to be able to tell from within the Tampermonkey script if this script is already running on another tab, to not execute or to maybe just be able to only show the notification once.
I have been looking into the Tampermonkey documentation for the following Grants:
GM_getTab(callback)
GM_saveTab(tab)
GM_getTabs(callback)
But I dont seem to be able to work out if this functionality would be possible or not.
Can someone help me shine some light on this topic, or perhaps share a solution?

On modern spec-compliant browsers, you can use BroadcastChannel inside the userscript to communicate with other tabs on the same domain. Make the timeout slightly random - add or subtract a few seconds to allow for all the open tabs to coordinate. When sending a notification, also send a message in the BroadcastChannel telling other instances of the userscript to reset their timers as well.
const channel = new BroadcastChannel('logout-notify');
let timeoutId;
function makeTimeout() {
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
GM_notification({
title: 'Refresh Business Manager', text: 'Your about to be logged out of staging, click here to refresh your login.', image: 'https://i.stack.imgur.com/geLPT.png',
onclick: () => {
console.log("My notice was clicked.");
location.reload();
}
});
channel.postMessage('notified');
}, 1000 * 60 * 20 + (Math.random() * 10000)); // 20 minutes plus up to 10 seconds
}
// When below runs, another tab created a notification
// reset the timeout for this tab, schedule another one for 20ish minutes from now
channel.onmessage = makeTimeout;
// initialize timeout on pageload?
makeTimeout();
This code will result in alerts being shown only once every 20 minutes at most. If you close one tab you were working on, other tabs will take up the slack without interruption.

Related

Limit cache length of chrome notifications when Windows is locked

Is there any sort of option in the Web API Notifications to allow a notification to not be displayed while the PC is locked?
I have setup a web application that displays Notifications when certain events happen. This is something I use at work to monitor different things so I leave it open nearly 24/7. Problem is that when the PC is locked, the notifications are saved until I unlock the PC. Then I get flooded with all the notifications that were triggered while the PC was locked.
I cannot use an sort of idle page script to only show notifications if the page was used or interacted with in the last X number of minutes because I don't actually want to have to look at the page to still allow the notifications.
// set title and message body
var title = 'Test message';
var message 'This is a test message that should only show when logged into the PC';
// setup some options
var options = {
// someOptionToDisableLockedNotificationsOnWindows?
body: message,
icon: 'assets/yukon_logo_small.png',
};
// create the notification using the above settings
var notification = new Notification( title, options );
// focus tab and close the notification on click
notification.addEventListener('click', function(e) {
window.focus();
e.target.close();
}, false);
I have tried to randomly set some options, don't remember them all but they didn't do anything or broke my code.
I was not able to find anything in the API:
https://developer.mozilla.org/en-US/docs/Web/API/Notifications_API

Web Workers in Chrome v54 run twice as slow when running in an inactive tab

I have a webapp that plays audio with fade in/out transitions using setTimeout. It is well known that chrome and other browsers throttle setTimeout execution when it is running in an inactive (or background) tab. So to solve this issue, I use this script (https://github.com/turuslan/HackTimer) which patches the Timer API to use Web Workers instead. This allows the volume fading transitions to run in the background so that they will always be executed and completed in the same time regardless if the tab is active. This used to work great in chrome, but now I have noticed that in version 54 (and probably earlier versions) of chrome, the Web Workers are being executed twice as slow when running in an inactive tab. This issue does not happen and works perfectly with FireFox and even IE11!
Is this a chrome bug? Or is it intended behavior? Is there another workaround to this issue? I don't want to require that the webapp to be opened in a separate window, so that is not an option.
Here is a demonstration:
https://jsfiddle.net/2614xsj8/1/
// Open Chrome console and run this then wait a few seconds to see
// it is executing on time. Then open or switch to a new tab and wait
// a few seconds. Come back to see how much slower the setTimeout
// function was being called even when using HackTimer which patches
// the Timer API to use Web Workers
var rate = 1000;
var start = new Date().getTime();
var func = function() {
var now = new Date().getTime();
var time = now - start;
var status = Math.abs(rate - time) <= 50 ? "on time" : (time / rate) + " times slower";
console.log("executed in [" + time + "] milliseconds. " + status);
start = now;
setTimeout(func, rate);
};
setTimeout(func, rate);
<script src="https://cdn.rawgit.com/turuslan/HackTimer/master/HackTimer.js" type="text/javascript"></script>
Using Task Scheduler would be the best way to do it then you don't really on timeout or interval but a specific date
But this requires using service workers instead... which is a bummer. cuz then your site also needs to be whitelisted in order to run service workers (so your site needs a valid SSL cert)
// https://example.com/serviceworker.js
this.ontask = function(task) {
alert(task.data.message);
console.log("Task scheduled at: " + new Date(task.time));
// From here on we can write the data to IndexedDB, send it to any open windows,
// display a notification, etc.
}
// https://example.com/webapp.js
function onTaskAdded(task) {
console.log("Task successfully scheduled.");
}
function onError(error) {
alert("Sorry, couldn't set the alarm: " + error);
}
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
serviceWorkerRegistration.taskScheduler.add(Date.now() + (10 * 60000), {
message: "It's been 10 minutes, your soup is ready!"
}).then(onTaskAdded, onError);
});

location.reload(true) crashing browser tab

I have a website that uses PHP sessions, and I have implemented the following JS code to check every 60 seconds if a user's sessions is still active:
var timeoutInterval = 60000; // 1 minute
function checkTimeout() {
var timeoutWorker = new Worker("/include/cbpull.js");
timeoutWorker.postMessage('/cloud/timeout.php');
timeoutWorker.onmessage = function (result) {
if (result.data['result'] === false) {
location.reload(true);
}
}
}
function sessionTimeout() {
checkTimeout();
setInterval(checkTimeout, timeoutInterval);
}
sessionTimeout();
However, this code crashes the tab in Google Chrome when the session is timed out and location.reload(true) is called. What can I do to make the code work correctly?
Might the following be what's happening? On a session time-out, you reload the page, which immediately triggers sessionTimeout again, which again finds that the session is (still) expired, which reloads the page...

How to check idle time using jQuery and PHP?

I have a script that uses PHP SESSION. I manage PHP sessions using a class. Within this class I have a method that returns how many seconds remaining before the session expires.
Also, Evey time the user refresh the page or open a new page (new request to the server) the idle time counter starts over $_SESSION['MA_IDLE_TIMEOUT'] = time()+900;
What I am looking to do is display a dialog message 2 minutes before the PHP session expire and check if the user is still on the page or not. If the use clicks "Keep working" then the jQuery script will send PHP AJAX request to renew the session $_SESSION['MA_IDLE_TIMEOUT'] = time()+900;. If the user did not click anything or clicked on "Log out" then the session ends and the user is redirected to the login page.
I did find a nice plugin that will somewhat does the Job jquery-idle-timeout
The issue with this plugin is that it checks if the user is idle using JavaScript (if the keyboard/mouse) are being used. Here is the senario where this script does not help me: lets say my PHP sessions has a limit on 15 minutes/ 900 seconds. A user is reading a super long article on the same page. He/she will be scrolling, they are not really idle "from JavaScript perspective" but from a PHP perspective the use is idle. Then after 20 minutes the user refresh the page, then the user will be logged out since he/she have not sent a new request to the PHP server for over the 900 seconds limit.
How can I solve this problem? is there a better plugin to does the trick? if there something I missed in this plugin that will solve my problem?
Thanks
If the user is not making requests, and is not moving the mouse or keyboard or touching the device, etc., then from the app's point of view the user is "idle" even if their eyeballs are not.
If the user is scrolling you can use javascript to listen for scroll events (via onscroll, for example), but this will not be 100% reliable because (1) depends on javascript, and (2) doesn't work if you are viewing a short article or using a tall/large format monitor (like the ones you can rotate 90 degrees, for example).
Perhaps you could handle this differently: use cookies, single sign-on or similar techniques to pre-authenticate or automatically authenticate requests so that the user's session can safely die and be restarted without the user having to manually login.
The other way you could handle this is to maintain a "ping" process that routinely pings the server (via setInterval(), for example) to keep the session alive, and uses a separate timeout (maybe something like the "Authentication timeout" that ASP.NET uses) to keep track of when the "idle" user should be logged out. Then user actions such as scrolling, requesting pages, focusing in fields, moving mouse, etc., can do a "ping reset" that resets the idle counter to 0.
Example / Concept - leaving as exercise for reader to perfect it:
var idleTime = 0; // how long user is idle
var idleTimeout = 1000 * 60 * 20; // logout if user is idle for 20 mins
var pingFrequency = 1000 * 60; // ping every 60 seconds
var warningTime = 1000 * 60 * 2; // warning at 2 mins left
var warningVisible = false; // whether user has been warned
setInterval(SendPing, pingFrequency);
setInterval(IdleCounter, 1000); // fire every second
function IdleCounter() {
idleTime += 1000; // update idleTime (possible logic flaws here; untested example)
if (console) console.log("Idle time incremented. Now = " + idleTime.toString());
}
function SendPing() {
if (idleTime < idleTimeout) {
// keep pinging
var pingUrl = "tools/keepSessionAlive.php?idleTime=" + idleTime;
$.ajax({
url: pingUrl,
success: function () {
if (console) console.log("Ping response received");
},
error: function () {
if (console) console.log("Ping response error");
}
});
// if 2 mins left, could show a warning with "Keep me on" button
if ((idleTime <= (idleTimeout - (idleTimeout - warningTime))) && !warningVisible) {
ShowTimeoutWarning();
}
} else {
// user idle too long, kick 'em out!
if (console) console.log("Idle timeout reached, logging user out..");
alert("You will be logged off now dude");
window.location.href = "logoff.aspx"; // redirect to "bye" page that kills session
}
}
function ShowTimeoutWarning() {
// use jQuery UI dialog or something fun for the warning
// when user clicks OK set warningVisible = false, and idleTime = 0
if (console) console.log("User was warned of impending logoff");
}
function ResetIdleTime() {
// user did something; reset idle counter
idleTime = 0;
if (console) console.log("Idle time reset to 0");
}
$(document) // various events that can reset idle time
.on("mousemove", ResetIdleTime)
.on("click", ResetIdleTime)
.on("keydown", ResetIdleTime)
.children("body")
.on("scroll", ResetIdleTime);
I use a time cookie to log out an inactive user like this:
`$time = time();
// 5 minutes since last request
if(!empty($_COOKIE['_time'] && $time - $_COOKIE['_time'] >= 300)
{
// log user out
}
setcookie('_time', $time, '/');`
Hope this helps.

CasperJS- Register on a site and validate the mail sent on Gmail -for both slimer and phantom-

Edit : this is the windows behaviour, with linux it just fails.
First, if you succeeded navigate on gmail with casper (without random waiting time -from 20sec to 5min-), please tell me.
I want to register on our site, then validate my registration automatically with Gmail (an entire register step). Did someone do that before?
I have no problem to register, and I can login on my mailbox (Gmail) but after i have some troubles to navigate and validate my registration in Gmail, and i observe different behaviors between phantomJS and slimerJS.
In phantom it will work (without special commands), but it may take until 5 minutes before pass in the next step (waitForSelector). And with slimerjs it just stays stuck on the mailbox page.
EDIT : A strange thing : if i click manually (slimer) on a link which opens a popup, it stops being blocked and my navigation continues, it's like it can't detect the end of the step itself and can't perform the waitFor after the submit click without another interaction. Is it a refresh/reload problem?
Try that to see yourself :
casper.thenOpen('https://accounts.google.com/ServiceLogin?service=mail&continue=https://mail.google.com/mail/&hl=en', function(){
this.sendKeys("input#Email","your mail");
this.sendKeys("input#Passwd","your password");
this.click("input#signIn.rc-button-submit");
console.log(this.getCurrentUrl());
this.waitForSelector(".aeF",function(){//fail with linux -> timeout
this.test.pass("ok"); //windows -> stuck in slimer, several times in phantom
this.test.assertExists(".T-I.J-J5-Ji.T-I-KE.L3","Gmail Home ok");
console.log("url "+this.getCurrentUrl());
});
And i don't get any timeOut error. In slimerjs it just keeps the page opened.
If i do a waitForPopup instead of a waitForUrl, i have the error (timeout -> did not pop up), so why does a waitForUrl/waitForSelector... stay stuck ? I tried --web-security=no,--ignore-ssl-errors=true commands too (not linked but i tried --output-encoding=ISO 8859-1 too which doesn't work).
Here the differences between phantom and slimer (doc) :
http://docs.slimerjs.org/0.8/differences-with-phantomjs.html
(useless in this issue i think)
Well, we finally found a way to do it : the problem is by default gmail loop on ajax requests, to check some new mails, etc... see Page polls remote url, causing casper to block in step.
Fortunately google proposes a way to avoid that, using the simplified HTML version (you can for example use a special gmail address for your tests using this version) :
That way the script works as it should.
Bonus :
/*
* Click on an element specified by its selector and a part of its text content.
* This method resolves some problem as random space in textNode / more flexible too.
* Need to fix one bug though : when there is a tag in textContent of our selector.
*/
casper.clickSelectorHasText = function (selector, containsText){
var tmp = this.getElementsInfo(selector)
,i
,l
,bool=false
;
for (i=0,l=tmp.length;i<l; i++){
if(tmp[i].text && tmp[i].text.indexOf(containsText)!==-1){
this.clickLabel(tmp[i].text);
bool=true;
break;
}
}
casper.test.assert(bool, "clickSelectorHasText done, text and selector found -> click selector " + selector +" which contains text " + containsText);
};
casper.thenOpen('https://accounts.google.com/ServiceLogin?service=mail&continue=https://mail.google.com/mail/&hl=en', function scrapeCode(){
//log in
this.sendKeys("input#Email","your email");
this.sendKeys("input#Passwd","your password");
this.click("input#signIn.rc-button-submit");
//wait to redirect to our mailbox
this.waitForSelector("form[name='f']",function(){
//check main block
this.test.assertExists("form[name='f']","Gmail Home ok");
this.test.assertSelectorHasText("span", "Your gmail title message");
this.clickSelectorHasText("font", "one string which appears in font tag");
//wait inscription message appears
this.waitForSelector("div.msg",function(){
this.test.assertSelectorHasText("a","the message which activates your account--> in <a>");
});
})
//validate our account
.then(function(){
this.clickLabel("the message which activates your account--> in <a>");
this.waitForPopup(/mail/, function(){
this.test.pass("popup opened");
});
this.withPopup(/mail/, function(){
this.viewport(1400,800);
this.test.pass("With Popup");
//wait something on your website (for me selector .boxValid)
this.waitForSelector(".boxValid", function(){
/*
* Here your code after validation
*/
});
});
})
It might be possible to do it with normal gmail using event, see resource.received.

Categories