@ionite/react 0.0.24-beta.20260907.2 → 0.0.24-beta.20260907.3
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/index.js +604 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1 +1,604 @@
|
|
|
1
|
-
import{defaultLogger as VM,silentLogger as _M}from"@ionite/core";import{silentLogger as ZW}from"@ionite/core";import{createContext as $W,useContext as i,useMemo as OW}from"react";import{silentLogger as S}from"@ionite/core";import{useCallback as R,useEffect as v,useRef as c,useState as U}from"react";var JW=/^[a-zA-Z0-9._-]{1,1024}$/;function QW(W,M){if(!W.trim())throw Error(`${M} storageKey must be a non-empty string.`);if(!JW.test(W))throw Error(`${M} storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.`)}function D(W,M,C){if(M===void 0||M==="")return W;return QW(M,C),M}function n(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 g(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 V(W){let{url:M,storage:C,storageKey:H,disableListener:q,logoutEventName:J,codec:Q}=W,[Z,$]=U(null),[I,O]=U(!!M),[X,P]=U(null),N=c(W.fetchInit);N.current=W.fetchInit;let z=c(W.log);z.current=W.log;let A=R((B)=>{if(C==="memory"||typeof window>"u")return;try{let f=C==="local"?localStorage:sessionStorage;if(B===null)f.removeItem(H);else f.setItem(H,JSON.stringify(B))}catch(f){z.current.error("Error saving auth record to storage:",f)}},[C,H]),w=R((B)=>{$(B),A(B)},[A]),j=R(()=>{if(C==="memory")return null;return n(C,H,Q,z.current)},[C,H,Q]),Y=R(async()=>{if(!M)return;O(!0);try{let B=await g(M,Q,N.current,z.current);if(B.kind==="ok")$(B.value),P(null),A(B.value);else if(B.kind==="unauthenticated")$(null),P(null),A(null);else P(B.error)}finally{O(!1)}},[M,Q,A]);return v(()=>{let B=j();if(B)$(B);if(M)Y();else O(!1)},[j,M,Y]),v(()=>{if(q||C==="memory")return;let B=(F)=>{if(F.key!==H)return;if(F.newValue===null){$(null);return}try{let x=JSON.parse(F.newValue),E=Q.validate(x);if(!E)return;let u=Q.revive(E);if(Q.isExpired(u)){$(null);return}$(u)}catch(x){z.current.error("Error parsing auth record from storage event:",x)}},f=()=>{$(null),P(null)};return window.addEventListener("storage",B),window.addEventListener(J,f),()=>{window.removeEventListener("storage",B),window.removeEventListener(J,f)}},[q,C,H,J,Q]),{value:Z,setValue:w,isLoading:I,error:X}}async function d(W,M,C){let{storageKey:H,storage:q,fetchInit:J,log:Q=S}=C;if(q==="local"||q==="session"){let Z=n(q,H,M,Q);if(Z)return Z}if(W){let Z=await g(W,M,J,Q);if(Z.kind==="ok")return Z.value}return}async function _(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=D(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 m="ion.ciam.customer",l="ion-ciam-logout",T="CIAMProvider",s={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()}},y=$W(void 0);function BW({storageKey:W,customerUrl:M,storage:C="memory",disableListener:H=!1,fetchInit:q,log:J=ZW,children:Q}){let Z=D(m,W,T),{value:$,setValue:I,isLoading:O,error:X}=V({url:M,storage:C,storageKey:Z,disableListener:H,logoutEventName:l,codec:s,fetchInit:q,log:J}),P=OW(()=>({customer:$,setCustomer:I,isLoading:O,error:X}),[$,I,O,X]);return PW(y.Provider,{value:P,children:Q})}function IW(){let W=i(y);if(W===void 0)throw Error("useCIAMContext must be used within a CIAMProvider");return W}function G(){return i(y)}async function XW(W,M){return d(W,s,{storageKey:D(m,M?.storageKey,T),storage:M?.storage??"memory",fetchInit:M?.fetchInit,log:M?.log})}async function YW(W,M){return _(W,{defaultStorageKey:m,logoutEventName:l,providerLabel:T},M)}import{silentLogger as a}from"@ionite/core";import{useCallback as zW,useEffect as AW,useState as b}from"react";import{jsx as h,jsxs as K}from"react/jsx-runtime";function t(W,M=a){let[C,H]=b([]),[q,J]=b(Boolean(W)),[Q,Z]=b(null),$=zW(()=>{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 AW(()=>{return $()},[$]),{providers:C,isLoading:q,error:Q,refresh:$}}function fW(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 GW({providersUrl:W,limit:M,theme:C="light",renderProvider:H,loadingFallback:q,emptyFallback:J,errorFallback:Q,moreLabel:Z="Show more",className:$,log:I=a}){let{providers:O,isLoading:X,error:P,refresh:N}=t(W,I),[z,A]=b(!1);if(X)return q??null;if(P)return Q?Q(P,N):J??null;if(O.length===0)return J??null;let w=!z&&typeof M==="number"&&M>0?O.slice(0,M):O,j=w.length<O.length;return K("div",{className:$,style:{display:"flex",flexDirection:"column",gap:"0.5rem"},"data-ion-ciam-providers":"",children:[w.map((Y)=>H?h("span",{children:H(Y)},Y.name):K("a",{href:Y.loginUrl,style:fW(Y,C),"data-ion-ciam-provider":Y.name,children:[Y.iconUrl?h("img",{src:Y.iconUrl,alt:"",width:20,height:20,"aria-hidden":"true"}):null,K("span",{children:["Continue with ",Y.displayName]})]},Y.name)),j?h("button",{type:"button",onClick:()=>A(!0),"data-ion-ciam-show-more":"",children:Z}):null]})}import{silentLogger as kW}from"@ionite/core";import{createContext as DW,useContext as o,useMemo as NW}from"react";import{jsx as _W}from"react/jsx-runtime";var r="ion.sso.user",e="ion-sso-logout",WW="SSOProvider",wW={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()}},L=DW(void 0);function jW({storageKey:W,userUrl:M,storage:C="memory",disableListener:H=!1,fetchInit:q,log:J=kW,children:Q}){let Z=D(r,W,WW),{value:$,setValue:I,isLoading:O,error:X}=V({url:M,storage:C,storageKey:Z,disableListener:H,logoutEventName:e,codec:wW,fetchInit:q,log:J}),P=NW(()=>({user:$,setUser:I,isLoading:O,error:X}),[$,I,O,X]);return _W(L.Provider,{value:P,children:Q})}function RW(){let W=o(L);if(W===void 0)throw Error("useSSOContext must be used within a SSOProvider");return W}function k(){return o(L)}async function VW(W,M){return _(W,{defaultStorageKey:r,logoutEventName:e,providerLabel:WW},M)}import{jsx as xW,Fragment as FW}from"react/jsx-runtime";function bW({complete:W=!1,audience:M="workforce",children:C}){let H=k(),q=G();if((M==="customer"?!!q?.isLoading:!!H?.isLoading)&&!W)return xW(FW,{children:C});return null}import{jsx as mW,Fragment as SW}from"react/jsx-runtime";function UW({audience:W="workforce",children:M}){let C=k(),H=G();if(W==="customer"?!!H?.customer:!!C?.user)return mW(SW,{children:M});return null}import{jsx as hW,Fragment as yW}from"react/jsx-runtime";function TW({audience:W="workforce",children:M}){let C=k(),H=G(),q=W==="customer"?!!H?.customer:!!C?.user,J=W==="customer"?!!H?.isLoading:!!C?.isLoading;if(q||J)return null;return hW(yW,{children:M})}import{buildTenantPath as MW,DEFAULT_TENANT_API_NAMESPACE as KW,DEFAULT_TENANT_UI_NAMESPACE as LW,normalizeTenantRoutingStrategy as CW}from"@ionite/core";import{createContext as pW,useContext as qW,useMemo as EW}from"react";import{jsx as nW}from"react/jsx-runtime";var p=pW(null);function HW(W,M){let C=CW(M),H=C.ui??LW,q=C.api??KW;return{ui:(J="/")=>MW(W,J,H),api:(J="/")=>MW(W,J,q)}}function uW({id:W,strategy:M,children:C}){let H=EW(()=>{let q=CW(M);return{id:W,strategy:q,urls:HW(W,q)}},[M,W]);return nW(p.Provider,{value:H,children:C})}function vW(){let W=qW(p);if(!W)throw Error("useTenant() must be used within a TenantProvider");return W}function cW(){return qW(p)??void 0}export{vW as useTenant,RW as useSSOContext,cW as useOptionalTenant,k as useOptionalSSOContext,G as useOptionalCIAMContext,t as useCIAMProviders,IW as useCIAMContext,_M as silentLogger,YW as logoutCustomer,VW as logout,XW as getCustomer,VM as defaultLogger,HW as createTenantUrls,uW as TenantProvider,TW as SignedOut,UW as SignedIn,bW as SignInLoading,jW as SSOProvider,GW as CIAMProviderList,BW as CIAMProvider};
|
|
1
|
+
// packages/react/src/index.ts
|
|
2
|
+
import { defaultLogger, silentLogger as silentLogger5 } from "@ionite/core";
|
|
3
|
+
|
|
4
|
+
// packages/react/src/ciam-provider.tsx
|
|
5
|
+
import { silentLogger as silentLogger2 } from "@ionite/core";
|
|
6
|
+
import { createContext, useContext, useMemo } from "react";
|
|
7
|
+
|
|
8
|
+
// packages/react/src/auth-resource.tsx
|
|
9
|
+
import { silentLogger } from "@ionite/core";
|
|
10
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
11
|
+
var VALID_STORAGE_KEY = /^[a-zA-Z0-9._-]{1,1024}$/;
|
|
12
|
+
function validateStorageKey(key, providerLabel) {
|
|
13
|
+
if (!key.trim()) {
|
|
14
|
+
throw new Error(`${providerLabel} storageKey must be a non-empty string.`);
|
|
15
|
+
}
|
|
16
|
+
if (!VALID_STORAGE_KEY.test(key)) {
|
|
17
|
+
throw new Error(`${providerLabel} storageKey must be 1-1024 characters and contain only letters, numbers, dots, underscores, and hyphens.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function resolveStorageKey(defaultKey, storageKey, providerLabel) {
|
|
21
|
+
if (storageKey === undefined || storageKey === "") {
|
|
22
|
+
return defaultKey;
|
|
23
|
+
}
|
|
24
|
+
validateStorageKey(storageKey, providerLabel);
|
|
25
|
+
return storageKey;
|
|
26
|
+
}
|
|
27
|
+
function loadFromStorageForKey(storage, storageKey, codec, log = silentLogger) {
|
|
28
|
+
if (typeof window === "undefined")
|
|
29
|
+
return null;
|
|
30
|
+
try {
|
|
31
|
+
const store = storage === "local" ? localStorage : sessionStorage;
|
|
32
|
+
const raw = store.getItem(storageKey);
|
|
33
|
+
if (!raw)
|
|
34
|
+
return null;
|
|
35
|
+
const parsed = JSON.parse(raw);
|
|
36
|
+
const validated = codec.validate(parsed);
|
|
37
|
+
if (!validated) {
|
|
38
|
+
store.removeItem(storageKey);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
const revived = codec.revive(validated);
|
|
42
|
+
if (codec.isExpired(revived)) {
|
|
43
|
+
store.removeItem(storageKey);
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
return revived;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
log.error("Error loading auth record from storage:", error);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function fetchResourceFromServer(url, codec, fetchInit, log = silentLogger) {
|
|
53
|
+
try {
|
|
54
|
+
const response = await fetch(url, fetchInit);
|
|
55
|
+
if (response.status === 401) {
|
|
56
|
+
return { kind: "unauthenticated" };
|
|
57
|
+
}
|
|
58
|
+
if (!response.ok) {
|
|
59
|
+
const error = new Error(`Failed to fetch auth record: ${response.status} ${response.statusText}`);
|
|
60
|
+
log.error("Error fetching auth record from URL:", error);
|
|
61
|
+
return { kind: "error", error };
|
|
62
|
+
}
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
const validated = codec.validate(data);
|
|
65
|
+
if (!validated) {
|
|
66
|
+
const error = new Error("Auth record from server failed validation.");
|
|
67
|
+
log.error("Error validating auth record from URL:", error);
|
|
68
|
+
return { kind: "error", error };
|
|
69
|
+
}
|
|
70
|
+
return { kind: "ok", value: codec.revive(validated) };
|
|
71
|
+
} catch (err) {
|
|
72
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
73
|
+
log.error("Error fetching auth record from URL:", error);
|
|
74
|
+
return { kind: "error", error };
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function useAuthResource(options) {
|
|
78
|
+
const { url, storage, storageKey, disableListener, logoutEventName, codec } = options;
|
|
79
|
+
const [value, setValueState] = useState(null);
|
|
80
|
+
const [isLoading, setIsLoading] = useState(!!url);
|
|
81
|
+
const [error, setError] = useState(null);
|
|
82
|
+
const fetchInitRef = useRef(options.fetchInit);
|
|
83
|
+
fetchInitRef.current = options.fetchInit;
|
|
84
|
+
const logRef = useRef(options.log);
|
|
85
|
+
logRef.current = options.log;
|
|
86
|
+
const saveToStorage = useCallback((next) => {
|
|
87
|
+
if (storage === "memory" || typeof window === "undefined")
|
|
88
|
+
return;
|
|
89
|
+
try {
|
|
90
|
+
const store = storage === "local" ? localStorage : sessionStorage;
|
|
91
|
+
if (next === null) {
|
|
92
|
+
store.removeItem(storageKey);
|
|
93
|
+
} else {
|
|
94
|
+
store.setItem(storageKey, JSON.stringify(next));
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
logRef.current.error("Error saving auth record to storage:", err);
|
|
98
|
+
}
|
|
99
|
+
}, [storage, storageKey]);
|
|
100
|
+
const setValue = useCallback((next) => {
|
|
101
|
+
setValueState(next);
|
|
102
|
+
saveToStorage(next);
|
|
103
|
+
}, [saveToStorage]);
|
|
104
|
+
const loadFromStorage = useCallback(() => {
|
|
105
|
+
if (storage === "memory")
|
|
106
|
+
return null;
|
|
107
|
+
return loadFromStorageForKey(storage, storageKey, codec, logRef.current);
|
|
108
|
+
}, [storage, storageKey, codec]);
|
|
109
|
+
const refresh = useCallback(async () => {
|
|
110
|
+
if (!url)
|
|
111
|
+
return;
|
|
112
|
+
setIsLoading(true);
|
|
113
|
+
try {
|
|
114
|
+
const outcome = await fetchResourceFromServer(url, codec, fetchInitRef.current, logRef.current);
|
|
115
|
+
if (outcome.kind === "ok") {
|
|
116
|
+
setValueState(outcome.value);
|
|
117
|
+
setError(null);
|
|
118
|
+
saveToStorage(outcome.value);
|
|
119
|
+
} else if (outcome.kind === "unauthenticated") {
|
|
120
|
+
setValueState(null);
|
|
121
|
+
setError(null);
|
|
122
|
+
saveToStorage(null);
|
|
123
|
+
} else {
|
|
124
|
+
setError(outcome.error);
|
|
125
|
+
}
|
|
126
|
+
} finally {
|
|
127
|
+
setIsLoading(false);
|
|
128
|
+
}
|
|
129
|
+
}, [url, codec, saveToStorage]);
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
const stored = loadFromStorage();
|
|
132
|
+
if (stored)
|
|
133
|
+
setValueState(stored);
|
|
134
|
+
if (url) {
|
|
135
|
+
refresh();
|
|
136
|
+
} else {
|
|
137
|
+
setIsLoading(false);
|
|
138
|
+
}
|
|
139
|
+
}, [loadFromStorage, url, refresh]);
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (disableListener || storage === "memory")
|
|
142
|
+
return;
|
|
143
|
+
const handleStorageChange = (event) => {
|
|
144
|
+
if (event.key !== storageKey)
|
|
145
|
+
return;
|
|
146
|
+
if (event.newValue === null) {
|
|
147
|
+
setValueState(null);
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
try {
|
|
151
|
+
const parsed = JSON.parse(event.newValue);
|
|
152
|
+
const validated = codec.validate(parsed);
|
|
153
|
+
if (!validated)
|
|
154
|
+
return;
|
|
155
|
+
const revived = codec.revive(validated);
|
|
156
|
+
if (codec.isExpired(revived)) {
|
|
157
|
+
setValueState(null);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
setValueState(revived);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
logRef.current.error("Error parsing auth record from storage event:", err);
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
const handleLogout = () => {
|
|
166
|
+
setValueState(null);
|
|
167
|
+
setError(null);
|
|
168
|
+
};
|
|
169
|
+
window.addEventListener("storage", handleStorageChange);
|
|
170
|
+
window.addEventListener(logoutEventName, handleLogout);
|
|
171
|
+
return () => {
|
|
172
|
+
window.removeEventListener("storage", handleStorageChange);
|
|
173
|
+
window.removeEventListener(logoutEventName, handleLogout);
|
|
174
|
+
};
|
|
175
|
+
}, [disableListener, storage, storageKey, logoutEventName, codec]);
|
|
176
|
+
return { value, setValue, isLoading, error };
|
|
177
|
+
}
|
|
178
|
+
async function getResource(url, codec, options) {
|
|
179
|
+
const { storageKey, storage, fetchInit, log = silentLogger } = options;
|
|
180
|
+
if (storage === "local" || storage === "session") {
|
|
181
|
+
const fromStorage = loadFromStorageForKey(storage, storageKey, codec, log);
|
|
182
|
+
if (fromStorage)
|
|
183
|
+
return fromStorage;
|
|
184
|
+
}
|
|
185
|
+
if (url) {
|
|
186
|
+
const outcome = await fetchResourceFromServer(url, codec, fetchInit, log);
|
|
187
|
+
if (outcome.kind === "ok")
|
|
188
|
+
return outcome.value;
|
|
189
|
+
}
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
async function logoutResource(logoutUrl, config, options) {
|
|
193
|
+
try {
|
|
194
|
+
const response = await fetch(logoutUrl, {
|
|
195
|
+
...options?.fetchInit,
|
|
196
|
+
headers: { Accept: "application/json", ...options?.fetchInit?.headers }
|
|
197
|
+
});
|
|
198
|
+
if (!response.ok) {
|
|
199
|
+
return { success: false, error: `HTTP ${response.status}` };
|
|
200
|
+
}
|
|
201
|
+
const data = await response.json();
|
|
202
|
+
if (!data.success) {
|
|
203
|
+
return { success: false, error: data.message || "Logout failed" };
|
|
204
|
+
}
|
|
205
|
+
const redirect = typeof data.redirect === "string" ? data.redirect : undefined;
|
|
206
|
+
if (typeof window !== "undefined") {
|
|
207
|
+
const key = resolveStorageKey(config.defaultStorageKey, options?.storageKey, config.providerLabel);
|
|
208
|
+
localStorage.removeItem(key);
|
|
209
|
+
sessionStorage.removeItem(key);
|
|
210
|
+
window.dispatchEvent(new CustomEvent(config.logoutEventName));
|
|
211
|
+
if (redirect && options?.navigate !== false) {
|
|
212
|
+
window.location.assign(redirect);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return { success: true, redirect };
|
|
216
|
+
} catch (error) {
|
|
217
|
+
return {
|
|
218
|
+
success: false,
|
|
219
|
+
error: error instanceof Error ? error.message : "Network error"
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// packages/react/src/ciam-provider.tsx
|
|
225
|
+
import { jsx } from "react/jsx-runtime";
|
|
226
|
+
var DEFAULT_STORAGE_KEY = "ion.ciam.customer";
|
|
227
|
+
var LOGOUT_EVENT = "ion-ciam-logout";
|
|
228
|
+
var PROVIDER_LABEL = "CIAMProvider";
|
|
229
|
+
var customerCodec = {
|
|
230
|
+
validate(raw) {
|
|
231
|
+
if (!raw || typeof raw !== "object")
|
|
232
|
+
return null;
|
|
233
|
+
const candidate = raw;
|
|
234
|
+
if (typeof candidate.userName !== "string" || typeof candidate.name !== "string" || typeof candidate.email !== "string") {
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
return raw;
|
|
238
|
+
},
|
|
239
|
+
revive(customer) {
|
|
240
|
+
if (customer.ciam?.expires) {
|
|
241
|
+
customer.ciam.expires = new Date(customer.ciam.expires);
|
|
242
|
+
}
|
|
243
|
+
return customer;
|
|
244
|
+
},
|
|
245
|
+
isExpired(customer) {
|
|
246
|
+
const expires = customer.ciam?.expires;
|
|
247
|
+
if (!expires)
|
|
248
|
+
return false;
|
|
249
|
+
const time = expires instanceof Date ? expires.getTime() : new Date(expires).getTime();
|
|
250
|
+
return Number.isFinite(time) && time <= Date.now();
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
var CTX = createContext(undefined);
|
|
254
|
+
function CIAMProvider({
|
|
255
|
+
storageKey: storageKeyProp,
|
|
256
|
+
customerUrl,
|
|
257
|
+
storage = "memory",
|
|
258
|
+
disableListener = false,
|
|
259
|
+
fetchInit,
|
|
260
|
+
log = silentLogger2,
|
|
261
|
+
children
|
|
262
|
+
}) {
|
|
263
|
+
const storageKey = resolveStorageKey(DEFAULT_STORAGE_KEY, storageKeyProp, PROVIDER_LABEL);
|
|
264
|
+
const { value, setValue, isLoading, error } = useAuthResource({
|
|
265
|
+
url: customerUrl,
|
|
266
|
+
storage,
|
|
267
|
+
storageKey,
|
|
268
|
+
disableListener,
|
|
269
|
+
logoutEventName: LOGOUT_EVENT,
|
|
270
|
+
codec: customerCodec,
|
|
271
|
+
fetchInit,
|
|
272
|
+
log
|
|
273
|
+
});
|
|
274
|
+
const contextValue = useMemo(() => ({ customer: value, setCustomer: setValue, isLoading, error }), [value, setValue, isLoading, error]);
|
|
275
|
+
return /* @__PURE__ */ jsx(CTX.Provider, {
|
|
276
|
+
value: contextValue,
|
|
277
|
+
children
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
function useCIAMContext() {
|
|
281
|
+
const context = useContext(CTX);
|
|
282
|
+
if (context === undefined) {
|
|
283
|
+
throw new Error("useCIAMContext must be used within a CIAMProvider");
|
|
284
|
+
}
|
|
285
|
+
return context;
|
|
286
|
+
}
|
|
287
|
+
function useOptionalCIAMContext() {
|
|
288
|
+
return useContext(CTX);
|
|
289
|
+
}
|
|
290
|
+
async function getCustomer(customerUrl, options) {
|
|
291
|
+
return getResource(customerUrl, customerCodec, {
|
|
292
|
+
storageKey: resolveStorageKey(DEFAULT_STORAGE_KEY, options?.storageKey, PROVIDER_LABEL),
|
|
293
|
+
storage: options?.storage ?? "memory",
|
|
294
|
+
fetchInit: options?.fetchInit,
|
|
295
|
+
log: options?.log
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
async function logoutCustomer(logoutUrl, options) {
|
|
299
|
+
return logoutResource(logoutUrl, { defaultStorageKey: DEFAULT_STORAGE_KEY, logoutEventName: LOGOUT_EVENT, providerLabel: PROVIDER_LABEL }, options);
|
|
300
|
+
}
|
|
301
|
+
// packages/react/src/ciam-providers.tsx
|
|
302
|
+
import { silentLogger as silentLogger3 } from "@ionite/core";
|
|
303
|
+
import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
|
|
304
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
305
|
+
function useCIAMProviders(providersUrl, log = silentLogger3) {
|
|
306
|
+
const [providers, setProviders] = useState2([]);
|
|
307
|
+
const [isLoading, setIsLoading] = useState2(Boolean(providersUrl));
|
|
308
|
+
const [error, setError] = useState2(null);
|
|
309
|
+
const refresh = useCallback2(() => {
|
|
310
|
+
if (!providersUrl)
|
|
311
|
+
return;
|
|
312
|
+
let cancelled = false;
|
|
313
|
+
setIsLoading(true);
|
|
314
|
+
setError(null);
|
|
315
|
+
fetch(providersUrl, { headers: { Accept: "application/json" } }).then(async (response) => {
|
|
316
|
+
if (!response.ok) {
|
|
317
|
+
throw new Error(`Failed to fetch CIAM providers: ${response.status} ${response.statusText}`);
|
|
318
|
+
}
|
|
319
|
+
const data = await response.json();
|
|
320
|
+
if (!cancelled)
|
|
321
|
+
setProviders(Array.isArray(data.providers) ? data.providers : []);
|
|
322
|
+
}).catch((err) => {
|
|
323
|
+
const normalized = err instanceof Error ? err : new Error(String(err));
|
|
324
|
+
log.error("Error fetching CIAM providers:", normalized);
|
|
325
|
+
if (!cancelled) {
|
|
326
|
+
setProviders([]);
|
|
327
|
+
setError(normalized);
|
|
328
|
+
}
|
|
329
|
+
}).finally(() => {
|
|
330
|
+
if (!cancelled)
|
|
331
|
+
setIsLoading(false);
|
|
332
|
+
});
|
|
333
|
+
return () => {
|
|
334
|
+
cancelled = true;
|
|
335
|
+
};
|
|
336
|
+
}, [providersUrl, log]);
|
|
337
|
+
useEffect2(() => {
|
|
338
|
+
const cleanup = refresh();
|
|
339
|
+
return cleanup;
|
|
340
|
+
}, [refresh]);
|
|
341
|
+
return { providers, isLoading, error, refresh };
|
|
342
|
+
}
|
|
343
|
+
function providerButtonStyle(provider, theme) {
|
|
344
|
+
const background = theme === "dark" ? provider.colors?.dark : provider.colors?.light;
|
|
345
|
+
return {
|
|
346
|
+
display: "flex",
|
|
347
|
+
alignItems: "center",
|
|
348
|
+
gap: "0.5rem",
|
|
349
|
+
width: "100%",
|
|
350
|
+
padding: "0.625rem 0.875rem",
|
|
351
|
+
border: "1px solid rgba(0,0,0,0.15)",
|
|
352
|
+
borderRadius: "0.5rem",
|
|
353
|
+
background: background ?? (theme === "dark" ? "#1f1f1f" : "#ffffff"),
|
|
354
|
+
color: theme === "dark" ? "#ffffff" : "#111111",
|
|
355
|
+
textDecoration: "none",
|
|
356
|
+
font: "inherit",
|
|
357
|
+
cursor: "pointer"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function CIAMProviderList({
|
|
361
|
+
providersUrl,
|
|
362
|
+
limit,
|
|
363
|
+
theme = "light",
|
|
364
|
+
renderProvider,
|
|
365
|
+
loadingFallback,
|
|
366
|
+
emptyFallback,
|
|
367
|
+
errorFallback,
|
|
368
|
+
moreLabel = "Show more",
|
|
369
|
+
className,
|
|
370
|
+
log = silentLogger3
|
|
371
|
+
}) {
|
|
372
|
+
const { providers, isLoading, error, refresh } = useCIAMProviders(providersUrl, log);
|
|
373
|
+
const [expanded, setExpanded] = useState2(false);
|
|
374
|
+
if (isLoading) {
|
|
375
|
+
return loadingFallback ?? null;
|
|
376
|
+
}
|
|
377
|
+
if (error) {
|
|
378
|
+
return errorFallback ? errorFallback(error, refresh) : emptyFallback ?? null;
|
|
379
|
+
}
|
|
380
|
+
if (providers.length === 0) {
|
|
381
|
+
return emptyFallback ?? null;
|
|
382
|
+
}
|
|
383
|
+
const visible = !expanded && typeof limit === "number" && limit > 0 ? providers.slice(0, limit) : providers;
|
|
384
|
+
const hasMore = visible.length < providers.length;
|
|
385
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
386
|
+
className,
|
|
387
|
+
style: { display: "flex", flexDirection: "column", gap: "0.5rem" },
|
|
388
|
+
"data-ion-ciam-providers": "",
|
|
389
|
+
children: [
|
|
390
|
+
visible.map((provider) => renderProvider ? /* @__PURE__ */ jsx2("span", {
|
|
391
|
+
children: renderProvider(provider)
|
|
392
|
+
}, provider.name) : /* @__PURE__ */ jsxs("a", {
|
|
393
|
+
href: provider.loginUrl,
|
|
394
|
+
style: providerButtonStyle(provider, theme),
|
|
395
|
+
"data-ion-ciam-provider": provider.name,
|
|
396
|
+
children: [
|
|
397
|
+
provider.iconUrl ? /* @__PURE__ */ jsx2("img", {
|
|
398
|
+
src: provider.iconUrl,
|
|
399
|
+
alt: "",
|
|
400
|
+
width: 20,
|
|
401
|
+
height: 20,
|
|
402
|
+
"aria-hidden": "true"
|
|
403
|
+
}) : null,
|
|
404
|
+
/* @__PURE__ */ jsxs("span", {
|
|
405
|
+
children: [
|
|
406
|
+
"Continue with ",
|
|
407
|
+
provider.displayName
|
|
408
|
+
]
|
|
409
|
+
})
|
|
410
|
+
]
|
|
411
|
+
}, provider.name)),
|
|
412
|
+
hasMore ? /* @__PURE__ */ jsx2("button", {
|
|
413
|
+
type: "button",
|
|
414
|
+
onClick: () => setExpanded(true),
|
|
415
|
+
"data-ion-ciam-show-more": "",
|
|
416
|
+
children: moreLabel
|
|
417
|
+
}) : null
|
|
418
|
+
]
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
// packages/react/src/sso-provider.tsx
|
|
422
|
+
import { silentLogger as silentLogger4 } from "@ionite/core";
|
|
423
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
424
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
425
|
+
var DEFAULT_STORAGE_KEY2 = "ion.sso.user";
|
|
426
|
+
var LOGOUT_EVENT2 = "ion-sso-logout";
|
|
427
|
+
var PROVIDER_LABEL2 = "SSOProvider";
|
|
428
|
+
var userCodec = {
|
|
429
|
+
validate(raw) {
|
|
430
|
+
if (!raw || typeof raw !== "object")
|
|
431
|
+
return null;
|
|
432
|
+
const candidate = raw;
|
|
433
|
+
if (typeof candidate.userName !== "string" || typeof candidate.name !== "string" || typeof candidate.email !== "string") {
|
|
434
|
+
return null;
|
|
435
|
+
}
|
|
436
|
+
return raw;
|
|
437
|
+
},
|
|
438
|
+
revive(user) {
|
|
439
|
+
if (user.sso?.expires) {
|
|
440
|
+
user.sso.expires = new Date(user.sso.expires);
|
|
441
|
+
}
|
|
442
|
+
return user;
|
|
443
|
+
},
|
|
444
|
+
isExpired(user) {
|
|
445
|
+
const expires = user.sso?.expires;
|
|
446
|
+
if (!expires)
|
|
447
|
+
return false;
|
|
448
|
+
const time = expires instanceof Date ? expires.getTime() : new Date(expires).getTime();
|
|
449
|
+
return Number.isFinite(time) && time <= Date.now();
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
var CTX2 = createContext2(undefined);
|
|
453
|
+
function SSOProvider({
|
|
454
|
+
storageKey: storageKeyProp,
|
|
455
|
+
userUrl,
|
|
456
|
+
storage = "memory",
|
|
457
|
+
disableListener = false,
|
|
458
|
+
fetchInit,
|
|
459
|
+
log = silentLogger4,
|
|
460
|
+
children
|
|
461
|
+
}) {
|
|
462
|
+
const storageKey = resolveStorageKey(DEFAULT_STORAGE_KEY2, storageKeyProp, PROVIDER_LABEL2);
|
|
463
|
+
const { value, setValue, isLoading, error } = useAuthResource({
|
|
464
|
+
url: userUrl,
|
|
465
|
+
storage,
|
|
466
|
+
storageKey,
|
|
467
|
+
disableListener,
|
|
468
|
+
logoutEventName: LOGOUT_EVENT2,
|
|
469
|
+
codec: userCodec,
|
|
470
|
+
fetchInit,
|
|
471
|
+
log
|
|
472
|
+
});
|
|
473
|
+
const contextValue = useMemo2(() => ({ user: value, setUser: setValue, isLoading, error }), [value, setValue, isLoading, error]);
|
|
474
|
+
return /* @__PURE__ */ jsx3(CTX2.Provider, {
|
|
475
|
+
value: contextValue,
|
|
476
|
+
children
|
|
477
|
+
});
|
|
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 logout(logoutUrl, options) {
|
|
490
|
+
return logoutResource(logoutUrl, { defaultStorageKey: DEFAULT_STORAGE_KEY2, logoutEventName: LOGOUT_EVENT2, providerLabel: PROVIDER_LABEL2 }, options);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// packages/react/src/sign-in-loading.tsx
|
|
494
|
+
import { jsx as jsx4, Fragment } from "react/jsx-runtime";
|
|
495
|
+
function SignInLoading({
|
|
496
|
+
complete = false,
|
|
497
|
+
audience = "workforce",
|
|
498
|
+
children
|
|
499
|
+
}) {
|
|
500
|
+
const sso = useOptionalSSOContext();
|
|
501
|
+
const ciam = useOptionalCIAMContext();
|
|
502
|
+
const isLoading = audience === "customer" ? !!ciam?.isLoading : !!sso?.isLoading;
|
|
503
|
+
if (isLoading && !complete)
|
|
504
|
+
return /* @__PURE__ */ jsx4(Fragment, {
|
|
505
|
+
children
|
|
506
|
+
});
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
// packages/react/src/signed-in.tsx
|
|
510
|
+
import { jsx as jsx5, Fragment as Fragment2 } from "react/jsx-runtime";
|
|
511
|
+
function SignedIn({
|
|
512
|
+
audience = "workforce",
|
|
513
|
+
children
|
|
514
|
+
}) {
|
|
515
|
+
const sso = useOptionalSSOContext();
|
|
516
|
+
const ciam = useOptionalCIAMContext();
|
|
517
|
+
const present = audience === "customer" ? !!ciam?.customer : !!sso?.user;
|
|
518
|
+
if (present)
|
|
519
|
+
return /* @__PURE__ */ jsx5(Fragment2, {
|
|
520
|
+
children
|
|
521
|
+
});
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
// packages/react/src/signed-out.tsx
|
|
525
|
+
import { jsx as jsx6, Fragment as Fragment3 } from "react/jsx-runtime";
|
|
526
|
+
function SignedOut({
|
|
527
|
+
audience = "workforce",
|
|
528
|
+
children
|
|
529
|
+
}) {
|
|
530
|
+
const sso = useOptionalSSOContext();
|
|
531
|
+
const ciam = useOptionalCIAMContext();
|
|
532
|
+
const present = audience === "customer" ? !!ciam?.customer : !!sso?.user;
|
|
533
|
+
const isLoading = audience === "customer" ? !!ciam?.isLoading : !!sso?.isLoading;
|
|
534
|
+
if (present || isLoading)
|
|
535
|
+
return null;
|
|
536
|
+
return /* @__PURE__ */ jsx6(Fragment3, {
|
|
537
|
+
children
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
// packages/react/src/tenant-context.tsx
|
|
541
|
+
import {
|
|
542
|
+
buildTenantPath,
|
|
543
|
+
DEFAULT_TENANT_API_NAMESPACE,
|
|
544
|
+
DEFAULT_TENANT_UI_NAMESPACE,
|
|
545
|
+
normalizeTenantRoutingStrategy
|
|
546
|
+
} from "@ionite/core";
|
|
547
|
+
import { createContext as createContext3, useContext as useContext3, useMemo as useMemo3 } from "react";
|
|
548
|
+
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
549
|
+
var TenantContext = createContext3(null);
|
|
550
|
+
function createTenantUrls(id, strategy) {
|
|
551
|
+
const normalized = normalizeTenantRoutingStrategy(strategy);
|
|
552
|
+
const uiNamespace = normalized.ui ?? DEFAULT_TENANT_UI_NAMESPACE;
|
|
553
|
+
const apiNamespace = normalized.api ?? DEFAULT_TENANT_API_NAMESPACE;
|
|
554
|
+
return {
|
|
555
|
+
ui: (restPath = "/") => buildTenantPath(id, restPath, uiNamespace),
|
|
556
|
+
api: (restPath = "/") => buildTenantPath(id, restPath, apiNamespace)
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
function TenantProvider({ id, strategy, children }) {
|
|
560
|
+
const value = useMemo3(() => {
|
|
561
|
+
const normalized = normalizeTenantRoutingStrategy(strategy);
|
|
562
|
+
return {
|
|
563
|
+
id,
|
|
564
|
+
strategy: normalized,
|
|
565
|
+
urls: createTenantUrls(id, normalized)
|
|
566
|
+
};
|
|
567
|
+
}, [strategy, id]);
|
|
568
|
+
return /* @__PURE__ */ jsx7(TenantContext.Provider, {
|
|
569
|
+
value,
|
|
570
|
+
children
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
function useTenant() {
|
|
574
|
+
const value = useContext3(TenantContext);
|
|
575
|
+
if (!value) {
|
|
576
|
+
throw new Error("useTenant() must be used within a TenantProvider");
|
|
577
|
+
}
|
|
578
|
+
return value;
|
|
579
|
+
}
|
|
580
|
+
function useOptionalTenant() {
|
|
581
|
+
return useContext3(TenantContext) ?? undefined;
|
|
582
|
+
}
|
|
583
|
+
export {
|
|
584
|
+
useTenant,
|
|
585
|
+
useSSOContext,
|
|
586
|
+
useOptionalTenant,
|
|
587
|
+
useOptionalSSOContext,
|
|
588
|
+
useOptionalCIAMContext,
|
|
589
|
+
useCIAMProviders,
|
|
590
|
+
useCIAMContext,
|
|
591
|
+
silentLogger5 as silentLogger,
|
|
592
|
+
logoutCustomer,
|
|
593
|
+
logout,
|
|
594
|
+
getCustomer,
|
|
595
|
+
defaultLogger,
|
|
596
|
+
createTenantUrls,
|
|
597
|
+
TenantProvider,
|
|
598
|
+
SignedOut,
|
|
599
|
+
SignedIn,
|
|
600
|
+
SignInLoading,
|
|
601
|
+
SSOProvider,
|
|
602
|
+
CIAMProviderList,
|
|
603
|
+
CIAMProvider
|
|
604
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ionite/react",
|
|
3
|
-
"version": "0.0.24-beta.20260907.
|
|
3
|
+
"version": "0.0.24-beta.20260907.3",
|
|
4
4
|
"description": "ionite React Components",
|
|
5
5
|
"private": false,
|
|
6
6
|
"author": "ionite",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"./package.json": "./package.json"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@ionite/core": "0.0.24-beta.20260907.
|
|
27
|
+
"@ionite/core": "0.0.24-beta.20260907.3"
|
|
28
28
|
},
|
|
29
29
|
"peerDependencies": {
|
|
30
30
|
"react": "^18.0.0 || ^19.0.0",
|