From 03690daa354c6e0ab8efb3ca6bc011ebeb100407 Mon Sep 17 00:00:00 2001 From: Jeremy Wharton Date: Thu, 10 Aug 2023 16:56:21 -0500 Subject: [PATCH] web/satellite/vuetify-poc: add session timeout and refresh This change allows sessions within the Vuetify project to be refreshed. In addition, dialogs appear to inform the user when a session is about to expire and when it has expired. Resolves #6147 Change-Id: I53d5508825aa9992e4fed8ce7b957d949ff28e2d --- .../src/components/utils/SessionWrapper.vue | 7 +- .../src/composables/useSessionTimeout.ts | 32 ++--- .../utils/constants/analyticsEventNames.ts | 1 + .../components/dialogs/InactivityDialog.vue | 135 ++++++++++++++++++ .../dialogs/SessionExpiredDialog.vue | 76 ++++++++++ .../src/components/utils/SessionWrapper.vue | 44 ++++++ .../src/layouts/default/Account.vue | 29 +++- .../src/layouts/default/AllProjects.vue | 8 +- .../src/layouts/default/Default.vue | 6 +- 9 files changed, 314 insertions(+), 24 deletions(-) create mode 100644 web/satellite/vuetify-poc/src/components/dialogs/InactivityDialog.vue create mode 100644 web/satellite/vuetify-poc/src/components/dialogs/SessionExpiredDialog.vue create mode 100644 web/satellite/vuetify-poc/src/components/utils/SessionWrapper.vue diff --git a/web/satellite/src/components/utils/SessionWrapper.vue b/web/satellite/src/components/utils/SessionWrapper.vue index 8d7dc6109..e284f45c0 100644 --- a/web/satellite/src/components/utils/SessionWrapper.vue +++ b/web/satellite/src/components/utils/SessionWrapper.vue @@ -19,13 +19,18 @@ import { useRouter } from 'vue-router'; import { useAnalyticsStore } from '@/store/modules/analyticsStore'; import { useSessionTimeout, INACTIVITY_MODAL_DURATION } from '@/composables/useSessionTimeout'; import { RouteConfig } from '@/types/router'; +import { useAppStore } from '@/store/modules/appStore'; +import { MODALS } from '@/utils/constants/appStatePopUps'; import InactivityModal from '@/components/modals/InactivityModal.vue'; import SessionExpiredModal from '@/components/modals/SessionExpiredModal.vue'; const analyticsStore = useAnalyticsStore(); +const appStore = useAppStore(); -const sessionTimeout = useSessionTimeout(); +const sessionTimeout = useSessionTimeout({ + showEditSessionTimeoutModal: () => appStore.updateActiveModal(MODALS.editSessionTimeout), +}); const router = useRouter(); /** diff --git a/web/satellite/src/composables/useSessionTimeout.ts b/web/satellite/src/composables/useSessionTimeout.ts index 71f17ebed..7cdab4959 100644 --- a/web/satellite/src/composables/useSessionTimeout.ts +++ b/web/satellite/src/composables/useSessionTimeout.ts @@ -19,12 +19,15 @@ import { useNotificationsStore } from '@/store/modules/notificationsStore'; import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames'; import { useNotify } from '@/utils/hooks'; import { LocalData } from '@/utils/localData'; -import { MODALS } from '@/utils/constants/appStatePopUps'; + +export interface UseSessionTimeoutOptions { + showEditSessionTimeoutModal: () => void; +} const RESET_ACTIVITY_EVENTS: readonly string[] = ['keypress', 'mousemove', 'mousedown', 'touchmove']; export const INACTIVITY_MODAL_DURATION = 60000; -export function useSessionTimeout() { +export function useSessionTimeout(opts: UseSessionTimeoutOptions) { const initialized = ref(false); const inactivityTimerId = ref | null>(null); @@ -132,23 +135,20 @@ export function useSessionTimeout() { /** * Adds DOM event listeners and starts session timers. */ - function setupSessionTimers(): void { + async function setupSessionTimers(): Promise { if (initialized.value || !configStore.state.config.inactivityTimerEnabled) return; const expiresAt = LocalData.getSessionExpirationDate(); - - if (expiresAt) { - RESET_ACTIVITY_EVENTS.forEach((eventName: string) => { - document.addEventListener(eventName, onSessionActivity, false); - }); - - if (expiresAt.getTime() - sessionDuration.value + sessionRefreshInterval.value < Date.now()) { - refreshSession(); - } - - restartSessionTimers(); + if (!expiresAt || expiresAt.getTime() - sessionDuration.value + sessionRefreshInterval.value < Date.now()) { + await refreshSession(); } + RESET_ACTIVITY_EVENTS.forEach((eventName: string) => { + document.addEventListener(eventName, onSessionActivity, false); + }); + + restartSessionTimers(); + initialized.value = true; } @@ -177,7 +177,7 @@ export function useSessionTimeout() { }, INACTIVITY_MODAL_DURATION); }, sessionDuration.value - INACTIVITY_MODAL_DURATION); - if (!debugTimerShown.value) return; + if (!configStore.state.config.inactivityTimerViewerEnabled) return; const debugTimer = () => { const expiresAt = LocalData.getSessionExpirationDate(); @@ -221,7 +221,7 @@ export function useSessionTimeout() { isSessionRefreshing.value = false; if (manual && !usersStore.state.settings.sessionDuration) { - appStore.updateActiveModal(MODALS.editSessionTimeout); + opts.showEditSessionTimeoutModal(); } } diff --git a/web/satellite/src/utils/constants/analyticsEventNames.ts b/web/satellite/src/utils/constants/analyticsEventNames.ts index 1ae14210e..b6055ac38 100644 --- a/web/satellite/src/utils/constants/analyticsEventNames.ts +++ b/web/satellite/src/utils/constants/analyticsEventNames.ts @@ -62,6 +62,7 @@ export enum AnalyticsEvent { export enum AnalyticsErrorEventSource { ACCESS_GRANTS_PAGE = 'Access grants page', + ACCOUNT_PAGE = 'Account page', ACCOUNT_SETTINGS_AREA = 'Account settings area', BILLING_HISTORY_TAB = 'Billing history tab', BILLING_COUPONS_TAB = 'Billing coupons tab', diff --git a/web/satellite/vuetify-poc/src/components/dialogs/InactivityDialog.vue b/web/satellite/vuetify-poc/src/components/dialogs/InactivityDialog.vue new file mode 100644 index 000000000..6901a0818 --- /dev/null +++ b/web/satellite/vuetify-poc/src/components/dialogs/InactivityDialog.vue @@ -0,0 +1,135 @@ +// Copyright (C) 2023 Storj Labs, Inc. +// See LICENSE for copying information. + + + + diff --git a/web/satellite/vuetify-poc/src/components/dialogs/SessionExpiredDialog.vue b/web/satellite/vuetify-poc/src/components/dialogs/SessionExpiredDialog.vue new file mode 100644 index 000000000..e8bc2c2c3 --- /dev/null +++ b/web/satellite/vuetify-poc/src/components/dialogs/SessionExpiredDialog.vue @@ -0,0 +1,76 @@ +// Copyright (C) 2023 Storj Labs, Inc. +// See LICENSE for copying information. + + + + diff --git a/web/satellite/vuetify-poc/src/components/utils/SessionWrapper.vue b/web/satellite/vuetify-poc/src/components/utils/SessionWrapper.vue new file mode 100644 index 000000000..5cbe8ba5b --- /dev/null +++ b/web/satellite/vuetify-poc/src/components/utils/SessionWrapper.vue @@ -0,0 +1,44 @@ +// Copyright (C) 2023 Storj Labs, Inc. +// See LICENSE for copying information. + + + + diff --git a/web/satellite/vuetify-poc/src/layouts/default/Account.vue b/web/satellite/vuetify-poc/src/layouts/default/Account.vue index 1a658e5c8..0e410639c 100644 --- a/web/satellite/vuetify-poc/src/layouts/default/Account.vue +++ b/web/satellite/vuetify-poc/src/layouts/default/Account.vue @@ -3,13 +3,16 @@ diff --git a/web/satellite/vuetify-poc/src/layouts/default/AllProjects.vue b/web/satellite/vuetify-poc/src/layouts/default/AllProjects.vue index d228a2a6e..dff249931 100644 --- a/web/satellite/vuetify-poc/src/layouts/default/AllProjects.vue +++ b/web/satellite/vuetify-poc/src/layouts/default/AllProjects.vue @@ -3,8 +3,10 @@ @@ -19,6 +21,8 @@ import { useUsersStore } from '@/store/modules/usersStore'; import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames'; import { useNotify } from '@/utils/hooks'; +import SessionWrapper from '@poc/components/utils/SessionWrapper.vue'; + const usersStore = useUsersStore(); const notify = useNotify(); diff --git a/web/satellite/vuetify-poc/src/layouts/default/Default.vue b/web/satellite/vuetify-poc/src/layouts/default/Default.vue index 7cff4c841..432d079e6 100644 --- a/web/satellite/vuetify-poc/src/layouts/default/Default.vue +++ b/web/satellite/vuetify-poc/src/layouts/default/Default.vue @@ -6,11 +6,11 @@
- + @@ -35,6 +35,8 @@ import { useAccessGrantsStore } from '@/store/modules/accessGrantsStore'; import { AnalyticsErrorEventSource } from '@/utils/constants/analyticsEventNames'; import { useNotify } from '@/utils/hooks'; +import SessionWrapper from '@poc/components/utils/SessionWrapper.vue'; + const router = useRouter(); const route = useRoute(); const notify = useNotify();