idle-vue plugin for idle state issue - vue.js

I am using idle-vue plugin to see if any logged-in user is inactive for more than 10 minutes and if yes then logged them out after displaying a pop-up message.
I have followed this https://medium.com/js-dojo/how-to-set-timer-idle-in-vue-1f4b57beb886 site to implement the code. But here I want to let user to decide whether he wants to continue the session or to logout from active session. and I want that option on idle-dialog page. But as soon as the popup displays and I move cursor to click the option, the popup disappears as the idleVue value becomes false because I as a user has made any activity.
Can anyone please suggest how to achieve this functionality with idle-vue plugin or do I need to use any other plugin?

The idleness of this plugin will break when you do any activity as the mouse moves, keys down, mouse down, and touches start. So as the popup will close when you try to move the mouse.
Reversing the logic can help here.
For example, do not open the popup when the idle time reaches but open it when the user makes any activity after an inactive period.
In another word, do not open the popup instantly after idle time expires but open it when the user comes after long inactivity. In that way, the popup will be visible always and the user will come to know that he has been idle for so long, and now he can make a choice to log out or continue the session.
idle-vue library has two hooks, onIdle and onActive. You can use onActive hook to open the popup.
onActive() {
// Open the dialog when the user becomes active after an inactive period.
this.idle_dialog = true;
},
Now, you can allow users to choose options from that dialog whether to log out or to continue.

Using VueUse's useIdle (with Swal alerts - but can be done with other components). The alert shows up a few minutes before logging you out so you have time to react;
<script setup lang="ts">
import Swal from "sweetalert2";
import { useIdle, useTimestamp, watchThrottled } from "#vueuse/core";
...
const EXTEND_MESSAGE_TIME = 60 * 13; //13 minutes
const IDLE_TIMEOUT = 60 * 15; //15 minutes
const now = useTimestamp();
const { idle, lastActive } = useIdle(IDLE_TIMEOUT);
const authStore = useAuthStore();
const router = useRouter();
const idleAlertShowing = ref<boolean>(false);
let idleTimePassed, idleTimeLeft;
const idledFor = computed(() => {
return Math.floor((now.value - lastActive.value) / 1000);
});
const formatTime = (time: number) => {
const minutes = Math.floor(time / 60);
const seconds = time - minutes * 60;
return `${minutes > 0 ? `${minutes} minutes and ` : ""}${seconds} second${
seconds > 1 ? "s" : ""
}`;
};
...
watchThrottled(
idledFor,
(newValue) => {
if (newValue === EXTEND_MESSAGE_TIME) {
Swal.fire({
html: `You have been inactive for <span id='swal-idle-time-passed'>${formatTime(
newValue
)}</span> and you will be timed out in <span id='swal-idle-time-left'>${formatTime(
IDLE_TIMEOUT - newValue
)}</span>.`,
showCancelButton: false,
showConfirmButton: false
});
idleAlertShowing.value = true;
idleTimePassed = document.querySelector("#swal-idle-time-passed");
idleTimeLeft = document.querySelector("#swal-idle-time-left");
} else if (newValue >= IDLE_TIMEOUT) {
authStore.logout();
router.push({ name: "sign-in" });
if (Swal.isVisible()) {
idleAlertShowing.value = false;
Swal.close();
}
} else if (newValue > EXTEND_MESSAGE_TIME && idle.value === true) {
if (idleTimePassed && idleTimeLeft) {
idleTimePassed.innerText = formatTime(newValue);
idleTimeLeft.innerText = formatTime(IDLE_TIMEOUT - newValue);
}
} else if (newValue <= 0) {
if (idleAlertShowing.value === true && Swal.isVisible()) {
Swal.close();
}
}
},
{ throttle: 500 }
);

Related

Vuetify expansion panels set value with sessionStorage and do not trigger watcher

I am passing from a parent component expand prop as true.
in my component, I have an expansion-panels vuetify component with v-model="panel"
<v-expansion-panels
:focusable="!totalCount > 0"
v-model="panel"
multiple
v-if="!isLoading"
>
I am declaring my prop here
props: {
expand: Boolean,
}
And in my data, I use an array with an index to open or close my panel (I only have one)
panel: this.expand ? [0] : [1],
What I want to achieve is to store the user's choice to expand or not this panel, the first time he lands on the page it should be expanded but then when he navigates on the app and he decided to collapse the panel when he gets back the panel is still collapsed.
But as I want the user to see the expanded panel every day his choice should be removed after 12 hours.
So I decided to use sessionStorage and also store a date and when mounting the component check if my date is less or more than the time when the choice was set
mounted: function () {
this.getTimesheetsKpis();
if (sessionStorage.getItem("analyticsSession") !== null) {
var analyticsSession = JSON.parse(
sessionStorage.getItem("analyticsSession")
);
var currentDate = moment(new Date());
var sessionDate = analyticsSession.date;
var duration = moment.duration(currentDate.diff(sessionDate));
var hours = duration.asHours();
console.log("hours", hours);
var secs = duration.asSeconds();
console.log("secs", secs);
if (secs > 45 /* hours >= 12 */) {
console.log("session expired");
sessionStorage.removeItem("analyticsSession");
} else {
this.panel = analyticsSession.panel;
}
}
},
In my methods, I use a function to set the session storage item like that
setSessionStorage() {
console.log("session storage function triggered");
var today = moment(new Date());
var analyticsSession = {
date: today,
panel: this.panel,
};
sessionStorage.setItem(
"analyticsSession",
JSON.stringify(analyticsSession)
);
},
And I watch if the panel state changes to trigger that function
watch: {
panel: {
handler(newVal, oldVal){
console.log(oldVal.length, newVal.length)
if(oldVal.length !== newVal){
console.log("panel watcher", newVal, oldVal);
this.setSessionStorage();
}
}
},
My issue is that the watcher will trigger every time I navigate back to this component and will trigger the setSessionStorage function I guess it's because the expanded value is set to true and the watcher will compare with this value.
How can I avoid that?

How to correctly use setInterval for multiple functions being called repeatedly in React Native

I am building a simple app in React Native that aims to flash different colors on the screen at certain time intervals. My implementation is as follows:
useEffect(() => {
var blinkOnValue;
var blinkOffValue;
function blinkOn() {
const colorAndWord = getRandomColor(colorArray);
setBackground(colorAndWord.color);
}
function blinkOff() {
setBackground('#F3F3F3');
}
if (strobeStart) {
if (on) {
blinkOnValue = setInterval(() => {
blinkOn();
setOn(false);
}, info.length * 1000);
} else {
blinkOffValue = setInterval(() => {
blinkOff();
setOn(true);
}, info.delay * 1000);
}
}
return () => {
on ? clearInterval(blinkOnValue) : clearInterval(blinkOffValue);
};
}, [colorArray, info.delay, info.length, on, strobeStart]);
The blinkOn function sets the background a certain color and the blinkOff function sets the background a default light gray-ish color. These functions should alternate back and forth, blinking on and off at different intervals. For example, if info.length is 2 and info.delay is 0.5, then the color should flash on for 2 seconds and then the screen should be light gray for 0.5 seconds and repeat. However, the duration of both of the blinkOn and blinkOff are happening for the same amount of time, no matter what the two values are. Sometimes it uses the value from info.length, and sometimes it uses the value from info.delay which is also quite strange.
I think it has something to do with components mounting and unmounting correctly but honestly I am quite lost. If anyone has any advice on how to make this code consistently work where it flashes appropriately I would really appreciate it.
Instead of trying to time your events just right, I suggest using a single timer and computing the blink state from the current system time.
var oldState = true;
function blink() {
var ms = new Date().getTime();
var t = ms % (info.delay + info.length);
var state = (t < info.length ? true : false);
if (state == oldState) return;
if (state) {
blinkOn();
}
else
{
blinkOff();
}
oldState = state;
}
Now set a short timer to check the time and update the blink state as needed:
setInterval( () => blink(), 100 );

Locking a React Native App After an Amount of Time

I am working on a React Native application which shows a pincode screen upon login and each time the app is started (no longer active in the background). I would like to display this pincode screen each time a user leaves the app in the background longer than 5 minutes. This app will be deployed on Android and iOS.
Looking at search results shows me options for third-party libraries that may offer this functionality. Is there a way to do this without needing to resort to third-party tooling?
You could try to implement AppState change event
AppState.addEventListener('change', onAppStateChange);
let lastTimestamp = 0;
function onAppStateChange(state) {
if (state === 'background') {
// Store current time
lastTimestamp = Date.now();
} else if (state === 'active') {
if (Date.now - lastTimestamp > 5 * 60 * 1000) {
// Show you pin code screen
}
}
}
// Remember to remove listener when you don't need it
AppState.removeEventListener('change', onAppStateChange);
You can use PanResponder
import PanResponder from react-native
Add below inside your constructor
this. panResponser = PanResponder.create({
onStartShouldSetPanResponderCapture: () => {
let now = new Date().getTime();
if (now < your_desired_expiry_time) {
let newTime = new Date().getTime() + your_logout_time_in_milliseconds;
//now you can update your expiry time with newTime
}
else {
//go back to login
}
}
})
Usage
<View {...this.panResponser.panHandlers}>
...your other views
</View>

React Native Clock - setting a timer for a long period of time

I am trying to create an app with React Native that needs to show a clock on screen (only minutes need to update correct, not seconds). The app may be on screen (visible) for multiple minutes.
Is there a correct way to do this, as so far every way I have tried brings up 'Setting a timer for a long period of time, i.e. multiple minutes, is a performance and correctness issue on Android...'.
How can I accomplish a simple clock with correctness and minimal effect on battery etc.
The clock works fine, I just get the warning. I've tried various methods including regular setTimeout/Interval and this.setTimeout, also react-native-background-timer.
I also only need to update the clock when this screen comes back into view so I cancel the timer on componentDidUnmount. The clock only needs to update on minutes, not seconds, and doesn't need to be 100% accurate i.e. a small delay until the minutes updates is fine.
Thanks!
Code:
import React, {Component} from 'react';
import {View, Image, AppState} from 'react-native';
import {Text} from 'react-native';
import Styles from '../../styles/Styles';
import BackgroundTimer from 'react-native-background-timer';
/*
Digital Clock
*/
export default class UIDigitalClock extends Component {
// Render -----------------
render() {
var label = this.formatAMPM(this.state.time);
return (
<Text style={[Styles.h0,{fontSize:80,opacity:0.7}]}>{label}</Text>
);
}
formatAMPM(date) {
var hours = date.getHours();
var minutes = date.getMinutes();
var ampm = hours >= 12 ? 'pm' : 'am';
hours = hours % 12;
hours = hours ? hours : 12; // the hour '0' should be '12'
minutes = minutes < 10 ? '0'+minutes : minutes;
var strTime = hours + ':' + minutes;
return strTime;
}
//--------------------------------
constructor(props) {
super(props);
this.state = {
time: new Date()
}
}
timer = null;
componentWillMount() {
AppState.addEventListener('change', this.handleAppStateChange);
console.log('Digital Clock - Mount - Start');
this.startTimer();
}
componentWillUnmount() {
AppState.removeEventListener('change', this.handleAppStateChange);
this.stopTimer();
}
startTimer() {
return;
if (this.timer>0) return; // Already started
this.timer = BackgroundTimer.setInterval(() => {
// this will be executed every 200 ms
// even when app is the the background
this.updateTime();
}, 1000);
}
stopTimer() {
console.log("Digital Clock - stop ");
if (this.timer>0) {
BackgroundTimer.clearInterval(this.timer);
this.timer = -1;
}
}
handleAppStateChange = (appState) => {
console.log("Digital Clock app state: "+appState);
if (appState=='background') {
console.log('Digital Clock - App in Background - Stop');
this.stopTimer();
} else if (appState=='active') {
console.log('Digital Clock - App is Active - Start');
this.startTimer();
}
}
updateTime() {
let time = this.state.time;
let newTime = new Date();
// TODO - check if the app is in the foreground
console.log('Digital Clock - Tic ');
// Only update the render when the time changes...
if (!time || (time.getMinutes() != newTime.getMinutes() || time.getHours() != newTime.getHours())) {
console.log('Digital Clock - Time changed (mins/hours) - updating...');
this.setState({time: newTime});
}
}
}
Take a look at Later.js. You could set a schedule to fire every minutes
const schedule = later.parse.text('every 1 mins');
later.date.localTime();
later.setTimeout(<your function>, schedule);
Maybe this issue here can help people:
https://stackoverflow.com/a/49886834/12056841
This bug can't be fixed here we can only work around the error there are workarounds available that disable the warning

Transition with keepScrollPosition and navigateBack

We are using Durandal for our SPA application and came to a, in my opinion, common use case. We have two pages: one page is a list of entities (with filters, sorting, virtual scroll) and another is detail preview of an entity. So, user is on list page and set a filter and a list of results comes out. After scrolling a little bit down user notice an entity which he/she would like to see details for. So clicking on a proper link user is navigated to details preview page.
After "work finished" on preview page user click back button (in app itself or browser) and he/she is back on the list page. However, default 'entrance' transition scroll the page to the top and not to the position on list where user pressed preview. So in order to 'read' list further user have to scroll down where he/she was before pressing preview.
So I started to create new transition which will for certain pages (like list-search pages) keep the scroll position and for other pages (like preview or edit pages) scroll to top on transition complete. And this was easy to do however, I was surprised when I noticed that there are strange behavior on preview pages when I hit navigateBack 'button'. My already long story short, after investigation I found out that windows.history.back is completing earlier then the transition is made and this cause that preview pages are scrolled automatically down to position of previous (list) page when back button is hit. This scrolling have a very unpleasant effect on UI not mentioning that it is 'total catastrophe' for my transition.
Any idea or suggestion what could I do in this case?
Here is the code of transition. It is just a working copy not finished yet as far as I have this problem.
define(['../system'], function (system) {
var fadeOutDuration = 100;
var scrollPositions = new Array();
var getScrollObjectFor = function (node) {
var elemObjs = scrollPositions.filter(function (ele) {
return ele.element === node;
});
if (elemObjs.length > 0)
return elemObjs[0];
else
return null;
};
var addScrollPositionFor = function (node) {
var elemObj = getScrollObjectFor(node);
if (elemObj) {
elemObj.scrollPosition = $(document).scrollTop();
}
else {
scrollPositions.push({element: node, scrollPosition: $(document).scrollTop()});
}
};
var scrollTransition = function (parent, newChild, settings) {
return system.defer(function (dfd) {
function endTransition() {
dfd.resolve();
}
function scrollIfNeeded() {
var elemObj = getScrollObjectFor(newChild);
if (elemObj)
{
$(document).scrollTop(elemObj.scrollPosition);
}
else {
$(document).scrollTop(0);
}
}
if (!newChild) {
if (settings.activeView) {
addScrollPositionFor(settings.activeView);
$(settings.activeView).fadeOut(fadeOutDuration, function () {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
});
} else {
if (!settings.cacheViews) {
ko.virtualElements.emptyNode(parent);
}
endTransition();
}
} else {
var $previousView = $(settings.activeView);
var duration = settings.duration || 500;
var fadeOnly = !!settings.fadeOnly;
function startTransition() {
if (settings.cacheViews) {
if (settings.composingNewView) {
ko.virtualElements.prepend(parent, newChild);
}
} else {
ko.virtualElements.emptyNode(parent);
ko.virtualElements.prepend(parent, newChild);
}
var startValues = {
marginLeft: fadeOnly ? '0' : '20px',
marginRight: fadeOnly ? '0' : '-20px',
opacity: 0,
display: 'block'
};
var endValues = {
marginRight: 0,
marginLeft: 0,
opacity: 1
};
$(newChild).css(startValues);
var animateOptions = {
duration: duration,
easing : 'swing',
complete: endTransition,
done: scrollIfNeeded
};
$(newChild).animate(endValues, animateOptions);
}
if ($previousView.length) {
addScrollPositionFor(settings.activeView);
$previousView.fadeOut(fadeOutDuration, startTransition);
} else {
startTransition();
}
}
}).promise();
};
return scrollTransition;
});
A simpler approach could be to store the scroll position when the module deactivates and restore the scroll on viewAttached.
You could store the positions in some global app variable:
app.scrollPositions = app.scrollPositions || {};
app.scrollPositions[system.getModuleId(this)] = theCurrentScrollPosition;