@enterprisestandard/react 0.0.20-beta.20260623.2 → 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 +91 -0
- package/dist/index.d.ts +53 -35
- package/dist/index.js +1 -1
- package/package.json +2 -3
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
|
-
|
|
2
|
-
import {
|
|
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
|
-
|
|
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,
|
|
25
|
-
|
|
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
|
|
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?:
|
|
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?:
|
|
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
|
|
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?:
|
|
115
|
+
storage?: StorageType;
|
|
98
116
|
disableListener?: boolean;
|
|
99
|
-
|
|
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,
|
|
112
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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 +1 @@
|
|
|
1
|
-
export*from"@enterprisestandard/core";import{generateULID as Bi}from"@enterprisestandard/core";import{silentLogger as Y}from"@enterprisestandard/core";import{createContext as s,useCallback as J,useContext as b,useEffect as C,useState as L}from"react";import{jsx as Rn}from"react/jsx-runtime";var G=s(void 0),r=/^[a-zA-Z0-9._-]{1,1024}$/;function e(n){if(!n.trim())throw Error("CIAMProvider storageKey must be a non-empty string.");if(!r.test(n))throw Error("CIAMProvider storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.")}var u=(n)=>{if(n===void 0||n==="")return"es.ciam.customer";return e(n),n};function Z(n,T,i=Y){if(typeof window>"u")return null;try{let P=(n==="local"?localStorage:sessionStorage).getItem(T);if(!P)return null;let R=JSON.parse(P);if(R.ciam?.expires)R.ciam.expires=new Date(R.ciam.expires);return R}catch(f){return i.error("Error loading customer from storage:",f),null}}async function F(n,T=Y){try{let i=await fetch(n);if(i.status===401)return null;if(!i.ok)throw Error(`Failed to fetch customer: ${i.status} ${i.statusText}`);let f=await i.json();if(f.ciam?.expires&&typeof f.ciam.expires==="string")f.ciam.expires=new Date(f.ciam.expires);return f}catch(i){return T.error("Error fetching customer from URL:",i),null}}function nn({storageKey:n,customerUrl:T,storage:i="memory",disableListener:f=!1,log:P=Y,children:R}){let[q,M]=L(null),[E,x]=L(!!T),A=u(n),N=J(()=>{if(i==="memory")return null;return Z(i,A,P)},[i,A,P]),W=J((c)=>{if(i==="memory"||typeof window>"u")return;try{let $=i==="local"?localStorage:sessionStorage;if(c===null)$.removeItem(A);else $.setItem(A,JSON.stringify(c))}catch($){P.error("Error saving customer to storage:",$)}},[i,A,P]),O=J((c)=>{M(c),W(c)},[W]),B=J(async()=>{if(!T)return;x(!0);try{let c=await F(T,P);M(c),W(c)}finally{x(!1)}},[T,W,P]);C(()=>{let c=N();if(c)M(c);if(T)B();else x(!1)},[N,T,B]),C(()=>{if(f||i==="memory")return;let c=(z)=>{if(z.key!==A)return;if(z.newValue===null)M(null);else try{let I=JSON.parse(z.newValue);if(I.ciam?.expires)I.ciam.expires=new Date(I.ciam.expires);M(I)}catch(I){P.error("Error parsing customer from storage event:",I)}},$=()=>{M(null)};return window.addEventListener("storage",c),window.addEventListener("es-ciam-logout",$),()=>{window.removeEventListener("storage",c),window.removeEventListener("es-ciam-logout",$)}},[f,i,A,P]);let w={customer:q,setCustomer:O,isLoading:E};return Rn(G.Provider,{value:w,children:R})}function Tn(){let n=b(G);if(n===void 0)throw Error("useCIAMContext must be used within a CIAMProvider");return n}function _(){return b(G)}async function fn(n,T){let i=u(T),f=Z("local",i);if(f)return f;let P=Z("session",i);if(P)return P;if(n){let R=await F(n);if(R)return R}return}async function Pn(n){try{let T=await fetch(n,{headers:{Accept:"application/json"}});if(!T.ok)return{success:!1,error:`HTTP ${T.status}`};let i=await T.json();if(!i.success)return{success:!1,error:i.message||"Logout failed"};if(typeof window<"u")localStorage.removeItem("es.ciam.customer"),sessionStorage.removeItem("es.ciam.customer"),window.dispatchEvent(new CustomEvent("es-ciam-logout"));return{success:!0}}catch(T){return{success:!1,error:T instanceof Error?T.message:"Network error"}}}import{silentLogger as y}from"@enterprisestandard/core";import{useCallback as An,useEffect as cn,useState as Q}from"react";import{jsx as p,jsxs as X}from"react/jsx-runtime";function S(n,T=y){let[i,f]=Q([]),[P,R]=Q(Boolean(n)),[q,M]=Q(null),E=An(()=>{if(!n)return;let x=!1;return R(!0),M(null),fetch(n,{headers:{Accept:"application/json"}}).then(async(A)=>{if(!A.ok)throw Error(`Failed to fetch CIAM providers: ${A.status} ${A.statusText}`);let N=await A.json();if(!x)f(Array.isArray(N.providers)?N.providers:[])}).catch((A)=>{let N=A instanceof Error?A:Error(String(A));if(T.error("Error fetching CIAM providers:",N),!x)M(N)}).finally(()=>{if(!x)R(!1)}),()=>{x=!0}},[n,T]);return cn(()=>{return E()},[E]),{providers:i,isLoading:P,error:q,refresh:E}}function Mn(n,T){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:(T==="dark"?n.colors?.dark:n.colors?.light)??(T==="dark"?"#1f1f1f":"#ffffff"),color:T==="dark"?"#ffffff":"#111111",textDecoration:"none",font:"inherit",cursor:"pointer"}}function On({providersUrl:n,limit:T,theme:i="light",renderProvider:f,loadingFallback:P,emptyFallback:R,moreLabel:q="Show more",className:M,log:E=y}){let{providers:x,isLoading:A}=S(n,E),[N,W]=Q(!1);if(A)return P??null;if(x.length===0)return R??null;let O=!N&&typeof T==="number"&&T>0?x.slice(0,T):x,B=O.length<x.length;return X("div",{className:M,style:{display:"flex",flexDirection:"column",gap:"0.5rem"},"data-es-ciam-providers":"",children:[O.map((w)=>f?p("span",{children:f(w)},w.name):X("a",{href:w.loginUrl,style:Mn(w,i),"data-es-ciam-provider":w.name,children:[w.iconUrl?p("img",{src:w.iconUrl,alt:"",width:20,height:20,"aria-hidden":"true"}):null,X("span",{children:["Continue with ",w.displayName]})]},w.name)),B?p("button",{type:"button",onClick:()=>W(!0),"data-es-ciam-show-more":"",children:q}):null]})}import{silentLogger as h}from"@enterprisestandard/core";import{createContext as xn,useCallback as V,useContext as K,useEffect as t,useState as m}from"react";import{jsx as qn}from"react/jsx-runtime";var k=xn(void 0),Nn=/^[a-zA-Z0-9._-]{1,1024}$/;function $n(n){if(!n.trim())throw Error("SSOProvider storageKey must be a non-empty string.");if(!Nn.test(n))throw Error("SSOProvider storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.")}var v=(n)=>{if(n===void 0||n==="")return"es.sso.user";return $n(n),n};function D(n,T,i=h){if(typeof window>"u")return null;try{let P=(n==="local"?localStorage:sessionStorage).getItem(T);if(!P)return null;let R=JSON.parse(P);if(R.sso?.expires)R.sso.expires=new Date(R.sso.expires);return R}catch(f){return i.error("Error loading user from storage:",f),null}}async function U(n,T=h){try{let i=await fetch(n);if(i.status===401)return null;if(!i.ok)throw Error(`Failed to fetch user: ${i.status} ${i.statusText}`);let f=await i.json();if(f.sso?.expires&&typeof f.sso.expires==="string")f.sso.expires=new Date(f.sso.expires);return f}catch(i){return T.error("Error fetching user from URL:",i),null}}function Wn({storageKey:n,userUrl:T,storage:i="memory",disableListener:f=!1,log:P=h,children:R}){let[q,M]=m(null),[E,x]=m(!!T),A=v(n),N=V(()=>{if(i==="memory")return null;return D(i,A,P)},[i,A,P]),W=V((c)=>{if(i==="memory"||typeof window>"u")return;try{let $=i==="local"?localStorage:sessionStorage;if(c===null)$.removeItem(A);else $.setItem(A,JSON.stringify(c))}catch($){P.error("Error saving user to storage:",$)}},[i,A,P]),O=V((c)=>{M(c),W(c)},[W]),B=V(async()=>{if(!T)return;x(!0);try{let c=await U(T,P);M(c),W(c)}finally{x(!1)}},[T,W,P]);t(()=>{let c=N();if(c)M(c);if(T)B();else x(!1)},[N,T,B]),t(()=>{if(f||i==="memory")return;let c=(z)=>{if(z.key!==A)return;if(z.newValue===null)M(null);else try{let I=JSON.parse(z.newValue);if(I.sso?.expires)I.sso.expires=new Date(I.sso.expires);M(I)}catch(I){P.error("Error parsing user from storage event:",I)}},$=()=>{M(null)};return window.addEventListener("storage",c),window.addEventListener("es-sso-logout",$),()=>{window.removeEventListener("storage",c),window.removeEventListener("es-sso-logout",$)}},[f,i,A,P]);let w={user:q,setUser:O,isLoading:E};return qn(k.Provider,{value:w,children:R})}function wn(){let n=K(k);if(n===void 0)throw Error("useSSOContext must be used within a SSOProvider");return n}function H(){return K(k)}async function In(n,T){let i=v(T),f=D("local",i);if(f)return f;let P=D("session",i);if(P)return P;if(n){let R=await U(n);if(R)return R}return}async function En(n){try{let T=await fetch(n,{headers:{Accept:"application/json"}});if(!T.ok)return{success:!1,error:`HTTP ${T.status}`};let i=await T.json();if(!i.success)return{success:!1,error:i.message||"Logout failed"};if(typeof window<"u")localStorage.removeItem("es.sso.user"),sessionStorage.removeItem("es.sso.user"),window.dispatchEvent(new CustomEvent("es-sso-logout"));return{success:!0}}catch(T){return{success:!1,error:T instanceof Error?T.message:"Network error"}}}import{jsx as _n,Fragment as zn}from"react/jsx-runtime";function Bn({complete:n=!1,audience:T="workforce",children:i}){let f=H(),P=_();if((T==="customer"?!!P?.isLoading:!!f?.isLoading)&&!n)return _n(zn,{children:i});return null}import{jsx as Qn,Fragment as Jn}from"react/jsx-runtime";function Hn({audience:n="workforce",children:T}){let i=H(),f=_();if(n==="customer"?!!f?.customer:!!i?.user)return Qn(Jn,{children:T});return null}import{jsx as Yn,Fragment as Zn}from"react/jsx-runtime";function Vn({audience:n="workforce",children:T}){let i=H(),f=_(),P=n==="customer"?!!f?.customer:!!i?.user,R=n==="customer"?!!f?.isLoading:!!i?.isLoading;if(P||R)return null;return Yn(Zn,{children:T})}import{buildTenantPath as d,DEFAULT_TENANT_API_NAMESPACE as Gn,DEFAULT_TENANT_UI_NAMESPACE as pn,normalizeTenantRoutingStrategy as a}from"@enterprisestandard/core";import{createContext as Xn,useContext as Dn,useMemo as hn}from"react";import{jsx as Cn}from"react/jsx-runtime";var g=Xn(null);function o(n,T){let i=a(T);if(i.type==="jwt"){let R=i.ui?.segments??[],q=i.api?.segments??["api"];return{ui:(M="/")=>l(R,M),api:(M="/")=>l(q,M)}}let f=i.ui??pn,P=i.api??Gn;return{ui:(R="/")=>d(n,R,f),api:(R="/")=>d(n,R,P)}}function l(n,T="/"){let i=n.join("/"),f=T.trim().replace(/^\/+/,"");if(!i&&!f)return"/";if(!i)return`/${f}`;if(!f)return`/${i}`;return`/${i}/${f}`}function kn({id:n,strategy:T,children:i}){let f=hn(()=>{let P=a(T);return{id:n,strategy:P,urls:o(n,P)}},[T,n]);return Cn(g.Provider,{value:f,children:i})}function jn(){let n=Dn(g);if(!n)throw Error("useTenant() must be used within a TenantProvider");return n}import{useCallback as Ln,useEffect as bn,useMemo as un,useState as j}from"react";function Fn(n={}){let T=n.tenantManagerBaseUrl??"/api/tenants/mine",[i,f]=j(null),[P,R]=j(!0),[q,M]=j(null),E=un(()=>{let O=new URL(T,"http://localhost");if(n.start!=null)O.searchParams.set("start",String(n.start));if(n.limit!=null)O.searchParams.set("limit",String(n.limit));if(n.order)O.searchParams.set("order",n.order);return`${O.pathname}${O.search}`},[n.limit,n.order,n.start,T]),x=Ln(async()=>{R(!0),M(null);try{let O=await fetch(E);if(!O.ok){f(null),M(Error(`Tenant directory request failed with status ${O.status}`));return}let B=await O.json();f(B)}catch(O){f(null),M(O instanceof Error?O:Error("Failed to load tenant directory"))}finally{R(!1)}},[E]);bn(()=>{x()},[x]);let A=i?.tenants??{},N=i?.ids??[],W=N.map((O)=>A[O]).filter((O)=>!!O);return{directory:i,tenants:A,ids:N,items:W,isLoading:P,error:q,refresh:x}}export{Fn as useTenantDirectory,jn as useTenant,wn as useSSOContext,H as useOptionalSSOContext,_ as useOptionalCIAMContext,S as useCIAMProviders,Tn as useCIAMContext,Pn as logoutCustomer,En as logout,In as getUser,fn as getCustomer,Bi as generateULID,o as createTenantUrls,kn as TenantProvider,Vn as SignedOut,Hn as SignedIn,Bn as SignInLoading,Wn as SSOProvider,On as CIAMProviderList,nn as CIAMProvider};
|
|
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.
|
|
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.
|
|
24
|
+
"@enterprisestandard/core": "0.0.20-beta.20260625.1"
|
|
26
25
|
},
|
|
27
26
|
"peerDependencies": {
|
|
28
27
|
"react": "^18.0.0 || ^19.0.0",
|