@enterprisestandard/react 0.0.20-beta.20260623.1 → 0.0.20-beta.20260625.1

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/README.md ADDED
@@ -0,0 +1,91 @@
1
+ # @enterprisestandard/react
2
+
3
+ Client/browser React components and hooks for Enterprise Standard apps: sign-in UI, current-user
4
+ state, and tenant URL helpers. This package is **client-safe** — it never pulls server-only SDK code
5
+ (tenant stores, session discovery, webhooks, vault, IAM/SCIM handlers) into your bundle.
6
+
7
+ > Import server-side and shared types/utilities from `@enterprisestandard/core`, and server handler
8
+ > code from `@enterprisestandard/core/server` or `@enterprisestandard/server`. This package
9
+ > intentionally re-exports only the client-safe symbols listed below.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ bun add @enterprisestandard/react
15
+ ```
16
+
17
+ Peer dependencies: `react` and `react-dom` (`^18.0.0 || ^19.0.0`).
18
+
19
+ ## Client-safe exports
20
+
21
+ Workforce (SSO):
22
+
23
+ - `SSOProvider`
24
+ - `useSSOContext` / `useOptionalSSOContext` (non-throwing)
25
+ - `getUser`
26
+ - `logout`
27
+
28
+ Customer (CIAM):
29
+
30
+ - `CIAMProvider`
31
+ - `useCIAMContext` / `useOptionalCIAMContext` (non-throwing)
32
+ - `CIAMProviderList`, `useCIAMProviders`
33
+ - `getCustomer`
34
+ - `logoutCustomer`
35
+
36
+ Gating / shared:
37
+
38
+ - `SignedIn`, `SignedOut`, `SignInLoading` (each accepts `audience?: 'workforce' | 'customer'`, default `'workforce'`)
39
+ - `TenantProvider`, `useTenant`, `useOptionalTenant` (non-throwing), `createTenantUrls`
40
+
41
+ Re-exported client-safe types/utilities from core: `WorkforceUser`, `Customer`, `AuthenticatedUser`,
42
+ `CIAMProviderDescriptor` (+ branding/color/type), `IdTokenClaims`, `TenantRoutingStrategy`,
43
+ `TenantPathNamespace`, `Logger`, `silentLogger`, `defaultLogger`.
44
+
45
+ ## Canonical pattern
46
+
47
+ ```tsx
48
+ import { SignedIn, SignedOut, SignInLoading, SSOProvider } from '@enterprisestandard/react';
49
+
50
+ <SSOProvider userUrl="/api/es/auth/user">
51
+ <SignInLoading>Loading…</SignInLoading>
52
+ <SignedIn>
53
+ <UserDisplay />
54
+ </SignedIn>
55
+ <SignedOut>
56
+ <a href="/api/es/auth/login">Sign in</a>
57
+ </SignedOut>
58
+ </SSOProvider>;
59
+ ```
60
+
61
+ ## Auth state and errors
62
+
63
+ `useSSOContext()` / `useCIAMContext()` return `{ user|customer, setUser|setCustomer, isLoading, error }`.
64
+
65
+ `error` lets you distinguish a backend outage from a signed-out user:
66
+
67
+ - `value === null && error === null` → signed out (the server returned `401`).
68
+ - `value === null && error !== null` → the fetch failed for a non-auth reason (network/5xx). Any
69
+ previously loaded value is preserved; render a "try again" affordance rather than treating the
70
+ user as logged out.
71
+
72
+ `CIAMProviderList` accepts an `errorFallback={(error, retry) => ...}` and clears the stale list when a
73
+ refresh fails.
74
+
75
+ ## Storage
76
+
77
+ `storage` defaults to `'memory'` (no tokens are ever stored). Opt into `'local'`/`'session'` to
78
+ survive reloads; the stored record is a non-secret display subset. On load, records are validated
79
+ against an identity shape and discarded if expired. A custom `storageKey` is honored by the provider,
80
+ `getUser`/`getCustomer`, and `logout`/`logoutCustomer`.
81
+
82
+ ## Cross-origin
83
+
84
+ By default `fetch` does not send cookies cross-origin. When `userUrl`/`customerUrl` is on a different
85
+ origin, pass `fetchInit={{ credentials: 'include' }}` (keep the object stable/memoized). The same
86
+ option exists on `getUser`/`getCustomer`/`logout`/`logoutCustomer`.
87
+
88
+ ## Docs
89
+
90
+ See `site/public/llms-react.txt` for the full usage guide, and the root `README.md` for server-side
91
+ setup that backs these components.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from "@enterprisestandard/core";
2
- import { generateULID } from "@enterprisestandard/core";
3
- import { Customer, Logger } from "@enterprisestandard/core";
1
+ import { AuthenticatedUser, CIAMProviderBranding, CIAMProviderColors, CIAMProviderDescriptor as CIAMProviderDescriptor3, CIAMProviderType, Customer as Customer2, IdTokenClaims, TenantPathNamespace, TenantRoutingStrategy as TenantRoutingStrategy2, WorkforceUser as WorkforceUser2 } from "@enterprisestandard/core";
2
+ import { defaultLogger, Logger as Logger5, silentLogger } from "@enterprisestandard/core";
3
+ import { Customer, Logger as Logger2 } from "@enterprisestandard/core";
4
4
  import { ReactNode as ReactNode2 } from "react";
5
5
  type StorageType = "local" | "session" | "memory";
6
6
  interface CIAMProviderProps {
@@ -9,24 +9,38 @@ interface CIAMProviderProps {
9
9
  storageKey?: string;
10
10
  storage?: StorageType;
11
11
  disableListener?: boolean;
12
- log?: Logger;
12
+ /** Extra fetch options for `customerUrl` (e.g. `{ credentials: 'include' }` when it is cross-origin). Keep it stable/memoized. */
13
+ fetchInit?: RequestInit;
14
+ log?: Logger2;
13
15
  children: ReactNode2;
14
16
  }
15
17
  interface CIAMContext {
16
18
  customer: Customer | null;
17
19
  setCustomer: (customer: Customer | null) => void;
18
20
  isLoading: boolean;
21
+ /** Set when the last customer fetch failed for a non-auth reason (network/5xx). Null on success or 401 (signed out). */
22
+ error: Error | null;
19
23
  }
20
- declare function CIAMProvider({ storageKey: storageKeyProp, customerUrl, storage, disableListener, log, children }: CIAMProviderProps): React.ReactNode2;
24
+ declare function CIAMProvider({ storageKey: storageKeyProp, customerUrl, storage, disableListener, fetchInit, log, children }: CIAMProviderProps): React.ReactNode2;
21
25
  declare function useCIAMContext(): CIAMContext;
22
26
  /** Non-throwing variant; returns undefined when no CIAMProvider is present. */
23
27
  declare function useOptionalCIAMContext(): CIAMContext | undefined;
24
- declare function getCustomer(customerUrl: string, storageKey?: string): Promise<Customer | undefined>;
25
- declare function logoutCustomer(logoutUrl: string): Promise<{
28
+ declare function getCustomer(customerUrl: string, options?: {
29
+ storageKey?: string;
30
+ storage?: StorageType;
31
+ fetchInit?: RequestInit;
32
+ log?: Logger2;
33
+ }): Promise<Customer | undefined>;
34
+ declare function logoutCustomer(logoutUrl: string, options?: {
35
+ navigate?: boolean;
36
+ storageKey?: string;
37
+ fetchInit?: RequestInit;
38
+ }): Promise<{
26
39
  success: boolean;
40
+ redirect?: string;
27
41
  error?: string;
28
42
  }>;
29
- import { CIAMProviderDescriptor, Logger as Logger2 } from "@enterprisestandard/core";
43
+ import { CIAMProviderDescriptor, Logger as Logger3 } from "@enterprisestandard/core";
30
44
  import { ReactNode as ReactNode3 } from "react";
31
45
  type UseCIAMProvidersResult = {
32
46
  /** Providers in the recommended display order returned by the discovery endpoint. */
@@ -42,7 +56,7 @@ type UseCIAMProvidersResult = {
42
56
  * returned in the recommended presentation order declared in RemoteConfig; do
43
57
  * not re-sort it.
44
58
  */
45
- declare function useCIAMProviders(providersUrl: string, log?: Logger2): UseCIAMProvidersResult;
59
+ declare function useCIAMProviders(providersUrl: string, log?: Logger3): UseCIAMProvidersResult;
46
60
  type CIAMProviderListProps = {
47
61
  /** Discovery endpoint URL (`${basePath}/auth/customer/providers`). */
48
62
  providersUrl: string;
@@ -59,10 +73,15 @@ type CIAMProviderListProps = {
59
73
  loadingFallback?: ReactNode3;
60
74
  /** Rendered when no providers are available. */
61
75
  emptyFallback?: ReactNode3;
76
+ /**
77
+ * Rendered when the provider list could not be fetched (network/5xx). Receives the error and a
78
+ * `retry` callback. When omitted, the list falls back to `emptyFallback` on error.
79
+ */
80
+ errorFallback?: (error: Error, retry: () => void) => ReactNode3;
62
81
  /** Label for the "show more" button. */
63
82
  moreLabel?: string;
64
83
  className?: string;
65
- log?: Logger2;
84
+ log?: Logger3;
66
85
  };
67
86
  /**
68
87
  * Renders the CIAM customer-login providers as branded buttons in the
@@ -70,7 +89,7 @@ type CIAMProviderListProps = {
70
89
  * (mirrors the SSO `<a href={LOGIN_URL}>` pattern). Supports an optional
71
90
  * `limit` (first N + "show more") that preserves order by default.
72
91
  */
73
- declare function CIAMProviderList({ providersUrl, limit, theme, renderProvider, loadingFallback, emptyFallback, moreLabel, className, log }: CIAMProviderListProps): React.ReactNode3;
92
+ declare function CIAMProviderList({ providersUrl, limit, theme, renderProvider, loadingFallback, emptyFallback, errorFallback, moreLabel, className, log }: CIAMProviderListProps): React.ReactNode3;
74
93
  import { PropsWithChildren } from "react";
75
94
  type Audience = "workforce" | "customer";
76
95
  declare function SignInLoading({ complete, audience, children }: {
@@ -87,30 +106,43 @@ type Audience3 = "workforce" | "customer";
87
106
  declare function SignedOut({ audience, children }: {
88
107
  audience?: Audience3;
89
108
  } & PropsWithChildren3): React.ReactNode;
90
- import { Logger as Logger3, WorkforceUser } from "@enterprisestandard/core";
109
+ import { Logger as Logger4, WorkforceUser } from "@enterprisestandard/core";
91
110
  import { ReactNode as ReactNode4 } from "react";
92
- type StorageType2 = "local" | "session" | "memory";
93
111
  interface SSOProviderProps {
94
112
  userUrl: string;
95
113
  /** Optional storage key. When provided, this key is used for localStorage/sessionStorage; otherwise `'es.sso.user'` is used. */
96
114
  storageKey?: string;
97
- storage?: StorageType2;
115
+ storage?: StorageType;
98
116
  disableListener?: boolean;
99
- log?: Logger3;
117
+ /** Extra fetch options for `userUrl` (e.g. `{ credentials: 'include' }` when it is cross-origin). Keep it stable/memoized. */
118
+ fetchInit?: RequestInit;
119
+ log?: Logger4;
100
120
  children: ReactNode4;
101
121
  }
102
122
  interface SSOContext {
103
123
  user: WorkforceUser | null;
104
124
  setUser: (user: WorkforceUser | null) => void;
105
125
  isLoading: boolean;
126
+ /** Set when the last user fetch failed for a non-auth reason (network/5xx). Null on success or 401 (signed out). */
127
+ error: Error | null;
106
128
  }
107
- declare function SSOProvider({ storageKey: storageKeyProp, userUrl, storage, disableListener, log, children }: SSOProviderProps): React.ReactNode4;
129
+ declare function SSOProvider({ storageKey: storageKeyProp, userUrl, storage, disableListener, fetchInit, log, children }: SSOProviderProps): React.ReactNode4;
108
130
  declare function useSSOContext(): SSOContext;
109
131
  /** Non-throwing variant; returns undefined when no SSOProvider is present. */
110
132
  declare function useOptionalSSOContext(): SSOContext | undefined;
111
- declare function getUser(userUrl: string, storageKey?: string): Promise<WorkforceUser | undefined>;
112
- declare function logout(logoutUrl: string): Promise<{
133
+ declare function getUser(userUrl: string, options?: {
134
+ storageKey?: string;
135
+ storage?: StorageType;
136
+ fetchInit?: RequestInit;
137
+ log?: Logger4;
138
+ }): Promise<WorkforceUser | undefined>;
139
+ declare function logout(logoutUrl: string, options?: {
140
+ navigate?: boolean;
141
+ storageKey?: string;
142
+ fetchInit?: RequestInit;
143
+ }): Promise<{
113
144
  success: boolean;
145
+ redirect?: string;
114
146
  error?: string;
115
147
  }>;
116
148
  import { TenantRoutingStrategy } from "@enterprisestandard/core";
@@ -131,20 +163,6 @@ type TenantProviderProps = {
131
163
  declare function createTenantUrls(id: string, strategy?: TenantRoutingStrategy): TenantUrls;
132
164
  declare function TenantProvider({ id, strategy, children }: TenantProviderProps): React.ReactNode;
133
165
  declare function useTenant(): TenantContextValue;
134
- import { TenantDirectoryResponse, TenantResponse } from "@enterprisestandard/core";
135
- type UseTenantDirectoryOptions = {
136
- tenantManagerBaseUrl?: string;
137
- start?: number;
138
- limit?: number;
139
- order?: string;
140
- };
141
- declare function useTenantDirectory<TTenant extends TenantResponse = TenantResponse>(options?: UseTenantDirectoryOptions): {
142
- directory: TenantDirectoryResponse<TTenant> | null;
143
- tenants: Record<string, TTenant>;
144
- ids: string[];
145
- items: TTenant[];
146
- isLoading: boolean;
147
- error: Error | null;
148
- refresh: () => Promise<void>;
149
- };
150
- export { useTenantDirectory, useTenant, useSSOContext, useOptionalSSOContext, useOptionalCIAMContext, useCIAMProviders, useCIAMContext, logoutCustomer, logout, getUser, getCustomer, generateULID, createTenantUrls, UseTenantDirectoryOptions, UseCIAMProvidersResult, TenantProvider, SignedOut, SignedIn, SignInLoading, SSOProvider, CIAMProviderListProps, CIAMProviderList, CIAMProvider };
166
+ /** Non-throwing variant; returns undefined when no TenantProvider is present (parity with `useOptionalSSOContext`). */
167
+ declare function useOptionalTenant(): TenantContextValue | undefined;
168
+ export { useTenant, useSSOContext, useOptionalTenant, useOptionalSSOContext, useOptionalCIAMContext, useCIAMProviders, useCIAMContext, silentLogger, logoutCustomer, logout, getUser, getCustomer, defaultLogger, createTenantUrls, WorkforceUser2 as WorkforceUser, UseCIAMProvidersResult, TenantUrls, TenantRoutingStrategy2 as TenantRoutingStrategy, TenantProviderProps, TenantProvider, TenantPathNamespace, TenantContextValue, SignedOut, SignedIn, SignInLoading, SSOProvider, Logger5 as Logger, IdTokenClaims, Customer2 as Customer, CIAMProviderType, CIAMProviderListProps, CIAMProviderList, CIAMProviderDescriptor3 as CIAMProviderDescriptor, CIAMProviderColors, CIAMProviderBranding, CIAMProvider, AuthenticatedUser };
package/dist/index.js CHANGED
@@ -1,712 +1 @@
1
- // packages/react/src/index.ts
2
- export * from "@enterprisestandard/core";
3
- import { generateULID } from "@enterprisestandard/core";
4
-
5
- // packages/react/src/ciam-provider.tsx
6
- import { silentLogger } from "@enterprisestandard/core";
7
- import { createContext, useCallback, useContext, useEffect, useState } from "react";
8
- import { jsxDEV } from "react/jsx-dev-runtime";
9
- var CTX = createContext(undefined);
10
- var VALID_STORAGE_KEY = /^[a-zA-Z0-9._-]{1,1024}$/;
11
- function validateStorageKey(key) {
12
- if (!key.trim()) {
13
- throw new Error("CIAMProvider storageKey must be a non-empty string.");
14
- }
15
- if (!VALID_STORAGE_KEY.test(key)) {
16
- throw new Error("CIAMProvider storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.");
17
- }
18
- }
19
- var getStorageKey = (storageKey) => {
20
- if (storageKey === undefined || storageKey === "") {
21
- return "es.ciam.customer";
22
- }
23
- validateStorageKey(storageKey);
24
- return storageKey;
25
- };
26
- function loadCustomerFromStorageForKey(storage, storageKey, log = silentLogger) {
27
- if (typeof window === "undefined")
28
- return null;
29
- try {
30
- const storageObject = storage === "local" ? localStorage : sessionStorage;
31
- const customerData = storageObject.getItem(storageKey);
32
- if (!customerData)
33
- return null;
34
- const parsedCustomer = JSON.parse(customerData);
35
- if (parsedCustomer.ciam?.expires) {
36
- parsedCustomer.ciam.expires = new Date(parsedCustomer.ciam.expires);
37
- }
38
- return parsedCustomer;
39
- } catch (error) {
40
- log.error("Error loading customer from storage:", error);
41
- return null;
42
- }
43
- }
44
- async function fetchCustomerFromServer(customerUrl, log = silentLogger) {
45
- try {
46
- const response = await fetch(customerUrl);
47
- if (response.status === 401) {
48
- return null;
49
- }
50
- if (!response.ok) {
51
- throw new Error(`Failed to fetch customer: ${response.status} ${response.statusText}`);
52
- }
53
- const customerData = await response.json();
54
- if (customerData.ciam?.expires && typeof customerData.ciam.expires === "string") {
55
- customerData.ciam.expires = new Date(customerData.ciam.expires);
56
- }
57
- return customerData;
58
- } catch (error) {
59
- log.error("Error fetching customer from URL:", error);
60
- return null;
61
- }
62
- }
63
- function CIAMProvider({
64
- storageKey: storageKeyProp,
65
- customerUrl,
66
- storage = "memory",
67
- disableListener = false,
68
- log = silentLogger,
69
- children
70
- }) {
71
- const [customer, setCustomerState] = useState(null);
72
- const [isLoading, setIsLoading] = useState(!!customerUrl);
73
- const storageKey = getStorageKey(storageKeyProp);
74
- const loadCustomerFromStorage = useCallback(() => {
75
- if (storage === "memory")
76
- return null;
77
- return loadCustomerFromStorageForKey(storage, storageKey, log);
78
- }, [storage, storageKey, log]);
79
- const saveCustomerToStorage = useCallback((customer2) => {
80
- if (storage === "memory" || typeof window === "undefined")
81
- return;
82
- try {
83
- const storageObject = storage === "local" ? localStorage : sessionStorage;
84
- if (customer2 === null) {
85
- storageObject.removeItem(storageKey);
86
- } else {
87
- storageObject.setItem(storageKey, JSON.stringify(customer2));
88
- }
89
- } catch (error) {
90
- log.error("Error saving customer to storage:", error);
91
- }
92
- }, [storage, storageKey, log]);
93
- const setCustomer = useCallback((newCustomer) => {
94
- setCustomerState(newCustomer);
95
- saveCustomerToStorage(newCustomer);
96
- }, [saveCustomerToStorage]);
97
- const fetchCustomerFromUrl = useCallback(async () => {
98
- if (!customerUrl)
99
- return;
100
- setIsLoading(true);
101
- try {
102
- const customerData = await fetchCustomerFromServer(customerUrl, log);
103
- setCustomerState(customerData);
104
- saveCustomerToStorage(customerData);
105
- } finally {
106
- setIsLoading(false);
107
- }
108
- }, [customerUrl, saveCustomerToStorage, log]);
109
- useEffect(() => {
110
- const storedCustomer = loadCustomerFromStorage();
111
- if (storedCustomer) {
112
- setCustomerState(storedCustomer);
113
- }
114
- if (customerUrl) {
115
- fetchCustomerFromUrl();
116
- } else {
117
- setIsLoading(false);
118
- }
119
- }, [loadCustomerFromStorage, customerUrl, fetchCustomerFromUrl]);
120
- useEffect(() => {
121
- if (disableListener || storage === "memory")
122
- return;
123
- const handleStorageChange = (event) => {
124
- if (event.key !== storageKey)
125
- return;
126
- if (event.newValue === null) {
127
- setCustomerState(null);
128
- } else {
129
- try {
130
- const parsedCustomer = JSON.parse(event.newValue);
131
- if (parsedCustomer.ciam?.expires) {
132
- parsedCustomer.ciam.expires = new Date(parsedCustomer.ciam.expires);
133
- }
134
- setCustomerState(parsedCustomer);
135
- } catch (error) {
136
- log.error("Error parsing customer from storage event:", error);
137
- }
138
- }
139
- };
140
- const handleLogout = () => {
141
- setCustomerState(null);
142
- };
143
- window.addEventListener("storage", handleStorageChange);
144
- window.addEventListener("es-ciam-logout", handleLogout);
145
- return () => {
146
- window.removeEventListener("storage", handleStorageChange);
147
- window.removeEventListener("es-ciam-logout", handleLogout);
148
- };
149
- }, [disableListener, storage, storageKey, log]);
150
- const contextValue = {
151
- customer,
152
- setCustomer,
153
- isLoading
154
- };
155
- return /* @__PURE__ */ jsxDEV(CTX.Provider, {
156
- value: contextValue,
157
- children
158
- }, undefined, false, undefined, this);
159
- }
160
- function useCIAMContext() {
161
- const context = useContext(CTX);
162
- if (context === undefined) {
163
- throw new Error("useCIAMContext must be used within a CIAMProvider");
164
- }
165
- return context;
166
- }
167
- function useOptionalCIAMContext() {
168
- return useContext(CTX);
169
- }
170
- async function getCustomer(customerUrl, storageKey) {
171
- const key = getStorageKey(storageKey);
172
- const fromLocal = loadCustomerFromStorageForKey("local", key);
173
- if (fromLocal)
174
- return fromLocal;
175
- const fromSession = loadCustomerFromStorageForKey("session", key);
176
- if (fromSession)
177
- return fromSession;
178
- if (customerUrl) {
179
- const fromServer = await fetchCustomerFromServer(customerUrl);
180
- if (fromServer)
181
- return fromServer;
182
- }
183
- return;
184
- }
185
- async function logoutCustomer(logoutUrl) {
186
- try {
187
- const response = await fetch(logoutUrl, {
188
- headers: { Accept: "application/json" }
189
- });
190
- if (!response.ok) {
191
- return { success: false, error: `HTTP ${response.status}` };
192
- }
193
- const data = await response.json();
194
- if (!data.success) {
195
- return { success: false, error: data.message || "Logout failed" };
196
- }
197
- if (typeof window !== "undefined") {
198
- localStorage.removeItem("es.ciam.customer");
199
- sessionStorage.removeItem("es.ciam.customer");
200
- window.dispatchEvent(new CustomEvent("es-ciam-logout"));
201
- }
202
- return { success: true };
203
- } catch (error) {
204
- return {
205
- success: false,
206
- error: error instanceof Error ? error.message : "Network error"
207
- };
208
- }
209
- }
210
- // packages/react/src/ciam-providers.tsx
211
- import { silentLogger as silentLogger2 } from "@enterprisestandard/core";
212
- import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
213
- import { jsxDEV as jsxDEV2 } from "react/jsx-dev-runtime";
214
- function useCIAMProviders(providersUrl, log = silentLogger2) {
215
- const [providers, setProviders] = useState2([]);
216
- const [isLoading, setIsLoading] = useState2(Boolean(providersUrl));
217
- const [error, setError] = useState2(null);
218
- const refresh = useCallback2(() => {
219
- if (!providersUrl)
220
- return;
221
- let cancelled = false;
222
- setIsLoading(true);
223
- setError(null);
224
- fetch(providersUrl, { headers: { Accept: "application/json" } }).then(async (response) => {
225
- if (!response.ok) {
226
- throw new Error(`Failed to fetch CIAM providers: ${response.status} ${response.statusText}`);
227
- }
228
- const data = await response.json();
229
- if (!cancelled)
230
- setProviders(Array.isArray(data.providers) ? data.providers : []);
231
- }).catch((err) => {
232
- const normalized = err instanceof Error ? err : new Error(String(err));
233
- log.error("Error fetching CIAM providers:", normalized);
234
- if (!cancelled)
235
- setError(normalized);
236
- }).finally(() => {
237
- if (!cancelled)
238
- setIsLoading(false);
239
- });
240
- return () => {
241
- cancelled = true;
242
- };
243
- }, [providersUrl, log]);
244
- useEffect2(() => {
245
- const cleanup = refresh();
246
- return cleanup;
247
- }, [refresh]);
248
- return { providers, isLoading, error, refresh };
249
- }
250
- function providerButtonStyle(provider, theme) {
251
- const background = theme === "dark" ? provider.colors?.dark : provider.colors?.light;
252
- return {
253
- display: "flex",
254
- alignItems: "center",
255
- gap: "0.5rem",
256
- width: "100%",
257
- padding: "0.625rem 0.875rem",
258
- border: "1px solid rgba(0,0,0,0.15)",
259
- borderRadius: "0.5rem",
260
- background: background ?? (theme === "dark" ? "#1f1f1f" : "#ffffff"),
261
- color: theme === "dark" ? "#ffffff" : "#111111",
262
- textDecoration: "none",
263
- font: "inherit",
264
- cursor: "pointer"
265
- };
266
- }
267
- function CIAMProviderList({
268
- providersUrl,
269
- limit,
270
- theme = "light",
271
- renderProvider,
272
- loadingFallback,
273
- emptyFallback,
274
- moreLabel = "Show more",
275
- className,
276
- log = silentLogger2
277
- }) {
278
- const { providers, isLoading } = useCIAMProviders(providersUrl, log);
279
- const [expanded, setExpanded] = useState2(false);
280
- if (isLoading) {
281
- return loadingFallback ?? null;
282
- }
283
- if (providers.length === 0) {
284
- return emptyFallback ?? null;
285
- }
286
- const visible = !expanded && typeof limit === "number" && limit > 0 ? providers.slice(0, limit) : providers;
287
- const hasMore = visible.length < providers.length;
288
- return /* @__PURE__ */ jsxDEV2("div", {
289
- className,
290
- style: { display: "flex", flexDirection: "column", gap: "0.5rem" },
291
- "data-es-ciam-providers": "",
292
- children: [
293
- visible.map((provider) => renderProvider ? /* @__PURE__ */ jsxDEV2("span", {
294
- children: renderProvider(provider)
295
- }, provider.name, false, undefined, this) : /* @__PURE__ */ jsxDEV2("a", {
296
- href: provider.loginUrl,
297
- style: providerButtonStyle(provider, theme),
298
- "data-es-ciam-provider": provider.name,
299
- children: [
300
- provider.iconUrl ? /* @__PURE__ */ jsxDEV2("img", {
301
- src: provider.iconUrl,
302
- alt: "",
303
- width: 20,
304
- height: 20,
305
- "aria-hidden": "true"
306
- }, undefined, false, undefined, this) : null,
307
- /* @__PURE__ */ jsxDEV2("span", {
308
- children: [
309
- "Continue with ",
310
- provider.displayName
311
- ]
312
- }, undefined, true, undefined, this)
313
- ]
314
- }, provider.name, true, undefined, this)),
315
- hasMore ? /* @__PURE__ */ jsxDEV2("button", {
316
- type: "button",
317
- onClick: () => setExpanded(true),
318
- "data-es-ciam-show-more": "",
319
- children: moreLabel
320
- }, undefined, false, undefined, this) : null
321
- ]
322
- }, undefined, true, undefined, this);
323
- }
324
- // packages/react/src/sso-provider.tsx
325
- import { silentLogger as silentLogger3 } from "@enterprisestandard/core";
326
- import { createContext as createContext2, useCallback as useCallback3, useContext as useContext2, useEffect as useEffect3, useState as useState3 } from "react";
327
- import { jsxDEV as jsxDEV3 } from "react/jsx-dev-runtime";
328
- var CTX2 = createContext2(undefined);
329
- var VALID_STORAGE_KEY2 = /^[a-zA-Z0-9._-]{1,1024}$/;
330
- function validateStorageKey2(key) {
331
- if (!key.trim()) {
332
- throw new Error("SSOProvider storageKey must be a non-empty string.");
333
- }
334
- if (!VALID_STORAGE_KEY2.test(key)) {
335
- throw new Error("SSOProvider storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.");
336
- }
337
- }
338
- var getStorageKey2 = (storageKey) => {
339
- if (storageKey === undefined || storageKey === "") {
340
- return "es.sso.user";
341
- }
342
- validateStorageKey2(storageKey);
343
- return storageKey;
344
- };
345
- function loadUserFromStorageForKey(storage, storageKey, log = silentLogger3) {
346
- if (typeof window === "undefined")
347
- return null;
348
- try {
349
- const storageObject = storage === "local" ? localStorage : sessionStorage;
350
- const userData = storageObject.getItem(storageKey);
351
- if (!userData)
352
- return null;
353
- const parsedUser = JSON.parse(userData);
354
- if (parsedUser.sso?.expires) {
355
- parsedUser.sso.expires = new Date(parsedUser.sso.expires);
356
- }
357
- return parsedUser;
358
- } catch (error) {
359
- log.error("Error loading user from storage:", error);
360
- return null;
361
- }
362
- }
363
- async function fetchUserFromServer(userUrl, log = silentLogger3) {
364
- try {
365
- const response = await fetch(userUrl);
366
- if (response.status === 401) {
367
- return null;
368
- }
369
- if (!response.ok) {
370
- throw new Error(`Failed to fetch user: ${response.status} ${response.statusText}`);
371
- }
372
- const userData = await response.json();
373
- if (userData.sso?.expires && typeof userData.sso.expires === "string") {
374
- userData.sso.expires = new Date(userData.sso.expires);
375
- }
376
- return userData;
377
- } catch (error) {
378
- log.error("Error fetching user from URL:", error);
379
- return null;
380
- }
381
- }
382
- function SSOProvider({
383
- storageKey: storageKeyProp,
384
- userUrl,
385
- storage = "memory",
386
- disableListener = false,
387
- log = silentLogger3,
388
- children
389
- }) {
390
- const [user, setUserState] = useState3(null);
391
- const [isLoading, setIsLoading] = useState3(!!userUrl);
392
- const storageKey = getStorageKey2(storageKeyProp);
393
- const loadUserFromStorage = useCallback3(() => {
394
- if (storage === "memory")
395
- return null;
396
- return loadUserFromStorageForKey(storage, storageKey, log);
397
- }, [storage, storageKey, log]);
398
- const saveUserToStorage = useCallback3((user2) => {
399
- if (storage === "memory" || typeof window === "undefined")
400
- return;
401
- try {
402
- const storageObject = storage === "local" ? localStorage : sessionStorage;
403
- if (user2 === null) {
404
- storageObject.removeItem(storageKey);
405
- } else {
406
- storageObject.setItem(storageKey, JSON.stringify(user2));
407
- }
408
- } catch (error) {
409
- log.error("Error saving user to storage:", error);
410
- }
411
- }, [storage, storageKey, log]);
412
- const setUser = useCallback3((newUser) => {
413
- setUserState(newUser);
414
- saveUserToStorage(newUser);
415
- }, [saveUserToStorage]);
416
- const fetchUserFromUrl = useCallback3(async () => {
417
- if (!userUrl)
418
- return;
419
- setIsLoading(true);
420
- try {
421
- const userData = await fetchUserFromServer(userUrl, log);
422
- setUserState(userData);
423
- saveUserToStorage(userData);
424
- } finally {
425
- setIsLoading(false);
426
- }
427
- }, [userUrl, saveUserToStorage, log]);
428
- useEffect3(() => {
429
- const storedUser = loadUserFromStorage();
430
- if (storedUser) {
431
- setUserState(storedUser);
432
- }
433
- if (userUrl) {
434
- fetchUserFromUrl();
435
- } else {
436
- setIsLoading(false);
437
- }
438
- }, [loadUserFromStorage, userUrl, fetchUserFromUrl]);
439
- useEffect3(() => {
440
- if (disableListener || storage === "memory")
441
- return;
442
- const handleStorageChange = (event) => {
443
- if (event.key !== storageKey)
444
- return;
445
- if (event.newValue === null) {
446
- setUserState(null);
447
- } else {
448
- try {
449
- const parsedUser = JSON.parse(event.newValue);
450
- if (parsedUser.sso?.expires) {
451
- parsedUser.sso.expires = new Date(parsedUser.sso.expires);
452
- }
453
- setUserState(parsedUser);
454
- } catch (error) {
455
- log.error("Error parsing user from storage event:", error);
456
- }
457
- }
458
- };
459
- const handleLogout = () => {
460
- setUserState(null);
461
- };
462
- window.addEventListener("storage", handleStorageChange);
463
- window.addEventListener("es-sso-logout", handleLogout);
464
- return () => {
465
- window.removeEventListener("storage", handleStorageChange);
466
- window.removeEventListener("es-sso-logout", handleLogout);
467
- };
468
- }, [disableListener, storage, storageKey, log]);
469
- const contextValue = {
470
- user,
471
- setUser,
472
- isLoading
473
- };
474
- return /* @__PURE__ */ jsxDEV3(CTX2.Provider, {
475
- value: contextValue,
476
- children
477
- }, undefined, false, undefined, this);
478
- }
479
- function useSSOContext() {
480
- const context = useContext2(CTX2);
481
- if (context === undefined) {
482
- throw new Error("useSSOContext must be used within a SSOProvider");
483
- }
484
- return context;
485
- }
486
- function useOptionalSSOContext() {
487
- return useContext2(CTX2);
488
- }
489
- async function getUser(userUrl, storageKey) {
490
- const key = getStorageKey2(storageKey);
491
- const fromLocal = loadUserFromStorageForKey("local", key);
492
- if (fromLocal)
493
- return fromLocal;
494
- const fromSession = loadUserFromStorageForKey("session", key);
495
- if (fromSession)
496
- return fromSession;
497
- if (userUrl) {
498
- const fromServer = await fetchUserFromServer(userUrl);
499
- if (fromServer)
500
- return fromServer;
501
- }
502
- return;
503
- }
504
- async function logout(logoutUrl) {
505
- try {
506
- const response = await fetch(logoutUrl, {
507
- headers: { Accept: "application/json" }
508
- });
509
- if (!response.ok) {
510
- return { success: false, error: `HTTP ${response.status}` };
511
- }
512
- const data = await response.json();
513
- if (!data.success) {
514
- return { success: false, error: data.message || "Logout failed" };
515
- }
516
- if (typeof window !== "undefined") {
517
- localStorage.removeItem("es.sso.user");
518
- sessionStorage.removeItem("es.sso.user");
519
- window.dispatchEvent(new CustomEvent("es-sso-logout"));
520
- }
521
- return { success: true };
522
- } catch (error) {
523
- return {
524
- success: false,
525
- error: error instanceof Error ? error.message : "Network error"
526
- };
527
- }
528
- }
529
-
530
- // packages/react/src/sign-in-loading.tsx
531
- import { jsxDEV as jsxDEV4, Fragment } from "react/jsx-dev-runtime";
532
- function SignInLoading({
533
- complete = false,
534
- audience = "workforce",
535
- children
536
- }) {
537
- const sso = useOptionalSSOContext();
538
- const ciam = useOptionalCIAMContext();
539
- const isLoading = audience === "customer" ? !!ciam?.isLoading : !!sso?.isLoading;
540
- if (isLoading && !complete)
541
- return /* @__PURE__ */ jsxDEV4(Fragment, {
542
- children
543
- }, undefined, false, undefined, this);
544
- return null;
545
- }
546
- // packages/react/src/signed-in.tsx
547
- import { jsxDEV as jsxDEV5, Fragment as Fragment2 } from "react/jsx-dev-runtime";
548
- function SignedIn({
549
- audience = "workforce",
550
- children
551
- }) {
552
- const sso = useOptionalSSOContext();
553
- const ciam = useOptionalCIAMContext();
554
- const present = audience === "customer" ? !!ciam?.customer : !!sso?.user;
555
- if (present)
556
- return /* @__PURE__ */ jsxDEV5(Fragment2, {
557
- children
558
- }, undefined, false, undefined, this);
559
- return null;
560
- }
561
- // packages/react/src/signed-out.tsx
562
- import { jsxDEV as jsxDEV6, Fragment as Fragment3 } from "react/jsx-dev-runtime";
563
- function SignedOut({
564
- audience = "workforce",
565
- children
566
- }) {
567
- const sso = useOptionalSSOContext();
568
- const ciam = useOptionalCIAMContext();
569
- const present = audience === "customer" ? !!ciam?.customer : !!sso?.user;
570
- const isLoading = audience === "customer" ? !!ciam?.isLoading : !!sso?.isLoading;
571
- if (present || isLoading)
572
- return null;
573
- return /* @__PURE__ */ jsxDEV6(Fragment3, {
574
- children
575
- }, undefined, false, undefined, this);
576
- }
577
- // packages/react/src/tenant-context.tsx
578
- import {
579
- buildTenantPath,
580
- DEFAULT_TENANT_API_NAMESPACE,
581
- DEFAULT_TENANT_UI_NAMESPACE,
582
- normalizeTenantRoutingStrategy
583
- } from "@enterprisestandard/core";
584
- import { createContext as createContext3, useContext as useContext3, useMemo } from "react";
585
- import { jsxDEV as jsxDEV7 } from "react/jsx-dev-runtime";
586
- var TenantContext = createContext3(null);
587
- function createTenantUrls(id, strategy) {
588
- const normalized = normalizeTenantRoutingStrategy(strategy);
589
- if (normalized.type === "jwt") {
590
- const uiSegments = normalized.ui?.segments ?? [];
591
- const apiSegments = normalized.api?.segments ?? ["api"];
592
- return {
593
- ui: (restPath = "/") => buildStaticPath(uiSegments, restPath),
594
- api: (restPath = "/") => buildStaticPath(apiSegments, restPath)
595
- };
596
- }
597
- const uiNamespace = normalized.ui ?? DEFAULT_TENANT_UI_NAMESPACE;
598
- const apiNamespace = normalized.api ?? DEFAULT_TENANT_API_NAMESPACE;
599
- return {
600
- ui: (restPath = "/") => buildTenantPath(id, restPath, uiNamespace),
601
- api: (restPath = "/") => buildTenantPath(id, restPath, apiNamespace)
602
- };
603
- }
604
- function buildStaticPath(prefixSegments, restPath = "/") {
605
- const prefix = prefixSegments.join("/");
606
- const rest = restPath.trim().replace(/^\/+/, "");
607
- if (!prefix && !rest)
608
- return "/";
609
- if (!prefix)
610
- return `/${rest}`;
611
- if (!rest)
612
- return `/${prefix}`;
613
- return `/${prefix}/${rest}`;
614
- }
615
- function TenantProvider({ id, strategy, children }) {
616
- const value = useMemo(() => {
617
- const normalized = normalizeTenantRoutingStrategy(strategy);
618
- return {
619
- id,
620
- strategy: normalized,
621
- urls: createTenantUrls(id, normalized)
622
- };
623
- }, [strategy, id]);
624
- return /* @__PURE__ */ jsxDEV7(TenantContext.Provider, {
625
- value,
626
- children
627
- }, undefined, false, undefined, this);
628
- }
629
- function useTenant() {
630
- const value = useContext3(TenantContext);
631
- if (!value) {
632
- throw new Error("useTenant() must be used within a TenantProvider");
633
- }
634
- return value;
635
- }
636
- // packages/react/src/use-tenant-directory.ts
637
- import { useCallback as useCallback4, useEffect as useEffect4, useMemo as useMemo2, useState as useState4 } from "react";
638
- function useTenantDirectory(options = {}) {
639
- const tenantManagerBaseUrl = options.tenantManagerBaseUrl ?? "/api/tenants/mine";
640
- const [directory, setDirectory] = useState4(null);
641
- const [isLoading, setIsLoading] = useState4(true);
642
- const [error, setError] = useState4(null);
643
- const requestUrl = useMemo2(() => {
644
- const url = new URL(tenantManagerBaseUrl, "http://localhost");
645
- if (options.start != null) {
646
- url.searchParams.set("start", String(options.start));
647
- }
648
- if (options.limit != null) {
649
- url.searchParams.set("limit", String(options.limit));
650
- }
651
- if (options.order) {
652
- url.searchParams.set("order", options.order);
653
- }
654
- return `${url.pathname}${url.search}`;
655
- }, [options.limit, options.order, options.start, tenantManagerBaseUrl]);
656
- const refresh = useCallback4(async () => {
657
- setIsLoading(true);
658
- setError(null);
659
- try {
660
- const response = await fetch(requestUrl);
661
- if (!response.ok) {
662
- setDirectory(null);
663
- setError(new Error(`Tenant directory request failed with status ${response.status}`));
664
- return;
665
- }
666
- const data = await response.json();
667
- setDirectory(data);
668
- } catch (cause) {
669
- setDirectory(null);
670
- setError(cause instanceof Error ? cause : new Error("Failed to load tenant directory"));
671
- } finally {
672
- setIsLoading(false);
673
- }
674
- }, [requestUrl]);
675
- useEffect4(() => {
676
- refresh();
677
- }, [refresh]);
678
- const tenants = directory?.tenants ?? {};
679
- const ids = directory?.ids ?? [];
680
- const items = ids.map((id) => tenants[id]).filter((tenant) => !!tenant);
681
- return {
682
- directory,
683
- tenants,
684
- ids,
685
- items,
686
- isLoading,
687
- error,
688
- refresh
689
- };
690
- }
691
- export {
692
- useTenantDirectory,
693
- useTenant,
694
- useSSOContext,
695
- useOptionalSSOContext,
696
- useOptionalCIAMContext,
697
- useCIAMProviders,
698
- useCIAMContext,
699
- logoutCustomer,
700
- logout,
701
- getUser,
702
- getCustomer,
703
- generateULID,
704
- createTenantUrls,
705
- TenantProvider,
706
- SignedOut,
707
- SignedIn,
708
- SignInLoading,
709
- SSOProvider,
710
- CIAMProviderList,
711
- CIAMProvider
712
- };
1
+ import{defaultLogger as RM,silentLogger as FM}from"@enterprisestandard/core";import{silentLogger as $W}from"@enterprisestandard/core";import{createContext as OW,useContext as a,useMemo as BW}from"react";import{silentLogger as S}from"@enterprisestandard/core";import{useCallback as V,useEffect as g,useRef as i,useState as U}from"react";var QW=/^[a-zA-Z0-9._-]{1,1024}$/;function ZW(W,M){if(!W.trim())throw Error(`${M} storageKey must be a non-empty string.`);if(!QW.test(W))throw Error(`${M} storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.`)}function G(W,M,C){if(M===void 0||M==="")return W;return ZW(M,C),M}function d(W,M,C,H=S){if(typeof window>"u")return null;try{let q=W==="local"?localStorage:sessionStorage,J=q.getItem(M);if(!J)return null;let Q=JSON.parse(J),Z=C.validate(Q);if(!Z)return q.removeItem(M),null;let $=C.revive(Z);if(C.isExpired($))return q.removeItem(M),null;return $}catch(q){return H.error("Error loading auth record from storage:",q),null}}async function l(W,M,C,H=S){try{let q=await fetch(W,C);if(q.status===401)return{kind:"unauthenticated"};if(!q.ok){let Z=Error(`Failed to fetch auth record: ${q.status} ${q.statusText}`);return H.error("Error fetching auth record from URL:",Z),{kind:"error",error:Z}}let J=await q.json(),Q=M.validate(J);if(!Q){let Z=Error("Auth record from server failed validation.");return H.error("Error validating auth record from URL:",Z),{kind:"error",error:Z}}return{kind:"ok",value:M.revive(Q)}}catch(q){let J=q instanceof Error?q:Error(String(q));return H.error("Error fetching auth record from URL:",J),{kind:"error",error:J}}}function b(W){let{url:M,storage:C,storageKey:H,disableListener:q,logoutEventName:J,codec:Q}=W,[Z,$]=U(null),[I,O]=U(!!M),[X,z]=U(null),N=i(W.fetchInit);N.current=W.fetchInit;let P=i(W.log);P.current=W.log;let f=V((B)=>{if(C==="memory"||typeof window>"u")return;try{let k=C==="local"?localStorage:sessionStorage;if(B===null)k.removeItem(H);else k.setItem(H,JSON.stringify(B))}catch(k){P.current.error("Error saving auth record to storage:",k)}},[C,H]),w=V((B)=>{$(B),f(B)},[f]),j=V(()=>{if(C==="memory")return null;return d(C,H,Q,P.current)},[C,H,Q]),Y=V(async()=>{if(!M)return;O(!0);try{let B=await l(M,Q,N.current,P.current);if(B.kind==="ok")$(B.value),z(null),f(B.value);else if(B.kind==="unauthenticated")$(null),z(null),f(null);else z(B.error)}finally{O(!1)}},[M,Q,f]);return g(()=>{let B=j();if(B)$(B);if(M)Y();else O(!1)},[j,M,Y]),g(()=>{if(q||C==="memory")return;let B=(_)=>{if(_.key!==H)return;if(_.newValue===null){$(null);return}try{let m=JSON.parse(_.newValue),c=Q.validate(m);if(!c)return;let n=Q.revive(c);if(Q.isExpired(n)){$(null);return}$(n)}catch(m){P.current.error("Error parsing auth record from storage event:",m)}},k=()=>{$(null),z(null)};return window.addEventListener("storage",B),window.addEventListener(J,k),()=>{window.removeEventListener("storage",B),window.removeEventListener(J,k)}},[q,C,H,J,Q]),{value:Z,setValue:w,isLoading:I,error:X}}async function R(W,M,C){let{storageKey:H,storage:q,fetchInit:J,log:Q=S}=C;if(q==="local"||q==="session"){let Z=d(q,H,M,Q);if(Z)return Z}if(W){let Z=await l(W,M,J,Q);if(Z.kind==="ok")return Z.value}return}async function F(W,M,C){try{let H=await fetch(W,{...C?.fetchInit,headers:{Accept:"application/json",...C?.fetchInit?.headers}});if(!H.ok)return{success:!1,error:`HTTP ${H.status}`};let q=await H.json();if(!q.success)return{success:!1,error:q.message||"Logout failed"};let J=typeof q.redirect==="string"?q.redirect:void 0;if(typeof window<"u"){let Q=G(M.defaultStorageKey,C?.storageKey,M.providerLabel);if(localStorage.removeItem(Q),sessionStorage.removeItem(Q),window.dispatchEvent(new CustomEvent(M.logoutEventName)),J&&C?.navigate!==!1)window.location.assign(J)}return{success:!0,redirect:J}}catch(H){return{success:!1,error:H instanceof Error?H.message:"Network error"}}}import{jsx as PW}from"react/jsx-runtime";var T="es.ciam.customer",s="es-ciam-logout",y="CIAMProvider",t={validate(W){if(!W||typeof W!=="object")return null;let M=W;if(typeof M.userName!=="string"||typeof M.name!=="string"||typeof M.email!=="string")return null;return W},revive(W){if(W.ciam?.expires)W.ciam.expires=new Date(W.ciam.expires);return W},isExpired(W){let M=W.ciam?.expires;if(!M)return!1;let C=M instanceof Date?M.getTime():new Date(M).getTime();return Number.isFinite(C)&&C<=Date.now()}},h=OW(void 0);function IW({storageKey:W,customerUrl:M,storage:C="memory",disableListener:H=!1,fetchInit:q,log:J=$W,children:Q}){let Z=G(T,W,y),{value:$,setValue:I,isLoading:O,error:X}=b({url:M,storage:C,storageKey:Z,disableListener:H,logoutEventName:s,codec:t,fetchInit:q,log:J}),z=BW(()=>({customer:$,setCustomer:I,isLoading:O,error:X}),[$,I,O,X]);return PW(h.Provider,{value:z,children:Q})}function XW(){let W=a(h);if(W===void 0)throw Error("useCIAMContext must be used within a CIAMProvider");return W}function A(){return a(h)}async function YW(W,M){return R(W,t,{storageKey:G(T,M?.storageKey,y),storage:M?.storage??"memory",fetchInit:M?.fetchInit,log:M?.log})}async function zW(W,M){return F(W,{defaultStorageKey:T,logoutEventName:s,providerLabel:y},M)}import{silentLogger as o}from"@enterprisestandard/core";import{useCallback as fW,useEffect as kW,useState as x}from"react";import{jsx as K,jsxs as L}from"react/jsx-runtime";function r(W,M=o){let[C,H]=x([]),[q,J]=x(Boolean(W)),[Q,Z]=x(null),$=fW(()=>{if(!W)return;let I=!1;return J(!0),Z(null),fetch(W,{headers:{Accept:"application/json"}}).then(async(O)=>{if(!O.ok)throw Error(`Failed to fetch CIAM providers: ${O.status} ${O.statusText}`);let X=await O.json();if(!I)H(Array.isArray(X.providers)?X.providers:[])}).catch((O)=>{let X=O instanceof Error?O:Error(String(O));if(M.error("Error fetching CIAM providers:",X),!I)H([]),Z(X)}).finally(()=>{if(!I)J(!1)}),()=>{I=!0}},[W,M]);return kW(()=>{return $()},[$]),{providers:C,isLoading:q,error:Q,refresh:$}}function GW(W,M){return{display:"flex",alignItems:"center",gap:"0.5rem",width:"100%",padding:"0.625rem 0.875rem",border:"1px solid rgba(0,0,0,0.15)",borderRadius:"0.5rem",background:(M==="dark"?W.colors?.dark:W.colors?.light)??(M==="dark"?"#1f1f1f":"#ffffff"),color:M==="dark"?"#ffffff":"#111111",textDecoration:"none",font:"inherit",cursor:"pointer"}}function AW({providersUrl:W,limit:M,theme:C="light",renderProvider:H,loadingFallback:q,emptyFallback:J,errorFallback:Q,moreLabel:Z="Show more",className:$,log:I=o}){let{providers:O,isLoading:X,error:z,refresh:N}=r(W,I),[P,f]=x(!1);if(X)return q??null;if(z)return Q?Q(z,N):J??null;if(O.length===0)return J??null;let w=!P&&typeof M==="number"&&M>0?O.slice(0,M):O,j=w.length<O.length;return L("div",{className:$,style:{display:"flex",flexDirection:"column",gap:"0.5rem"},"data-es-ciam-providers":"",children:[w.map((Y)=>H?K("span",{children:H(Y)},Y.name):L("a",{href:Y.loginUrl,style:GW(Y,C),"data-es-ciam-provider":Y.name,children:[Y.iconUrl?K("img",{src:Y.iconUrl,alt:"",width:20,height:20,"aria-hidden":"true"}):null,L("span",{children:["Continue with ",Y.displayName]})]},Y.name)),j?K("button",{type:"button",onClick:()=>f(!0),"data-es-ciam-show-more":"",children:Z}):null]})}import{silentLogger as DW}from"@enterprisestandard/core";import{createContext as NW,useContext as e,useMemo as wW}from"react";import{jsx as FW}from"react/jsx-runtime";var E="es.sso.user",WW="es-sso-logout",p="SSOProvider",MW={validate(W){if(!W||typeof W!=="object")return null;let M=W;if(typeof M.userName!=="string"||typeof M.name!=="string"||typeof M.email!=="string")return null;return W},revive(W){if(W.sso?.expires)W.sso.expires=new Date(W.sso.expires);return W},isExpired(W){let M=W.sso?.expires;if(!M)return!1;let C=M instanceof Date?M.getTime():new Date(M).getTime();return Number.isFinite(C)&&C<=Date.now()}},v=NW(void 0);function jW({storageKey:W,userUrl:M,storage:C="memory",disableListener:H=!1,fetchInit:q,log:J=DW,children:Q}){let Z=G(E,W,p),{value:$,setValue:I,isLoading:O,error:X}=b({url:M,storage:C,storageKey:Z,disableListener:H,logoutEventName:WW,codec:MW,fetchInit:q,log:J}),z=wW(()=>({user:$,setUser:I,isLoading:O,error:X}),[$,I,O,X]);return FW(v.Provider,{value:z,children:Q})}function VW(){let W=e(v);if(W===void 0)throw Error("useSSOContext must be used within a SSOProvider");return W}function D(){return e(v)}async function bW(W,M){return R(W,MW,{storageKey:G(E,M?.storageKey,p),storage:M?.storage??"memory",fetchInit:M?.fetchInit,log:M?.log})}async function RW(W,M){return F(W,{defaultStorageKey:E,logoutEventName:WW,providerLabel:p},M)}import{jsx as mW,Fragment as _W}from"react/jsx-runtime";function xW({complete:W=!1,audience:M="workforce",children:C}){let H=D(),q=A();if((M==="customer"?!!q?.isLoading:!!H?.isLoading)&&!W)return mW(_W,{children:C});return null}import{jsx as TW,Fragment as SW}from"react/jsx-runtime";function UW({audience:W="workforce",children:M}){let C=D(),H=A();if(W==="customer"?!!H?.customer:!!C?.user)return TW(SW,{children:M});return null}import{jsx as KW,Fragment as hW}from"react/jsx-runtime";function yW({audience:W="workforce",children:M}){let C=D(),H=A(),q=W==="customer"?!!H?.customer:!!C?.user,J=W==="customer"?!!H?.isLoading:!!C?.isLoading;if(q||J)return null;return KW(hW,{children:M})}import{buildTenantPath as CW,DEFAULT_TENANT_API_NAMESPACE as LW,DEFAULT_TENANT_UI_NAMESPACE as EW,normalizeTenantRoutingStrategy as qW}from"@enterprisestandard/core";import{createContext as pW,useContext as HW,useMemo as vW}from"react";import{jsx as gW}from"react/jsx-runtime";var u=pW(null);function JW(W,M){let C=qW(M),H=C.ui??EW,q=C.api??LW;return{ui:(J="/")=>CW(W,J,H),api:(J="/")=>CW(W,J,q)}}function uW({id:W,strategy:M,children:C}){let H=vW(()=>{let q=qW(M);return{id:W,strategy:q,urls:JW(W,q)}},[M,W]);return gW(u.Provider,{value:H,children:C})}function cW(){let W=HW(u);if(!W)throw Error("useTenant() must be used within a TenantProvider");return W}function nW(){return HW(u)??void 0}export{cW as useTenant,VW as useSSOContext,nW as useOptionalTenant,D as useOptionalSSOContext,A as useOptionalCIAMContext,r as useCIAMProviders,XW as useCIAMContext,FM as silentLogger,zW as logoutCustomer,RW as logout,bW as getUser,YW as getCustomer,RM as defaultLogger,JW as createTenantUrls,uW as TenantProvider,yW as SignedOut,UW as SignedIn,xW as SignInLoading,jW as SSOProvider,AW as CIAMProviderList,IW as CIAMProvider};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enterprisestandard/react",
3
- "version": "0.0.20-beta.20260623.1",
3
+ "version": "0.0.20-beta.20260625.1",
4
4
  "description": "Enterprise Standard React Components",
5
5
  "private": false,
6
6
  "author": "enterprisestandard",
@@ -18,11 +18,10 @@
18
18
  "default": "./dist/index.js"
19
19
  }
20
20
  },
21
- "./styles.css": "./dist/index.css",
22
21
  "./package.json": "./package.json"
23
22
  },
24
23
  "dependencies": {
25
- "@enterprisestandard/core": "0.0.20-beta.20260623.1"
24
+ "@enterprisestandard/core": "0.0.20-beta.20260625.1"
26
25
  },
27
26
  "peerDependencies": {
28
27
  "react": "^18.0.0 || ^19.0.0",