1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { connect } from "react-redux";
6 import { useIdleTimer } from "react-idle-timer";
7 import { Dispatch } from "redux";
9 import { RootState } from "store/store";
10 import { SnackbarKind, snackbarActions } from "store/snackbar/snackbar-actions";
11 import { logout } from "store/auth/auth-action";
12 import parse from "parse-duration";
13 import React from "react";
14 import { min } from "lodash";
16 interface AutoLogoutDataProps {
17 sessionIdleTimeout: number;
18 lastWarningDuration: number;
21 interface AutoLogoutActionProps {
23 doWarn: (message: string, duration: number) => void;
24 doCloseWarn: () => void;
27 const mapStateToProps = (state: RootState, ownProps: any): AutoLogoutDataProps => ({
28 sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0,
29 lastWarningDuration: ownProps.lastWarningDuration || 60,
32 const mapDispatchToProps = (dispatch: Dispatch): AutoLogoutActionProps => ({
33 doLogout: () => dispatch<any>(logout(true)),
34 doWarn: (message: string, duration: number) =>
35 dispatch(snackbarActions.OPEN_SNACKBAR({
36 message, hideDuration: duration, kind: SnackbarKind.WARNING })),
37 doCloseWarn: () => dispatch(snackbarActions.CLOSE_SNACKBAR()),
40 export type AutoLogoutProps = AutoLogoutDataProps & AutoLogoutActionProps;
42 const debounce = (delay: number | undefined, fn: Function) => {
43 let timerId: NodeJS.Timer | null;
44 return (...args: any[]) => {
45 if (timerId) { clearTimeout(timerId); }
46 timerId = setTimeout(() => {
53 export const LAST_ACTIVE_TIMESTAMP = 'lastActiveTimestamp';
55 export const AutoLogoutComponent = (props: AutoLogoutProps) => {
56 let logoutTimer: NodeJS.Timer;
57 const lastWarningDuration = min([props.lastWarningDuration, props.sessionIdleTimeout])! * 1000;
59 // Runs once after render
60 React.useEffect(() => {
61 window.addEventListener('storage', handleStorageEvents);
64 window.removeEventListener('storage', handleStorageEvents);
68 const handleStorageEvents = (e: StorageEvent) => {
69 if (e.key === LAST_ACTIVE_TIMESTAMP) {
70 // Other tab activity detected by a localStorage change event.
78 const handleOnIdle = () => {
79 logoutTimer = setTimeout(
80 () => props.doLogout(), lastWarningDuration);
82 "Your session is about to be closed due to inactivity",
86 const handleOnActive = () => {
87 if (logoutTimer) { clearTimeout(logoutTimer); }
91 const handleOnAction = () => {
92 // Notify the other tabs there was some activity.
93 const now = (new Date()).getTime();
94 localStorage.setItem(LAST_ACTIVE_TIMESTAMP, now.toString());
97 const { reset } = useIdleTimer({
98 timeout: (props.lastWarningDuration < props.sessionIdleTimeout)
99 ? 1000 * (props.sessionIdleTimeout - props.lastWarningDuration)
101 onIdle: handleOnIdle,
102 onActive: handleOnActive,
103 onAction: handleOnAction,
110 export const AutoLogout = connect(mapStateToProps, mapDispatchToProps)(AutoLogoutComponent);