@oxyhq/auth 2.0.8 → 2.0.9
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 +3 -2
- package/dist/cjs/.tsbuildinfo +1 -1
- package/dist/cjs/WebOxyProvider.js +15 -34
- package/dist/cjs/hooks/useWebSSO.js +44 -7
- package/dist/esm/.tsbuildinfo +1 -1
- package/dist/esm/WebOxyProvider.js +15 -34
- package/dist/esm/hooks/useWebSSO.js +44 -7
- package/dist/types/.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/WebOxyProvider.tsx +16 -34
- package/src/hooks/useWebSSO.ts +46 -7
|
@@ -17,36 +17,19 @@ const react_query_1 = require("@tanstack/react-query");
|
|
|
17
17
|
const queryClient_1 = require("./hooks/queryClient");
|
|
18
18
|
const WebOxyContext = (0, react_1.createContext)(null);
|
|
19
19
|
/**
|
|
20
|
-
* Module-level run-once guard for
|
|
20
|
+
* Module-level run-once guard for FedCM silent sign-in.
|
|
21
21
|
*
|
|
22
22
|
* The init effect runs again whenever the provider remounts (route change,
|
|
23
23
|
* StrictMode double-invoke, error-boundary recovery). The redirect-callback
|
|
24
|
-
* and local-session-restore steps are cheap and idempotent, but
|
|
25
|
-
* `
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* The FedCM leg is now centrally memoized inside `@oxyhq/core`'s
|
|
30
|
-
* `silentSignInWithFedCM` (at-most-once `navigator.credentials.get` per page
|
|
31
|
-
* load). The iframe fallback, however, is a SEPARATE mechanism the core guard
|
|
32
|
-
* does not cover — without this guard a remount storm would create a hidden
|
|
33
|
-
* iframe per remount. So this guard is intentionally retained to keep the
|
|
34
|
-
* provider's WHOLE silent step run-once. Keyed on `origin + baseURL` to match
|
|
35
|
-
* the core guard's keying (so the two stay in lockstep) and to survive
|
|
36
|
-
* instance churn; never cleared because only a fresh page load can change the
|
|
37
|
-
* IdP/iframe session state.
|
|
24
|
+
* and local-session-restore steps are cheap and idempotent, but the FedCM
|
|
25
|
+
* `silentSignIn()` step triggers `navigator.credentials.get`, which must fire
|
|
26
|
+
* AT MOST ONCE per page load — otherwise a remount storm becomes a credential
|
|
27
|
+
* request storm. Keyed by origin so the guard survives instance churn; never
|
|
28
|
+
* cleared because only a fresh page load can change the IdP session state.
|
|
38
29
|
*/
|
|
39
|
-
const
|
|
40
|
-
function silentSignInKey(
|
|
41
|
-
|
|
42
|
-
let baseURL = '';
|
|
43
|
-
try {
|
|
44
|
-
baseURL = oxyServices.getBaseURL();
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
baseURL = '';
|
|
48
|
-
}
|
|
49
|
-
return `${origin}|${baseURL}`;
|
|
30
|
+
const fedcmSilentSignInAttempted = new Set();
|
|
31
|
+
function silentSignInKey() {
|
|
32
|
+
return typeof window !== 'undefined' ? window.location.origin : 'no-origin';
|
|
50
33
|
}
|
|
51
34
|
/**
|
|
52
35
|
* Web-only Oxy Provider
|
|
@@ -149,14 +132,12 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
|
|
|
149
132
|
await authManager.signOut();
|
|
150
133
|
}
|
|
151
134
|
}
|
|
152
|
-
//
|
|
153
|
-
// change / StrictMode / error recovery) must not re-trigger
|
|
154
|
-
// browser credential request
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (!silentSignInAttempted.has(ssoKey)) {
|
|
159
|
-
silentSignInAttempted.add(ssoKey);
|
|
135
|
+
// FedCM silent sign-in: run AT MOST ONCE per page load. A remount
|
|
136
|
+
// (route change / StrictMode / error recovery) must not re-trigger
|
|
137
|
+
// the browser credential request.
|
|
138
|
+
const ssoKey = silentSignInKey();
|
|
139
|
+
if (!fedcmSilentSignInAttempted.has(ssoKey)) {
|
|
140
|
+
fedcmSilentSignInAttempted.add(ssoKey);
|
|
160
141
|
try {
|
|
161
142
|
const session = await crossDomainAuth.silentSignIn();
|
|
162
143
|
if (mounted && session?.user) {
|
|
@@ -19,6 +19,35 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
19
19
|
exports.useWebSSO = useWebSSO;
|
|
20
20
|
exports.isWebBrowser = isWebBrowser;
|
|
21
21
|
const react_1 = require("react");
|
|
22
|
+
/**
|
|
23
|
+
* Module-level guard tracking which (origin + API) signatures have already
|
|
24
|
+
* had a silent SSO attempt this page load.
|
|
25
|
+
*
|
|
26
|
+
* A per-component `useRef` guard resets whenever the provider remounts (route
|
|
27
|
+
* churn, StrictMode double-invoke, error-boundary recovery), which previously
|
|
28
|
+
* allowed silent SSO to re-fire and — combined with a routing redirect loop —
|
|
29
|
+
* produced an accelerating `navigator.credentials.get` retry storm. Keying the
|
|
30
|
+
* guard on a stable signature instead of the component instance makes silent
|
|
31
|
+
* SSO fire EXACTLY ONCE per page load regardless of how many times the
|
|
32
|
+
* provider mounts. The set is intentionally never cleared: a fresh page load
|
|
33
|
+
* (the only thing that can change the answer) starts a fresh module scope.
|
|
34
|
+
*/
|
|
35
|
+
const silentSSOAttempted = new Set();
|
|
36
|
+
/**
|
|
37
|
+
* Build a stable signature for the silent-SSO run-once guard. Two providers
|
|
38
|
+
* pointed at the same API from the same origin share one attempt.
|
|
39
|
+
*/
|
|
40
|
+
function ssoSignature(oxyServices) {
|
|
41
|
+
const origin = typeof window !== 'undefined' ? window.location.origin : 'no-origin';
|
|
42
|
+
let baseURL = '';
|
|
43
|
+
try {
|
|
44
|
+
baseURL = oxyServices.getBaseURL();
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
baseURL = '';
|
|
48
|
+
}
|
|
49
|
+
return `${origin}|${baseURL}`;
|
|
50
|
+
}
|
|
22
51
|
/**
|
|
23
52
|
* Check if we're running in a web browser environment (not React Native)
|
|
24
53
|
*/
|
|
@@ -123,12 +152,12 @@ function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onError, ena
|
|
|
123
152
|
}, [oxyServices, onSessionFound, onError, fedCMSupported]);
|
|
124
153
|
// Auto-check SSO on mount (web only, FedCM only, not on auth domain).
|
|
125
154
|
//
|
|
126
|
-
//
|
|
127
|
-
//
|
|
128
|
-
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
155
|
+
// Run-once is enforced by TWO guards:
|
|
156
|
+
// 1. `hasCheckedRef` — cheap per-instance fast-path so effect re-runs
|
|
157
|
+
// (from changing deps) within one mount never re-fire.
|
|
158
|
+
// 2. `silentSSOAttempted` — module-level, survives remounts/StrictMode so
|
|
159
|
+
// silent SSO fires exactly once per page load even if the provider
|
|
160
|
+
// unmounts and remounts.
|
|
132
161
|
(0, react_1.useEffect)(() => {
|
|
133
162
|
if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider()) {
|
|
134
163
|
if (isIdentityProvider()) {
|
|
@@ -136,14 +165,22 @@ function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onError, ena
|
|
|
136
165
|
}
|
|
137
166
|
return;
|
|
138
167
|
}
|
|
168
|
+
const signature = ssoSignature(oxyServices);
|
|
169
|
+
if (silentSSOAttempted.has(signature)) {
|
|
170
|
+
// Already attempted this page load (e.g. before a remount) — do not
|
|
171
|
+
// re-fire. Mark the local fast-path too so subsequent re-renders skip.
|
|
172
|
+
hasCheckedRef.current = true;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
139
175
|
hasCheckedRef.current = true;
|
|
176
|
+
silentSSOAttempted.add(signature);
|
|
140
177
|
if (fedCMSupported) {
|
|
141
178
|
checkSSO();
|
|
142
179
|
}
|
|
143
180
|
else {
|
|
144
181
|
onSSOUnavailable?.();
|
|
145
182
|
}
|
|
146
|
-
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
|
|
183
|
+
}, [enabled, checkSSO, fedCMSupported, onSSOUnavailable, oxyServices]);
|
|
147
184
|
return {
|
|
148
185
|
checkSSO,
|
|
149
186
|
signInWithFedCM,
|