@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.
@@ -17,19 +17,36 @@ 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 FedCM silent sign-in.
20
+ * Module-level run-once guard for the provider's silent sign-in step.
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 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.
24
+ * and local-session-restore steps are cheap and idempotent, but
25
+ * `crossDomainAuth.silentSignIn()` is NOT: it first tries FedCM silent
26
+ * mediation (`navigator.credentials.get`) and, if that yields nothing, falls
27
+ * back to an iframe-based silent auth against `/auth/silent`.
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.
29
38
  */
30
- const fedcmSilentSignInAttempted = new Set();
31
- function silentSignInKey() {
32
- return typeof window !== 'undefined' ? window.location.origin : 'no-origin';
39
+ const silentSignInAttempted = new Set();
40
+ function silentSignInKey(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}`;
33
50
  }
34
51
  /**
35
52
  * Web-only Oxy Provider
@@ -132,12 +149,14 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
132
149
  await authManager.signOut();
133
150
  }
134
151
  }
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);
152
+ // Silent sign-in: run AT MOST ONCE per page load. A remount (route
153
+ // change / StrictMode / error recovery) must not re-trigger the
154
+ // browser credential request OR the iframe fallback. The FedCM leg is
155
+ // additionally memoized in core; this guard also covers the iframe
156
+ // fallback that core does not.
157
+ const ssoKey = silentSignInKey(oxyServices);
158
+ if (!silentSignInAttempted.has(ssoKey)) {
159
+ silentSignInAttempted.add(ssoKey);
141
160
  try {
142
161
  const session = await crossDomainAuth.silentSignIn();
143
162
  if (mounted && session?.user) {
@@ -19,35 +19,6 @@ 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
- }
51
22
  /**
52
23
  * Check if we're running in a web browser environment (not React Native)
53
24
  */
@@ -152,12 +123,12 @@ function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onError, ena
152
123
  }, [oxyServices, onSessionFound, onError, fedCMSupported]);
153
124
  // Auto-check SSO on mount (web only, FedCM only, not on auth domain).
154
125
  //
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.
126
+ // `hasCheckedRef` is a cheap per-instance fast-path so effect re-runs (from
127
+ // changing deps) within one mount never re-fire. The page-load run-once
128
+ // guarantee silent SSO invoking `navigator.credentials.get` AT MOST ONCE
129
+ // per page load across remounts / StrictMode / multiple consumers — lives in
130
+ // `@oxyhq/core`'s `silentSignInWithFedCM`, which memoizes the first silent
131
+ // attempt's result for this origin + API. The hook therefore calls it freely.
161
132
  (0, react_1.useEffect)(() => {
162
133
  if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider()) {
163
134
  if (isIdentityProvider()) {
@@ -165,22 +136,14 @@ function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onError, ena
165
136
  }
166
137
  return;
167
138
  }
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
- }
175
139
  hasCheckedRef.current = true;
176
- silentSSOAttempted.add(signature);
177
140
  if (fedCMSupported) {
178
141
  checkSSO();
179
142
  }
180
143
  else {
181
144
  onSSOUnavailable?.();
182
145
  }
183
- }, [enabled, checkSSO, fedCMSupported, onSSOUnavailable, oxyServices]);
146
+ }, [enabled, checkSSO, fedCMSupported, onSSOUnavailable]);
184
147
  return {
185
148
  checkSSO,
186
149
  signInWithFedCM,