@oxyhq/auth 2.0.6 → 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.
@@ -16,6 +16,38 @@ const core_1 = require("@oxyhq/core");
16
16
  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
+ /**
20
+ * Module-level run-once guard for the provider's silent sign-in step.
21
+ *
22
+ * The init effect runs again whenever the provider remounts (route change,
23
+ * StrictMode double-invoke, error-boundary recovery). The redirect-callback
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.
38
+ */
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}`;
50
+ }
19
51
  /**
20
52
  * Web-only Oxy Provider
21
53
  *
@@ -117,15 +149,24 @@ function WebOxyProvider({ children, baseURL, authWebUrl, onAuthStateChange, onEr
117
149
  await authManager.signOut();
118
150
  }
119
151
  }
120
- try {
121
- const session = await crossDomainAuth.silentSignIn();
122
- if (mounted && session?.user) {
123
- await handleAuthSuccess(session, 'fedcm');
124
- return;
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);
160
+ try {
161
+ const session = await crossDomainAuth.silentSignIn();
162
+ if (mounted && session?.user) {
163
+ await handleAuthSuccess(session, 'fedcm');
164
+ return;
165
+ }
166
+ }
167
+ catch {
168
+ // Silent sign-in failed — resolve to unauthenticated below.
125
169
  }
126
- }
127
- catch {
128
- // Silent sign-in failed
129
170
  }
130
171
  if (mounted)
131
172
  setIsLoading(false);
@@ -121,7 +121,14 @@ function useWebSSO({ oxyServices, onSessionFound, onSSOUnavailable, onError, ena
121
121
  isCheckingRef.current = false;
122
122
  }
123
123
  }, [oxyServices, onSessionFound, onError, fedCMSupported]);
124
- // Auto-check SSO on mount (web only, FedCM only, not on auth domain)
124
+ // Auto-check SSO on mount (web only, FedCM only, not on auth domain).
125
+ //
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.
125
132
  (0, react_1.useEffect)(() => {
126
133
  if (!enabled || !isWebBrowser() || hasCheckedRef.current || isIdentityProvider()) {
127
134
  if (isIdentityProvider()) {