@oxyhq/auth 2.0.7 → 2.0.8
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 +15 -1
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/WebOxyProvider.js +34 -15
- package/dist/cjs/hooks/useWebSSO.js +7 -44
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/WebOxyProvider.js +34 -15
- package/dist/esm/hooks/useWebSSO.js +7 -44
- package/dist/types/.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/WebOxyProvider.tsx +34 -16
- package/src/hooks/useWebSSO.ts +7 -46
package/package.json
CHANGED
package/src/WebOxyProvider.tsx
CHANGED
|
@@ -59,20 +59,36 @@ export interface WebOxyContextValue extends WebAuthState, WebAuthActions {
|
|
|
59
59
|
const WebOxyContext = createContext<WebOxyContextValue | null>(null);
|
|
60
60
|
|
|
61
61
|
/**
|
|
62
|
-
* Module-level run-once guard for
|
|
62
|
+
* Module-level run-once guard for the provider's silent sign-in step.
|
|
63
63
|
*
|
|
64
64
|
* The init effect runs again whenever the provider remounts (route change,
|
|
65
65
|
* StrictMode double-invoke, error-boundary recovery). The redirect-callback
|
|
66
|
-
* and local-session-restore steps are cheap and idempotent, but
|
|
67
|
-
* `silentSignIn()`
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
66
|
+
* and local-session-restore steps are cheap and idempotent, but
|
|
67
|
+
* `crossDomainAuth.silentSignIn()` is NOT: it first tries FedCM silent
|
|
68
|
+
* mediation (`navigator.credentials.get`) and, if that yields nothing, falls
|
|
69
|
+
* back to an iframe-based silent auth against `/auth/silent`.
|
|
70
|
+
*
|
|
71
|
+
* The FedCM leg is now centrally memoized inside `@oxyhq/core`'s
|
|
72
|
+
* `silentSignInWithFedCM` (at-most-once `navigator.credentials.get` per page
|
|
73
|
+
* load). The iframe fallback, however, is a SEPARATE mechanism the core guard
|
|
74
|
+
* does not cover — without this guard a remount storm would create a hidden
|
|
75
|
+
* iframe per remount. So this guard is intentionally retained to keep the
|
|
76
|
+
* provider's WHOLE silent step run-once. Keyed on `origin + baseURL` to match
|
|
77
|
+
* the core guard's keying (so the two stay in lockstep) and to survive
|
|
78
|
+
* instance churn; never cleared because only a fresh page load can change the
|
|
79
|
+
* IdP/iframe session state.
|
|
71
80
|
*/
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
function silentSignInKey(): string {
|
|
75
|
-
|
|
81
|
+
const silentSignInAttempted = new Set<string>();
|
|
82
|
+
|
|
83
|
+
function silentSignInKey(oxyServices: OxyServices): string {
|
|
84
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : 'no-origin';
|
|
85
|
+
let baseURL = '';
|
|
86
|
+
try {
|
|
87
|
+
baseURL = oxyServices.getBaseURL();
|
|
88
|
+
} catch {
|
|
89
|
+
baseURL = '';
|
|
90
|
+
}
|
|
91
|
+
return `${origin}|${baseURL}`;
|
|
76
92
|
}
|
|
77
93
|
|
|
78
94
|
export interface WebOxyProviderProps {
|
|
@@ -208,12 +224,14 @@ export function WebOxyProvider({
|
|
|
208
224
|
}
|
|
209
225
|
}
|
|
210
226
|
|
|
211
|
-
//
|
|
212
|
-
//
|
|
213
|
-
//
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
227
|
+
// Silent sign-in: run AT MOST ONCE per page load. A remount (route
|
|
228
|
+
// change / StrictMode / error recovery) must not re-trigger the
|
|
229
|
+
// browser credential request OR the iframe fallback. The FedCM leg is
|
|
230
|
+
// additionally memoized in core; this guard also covers the iframe
|
|
231
|
+
// fallback that core does not.
|
|
232
|
+
const ssoKey = silentSignInKey(oxyServices);
|
|
233
|
+
if (!silentSignInAttempted.has(ssoKey)) {
|
|
234
|
+
silentSignInAttempted.add(ssoKey);
|
|
217
235
|
try {
|
|
218
236
|
const session = await crossDomainAuth.silentSignIn();
|
|
219
237
|
if (mounted && session?.user) {
|
package/src/hooks/useWebSSO.ts
CHANGED
|
@@ -38,36 +38,6 @@ interface UseWebSSOResult {
|
|
|
38
38
|
isFedCMSupported: boolean;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
/**
|
|
42
|
-
* Module-level guard tracking which (origin + API) signatures have already
|
|
43
|
-
* had a silent SSO attempt this page load.
|
|
44
|
-
*
|
|
45
|
-
* A per-component `useRef` guard resets whenever the provider remounts (route
|
|
46
|
-
* churn, StrictMode double-invoke, error-boundary recovery), which previously
|
|
47
|
-
* allowed silent SSO to re-fire and — combined with a routing redirect loop —
|
|
48
|
-
* produced an accelerating `navigator.credentials.get` retry storm. Keying the
|
|
49
|
-
* guard on a stable signature instead of the component instance makes silent
|
|
50
|
-
* SSO fire EXACTLY ONCE per page load regardless of how many times the
|
|
51
|
-
* provider mounts. The set is intentionally never cleared: a fresh page load
|
|
52
|
-
* (the only thing that can change the answer) starts a fresh module scope.
|
|
53
|
-
*/
|
|
54
|
-
const silentSSOAttempted = new Set<string>();
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Build a stable signature for the silent-SSO run-once guard. Two providers
|
|
58
|
-
* pointed at the same API from the same origin share one attempt.
|
|
59
|
-
*/
|
|
60
|
-
function ssoSignature(oxyServices: OxyServices): string {
|
|
61
|
-
const origin = typeof window !== 'undefined' ? window.location.origin : 'no-origin';
|
|
62
|
-
let baseURL = '';
|
|
63
|
-
try {
|
|
64
|
-
baseURL = oxyServices.getBaseURL();
|
|
65
|
-
} catch {
|
|
66
|
-
baseURL = '';
|
|
67
|
-
}
|
|
68
|
-
return `${origin}|${baseURL}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
41
|
/**
|
|
72
42
|
* Check if we're running in a web browser environment (not React Native)
|
|
73
43
|
*/
|
|
@@ -190,12 +160,12 @@ export function useWebSSO({
|
|
|
190
160
|
|
|
191
161
|
// Auto-check SSO on mount (web only, FedCM only, not on auth domain).
|
|
192
162
|
//
|
|
193
|
-
//
|
|
194
|
-
//
|
|
195
|
-
//
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
163
|
+
// `hasCheckedRef` is a cheap per-instance fast-path so effect re-runs (from
|
|
164
|
+
// changing deps) within one mount never re-fire. The page-load run-once
|
|
165
|
+
// guarantee — silent SSO invoking `navigator.credentials.get` AT MOST ONCE
|
|
166
|
+
// per page load across remounts / StrictMode / multiple consumers — lives in
|
|
167
|
+
// `@oxyhq/core`'s `silentSignInWithFedCM`, which memoizes the first silent
|
|
168
|
+
// attempt's result for this origin + API. The hook therefore calls it freely.
|
|
199
169
|
useEffect(() => {
|
|
200
170
|
if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider()) {
|
|
201
171
|
if (isIdentityProvider()) {
|
|
@@ -204,23 +174,14 @@ export function useWebSSO({
|
|
|
204
174
|
return;
|
|
205
175
|
}
|
|
206
176
|
|
|
207
|
-
const signature = ssoSignature(oxyServices);
|
|
208
|
-
if (silentSSOAttempted.has(signature)) {
|
|
209
|
-
// Already attempted this page load (e.g. before a remount) — do not
|
|
210
|
-
// re-fire. Mark the local fast-path too so subsequent re-renders skip.
|
|
211
|
-
hasCheckedRef.current = true;
|
|
212
|
-
return;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
177
|
hasCheckedRef.current = true;
|
|
216
|
-
silentSSOAttempted.add(signature);
|
|
217
178
|
|
|
218
179
|
if (fedCMSupported) {
|
|
219
180
|
checkSSO();
|
|
220
181
|
} else {
|
|
221
182
|
onSSOUnavailable?.();
|
|
222
183
|
}
|
|
223
|
-
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable
|
|
184
|
+
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
|
|
224
185
|
|
|
225
186
|
return {
|
|
226
187
|
checkSSO,
|