From 747248454d6724abd8618e48499d885190958685 Mon Sep 17 00:00:00 2001 From: garronej Date: Sun, 7 Nov 2021 20:17:14 +0100 Subject: [PATCH] Better debugging experience with user profile --- src/lib/getKcContext/getKcContext.ts | 62 ++++++++++++++++--- .../getKcContext/getKcContextFromWindow.ts | 11 ++++ src/lib/getKcContext/index.ts | 1 + .../kcContextMocks/kcContextMocks.ts | 23 ------- src/lib/i18n/useKcLanguageTag.ts | 4 +- src/lib/tools/deepAssign.ts | 31 +++++++++- src/test/lib/getKcContext.ts | 2 +- 7 files changed, 97 insertions(+), 37 deletions(-) create mode 100644 src/lib/getKcContext/getKcContextFromWindow.ts diff --git a/src/lib/getKcContext/getKcContext.ts b/src/lib/getKcContext/getKcContext.ts index cea36d02..7c076fb3 100644 --- a/src/lib/getKcContext/getKcContext.ts +++ b/src/lib/getKcContext/getKcContext.ts @@ -1,13 +1,12 @@ -import type { KcContextBase } from "./KcContextBase"; +import type { KcContextBase, Attribute } from "./KcContextBase"; import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks"; -import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName"; -import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey"; import type { DeepPartial } from "../tools/DeepPartial"; import { deepAssign } from "../tools/deepAssign"; - -export type ExtendsKcContextBase = [KcContextExtended] extends [never] - ? KcContextBase - : AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common, KcContextBase>; +import { id } from "tsafe/id"; +import { exclude } from "tsafe/exclude"; +import { assert } from "tsafe/assert"; +import type { ExtendsKcContextBase } from "./getKcContextFromWindow"; +import { getKcContextFromWindow } from "./getKcContextFromWindow"; export function getKcContext(params?: { mockPageId?: ExtendsKcContextBase["pageId"]; @@ -44,12 +43,55 @@ export function getKcContext(kcContext).profile.attributes = []; + id(kcContext).profile.attributesByName = {}; + + const partialAttributes = [ + ...((partialKcContextCustomMock as DeepPartial).profile?.attributes ?? []), + ].filter(exclude(undefined)); + + attributes.forEach(attribute => { + const partialAttribute = partialAttributes.find(({ name }) => name === attribute.name); + + const augmentedAttribute: Attribute = {} as any; + + deepAssign({ + "target": augmentedAttribute, + "source": attribute, + }); + + if (partialAttribute !== undefined) { + partialAttributes.splice(partialAttributes.indexOf(partialAttribute), 1); + + deepAssign({ + "target": augmentedAttribute, + "source": partialAttribute, + }); + } + + id(kcContext).profile.attributes.push(augmentedAttribute); + id(kcContext).profile.attributesByName[augmentedAttribute.name] = augmentedAttribute; + }); + + partialAttributes.forEach(partialAttribute => { + const { name } = partialAttribute; + + assert(name !== undefined, "If you define a mock attribute it must have at least a name"); + + id(kcContext).profile.attributes.push(partialAttribute as any); + id(kcContext).profile.attributesByName[name] = partialAttribute as any; + }); + } } return { kcContext }; } - return { - "kcContext": typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName], - }; + return { "kcContext": getKcContextFromWindow() }; } diff --git a/src/lib/getKcContext/getKcContextFromWindow.ts b/src/lib/getKcContext/getKcContextFromWindow.ts new file mode 100644 index 00000000..0142277b --- /dev/null +++ b/src/lib/getKcContext/getKcContextFromWindow.ts @@ -0,0 +1,11 @@ +import type { KcContextBase } from "./KcContextBase"; +import type { AndByDiscriminatingKey } from "../tools/AndByDiscriminatingKey"; +import { ftlValuesGlobalName } from "../../bin/build-keycloak-theme/ftlValuesGlobalName"; + +export type ExtendsKcContextBase = [KcContextExtended] extends [never] + ? KcContextBase + : AndByDiscriminatingKey<"pageId", KcContextExtended & KcContextBase.Common, KcContextBase>; + +export function getKcContextFromWindow(): ExtendsKcContextBase | undefined { + return typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName]; +} diff --git a/src/lib/getKcContext/index.ts b/src/lib/getKcContext/index.ts index 98ef0583..54458cdc 100644 --- a/src/lib/getKcContext/index.ts +++ b/src/lib/getKcContext/index.ts @@ -1,2 +1,3 @@ export type { KcContextBase, Attribute, Validators } from "./KcContextBase"; +export type { ExtendsKcContextBase } from "./getKcContextFromWindow"; export { getKcContext } from "./getKcContext"; diff --git a/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts b/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts index 0c5dade7..076d2e65 100644 --- a/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts +++ b/src/lib/getKcContext/kcContextMocks/kcContextMocks.ts @@ -278,29 +278,6 @@ export const kcContextMocks: KcContextBase[] = [ "readOnly": false, "name": "lastName", }, - { - "validators": { - "length": { - "ignore.empty.value": true, - "min": "3", - "max": "9", - }, - "up-immutable-attribute": {}, - "up-attribute-required-by-metadata-value": {}, - "email": { - "ignore.empty.value": true, - }, - }, - "displayName": "${foo}", - "annotations": { - "this_is_second_key": "this_is_second_value", - "this_is_first_key": "this_is_first_value", - }, - "required": true, - "groupAnnotations": {}, - "readOnly": false, - "name": "foo", - }, ]; return { diff --git a/src/lib/i18n/useKcLanguageTag.ts b/src/lib/i18n/useKcLanguageTag.ts index db82d89e..9873d30e 100644 --- a/src/lib/i18n/useKcLanguageTag.ts +++ b/src/lib/i18n/useKcLanguageTag.ts @@ -1,5 +1,5 @@ import { createUseGlobalState } from "powerhooks/useGlobalState"; -import { getKcContext } from "../getKcContext"; +import { getKcContextFromWindow } from "../getKcContext/getKcContextFromWindow"; import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag"; import type { StatefulEvt } from "powerhooks"; import { KcLanguageTag } from "./KcLanguageTag"; @@ -8,7 +8,7 @@ import { KcLanguageTag } from "./KcLanguageTag"; const wrap = createUseGlobalState( "kcLanguageTag", () => { - const { kcContext } = getKcContext(); + const kcContext = getKcContextFromWindow(); const languageLike = kcContext?.locale?.current ?? (typeof navigator === "undefined" ? undefined : navigator.language); diff --git a/src/lib/tools/deepAssign.ts b/src/lib/tools/deepAssign.ts index edbed44b..71a24064 100644 --- a/src/lib/tools/deepAssign.ts +++ b/src/lib/tools/deepAssign.ts @@ -1,9 +1,38 @@ import { assert } from "tsafe/assert"; import { is } from "tsafe/is"; +function deepClone(src: T): T { + const generateId = (() => { + const prefix = "xIfKdLsIIdJdLdOeJqePe"; + + let counter = 0; + + return () => `${prefix}${counter++}`; + })(); + + const map = new Map(); + + return JSON.parse( + JSON.stringify(src, (...[, value]) => { + if (typeof value === "function") { + const id = generateId(); + + map.set(id, value); + + return id; + } + + return value; + }), + (...[, value]) => (typeof value === "string" && map.has(value) ? map.get(value) : value), + ); +} + //Warning: Be mindful that because of array this is not idempotent. export function deepAssign(params: { target: Record; source: Record }) { - const { target, source } = params; + const { target } = params; + + const source = deepClone(params.source); Object.keys(source).forEach(key => { var dereferencedSource = source[key]; diff --git a/src/test/lib/getKcContext.ts b/src/test/lib/getKcContext.ts index 72aae782..8fc5a154 100644 --- a/src/test/lib/getKcContext.ts +++ b/src/test/lib/getKcContext.ts @@ -1,6 +1,6 @@ import { getKcContext } from "../../lib/getKcContext"; import type { KcContextBase } from "../../lib/getKcContext"; -import type { ExtendsKcContextBase } from "../../lib/getKcContext/getKcContext"; +import type { ExtendsKcContextBase } from "../../lib/getKcContext"; import { same } from "evt/tools/inDepth"; import { assert } from "tsafe/assert"; import type { Equals } from "tsafe";