From 4646330f3f6fcc44af171ecb3fafc954ddfa806c Mon Sep 17 00:00:00 2001
From: Elena Shostak <165678770+elena-shostak@users.noreply.github.com>
Date: Mon, 29 Sep 2025 23:15:35 +0200
Subject: [PATCH] [a11y] Changed session timeout toast to modal (#235957)
## Summary
Changed session timeout toast to session timeout modal.
Before
After
### Checklist
- [x] The PR description includes the appropriate Release Notes section,
and the correct `release_note:*` label is applied per the
[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
- [x] Review the [backport
guidelines](https://docs.google.com/document/d/1VyN5k91e5OVumlc0Gb9RPa3h1ewuPE705nRtioPiTvY/edit?usp=sharing)
and apply applicable `backport:*` labels.
__Closes: https://github.com/elastic/kibana/issues/138333__
---------
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
(cherry picked from commit 75d4b7686872652368fe7d4afb0c5a4295631ec7)
# Conflicts:
# x-pack/platform/plugins/private/translations/translations/de-DE.json
# x-pack/platform/plugins/shared/security/public/session/session_timeout.test.ts
# x-pack/platform/plugins/shared/security/public/session/session_timeout.ts
---
.../translations/translations/fr-FR.json | 1 -
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
.../plugins/shared/security/public/plugin.tsx | 11 +-
.../session/session_expiration_modal.test.tsx | 107 +++++++++++++++++
.../session/session_expiration_modal.tsx | 108 ++++++++++++++++++
.../session/session_expiration_toast.test.tsx | 30 +----
.../session/session_expiration_toast.tsx | 32 +-----
.../public/session/session_timeout.test.ts | 51 +++++----
.../public/session/session_timeout.ts | 54 +++++----
10 files changed, 292 insertions(+), 104 deletions(-)
create mode 100644 x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.test.tsx
create mode 100644 x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.tsx
diff --git a/x-pack/platform/plugins/private/translations/translations/fr-FR.json b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
index be079c80fe35d..26177be39dd1a 100644
--- a/x-pack/platform/plugins/private/translations/translations/fr-FR.json
+++ b/x-pack/platform/plugins/private/translations/translations/fr-FR.json
@@ -35693,7 +35693,6 @@
"xpack.security.roleMappings.createBreadcrumb": "Créer",
"xpack.security.roles.createBreadcrumb": "Créer",
"xpack.security.sessionExpirationToast.body": "Vous serez déconnecté {timeout}.",
- "xpack.security.sessionExpirationToast.extendButton": "Rester connecté",
"xpack.security.sessionExpirationToast.title": "Délai d'expiration de session",
"xpack.security.uiApi.errorBoundaryToastMessage": "Rechargez la page pour continuer.",
"xpack.security.uiApi.errorBoundaryToastTitle": "Impossible de charger la ressource Kibana",
diff --git a/x-pack/platform/plugins/private/translations/translations/ja-JP.json b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
index f4bebf65b9f42..cab56768cc08d 100644
--- a/x-pack/platform/plugins/private/translations/translations/ja-JP.json
+++ b/x-pack/platform/plugins/private/translations/translations/ja-JP.json
@@ -35669,7 +35669,6 @@
"xpack.security.roleMappings.createBreadcrumb": "作成",
"xpack.security.roles.createBreadcrumb": "作成",
"xpack.security.sessionExpirationToast.body": "ログアウト{timeout}します。",
- "xpack.security.sessionExpirationToast.extendButton": "ログイン状態を維持",
"xpack.security.sessionExpirationToast.title": "セッションタイムアウト",
"xpack.security.uiApi.errorBoundaryToastMessage": "続行するにはページを再読み込みしてください。",
"xpack.security.uiApi.errorBoundaryToastTitle": "Kibanaアセットを読み込めませんでした",
diff --git a/x-pack/platform/plugins/private/translations/translations/zh-CN.json b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
index eff87830f9c9b..a096e1906ca45 100644
--- a/x-pack/platform/plugins/private/translations/translations/zh-CN.json
+++ b/x-pack/platform/plugins/private/translations/translations/zh-CN.json
@@ -35727,7 +35727,6 @@
"xpack.security.roleMappings.createBreadcrumb": "创建",
"xpack.security.roles.createBreadcrumb": "创建",
"xpack.security.sessionExpirationToast.body": "您会在 {timeout} 后自动注销。",
- "xpack.security.sessionExpirationToast.extendButton": "保持登录",
"xpack.security.sessionExpirationToast.title": "会话超时",
"xpack.security.uiApi.errorBoundaryToastMessage": "重新加载页面以继续。",
"xpack.security.uiApi.errorBoundaryToastTitle": "无法加载 Kibana 资产",
diff --git a/x-pack/platform/plugins/shared/security/public/plugin.tsx b/x-pack/platform/plugins/shared/security/public/plugin.tsx
index beb1015f8bcee..58553f8765ec9 100644
--- a/x-pack/platform/plugins/shared/security/public/plugin.tsx
+++ b/x-pack/platform/plugins/shared/security/public/plugin.tsx
@@ -192,7 +192,7 @@ export class SecurityPlugin
core: CoreStart,
{ management, share }: PluginStartDependencies
): SecurityPluginStart {
- const { application, http, notifications } = core;
+ const { application, http, notifications, overlays } = core;
const { anonymousPaths } = http;
const logoutUrl = getLogoutUrl(http);
@@ -200,7 +200,14 @@ export class SecurityPlugin
const sessionExpired = new SessionExpired(application, logoutUrl, tenant);
http.intercept(new UnauthorizedResponseHttpInterceptor(sessionExpired, anonymousPaths));
- this.sessionTimeout = new SessionTimeout(core, notifications, sessionExpired, http, tenant);
+ this.sessionTimeout = new SessionTimeout(
+ core,
+ notifications,
+ overlays,
+ sessionExpired,
+ http,
+ tenant
+ );
this.sessionTimeout.start();
this.securityCheckupService.start(core);
diff --git a/x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.test.tsx b/x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.test.tsx
new file mode 100644
index 0000000000000..944dde7f46166
--- /dev/null
+++ b/x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.test.tsx
@@ -0,0 +1,107 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import { render } from '@testing-library/react';
+import React from 'react';
+import { of } from 'rxjs';
+
+import { I18nProvider } from '@kbn/i18n-react';
+
+import { SessionExpirationModal } from './session_expiration_modal';
+import type { SessionState } from './session_timeout';
+
+describe('SessionExpirationModal', () => {
+ it('renders modal when session state is available', () => {
+ const sessionState$ = of({
+ lastExtensionTime: Date.now(),
+ expiresInMs: 60 * 1000,
+ canBeExtended: true,
+ });
+ const onExtend = jest.fn();
+ const onClose = jest.fn();
+
+ const { getByTestId } = render(
+
+
+
+ );
+
+ const extendButton = getByTestId('session-expiration-extend-button');
+
+ expect(extendButton).toHaveTextContent('Stay logged in');
+ expect(extendButton).toHaveFocus();
+ });
+
+ it('renders null when session state is not available', () => {
+ const sessionState$ = of(null);
+ const onExtend = jest.fn();
+ const onClose = jest.fn();
+
+ const { container } = render(
+
+
+
+ );
+
+ expect(container.firstChild).toBeNull();
+ });
+
+ it('renders null when expiresInMs is not available', () => {
+ const sessionState$ = of({
+ lastExtensionTime: Date.now(),
+ expiresInMs: null,
+ canBeExtended: true,
+ });
+ const onExtend = jest.fn();
+ const onClose = jest.fn();
+
+ const { container } = render(
+
+
+
+ );
+
+ expect(container.firstChild).toBeNull();
+ });
+
+ it('has proper accessibility attributes', () => {
+ const sessionState$ = of({
+ lastExtensionTime: Date.now(),
+ expiresInMs: 60 * 1000,
+ canBeExtended: true,
+ });
+ const onExtend = jest.fn();
+ const onClose = jest.fn();
+
+ const { queryByRole, getByText } = render(
+
+
+
+ );
+
+ const modal = queryByRole('dialog');
+ expect(modal).toHaveAttribute('aria-labelledby', 'session-expiration-modal-title');
+ expect(getByText('Session timeout')).toHaveAttribute('id', 'session-expiration-modal-title');
+ });
+});
diff --git a/x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.tsx b/x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.tsx
new file mode 100644
index 0000000000000..8c13059c58a48
--- /dev/null
+++ b/x-pack/platform/plugins/shared/security/public/session/session_expiration_modal.tsx
@@ -0,0 +1,108 @@
+/*
+ * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
+ * or more contributor license agreements. Licensed under the Elastic License
+ * 2.0; you may not use this file except in compliance with the Elastic License
+ * 2.0.
+ */
+
+import {
+ EuiButton,
+ EuiFlexGroup,
+ EuiIcon,
+ EuiModal,
+ EuiModalBody,
+ EuiModalFooter,
+ EuiModalHeader,
+ EuiModalHeaderTitle,
+} from '@elastic/eui';
+import type { FunctionComponent } from 'react';
+import React from 'react';
+import useAsyncFn from 'react-use/lib/useAsyncFn';
+import useObservable from 'react-use/lib/useObservable';
+import type { Observable } from 'rxjs';
+
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage, FormattedRelativeTime } from '@kbn/i18n-react';
+import { toMountPoint } from '@kbn/react-kibana-mount';
+
+import type { SessionState } from './session_timeout';
+import type { StartServices } from '..';
+import { SESSION_GRACE_PERIOD_MS } from '../../common/constants';
+
+export interface SessionExpirationModalProps {
+ sessionState$: Observable;
+ onExtend: () => Promise;
+ onClose: () => void;
+}
+
+export const SessionExpirationModal: FunctionComponent = ({
+ sessionState$,
+ onExtend,
+ onClose,
+}) => {
+ const state = useObservable(sessionState$);
+ const [{ loading }, extend] = useAsyncFn(onExtend);
+
+ if (!state || !state.expiresInMs) {
+ return null;
+ }
+
+ const timeoutSeconds = Math.max(state.expiresInMs - SESSION_GRACE_PERIOD_MS, 0) / 1000;
+
+ return (
+
+
+
+
+ {i18n.translate('xpack.security.sessionExpirationModal.title', {
+ defaultMessage: 'Session timeout',
+ })}
+
+
+
+
+ ,
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const createSessionExpirationModal = (
+ services: StartServices,
+ sessionState$: Observable,
+ onExtend: () => Promise,
+ onClose: () => void
+) => {
+ return toMountPoint(
+ ,
+ services
+ );
+};
diff --git a/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.test.tsx b/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.test.tsx
index 46b733c535ec4..03224e6ab3902 100644
--- a/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.test.tsx
+++ b/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.test.tsx
@@ -4,7 +4,7 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import { fireEvent, render } from '@testing-library/react';
+import { render } from '@testing-library/react';
import React from 'react';
import { of } from 'rxjs';
@@ -22,9 +22,8 @@ describe('createSessionExpirationToast', () => {
expiresInMs: 60 * 1000,
canBeExtended: true,
});
- const onExtend = jest.fn();
const onClose = jest.fn();
- const toast = createSessionExpirationToast(coreStart, sessionState$, onExtend, onClose);
+ const toast = createSessionExpirationToast(coreStart, sessionState$, onClose);
expect(toast).toEqual(
expect.objectContaining({
@@ -49,7 +48,7 @@ describe('SessionExpirationToast', () => {
const { getByText } = render(
-
+
);
getByText(/You will be logged out in [0-9]+ minutes/);
@@ -64,41 +63,22 @@ describe('SessionExpirationToast', () => {
const { getByText } = render(
-
+
);
getByText(/You will be logged out in [0-9]+ seconds/);
});
- it('renders extend button if session can be extended', () => {
- const sessionState$ = of({
- lastExtensionTime: Date.now(),
- expiresInMs: 60 * 1000,
- canBeExtended: true,
- });
- const onExtend = jest.fn().mockReturnValue(new Promise(() => {}));
-
- const { getByRole } = render(
-
-
-
- );
- fireEvent.click(getByRole('button', { name: 'Stay logged in' }));
-
- expect(onExtend).toHaveBeenCalled();
- });
-
it('does not render extend button if session cannot be extended', () => {
const sessionState$ = of({
lastExtensionTime: Date.now(),
expiresInMs: 60 * 1000,
canBeExtended: false,
});
- const onExtend = jest.fn();
const { queryByRole } = render(
-
+
);
expect(queryByRole('button', { name: 'Stay logged in' })).toBeNull();
diff --git a/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.tsx b/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.tsx
index f38638a77bc33..9e9e7f0b8d199 100644
--- a/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.tsx
+++ b/x-pack/platform/plugins/shared/security/public/session/session_expiration_toast.tsx
@@ -5,10 +5,8 @@
* 2.0.
*/
-import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import type { FunctionComponent } from 'react';
import React from 'react';
-import useAsyncFn from 'react-use/lib/useAsyncFn';
import useObservable from 'react-use/lib/useObservable';
import type { Observable } from 'rxjs';
@@ -23,15 +21,12 @@ import { SESSION_GRACE_PERIOD_MS } from '../../common/constants';
export interface SessionExpirationToastProps {
sessionState$: Observable;
- onExtend: () => Promise;
}
export const SessionExpirationToast: FunctionComponent = ({
sessionState$,
- onExtend,
}) => {
const state = useObservable(sessionState$);
- const [{ loading }, extend] = useAsyncFn(onExtend);
if (!state || !state.expiresInMs) {
return null;
@@ -42,39 +37,19 @@ export const SessionExpirationToast: FunctionComponent,
}}
/>
);
- if (state.canBeExtended) {
- return (
- <>
- {expirationWarning}
-
-
-
-
-
-
-
-
- >
- );
- }
-
return expirationWarning;
};
export const createSessionExpirationToast = (
services: StartServices,
sessionState$: Observable,
- onExtend: () => Promise,
onClose: () => void
): ToastInput => {
return {
@@ -83,10 +58,7 @@ export const createSessionExpirationToast = (
title: i18n.translate('xpack.security.sessionExpirationToast.title', {
defaultMessage: 'Session timeout',
}),
- text: toMountPoint(
- ,
- services
- ),
+ text: toMountPoint(, services),
onClose,
toastLifeTimeMs: 0x7fffffff, // Toast is hidden based on observable so using maximum possible timeout
};
diff --git a/x-pack/platform/plugins/shared/security/public/session/session_timeout.test.ts b/x-pack/platform/plugins/shared/security/public/session/session_timeout.test.ts
index 71180a83a8cbe..62c729c471ac7 100644
--- a/x-pack/platform/plugins/shared/security/public/session/session_timeout.test.ts
+++ b/x-pack/platform/plugins/shared/security/public/session/session_timeout.test.ts
@@ -5,7 +5,6 @@
* 2.0.
*/
-import type { ToastInputFields } from '@kbn/core/public';
import { coreMock } from '@kbn/core/public/mocks';
import {
clearBroadcastChannelInstances,
@@ -37,13 +36,23 @@ const nowMock = jest.spyOn(Date, 'now');
const visibilityStateMock = jest.spyOn(document, 'visibilityState', 'get');
function createSessionTimeout(expiresInMs: number | null = 60 * 60 * 1000, canBeExtended = true) {
- const { notifications, http } = coreMock.createSetup();
- const coreStart = coreMock.createStart();
+ const { http, notifications, overlays, ...coreStart } = coreMock.createStart();
const toast = Symbol();
+ const modal = {
+ close: jest.fn(),
+ };
notifications.toasts.add.mockReturnValue(toast as any);
+ overlays.openModal.mockReturnValue(modal as any);
const sessionExpired = createSessionExpiredMock();
const tenant = 'test';
- const sessionTimeout = new SessionTimeout(coreStart, notifications, sessionExpired, http, tenant);
+ const sessionTimeout = new SessionTimeout(
+ coreStart,
+ notifications,
+ overlays,
+ sessionExpired,
+ http,
+ tenant
+ );
http.fetch.mockResolvedValue({
expiresInMs,
@@ -51,7 +60,7 @@ function createSessionTimeout(expiresInMs: number | null = 60 * 60 * 1000, canBe
provider: { type: 'basic', name: 'basic1' },
} as SessionInfo);
- return { sessionTimeout, sessionExpired, notifications, http };
+ return { sessionTimeout, sessionExpired, notifications, http, overlays, modal };
}
describe('SessionTimeout', () => {
@@ -263,20 +272,19 @@ describe('SessionTimeout', () => {
});
it('shows warning before session expires', async () => {
- const { sessionTimeout, notifications } = createSessionTimeout(60 * 60 * 1000);
+ const { sessionTimeout, notifications, overlays } = createSessionTimeout(60 * 60 * 1000);
await sessionTimeout.start();
jest.advanceTimersByTime(
60 * 60 * 1000 - SESSION_GRACE_PERIOD_MS - SESSION_EXPIRATION_WARNING_MS
);
- expect(notifications.toasts.add).toHaveBeenCalledWith(
- expect.objectContaining({ color: 'warning', iconType: 'clock' })
- );
+ expect(overlays.openModal).toHaveBeenCalled();
+ expect(notifications.toasts.add).not.toHaveBeenCalled();
});
it('extends session when closing expiration warning', async () => {
- const { sessionTimeout, notifications, http } = createSessionTimeout(60 * 60 * 1000);
+ const { sessionTimeout, overlays, http } = createSessionTimeout(60 * 60 * 1000);
await sessionTimeout.start();
expect(http.fetch).toHaveBeenCalledTimes(1);
@@ -293,19 +301,11 @@ describe('SessionTimeout', () => {
expect.objectContaining({ asSystemRequest: true })
);
- const [toast] = notifications.toasts.add.mock.calls[0] as [ToastInputFields];
-
- toast.onClose!();
-
- expect(http.fetch).toHaveBeenCalledTimes(3);
- expect(http.fetch).toHaveBeenLastCalledWith(
- SESSION_ROUTE,
- expect.objectContaining({ asSystemRequest: false })
- );
+ expect(overlays.openModal).toHaveBeenCalled();
});
it('show warning 5 minutes before expiration if not previously dismissed', async () => {
- const { sessionTimeout, notifications } = createSessionTimeout(null);
+ const { sessionTimeout, notifications, overlays } = createSessionTimeout(null, false);
await sessionTimeout.start();
const expiresInMs = 10 * 60 * 1000;
@@ -321,10 +321,11 @@ describe('SessionTimeout', () => {
jest.advanceTimersByTime(showWarningInMs);
expect(notifications.toasts.add).toHaveBeenCalled();
+ expect(overlays.openModal).not.toHaveBeenCalled();
});
it('do not show warning again if previously dismissed', async () => {
- const { sessionTimeout, notifications } = createSessionTimeout(null);
+ const { sessionTimeout, notifications, overlays } = createSessionTimeout(null, false);
await sessionTimeout.start();
const expiresInMs = 10 * 60 * 1000;
@@ -348,6 +349,7 @@ describe('SessionTimeout', () => {
// dismissed for 10 minutes we will only show it after 10 minutes have elapsed
jest.advanceTimersByTime(showWarningInMs);
expect(notifications.toasts.add).not.toHaveBeenCalled();
+ expect(overlays.openModal).not.toHaveBeenCalled();
// Advance the timer further so that a total have 10 minutes would have passed. This is the
// expiration time of the warning that was dismissed.
@@ -356,19 +358,20 @@ describe('SessionTimeout', () => {
});
it('hides warning if session gets extended', async () => {
- const { sessionTimeout, notifications } = createSessionTimeout(60 * 60 * 1000);
+ const { sessionTimeout, overlays } = createSessionTimeout(60 * 60 * 1000);
await sessionTimeout.start();
jest.advanceTimersByTime(
60 * 60 * 1000 - SESSION_GRACE_PERIOD_MS - SESSION_EXPIRATION_WARNING_MS
);
- expect(notifications.toasts.add).toHaveBeenCalled();
+ expect(overlays.openModal).toHaveBeenCalled();
// eslint-disable-next-line dot-notation
await sessionTimeout['fetchSessionInfo'](true);
- expect(notifications.toasts.remove).toHaveBeenCalled();
+ const modalInstance = overlays.openModal.mock.results[0].value;
+ expect(modalInstance.close).toHaveBeenCalled();
});
it('logs user out slightly before session expires', async () => {
diff --git a/x-pack/platform/plugins/shared/security/public/session/session_timeout.ts b/x-pack/platform/plugins/shared/security/public/session/session_timeout.ts
index e119e43db1b72..79cc7c10b20f5 100644
--- a/x-pack/platform/plugins/shared/security/public/session/session_timeout.ts
+++ b/x-pack/platform/plugins/shared/security/public/session/session_timeout.ts
@@ -11,10 +11,12 @@ import { BehaviorSubject, skip, tap, throttleTime } from 'rxjs';
import type {
HttpFetchOptionsWithPath,
HttpSetup,
- NotificationsSetup,
+ NotificationsStart,
+ OverlayStart,
Toast,
} from '@kbn/core/public';
+import { createSessionExpirationModal } from './session_expiration_modal';
import { createSessionExpirationToast } from './session_expiration_toast';
import type { SessionExpired } from './session_expired';
import type { StartServices } from '..';
@@ -48,6 +50,7 @@ export class SessionTimeout {
private subscription?: Subscription;
private warningToast?: Toast;
+ private warningModal?: ReturnType;
private stopActivityMonitor?: Function;
private stopVisibilityMonitor?: Function;
@@ -59,7 +62,8 @@ export class SessionTimeout {
constructor(
private startServices: StartServices,
- private notifications: NotificationsSetup,
+ private notifications: NotificationsStart,
+ private overlays: OverlayStart,
private sessionExpired: Pick,
private http: HttpSetup,
private tenant: string
@@ -222,6 +226,7 @@ export class SessionTimeout {
return (
!this.isFetchingSessionInfo &&
!this.warningToast &&
+ !this.warningModal &&
Date.now() >
lastExtensionTime + SESSION_EXTENSION_THROTTLE_MS * Math.exp(this.consecutiveErrorCount)
);
@@ -256,23 +261,26 @@ export class SessionTimeout {
};
private showWarning = () => {
- if (!this.warningToast) {
- const onExtend = async () => {
- const { canBeExtended } = this.sessionState$.getValue();
- if (canBeExtended) {
- await this.fetchSessionInfo(true);
- }
- };
- const onClose = () => {
- this.hideWarning(true);
+ const { canBeExtended } = this.sessionState$.getValue();
+
+ const onExtend = async () => {
+ await this.fetchSessionInfo(true);
+ };
+
+ const onClose = () => {
+ this.hideWarning(true);
+
+ if (canBeExtended) {
return onExtend();
- };
- const toast = createSessionExpirationToast(
- this.startServices,
- this.sessionState$,
- onExtend,
- onClose
+ }
+ };
+
+ if (canBeExtended && !this.warningModal) {
+ this.warningModal = this.overlays.openModal(
+ createSessionExpirationModal(this.startServices, this.sessionState$, onExtend, onClose)
);
+ } else if (!canBeExtended && !this.warningToast) {
+ const toast = createSessionExpirationToast(this.startServices, this.sessionState$, onClose);
this.warningToast = this.notifications.toasts.add(toast);
}
};
@@ -281,9 +289,15 @@ export class SessionTimeout {
if (this.warningToast) {
this.notifications.toasts.remove(this.warningToast);
this.warningToast = undefined;
- if (snooze) {
- this.snoozedWarningState = this.sessionState$.getValue();
- }
+ }
+
+ if (this.warningModal) {
+ this.warningModal.close();
+ this.warningModal = undefined;
+ }
+
+ if (snooze) {
+ this.snoozedWarningState = this.sessionState$.getValue();
}
};
}