Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates timeout handling to match requirements #333

Merged
merged 4 commits into from
Jul 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 48 additions & 45 deletions components/TimeoutModal.tsx
Original file line number Diff line number Diff line change
@@ -1,80 +1,83 @@
/**
* Component to match EDD's session behavior
*
* In order to match EDD's session expiration policy, we redirect users back to EDD
* login page after REDIRECT_TIMER minutes. We also show this timeout modal
* WARNING_DURATION minutes prior to the redirect. X'ing out the modal or clicking
* outside it lets the user stay on the page until the redirect.
*/

import { useState, useEffect } from 'react'
import { useTranslation } from 'next-i18next'
import Modal from 'react-bootstrap/Modal'

import { Button } from './Button'
import getUrl from '../utils/getUrl'

let timeOutTimerId: NodeJS.Timeout | null = null
let warningTimerId: NodeJS.Timeout | null = null

export interface TimeoutModalProps {
lomky marked this conversation as resolved.
Show resolved Hide resolved
action: string
userArrivedFromUioMobile: boolean
timedOut: boolean
}

export const TimeoutModal: React.FC<TimeoutModalProps> = (props) => {
export const TimeoutModal: React.FC<TimeoutModalProps> = ({ timedOut, userArrivedFromUioMobile }) => {
const { t } = useTranslation()
const { action, timedOut } = props
const TIMEOUT_MS = 30 * 60 * 1000
const TIMEOUT_DISPLAY_TIME_IN_MINUTES = 5
const TIMEOUT_WARNING_MS = TIMEOUT_MS - TIMEOUT_DISPLAY_TIME_IN_MINUTES * 60 * 1000
const [numberOfMinutes, setNumberOfMinutes] = useState(TIMEOUT_DISPLAY_TIME_IN_MINUTES)
const [showWarningModal, setShowWarningModal] = useState<boolean | null>(timedOut)

useEffect(() => {
if (showWarningModal) {
const timer = setTimeout(() => {
setNumberOfMinutes(numberOfMinutes - 1)
}, 60 * 1000)
return () => clearTimeout(timer)
}
Comment on lines -25 to -31
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was just here twice. Oops 😅

})
// handy converter for Minutes -> Milliseconds
const ONE_MINUTE_MS = 60 * 1000

// keep times in human readable minutes
const REDIRECT_TIMER = 30
const WARNING_DURATION = 5
const WARNING_TIMER = REDIRECT_TIMER - WARNING_DURATION
Comment on lines +27 to +33
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Converted these to human readable minutes, converting to milliseconds just in time


const [numberOfMinutes, setNumberOfMinutes] = useState(WARNING_DURATION)
const [showWarningModal, setShowWarningModal] = useState<boolean | null>(timedOut)
const [warned, setWarned] = useState<boolean | null>(timedOut)

useEffect(() => {
if (showWarningModal) {
const timer = setTimeout(() => {
setNumberOfMinutes(numberOfMinutes - 1)
}, 60 * 1000)
}, ONE_MINUTE_MS)
return () => clearTimeout(timer)
}
})

function clear() {
if (timeOutTimerId) {
clearTimeout(timeOutTimerId)
timeOutTimerId = null
}
if (warningTimerId) {
clearTimeout(warningTimerId)
warningTimerId = null
}
}

function startOrUpdate() {
Copy link
Contributor Author

@lomky lomky Jul 23, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The big change in this PR is this is no longer a loop that resets, but instead we only run once. So no longer any need to clear and reset our base state, and instead we need to just track if we're before warning, warning, or done warning.

showWarningModal tells us whether we're actively warning or not, and warned tells us whether we've warned or not.

clear()
function startTimers() {
// Show the warning modal for a bit before navigating
warningTimerId = setTimeout(() => {
setShowWarningModal(true)
setNumberOfMinutes(TIMEOUT_DISPLAY_TIME_IN_MINUTES)
}, TIMEOUT_WARNING_MS)
timeOutTimerId = setTimeout(() => {
setWarned(true)
setNumberOfMinutes(WARNING_DURATION)
}, WARNING_TIMER * ONE_MINUTE_MS)
// And at the end, send back to EDD
setTimeout(() => {
if (typeof window !== 'undefined') {
const eddLocation = getUrl('edd-log-in')?.concat(encodeURIComponent(window.location.toString()))
window.location.href = eddLocation || ''
const eddLoginLink = getUrl('edd-log-in')?.concat(encodeURIComponent(window.location.toString()))
window.location.href = eddLoginLink || ''
}
}, TIMEOUT_MS)
}, REDIRECT_TIMER * ONE_MINUTE_MS)
}

function closeWarningModal() {
setShowWarningModal(false)
startOrUpdate()

if (warningTimerId) {
clearTimeout(warningTimerId)
warningTimerId = null
}
Comment on lines +67 to +70
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clear our all timers related to the going-to-warn.

}

function redirectToUIHome() {
const uioHomeLink = userArrivedFromUioMobile ? getUrl('uio-home-url-mobile') : getUrl('uio-home-url-desktop')
window.location.href = uioHomeLink || ''
Comment on lines +73 to +75
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another change: instead of the button dismissing the modal, it lets the user head back to UI Home to keep their session active. This is only if they hit the button though - X'ing out the modal or clicking outside it lets them stay

}

// If the modal is showing, we don't want to restart the timer.
if (action === 'startOrUpdate' && !showWarningModal) {
startOrUpdate()
} else if (action === 'clear') {
clear()
// If the warning modal hasn't shown, kickoff!
if (!warned) {
startTimers()
}

return (
Expand All @@ -84,9 +87,9 @@ export const TimeoutModal: React.FC<TimeoutModalProps> = (props) => {
<strong>{t('timeout-modal.header')}</strong>
</Modal.Title>
</Modal.Header>
<Modal.Body>{t('timeout-modal.warning', { numberOfMinutes })}</Modal.Body>
<Modal.Body>{t('timeout-modal.warning', { count: numberOfMinutes })}</Modal.Body>
<Modal.Footer className="border-0">
<Button onClick={closeWarningModal} label={t('timeout-modal.button')} />
<Button onClick={redirectToUIHome} label={t('timeout-modal.button')} />
</Modal.Footer>
</Modal>
)
Expand Down
2 changes: 1 addition & 1 deletion pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export default function Home({
</Head>
<Header userArrivedFromUioMobile={userArrivedFromUioMobile} />
{mainComponent}
<TimeoutModal action="startOrUpdate" timedOut={timedOut} />
<TimeoutModal userArrivedFromUioMobile={userArrivedFromUioMobile} timedOut={timedOut} />
<Footer />
{console.dir({ scenarioContent })} {/* @TODO: Remove. For development purposes only. */}
</Container>
Expand Down
5 changes: 3 additions & 2 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@
},
"timeout-modal": {
"header": "Your Session Will End Soon",
"warning": "To protect your information, you will be logged out in {{numberOfMinutes}} minutes if you do not continue.",
"button": "Stay Logged In"
"warning": "To protect your information, you will be logged out in {{count}} minute if you do not continue",
"warning_plural": "To protect your information, you will be logged out in {{count}} minutes if you do not continue.",
"button": "Return to UI Home"
},
"urls": {
"edd": {
Expand Down
5 changes: 3 additions & 2 deletions public/locales/es/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,8 +36,9 @@
},
"timeout-modal": {
"header": "Tu sesión terminará pronto",
"warning": "Para proteger su información, se cerrará la sesión en {{numberOfMinutes}} minutos si no continúa.",
"button": "Sigue Trabajando"
"warning": "Para proteger su información, se cerrará la sesión en {{count}} minuto si no continúa",
lomky marked this conversation as resolved.
Show resolved Hide resolved
"warning_plural": "Para proteger su información, se cerrará la sesión en {{count}} minutos si no continúa.",
"button": "Volver a UI Casa"
},
"urls": {
"edd": {
Expand Down