@thunderid/react 1.0.4 → 1.0.5
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/cjs/components/adapters/Consent.cjs +21 -26
- package/dist/cjs/components/adapters/Consent.cjs.map +1 -1
- package/dist/cjs/components/adapters/ConsentCheckboxList.cjs +9 -3
- package/dist/cjs/components/adapters/ConsentCheckboxList.cjs.map +1 -1
- package/dist/cjs/components/presentation/LanguageSwitcher/LanguageSwitcher.cjs +10 -2
- package/dist/cjs/components/presentation/LanguageSwitcher/LanguageSwitcher.cjs.map +1 -1
- package/dist/cjs/components/presentation/auth/AuthOptionFactory.cjs +1 -2
- package/dist/cjs/components/presentation/auth/AuthOptionFactory.cjs.map +1 -1
- package/dist/cjs/contexts/FlowMeta/FlowMetaProvider.cjs +13 -5
- package/dist/cjs/contexts/FlowMeta/FlowMetaProvider.cjs.map +1 -1
- package/dist/cjs/utils/resolveTranslationsInObject.cjs +3 -1
- package/dist/cjs/utils/resolveTranslationsInObject.cjs.map +1 -1
- package/dist/components/adapters/Consent.d.ts +1 -18
- package/dist/components/adapters/Consent.d.ts.map +1 -1
- package/dist/components/adapters/Consent.js +21 -25
- package/dist/components/adapters/Consent.js.map +1 -1
- package/dist/components/adapters/ConsentCheckboxList.d.ts +5 -0
- package/dist/components/adapters/ConsentCheckboxList.d.ts.map +1 -1
- package/dist/components/adapters/ConsentCheckboxList.js +9 -3
- package/dist/components/adapters/ConsentCheckboxList.js.map +1 -1
- package/dist/components/presentation/LanguageSwitcher/LanguageSwitcher.d.ts.map +1 -1
- package/dist/components/presentation/LanguageSwitcher/LanguageSwitcher.js +11 -3
- package/dist/components/presentation/LanguageSwitcher/LanguageSwitcher.js.map +1 -1
- package/dist/components/presentation/auth/AuthOptionFactory.d.ts.map +1 -1
- package/dist/components/presentation/auth/AuthOptionFactory.js +1 -2
- package/dist/components/presentation/auth/AuthOptionFactory.js.map +1 -1
- package/dist/contexts/FlowMeta/FlowMetaProvider.d.ts +13 -0
- package/dist/contexts/FlowMeta/FlowMetaProvider.d.ts.map +1 -1
- package/dist/contexts/FlowMeta/FlowMetaProvider.js +13 -5
- package/dist/contexts/FlowMeta/FlowMetaProvider.js.map +1 -1
- package/dist/utils/resolveTranslationsInObject.d.ts.map +1 -1
- package/dist/utils/resolveTranslationsInObject.js +3 -1
- package/dist/utils/resolveTranslationsInObject.js.map +1 -1
- package/package.json +2 -2
|
@@ -6,6 +6,19 @@ export interface FlowMetaProviderProps {
|
|
|
6
6
|
* @default true
|
|
7
7
|
*/
|
|
8
8
|
enabled?: boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Overrides how flow metadata is fetched, routing the request through a caller-supplied
|
|
11
|
+
* function (e.g. a Next.js Server Action) instead of the provider's default direct
|
|
12
|
+
* browser-to-`baseUrl` `fetch()`. Use this when the ThunderID server's `baseUrl` is a
|
|
13
|
+
* different origin than the app and CORS isn't (or can't be) configured for it — the
|
|
14
|
+
* override runs server-side, so the browser never talks to `baseUrl` directly.
|
|
15
|
+
*
|
|
16
|
+
* Called for both the initial fetch and `switchLanguage()`.
|
|
17
|
+
*/
|
|
18
|
+
fetchMeta?: (params: {
|
|
19
|
+
applicationId?: string;
|
|
20
|
+
language?: string;
|
|
21
|
+
}) => Promise<FlowMetadataResponse>;
|
|
9
22
|
/**
|
|
10
23
|
* Flow metadata resolved ahead of time (e.g. fetched server-side during SSR) and used to seed
|
|
11
24
|
* the provider's state. When present, the provider skips its own initial client-side fetch —
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FlowMetaProvider.d.ts","sourceRoot":"","sources":["../../../src/contexts/FlowMeta/FlowMetaProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,oBAAoB,EAMrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAoE,MAAM,OAAO,CAAC;AAM/G,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;OAKG;IACH,WAAW,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,QAAA,MAAM,gBAAgB,EAAE,EAAE,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,
|
|
1
|
+
{"version":3,"file":"FlowMetaProvider.d.ts","sourceRoot":"","sources":["../../../src/contexts/FlowMeta/FlowMetaProvider.tsx"],"names":[],"mappings":"AAGA,OAAO,EACL,oBAAoB,EAMrB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAC,EAAE,EAAE,iBAAiB,EAAoE,MAAM,OAAO,CAAC;AAM/G,MAAM,WAAW,qBAAqB;IACpC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE;QAAC,aAAa,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAC,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAEnG;;;;;OAKG;IACH,WAAW,CAAC,EAAE,oBAAoB,GAAG,IAAI,CAAC;CAC3C;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,QAAA,MAAM,gBAAgB,EAAE,EAAE,CAAC,iBAAiB,CAAC,qBAAqB,CAAC,CA8KlE,CAAC;AAEF,eAAe,gBAAgB,CAAC"}
|
|
@@ -29,7 +29,7 @@ import { jsx } from "react/jsx-runtime";
|
|
|
29
29
|
* </FlowMetaProvider>
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
const FlowMetaProvider = ({ children, enabled = true, initialMeta = null }) => {
|
|
32
|
+
const FlowMetaProvider = ({ children, enabled = true, fetchMeta, initialMeta = null }) => {
|
|
33
33
|
const { baseUrl, endpoints, applicationId, isInitialized } = useThunderID_default();
|
|
34
34
|
const i18nContext = useI18n_default();
|
|
35
35
|
const [meta, setMeta] = useState(initialMeta);
|
|
@@ -48,7 +48,10 @@ const FlowMetaProvider = ({ children, enabled = true, initialMeta = null }) => {
|
|
|
48
48
|
setIsLoading(true);
|
|
49
49
|
setError(null);
|
|
50
50
|
try {
|
|
51
|
-
setMeta(await
|
|
51
|
+
setMeta(fetchMeta ? await fetchMeta({
|
|
52
|
+
applicationId,
|
|
53
|
+
language: i18nContext?.currentLanguage
|
|
54
|
+
}) : await getFlowMeta({
|
|
52
55
|
baseUrl,
|
|
53
56
|
url: resolveResourceEndpoint("flowMeta", { endpoints }),
|
|
54
57
|
...applicationId ? {
|
|
@@ -68,14 +71,18 @@ const FlowMetaProvider = ({ children, enabled = true, initialMeta = null }) => {
|
|
|
68
71
|
endpoints,
|
|
69
72
|
applicationId,
|
|
70
73
|
isInitialized,
|
|
71
|
-
i18nContext?.currentLanguage
|
|
74
|
+
i18nContext?.currentLanguage,
|
|
75
|
+
fetchMeta
|
|
72
76
|
]);
|
|
73
77
|
const switchLanguage = useCallback(async (language) => {
|
|
74
78
|
if (!enabled) return;
|
|
75
79
|
setIsLoading(true);
|
|
76
80
|
setError(null);
|
|
77
81
|
try {
|
|
78
|
-
const result = await
|
|
82
|
+
const result = fetchMeta ? await fetchMeta({
|
|
83
|
+
applicationId,
|
|
84
|
+
language
|
|
85
|
+
}) : await getFlowMeta({
|
|
79
86
|
baseUrl,
|
|
80
87
|
url: resolveResourceEndpoint("flowMeta", { endpoints }),
|
|
81
88
|
...applicationId ? {
|
|
@@ -106,7 +113,8 @@ const FlowMetaProvider = ({ children, enabled = true, initialMeta = null }) => {
|
|
|
106
113
|
baseUrl,
|
|
107
114
|
endpoints,
|
|
108
115
|
applicationId,
|
|
109
|
-
i18nContext
|
|
116
|
+
i18nContext,
|
|
117
|
+
fetchMeta
|
|
110
118
|
]);
|
|
111
119
|
useEffect(() => {
|
|
112
120
|
if (pendingLanguage && i18nContext?.setLanguage) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FlowMetaProvider.js","names":["FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>>","useThunderID","i18nContext: I18nContextValue","useI18n","lastFetchedRef: RefObject<(() => Promise<void>) | null>","initialMetaConsumedRef: RefObject<boolean>","fetchFlowMeta: () => Promise<void>","err: unknown","switchLanguage: (language: string) => Promise<void>","result: FlowMetadataResponse","flatTranslations: Record<string, string>","value","bundle: I18nBundle","metaLanguage: string","bundlesToInject: Record<string, I18nBundle>","value: any","FlowMetaContext"],"sources":["../../../src/contexts/FlowMeta/FlowMetaProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n FlowMetadataResponse,\n FlowMetaType,\n getFlowMeta,\n I18nBundle,\n resolveResourceEndpoint,\n TranslationBundleConstants,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, RefObject, useCallback, useEffect, useRef, useState} from 'react';\nimport FlowMetaContext from './FlowMetaContext';\nimport {I18nContextValue} from '../I18n/I18nContext';\nimport useI18n from '../I18n/useI18n';\nimport useThunderID from '../ThunderID/useThunderID';\n\nexport interface FlowMetaProviderProps {\n /**\n * When false the provider skips fetching and provides null meta.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Flow metadata resolved ahead of time (e.g. fetched server-side during SSR) and used to seed\n * the provider's state. When present, the provider skips its own initial client-side fetch —\n * avoiding a redundant request and the flash of untranslated i18n keys while that fetch is in\n * flight — but still fetches normally on subsequent changes (e.g. an explicit language switch).\n */\n initialMeta?: FlowMetadataResponse | null;\n}\n\n/**\n * FlowMetaProvider fetches flow metadata from the `GET /flow/meta` endpoint\n * (v2 API) and makes it available to child components through `FlowMetaContext`.\n *\n * It is designed to be used in v2 embedded-flow scenarios and integrates with\n * `ThemeProvider` so that theme settings (colors, direction, typography, …)\n * from the server-side design configuration are applied automatically.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider\n * config={{\n * baseUrl: 'https://localhost:8090',\n * type: FlowMetaType.App,\n * id: 'your-app-id',\n * }}\n * >\n * <ThemeProvider>\n * <App />\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n */\nconst FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>> = ({\n children,\n enabled = true,\n initialMeta = null,\n}: PropsWithChildren<FlowMetaProviderProps>): ReactElement => {\n const {baseUrl, endpoints, applicationId, isInitialized} = useThunderID();\n const i18nContext: I18nContextValue = useI18n();\n\n const [meta, setMeta] = useState<FlowMetadataResponse | null>(initialMeta);\n const [isLoading, setIsLoading] = useState<boolean>(!initialMeta);\n const [error, setError] = useState<Error | null>(null);\n const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);\n\n // Track the last fetchFlowMeta reference that was actually dispatched.\n // This prevents two classes of double-fetch:\n // 1. React StrictMode simulates unmount+remount — the re-mount fires the\n // effect again with the same fetchFlowMeta reference; without this guard\n // the else-branch would issue a redundant second network request.\n // 2. Rapid dependency changes (e.g. baseUrl stabilising) that produce two\n // effect firings before the first fetch completes.\n const lastFetchedRef: RefObject<(() => Promise<void>) | null> = useRef<(() => Promise<void>) | null>(null);\n const initialMetaConsumedRef: RefObject<boolean> = useRef(false);\n const fetchFlowMeta: () => Promise<void> = useCallback(async (): Promise<void> => {\n if (!enabled) {\n setMeta(null);\n setIsLoading(false);\n return;\n }\n\n // Defer until ThunderID finishes initializing (e.g. loading applicationId\n // from storage on refresh). Once initialized, proceed even if applicationId\n // is absent — some flows (e.g. AcceptInvite) have no applicationId by design.\n if (!isInitialized && !applicationId) {\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n language: i18nContext?.currentLanguage,\n });\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [enabled, baseUrl, endpoints, applicationId, isInitialized, i18nContext?.currentLanguage]);\n\n const switchLanguage: (language: string) => Promise<void> = useCallback(\n async (language: string): Promise<void> => {\n if (!enabled) return;\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n language,\n });\n\n // Inject translations for the new language before switching\n if (result.i18n?.translations && i18nContext?.injectBundles) {\n const flatTranslations: Record<string, string> = {};\n Object.entries(result.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n i18nContext.injectBundles({[language]: bundle});\n }\n\n // Defer setLanguage to the next effect cycle so injectBundles state\n // is committed before I18nProvider's setLanguage checks mergedBundles.\n setPendingLanguage(language);\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n },\n [enabled, baseUrl, endpoints, applicationId, i18nContext],\n );\n\n // After injectBundles + setPendingLanguage are batched and committed, this\n // effect fires with the updated i18nContext (mergedBundles now includes the\n // new language), so setLanguage succeeds on the first switch.\n useEffect(() => {\n if (pendingLanguage && i18nContext?.setLanguage) {\n i18nContext.setLanguage(pendingLanguage);\n setPendingLanguage(null);\n }\n }, [pendingLanguage, i18nContext?.setLanguage]);\n\n useEffect(() => {\n if (!initialMetaConsumedRef.current) {\n initialMetaConsumedRef.current = true;\n\n if (initialMeta) {\n // Seeded from SSR (or another caller) — skip the redundant first client-side fetch.\n // Later dependency changes (e.g. an explicit language switch) still fetch normally.\n lastFetchedRef.current = fetchFlowMeta;\n return;\n }\n }\n\n if (lastFetchedRef.current === fetchFlowMeta) {\n // Same reference as the last dispatch — this is a StrictMode re-mount\n // or an effect re-fire with unchanged deps. Skip to avoid a duplicate fetch.\n return;\n }\n\n lastFetchedRef.current = fetchFlowMeta;\n fetchFlowMeta();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fetchFlowMeta]);\n\n // When meta loads with i18n translations, inject them into the i18n system.\n // Meta translations act as the base layer — prop-provided bundles still take precedence.\n useEffect(() => {\n if (!meta?.i18n?.translations || !i18nContext?.injectBundles) {\n return;\n }\n\n const metaLanguage: string = meta.i18n.language || TranslationBundleConstants.FALLBACK_LOCALE;\n\n // Flatten namespace-keyed translations to dot-path keys:\n // { \"signin\": { \"heading\": \"Sign In\" } } → { \"signin.heading\": \"Sign In\" }\n const flatTranslations: Record<string, string> = {};\n Object.entries(meta.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n\n // Inject under the meta language code and the i18n current language so\n // lookups succeed regardless of whether the system uses \"en\" or \"en-US\".\n const bundlesToInject: Record<string, I18nBundle> = {[metaLanguage]: bundle};\n if (i18nContext.currentLanguage && i18nContext.currentLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.currentLanguage] = bundle;\n }\n if (i18nContext.fallbackLanguage && i18nContext.fallbackLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.fallbackLanguage] = bundle;\n }\n\n i18nContext.injectBundles(bundlesToInject);\n }, [meta?.i18n?.translations, i18nContext?.injectBundles]);\n\n const value: any = {\n error,\n fetchFlowMeta,\n isLoading,\n meta,\n switchLanguage,\n };\n\n return <FlowMetaContext.Provider value={value}>{children}</FlowMetaContext.Provider>;\n};\n\nexport default FlowMetaProvider;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"FlowMetaProvider.js","names":["FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>>","useThunderID","i18nContext: I18nContextValue","useI18n","lastFetchedRef: RefObject<(() => Promise<void>) | null>","initialMetaConsumedRef: RefObject<boolean>","fetchFlowMeta: () => Promise<void>","err: unknown","switchLanguage: (language: string) => Promise<void>","result: FlowMetadataResponse","flatTranslations: Record<string, string>","value","bundle: I18nBundle","metaLanguage: string","bundlesToInject: Record<string, I18nBundle>","value: any","FlowMetaContext"],"sources":["../../../src/contexts/FlowMeta/FlowMetaProvider.tsx"],"sourcesContent":["// Copyright 2026 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n FlowMetadataResponse,\n FlowMetaType,\n getFlowMeta,\n I18nBundle,\n resolveResourceEndpoint,\n TranslationBundleConstants,\n} from '@thunderid/browser';\nimport {FC, PropsWithChildren, ReactElement, RefObject, useCallback, useEffect, useRef, useState} from 'react';\nimport FlowMetaContext from './FlowMetaContext';\nimport {I18nContextValue} from '../I18n/I18nContext';\nimport useI18n from '../I18n/useI18n';\nimport useThunderID from '../ThunderID/useThunderID';\n\nexport interface FlowMetaProviderProps {\n /**\n * When false the provider skips fetching and provides null meta.\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Overrides how flow metadata is fetched, routing the request through a caller-supplied\n * function (e.g. a Next.js Server Action) instead of the provider's default direct\n * browser-to-`baseUrl` `fetch()`. Use this when the ThunderID server's `baseUrl` is a\n * different origin than the app and CORS isn't (or can't be) configured for it — the\n * override runs server-side, so the browser never talks to `baseUrl` directly.\n *\n * Called for both the initial fetch and `switchLanguage()`.\n */\n fetchMeta?: (params: {applicationId?: string; language?: string}) => Promise<FlowMetadataResponse>;\n\n /**\n * Flow metadata resolved ahead of time (e.g. fetched server-side during SSR) and used to seed\n * the provider's state. When present, the provider skips its own initial client-side fetch —\n * avoiding a redundant request and the flash of untranslated i18n keys while that fetch is in\n * flight — but still fetches normally on subsequent changes (e.g. an explicit language switch).\n */\n initialMeta?: FlowMetadataResponse | null;\n}\n\n/**\n * FlowMetaProvider fetches flow metadata from the `GET /flow/meta` endpoint\n * (v2 API) and makes it available to child components through `FlowMetaContext`.\n *\n * It is designed to be used in v2 embedded-flow scenarios and integrates with\n * `ThemeProvider` so that theme settings (colors, direction, typography, …)\n * from the server-side design configuration are applied automatically.\n *\n * @example\n * ```tsx\n * <FlowMetaProvider\n * config={{\n * baseUrl: 'https://localhost:8090',\n * type: FlowMetaType.App,\n * id: 'your-app-id',\n * }}\n * >\n * <ThemeProvider>\n * <App />\n * </ThemeProvider>\n * </FlowMetaProvider>\n * ```\n */\nconst FlowMetaProvider: FC<PropsWithChildren<FlowMetaProviderProps>> = ({\n children,\n enabled = true,\n fetchMeta,\n initialMeta = null,\n}: PropsWithChildren<FlowMetaProviderProps>): ReactElement => {\n const {baseUrl, endpoints, applicationId, isInitialized} = useThunderID();\n const i18nContext: I18nContextValue = useI18n();\n\n const [meta, setMeta] = useState<FlowMetadataResponse | null>(initialMeta);\n const [isLoading, setIsLoading] = useState<boolean>(!initialMeta);\n const [error, setError] = useState<Error | null>(null);\n const [pendingLanguage, setPendingLanguage] = useState<string | null>(null);\n\n // Track the last fetchFlowMeta reference that was actually dispatched.\n // This prevents two classes of double-fetch:\n // 1. React StrictMode simulates unmount+remount — the re-mount fires the\n // effect again with the same fetchFlowMeta reference; without this guard\n // the else-branch would issue a redundant second network request.\n // 2. Rapid dependency changes (e.g. baseUrl stabilising) that produce two\n // effect firings before the first fetch completes.\n const lastFetchedRef: RefObject<(() => Promise<void>) | null> = useRef<(() => Promise<void>) | null>(null);\n const initialMetaConsumedRef: RefObject<boolean> = useRef(false);\n const fetchFlowMeta: () => Promise<void> = useCallback(async (): Promise<void> => {\n if (!enabled) {\n setMeta(null);\n setIsLoading(false);\n return;\n }\n\n // Defer until ThunderID finishes initializing (e.g. loading applicationId\n // from storage on refresh). Once initialized, proceed even if applicationId\n // is absent — some flows (e.g. AcceptInvite) have no applicationId by design.\n if (!isInitialized && !applicationId) {\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = fetchMeta\n ? await fetchMeta({applicationId, language: i18nContext?.currentLanguage})\n : await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n language: i18nContext?.currentLanguage,\n });\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n }, [enabled, baseUrl, endpoints, applicationId, isInitialized, i18nContext?.currentLanguage, fetchMeta]);\n\n const switchLanguage: (language: string) => Promise<void> = useCallback(\n async (language: string): Promise<void> => {\n if (!enabled) return;\n\n setIsLoading(true);\n setError(null);\n\n try {\n const result: FlowMetadataResponse = fetchMeta\n ? await fetchMeta({applicationId, language})\n : await getFlowMeta({\n baseUrl,\n url: resolveResourceEndpoint('flowMeta', {endpoints}),\n ...(applicationId ? {id: applicationId, type: FlowMetaType.App} : {}),\n language,\n });\n\n // Inject translations for the new language before switching\n if (result.i18n?.translations && i18nContext?.injectBundles) {\n const flatTranslations: Record<string, string> = {};\n Object.entries(result.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n i18nContext.injectBundles({[language]: bundle});\n }\n\n // Defer setLanguage to the next effect cycle so injectBundles state\n // is committed before I18nProvider's setLanguage checks mergedBundles.\n setPendingLanguage(language);\n setMeta(result);\n } catch (err: unknown) {\n setError(err instanceof Error ? err : new Error(String(err)));\n } finally {\n setIsLoading(false);\n }\n },\n [enabled, baseUrl, endpoints, applicationId, i18nContext, fetchMeta],\n );\n\n // After injectBundles + setPendingLanguage are batched and committed, this\n // effect fires with the updated i18nContext (mergedBundles now includes the\n // new language), so setLanguage succeeds on the first switch.\n useEffect(() => {\n if (pendingLanguage && i18nContext?.setLanguage) {\n i18nContext.setLanguage(pendingLanguage);\n setPendingLanguage(null);\n }\n }, [pendingLanguage, i18nContext?.setLanguage]);\n\n useEffect(() => {\n if (!initialMetaConsumedRef.current) {\n initialMetaConsumedRef.current = true;\n\n if (initialMeta) {\n // Seeded from SSR (or another caller) — skip the redundant first client-side fetch.\n // Later dependency changes (e.g. an explicit language switch) still fetch normally.\n lastFetchedRef.current = fetchFlowMeta;\n return;\n }\n }\n\n if (lastFetchedRef.current === fetchFlowMeta) {\n // Same reference as the last dispatch — this is a StrictMode re-mount\n // or an effect re-fire with unchanged deps. Skip to avoid a duplicate fetch.\n return;\n }\n\n lastFetchedRef.current = fetchFlowMeta;\n fetchFlowMeta();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [fetchFlowMeta]);\n\n // When meta loads with i18n translations, inject them into the i18n system.\n // Meta translations act as the base layer — prop-provided bundles still take precedence.\n useEffect(() => {\n if (!meta?.i18n?.translations || !i18nContext?.injectBundles) {\n return;\n }\n\n const metaLanguage: string = meta.i18n.language || TranslationBundleConstants.FALLBACK_LOCALE;\n\n // Flatten namespace-keyed translations to dot-path keys:\n // { \"signin\": { \"heading\": \"Sign In\" } } → { \"signin.heading\": \"Sign In\" }\n const flatTranslations: Record<string, string> = {};\n Object.entries(meta.i18n.translations).forEach(([namespace, keys]: [string, Record<string, string>]) => {\n Object.entries(keys).forEach(([key, value]: [string, string]) => {\n flatTranslations[`${namespace}.${key}`] = value;\n });\n });\n\n const bundle: I18nBundle = {translations: flatTranslations} as unknown as I18nBundle;\n\n // Inject under the meta language code and the i18n current language so\n // lookups succeed regardless of whether the system uses \"en\" or \"en-US\".\n const bundlesToInject: Record<string, I18nBundle> = {[metaLanguage]: bundle};\n if (i18nContext.currentLanguage && i18nContext.currentLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.currentLanguage] = bundle;\n }\n if (i18nContext.fallbackLanguage && i18nContext.fallbackLanguage !== metaLanguage) {\n bundlesToInject[i18nContext.fallbackLanguage] = bundle;\n }\n\n i18nContext.injectBundles(bundlesToInject);\n }, [meta?.i18n?.translations, i18nContext?.injectBundles]);\n\n const value: any = {\n error,\n fetchFlowMeta,\n isLoading,\n meta,\n switchLanguage,\n };\n\n return <FlowMetaContext.Provider value={value}>{children}</FlowMetaContext.Provider>;\n};\n\nexport default FlowMetaProvider;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveTranslationsInObject.d.ts","sourceRoot":"","sources":["../../src/utils/resolveTranslationsInObject.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,oBAAoB,EAA8B,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAC,cAAc,EAAC,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvE,KAAK,CAAC,EACN,GAAG,cAAc,CAAC,GAAG,CAAC,EACtB,aAAY,MAAM,
|
|
1
|
+
{"version":3,"file":"resolveTranslationsInObject.d.ts","sourceRoot":"","sources":["../../src/utils/resolveTranslationsInObject.ts"],"names":[],"mappings":"AAGA,OAAO,EAAC,oBAAoB,EAA8B,MAAM,oBAAoB,CAAC;AACrF,OAAO,EAAC,cAAc,EAAC,MAAM,yBAAyB,CAAC;AAEvD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,GAAI,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACvE,KAAK,CAAC,EACN,GAAG,cAAc,CAAC,GAAG,CAAC,EACtB,aAAY,MAAM,EAAwE,EAC1F,OAAO,oBAAoB,GAAG,IAAI,KACjC,CAUF,CAAC;AAEF,eAAe,2BAA2B,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resolveTranslationsInObject.js","names":["resolved: T"],"sources":["../../src/utils/resolveTranslationsInObject.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {FlowMetadataResponse, resolveFlowTemplateLiterals} from '@thunderid/browser';\nimport {UseTranslation} from '../hooks/useTranslation';\n\n/**\n * Resolves all {{ t() }} and {{ meta() }} template expressions in an object's string properties.\n * @param obj - The object to process\n * @param t - The translation function from useTranslation\n * @param properties - Array of property names to resolve (optional, defaults to common properties)\n * @param meta - Optional flow metadata for resolving meta() expressions\n * @returns A new object with resolved template strings\n */\nexport const resolveTranslationsInObject = <T extends Record<string, any>>(\n obj: T,\n t: UseTranslation['t'],\n properties: string[] = ['label', 'placeholder', 'text', 'title', 'subtitle'],\n meta?: FlowMetadataResponse | null,\n): T => {\n const resolved: T = {...obj};\n\n properties.forEach((prop: string) => {\n if (resolved[prop] && typeof resolved[prop] === 'string') {\n (resolved as any)[prop] = resolveFlowTemplateLiterals(resolved[prop], {meta, t});\n }\n });\n\n return resolved;\n};\n\nexport default resolveTranslationsInObject;\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,+BACX,KACA,GACA,aAAuB;CAAC;CAAS;CAAe;CAAQ;CAAS;
|
|
1
|
+
{"version":3,"file":"resolveTranslationsInObject.js","names":["resolved: T"],"sources":["../../src/utils/resolveTranslationsInObject.ts"],"sourcesContent":["// Copyright 2025 The ThunderID Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {FlowMetadataResponse, resolveFlowTemplateLiterals} from '@thunderid/browser';\nimport {UseTranslation} from '../hooks/useTranslation';\n\n/**\n * Resolves all {{ t() }} and {{ meta() }} template expressions in an object's string properties.\n * @param obj - The object to process\n * @param t - The translation function from useTranslation\n * @param properties - Array of property names to resolve (optional, defaults to common properties)\n * @param meta - Optional flow metadata for resolving meta() expressions\n * @returns A new object with resolved template strings\n */\nexport const resolveTranslationsInObject = <T extends Record<string, any>>(\n obj: T,\n t: UseTranslation['t'],\n properties: string[] = ['label', 'placeholder', 'text', 'title', 'subtitle', 'alt', 'src'],\n meta?: FlowMetadataResponse | null,\n): T => {\n const resolved: T = {...obj};\n\n properties.forEach((prop: string) => {\n if (resolved[prop] && typeof resolved[prop] === 'string') {\n (resolved as any)[prop] = resolveFlowTemplateLiterals(resolved[prop], {meta, t});\n }\n });\n\n return resolved;\n};\n\nexport default resolveTranslationsInObject;\n"],"mappings":";;;;;;;;;;;AAcA,MAAa,+BACX,KACA,GACA,aAAuB;CAAC;CAAS;CAAe;CAAQ;CAAS;CAAY;CAAO;CAAM,EAC1F,SACM;CACN,MAAMA,WAAc,EAAC,GAAG,KAAI;AAE5B,YAAW,SAAS,SAAiB;AACnC,MAAI,SAAS,SAAS,OAAO,SAAS,UAAU,SAC9C,CAAC,SAAiB,QAAQ,4BAA4B,SAAS,OAAO;GAAC;GAAM;GAAE,CAAC;GAElF;AAEF,QAAO"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@thunderid/react",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "React SDK for ThunderID",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"thunderid",
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
"@floating-ui/react": "0.27.12",
|
|
63
63
|
"dompurify": "3.4.13",
|
|
64
64
|
"tslib": "2.8.1",
|
|
65
|
-
"@thunderid/browser": "^1.0.
|
|
65
|
+
"@thunderid/browser": "^1.0.5"
|
|
66
66
|
},
|
|
67
67
|
"publishConfig": {
|
|
68
68
|
"access": "public"
|