@strapi/admin 5.53.0 → 5.55.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin/admin/src/App.js +1 -1
- package/dist/admin/admin/src/App.js.map +1 -1
- package/dist/admin/admin/src/App.mjs +1 -1
- package/dist/admin/admin/src/App.mjs.map +1 -1
- package/dist/admin/admin/src/components/DismissibleBanner.js +60 -0
- package/dist/admin/admin/src/components/DismissibleBanner.js.map +1 -0
- package/dist/admin/admin/src/components/DismissibleBanner.mjs +58 -0
- package/dist/admin/admin/src/components/DismissibleBanner.mjs.map +1 -0
- package/dist/admin/admin/src/components/Form.js +11 -2
- package/dist/admin/admin/src/components/Form.js.map +1 -1
- package/dist/admin/admin/src/components/Form.mjs +11 -2
- package/dist/admin/admin/src/components/Form.mjs.map +1 -1
- package/dist/admin/admin/src/components/FormInputs/DateTime.js +1 -1
- package/dist/admin/admin/src/components/FormInputs/DateTime.js.map +1 -1
- package/dist/admin/admin/src/components/FormInputs/DateTime.mjs +1 -1
- package/dist/admin/admin/src/components/FormInputs/DateTime.mjs.map +1 -1
- package/dist/admin/admin/src/components/FormInputs/Time.js +1 -1
- package/dist/admin/admin/src/components/FormInputs/Time.js.map +1 -1
- package/dist/admin/admin/src/components/FormInputs/Time.mjs +1 -1
- package/dist/admin/admin/src/components/FormInputs/Time.mjs.map +1 -1
- package/dist/admin/admin/src/components/LanguageProvider.js +35 -1
- package/dist/admin/admin/src/components/LanguageProvider.js.map +1 -1
- package/dist/admin/admin/src/components/LanguageProvider.mjs +17 -2
- package/dist/admin/admin/src/components/LanguageProvider.mjs.map +1 -1
- package/dist/admin/admin/src/components/MainNav/NavUser.js +1 -2
- package/dist/admin/admin/src/components/MainNav/NavUser.js.map +1 -1
- package/dist/admin/admin/src/components/MainNav/NavUser.mjs +1 -2
- package/dist/admin/admin/src/components/MainNav/NavUser.mjs.map +1 -1
- package/dist/admin/admin/src/components/MediaLibraryBanner.js +85 -0
- package/dist/admin/admin/src/components/MediaLibraryBanner.js.map +1 -0
- package/dist/admin/admin/src/components/MediaLibraryBanner.mjs +83 -0
- package/dist/admin/admin/src/components/MediaLibraryBanner.mjs.map +1 -0
- package/dist/admin/admin/src/components/Providers.js +6 -6
- package/dist/admin/admin/src/components/Providers.js.map +1 -1
- package/dist/admin/admin/src/components/Providers.mjs +6 -6
- package/dist/admin/admin/src/components/Providers.mjs.map +1 -1
- package/dist/admin/admin/src/components/SubNav.js +113 -35
- package/dist/admin/admin/src/components/SubNav.js.map +1 -1
- package/dist/admin/admin/src/components/SubNav.mjs +115 -37
- package/dist/admin/admin/src/components/SubNav.mjs.map +1 -1
- package/dist/admin/admin/src/components/SubNavFolder.js +188 -0
- package/dist/admin/admin/src/components/SubNavFolder.js.map +1 -0
- package/dist/admin/admin/src/components/SubNavFolder.mjs +186 -0
- package/dist/admin/admin/src/components/SubNavFolder.mjs.map +1 -0
- package/dist/admin/admin/src/components/UpsellBanner.js +56 -86
- package/dist/admin/admin/src/components/UpsellBanner.js.map +1 -1
- package/dist/admin/admin/src/components/UpsellBanner.mjs +58 -88
- package/dist/admin/admin/src/components/UpsellBanner.mjs.map +1 -1
- package/dist/admin/admin/src/core/store/configure.js +14 -2
- package/dist/admin/admin/src/core/store/configure.js.map +1 -1
- package/dist/admin/admin/src/core/store/configure.mjs +14 -2
- package/dist/admin/admin/src/core/store/configure.mjs.map +1 -1
- package/dist/admin/admin/src/core/store/hooks.mjs +1 -1
- package/dist/admin/admin/src/features/Auth.js +162 -46
- package/dist/admin/admin/src/features/Auth.js.map +1 -1
- package/dist/admin/admin/src/features/Auth.mjs +164 -48
- package/dist/admin/admin/src/features/Auth.mjs.map +1 -1
- package/dist/admin/admin/src/features/Tracking.js.map +1 -1
- package/dist/admin/admin/src/features/Tracking.mjs.map +1 -1
- package/dist/admin/admin/src/hooks/useRegisterUnsavedChanges.js +19 -0
- package/dist/admin/admin/src/hooks/useRegisterUnsavedChanges.js.map +1 -0
- package/dist/admin/admin/src/hooks/useRegisterUnsavedChanges.mjs +17 -0
- package/dist/admin/admin/src/hooks/useRegisterUnsavedChanges.mjs.map +1 -0
- package/dist/admin/admin/src/hooks/{useIdleSessionLogout.js → useSessionKeepalive.js} +34 -27
- package/dist/admin/admin/src/hooks/useSessionKeepalive.js.map +1 -0
- package/dist/admin/admin/src/hooks/{useIdleSessionLogout.mjs → useSessionKeepalive.mjs} +34 -27
- package/dist/admin/admin/src/hooks/useSessionKeepalive.mjs.map +1 -0
- package/dist/admin/admin/src/layouts/AuthenticatedLayout.js +2 -0
- package/dist/admin/admin/src/layouts/AuthenticatedLayout.js.map +1 -1
- package/dist/admin/admin/src/layouts/AuthenticatedLayout.mjs +2 -0
- package/dist/admin/admin/src/layouts/AuthenticatedLayout.mjs.map +1 -1
- package/dist/admin/admin/src/pages/SessionsPage.js +2 -5
- package/dist/admin/admin/src/pages/SessionsPage.js.map +1 -1
- package/dist/admin/admin/src/pages/SessionsPage.mjs +2 -5
- package/dist/admin/admin/src/pages/SessionsPage.mjs.map +1 -1
- package/dist/admin/admin/src/render.js +6 -1
- package/dist/admin/admin/src/render.js.map +1 -1
- package/dist/admin/admin/src/render.mjs +6 -1
- package/dist/admin/admin/src/render.mjs.map +1 -1
- package/dist/admin/admin/src/translations/en.json.js +16 -0
- package/dist/admin/admin/src/translations/en.json.js.map +1 -1
- package/dist/admin/admin/src/translations/en.json.mjs +16 -0
- package/dist/admin/admin/src/translations/en.json.mjs.map +1 -1
- package/dist/admin/admin/src/utils/baseQuery.js +3 -13
- package/dist/admin/admin/src/utils/baseQuery.js.map +1 -1
- package/dist/admin/admin/src/utils/baseQuery.mjs +3 -13
- package/dist/admin/admin/src/utils/baseQuery.mjs.map +1 -1
- package/dist/admin/admin/src/utils/getFetchClient.js +34 -5
- package/dist/admin/admin/src/utils/getFetchClient.js.map +1 -1
- package/dist/admin/admin/src/utils/getFetchClient.mjs +34 -6
- package/dist/admin/admin/src/utils/getFetchClient.mjs.map +1 -1
- package/dist/admin/admin/src/utils/unsavedChangesRegistry.js +25 -0
- package/dist/admin/admin/src/utils/unsavedChangesRegistry.js.map +1 -0
- package/dist/admin/admin/src/utils/unsavedChangesRegistry.mjs +22 -0
- package/dist/admin/admin/src/utils/unsavedChangesRegistry.mjs.map +1 -0
- package/dist/admin/admin/tests/utils.js +14 -14
- package/dist/admin/admin/tests/utils.js.map +1 -1
- package/dist/admin/admin/tests/utils.mjs +14 -14
- package/dist/admin/admin/tests/utils.mjs.map +1 -1
- package/dist/admin/ee/admin/src/hooks/useAIAvailability.js +1 -6
- package/dist/admin/ee/admin/src/hooks/useAIAvailability.js.map +1 -1
- package/dist/admin/ee/admin/src/hooks/useAIAvailability.mjs +1 -6
- package/dist/admin/ee/admin/src/hooks/useAIAvailability.mjs.map +1 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/ApplicationInfoPage/components/AIUsage.js +7 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/ApplicationInfoPage/components/AIUsage.js.map +1 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/ApplicationInfoPage/components/AIUsage.mjs +7 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/ApplicationInfoPage/components/AIUsage.mjs.map +1 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.js +9 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.js.map +1 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.mjs +9 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.mjs.map +1 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/SingleSignOnPage.js +0 -4
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/SingleSignOnPage.js.map +1 -1
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/SingleSignOnPage.mjs +0 -4
- package/dist/admin/ee/admin/src/pages/SettingsPage/pages/SingleSignOnPage.mjs.map +1 -1
- package/dist/admin/index.js +1 -0
- package/dist/admin/index.js.map +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/components/DismissibleBanner.d.ts +8 -0
- package/dist/admin/src/components/MediaLibraryBanner.d.ts +2 -0
- package/dist/admin/src/components/SubNav.d.ts +27 -8
- package/dist/admin/src/components/SubNavFolder.d.ts +15 -0
- package/dist/admin/src/features/Tracking.d.ts +2 -1
- package/dist/admin/src/hooks/useRegisterUnsavedChanges.d.ts +6 -0
- package/dist/admin/src/hooks/useSessionKeepalive.d.ts +88 -0
- package/dist/admin/src/services/admin.d.ts +1 -0
- package/dist/admin/src/utils/getFetchClient.d.ts +14 -6
- package/dist/admin/src/utils/unsavedChangesRegistry.d.ts +11 -0
- package/dist/ee/admin/src/pages/SettingsPage/pages/AuditLogs/utils/getActionTypesDefaultMessages.d.ts +8 -0
- package/dist/ee/server/src/controllers/admin.d.ts.map +1 -1
- package/dist/ee/server/src/index.d.ts.map +1 -1
- package/dist/server/ee/server/src/controllers/admin.js +1 -5
- package/dist/server/ee/server/src/controllers/admin.js.map +1 -1
- package/dist/server/ee/server/src/controllers/admin.mjs +1 -5
- package/dist/server/ee/server/src/controllers/admin.mjs.map +1 -1
- package/dist/server/ee/server/src/index.js +2 -0
- package/dist/server/ee/server/src/index.js.map +1 -1
- package/dist/server/ee/server/src/index.mjs +2 -0
- package/dist/server/ee/server/src/index.mjs.map +1 -1
- package/dist/server/server/src/ai/controllers/ai.js +3 -3
- package/dist/server/server/src/ai/controllers/ai.js.map +1 -1
- package/dist/server/server/src/ai/controllers/ai.mjs +3 -3
- package/dist/server/server/src/ai/controllers/ai.mjs.map +1 -1
- package/dist/server/server/src/ai/services/ai.js +37 -12
- package/dist/server/server/src/ai/services/ai.js.map +1 -1
- package/dist/server/server/src/ai/services/ai.mjs +37 -12
- package/dist/server/server/src/ai/services/ai.mjs.map +1 -1
- package/dist/server/server/src/audit-logs/tokens.js +106 -0
- package/dist/server/server/src/audit-logs/tokens.js.map +1 -0
- package/dist/server/server/src/audit-logs/tokens.mjs +99 -0
- package/dist/server/server/src/audit-logs/tokens.mjs.map +1 -0
- package/dist/server/server/src/controllers/admin.js +10 -1
- package/dist/server/server/src/controllers/admin.js.map +1 -1
- package/dist/server/server/src/controllers/admin.mjs +10 -1
- package/dist/server/server/src/controllers/admin.mjs.map +1 -1
- package/dist/server/server/src/services/api-token.js +106 -4
- package/dist/server/server/src/services/api-token.js.map +1 -1
- package/dist/server/server/src/services/api-token.mjs +107 -5
- package/dist/server/server/src/services/api-token.mjs.map +1 -1
- package/dist/server/server/src/services/transfer/token.js +56 -4
- package/dist/server/server/src/services/transfer/token.js.map +1 -1
- package/dist/server/server/src/services/transfer/token.mjs +57 -5
- package/dist/server/server/src/services/transfer/token.mjs.map +1 -1
- package/dist/server/src/ai/services/ai.d.ts +3 -1
- package/dist/server/src/ai/services/ai.d.ts.map +1 -1
- package/dist/server/src/audit-logs/tokens.d.ts +86 -0
- package/dist/server/src/audit-logs/tokens.d.ts.map +1 -0
- package/dist/server/src/controllers/admin.d.ts +1 -0
- package/dist/server/src/controllers/admin.d.ts.map +1 -1
- package/dist/server/src/controllers/index.d.ts +1 -0
- package/dist/server/src/controllers/index.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +1 -0
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/services/api-token.d.ts.map +1 -1
- package/dist/server/src/services/transfer/token.d.ts.map +1 -1
- package/dist/shared/contracts/admin.d.ts +1 -0
- package/dist/shared/contracts/admin.d.ts.map +1 -1
- package/dist/shared/contracts/shared.d.ts +1 -0
- package/dist/shared/contracts/shared.d.ts.map +1 -1
- package/package.json +7 -7
- package/dist/admin/admin/src/hooks/useIdleSessionLogout.js.map +0 -1
- package/dist/admin/admin/src/hooks/useIdleSessionLogout.mjs.map +0 -1
- package/dist/admin/src/hooks/useIdleSessionLogout.d.ts +0 -87
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UpsellBanner.mjs","sources":["../../../../../admin/src/components/UpsellBanner.tsx"],"sourcesContent":["import { useEffect } from 'react';\n\nimport { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';\nimport { Box, Flex, IconButton, LinkButton, Typography } from '@strapi/design-system';\nimport { ArrowsOut, Cross } from '@strapi/icons';\nimport { isAfter, isValid, subDays } from 'date-fns';\nimport { useIntl } from 'react-intl';\nimport { styled } from 'styled-components';\n\nimport { useGetLicenseTrialTimeLeftQuery } from '../../src/services/admin';\nimport { RESPONSIVE_DEFAULT_SPACING } from '../constants/theme';\nimport { useScopedPersistentState } from '../hooks/usePersistentState';\n\nconst BannerBackground = styled(Flex)`\n background: linear-gradient(\n 90deg,\n ${({ theme }) => theme.colors.primary600} 0%,\n ${({ theme }) => theme.colors.alternative600} 121.48%\n );\n position: relative;\n`;\n\nconst FixedButtonWrapper = styled(Box)`\n position: fixed;\n display: flex;\n flex-direction: column;\n z-index: 11;\n align-items: flex-end;\n top: 9px;\n right: 16px;\n`;\n\nconst Banner = ({\n isTrialEndedRecently,\n onDismiss,\n}: {\n isTrialEndedRecently: boolean;\n onDismiss: () => void;\n}) => {\n const { formatMessage } = useIntl();\n\n return (\n <>\n <BannerBackground width=\"100%\" justifyContent=\"center\">\n <Flex\n justifyContent=\"center\"\n alignItems=\"center\"\n width=\"100%\"\n paddingTop={2}\n paddingBottom={2}\n paddingLeft={RESPONSIVE_DEFAULT_SPACING}\n paddingRight={RESPONSIVE_DEFAULT_SPACING}\n gap={2}\n >\n <Box>\n <Typography\n variant=\"delta\"\n fontWeight=\"bold\"\n textColor=\"neutral0\"\n textAlign=\"center\"\n fontSize={2}\n >\n {formatMessage(\n isTrialEndedRecently\n ? {\n id: 'app.components.UpsellBanner.intro.ended',\n defaultMessage: 'Your trial has ended: ',\n }\n : {\n id: 'app.components.UpsellBanner.intro',\n defaultMessage: 'Access to Growth plan features: ',\n }\n )}\n </Typography>\n <Typography\n variant=\"delta\"\n textColor=\"neutral0\"\n textAlign=\"center\"\n paddingRight={4}\n fontSize={2}\n >\n {formatMessage(\n isTrialEndedRecently\n ? {\n id: 'app.components.UpsellBanner.text.ended',\n defaultMessage: 'Keep access to Growth features by upgrading now.',\n }\n : {\n id: 'app.components.UpsellBanner.text',\n defaultMessage:\n 'As part of your trial, you can explore premium tools such as Content History, Releases, and Single Sign-On (SSO).',\n }\n )}\n </Typography>\n </Box>\n <Box>\n <LinkButton\n width=\"max-content\"\n variant=\"tertiary\"\n href=\"https://billing.strapi.io\"\n target=\"_blank\"\n >\n {formatMessage(\n isTrialEndedRecently\n ? {\n id: 'app.components.UpsellBanner.button.ended',\n defaultMessage: 'Keep Growth plan',\n }\n : {\n id: 'app.components.UpsellBanner.button',\n defaultMessage: 'Upgrade now',\n }\n )}\n </LinkButton>\n </Box>\n </Flex>\n </BannerBackground>\n <FixedButtonWrapper>\n <IconButton\n withTooltip={false}\n label={formatMessage({\n id: 'app.components.UpsellBanner.close',\n defaultMessage: 'Close',\n })}\n onClick={onDismiss}\n >\n <Cross />\n </IconButton>\n </FixedButtonWrapper>\n </>\n );\n};\n\nconst UpsellBanner = () => {\n const { license } = useLicenseLimits();\n const { formatMessage } = useIntl();\n\n const [cachedTrialEndsAt, setCachedTrialEndsAt] = useScopedPersistentState<string | undefined>(\n 'STRAPI_FREE_TRIAL_ENDS_AT',\n undefined\n );\n\n const [dismissedFor, setDismissedFor] = useScopedPersistentState<string | undefined>(\n 'STRAPI_UPSELL_BANNER_DISMISSED_FOR',\n undefined\n );\n\n const sevenDaysAgo = subDays(new Date(), 7);\n\n const timeLeftData = useGetLicenseTrialTimeLeftQuery(undefined, {\n skip: !license?.isTrial,\n });\n\n useEffect(() => {\n if (timeLeftData.data?.trialEndsAt) {\n setCachedTrialEndsAt(timeLeftData.data.trialEndsAt);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [timeLeftData.data?.trialEndsAt]);\n\n // When the license is not a trial + not EE, and the cached trial end date is found in the localstorage, that means the trial has ended\n // We show the banner to encourage the user to upgrade (for 7 days after the trial ends)\n const isTrialEndedRecently = Boolean(\n !license?.isTrial &&\n !window.strapi.isEE &&\n cachedTrialEndsAt &&\n isAfter(new Date(cachedTrialEndsAt), sevenDaysAgo)\n );\n\n const trialEndsAt = timeLeftData.data?.trialEndsAt ?? cachedTrialEndsAt;\n\n const toCanonicalISO = (v: string | undefined): string | undefined => {\n if (!v) return undefined;\n const date = new Date(v);\n return isValid(date) ? date.toISOString() : undefined;\n };\n\n const isDismissed = Boolean(\n trialEndsAt && toCanonicalISO(dismissedFor) === toCanonicalISO(trialEndsAt)\n );\n\n const handleDismiss = () => setDismissedFor(toCanonicalISO(trialEndsAt));\n const handleReopen = () => setDismissedFor(undefined);\n\n if (!(timeLeftData.data?.trialEndsAt || isTrialEndedRecently)) {\n return null;\n }\n\n if (isDismissed) {\n return (\n <FixedButtonWrapper>\n <IconButton\n withTooltip={false}\n label={formatMessage({\n id: 'app.components.UpsellBanner.reopen',\n defaultMessage: 'Reopen banner',\n })}\n onClick={handleReopen}\n >\n <ArrowsOut />\n </IconButton>\n </FixedButtonWrapper>\n );\n }\n\n return <Banner isTrialEndedRecently={isTrialEndedRecently} onDismiss={handleDismiss} />;\n};\n\nexport { UpsellBanner };\n"],"names":["BannerBackground","styled","Flex","theme","colors","primary600","alternative600","FixedButtonWrapper","Box","Banner","isTrialEndedRecently","onDismiss","formatMessage","useIntl","_jsxs","_Fragment","_jsx","width","justifyContent","alignItems","paddingTop","paddingBottom","paddingLeft","RESPONSIVE_DEFAULT_SPACING","paddingRight","gap","Typography","variant","fontWeight","textColor","textAlign","fontSize","id","defaultMessage","LinkButton","href","target","IconButton","withTooltip","label","onClick","Cross","UpsellBanner","license","useLicenseLimits","cachedTrialEndsAt","setCachedTrialEndsAt","useScopedPersistentState","undefined","dismissedFor","setDismissedFor","sevenDaysAgo","subDays","Date","timeLeftData","useGetLicenseTrialTimeLeftQuery","skip","isTrial","useEffect","data","trialEndsAt","Boolean","window","strapi","isEE","isAfter","toCanonicalISO","v","date","isValid","toISOString","isDismissed","handleDismiss","handleReopen","ArrowsOut"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"UpsellBanner.mjs","sources":["../../../../../admin/src/components/UpsellBanner.tsx"],"sourcesContent":["import { useEffect } from 'react';\n\nimport { useLicenseLimits } from '@strapi/admin/strapi-admin/ee';\nimport { Box, IconButton, LinkButton, Typography } from '@strapi/design-system';\nimport { ArrowsOut } from '@strapi/icons';\nimport { isAfter, isValid, subDays } from 'date-fns';\nimport { useIntl } from 'react-intl';\nimport { styled } from 'styled-components';\n\nimport { useGetLicenseTrialTimeLeftQuery } from '../../src/services/admin';\nimport { useScopedPersistentState } from '../hooks/usePersistentState';\n\nimport { DismissibleBanner } from './DismissibleBanner';\n\n/**\n * Unlike `DismissibleBanner`'s own close button, this has no banner box left to\n * anchor to once dismissed (the banner unmounts entirely), so it stays pinned\n * to the viewport instead.\n */\nconst FixedButtonWrapper = styled(Box)`\n position: fixed;\n display: flex;\n flex-direction: column;\n z-index: 1;\n align-items: flex-end;\n top: 9px;\n right: 16px;\n`;\n\nconst Banner = ({\n isTrialEndedRecently,\n onDismiss,\n}: {\n isTrialEndedRecently: boolean;\n onDismiss: () => void;\n}) => {\n const { formatMessage } = useIntl();\n\n return (\n <DismissibleBanner\n onDismiss={onDismiss}\n closeLabel={formatMessage({\n id: 'app.components.UpsellBanner.close',\n defaultMessage: 'Close',\n })}\n message={\n <>\n <Typography\n variant=\"delta\"\n fontWeight=\"bold\"\n textColor=\"neutral0\"\n textAlign=\"center\"\n fontSize={2}\n >\n {formatMessage(\n isTrialEndedRecently\n ? {\n id: 'app.components.UpsellBanner.intro.ended',\n defaultMessage: 'Your trial has ended: ',\n }\n : {\n id: 'app.components.UpsellBanner.intro',\n defaultMessage: 'Access to Growth plan features: ',\n }\n )}\n </Typography>\n <Typography\n variant=\"delta\"\n textColor=\"neutral0\"\n textAlign=\"center\"\n paddingRight={4}\n fontSize={2}\n >\n {formatMessage(\n isTrialEndedRecently\n ? {\n id: 'app.components.UpsellBanner.text.ended',\n defaultMessage: 'Keep access to Growth features by upgrading now.',\n }\n : {\n id: 'app.components.UpsellBanner.text',\n defaultMessage:\n 'As part of your trial, you can explore premium tools such as Content History, Releases, and Single Sign-On (SSO).',\n }\n )}\n </Typography>\n </>\n }\n action={\n <LinkButton\n width=\"max-content\"\n variant=\"tertiary\"\n href=\"https://billing.strapi.io\"\n target=\"_blank\"\n >\n {formatMessage(\n isTrialEndedRecently\n ? {\n id: 'app.components.UpsellBanner.button.ended',\n defaultMessage: 'Keep Growth plan',\n }\n : {\n id: 'app.components.UpsellBanner.button',\n defaultMessage: 'Upgrade now',\n }\n )}\n </LinkButton>\n }\n />\n );\n};\n\nconst UpsellBanner = () => {\n const { license } = useLicenseLimits();\n const { formatMessage } = useIntl();\n\n const [cachedTrialEndsAt, setCachedTrialEndsAt] = useScopedPersistentState<string | undefined>(\n 'STRAPI_FREE_TRIAL_ENDS_AT',\n undefined\n );\n\n const [dismissedFor, setDismissedFor] = useScopedPersistentState<string | undefined>(\n 'STRAPI_UPSELL_BANNER_DISMISSED_FOR',\n undefined\n );\n\n const sevenDaysAgo = subDays(new Date(), 7);\n\n const timeLeftData = useGetLicenseTrialTimeLeftQuery(undefined, {\n skip: !license?.isTrial,\n });\n\n useEffect(() => {\n if (timeLeftData.data?.trialEndsAt) {\n setCachedTrialEndsAt(timeLeftData.data.trialEndsAt);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [timeLeftData.data?.trialEndsAt]);\n\n // When the license is not a trial + not EE, and the cached trial end date is found in the localstorage, that means the trial has ended\n // We show the banner to encourage the user to upgrade (for 7 days after the trial ends)\n const isTrialEndedRecently = Boolean(\n !license?.isTrial &&\n !window.strapi.isEE &&\n cachedTrialEndsAt &&\n isAfter(new Date(cachedTrialEndsAt), sevenDaysAgo)\n );\n\n const trialEndsAt = timeLeftData.data?.trialEndsAt ?? cachedTrialEndsAt;\n\n const toCanonicalISO = (v: string | undefined): string | undefined => {\n if (!v) return undefined;\n const date = new Date(v);\n return isValid(date) ? date.toISOString() : undefined;\n };\n\n const isDismissed = Boolean(\n trialEndsAt && toCanonicalISO(dismissedFor) === toCanonicalISO(trialEndsAt)\n );\n\n const handleDismiss = () => setDismissedFor(toCanonicalISO(trialEndsAt));\n const handleReopen = () => setDismissedFor(undefined);\n\n if (!(timeLeftData.data?.trialEndsAt || isTrialEndedRecently)) {\n return null;\n }\n\n if (isDismissed) {\n return (\n <FixedButtonWrapper>\n <IconButton\n withTooltip={false}\n label={formatMessage({\n id: 'app.components.UpsellBanner.reopen',\n defaultMessage: 'Reopen banner',\n })}\n onClick={handleReopen}\n >\n <ArrowsOut />\n </IconButton>\n </FixedButtonWrapper>\n );\n }\n\n return <Banner isTrialEndedRecently={isTrialEndedRecently} onDismiss={handleDismiss} />;\n};\n\nexport { UpsellBanner };\n"],"names":["FixedButtonWrapper","styled","Box","Banner","isTrialEndedRecently","onDismiss","formatMessage","useIntl","_jsx","DismissibleBanner","closeLabel","id","defaultMessage","message","_jsxs","_Fragment","Typography","variant","fontWeight","textColor","textAlign","fontSize","paddingRight","action","LinkButton","width","href","target","UpsellBanner","license","useLicenseLimits","cachedTrialEndsAt","setCachedTrialEndsAt","useScopedPersistentState","undefined","dismissedFor","setDismissedFor","sevenDaysAgo","subDays","Date","timeLeftData","useGetLicenseTrialTimeLeftQuery","skip","isTrial","useEffect","data","trialEndsAt","Boolean","window","strapi","isEE","isAfter","toCanonicalISO","v","date","isValid","toISOString","isDismissed","handleDismiss","handleReopen","IconButton","withTooltip","label","onClick","ArrowsOut"],"mappings":"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"}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
var toolkit = require('@reduxjs/toolkit');
|
|
4
4
|
var reducer = require('../../reducer.js');
|
|
5
5
|
var api = require('../../services/api.js');
|
|
6
|
+
var getFetchClient = require('../../utils/getFetchClient.js');
|
|
6
7
|
var basename = require('../utils/basename.js');
|
|
7
8
|
|
|
8
9
|
/**
|
|
@@ -60,9 +61,20 @@ const injectReducerStoreEnhancer = (appReducers)=>(next)=>(...args)=>{
|
|
|
60
61
|
});
|
|
61
62
|
return store;
|
|
62
63
|
};
|
|
63
|
-
|
|
64
|
+
/**
|
|
65
|
+
* A 401 here means the fetch layer already tried to refresh the access token and
|
|
66
|
+
* the server refused: the session is over. Hand that to `AuthProvider`, which
|
|
67
|
+
* can warn about unsaved changes before clearing auth state. Reloading the page
|
|
68
|
+
* instead (as this used to) unmounts every form first, so the warning — and the
|
|
69
|
+
* user's edits — never make it to the screen. Falls back to the hard redirect
|
|
70
|
+
* when no provider is listening, so the user is never stranded on a dead page.
|
|
71
|
+
*
|
|
72
|
+
* `triggerSessionExpired` is idempotent per dead session (baseQuery often
|
|
73
|
+
* already called it for this 401). The `!` check is only the no-listener
|
|
74
|
+
* fallback; it must not run logout a second time.
|
|
75
|
+
*/ const rtkQueryUnauthorizedMiddleware = ({ dispatch })=>(next)=>(action)=>{
|
|
64
76
|
// isRejectedWithValue Or isRejected
|
|
65
|
-
if (toolkit.isRejected(action) && action.payload?.status === 401) {
|
|
77
|
+
if (toolkit.isRejected(action) && action.payload?.status === 401 && !getFetchClient.triggerSessionExpired()) {
|
|
66
78
|
dispatch(reducer.logout());
|
|
67
79
|
const basename$1 = basename.getBasename();
|
|
68
80
|
window.location.href = `${basename$1}/auth/login`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configure.js","sources":["../../../../../../admin/src/core/store/configure.ts"],"sourcesContent":["import {\n configureStore,\n StoreEnhancer,\n Middleware,\n Reducer,\n combineReducers,\n MiddlewareAPI,\n isRejected,\n} from '@reduxjs/toolkit';\n\nimport { reducer as appReducer, AppState, logout } from '../../reducer';\nimport { adminApi } from '../../services/api';\nimport { getBasename } from '../utils/basename';\n\n/**\n * @description Static reducers are ones we know, they live in the admin package.\n */\nconst staticReducers = {\n [adminApi.reducerPath]: adminApi.reducer,\n admin_app: appReducer,\n} as const;\n\nconst injectReducerStoreEnhancer: (appReducers: Record<string, Reducer>) => StoreEnhancer =\n (appReducers) =>\n (next) =>\n (...args) => {\n const store = next(...args);\n\n const asyncReducers: Record<string, Reducer> = {};\n\n return {\n ...store,\n asyncReducers,\n injectReducer: (key: string, asyncReducer: Reducer) => {\n asyncReducers[key] = asyncReducer;\n store.replaceReducer(\n // @ts-expect-error we dynamically add reducers which makes the types uncomfortable.\n combineReducers({\n ...appReducers,\n ...asyncReducers,\n })\n );\n },\n };\n };\n\ntype PreloadState = Partial<{\n admin_app: AppState;\n}>;\n\ntype DefaultMiddlewareOptions = {\n serializableCheck?: boolean;\n immutableCheck?: boolean;\n};\n\n/**\n * @description This is the main store configuration function, injected Reducers use our legacy app.addReducer API,\n * which we're trying to phase out. App Middlewares could potentially be improved...?\n */\nconst configureStoreImpl = (\n preloadedState: PreloadState = {},\n appMiddlewares: Array<() => Middleware> = [],\n injectedReducers: Record<string, Reducer> = {}\n) => {\n const coreReducers = { ...staticReducers, ...injectedReducers } as const;\n\n const defaultMiddlewareOptions: DefaultMiddlewareOptions = {};\n\n // These are already disabled in 'production' env but we also need to disable it in test environments\n // However, we want to leave them on for development so any issues can still be caught\n if (process.env.NODE_ENV === 'test') {\n defaultMiddlewareOptions.serializableCheck = false;\n defaultMiddlewareOptions.immutableCheck = false;\n }\n\n const store = configureStore({\n preloadedState: {\n admin_app: preloadedState.admin_app,\n },\n reducer: coreReducers,\n devTools: process.env.NODE_ENV !== 'production',\n middleware: (getDefaultMiddleware) => [\n ...getDefaultMiddleware(defaultMiddlewareOptions),\n rtkQueryUnauthorizedMiddleware,\n adminApi.middleware,\n ...appMiddlewares.map((m) => m()),\n ],\n enhancers: [injectReducerStoreEnhancer(coreReducers)],\n });\n\n return store;\n};\n\nconst rtkQueryUnauthorizedMiddleware: Middleware =\n ({ dispatch }: MiddlewareAPI) =>\n (next) =>\n (action) => {\n // isRejectedWithValue Or isRejected\n if (isRejected(action) && action.payload?.status === 401) {\n dispatch(logout());\n const basename = getBasename();\n window.location.href = `${basename}/auth/login`;\n return;\n }\n\n return next(action);\n };\n\ntype Store = ReturnType<typeof configureStoreImpl> & {\n asyncReducers: Record<string, Reducer>;\n injectReducer: (key: string, asyncReducer: Reducer) => void;\n};\n\ntype RootState = ReturnType<Store['getState']>;\n\ntype Dispatch = Store['dispatch'];\n\nexport { configureStoreImpl as configureStore };\nexport type { RootState, Dispatch, AppState, Store, PreloadState };\n"],"names":["staticReducers","adminApi","reducerPath","reducer","admin_app","appReducer","injectReducerStoreEnhancer","appReducers","next","args","store","asyncReducers","injectReducer","key","asyncReducer","replaceReducer","combineReducers","configureStoreImpl","preloadedState","appMiddlewares","injectedReducers","coreReducers","defaultMiddlewareOptions","process","env","NODE_ENV","serializableCheck","immutableCheck","configureStore","devTools","middleware","getDefaultMiddleware","rtkQueryUnauthorizedMiddleware","map","m","enhancers","dispatch","action","isRejected","payload","status","logout","basename","getBasename","window","location","href"],"mappings":"
|
|
1
|
+
{"version":3,"file":"configure.js","sources":["../../../../../../admin/src/core/store/configure.ts"],"sourcesContent":["import {\n configureStore,\n StoreEnhancer,\n Middleware,\n Reducer,\n combineReducers,\n MiddlewareAPI,\n isRejected,\n} from '@reduxjs/toolkit';\n\nimport { reducer as appReducer, AppState, logout } from '../../reducer';\nimport { adminApi } from '../../services/api';\nimport { triggerSessionExpired } from '../../utils/getFetchClient';\nimport { getBasename } from '../utils/basename';\n\n/**\n * @description Static reducers are ones we know, they live in the admin package.\n */\nconst staticReducers = {\n [adminApi.reducerPath]: adminApi.reducer,\n admin_app: appReducer,\n} as const;\n\nconst injectReducerStoreEnhancer: (appReducers: Record<string, Reducer>) => StoreEnhancer =\n (appReducers) =>\n (next) =>\n (...args) => {\n const store = next(...args);\n\n const asyncReducers: Record<string, Reducer> = {};\n\n return {\n ...store,\n asyncReducers,\n injectReducer: (key: string, asyncReducer: Reducer) => {\n asyncReducers[key] = asyncReducer;\n store.replaceReducer(\n // @ts-expect-error we dynamically add reducers which makes the types uncomfortable.\n combineReducers({\n ...appReducers,\n ...asyncReducers,\n })\n );\n },\n };\n };\n\ntype PreloadState = Partial<{\n admin_app: AppState;\n}>;\n\ntype DefaultMiddlewareOptions = {\n serializableCheck?: boolean;\n immutableCheck?: boolean;\n};\n\n/**\n * @description This is the main store configuration function, injected Reducers use our legacy app.addReducer API,\n * which we're trying to phase out. App Middlewares could potentially be improved...?\n */\nconst configureStoreImpl = (\n preloadedState: PreloadState = {},\n appMiddlewares: Array<() => Middleware> = [],\n injectedReducers: Record<string, Reducer> = {}\n) => {\n const coreReducers = { ...staticReducers, ...injectedReducers } as const;\n\n const defaultMiddlewareOptions: DefaultMiddlewareOptions = {};\n\n // These are already disabled in 'production' env but we also need to disable it in test environments\n // However, we want to leave them on for development so any issues can still be caught\n if (process.env.NODE_ENV === 'test') {\n defaultMiddlewareOptions.serializableCheck = false;\n defaultMiddlewareOptions.immutableCheck = false;\n }\n\n const store = configureStore({\n preloadedState: {\n admin_app: preloadedState.admin_app,\n },\n reducer: coreReducers,\n devTools: process.env.NODE_ENV !== 'production',\n middleware: (getDefaultMiddleware) => [\n ...getDefaultMiddleware(defaultMiddlewareOptions),\n rtkQueryUnauthorizedMiddleware,\n adminApi.middleware,\n ...appMiddlewares.map((m) => m()),\n ],\n enhancers: [injectReducerStoreEnhancer(coreReducers)],\n });\n\n return store;\n};\n\n/**\n * A 401 here means the fetch layer already tried to refresh the access token and\n * the server refused: the session is over. Hand that to `AuthProvider`, which\n * can warn about unsaved changes before clearing auth state. Reloading the page\n * instead (as this used to) unmounts every form first, so the warning — and the\n * user's edits — never make it to the screen. Falls back to the hard redirect\n * when no provider is listening, so the user is never stranded on a dead page.\n *\n * `triggerSessionExpired` is idempotent per dead session (baseQuery often\n * already called it for this 401). The `!` check is only the no-listener\n * fallback; it must not run logout a second time.\n */\nconst rtkQueryUnauthorizedMiddleware: Middleware =\n ({ dispatch }: MiddlewareAPI) =>\n (next) =>\n (action) => {\n // isRejectedWithValue Or isRejected\n if (isRejected(action) && action.payload?.status === 401 && !triggerSessionExpired()) {\n dispatch(logout());\n const basename = getBasename();\n window.location.href = `${basename}/auth/login`;\n return;\n }\n\n return next(action);\n };\n\ntype Store = ReturnType<typeof configureStoreImpl> & {\n asyncReducers: Record<string, Reducer>;\n injectReducer: (key: string, asyncReducer: Reducer) => void;\n};\n\ntype RootState = ReturnType<Store['getState']>;\n\ntype Dispatch = Store['dispatch'];\n\nexport { configureStoreImpl as configureStore };\nexport type { RootState, Dispatch, AppState, Store, PreloadState };\n"],"names":["staticReducers","adminApi","reducerPath","reducer","admin_app","appReducer","injectReducerStoreEnhancer","appReducers","next","args","store","asyncReducers","injectReducer","key","asyncReducer","replaceReducer","combineReducers","configureStoreImpl","preloadedState","appMiddlewares","injectedReducers","coreReducers","defaultMiddlewareOptions","process","env","NODE_ENV","serializableCheck","immutableCheck","configureStore","devTools","middleware","getDefaultMiddleware","rtkQueryUnauthorizedMiddleware","map","m","enhancers","dispatch","action","isRejected","payload","status","triggerSessionExpired","logout","basename","getBasename","window","location","href"],"mappings":";;;;;;;;AAeA;;AAEC,IACD,MAAMA,cAAAA,GAAiB;AACrB,IAAA,CAACC,YAAAA,CAASC,WAAW,GAAGD,aAASE,OAAO;IACxCC,SAAAA,EAAWC;AACb,CAAA;AAEA,MAAMC,6BACJ,CAACC,WAAAA,GACD,CAACC,IAAAA,GACD,CAAC,GAAGC,IAAAA,GAAAA;AACF,YAAA,MAAMC,QAAQF,IAAAA,CAAAA,GAAQC,IAAAA,CAAAA;AAEtB,YAAA,MAAME,gBAAyC,EAAC;YAEhD,OAAO;AACL,gBAAA,GAAGD,KAAK;AACRC,gBAAAA,aAAAA;AACAC,gBAAAA,aAAAA,EAAe,CAACC,GAAAA,EAAaC,YAAAA,GAAAA;oBAC3BH,aAAa,CAACE,IAAI,GAAGC,YAAAA;oBACrBJ,KAAAA,CAAMK,cAAc;oBAElBC,uBAAAA,CAAgB;AACd,wBAAA,GAAGT,WAAW;AACd,wBAAA,GAAGI;AACL,qBAAA,CAAA,CAAA;AAEJ,gBAAA;AACF,aAAA;AACF,QAAA,CAAA;AAWF;;;AAGC,IACD,MAAMM,kBAAAA,GAAqB,CACzBC,cAAAA,GAA+B,EAAE,EACjCC,cAAAA,GAA0C,EAAE,EAC5CC,gBAAAA,GAA4C,EAAE,GAAA;AAE9C,IAAA,MAAMC,YAAAA,GAAe;AAAE,QAAA,GAAGrB,cAAc;AAAE,QAAA,GAAGoB;AAAiB,KAAA;AAE9D,IAAA,MAAME,2BAAqD,EAAC;;;AAI5D,IAAA,IAAIC,OAAAA,CAAQC,GAAG,CAACC,QAAQ,KAAK,MAAA,EAAQ;AACnCH,QAAAA,wBAAAA,CAAyBI,iBAAiB,GAAG,KAAA;AAC7CJ,QAAAA,wBAAAA,CAAyBK,cAAc,GAAG,KAAA;AAC5C,IAAA;AAEA,IAAA,MAAMjB,QAAQkB,sBAAAA,CAAe;QAC3BV,cAAAA,EAAgB;AACdd,YAAAA,SAAAA,EAAWc,eAAed;AAC5B,SAAA;QACAD,OAAAA,EAASkB,YAAAA;AACTQ,QAAAA,QAAAA,EAAUN,OAAAA,CAAQC,GAAG,CAACC,QAAQ,KAAK,YAAA;AACnCK,QAAAA,UAAAA,EAAY,CAACC,oBAAAA,GAAyB;mBACjCA,oBAAAA,CAAqBT,wBAAAA,CAAAA;AACxBU,gBAAAA,8BAAAA;AACA/B,gBAAAA,YAAAA,CAAS6B,UAAU;mBAChBX,cAAAA,CAAec,GAAG,CAAC,CAACC,CAAAA,GAAMA,CAAAA,EAAAA;AAC9B,aAAA;QACDC,SAAAA,EAAW;YAAC7B,0BAAAA,CAA2Be,YAAAA;AAAc;AACvD,KAAA,CAAA;IAEA,OAAOX,KAAAA;AACT;AAEA;;;;;;;;;;;IAYA,MAAMsB,iCACJ,CAAC,EAAEI,QAAQ,EAAiB,GAC5B,CAAC5B,IAAAA,GACD,CAAC6B,MAAAA,GAAAA;;YAEC,IAAIC,kBAAAA,CAAWD,WAAWA,MAAAA,CAAOE,OAAO,EAAEC,MAAAA,KAAW,GAAA,IAAO,CAACC,oCAAAA,EAAAA,EAAyB;gBACpFL,QAAAA,CAASM,cAAAA,EAAAA,CAAAA;AACT,gBAAA,MAAMC,UAAAA,GAAWC,oBAAAA,EAAAA;AACjBC,gBAAAA,MAAAA,CAAOC,QAAQ,CAACC,IAAI,GAAG,CAAA,EAAGJ,UAAAA,CAAS,WAAW,CAAC;AAC/C,gBAAA;AACF,YAAA;AAEA,YAAA,OAAOnC,IAAAA,CAAK6B,MAAAA,CAAAA;AACd,QAAA,CAAA;;;;"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { configureStore, isRejected, combineReducers } from '@reduxjs/toolkit';
|
|
2
2
|
import { reducer, logout } from '../../reducer.mjs';
|
|
3
3
|
import { adminApi } from '../../services/api.mjs';
|
|
4
|
+
import { triggerSessionExpired } from '../../utils/getFetchClient.mjs';
|
|
4
5
|
import { getBasename } from '../utils/basename.mjs';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -58,9 +59,20 @@ const injectReducerStoreEnhancer = (appReducers)=>(next)=>(...args)=>{
|
|
|
58
59
|
});
|
|
59
60
|
return store;
|
|
60
61
|
};
|
|
61
|
-
|
|
62
|
+
/**
|
|
63
|
+
* A 401 here means the fetch layer already tried to refresh the access token and
|
|
64
|
+
* the server refused: the session is over. Hand that to `AuthProvider`, which
|
|
65
|
+
* can warn about unsaved changes before clearing auth state. Reloading the page
|
|
66
|
+
* instead (as this used to) unmounts every form first, so the warning — and the
|
|
67
|
+
* user's edits — never make it to the screen. Falls back to the hard redirect
|
|
68
|
+
* when no provider is listening, so the user is never stranded on a dead page.
|
|
69
|
+
*
|
|
70
|
+
* `triggerSessionExpired` is idempotent per dead session (baseQuery often
|
|
71
|
+
* already called it for this 401). The `!` check is only the no-listener
|
|
72
|
+
* fallback; it must not run logout a second time.
|
|
73
|
+
*/ const rtkQueryUnauthorizedMiddleware = ({ dispatch })=>(next)=>(action)=>{
|
|
62
74
|
// isRejectedWithValue Or isRejected
|
|
63
|
-
if (isRejected(action) && action.payload?.status === 401) {
|
|
75
|
+
if (isRejected(action) && action.payload?.status === 401 && !triggerSessionExpired()) {
|
|
64
76
|
dispatch(logout());
|
|
65
77
|
const basename = getBasename();
|
|
66
78
|
window.location.href = `${basename}/auth/login`;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configure.mjs","sources":["../../../../../../admin/src/core/store/configure.ts"],"sourcesContent":["import {\n configureStore,\n StoreEnhancer,\n Middleware,\n Reducer,\n combineReducers,\n MiddlewareAPI,\n isRejected,\n} from '@reduxjs/toolkit';\n\nimport { reducer as appReducer, AppState, logout } from '../../reducer';\nimport { adminApi } from '../../services/api';\nimport { getBasename } from '../utils/basename';\n\n/**\n * @description Static reducers are ones we know, they live in the admin package.\n */\nconst staticReducers = {\n [adminApi.reducerPath]: adminApi.reducer,\n admin_app: appReducer,\n} as const;\n\nconst injectReducerStoreEnhancer: (appReducers: Record<string, Reducer>) => StoreEnhancer =\n (appReducers) =>\n (next) =>\n (...args) => {\n const store = next(...args);\n\n const asyncReducers: Record<string, Reducer> = {};\n\n return {\n ...store,\n asyncReducers,\n injectReducer: (key: string, asyncReducer: Reducer) => {\n asyncReducers[key] = asyncReducer;\n store.replaceReducer(\n // @ts-expect-error we dynamically add reducers which makes the types uncomfortable.\n combineReducers({\n ...appReducers,\n ...asyncReducers,\n })\n );\n },\n };\n };\n\ntype PreloadState = Partial<{\n admin_app: AppState;\n}>;\n\ntype DefaultMiddlewareOptions = {\n serializableCheck?: boolean;\n immutableCheck?: boolean;\n};\n\n/**\n * @description This is the main store configuration function, injected Reducers use our legacy app.addReducer API,\n * which we're trying to phase out. App Middlewares could potentially be improved...?\n */\nconst configureStoreImpl = (\n preloadedState: PreloadState = {},\n appMiddlewares: Array<() => Middleware> = [],\n injectedReducers: Record<string, Reducer> = {}\n) => {\n const coreReducers = { ...staticReducers, ...injectedReducers } as const;\n\n const defaultMiddlewareOptions: DefaultMiddlewareOptions = {};\n\n // These are already disabled in 'production' env but we also need to disable it in test environments\n // However, we want to leave them on for development so any issues can still be caught\n if (process.env.NODE_ENV === 'test') {\n defaultMiddlewareOptions.serializableCheck = false;\n defaultMiddlewareOptions.immutableCheck = false;\n }\n\n const store = configureStore({\n preloadedState: {\n admin_app: preloadedState.admin_app,\n },\n reducer: coreReducers,\n devTools: process.env.NODE_ENV !== 'production',\n middleware: (getDefaultMiddleware) => [\n ...getDefaultMiddleware(defaultMiddlewareOptions),\n rtkQueryUnauthorizedMiddleware,\n adminApi.middleware,\n ...appMiddlewares.map((m) => m()),\n ],\n enhancers: [injectReducerStoreEnhancer(coreReducers)],\n });\n\n return store;\n};\n\nconst rtkQueryUnauthorizedMiddleware: Middleware =\n ({ dispatch }: MiddlewareAPI) =>\n (next) =>\n (action) => {\n // isRejectedWithValue Or isRejected\n if (isRejected(action) && action.payload?.status === 401) {\n dispatch(logout());\n const basename = getBasename();\n window.location.href = `${basename}/auth/login`;\n return;\n }\n\n return next(action);\n };\n\ntype Store = ReturnType<typeof configureStoreImpl> & {\n asyncReducers: Record<string, Reducer>;\n injectReducer: (key: string, asyncReducer: Reducer) => void;\n};\n\ntype RootState = ReturnType<Store['getState']>;\n\ntype Dispatch = Store['dispatch'];\n\nexport { configureStoreImpl as configureStore };\nexport type { RootState, Dispatch, AppState, Store, PreloadState };\n"],"names":["staticReducers","adminApi","reducerPath","reducer","admin_app","appReducer","injectReducerStoreEnhancer","appReducers","next","args","store","asyncReducers","injectReducer","key","asyncReducer","replaceReducer","combineReducers","configureStoreImpl","preloadedState","appMiddlewares","injectedReducers","coreReducers","defaultMiddlewareOptions","process","env","NODE_ENV","serializableCheck","immutableCheck","configureStore","devTools","middleware","getDefaultMiddleware","rtkQueryUnauthorizedMiddleware","map","m","enhancers","dispatch","action","isRejected","payload","status","logout","basename","getBasename","window","location","href"],"mappings":"
|
|
1
|
+
{"version":3,"file":"configure.mjs","sources":["../../../../../../admin/src/core/store/configure.ts"],"sourcesContent":["import {\n configureStore,\n StoreEnhancer,\n Middleware,\n Reducer,\n combineReducers,\n MiddlewareAPI,\n isRejected,\n} from '@reduxjs/toolkit';\n\nimport { reducer as appReducer, AppState, logout } from '../../reducer';\nimport { adminApi } from '../../services/api';\nimport { triggerSessionExpired } from '../../utils/getFetchClient';\nimport { getBasename } from '../utils/basename';\n\n/**\n * @description Static reducers are ones we know, they live in the admin package.\n */\nconst staticReducers = {\n [adminApi.reducerPath]: adminApi.reducer,\n admin_app: appReducer,\n} as const;\n\nconst injectReducerStoreEnhancer: (appReducers: Record<string, Reducer>) => StoreEnhancer =\n (appReducers) =>\n (next) =>\n (...args) => {\n const store = next(...args);\n\n const asyncReducers: Record<string, Reducer> = {};\n\n return {\n ...store,\n asyncReducers,\n injectReducer: (key: string, asyncReducer: Reducer) => {\n asyncReducers[key] = asyncReducer;\n store.replaceReducer(\n // @ts-expect-error we dynamically add reducers which makes the types uncomfortable.\n combineReducers({\n ...appReducers,\n ...asyncReducers,\n })\n );\n },\n };\n };\n\ntype PreloadState = Partial<{\n admin_app: AppState;\n}>;\n\ntype DefaultMiddlewareOptions = {\n serializableCheck?: boolean;\n immutableCheck?: boolean;\n};\n\n/**\n * @description This is the main store configuration function, injected Reducers use our legacy app.addReducer API,\n * which we're trying to phase out. App Middlewares could potentially be improved...?\n */\nconst configureStoreImpl = (\n preloadedState: PreloadState = {},\n appMiddlewares: Array<() => Middleware> = [],\n injectedReducers: Record<string, Reducer> = {}\n) => {\n const coreReducers = { ...staticReducers, ...injectedReducers } as const;\n\n const defaultMiddlewareOptions: DefaultMiddlewareOptions = {};\n\n // These are already disabled in 'production' env but we also need to disable it in test environments\n // However, we want to leave them on for development so any issues can still be caught\n if (process.env.NODE_ENV === 'test') {\n defaultMiddlewareOptions.serializableCheck = false;\n defaultMiddlewareOptions.immutableCheck = false;\n }\n\n const store = configureStore({\n preloadedState: {\n admin_app: preloadedState.admin_app,\n },\n reducer: coreReducers,\n devTools: process.env.NODE_ENV !== 'production',\n middleware: (getDefaultMiddleware) => [\n ...getDefaultMiddleware(defaultMiddlewareOptions),\n rtkQueryUnauthorizedMiddleware,\n adminApi.middleware,\n ...appMiddlewares.map((m) => m()),\n ],\n enhancers: [injectReducerStoreEnhancer(coreReducers)],\n });\n\n return store;\n};\n\n/**\n * A 401 here means the fetch layer already tried to refresh the access token and\n * the server refused: the session is over. Hand that to `AuthProvider`, which\n * can warn about unsaved changes before clearing auth state. Reloading the page\n * instead (as this used to) unmounts every form first, so the warning — and the\n * user's edits — never make it to the screen. Falls back to the hard redirect\n * when no provider is listening, so the user is never stranded on a dead page.\n *\n * `triggerSessionExpired` is idempotent per dead session (baseQuery often\n * already called it for this 401). The `!` check is only the no-listener\n * fallback; it must not run logout a second time.\n */\nconst rtkQueryUnauthorizedMiddleware: Middleware =\n ({ dispatch }: MiddlewareAPI) =>\n (next) =>\n (action) => {\n // isRejectedWithValue Or isRejected\n if (isRejected(action) && action.payload?.status === 401 && !triggerSessionExpired()) {\n dispatch(logout());\n const basename = getBasename();\n window.location.href = `${basename}/auth/login`;\n return;\n }\n\n return next(action);\n };\n\ntype Store = ReturnType<typeof configureStoreImpl> & {\n asyncReducers: Record<string, Reducer>;\n injectReducer: (key: string, asyncReducer: Reducer) => void;\n};\n\ntype RootState = ReturnType<Store['getState']>;\n\ntype Dispatch = Store['dispatch'];\n\nexport { configureStoreImpl as configureStore };\nexport type { RootState, Dispatch, AppState, Store, PreloadState };\n"],"names":["staticReducers","adminApi","reducerPath","reducer","admin_app","appReducer","injectReducerStoreEnhancer","appReducers","next","args","store","asyncReducers","injectReducer","key","asyncReducer","replaceReducer","combineReducers","configureStoreImpl","preloadedState","appMiddlewares","injectedReducers","coreReducers","defaultMiddlewareOptions","process","env","NODE_ENV","serializableCheck","immutableCheck","configureStore","devTools","middleware","getDefaultMiddleware","rtkQueryUnauthorizedMiddleware","map","m","enhancers","dispatch","action","isRejected","payload","status","triggerSessionExpired","logout","basename","getBasename","window","location","href"],"mappings":";;;;;;AAeA;;AAEC,IACD,MAAMA,cAAAA,GAAiB;AACrB,IAAA,CAACC,QAAAA,CAASC,WAAW,GAAGD,SAASE,OAAO;IACxCC,SAAAA,EAAWC;AACb,CAAA;AAEA,MAAMC,6BACJ,CAACC,WAAAA,GACD,CAACC,IAAAA,GACD,CAAC,GAAGC,IAAAA,GAAAA;AACF,YAAA,MAAMC,QAAQF,IAAAA,CAAAA,GAAQC,IAAAA,CAAAA;AAEtB,YAAA,MAAME,gBAAyC,EAAC;YAEhD,OAAO;AACL,gBAAA,GAAGD,KAAK;AACRC,gBAAAA,aAAAA;AACAC,gBAAAA,aAAAA,EAAe,CAACC,GAAAA,EAAaC,YAAAA,GAAAA;oBAC3BH,aAAa,CAACE,IAAI,GAAGC,YAAAA;oBACrBJ,KAAAA,CAAMK,cAAc;oBAElBC,eAAAA,CAAgB;AACd,wBAAA,GAAGT,WAAW;AACd,wBAAA,GAAGI;AACL,qBAAA,CAAA,CAAA;AAEJ,gBAAA;AACF,aAAA;AACF,QAAA,CAAA;AAWF;;;AAGC,IACD,MAAMM,kBAAAA,GAAqB,CACzBC,cAAAA,GAA+B,EAAE,EACjCC,cAAAA,GAA0C,EAAE,EAC5CC,gBAAAA,GAA4C,EAAE,GAAA;AAE9C,IAAA,MAAMC,YAAAA,GAAe;AAAE,QAAA,GAAGrB,cAAc;AAAE,QAAA,GAAGoB;AAAiB,KAAA;AAE9D,IAAA,MAAME,2BAAqD,EAAC;;;AAI5D,IAAA,IAAIC,OAAAA,CAAQC,GAAG,CAACC,QAAQ,KAAK,MAAA,EAAQ;AACnCH,QAAAA,wBAAAA,CAAyBI,iBAAiB,GAAG,KAAA;AAC7CJ,QAAAA,wBAAAA,CAAyBK,cAAc,GAAG,KAAA;AAC5C,IAAA;AAEA,IAAA,MAAMjB,QAAQkB,cAAAA,CAAe;QAC3BV,cAAAA,EAAgB;AACdd,YAAAA,SAAAA,EAAWc,eAAed;AAC5B,SAAA;QACAD,OAAAA,EAASkB,YAAAA;AACTQ,QAAAA,QAAAA,EAAUN,OAAAA,CAAQC,GAAG,CAACC,QAAQ,KAAK,YAAA;AACnCK,QAAAA,UAAAA,EAAY,CAACC,oBAAAA,GAAyB;mBACjCA,oBAAAA,CAAqBT,wBAAAA,CAAAA;AACxBU,gBAAAA,8BAAAA;AACA/B,gBAAAA,QAAAA,CAAS6B,UAAU;mBAChBX,cAAAA,CAAec,GAAG,CAAC,CAACC,CAAAA,GAAMA,CAAAA,EAAAA;AAC9B,aAAA;QACDC,SAAAA,EAAW;YAAC7B,0BAAAA,CAA2Be,YAAAA;AAAc;AACvD,KAAA,CAAA;IAEA,OAAOX,KAAAA;AACT;AAEA;;;;;;;;;;;IAYA,MAAMsB,iCACJ,CAAC,EAAEI,QAAQ,EAAiB,GAC5B,CAAC5B,IAAAA,GACD,CAAC6B,MAAAA,GAAAA;;YAEC,IAAIC,UAAAA,CAAWD,WAAWA,MAAAA,CAAOE,OAAO,EAAEC,MAAAA,KAAW,GAAA,IAAO,CAACC,qBAAAA,EAAAA,EAAyB;gBACpFL,QAAAA,CAASM,MAAAA,EAAAA,CAAAA;AACT,gBAAA,MAAMC,QAAAA,GAAWC,WAAAA,EAAAA;AACjBC,gBAAAA,MAAAA,CAAOC,QAAQ,CAACC,IAAI,GAAG,CAAA,EAAGJ,QAAAA,CAAS,WAAW,CAAC;AAC/C,gBAAA;AACF,YAAA;AAEA,YAAA,OAAOnC,IAAAA,CAAK6B,MAAAA,CAAAA;AACd,QAAA,CAAA;;;;"}
|
|
@@ -2,18 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
var jsxRuntime = require('react/jsx-runtime');
|
|
4
4
|
var React = require('react');
|
|
5
|
+
var designSystem = require('@strapi/design-system');
|
|
6
|
+
var reactIntl = require('react-intl');
|
|
5
7
|
var reactRouterDom = require('react-router-dom');
|
|
8
|
+
var ConfirmDialog = require('../components/ConfirmDialog.js');
|
|
6
9
|
var Context = require('../components/Context.js');
|
|
7
10
|
var hooks = require('../core/store/hooks.js');
|
|
8
11
|
var StrapiApp = require('./StrapiApp.js');
|
|
9
|
-
var useIdleSessionLogout = require('../hooks/useIdleSessionLogout.js');
|
|
10
12
|
var useQueryParams = require('../hooks/useQueryParams.js');
|
|
13
|
+
var useSessionKeepalive = require('../hooks/useSessionKeepalive.js');
|
|
11
14
|
var reducer = require('../reducer.js');
|
|
12
15
|
var api = require('../services/api.js');
|
|
13
16
|
var auth = require('../services/auth.js');
|
|
14
17
|
var normalizeAdminLocale = require('../translations/normalizeAdminLocale.js');
|
|
15
18
|
var deviceId = require('../utils/deviceId.js');
|
|
16
19
|
var getFetchClient = require('../utils/getFetchClient.js');
|
|
20
|
+
var unsavedChangesRegistry = require('../utils/unsavedChangesRegistry.js');
|
|
17
21
|
|
|
18
22
|
function _interopNamespace(e) {
|
|
19
23
|
if (e && e.__esModule) return e;
|
|
@@ -54,7 +58,32 @@ const [Provider, useAuth] = Context.createContext('Auth');
|
|
|
54
58
|
const STORAGE_KEYS = {
|
|
55
59
|
STATUS: 'isLoggedIn'
|
|
56
60
|
};
|
|
61
|
+
const isForcedLogout = (reason)=>reason === 'session-dead';
|
|
62
|
+
/**
|
|
63
|
+
* Copy for the unsaved-changes prompt. A server-confirmed dead session needs to
|
|
64
|
+
* say *why* the dialog appeared, otherwise it reads as the usual navigation
|
|
65
|
+
* blocker and users confirm straight through it.
|
|
66
|
+
*/ const LOGOUT_PROMPTS = {
|
|
67
|
+
voluntary: {
|
|
68
|
+
title: undefined,
|
|
69
|
+
body: {
|
|
70
|
+
id: 'global.prompt.unsaved',
|
|
71
|
+
defaultMessage: 'You have unsaved changes, are you sure you want to leave?'
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
'session-dead': {
|
|
75
|
+
title: {
|
|
76
|
+
id: 'app.session.ended.title',
|
|
77
|
+
defaultMessage: 'Session ended'
|
|
78
|
+
},
|
|
79
|
+
body: {
|
|
80
|
+
id: 'app.session.ended.unsaved',
|
|
81
|
+
defaultMessage: "Your session has ended and can't be resumed. Your unsaved changes will be lost when you log out."
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
57
85
|
const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken = false })=>{
|
|
86
|
+
const { formatMessage } = reactIntl.useIntl();
|
|
58
87
|
const dispatch = hooks.useTypedDispatch();
|
|
59
88
|
const runRbacMiddleware = StrapiApp.useStrapiApp('AuthProvider', (state)=>state.rbac.run);
|
|
60
89
|
const location = reactRouterDom.useLocation();
|
|
@@ -79,7 +108,46 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
79
108
|
const navigate = reactRouterDom.useNavigate();
|
|
80
109
|
const [loginMutation] = auth.useLoginMutation();
|
|
81
110
|
const [logoutMutation] = auth.useLogoutMutation();
|
|
82
|
-
const
|
|
111
|
+
const pendingLogoutRef = React__namespace.useRef(null);
|
|
112
|
+
const logoutGuardOpenRef = React__namespace.useRef(false);
|
|
113
|
+
const logoutGuardReasonRef = React__namespace.useRef('voluntary');
|
|
114
|
+
const [isSessionLogoutDialogOpen, setIsSessionLogoutDialogOpen] = React__namespace.useState(false);
|
|
115
|
+
const [sessionLogoutReason, setSessionLogoutReason] = React__namespace.useState('voluntary');
|
|
116
|
+
/**
|
|
117
|
+
* Prompt before discarding unsaved edits, then run `logoutFn`.
|
|
118
|
+
*
|
|
119
|
+
* - `voluntary`: Cancel leaves the user logged in (server session intact).
|
|
120
|
+
* - `session-dead`: the session is already unusable — Cancel (or dismiss)
|
|
121
|
+
* still runs `logoutFn` so the client cannot keep a stale token. While the
|
|
122
|
+
* dialog is open, a later `session-dead` request upgrades the pending action.
|
|
123
|
+
*/ const runLogoutWithGuard = React__namespace.useCallback((logoutFn, reason = 'voluntary')=>{
|
|
124
|
+
if (logoutGuardOpenRef.current) {
|
|
125
|
+
pendingLogoutRef.current = logoutFn;
|
|
126
|
+
if (isForcedLogout(reason)) {
|
|
127
|
+
logoutGuardReasonRef.current = reason;
|
|
128
|
+
setSessionLogoutReason(reason);
|
|
129
|
+
}
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (!unsavedChangesRegistry.hasUnsavedChanges()) {
|
|
133
|
+
logoutFn();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
logoutGuardOpenRef.current = true;
|
|
137
|
+
logoutGuardReasonRef.current = reason;
|
|
138
|
+
pendingLogoutRef.current = logoutFn;
|
|
139
|
+
setSessionLogoutReason(reason);
|
|
140
|
+
setIsSessionLogoutDialogOpen(true);
|
|
141
|
+
}, []);
|
|
142
|
+
const performGlobalLogout = React__namespace.useCallback(()=>{
|
|
143
|
+
void (async ()=>{
|
|
144
|
+
try {
|
|
145
|
+
const { post } = getFetchClient.getFetchClient();
|
|
146
|
+
await post('/admin/logout');
|
|
147
|
+
} catch {
|
|
148
|
+
// The session may already be invalid.
|
|
149
|
+
}
|
|
150
|
+
})();
|
|
83
151
|
dispatch(api.adminApi.util.resetApiState());
|
|
84
152
|
dispatch(reducer.logout());
|
|
85
153
|
navigate('/auth/login');
|
|
@@ -87,22 +155,40 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
87
155
|
dispatch,
|
|
88
156
|
navigate
|
|
89
157
|
]);
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
* shared `isLoggedIn`/`jwtToken` storage keys. Unlike `clearStateAndLogout`,
|
|
93
|
-
* this does not dispatch `logoutAction`, so it does not fire the cross-tab
|
|
94
|
-
* `storage` event that logs every tab out. Used by the speculative idle timer,
|
|
95
|
-
* which fires per-tab at the access token's `exp`: an idle tab must never tear
|
|
96
|
-
* down a session that another tab is still actively using. A genuine logout
|
|
97
|
-
* (user-initiated, or a server-confirmed 401 via `setOnSessionExpired`) still
|
|
98
|
-
* goes through `clearStateAndLogout` and broadcasts to all tabs.
|
|
99
|
-
*/ const clearLocalSessionAndRedirect = React__namespace.useCallback(()=>{
|
|
100
|
-
dispatch(api.adminApi.util.resetApiState());
|
|
101
|
-
dispatch(reducer.setToken(null));
|
|
102
|
-
navigate('/auth/login');
|
|
158
|
+
const clearStateAndLogout = React__namespace.useCallback(()=>{
|
|
159
|
+
runLogoutWithGuard(performGlobalLogout, 'session-dead');
|
|
103
160
|
}, [
|
|
104
|
-
|
|
105
|
-
|
|
161
|
+
performGlobalLogout,
|
|
162
|
+
runLogoutWithGuard
|
|
163
|
+
]);
|
|
164
|
+
const handleConfirmSessionLogout = React__namespace.useCallback(()=>{
|
|
165
|
+
logoutGuardOpenRef.current = false;
|
|
166
|
+
logoutGuardReasonRef.current = 'voluntary';
|
|
167
|
+
setIsSessionLogoutDialogOpen(false);
|
|
168
|
+
setSessionLogoutReason('voluntary');
|
|
169
|
+
const logoutFn = pendingLogoutRef.current;
|
|
170
|
+
pendingLogoutRef.current = null;
|
|
171
|
+
logoutFn?.();
|
|
172
|
+
}, []);
|
|
173
|
+
const handleCancelSessionLogout = React__namespace.useCallback(()=>{
|
|
174
|
+
const forced = isForcedLogout(logoutGuardReasonRef.current);
|
|
175
|
+
const logoutFn = pendingLogoutRef.current;
|
|
176
|
+
logoutGuardOpenRef.current = false;
|
|
177
|
+
logoutGuardReasonRef.current = 'voluntary';
|
|
178
|
+
pendingLogoutRef.current = null;
|
|
179
|
+
setIsSessionLogoutDialogOpen(false);
|
|
180
|
+
setSessionLogoutReason('voluntary');
|
|
181
|
+
// Forced paths: session is already dead — dismiss must still clear client state.
|
|
182
|
+
if (forced) {
|
|
183
|
+
logoutFn?.();
|
|
184
|
+
}
|
|
185
|
+
}, []);
|
|
186
|
+
const handleSessionLogoutDialogOpenChange = React__namespace.useCallback((open)=>{
|
|
187
|
+
if (!open) {
|
|
188
|
+
handleCancelSessionLogout();
|
|
189
|
+
}
|
|
190
|
+
}, [
|
|
191
|
+
handleCancelSessionLogout
|
|
106
192
|
]);
|
|
107
193
|
const resyncToken = React__namespace.useCallback((newToken)=>{
|
|
108
194
|
dispatch(reducer.setToken(newToken));
|
|
@@ -111,10 +197,11 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
111
197
|
]);
|
|
112
198
|
/**
|
|
113
199
|
* Track the timestamp of the user's last interaction. This is the activity
|
|
114
|
-
* signal `
|
|
115
|
-
* their session) apart from a genuinely idle one (
|
|
116
|
-
*
|
|
117
|
-
*
|
|
200
|
+
* signal `useSessionKeepalive` uses to tell an active user (silently renew
|
|
201
|
+
* their session) apart from a genuinely idle one (leave the stale access
|
|
202
|
+
* token alone until the next request). Successful API calls don't refresh
|
|
203
|
+
* the short-lived access token on their own, so without this an
|
|
204
|
+
* actively-working user would hit a 401 the moment the token expires.
|
|
118
205
|
*/ const lastActivityRef = React__namespace.useRef(Date.now());
|
|
119
206
|
React__namespace.useEffect(()=>{
|
|
120
207
|
const markActivity = ()=>{
|
|
@@ -140,7 +227,7 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
140
227
|
const getLastActivityAt = React__namespace.useCallback(()=>lastActivityRef.current, []);
|
|
141
228
|
/**
|
|
142
229
|
* Silently rotate the refresh token and mint a new access token. Returns
|
|
143
|
-
* `true` on success (the resulting `setToken` re-arms the
|
|
230
|
+
* `true` on success (the resulting `setToken` re-arms the renew timer) and
|
|
144
231
|
* `false` when the server rejects it (session genuinely over).
|
|
145
232
|
*/ const renewSession = React__namespace.useCallback(async ()=>{
|
|
146
233
|
try {
|
|
@@ -189,15 +276,15 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
189
276
|
clearStateAndLogout
|
|
190
277
|
]);
|
|
191
278
|
/**
|
|
192
|
-
* Session
|
|
193
|
-
* then either (1) re-syncs from a token another tab refreshed, (2)
|
|
194
|
-
* renews when the user has been active, or (3)
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
|
|
279
|
+
* Session keepalive around access-token expiry. The timer fires per-tab; the
|
|
280
|
+
* hook then either (1) re-syncs from a token another tab refreshed, (2)
|
|
281
|
+
* silently renews when the user has been active, or (3) does nothing when
|
|
282
|
+
* idle — leaving the stale access token in place so unsaved form state
|
|
283
|
+
* survives until the next request refreshes it. A server-confirmed dead
|
|
284
|
+
* session (renewal rejected) broadcasts globally via `onSessionDead`. See
|
|
285
|
+
* `useSessionKeepalive` for the full rationale.
|
|
286
|
+
*/ useSessionKeepalive.useSessionKeepalive({
|
|
199
287
|
token,
|
|
200
|
-
onExpired: clearLocalSessionAndRedirect,
|
|
201
288
|
onSessionDead: clearStateAndLogout,
|
|
202
289
|
getStoredToken: reducer.getStoredToken,
|
|
203
290
|
onResync: resyncToken,
|
|
@@ -229,6 +316,7 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
229
316
|
* because if something fails, it will throw an error.
|
|
230
317
|
*/ if ('data' in res) {
|
|
231
318
|
const { token } = res.data;
|
|
319
|
+
getFetchClient.resetSessionExpiredNotification();
|
|
232
320
|
dispatch(reducer.login({
|
|
233
321
|
token,
|
|
234
322
|
persist: rememberMe
|
|
@@ -239,14 +327,26 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
239
327
|
dispatch,
|
|
240
328
|
loginMutation
|
|
241
329
|
]);
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
330
|
+
/**
|
|
331
|
+
* Explicit user logout. Confirm unsaved changes *before* `logoutMutation` so
|
|
332
|
+
* Cancel leaves the server session and client token intact.
|
|
333
|
+
*/ const logout = React__namespace.useCallback(async ()=>{
|
|
334
|
+
runLogoutWithGuard(()=>{
|
|
335
|
+
void (async ()=>{
|
|
336
|
+
try {
|
|
337
|
+
await logoutMutation({
|
|
338
|
+
deviceId: deviceId.getOrCreateDeviceId()
|
|
339
|
+
});
|
|
340
|
+
} catch {
|
|
341
|
+
// The session may already be invalid.
|
|
342
|
+
}
|
|
343
|
+
performGlobalLogout();
|
|
344
|
+
})();
|
|
245
345
|
});
|
|
246
|
-
clearStateAndLogout();
|
|
247
346
|
}, [
|
|
248
|
-
|
|
249
|
-
|
|
347
|
+
logoutMutation,
|
|
348
|
+
performGlobalLogout,
|
|
349
|
+
runLogoutWithGuard
|
|
250
350
|
]);
|
|
251
351
|
const refetchPermissions = React__namespace.useCallback(async ()=>{
|
|
252
352
|
if (!isUninitialized) {
|
|
@@ -314,16 +414,32 @@ const AuthProvider = ({ children, _defaultPermissions = [], _disableRenewToken =
|
|
|
314
414
|
userPermissions
|
|
315
415
|
]);
|
|
316
416
|
const isLoading = isLoadingUser || isLoadingPermissions;
|
|
317
|
-
return /*#__PURE__*/ jsxRuntime.
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
417
|
+
return /*#__PURE__*/ jsxRuntime.jsxs(jsxRuntime.Fragment, {
|
|
418
|
+
children: [
|
|
419
|
+
/*#__PURE__*/ jsxRuntime.jsx(Provider, {
|
|
420
|
+
token: token,
|
|
421
|
+
user: user,
|
|
422
|
+
login: login,
|
|
423
|
+
logout: logout,
|
|
424
|
+
permissions: userPermissions,
|
|
425
|
+
checkUserHasPermissions: checkUserHasPermissions ?? NOOP_CHECK_USER_HAS_PERMISSIONS,
|
|
426
|
+
refetchPermissions: refetchPermissions,
|
|
427
|
+
isLoading: isLoading,
|
|
428
|
+
children: children
|
|
429
|
+
}),
|
|
430
|
+
/*#__PURE__*/ jsxRuntime.jsx(designSystem.Dialog.Root, {
|
|
431
|
+
open: isSessionLogoutDialogOpen,
|
|
432
|
+
onOpenChange: handleSessionLogoutDialogOpenChange,
|
|
433
|
+
children: /*#__PURE__*/ jsxRuntime.jsx(ConfirmDialog.ConfirmDialog, {
|
|
434
|
+
onConfirm: handleConfirmSessionLogout,
|
|
435
|
+
onCancel: handleCancelSessionLogout,
|
|
436
|
+
title: LOGOUT_PROMPTS[sessionLogoutReason].title ? formatMessage(LOGOUT_PROMPTS[sessionLogoutReason].title) : undefined,
|
|
437
|
+
// Forced session-dead: no Cancel that pretends the session is still valid.
|
|
438
|
+
startAction: isForcedLogout(sessionLogoutReason) ? /*#__PURE__*/ jsxRuntime.jsx(jsxRuntime.Fragment, {}) : undefined,
|
|
439
|
+
children: formatMessage(LOGOUT_PROMPTS[sessionLogoutReason].body)
|
|
440
|
+
})
|
|
441
|
+
})
|
|
442
|
+
]
|
|
327
443
|
});
|
|
328
444
|
};
|
|
329
445
|
|