inactivity timeout refactor for Vue

This commit is contained in:
Nemo Godebski-Pedersen 2025-05-21 19:21:02 +07:00
parent fea483833c
commit 7c24d821a5
5 changed files with 163 additions and 43 deletions

1
components.d.ts vendored
View File

@ -7,6 +7,7 @@ export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
AutoLogout: typeof import('./src/components/AutoLogout.vue')['default']
ChatMessage: typeof import('./src/components/ChatMessage.vue')['default']
ChatWidget: typeof import('./src/components/ChatWidget.vue')['default']
ChatWidgetOld: typeof import('./src/components/ChatWidgetOld.vue')['default']

View File

@ -1,36 +0,0 @@
function addSeconds(date, seconds) {
date.setSeconds(date.getSeconds() + seconds);
return date;
}
export function showCountdownTimer() {
// Get current date and time
var now = new Date().getTime();
let distance = countDownDate - now;
// Output the result in an element with id="countdown-timer-span"
let elementId = ("countdown-timer-span");
document.getElementById(elementId).innerHTML = "in " + Math.floor(distance / 1000) + "s";
// If the count down is over release resources
if (distance < 0) {
destroyCountdownTimer();
}
}
// Set the date we're counting down to
let countDownDate = addSeconds(new Date(), 5);
let showTimerInterval = null;
export function destroyCountdownTimer() {
clearInterval(showTimerInterval);
}
export function resetCountdownTimer(seconds) {
destroyCountdownTimer(); // Destroy previous timer if any
countDownDate = addSeconds(new Date(), seconds); // Set the date we're counting down to
showTimerInterval = setInterval(showCountdownTimer, 1000); // Update the count down every 1 second
}

View File

@ -0,0 +1,142 @@
<script setup lang="ts">
import { ElNotification, NotificationHandle } from 'element-plus';
import { ref, computed, h, onMounted, onBeforeUnmount } from 'vue';
// Props can be defined with defineProps
const props = defineProps({
// Define your props here
});
// Types of events that will reset the timeout
const events = ['click', 'mousemove', 'keydown', 'keypress', 'mousedown', 'scroll', 'load'];
// Set timers
let warningTimeout: NodeJS.Timeout;
let logoutTimeout: NodeJS.Timeout;
let logoutTime: number;
let countdownInterval: NodeJS.Timeout;
// Methods
function setTimers() {
// Should use a function here to get suggested timeout from OBP
const timeoutInSeconds = async () => {}
const warningDelay = 1000 * 4; // 4 seconds for development, change later
const logoutDelay = 1000 * 15; // 15 seconds for development, change later
logoutTime = Date.now() + logoutDelay;
warningTimeout = setTimeout(warningMessage, warningDelay); // 4 seconds for development, change later
logoutTimeout = setTimeout(logout, logoutDelay); // 15 seconds for development, change later
}
let warningNotification: NotificationHandle;
async function getOBPSuggestedTimeout() {
const obpApiHost = import.meta.env.VITE_OBP_API_HOST;
let timeoutInSeconds: number;
// Fetch the suggested timeout from the OBP API
const response = await fetch(`${obpApiHost}/obp/v5.1.0/ui/suggested-session-timeout`);
const json = await response.json();
if(json.timeout_in_seconds) {
timeoutInSeconds = json.timeout_in_seconds;
console.log(`Suggested value ${timeoutInSeconds} is used`);
} else {
timeoutInSeconds = 5 * 60 + 1; // Set default value to 301 seconds
console.log(`Default value ${timeoutInSeconds} is used`);
}
return timeoutInSeconds;
}
function resetTimeout() {
// Logic to reset the timeout
clearTimeout(warningTimeout);
clearTimeout(logoutTimeout);
clearInterval(countdownInterval);
if (warningNotification) {
warningNotification.close();
}
setTimers();
}
function warningMessage() {
// Logic to show warning message
console.log('Warning: You will be logged out soon');
let secondsLeft = ref(Math.ceil((logoutTime - Date.now()) / 1000));
// Update the countdown every second
countdownInterval = setInterval(() => {
secondsLeft.value = Math.ceil((logoutTime - Date.now()) / 1000);
// If time's up or almost up, clear the interval
if (secondsLeft.value <= 0) {
clearInterval(countdownInterval);
return;
}
}, 1000);
warningNotification = ElNotification({
title: 'Inactivity Warning',
message: () => h('p', null, [
h('span', null, 'You will be logged out in'),
h('strong', { style: 'color: red' }, ` ${secondsLeft.value} `),
h('span', null, 'seconds.'),
])
,
type: 'warning',
duration: 0,
position: 'top-left',
showClose: false,
})
}
function logout() {
// Logic to log out the user
console.log('Logging out...');
document.getElementById("logoff")?.click(); // If the ID of the logout button changes, this will not work
}
// Lifecycle hooks
onMounted(() => {
events.forEach(event => {
window.addEventListener(event, resetTimeout);
})
setTimers();
});
onBeforeUnmount(() => {
// Cleanup code before component is unmounted
clearTimeout(warningTimeout);
clearTimeout(logoutTimeout);
clearInterval(countdownInterval);
events.forEach(event => {
window.removeEventListener(event, resetTimeout);
});
});
</script>
<style scoped>
/* Your component styles here */
</style>
<template>
<div>
<!-- Your component content here -->
</div>
</template>

View File

@ -60,7 +60,8 @@ const headerLinksBackgroundColor = ref(headerLinksBackgroundColorSetting)
const clearActiveTab = () => {
const activeLinks = document.querySelectorAll('.router-link')
for (const active of activeLinks) {
if (active.id) {
// Skip login and logoff buttons
if (active.id && active.id !== 'login' && active.id !== 'logoff') {
active.style.backgroundColor = 'transparent'
active.style.color = '#39455f'
}
@ -163,11 +164,11 @@ const getCurrentPath = () => {
<arrow-down />
</el-icon>
</span>-->
<a v-bind:href="'/api/connect?redirect='+ encodeURIComponent(getCurrentPath())" v-show="isShowLoginButton" class="login-button router-link">
<a v-bind:href="'/api/connect?redirect='+ encodeURIComponent(getCurrentPath())" v-show="isShowLoginButton" class="login-button router-link" id="login">
{{ $t('header.login') }}
</a>
<span v-show="isShowLogOffButton" class="login-user">{{ loginUsername }}</span>
<a v-bind:href="'/api/user/logoff?redirect=' + encodeURIComponent(getCurrentPath())" v-show="isShowLogOffButton" class="logoff-button router-link">
<a v-bind:href="'/api/user/logoff?redirect=' + encodeURIComponent(getCurrentPath())" v-show="isShowLogOffButton" class="logoff-button router-link" id="logoff">
{{ $t('header.logoff') }}
</a>
</RouterView>
@ -233,8 +234,8 @@ nav {
cursor: pointer;
}
.login-button,
.logoff-button {
a.login-button,
a.logoff-button {
margin: 5px;
color: #ffffff;
background-color: #32b9ce;

View File

@ -28,14 +28,26 @@
<script setup lang="ts">
import SearchNav from '../components/SearchNav.vue'
import Menu from '../components/Menu.vue'
import AutoLogout from '../components/AutoLogout.vue'
import ChatWidget from '../components/ChatWidget.vue'
import Collections from '../components/Collections.vue'
import { inject } from 'vue'
import { onMounted, ref } from 'vue'
import { getCurrentUser } from '../obp'
const isLoggedIn = ref(false);
onMounted(async () => {
const currentUser = await getCurrentUser()
const currentResponseKeys = Object.keys(currentUser)
isLoggedIn.value = currentResponseKeys.includes('username')
})
const isChatbotEnabled = import.meta.env.VITE_CHATBOT_ENABLED === 'true'
</script>
<template>
<AutoLogout v-if=isLoggedIn />
<el-container class="root">
<el-aside class="search-nav" width="20%">
<!--Left-->