Merge branch '20085-Sharing-Dialog-Form-Validation-Error-fix' into main
[arvados.git] / src / views-components / auto-logout / auto-logout.tsx
index 52a1950ae087b885b8201ed56268592e190c7483..b4bef2b5efdb51b40d73542f51aad87b86969a16 100644 (file)
@@ -6,11 +6,11 @@ import { connect } from "react-redux";
 import { useIdleTimer } from "react-idle-timer";
 import { Dispatch } from "redux";
 
-import { RootState } from "~/store/store";
-import { SnackbarKind, snackbarActions } from "~/store/snackbar/snackbar-actions";
-import { logout } from "~/store/auth/auth-action";
+import { RootState } from "store/store";
+import { SnackbarKind, snackbarActions } from "store/snackbar/snackbar-actions";
+import { logout } from "store/auth/auth-action";
 import parse from "parse-duration";
-import * as React from "react";
+import React from "react";
 import { min } from "lodash";
 
 interface AutoLogoutDataProps {
@@ -24,49 +24,87 @@ interface AutoLogoutActionProps {
     doCloseWarn: () => void;
 }
 
-const mapStateToProps = (state: RootState, ownProps: any): AutoLogoutDataProps => {
-    return {
-        sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0,
-        lastWarningDuration: ownProps.lastWarningDuration || 60,
-    };
-};
+const mapStateToProps = (state: RootState, ownProps: any): AutoLogoutDataProps => ({
+    sessionIdleTimeout: parse(state.auth.config.clusterConfig.Workbench.IdleTimeout, 's') || 0,
+    lastWarningDuration: ownProps.lastWarningDuration || 60,
+});
 
 const mapDispatchToProps = (dispatch: Dispatch): AutoLogoutActionProps => ({
-    doLogout: () => dispatch<any>(logout(true)),
+    doLogout: () => dispatch<any>(logout(true, true)),
     doWarn: (message: string, duration: number) =>
         dispatch(snackbarActions.OPEN_SNACKBAR({
             message, hideDuration: duration, kind: SnackbarKind.WARNING })),
     doCloseWarn: () => dispatch(snackbarActions.CLOSE_SNACKBAR()),
 });
 
-type AutoLogoutProps = AutoLogoutDataProps & AutoLogoutActionProps;
+export type AutoLogoutProps = AutoLogoutDataProps & AutoLogoutActionProps;
 
-export const AutoLogout = connect(mapStateToProps, mapDispatchToProps)(
-    (props: AutoLogoutProps) => {
-        let logoutTimer: NodeJS.Timer;
-        const lastWarningDuration = min([props.lastWarningDuration, props.sessionIdleTimeout])! * 1000 ;
+const debounce = (delay: number | undefined, fn: Function) => {
+    let timerId: NodeJS.Timer | null;
+    return (...args: any[]) => {
+        if (timerId) { clearTimeout(timerId); }
+        timerId = setTimeout(() => {
+            fn(...args);
+            timerId = null;
+        }, delay);
+    };
+};
 
-        const handleOnIdle = () => {
-            logoutTimer = setTimeout(
-                () => props.doLogout(), lastWarningDuration);
-            props.doWarn(
-                "Your session is about to be closed due to inactivity",
-                lastWarningDuration);
-        };
+export const LAST_ACTIVE_TIMESTAMP = 'lastActiveTimestamp';
+
+export const AutoLogoutComponent = (props: AutoLogoutProps) => {
+    let logoutTimer: NodeJS.Timer;
+    const lastWarningDuration = min([props.lastWarningDuration, props.sessionIdleTimeout])! * 1000;
 
-        const handleOnActive = () => {
-            clearTimeout(logoutTimer);
-            props.doCloseWarn();
+    // Runs once after render
+    React.useEffect(() => {
+        window.addEventListener('storage', handleStorageEvents);
+        // Component cleanup
+        return () => {
+            window.removeEventListener('storage', handleStorageEvents);
         };
+    });
+
+    const handleStorageEvents = (e: StorageEvent) => {
+        if (e.key === LAST_ACTIVE_TIMESTAMP) {
+            // Other tab activity detected by a localStorage change event.
+            debounce(500, () => {
+                handleOnActive();
+                reset();
+            })();
+        }
+    };
 
-        useIdleTimer({
-            timeout: (props.lastWarningDuration < props.sessionIdleTimeout)
-                ? 1000 * (props.sessionIdleTimeout - props.lastWarningDuration)
-                : 1,
-            onIdle: handleOnIdle,
-            onActive: handleOnActive,
-            debounce: 500
-        });
+    const handleOnIdle = () => {
+        logoutTimer = setTimeout(
+            () => props.doLogout(), lastWarningDuration);
+        props.doWarn(
+            "Your session is about to be closed due to inactivity",
+            lastWarningDuration);
+    };
 
-        return <span />;
+    const handleOnActive = () => {
+        if (logoutTimer) { clearTimeout(logoutTimer); }
+        props.doCloseWarn();
+    };
+
+    const handleOnAction = () => {
+        // Notify the other tabs there was some activity.
+        const now = (new Date()).getTime();
+        localStorage.setItem(LAST_ACTIVE_TIMESTAMP, now.toString());
+    };
+
+    const { reset } = useIdleTimer({
+        timeout: (props.lastWarningDuration < props.sessionIdleTimeout)
+            ? 1000 * (props.sessionIdleTimeout - props.lastWarningDuration)
+            : 1,
+        onIdle: handleOnIdle,
+        onActive: handleOnActive,
+        onAction: handleOnAction,
+        debounce: 500
     });
+
+    return <span />;
+};
+
+export const AutoLogout = connect(mapStateToProps, mapDispatchToProps)(AutoLogoutComponent);