@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
|
@@ -12,19 +12,36 @@ import { QueryClientProvider } from '@tanstack/react-query';
|
|
|
12
12
|
import { attachQueryPersistence, createQueryClient } from './hooks/queryClient';
|
|
13
13
|
const WebOxyContext = createContext(null);
|
|
14
14
|
/**
|
|
15
|
-
* Module-level run-once guard for
|
|
15
|
+
* Module-level run-once guard for the provider's silent sign-in step.
|
|
16
16
|
*
|
|
17
17
|
* The init effect runs again whenever the provider remounts (route change,
|
|
18
18
|
* StrictMode double-invoke, error-boundary recovery). The redirect-callback
|
|
19
|
-
* and local-session-restore steps are cheap and idempotent, but
|
|
20
|
-
* `silentSignIn()`
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* and local-session-restore steps are cheap and idempotent, but
|
|
20
|
+
* `crossDomainAuth.silentSignIn()` is NOT: it first tries FedCM silent
|
|
21
|
+
* mediation (`navigator.credentials.get`) and, if that yields nothing, falls
|
|
22
|
+
* back to an iframe-based silent auth against `/auth/silent`.
|
|
23
|
+
*
|
|
24
|
+
* The FedCM leg is now centrally memoized inside `@oxyhq/core`'s
|
|
25
|
+
* `silentSignInWithFedCM` (at-most-once `navigator.credentials.get` per page
|
|
26
|
+
* load). The iframe fallback, however, is a SEPARATE mechanism the core guard
|
|
27
|
+
* does not cover — without this guard a remount storm would create a hidden
|
|
28
|
+
* iframe per remount. So this guard is intentionally retained to keep the
|
|
29
|
+
* provider's WHOLE silent step run-once. Keyed on `origin + baseURL` to match
|
|
30
|
+
* the core guard's keying (so the two stay in lockstep) and to survive
|
|
31
|
+
* instance churn; never cleared because only a fresh page load can change the
|
|
32
|
+
* IdP/iframe session state.
|
|
24
33
|
*/
|
|
25
|
-
const
|
|
26
|
-
function silentSignInKey() {
|
|
27
|
-
|
|
34
|
+
const silentSignInAttempted = new Set();
|
|
35
|
+
function silentSignInKey(oxyServices) {
|
|
36
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : 'no-origin';
|
|
37
|
+
let baseURL = '';
|
|
38
|
+
try {
|
|
39
|
+
baseURL = oxyServices.getBaseURL();
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
baseURL = '';
|
|
43
|
+
}
|
|
44
|
+
return `${origin}|${baseURL}`;
|
|
28
45
|
}
|
|
29
46
|
/**
|
|
30
47
|
* Web-only Oxy Provider
|
|
@@ -127,12 +144,14 @@ export function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChang
|
|
|
127
144
|
await authManager.signOut();
|
|
128
145
|
}
|
|
129
146
|
}
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
147
|
+
// Silent sign-in: run AT MOST ONCE per page load. A remount (route
|
|
148
|
+
// change / StrictMode / error recovery) must not re-trigger the
|
|
149
|
+
// browser credential request OR the iframe fallback. The FedCM leg is
|
|
150
|
+
// additionally memoized in core; this guard also covers the iframe
|
|
151
|
+
// fallback that core does not.
|
|
152
|
+
const ssoKey = silentSignInKey(oxyServices);
|
|
153
|
+
if (!silentSignInAttempted.has(ssoKey)) {
|
|
154
|
+
silentSignInAttempted.add(ssoKey);
|
|
136
155
|
try {
|
|
137
156
|
const session = await crossDomainAuth.silentSignIn();
|
|
138
157
|
if (mounted && session?.user) {
|
|
@@ -15,35 +15,6 @@
|
|
|
15
15
|
* @see https://developer.mozilla.org/en-US/docs/Web/API/FedCM_API
|
|
16
16
|
*/
|
|
17
17
|
import { useEffect, useRef, useCallback } from 'react';
|
|
18
|
-
/**
|
|
19
|
-
* Module-level guard tracking which (origin + API) signatures have already
|
|
20
|
-
* had a silent SSO attempt this page load.
|
|
21
|
-
*
|
|
22
|
-
* A per-component `useRef` guard resets whenever the provider remounts (route
|
|
23
|
-
* churn, StrictMode double-invoke, error-boundary recovery), which previously
|
|
24
|
-
* allowed silent SSO to re-fire and — combined with a routing redirect loop —
|
|
25
|
-
* produced an accelerating `navigator.credentials.get` retry storm. Keying the
|
|
26
|
-
* guard on a stable signature instead of the component instance makes silent
|
|
27
|
-
* SSO fire EXACTLY ONCE per page load regardless of how many times the
|
|
28
|
-
* provider mounts. The set is intentionally never cleared: a fresh page load
|
|
29
|
-
* (the only thing that can change the answer) starts a fresh module scope.
|
|
30
|
-
*/
|
|
31
|
-
const silentSSOAttempted = new Set();
|
|
32
|
-
/**
|
|
33
|
-
* Build a stable signature for the silent-SSO run-once guard. Two providers
|
|
34
|
-
* pointed at the same API from the same origin share one attempt.
|
|
35
|
-
*/
|
|
36
|
-
function ssoSignature(oxyServices) {
|
|
37
|
-
const origin = typeof window !== 'undefined' ? window.location.origin : 'no-origin';
|
|
38
|
-
let baseURL = '';
|
|
39
|
-
try {
|
|
40
|
-
baseURL = oxyServices.getBaseURL();
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
baseURL = '';
|
|
44
|
-
}
|
|
45
|
-
return `${origin}|${baseURL}`;
|
|
46
|
-
}
|
|
47
18
|
/**
|
|
48
19
|
* Check if we're running in a web browser environment (not React Native)
|
|
49
20
|
*/
|
|
@@ -148,12 +119,12 @@ export function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onErr
|
|
|
148
119
|
}, [oxyServices, onSessionFound, onError, fedCMSupported]);
|
|
149
120
|
// Auto-check SSO on mount (web only, FedCM only, not on auth domain).
|
|
150
121
|
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
//
|
|
122
|
+
// `hasCheckedRef` is a cheap per-instance fast-path so effect re-runs (from
|
|
123
|
+
// changing deps) within one mount never re-fire. The page-load run-once
|
|
124
|
+
// guarantee — silent SSO invoking `navigator.credentials.get` AT MOST ONCE
|
|
125
|
+
// per page load across remounts / StrictMode / multiple consumers — lives in
|
|
126
|
+
// `@oxyhq/core`'s `silentSignInWithFedCM`, which memoizes the first silent
|
|
127
|
+
// attempt's result for this origin + API. The hook therefore calls it freely.
|
|
157
128
|
useEffect(() => {
|
|
158
129
|
if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider()) {
|
|
159
130
|
if (isIdentityProvider()) {
|
|
@@ -161,22 +132,14 @@ export function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onErr
|
|
|
161
132
|
}
|
|
162
133
|
return;
|
|
163
134
|
}
|
|
164
|
-
const signature = ssoSignature(oxyServices);
|
|
165
|
-
if (silentSSOAttempted.has(signature)) {
|
|
166
|
-
// Already attempted this page load (e.g. before a remount) — do not
|
|
167
|
-
// re-fire. Mark the local fast-path too so subsequent re-renders skip.
|
|
168
|
-
hasCheckedRef.current = true;
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
135
|
hasCheckedRef.current = true;
|
|
172
|
-
silentSSOAttempted.add(signature);
|
|
173
136
|
if (fedCMSupported) {
|
|
174
137
|
checkSSO();
|
|
175
138
|
}
|
|
176
139
|
else {
|
|
177
140
|
onSSOUnavailable?.();
|
|
178
141
|
}
|
|
179
|
-
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable
|
|
142
|
+
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
|
|
180
143
|
return {
|
|
181
144
|
checkSSO,
|
|
182
145
|
signInWithFedCM,
|