@oxyhq/services 8.4.3 → 8.6.0

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.
@@ -14,6 +14,21 @@ export interface OxyContextState {
14
14
  isAuthenticated: boolean;
15
15
  isLoading: boolean;
16
16
  isTokenReady: boolean;
17
+ /**
18
+ * Whether the initial auth determination has concluded.
19
+ *
20
+ * `false` from mount until the FIRST cold-boot session restore finishes —
21
+ * during that window `isAuthenticated: false` is UNDETERMINED, not a
22
+ * definitive "logged out". Flips to `true` exactly once the restore concludes
23
+ * (a session was committed OR none exists) and never reverts. Consumers should
24
+ * defer their first auth-dependent fetch until this is `true` so a cold-boot
25
+ * web reload with an existing session does not fetch anonymous data.
26
+ *
27
+ * On native, cold boot runs only the `stored-session` step, so this resolves
28
+ * promptly. It is set in the restore `finally`, so the success, no-session,
29
+ * and error paths all reach `true` — it can never get stuck `false`.
30
+ */
31
+ isAuthResolved: boolean;
17
32
  isStorageReady: boolean;
18
33
  error: string | null;
19
34
  currentLanguage: string;
@@ -1 +1 @@
1
- {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,EAQL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AACrD,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAqBjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAOvE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAStD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,QAAQ,EAAE,aAAa,EAAE,CAAC;IAC1B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,eAAe,EAAE,OAAO,CAAC;IACzB,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,OAAO,CAAC;IACtB,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,uBAAuB,EAAE,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC,UAAU,CAAC,CAAC;IAC9E,mBAAmB,EAAE,MAAM,CAAC;IAC5B,yBAAyB,EAAE,MAAM,CAAC;IAGlC,WAAW,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACpC,YAAY,EAAE,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAG3C,MAAM,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAElE;;;OAGG;IACH,kBAAkB,EAAE,CAAC,OAAO,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAGrE,MAAM,EAAE,CAAC,eAAe,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,SAAS,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/B,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,eAAe,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,WAAW,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,iBAAiB,EAAE,MAAM,OAAO,CAC9B,KAAK,CAAC;QACJ,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC,CACH,CAAC;IACF,uBAAuB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7C,gBAAgB,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,iBAAiB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,mBAAmB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,gBAAgB,EAAE,MAAM,CAAC;IACzB,WAAW,EAAE,WAAW,CAAC;IACzB,SAAS,CAAC,EAAE,aAAa,CAAC;IAC1B,eAAe,CAAC,EAAE,CAAC,cAAc,EAAE,SAAS,GAAG;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,KAAK,IAAI,CAAC;IAC/G,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAG7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,eAAe,EAAE,cAAc,EAAE,CAAC;IAClC,WAAW,EAAE,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IAC7C,sBAAsB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5C,oBAAoB,EAAE,CAAC,IAAI,EAAE,yBAAyB,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACpF;AAED,QAAA,MAAM,UAAU,uCAA8C,CAAC;AAM/D,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI,KAAK,IAAI,CAAC;IAChD,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,KAAK,IAAI,CAAC;CACrC;AAkGD,eAAO,MAAM,WAAW,EAAE,KAAK,CAAC,EAAE,CAAC,uBAAuB,CAqwCzD,CAAC;AAEF,eAAO,MAAM,kBAAkB,mCAAc,CAAC;AA0D9C,eAAO,MAAM,MAAM,QAAO,eAMzB,CAAC;AAEF,eAAe,UAAU,CAAC"}
1
+ {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
@@ -33,6 +33,17 @@ export interface AuthState {
33
33
  isLoading: boolean;
34
34
  /** Whether the auth token is ready for API calls */
35
35
  isReady: boolean;
36
+ /**
37
+ * Whether the initial auth determination has concluded.
38
+ *
39
+ * `false` from mount until the first cold-boot session restore finishes;
40
+ * while `false`, `isAuthenticated: false` is UNDETERMINED (not a definitive
41
+ * "logged out"). Flips to `true` once — when a session is committed or none
42
+ * is found — and never reverts. Defer the first auth-dependent fetch until
43
+ * this is `true` so a cold-boot reload with an existing session does not load
44
+ * anonymous data.
45
+ */
46
+ isAuthResolved: boolean;
36
47
  /** Current error message, if any */
37
48
  error: string | null;
38
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;OAUG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;IACtD,0EAA0E;IAC1E,eAAe,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC9D,0CAA0C;IAC1C,gBAAgB,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,aAAa,CA2IvC"}
1
+ {"version":3,"file":"useAuth.d.ts","sourceRoot":"","sources":["../../../../../src/ui/hooks/useAuth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC/C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,WAAW,SAAS;IACxB,4DAA4D;IAC5D,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAElB,oCAAoC;IACpC,eAAe,EAAE,OAAO,CAAC;IAEzB,4DAA4D;IAC5D,SAAS,EAAE,OAAO,CAAC;IAEnB,oDAAoD;IACpD,OAAO,EAAE,OAAO,CAAC;IAEjB;;;;;;;;;OASG;IACH,cAAc,EAAE,OAAO,CAAC;IAExB,oCAAoC;IACpC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;;;;OAUG;IACH,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAE9E;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAE7B;;OAEG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAEhC;;OAEG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,aAAc,SAAQ,SAAS,EAAE,WAAW;IAC3D,6DAA6D;IAC7D,WAAW,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,aAAa,CAAC,CAAC;IACtD,0EAA0E;IAC1E,eAAe,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,iBAAiB,CAAC,CAAC;IAC9D,0CAA0C;IAC1C,gBAAgB,EAAE,UAAU,CAAC,OAAO,MAAM,CAAC,CAAC,kBAAkB,CAAC,CAAC;CACjE;AAED;;;;;;;;GAQG;AACH,wBAAgB,OAAO,IAAI,aAAa,CA6IvC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "8.4.3",
3
+ "version": "8.6.0",
4
4
  "description": "OxyHQ Expo/React Native SDK — UI components, screens, and native features",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -160,7 +160,7 @@
160
160
  "peerDependencies": {
161
161
  "@expo/vector-icons": "^15.0.3",
162
162
  "@oxyhq/bloom": ">=0.5.0",
163
- "@oxyhq/core": "^2.3.0",
163
+ "@oxyhq/core": "^2.4.0",
164
164
  "@react-native-community/netinfo": "^11.4.1",
165
165
  "@tanstack/query-async-storage-persister": "^5.100",
166
166
  "@tanstack/query-sync-storage-persister": "^5.100",
@@ -58,6 +58,21 @@ export interface OxyContextState {
58
58
  isAuthenticated: boolean;
59
59
  isLoading: boolean;
60
60
  isTokenReady: boolean;
61
+ /**
62
+ * Whether the initial auth determination has concluded.
63
+ *
64
+ * `false` from mount until the FIRST cold-boot session restore finishes —
65
+ * during that window `isAuthenticated: false` is UNDETERMINED, not a
66
+ * definitive "logged out". Flips to `true` exactly once the restore concludes
67
+ * (a session was committed OR none exists) and never reverts. Consumers should
68
+ * defer their first auth-dependent fetch until this is `true` so a cold-boot
69
+ * web reload with an existing session does not fetch anonymous data.
70
+ *
71
+ * On native, cold boot runs only the `stored-session` step, so this resolves
72
+ * promptly. It is set in the restore `finally`, so the success, no-session,
73
+ * and error paths all reach `true` — it can never get stuck `false`.
74
+ */
75
+ isAuthResolved: boolean;
61
76
  isStorageReady: boolean;
62
77
  error: string | null;
63
78
  currentLanguage: string;
@@ -163,6 +178,32 @@ function silentColdBootKey(oxyServices: OxyServices): string {
163
178
  return `${origin}|${baseURL}`;
164
179
  }
165
180
 
181
+ /**
182
+ * Per-step fail-fast budget for the cold-boot silent iframe (`silentSignIn`
183
+ * against the per-apex `/auth/silent` host).
184
+ *
185
+ * This step ONLY succeeds when a durable per-apex `fedcm_session` cookie exists
186
+ * (established by a prior `/sso` bounce). On the common reload of a logged-out
187
+ * tab — or a tab that restores via the now-earlier stored-session step — the
188
+ * iframe never posts a message, so the full wait would be dead latency in front
189
+ * of the terminal `/sso` bounce. `silentSignIn` already fails fast on a load
190
+ * error via `iframe.onerror`; this caps the no-message case. 2.5s is well above
191
+ * a same-origin iframe handshake yet a fraction of the legacy 5s default.
192
+ */
193
+ const SILENT_IFRAME_TIMEOUT = 2500;
194
+
195
+ /**
196
+ * Per-step fail-fast budget for the cold-boot refresh-cookie restore
197
+ * (`refreshAllSessions`).
198
+ *
199
+ * On a cross-domain RP the `Domain=oxy.so` refresh cookie never reaches
200
+ * `api.<apex>`, so this request returns no accounts (or stalls behind a slow
201
+ * endpoint) with no useful answer. As one cold-boot step it must not block the
202
+ * fall-through to the terminal `/sso` bounce. 3s bounds the wait while leaving
203
+ * ample headroom for a genuine first-party `*.oxy.so` rotation round-trip.
204
+ */
205
+ const COOKIE_RESTORE_TIMEOUT = 3000;
206
+
166
207
  /**
167
208
  * Whether `idpOrigin` is a same-site, first-party host of the current page —
168
209
  * i.e. it shares the page's registrable apex (last two labels), so a "no
@@ -284,6 +325,15 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
284
325
  );
285
326
 
286
327
  const [tokenReady, setTokenReady] = useState(true);
328
+ // Whether the FIRST cold-boot auth restore has concluded. Starts `false`
329
+ // (auth undetermined) and flips to `true` exactly once — monotonic, never
330
+ // reverts. It now flips the MOMENT a session commits (the common reload case
331
+ // unblocks immediately, without waiting for the rest of the cold-boot chain),
332
+ // with the restore `finally` as the no-session/error backstop. The ref makes
333
+ // the flip idempotent across both sites so the setters fire at most once. See
334
+ // `isAuthResolved` on the context type for the consumer contract.
335
+ const [authResolved, setAuthResolved] = useState(false);
336
+ const authResolvedRef = useRef(false);
287
337
  const [initialized, setInitialized] = useState(false);
288
338
  const setAuthState = useAuthStore.setState;
289
339
 
@@ -545,6 +595,26 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
545
595
  const onAuthStateChangeRef = useRef(onAuthStateChange);
546
596
  onAuthStateChangeRef.current = onAuthStateChange;
547
597
 
598
+ // Flip the auth-resolution gate (`authResolved` + `tokenReady`) the MOMENT a
599
+ // session commits, instead of waiting for the whole cold-boot chain to finish.
600
+ // Idempotent and monotonic via `authResolvedRef`: the first call wins and the
601
+ // setters fire at most once, so the restore `finally` backstop becomes a no-op
602
+ // once a commit site has already marked resolution. Called from EVERY place a
603
+ // user is actually committed (the FedCM/iframe/redirect/SSO path
604
+ // `handleWebSSOSession`, the cookie-restore path, and the stored-session path)
605
+ // so the common reload case unblocks the loading gate without sitting behind
606
+ // the remaining (now-skipped) cold-boot steps.
607
+ const markAuthResolved = useCallback(() => {
608
+ if (authResolvedRef.current) {
609
+ return;
610
+ }
611
+ authResolvedRef.current = true;
612
+ setTokenReady(true);
613
+ setAuthResolved(true);
614
+ }, []);
615
+ const markAuthResolvedRef = useRef(markAuthResolved);
616
+ markAuthResolvedRef.current = markAuthResolved;
617
+
548
618
  // `handleWebSSOSession` is declared further down (it depends on values that
549
619
  // are only available there). The FedCM/iframe cold-boot steps need to commit
550
620
  // a recovered session through it, so we route the call through a ref that is
@@ -578,7 +648,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
578
648
 
579
649
  let snapshot;
580
650
  try {
581
- snapshot = await oxyServices.refreshAllSessions();
651
+ // Bound the refresh so a cross-domain/stalled call cannot hang the cold
652
+ // boot in front of the terminal `/sso` bounce (see COOKIE_RESTORE_TIMEOUT).
653
+ snapshot = await oxyServices.refreshAllSessions({ timeout: COOKIE_RESTORE_TIMEOUT });
582
654
  } catch (fetchError) {
583
655
  // Offline / network error — fall through to the cached/stored-session flow.
584
656
  if (__DEV__) {
@@ -642,6 +714,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
642
714
  await persistSessionDurably(activeAccount.sessionId);
643
715
 
644
716
  loginSuccessRef.current(fullUser);
717
+ // A session is now committed — unblock the auth-resolution gate immediately
718
+ // rather than waiting for `runColdBoot` to return (idempotent).
719
+ markAuthResolvedRef.current();
645
720
  onAuthStateChangeRef.current?.(fullUser);
646
721
  return true;
647
722
  }, [oxyServices, persistSessionDurably]);
@@ -719,6 +794,11 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
719
794
  if (storedActiveSessionId) {
720
795
  try {
721
796
  await switchSessionRef.current(storedActiveSessionId);
797
+ // The stored session is committed (this is native's ONLY restore path
798
+ // and the common web reload winner). Unblock the auth-resolution gate
799
+ // immediately so the loading screen clears without waiting for the
800
+ // remaining cold-boot steps to be evaluated/short-circuited (idempotent).
801
+ markAuthResolvedRef.current();
722
802
  return true;
723
803
  } catch (switchError) {
724
804
  // Silently handle expected errors (invalid sessions, timeouts, network issues)
@@ -821,11 +901,21 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
821
901
  // web-only step is gated by `isWebBrowser()`, so on native ONLY
822
902
  // `stored-session` runs.
823
903
  //
824
- // Order (web): redirect callback → SSO return → FedCM silent (central) →
825
- // silent iframe (per-apex, the durable reload path) → cookie restore →
826
- // stored session → SSO bounce (terminal). The per-apex silent iframe is what
827
- // restores a durable cross-domain session on reload WITHOUT a top-level
828
- // bounce, so when it wins `sso-bounce` never fires (no flash, no loop).
904
+ // Order (web): redirect callback → SSO return → stored session → FedCM silent
905
+ // (central) → silent iframe (per-apex, the durable reload path) → cookie
906
+ // restore → SSO bounce (terminal).
907
+ //
908
+ // LATENCY (FIX A): `stored-session` runs BEFORE the slow no-redirect probes
909
+ // (`fedcm-silent`, `silent-iframe`, `cookie-restore`). On a normal reload the
910
+ // local bearer validates in one round-trip and wins, so `runColdBoot`
911
+ // short-circuits and never sits through those probes' timeouts (the prior
912
+ // serial sum was a ~20-30s stall). `redirect` and `sso-return` MUST stay
913
+ // first — they consume the URL fragment before anything can strip it. On a
914
+ // first visit with no local session, `stored-session` skips and the
915
+ // cross-domain fallback chain (fedcm → iframe → cookie → sso-bounce) runs
916
+ // exactly as before; the per-apex silent iframe still restores a durable
917
+ // cross-domain session on reload WITHOUT a top-level bounce, so when it wins
918
+ // `sso-bounce` never fires (no flash, no loop).
829
919
  // Order (native): stored session only (every web-only step is disabled
830
920
  // off-browser).
831
921
  const restoreSessionsFromStorage = useCallback(async (): Promise<void> => {
@@ -839,6 +929,16 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
839
929
  const silentKey = silentColdBootKey(oxyServices);
840
930
  const fedcmSupported = isWebBrowser() && oxyServices.isFedCMSupported?.() === true;
841
931
 
932
+ // FIX-B precondition flag: set true the instant the (now-earlier)
933
+ // `stored-session` step recovers a local bearer session. The slow web-only
934
+ // probes (`fedcm-silent`, `silent-iframe`) AND `enabled` on `!storedSessionRestored`
935
+ // so they are explicitly skipped once a local session won. `runColdBoot`
936
+ // already short-circuits on the first `{kind:'session'}`, so on a winning
937
+ // reload those `enabled` bodies are never even reached — this flag makes the
938
+ // intent explicit and is redundant-safe. On a first-visit-no-local-session,
939
+ // `stored-session` skips, this stays false, and the probes run as before.
940
+ let storedSessionRestored = false;
941
+
842
942
  try {
843
943
  const outcome = await runColdBoot<true>({
844
944
  steps: [
@@ -875,14 +975,47 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
875
975
  },
876
976
  },
877
977
  {
878
- // 2) FedCM silent reauthn (Chrome) against the CENTRAL IdP
978
+ // 2) Stored-session bearer restore. NO `enabled` gate runs on ALL
979
+ // platforms. This is native's ONLY restore path (every web-only step
980
+ // is disabled off-browser, so native reaches exactly this) AND the
981
+ // common WEB reload winner.
982
+ //
983
+ // ORDERING (FIX A): this step now runs BEFORE the slow web-only
984
+ // probes (`fedcm-silent`, `silent-iframe`, `cookie-restore`). On a
985
+ // normal reload the local bearer validates in one round-trip and
986
+ // wins; `runColdBoot` then short-circuits and never even evaluates
987
+ // the slow no-redirect probes that would otherwise time out (the
988
+ // ~20-30s serial stall). The `redirect` and `sso-return` steps stay
989
+ // AHEAD of this one — they must consume the URL fragment before any
990
+ // later step (or anything else) strips it. On a first visit with no
991
+ // local session this step skips and the cross-domain fallback chain
992
+ // (fedcm → iframe → cookie → sso-bounce) runs exactly as before.
993
+ id: 'stored-session',
994
+ run: async () => {
995
+ const restored = await restoreStoredSession();
996
+ if (restored) {
997
+ // FIX-B: record the win so the slow probes below explicitly skip
998
+ // (belt-and-suspenders; `runColdBoot` already short-circuits).
999
+ storedSessionRestored = true;
1000
+ return { kind: 'session', session: true };
1001
+ }
1002
+ return { kind: 'skip' };
1003
+ },
1004
+ },
1005
+ {
1006
+ // 3) FedCM silent reauthn (Chrome) against the CENTRAL IdP
879
1007
  // (auth.oxy.so). `silentSignInWithFedCM` plants the access token
880
1008
  // internally; we commit the returned session via
881
1009
  // `handleWebSSOSession`. Guarded so it fires at most once per page
882
1010
  // load across remounts. This is an enhancement layered above the
883
1011
  // opaque-code bounce: when it succeeds the bounce never fires.
1012
+ //
1013
+ // FIX-B: additionally skipped when the earlier `stored-session` step
1014
+ // already recovered a local session — the probe cannot improve on a
1015
+ // valid local bearer, and skipping it avoids the silent round-trip.
884
1016
  id: 'fedcm-silent',
885
- enabled: () => fedcmSupported && !servicesSilentAttempted.has(silentKey),
1017
+ enabled: () =>
1018
+ !storedSessionRestored && fedcmSupported && !servicesSilentAttempted.has(silentKey),
886
1019
  run: async () => {
887
1020
  servicesSilentAttempted.add(silentKey);
888
1021
  const session = await oxyServices.silentSignInWithFedCM?.();
@@ -894,7 +1027,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
894
1027
  },
895
1028
  },
896
1029
  {
897
- // 3) First-party silent iframe at the PER-APEX IdP — the DURABLE
1030
+ // 4) First-party silent iframe at the PER-APEX IdP — the DURABLE
898
1031
  // cross-domain reload-restore path. The durable session lives as a
899
1032
  // first-party `fedcm_session` cookie on `auth.<rp-apex>` (e.g.
900
1033
  // `auth.mention.earth`), established during the `/sso` bounce's
@@ -913,8 +1046,12 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
913
1046
  // equivalent. When auto-detection bails (localhost/IP/single-label)
914
1047
  // there is no per-apex IdP and the step skips. Web only; on native
915
1048
  // `isWebBrowser()` gates it off, so native never runs an iframe.
1049
+ //
1050
+ // FIX-B: additionally skipped when `stored-session` already won.
1051
+ // FIX-D: bounded by `SILENT_IFRAME_TIMEOUT` (plus `iframe.onerror`
1052
+ // fail-fast in core) so a no-message iframe cannot stall cold boot.
916
1053
  id: 'silent-iframe',
917
- enabled: () => isWebBrowser(),
1054
+ enabled: () => !storedSessionRestored && isWebBrowser(),
918
1055
  run: async () => {
919
1056
  const perApexAuthUrl = autoDetectAuthWebUrl();
920
1057
  if (!perApexAuthUrl || !commitWebSession) {
@@ -922,6 +1059,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
922
1059
  }
923
1060
  const session = await oxyServices.silentSignIn?.({
924
1061
  authWebUrlOverride: perApexAuthUrl,
1062
+ timeout: SILENT_IFRAME_TIMEOUT,
925
1063
  });
926
1064
  if (!session?.user || !session?.sessionId) {
927
1065
  return { kind: 'skip' };
@@ -931,12 +1069,14 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
931
1069
  },
932
1070
  },
933
1071
  {
934
- // 4) Refresh-cookie restore (first-party only). On `*.oxy.so` the
1072
+ // 5) Refresh-cookie restore (first-party only). On `*.oxy.so` the
935
1073
  // httpOnly `oxy_rt_${n}` cookies ride along and resurrect every
936
1074
  // device-local slot. On a cross-domain RP (mention.earth, …) the
937
1075
  // cookie is `Domain=oxy.so` so it never reaches `api.<apex>` —
938
1076
  // `refreshAllSessions` returns `{accounts:[]}` and this skips. That
939
1077
  // is correct; cross-domain restore is handled by the SSO bounce.
1078
+ // FIX-D: `restoreViaRefreshCookie` bounds the request with
1079
+ // `COOKIE_RESTORE_TIMEOUT` so a cross-domain stall cannot hang here.
940
1080
  id: 'cookie-restore',
941
1081
  enabled: () => isWebBrowser(),
942
1082
  run: async () => {
@@ -944,16 +1084,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
944
1084
  return restored ? { kind: 'session', session: true } : { kind: 'skip' };
945
1085
  },
946
1086
  },
947
- {
948
- // 5) Stored-session bearer restore. NO `enabled` gate — runs on ALL
949
- // platforms. This is native's ONLY restore path (every web-only step
950
- // is disabled off-browser, so native reaches exactly this).
951
- id: 'stored-session',
952
- run: async () => {
953
- const restored = await restoreStoredSession();
954
- return restored ? { kind: 'session', session: true } : { kind: 'skip' };
955
- },
956
- },
957
1087
  {
958
1088
  // 6) SSO bounce (TERMINAL, web only, at most once). No local session
959
1089
  // was found by any step above. Top-level navigate to the central
@@ -1028,7 +1158,14 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1028
1158
  }
1029
1159
  await clearSessionStateRef.current();
1030
1160
  } finally {
1031
- setTokenReady(true);
1161
+ // Backstop: mark auth resolved on EVERY exit path — success, no-session,
1162
+ // AND error→catch→finally — and on native (which only runs the
1163
+ // `stored-session` step), so the gate can never hang `false`. Idempotent
1164
+ // via `markAuthResolved`'s ref: when a commit site already flipped it
1165
+ // mid-chain (the common reload case), this is a no-op. When no session was
1166
+ // recovered (the unauthenticated/error path), this is where `tokenReady` +
1167
+ // `authResolved` finally flip. Monotonic — never reverts on later restores.
1168
+ markAuthResolved();
1032
1169
  }
1033
1170
  }, [
1034
1171
  oxyServices,
@@ -1036,6 +1173,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1036
1173
  restoreViaRefreshCookie,
1037
1174
  restoreStoredSession,
1038
1175
  runSsoReturn,
1176
+ markAuthResolved,
1039
1177
  ]);
1040
1178
 
1041
1179
  useEffect(() => {
@@ -1189,6 +1327,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1189
1327
  fullUser = session.user as unknown as User;
1190
1328
  }
1191
1329
  loginSuccess(fullUser);
1330
+ // A session is now committed (FedCM silent / per-apex iframe / redirect /
1331
+ // SSO-return / popup all funnel through here) — unblock the auth-resolution
1332
+ // gate immediately, ahead of the cold-boot chain returning (idempotent).
1333
+ markAuthResolvedRef.current();
1192
1334
  onAuthStateChange?.(fullUser);
1193
1335
  }, [oxyServices, updateSessions, setActiveSessionId, loginSuccess, onAuthStateChange, persistSessionDurably]);
1194
1336
 
@@ -1436,6 +1578,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1436
1578
  isAuthenticated,
1437
1579
  isLoading,
1438
1580
  isTokenReady: tokenReady,
1581
+ isAuthResolved: authResolved,
1439
1582
  isStorageReady: storage !== null,
1440
1583
  error,
1441
1584
  currentLanguage,
@@ -1492,6 +1635,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1492
1635
  storage,
1493
1636
  switchSessionForContext,
1494
1637
  tokenReady,
1638
+ authResolved,
1495
1639
  updateDeviceName,
1496
1640
  clearAllAccountData,
1497
1641
  useFollowHook,
@@ -1539,6 +1683,7 @@ const LOADING_STATE: OxyContextState = {
1539
1683
  isAuthenticated: false,
1540
1684
  isLoading: true,
1541
1685
  isTokenReady: false,
1686
+ isAuthResolved: false,
1542
1687
  isStorageReady: false,
1543
1688
  error: null,
1544
1689
  currentLanguage: 'en',
@@ -41,6 +41,18 @@ export interface AuthState {
41
41
  /** Whether the auth token is ready for API calls */
42
42
  isReady: boolean;
43
43
 
44
+ /**
45
+ * Whether the initial auth determination has concluded.
46
+ *
47
+ * `false` from mount until the first cold-boot session restore finishes;
48
+ * while `false`, `isAuthenticated: false` is UNDETERMINED (not a definitive
49
+ * "logged out"). Flips to `true` once — when a session is committed or none
50
+ * is found — and never reverts. Defer the first auth-dependent fetch until
51
+ * this is `true` so a cold-boot reload with an existing session does not load
52
+ * anonymous data.
53
+ */
54
+ isAuthResolved: boolean;
55
+
44
56
  /** Current error message, if any */
45
57
  error: string | null;
46
58
  }
@@ -99,6 +111,7 @@ export function useAuth(): UseAuthReturn {
99
111
  isAuthenticated,
100
112
  isLoading,
101
113
  isTokenReady,
114
+ isAuthResolved,
102
115
  error,
103
116
  signIn: oxySignIn,
104
117
  handlePopupSession,
@@ -219,6 +232,7 @@ export function useAuth(): UseAuthReturn {
219
232
  isAuthenticated,
220
233
  isLoading,
221
234
  isReady: isTokenReady,
235
+ isAuthResolved,
222
236
  error,
223
237
 
224
238
  // Actions