From d796721e0009e197c1b839205c1566a1ff17f4c7 Mon Sep 17 00:00:00 2001 From: Erik Jan de Wit Date: Tue, 19 Sep 2023 14:01:35 +0200 Subject: [PATCH] moved keycloak to context so it can be updated (#22488) * moved keycloak to context so it can be updated fixes: #11931 * PR comments --- .../src/personal-info/PersonalInfo.tsx | 6 +- js/apps/account-ui/src/root/Root.tsx | 57 ++++++++++--------- .../keycloak-masthead/src/KeycloakContext.tsx | 45 +++++++++++++++ js/libs/keycloak-masthead/src/Masthead.tsx | 9 ++- js/libs/keycloak-masthead/src/main.ts | 4 ++ js/libs/keycloak-masthead/src/util.ts | 15 +++-- 6 files changed, 95 insertions(+), 41 deletions(-) create mode 100644 js/libs/keycloak-masthead/src/KeycloakContext.tsx diff --git a/js/apps/account-ui/src/personal-info/PersonalInfo.tsx b/js/apps/account-ui/src/personal-info/PersonalInfo.tsx index bfdec84187..f8b0beceec 100644 --- a/js/apps/account-ui/src/personal-info/PersonalInfo.tsx +++ b/js/apps/account-ui/src/personal-info/PersonalInfo.tsx @@ -6,6 +6,7 @@ import { Form, Spinner, } from "@patternfly/react-core"; +import { useKeycloak } from "keycloak-masthead"; import { useState } from "react"; import { FormProvider, useForm } from "react-hook-form"; import { useTranslation } from "react-i18next"; @@ -18,7 +19,6 @@ import { import { Page } from "../components/page/Page"; import { environment } from "../environment"; import { TFuncKey } from "../i18n"; -import { keycloak } from "../keycloak"; import { usePromise } from "../utils/usePromise"; import { UserProfileFields } from "./UserProfileFields"; @@ -38,6 +38,7 @@ export const fieldName = (name: string) => const PersonalInfo = () => { const { t } = useTranslation(); + const keycloak = useKeycloak(); const [userProfileMetadata, setUserProfileMetadata] = useState(); const form = useForm({ mode: "onChange" }); @@ -55,6 +56,7 @@ const PersonalInfo = () => { const onSubmit = async (user: UserRepresentation) => { try { await savePersonalInfo(user); + keycloak?.updateToken(); addAlert(t("accountUpdatedMessage")); } catch (error) { addError(t("accountUpdatedError").toString()); @@ -114,7 +116,7 @@ const PersonalInfo = () => { id="delete-account-btn" variant="danger" onClick={() => - keycloak.login({ + keycloak?.keycloak.login({ action: "delete_account", }) } diff --git a/js/apps/account-ui/src/root/Root.tsx b/js/apps/account-ui/src/root/Root.tsx index 5f70cb2872..4b9006ccd8 100644 --- a/js/apps/account-ui/src/root/Root.tsx +++ b/js/apps/account-ui/src/root/Root.tsx @@ -1,6 +1,8 @@ import { Button, Page, Spinner } from "@patternfly/react-core"; +import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons"; import { KeycloakMasthead, + KeycloakProvider, Translations, TranslationsProvider, } from "keycloak-masthead"; @@ -8,8 +10,6 @@ import { Suspense, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { Outlet, useHref } from "react-router-dom"; import { AlertProvider } from "ui-shared"; - -import { ExternalLinkSquareAltIcon } from "@patternfly/react-icons"; import { environment } from "../environment"; import { keycloak } from "../keycloak"; import { joinPath } from "../utils/joinPath"; @@ -56,31 +56,32 @@ export const Root = () => { ); return ( - - ]} - keycloak={keycloak} - /> - - } - sidebar={} - isManagedSidebar - > - - }> - - - - + + + ]} + /> + + } + sidebar={} + isManagedSidebar + > + + }> + + + + + ); }; diff --git a/js/libs/keycloak-masthead/src/KeycloakContext.tsx b/js/libs/keycloak-masthead/src/KeycloakContext.tsx new file mode 100644 index 0000000000..8b5a64b4c2 --- /dev/null +++ b/js/libs/keycloak-masthead/src/KeycloakContext.tsx @@ -0,0 +1,45 @@ +import Keycloak, { type KeycloakTokenParsed } from "keycloak-js"; +import { + createContext, + PropsWithChildren, + useContext, + useMemo, + useState, +} from "react"; + +type KeycloakProps = { + keycloak: Keycloak; + token?: KeycloakTokenParsed; + updateToken: () => void; +}; + +const KeycloakContext = createContext(undefined); + +export type KeycloakProviderProps = { + keycloak: Keycloak; +}; + +export const useKeycloak = () => useContext(KeycloakContext); + +export const KeycloakProvider = ({ + keycloak, + children, +}: PropsWithChildren) => { + const [token, setToken] = useState(keycloak.tokenParsed); + const context = useMemo( + () => ({ + keycloak, + token, + updateToken: async () => { + await keycloak.updateToken(-1); + setToken(keycloak.tokenParsed); + }, + }), + [keycloak, token], + ); + return ( + + {children} + + ); +}; diff --git a/js/libs/keycloak-masthead/src/Masthead.tsx b/js/libs/keycloak-masthead/src/Masthead.tsx index d1ed9a5257..98f08dfac6 100644 --- a/js/libs/keycloak-masthead/src/Masthead.tsx +++ b/js/libs/keycloak-masthead/src/Masthead.tsx @@ -10,13 +10,13 @@ import { PageHeaderToolsGroup, PageHeaderToolsItem, } from "@patternfly/react-core"; -import Keycloak from "keycloak-js"; import { ReactNode } from "react"; import { KeycloakDropdown } from "./KeycloakDropdown"; import { useTranslation } from "./translation/useTranslation"; import { loggedInUserName } from "./util"; import { DefaultAvatar } from "./DefaultAvatar"; +import { useKeycloak } from "./KeycloakContext"; type BrandLogo = BrandProps & { href: string; @@ -30,7 +30,6 @@ type KeycloakMastheadProps = PageHeaderProps & { hasManageAccount?: boolean; hasUsername?: boolean; }; - keycloak?: Keycloak; kebabDropdownItems?: ReactNode[]; dropdownItems?: ReactNode[]; toolbarItems?: ReactNode[]; @@ -44,13 +43,13 @@ const KeycloakMasthead = ({ hasManageAccount = true, hasUsername = true, } = {}, - keycloak, kebabDropdownItems, dropdownItems = [], toolbarItems, ...rest }: KeycloakMastheadProps) => { const { t } = useTranslation(); + const { keycloak } = useKeycloak()!; const extraItems = []; if (hasManageAccount) { extraItems.push( @@ -104,8 +103,8 @@ const KeycloakMasthead = ({ data-testid="options" dropDownItems={[...dropdownItems, extraItems]} title={ - hasUsername && keycloak - ? loggedInUserName(keycloak, t) + hasUsername + ? loggedInUserName(keycloak?.tokenParsed, t) : undefined } /> diff --git a/js/libs/keycloak-masthead/src/main.ts b/js/libs/keycloak-masthead/src/main.ts index 5a62e89412..3a66242aaa 100644 --- a/js/libs/keycloak-masthead/src/main.ts +++ b/js/libs/keycloak-masthead/src/main.ts @@ -1,5 +1,9 @@ export { default as KeycloakMasthead } from "./Masthead"; +// Keycloak +export { KeycloakProvider, useKeycloak } from "./KeycloakContext"; +export type { KeycloakProviderProps } from "./KeycloakContext"; + // Translation export { defaultTranslations } from "./translation/translations"; export type { Translations } from "./translation/translations"; diff --git a/js/libs/keycloak-masthead/src/util.ts b/js/libs/keycloak-masthead/src/util.ts index 4aa4355528..0d881eeba1 100644 --- a/js/libs/keycloak-masthead/src/util.ts +++ b/js/libs/keycloak-masthead/src/util.ts @@ -1,15 +1,18 @@ -import Keycloak from "keycloak-js"; +import { type KeycloakTokenParsed } from "keycloak-js"; import { TranslateFunction } from "./translation/useTranslation"; -export function loggedInUserName(keycloak: Keycloak, t: TranslateFunction) { - if (!keycloak.tokenParsed) { +export function loggedInUserName( + token: KeycloakTokenParsed | undefined, + t: TranslateFunction, +) { + if (!token) { return t("unknownUser"); } - const givenName = keycloak.tokenParsed.given_name; - const familyName = keycloak.tokenParsed.family_name; - const preferredUsername = keycloak.tokenParsed.preferred_username; + const givenName = token.given_name; + const familyName = token.family_name; + const preferredUsername = token.preferred_username; if (givenName && familyName) { return t("fullName", { givenName, familyName });