Skip to content
Closed
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
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "global-shell-app",
"version": "1.9.1",
"version": "1.11.0",
"description": "",
"license": "BSD-3-Clause",
"private": true,
Expand Down Expand Up @@ -51,9 +51,13 @@
"typescript": "^5.7.3"
},
"dependencies": {
"@dhis2/app-runtime": "^3.14.0",
"@dhis2/app-runtime": "^3.16.0",
"@dhis2/pwa": "^12.3.0",
"@dhis2/ui": "^10.1.13",
"@types/js-cookie": "^3.0.6",
"@types/post-robot": "^10.0.6",
"js-cookie": "^3.0.5",
"post-robot": "^10.0.46",
"react-router": "^7.2.0",
"styled-jsx": "^4.0.1"
},
Expand Down
5 changes: 5 additions & 0 deletions src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import styles from './App.module.css'
import { ConnectedHeaderBar } from './components/ConnectedHeaderbar.jsx'
import { PluginLoader } from './components/PluginLoader.jsx'
import { RedirectHandler } from './components/RedirectHandler.tsx'
import { SessionHandler } from './components/session-handler/index.ts'
import { ClientPWAProvider } from './lib/clientPWAUpdateState.jsx'

const APPS_INFO_QUERY = {
Expand All @@ -27,8 +28,12 @@ const APPS_INFO_QUERY = {
}

const Layout = ({ appsInfoQuery }) => {
const config = useConfig()
const sessionTimeout = config?.systemInfo?.sessionTimeout

return (
<div className={styles.container}>
<SessionHandler sessionTimeout={Number(sessionTimeout)} />
<ConnectedHeaderBar appsInfoQuery={appsInfoQuery} />
{/* Skip the routes in dev; they don't make the same sense */}
{process.env.NODE_ENV !== 'development' ? <Outlet /> : null}
Expand Down
1 change: 1 addition & 0 deletions src/components/session-handler/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { SessionHandler } from './session-handler'
28 changes: 28 additions & 0 deletions src/components/session-handler/modal-countdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import i18n from '@dhis2/d2-i18n'
import {
Button,
ButtonStrip,
Modal,
ModalActions,
ModalContent,
ModalTitle,
} from '@dhis2/ui'
import * as React from 'react'

export const ExpirationCountdownModal = ({ countDown }) => {
return (
<Modal>
<ModalTitle>{i18n.t('Your session is about to expire')}</ModalTitle>
<ModalContent>
Your session will expire in {countDown} seconds.
</ModalContent>
<ModalActions>
<ButtonStrip end>
{/* can this get into undesired state when offline - i.e. can't extend but want to dismiss */}
<Button primary>Extend session</Button>
{/* <Button>Dismiss</Button> */}
</ButtonStrip>
</ModalActions>
</Modal>
)
}
47 changes: 47 additions & 0 deletions src/components/session-handler/modal-expired.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useConfig } from '@dhis2/app-runtime'
import i18n from '@dhis2/d2-i18n'
import {
Button,
ButtonStrip,
Modal,
ModalActions,
ModalContent,
ModalTitle,
} from '@dhis2/ui'
import * as React from 'react'

export const ExpiredModal = ({ dismissModal }) => {
const { baseUrl } = useConfig()

const goToLogin = () => {
window.open(baseUrl)
}
const dismiss = () => {
dismissModal()
}
// console.log(config)
return (
<Modal>
<ModalTitle>{i18n.t('Your session has expired')}</ModalTitle>
<ModalContent>
{/* {i18n.t('Your session has expired.')}{' '} */}
{/* {i18n.t(
'You can go to the login page or dismiss the modal if the app supports working offline.'
)} */}
<br />
Eos omnis cumque quia quaerat aut. Neque consequuntur sed non a
quibusdam eligendi. Fugit eveniet expedita nihil ab maxime sequi
nihil quidem. Et aut nobis assumenda in iure.
</ModalContent>
<ModalActions>
<ButtonStrip end>
<Button onClick={dismiss}>Dismiss</Button>

<Button primary onClick={goToLogin}>
Go to login
</Button>
</ButtonStrip>
</ModalActions>
</Modal>
)
}
53 changes: 53 additions & 0 deletions src/components/session-handler/session-handler.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import postRobot from 'post-robot'
import * as React from 'react'
import { ExpirationCountdownModal } from './modal-countdown'
import { ExpiredModal } from './modal-expired'
import { useCheckCookie } from './use-check-cookie'

const globalShellBroadcast = new BroadcastChannel('global-shell')
const broadCastMessage = 'BROADCAST_SESSION_EXPIRED'

// ToDO: there is some re-rendering hell going on with the component - still needs some tightening up
export const SessionHandler = ({ sessionTimeout }) => {
const { showWarning, time, expired, reset } = useCheckCookie({
sessionTimeout,
warningThreshold: Number(sessionTimeout) / 10,
})
const [modalHidden, hideModal] = React.useState(false)
const [received401, setReceived401] = React.useState(false)

globalShellBroadcast.addEventListener('message', (ev) => {
if (ev.data === broadCastMessage) {
console.log(
`[Session] Received broadcast message from another window: "${ev.data}" on channel "${globalShellBroadcast.name}"`
)
hideModal(false)
setReceived401(true)
}
})

React.useEffect(() => {
postRobot.on('notifyShell', (event) => {
console.log(
` [Session] [API monitor]: ${event?.data?.resource} (${event?.data.status})`
)
// todo: check API call are to DHIS2 server specifically?
if (event?.data?.status == 401) {
hideModal(false)
setReceived401(true)
// todo: should broadcast 2xx to extend session across windows
globalShellBroadcast.postMessage(broadCastMessage)
} else {
reset()
}
})
}, [])

if (!expired && showWarning) {
return <ExpirationCountdownModal countDown={time} />
}
if ((expired || received401) && !modalHidden) {
return <ExpiredModal dismissModal={() => hideModal(true)} />
}
return null
}
42 changes: 42 additions & 0 deletions src/components/session-handler/use-check-cookie.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { useEffect, useRef, useState } from 'react'

// const WARNING_THRESHOLD_IN_SECONDS = 20
const CHECK_INTERVAL = 1000 // ms countdown every second
export const useCheckCookie = ({ sessionTimeout, warningThreshold }) => {
const timeout = useRef(sessionTimeout)

// console.log('??? warningthreshold', warningThresholdSeconds)
const stime = useRef<number>(timeout.current)
const [time, setTime] = useState(timeout.current)

const [expired, setExpired] = useState(false)
const [warningShown, setShowWarning] = useState(false)

const reset = () => {
stime.current = Math.round(sessionTimeout)
setExpired(false)
setShowWarning(false)
setTime(stime.current)
}
useEffect(() => {
const interval = setInterval(() => {
console.log(`[Session] Remaining seconds: ${stime.current}`)
setShowWarning(stime.current < warningThreshold)
if (stime.current === 1) {
setExpired(true)
clearInterval(interval)
}
stime.current = stime.current - 1
setTime(stime.current)
}, CHECK_INTERVAL)

return () => clearInterval(interval)
}, [])

return {
reset,
time,
expired,
showWarning: warningShown,
}
}
97 changes: 64 additions & 33 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1845,47 +1845,63 @@
"@dhis2/pwa" "12.3.0"
moment "^2.24.0"

"@dhis2/app-runtime@^3.12.0", "@dhis2/app-runtime@^3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.14.0.tgz#85a6f337036ba31868fd7193360208ef174d4345"
integrity sha512-0qmP2QxhoK8dWCAOOomD+JDer44AC5rxOHuK5qINNt1K4BMZgG5axmkvk6/86SXTRtLj2+Iio7BLe9DBnh+Bxg==
dependencies:
"@dhis2/app-service-alerts" "3.14.0"
"@dhis2/app-service-config" "3.14.0"
"@dhis2/app-service-data" "3.14.0"
"@dhis2/app-service-offline" "3.14.0"
"@dhis2/app-service-plugin" "3.14.0"

"@dhis2/app-service-alerts@3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.14.0.tgz#93eab29b3e42115e9de509bec7daa172cc50a0f4"
integrity sha512-VhXc0w5fX+2Rd4J7MZmklnl7Fq7PLnCrSYDSWRZPYXbNo428QjVIiN/1g003gCatVPon3bl19LisZbn5vmNoRg==

"@dhis2/app-service-config@3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.14.0.tgz#823ce5e96c6deb8beec92bd65180ca611f0551a0"
integrity sha512-v/gnPMG7IZ6pEutYnHJff4S93w1DAQbeDiGtRqMffEiNxa9Bx5wc5MPTGMyeUGeEw8on3qaeP/2rQa+PGaQvcg==

"@dhis2/app-service-data@3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.14.0.tgz#7109852154af0b02240e077df9ae5e65a727a593"
integrity sha512-7DQVlVSCdJauduw+9LXrb13IsNagGzPNN2Iy9e2jVeE+eNqP6j0PeI6enHTIjaYA6OOSx/Ob7/HXkBVH8wyLlg==
"@dhis2/app-runtime@^3.12.0", "@dhis2/app-runtime@^3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-runtime/-/app-runtime-3.16.0.tgz#50bf19b1d8f648940d5c5022cc4cd2f9a23cd5b5"
integrity sha512-4PbXIpjoclQwVPkZrZwKUUPqN4UWbTE6nuC4h70q2fY6rHEE+QPSBIEDno+rxpQIMy+HtZm0hrDEUK387EMKjA==
dependencies:
"@dhis2/app-service-alerts" "3.16.0"
"@dhis2/app-service-config" "3.16.0"
"@dhis2/app-service-data" "3.16.0"
"@dhis2/app-service-offline" "3.16.0"
"@dhis2/app-service-plugin" "3.16.0"
"@dhis2/app-service-user" "3.16.0"
prop-types "^15.7.2"

"@dhis2/app-service-alerts@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-alerts/-/app-service-alerts-3.16.0.tgz#373d9ac1aee462cc2f12a9fa7ccfe2d4c986022b"
integrity sha512-aTC47gbYebuf0yJfSX7POpmaC+ejG7DCBmAm+qBL3NkBqlsmNI3A0sO9EjztEvkuxnLA/WRuKhu1an+0jsmpTw==
dependencies:
prop-types "^15.7.2"

"@dhis2/app-service-config@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-config/-/app-service-config-3.16.0.tgz#5b2bd81f638dd4f02e811679d60b56bbd1147d7a"
integrity sha512-kU9aavV8GBjRP8NU/ZsmOuQSSs8p5kcX3bTaCvlw7dgRA4eHscdG24JaHYqAfPFQI0ntPT88Qf8m86Obz375cA==
dependencies:
prop-types "^15.7.2"

"@dhis2/app-service-data@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-data/-/app-service-data-3.16.0.tgz#8f47a9de9004288f0c21c34d22a4cabf93313671"
integrity sha512-RFF4wg2m/6dIEl1KmiaX2bw3TPMgB6R9gu27HyOCFNYT808wtFkvyrpa8TAWjF4m+y+yLZyKtHO0yEXp7otfHQ==
dependencies:
"@tanstack/react-query" "^4.36.1"
prop-types "^15.7.2"

"@dhis2/app-service-offline@3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.14.0.tgz#9164f4ef1ffef18d52a77f36bfd3683b5ccbdb81"
integrity sha512-sgp7AtaPIvNt5TeXCrVR2/ndtgx9p+TstQWiBzRq75R894P34nqBX9jWMVUoNDZU35OfcGqtxHfOrpoLrdZ0XA==
"@dhis2/app-service-offline@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-offline/-/app-service-offline-3.16.0.tgz#73848e8d3e29c7eca0b29b6d2504e65be93463aa"
integrity sha512-I67msCV3miwh4La4Dpb0HI2dab5Hs20mNmJGPbaN7WLizxbgVMvyrqtTXBGOT7iZulTgc0fB4zUSAaeE3r7pvA==
dependencies:
lodash "^4.17.21"
prop-types "^15.7.2"

"@dhis2/app-service-plugin@3.14.0":
version "3.14.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-plugin/-/app-service-plugin-3.14.0.tgz#ea6ed4afc805072352e861e5412223b056e2edcf"
integrity sha512-3D9iPG6/HFT1dY7G6Wwwunv2bQ3hMzW2gsw8rFAcOphFHMujzQYUYuaZQmxg7rs/Hh/DPzLVOzsNAqOi1cS7wA==
"@dhis2/app-service-plugin@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-plugin/-/app-service-plugin-3.16.0.tgz#eb191e39eb0f183936073b998505be3035fdc29c"
integrity sha512-pjjlHLEdLFFr+fuDKjBBjeYM8nyZuBOl1Pi/jCzsAq1HdmxkPI4hIhsCbRkoRWpIs7s9EW0ONI/vf2bkxnct1Q==
dependencies:
post-robot "^10.0.46"
prop-types "^15.7.2"

"@dhis2/app-service-user@3.16.0":
version "3.16.0"
resolved "https://registry.yarnpkg.com/@dhis2/app-service-user/-/app-service-user-3.16.0.tgz#9033187955ad9b2f62793da7134d41c707595a7d"
integrity sha512-3bFeiy4NDtwvzo+mF9q70B6GslNKf0lbr9Ujb6PXsp9IqJpahzqFvzMDruTjP/eAdOCNE6o/UL8ZHgSux7aVTg==
dependencies:
prop-types "^15.7.2"

"@dhis2/app-shell@12.3.0":
version "12.3.0"
Expand Down Expand Up @@ -3030,6 +3046,11 @@
dependencies:
"@types/istanbul-lib-report" "*"

"@types/js-cookie@^3.0.6":
version "3.0.6"
resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-3.0.6.tgz#a04ca19e877687bd449f5ad37d33b104b71fdf95"
integrity sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==

"@types/json-schema@*", "@types/json-schema@^7.0.4", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9":
version "7.0.15"
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
Expand Down Expand Up @@ -3079,6 +3100,11 @@
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239"
integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==

"@types/post-robot@^10.0.6":
version "10.0.6"
resolved "https://registry.yarnpkg.com/@types/post-robot/-/post-robot-10.0.6.tgz#9c8b1db55dd109cc36aa5a0d8edbd46827fd572c"
integrity sha512-x+LaZPQ4TYW8aBbKpFZsJn7TGipka7tC2ZokE8gpCAlh4E4XFgxxcvt19LD41BeExmlzAtjHh700r3r8SdfX9A==

"@types/prettier@^2.1.5":
version "2.7.3"
resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
Expand Down Expand Up @@ -7743,6 +7769,11 @@ jiti@^1.20.0:
resolved "https://registry.yarnpkg.com/jiti/-/jiti-1.21.0.tgz#7c97f8fe045724e136a397f7340475244156105d"
integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==

js-cookie@^3.0.5:
version "3.0.5"
resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc"
integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==

"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
Expand Down
Loading