On my Angular web-app, when a browser refreshes or reloads, the login for the user is lost and he must go through the login steps again. I would like to allow the login status remain open for the user after the browser reload, but only within a short interval, perhaps 10 seconds or so. When the web-app reloads, it checks if the come-back is within these 10 seconds interval. For that I need to know when the refresh/reload or the last moment the app was active.
How do we determine the moment/time right before the browser reloads (or closes) or the nearest time to that?
You can capture the reload event and store a timestamp to the localstorage, then do check and comparison each time your app is initiated. A simple function can be:
window.onbeforeunload = ()=>{
localStorage.setItem('last_reload_time',(new Date()).getTime());
}
Then in your app, check for last_reload_time and do compare with current timestamp.
Another DOM event that may help is visibilitychange
In its simple JS form, I used the answer by Metabolic as the starting point.
However, the functionality of the event: "onbeforeunload" is a bit tricky as stated here: MDN, and few browsers, e.g. Chrome ware giving me cold shoulder on the event - not firing. Note, that in most cases the reload event fires, but is not caught by the debugger and if you'll place breakpoints in (eg: in fn: onBeforeUnload() ), do not expect them to stop your code on the event!
I used this approach with rxjs to resolve - on Angular.
import { fromEvent } from 'rxjs';
persistKey: string = 'TIME_BEFORE_UNLOAD';
//// eventually, instead of rxjs: fromEvent(...)
//// you can use this:
// #HostListener("window:beforeunload", ["$event"])
// unloadHandler(event: Event) {
// this.onBeforeUnload(event);
// }
ngOnInit() {
// use this to test and see;
// the time stamps should change in console
// after each reload click
console.log('-- Stored time before unload: ',
localStorage.getItem(this.persistKey));
this.subscribeToBrowserEvents();
}
private subscribeToBrowserEvents() {
fromEvent(window, 'beforeunload')
.subscribe(event => this.onBeforeUnload(event));
}
private onBeforeUnload(event) {
const val: string = new Date().toISOString();
localStorage.setItem(this.persistKey, val);
}
Related
I need to create a webpage that will generate a bad First Input Delay (FID) value.
In case you aren't aware, FID is part of Google's Web Core Vitals.
I want to simulate a bad FID because I am testing a website scanning tool that is supposed to flag a bad FID value. Therefore I want to simulate a bad value on a webpage to make sure it works.
To be clear - I am NOT trying to fix my First Input Delay. I want to create a webpage that gives a bad First Input Delay value on purpose.
But I'm not sure how to do that.
I have a HTML page with <button id="button">Click Me</button>. And in the <head> I have added this script:
<script type="module">
// Get the First Input Delay (FID) Score
import {onFID} from 'https://unpkg.com/web-vitals#3/dist/web-vitals.attribution.js?module';
// Get the button element
const button = document.getElementById('button');
// Add a click event listener to the button
button.addEventListener('click', async () => {
// Make a delay
await new Promise((resolve) => setTimeout(resolve, 5000));
// Print the FID score to the console
onFID(console.log);
});
</script>
The imported onFID method is what Google uses from Web Vitals to report the FID value.
You can see a live version of the above script here: http://seosins.com/extra-pages/first-input-delay/
But when I click the button, 5000 milliseconds later it only prints a FID of about 3 milliseconds.
The 5000 millisecond delay is not included in the FID value.
Why doesn't it report the FID value as 5003 milliseconds?
When I try to simulate a bad FID value I am doing something wrong.
What could it be?
Update:
As suggested in the comments, I have also tried adding a delay on the server using a Cloudflare Worker. That worker delayed the server response by 5000 milliseconds. But it didn't work, because the FID value was unchanged.
Also I do not think this is the correct approach because FID measures the time from when a user first interacts with your site (i.e. when they click a link, tap on a button, etc) to the time when the browser is actually able to respond to that interaction. While the Cloudflare Worker was only slowing down the initial server response. Therefore I have since removed this experiment from the page.
I think you misunderstand what FID is
From web.dev's page on First Input Delay (FID):
What is FID?
FID measures the time from when a user first interacts with a page (that is, when they click a link, tap on a button, or use a custom, JavaScript-powered control) to the time when the browser is actually able to begin processing event handlers in response to that interaction.
and
💡 Gotchas
FID only measures the "delay" in event processing. It does not measure the event processing time itself nor the time it takes the browser to update the UI after running event handlers.
also:
In general, input delay (a.k.a. input latency) happens because the browser's main thread is busy doing something else, so it can't (yet) respond to the user. One common reason this might happen is the browser is busy parsing and executing a large JavaScript file loaded by your app.
Here is my understanding: Actual FID measurement is built into Chrome. The web-vitals library simulates this using browser measurement APIs. The measurement isn't based on when onFID is called; onFID simply sets up a measurement event listener with those APIs. What is measured is the time between when a user clicks on something (e.g. the button) and when its event handler is triggered, not how long that handler takes to complete (see second quote above).
First, we need something that occupies (i.e. blocks) the JS Event Loop
setTimeout does not do that. It just delays when something happens. In the meantime the event loop is free to do other work, e.g. process user input. Instead you need code that does exactly what you're not supposed to do: Do some lengthy blocking CPU-bound work synchronously, thus preventing the event loop from handling other events including user input.
Here is a function that will block a thread for a given amount of time:
function blockThread (millis) {
let start = Date.now()
let x = 928342343234
while ((Date.now() - start) < millis) {
x = Math.sqrt(x) + 1
x = x * x
}
}
or maybe just:
function blockThread (millis) {
let start = Date.now()
while ((Date.now() - start) < millis) {
}
}
Now the question is: When/where do we block the event loop?
Before I reached the understanding above, my first thought was to just modify your approach: block the event loop in the button click event listener. Take your code, remove the async, and call blockThread instead of setting a timer. This runnable demo does that:
// this version of blockThread returns some info useful for logging
function blockThread (millis) {
let start = Date.now()
let i = 0
let x = 928342343234
while (true) {
i++
x = Math.sqrt(x) + 1
x = x * x
let elapsed = (Date.now() - start)
if (elapsed > millis) {
return {elapsed: elapsed, loopIterations: i}
}
}
}
const button = document.getElementById('button');
button.addEventListener('click', () => {
const r = blockThread(5000)
console.log(`${r.elapsed} millis elapsed after ${r.loopIterations} loop iterations`)
console.log('calling onFID')
window.onFID(console.log)
console.log('done')
})
<!DOCTYPE html>
<html>
<script type="module">
import {onFID} from 'https://unpkg.com/web-vitals#3/dist/web-vitals.attribution.js?module'
window.onFID = onFID
</script>
<head>
<title>Title of the document</title>
</head>
<body>
<button id='button'>Block for 5000 millis then get the First Input Delay (FID) Score</button> 👈🏾 click me!
</body>
<p> 🚩 Notice how the UI (i.e. StackOverflow) will be unresponsive to your clicks for 5 seconds after you press the above button. If you click on some other button or link on this page, it will only respond after the 5 seconds have elapsed</p>
</html>
I'd give the above a try to confirm, but I'd expect it to NOT impact the FID measurement because:
Both your version and mine blocks during the execution of the event handler, but does NOT delay its start.
What is measured is the time between when a user clicks on something (e.g. the button) and when its event handler is triggered.
I'm sure we need to block the event loop before the user clicks on an input, and for long enough that it remains blocked during and after that click. How long the event loop remains blocked after the click will be what the FID measures.
I'm also pretty sure we need to import and call onFID before we block the event loop.
The web-vitals library simulates Chrome's internal measurement. It needs to initialize and attach itself to the browser's measurement APIs as a callback in order for it to be able to measure anything. That's what calling onFID does.
So let's try a few options...
start blocking the event loop while the page is loaded
Looking at the Basic usage instructions for web-vitals I arrived at this:
<!-- This will run synchronously during page load -->
<script>
import {onFID} from 'web-vitals.js'
// Setup measurement of FID and log it as soon as it is
// measured, e.g. after the user clicks the button.
onFID(console.log)
// !!! insert the blockThread function declaration here !!!
// Block the event loop long enough so that you can click
// on the button before the event loop is unblocked. We are
// simulating page resources continuing to load that delays
// the time an input's event handler is ever called.
blockThread(5000)
</script>
But I suspect that calling blockThread as above will actually also block the page/DOM from loading so you won't even have a button to click until it's too late. In that case:
start blocking after the page is loaded and before the DOMContentLoaded event is triggered 👈🏾 (my bet is on this one)
<script>
import {onFID} from 'web-vitals.js'
onFID(console.log)
</script>
<script defer>
// !!! insert the blockThread function declaration here !!!
blockThread(5000)
</script>
If that still doesn't work, try this:
start blocking when the DOMContentLoaded event is triggered
<script>
import {onFID} from 'web-vitals.js'
onFID(console.log)
// !!! insert the blockThread function declaration here !!!
window.addEventListener('DOMContentLoaded', (event) => {
blockThread(5000)
});
</script>
🌶 Check out this this excellent answer to How to make JavaScript execute after page load? for more variations.
Let me know if none of these work, or which one does. I don't have the time right now to test this myself.
I have an IONIC 4 application where I need to call an api every 20 seconds and if the user moves to other page need to stop calling that api. I am able to make the api call at every 20 seconds but not able to stop it when I move to some other page. Here is my code below, may I know where I went wrong?
rateTimer:any;
constructor(private loginServiceService: LoginServiceService) {
this.loginServiceService.getappData();
this.rateTimer=setInterval(() => {
this.loginServiceService.getappData();
}, 10000);
}
// When I move to other page, I clear the setInterval from here
ngOnDestroy() {
clearInterval(this.rateTimer);
}
As far as I know Ionic has a cache for page-navigation which is why ngOnDestroy/ngOnInit will not always work. However, there's ionViewDidLeave which should definitely fire, once the current page is moved away from (see https://ionicframework.com/docs/api/router-outlet#life-cycle-hooks for more details):
export class YourComponent {
ionViewDidLeave() {
// Do actions here
}
}
I searched a lot for a solution to this certainly-not-unique problem, but I have not found anything that will work in my context of an HTML page.
I have an input text that contains some kind of source-code that generates something, and I can show a preview of that something on the same HTML page (by updating the background image, for example). Note that the source could be a LaTeX file, an email, a Java program, a ray-trace code, etc. The "action" to generate the preview has a certain cost to it, so I don't want to generate this preview at each modification to the source. But I'd like the preview to auto-update (the action to fire) without the user having to explicitly request it.
Another way to phrase the problem is to keep a source and sink synchronized with a certain reasonable frequency.
Here's my solution that's too greedy (updates at every change):
$('#source-text').keyup(function(){
updatePreview(); // update on a change
});
I tried throttling this by using a timestamp:
$('#source-text').keyup(function(){
if (nextTime "before" Now) { // pseudocode
updatePreview(); // update on a change
} else {
nextTime = Now + some delay // pseudocode
}
});
It's better, but it can miss the last updates once a user stops typing in the source-text field.
I thought of a "polling loop" for updates that runs at some reasonable interval and looks for changes or a flag meaning an update is needed. But I wasn't sure if that's a good model for an HTML page (or even how to do it in javascript).
Use setTimeout, but store the reference so you can prevent it from executing if further editing has occurred. Basically, only update the preview once 5 seconds past the last keystroke has passed (at least in the below example).
// maintain out of the scope of the event
var to;
$('#source-text').on('keyup',function(){
// if it exists, clear it and prevent it from occuring
if (to) clearTimeout(to);
// reassign it a new timeout that will expire (assuming user doesn't
// type before it's timed out)
to = setTimeout(function(){
updatePreview();
}, 5e3 /* 5 seconds or whatever */);
});
References:
clearTimeout
setTimeout
And not to self-bump, but here's another [related] answer: How to trigger an event in input text after I stop typing/writing?
I tweaked #bradchristie's answer, which wasn't quite the behavior I wanted (only one update occurs after the user stops typing - I want them to occur during typing, but at a throttled rate).
Here's the solution (demo at http://jsfiddle.net/p4u2mhb9/3/):
// maintain out of the scope of the event
var to;
var updateCount = 0;
var timerInProgress = false;
$('#source-text').on('keyup', function () {
// reassign a new timeout that will expire
if (!timerInProgress) {
timerInProgress = true;
to = setTimeout(function () {
timerInProgress = false;
updatePreview();
}, 1e3 /* 1 second */ );
}
});
I have a function called save(), this function gathers up all the inputs on the page, and performs an AJAX call to the server to save the state of the user's work.
save() is currently called when a user clicks the save button, or performs some other action which requires us to have the most current state on the server (generate a document from the page for example).
I am adding in the ability to auto save the user's work every so often. First I would like to prevent an AutoSave and a User generated save from running at the same time. So we have the following code (I am cutting most of the code and this is not a 1:1 but should be enough to get the idea across):
var isSaving=false;
var timeoutId;
var timeoutInterval=300000;
function save(showMsg)
{
//Don't save if we are already saving.
if (isSaving)
{
return;
}
isSaving=true;
//disables the autoSave timer so if we are saving via some other method
//we won't kick off the timer.
disableAutoSave();
if (showMsg) { //show a saving popup}
params=CollectParams();
PerformCallBack(params,endSave,endSaveError);
}
function endSave()
{
isSaving=false;
//hides popup if it's visible
//Turns auto saving back on so we save x milliseconds after the last save.
enableAutoSave();
}
function endSaveError()
{
alert("Ooops");
endSave();
}
function enableAutoSave()
{
timeoutId=setTimeOut(function(){save(false);},timeoutInterval);
}
function disableAutoSave()
{
cancelTimeOut(timeoutId);
}
My question is if this code is safe? Do the major browsers allow only a single thread to execute at a time?
One thought I had is it would be worse for the user to click save and get no response because we are autosaving (And I know how to modify the code to handle this). Anyone see any other issues here?
JavaScript in browsers is single threaded. You will only ever be in one function at any point in time. Functions will complete before the next one is entered. You can count on this behavior, so if you are in your save() function, you will never enter it again until the current one has finished.
Where this sometimes gets confusing (and yet remains true) is when you have asynchronous server requests (or setTimeouts or setIntervals), because then it feels like your functions are being interleaved. They're not.
In your case, while two save() calls will not overlap each other, your auto-save and user save could occur back-to-back.
If you just want a save to happen at least every x seconds, you can do a setInterval on your save function and forget about it. I don't see a need for the isSaving flag.
I think your code could be simplified a lot:
var intervalTime = 300000;
var intervalId = setInterval("save('my message')", intervalTime);
function save(showMsg)
{
if (showMsg) { //show a saving popup}
params=CollectParams();
PerformCallBack(params, endSave, endSaveError);
// You could even reset your interval now that you know we just saved.
// Of course, you'll need to know it was a successful save.
// Doing this will prevent the user clicking save only to have another
// save bump them in the face right away because an interval comes up.
clearInterval(intervalId);
intervalId = setInterval("save('my message')", intervalTime);
}
function endSave()
{
// no need for this method
alert("I'm done saving!");
}
function endSaveError()
{
alert("Ooops");
endSave();
}
All major browsers only support one javascript thread (unless you use web workers) on a page.
XHR requests can be asynchronous, though. But as long as you disable the ability to save until the current request to save returns, everything should work out just fine.
My only suggestion, is to make sure you indicate to the user somehow when an autosave occurs (disable the save button, etc).
All the major browsers currently single-thread javascript execution (just don't use web workers since a few browsers support this technique!), so this approach is safe.
For a bunch of references, see Is JavaScript Multithreaded?
Looks safe to me. Javascript is single threaded (unless you are using webworkers)
Its not quite on topic but this post by John Resig covers javascript threading and timers:
http://ejohn.org/blog/how-javascript-timers-work/
I think the way you're handling it is best for your situation. By using the flag you're guaranteeing that the asynchronous calls aren't overlapping. I've had to deal with asynchronous calls to the server as well and also used some sort of flag to prevent overlap.
As others have already pointed out JavaScript is single threaded, but asynchronous calls can be tricky if you're expecting things to say the same or not happen during the round trip to the server.
One thing, though, is that I don't think you actually need to disable the auto-save. If the auto-save tries to happen when a user is saving then the save method will simply return and nothing will happen. On the other hand you're needlessly disabling and reenabling the autosave every time autosave is activated. I'd recommend changing to setInterval and then forgetting about it.
Also, I'm a stickler for minimizing global variables. I'd probably refactor your code like this:
var saveWork = (function() {
var isSaving=false;
var timeoutId;
var timeoutInterval=300000;
function endSave() {
isSaving=false;
//hides popup if it's visible
}
function endSaveError() {
alert("Ooops");
endSave();
}
function _save(showMsg) {
//Don't save if we are already saving.
if (isSaving)
{
return;
}
isSaving=true;
if (showMsg) { //show a saving popup}
params=CollectParams();
PerformCallBack(params,endSave,endSaveError);
}
return {
save: function(showMsg) { _save(showMsg); },
enableAutoSave: function() {
timeoutId=setInterval(function(){_save(false);},timeoutInterval);
},
disableAutoSave: function() {
cancelTimeOut(timeoutId);
}
};
})();
You don't have to refactor it like that, of course, but like I said, I like to minimize globals. The important thing is that the whole thing should work without disabling and reenabling autosave every time you save.
Edit: Forgot had to create a private save function to be able to reference from enableAutoSave
I have a Flex client application. I need a clean up function to run in Flex when the user closes the browser. I found the following solution on the net, but it only works half-way for me. How could I fix it? Thanks in advance for any responses!
Symptoms
CustomEvent triggered, but not executed. >> EventHandler for CustomEvent.SEND_EVENTS is defined by a Mate EventMap. All the handler does is to call an HTTPServiceInvoker. In debug console, I'm able to see the handler and HTTPServiceInvoker being triggered, but neither the resultHandlers nor the faultHandlers were called. I know this event handler has no problem because when I dispatch the same CustomEvent.SEND_EVENTS in a button click handler, it behaves exactly as I expected)
Browser seems to wait for cleanUp function to complete before it closes. (all traces were printed before browser closes down)
Code
I added the following into the index.template.html
window.onbeforeunload = clean_up;
function clean_up()
{
var flex = document.${application} || window.${application};
flex.cleanUp();
}
And used the following in the application MXML file
import flash.external.ExternalInterface;
public function init():void {
ExternalInterface.addCallback("cleanUp",cleanUp);
}
public function cleanUp():void {
var newEvent:CustomEvent = new CustomEvent(CustomEvent.SEND_EVENTS);
newEvent.requestObj = myFormModel;
dispatchEvent(newEvent);
// for testing purposes
// to see whether the browser waits for Flex cleanup to finish before closing down
var i:int;
for (i=0; i<10000; i++){
trace(i);
}
}
My Setup
FlexBuilder 3
Mate MVC Framework (Mate_08_9.swc)
FlashPlayer 10
Unfortunately, there is no solid way of doing such clean up functions that execute asynchronously. The result/fault events of the HTTPService occur asynchronously after the cleanUp method is returned. The browser waits only till the onbeforeunload function (the js clean_up function) returns. Unless you call event.preventDefault() from that function, the page will be closed. Note that calling preventDefault() will result in an ok/cancel popup asking:
Are you sure you want to navigate away from this page?
Press OK to continue, or Cancel to stay on the current page.
If the user selects OK, the browser will be closed nevertheless. You can use the event.returnValue property to add a custom message to the popop.
//tested only in Firefox
window.addEventListener("beforeunload", onUnload, false);
function onUnload(e)
{
e.returnValue = "Some text that you want inserted between " +
"'Are you sure' and 'Press OK' lines";
e.preventDefault();
}
You'll never be able to reliably detect the browser code 100% of the time. If you really need to run actions then the safest course of action is to have clients send "i'm still alive" messages to the server. The server needs to track time by client and when a client doesn't send a message within the specified amount of time (with some wiggle room), then run clean-up activities.
The longer you make the time the better, it depends on how time-critical the clean-up is. If you can get away with waiting 5 minutes that's great, otherwise look at 1 minute or 30 seconds or whatever is required for your app.
An alternate way to clean up the session on client side is to use JavaScript and external.interface class in as3. Here is sample code:
JavaScript:
function cleanUp()
{
var process;
var swfID="customRightClick";
if(navigator.appName.indexOf("Microsoft") != -1){
process = window[swfID];
}else
{
process = document[swfID];
}
process.cleanUp();
}
and in the as3 class where the clean up function is defined use this:
import flash.external.ExternalInterface;
if (ExternalInterface.available)
{
ExternalInterface.addCallback("cleanUp", cleanUp);
}
function cleanUp():void {// your code }