@oxyhq/services 12.1.0 → 12.1.1

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.
@@ -15,10 +15,10 @@
15
15
  *
16
16
  * 1. {@link createInSessionRefreshHandler} — an `AuthRefreshHandler` installed
17
17
  * on the owner client. It re-mints a fresh access token WITHOUT a page
18
- * reload by reusing the SAME durable per-apex silent-restore arms cold boot
19
- * uses (in order; first that yields a token wins). The linked client
20
- * (`createLinkedClient`) inherits the fix for free — its refresh delegates
21
- * back to the owner's `refreshAccessToken`.
18
+ * reload by composing the SAME silent-restore primitives cold boot uses
19
+ * ({@link mintSessionViaPerApexIframe}, {@link selectActiveRefreshAccount})
20
+ * not a copy. The linked client (`createLinkedClient`) inherits the fix for
21
+ * free: its refresh delegates back to the owner's `refreshAccessToken`.
22
22
  *
23
23
  * 2. {@link startTokenRefreshScheduler} — a proactive scheduler that refreshes
24
24
  * ~{@link TOKEN_REFRESH_LEAD_MS} before expiry (and on web tab-focus / native
@@ -42,40 +42,35 @@ export declare const TOKEN_REFRESH_LEAD_MS = 60000;
42
42
  /**
43
43
  * Build the in-session `AuthRefreshHandler` for the owner client.
44
44
  *
45
- * Arms (first to yield a fresh token wins; each is bounded and falls through on
46
- * failure). Every arm plants the fresh token internally, so on success we read
47
- * it back via `getAccessToken()`:
45
+ * Arms run in an explicit order; the first to plant a token wins. Each arm
46
+ * resolves to the planted token (read back via `getAccessToken()` the
47
+ * underlying SDK call plants it internally) or `null`. A throw in one arm is
48
+ * logged and treated as `null` so the chain continues.
48
49
  *
49
50
  * NATIVE (Expo): shared cross-app identity key re-mint
50
51
  * (`signInWithSharedIdentity` → challenge→sign→verify plants the tokens).
51
52
  * The ONLY silent native arm (mirrors cold boot's `shared-key-signin`); the
52
- * `/auth/silent` web iframe is NEVER attempted on native. Returns `null`
53
+ * `/auth/silent` web iframe is NEVER attempted on native. Resolves to `null`
53
54
  * when the device holds no shared identity (e.g. a password-only native
54
55
  * sign-in) so a genuinely dead session reconciles to logged-out rather than
55
56
  * staying a zombie.
56
57
  *
57
58
  * WEB, in order:
58
- * 1. First-party `/auth/silent` iframe at the per-apex IdP
59
- * (`silentSignIn` with `authWebUrlOverride = autoDetectAuthWebUrl()`).
60
- * The durable cross-apex path: the iframe reads the first-party
61
- * `fedcm_session` cookie on `auth.<apex>` and mints a fresh Oxy token. No
62
- * top-level navigation → works in a backgrounded tab. On a `*.oxy.so`
63
- * app the per-apex host IS the central host, so this also covers
64
- * same-apex.
59
+ * 1. Per-apex `/auth/silent` iframe {@link mintSessionViaPerApexIframe}
60
+ * (shared verbatim with cold boot). The durable cross-apex path; also
61
+ * covers `*.oxy.so` (the per-apex host IS the central host there).
65
62
  * 2. FedCM silent re-auth (Chrome) — `silentSignInWithFedCM`.
66
- * 3. Same-apex refresh cookie — `refreshAllSessions`. On `*.oxy.so` the
67
- * httpOnly `oxy_rt_${n}` cookies ride along; we plant the active
68
- * account's rotated token. On a cross-apex RP it returns `{accounts:[]}`
69
- * and is a clean no-op. Unlike the cold-boot cookie restore this does NOT
70
- * rebuild multi-session state — an in-session refresh only needs a fresh
71
- * bearer.
63
+ * 3. Same-apex refresh cookie — `refreshAllSessions` + the shared
64
+ * {@link selectActiveRefreshAccount}; plant the active account's rotated
65
+ * token. On a cross-apex RP it returns `{accounts:[]}` (clean no-op).
66
+ * Unlike the cold-boot cookie restore this does NOT rebuild multi-session
67
+ * state — an in-session refresh only needs a fresh bearer.
72
68
  *
73
- * NO RECURSION: none of these arms issue requests through the authed client's
69
+ * NO RECURSION: no arm issues a request through the authed client's
74
70
  * `refreshAccessToken` path. The iframe/FedCM transports are postMessage /
75
71
  * credential APIs; the follow-up `/session/user` fetch inside `silentSignIn`
76
72
  * runs against the just-planted FULL-TTL token (≫ the preflight lead), so it
77
- * never re-enters the refresh path; `refreshAllSessions` uses a raw `fetch` with
78
- * `credentials:'include'`.
73
+ * never re-enters the refresh path; `refreshAllSessions` uses a raw `fetch`.
79
74
  */
80
75
  export declare function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRefreshHandler;
81
76
  /**
@@ -92,14 +87,15 @@ export interface TokenRefreshSchedulerHandle {
92
87
  * current access token's `exp`, calling `httpService.refreshAccessToken`
93
88
  * ('preflight') — which runs the installed handler and is deduped + cooldown-
94
89
  * guarded. After every attempt it reschedules from the (possibly rotated) token.
95
- * It also reschedules whenever the token changes (`onTokensChanged`) and, on
96
- * web tab-focus / native app-foreground, refreshes immediately if already inside
97
- * the lead window (a long-hidden tab throttles timers, so the token can be
98
- * expired on return).
90
+ * It also reschedules whenever the token changes (`onTokensChanged` so a
91
+ * sign-out that clears the token cancels the timer) and, on web tab-focus /
92
+ * native app-foreground, refreshes immediately if already inside the lead window
93
+ * (a long-hidden tab throttles timers, so the token can be expired on return).
99
94
  *
100
- * No-ops cleanly when there is no token, an opaque/no-`exp` token, or the host
101
- * lacks `getAccessTokenExpiry` (older stubs) in those cases the reactive 401
102
- * path remains the only refresh trigger.
95
+ * The `exp` is derived directly from the JWT via `getAccessTokenExpiry()`. No-ops
96
+ * cleanly when there is no token, an opaque/no-`exp` token, or the host lacks
97
+ * `getAccessTokenExpiry` (older stubs) — the reactive 401 path stays the only
98
+ * refresh trigger in those cases.
103
99
  */
104
100
  export declare function startTokenRefreshScheduler(oxyServices: OxyServices): TokenRefreshSchedulerHandle;
105
101
  //# sourceMappingURL=inSessionTokenRefresh.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inSessionTokenRefresh.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/inSessionTokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAwBrB;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAY5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,WAAW,GAAG,kBAAkB,CA8D1F;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,2BAA2B,CA4FhG"}
1
+ {"version":3,"file":"inSessionTokenRefresh.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/inSessionTokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAyBrB;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAQ5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,WAAW,GAAG,kBAAkB,CAsD1F;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,2BAA2B,CA4FhG"}
@@ -1 +1 @@
1
- {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"OxyContext.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/OxyContext.tsx"],"names":[],"mappings":"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"}
@@ -15,10 +15,10 @@
15
15
  *
16
16
  * 1. {@link createInSessionRefreshHandler} — an `AuthRefreshHandler` installed
17
17
  * on the owner client. It re-mints a fresh access token WITHOUT a page
18
- * reload by reusing the SAME durable per-apex silent-restore arms cold boot
19
- * uses (in order; first that yields a token wins). The linked client
20
- * (`createLinkedClient`) inherits the fix for free — its refresh delegates
21
- * back to the owner's `refreshAccessToken`.
18
+ * reload by composing the SAME silent-restore primitives cold boot uses
19
+ * ({@link mintSessionViaPerApexIframe}, {@link selectActiveRefreshAccount})
20
+ * not a copy. The linked client (`createLinkedClient`) inherits the fix for
21
+ * free: its refresh delegates back to the owner's `refreshAccessToken`.
22
22
  *
23
23
  * 2. {@link startTokenRefreshScheduler} — a proactive scheduler that refreshes
24
24
  * ~{@link TOKEN_REFRESH_LEAD_MS} before expiry (and on web tab-focus / native
@@ -42,40 +42,35 @@ export declare const TOKEN_REFRESH_LEAD_MS = 60000;
42
42
  /**
43
43
  * Build the in-session `AuthRefreshHandler` for the owner client.
44
44
  *
45
- * Arms (first to yield a fresh token wins; each is bounded and falls through on
46
- * failure). Every arm plants the fresh token internally, so on success we read
47
- * it back via `getAccessToken()`:
45
+ * Arms run in an explicit order; the first to plant a token wins. Each arm
46
+ * resolves to the planted token (read back via `getAccessToken()` the
47
+ * underlying SDK call plants it internally) or `null`. A throw in one arm is
48
+ * logged and treated as `null` so the chain continues.
48
49
  *
49
50
  * NATIVE (Expo): shared cross-app identity key re-mint
50
51
  * (`signInWithSharedIdentity` → challenge→sign→verify plants the tokens).
51
52
  * The ONLY silent native arm (mirrors cold boot's `shared-key-signin`); the
52
- * `/auth/silent` web iframe is NEVER attempted on native. Returns `null`
53
+ * `/auth/silent` web iframe is NEVER attempted on native. Resolves to `null`
53
54
  * when the device holds no shared identity (e.g. a password-only native
54
55
  * sign-in) so a genuinely dead session reconciles to logged-out rather than
55
56
  * staying a zombie.
56
57
  *
57
58
  * WEB, in order:
58
- * 1. First-party `/auth/silent` iframe at the per-apex IdP
59
- * (`silentSignIn` with `authWebUrlOverride = autoDetectAuthWebUrl()`).
60
- * The durable cross-apex path: the iframe reads the first-party
61
- * `fedcm_session` cookie on `auth.<apex>` and mints a fresh Oxy token. No
62
- * top-level navigation → works in a backgrounded tab. On a `*.oxy.so`
63
- * app the per-apex host IS the central host, so this also covers
64
- * same-apex.
59
+ * 1. Per-apex `/auth/silent` iframe {@link mintSessionViaPerApexIframe}
60
+ * (shared verbatim with cold boot). The durable cross-apex path; also
61
+ * covers `*.oxy.so` (the per-apex host IS the central host there).
65
62
  * 2. FedCM silent re-auth (Chrome) — `silentSignInWithFedCM`.
66
- * 3. Same-apex refresh cookie — `refreshAllSessions`. On `*.oxy.so` the
67
- * httpOnly `oxy_rt_${n}` cookies ride along; we plant the active
68
- * account's rotated token. On a cross-apex RP it returns `{accounts:[]}`
69
- * and is a clean no-op. Unlike the cold-boot cookie restore this does NOT
70
- * rebuild multi-session state — an in-session refresh only needs a fresh
71
- * bearer.
63
+ * 3. Same-apex refresh cookie — `refreshAllSessions` + the shared
64
+ * {@link selectActiveRefreshAccount}; plant the active account's rotated
65
+ * token. On a cross-apex RP it returns `{accounts:[]}` (clean no-op).
66
+ * Unlike the cold-boot cookie restore this does NOT rebuild multi-session
67
+ * state — an in-session refresh only needs a fresh bearer.
72
68
  *
73
- * NO RECURSION: none of these arms issue requests through the authed client's
69
+ * NO RECURSION: no arm issues a request through the authed client's
74
70
  * `refreshAccessToken` path. The iframe/FedCM transports are postMessage /
75
71
  * credential APIs; the follow-up `/session/user` fetch inside `silentSignIn`
76
72
  * runs against the just-planted FULL-TTL token (≫ the preflight lead), so it
77
- * never re-enters the refresh path; `refreshAllSessions` uses a raw `fetch` with
78
- * `credentials:'include'`.
73
+ * never re-enters the refresh path; `refreshAllSessions` uses a raw `fetch`.
79
74
  */
80
75
  export declare function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRefreshHandler;
81
76
  /**
@@ -92,14 +87,15 @@ export interface TokenRefreshSchedulerHandle {
92
87
  * current access token's `exp`, calling `httpService.refreshAccessToken`
93
88
  * ('preflight') — which runs the installed handler and is deduped + cooldown-
94
89
  * guarded. After every attempt it reschedules from the (possibly rotated) token.
95
- * It also reschedules whenever the token changes (`onTokensChanged`) and, on
96
- * web tab-focus / native app-foreground, refreshes immediately if already inside
97
- * the lead window (a long-hidden tab throttles timers, so the token can be
98
- * expired on return).
90
+ * It also reschedules whenever the token changes (`onTokensChanged` so a
91
+ * sign-out that clears the token cancels the timer) and, on web tab-focus /
92
+ * native app-foreground, refreshes immediately if already inside the lead window
93
+ * (a long-hidden tab throttles timers, so the token can be expired on return).
99
94
  *
100
- * No-ops cleanly when there is no token, an opaque/no-`exp` token, or the host
101
- * lacks `getAccessTokenExpiry` (older stubs) in those cases the reactive 401
102
- * path remains the only refresh trigger.
95
+ * The `exp` is derived directly from the JWT via `getAccessTokenExpiry()`. No-ops
96
+ * cleanly when there is no token, an opaque/no-`exp` token, or the host lacks
97
+ * `getAccessTokenExpiry` (older stubs) — the reactive 401 path stays the only
98
+ * refresh trigger in those cases.
103
99
  */
104
100
  export declare function startTokenRefreshScheduler(oxyServices: OxyServices): TokenRefreshSchedulerHandle;
105
101
  //# sourceMappingURL=inSessionTokenRefresh.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inSessionTokenRefresh.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/inSessionTokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAwBrB;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAY5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,WAAW,GAAG,kBAAkB,CA8D1F;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,2BAA2B,CA4FhG"}
1
+ {"version":3,"file":"inSessionTokenRefresh.d.ts","sourceRoot":"","sources":["../../../../../src/ui/context/inSessionTokenRefresh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EACV,WAAW,EACX,kBAAkB,EAEnB,MAAM,aAAa,CAAC;AAyBrB;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAS,CAAC;AAQ5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,6BAA6B,CAAC,WAAW,EAAE,WAAW,GAAG,kBAAkB,CAsD1F;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,0BAA0B,CAAC,WAAW,EAAE,WAAW,GAAG,2BAA2B,CA4FhG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "12.1.0",
3
+ "version": "12.1.1",
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",
@@ -1418,14 +1418,13 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1418
1418
  // step that prevents the re-bounce loop: when it finds a session,
1419
1419
  // the terminal `sso-bounce` never fires.
1420
1420
  //
1421
- // The instance is configured with `authWebUrl=auth.oxy.so` (central,
1422
- // for the bounce + FedCM), so we explicitly point the iframe at the
1423
- // per-apex host via `autoDetectAuthWebUrl()` and `silentSignIn`'s
1424
- // `authWebUrlOverride`. On a `*.oxy.so` RP the per-apex host IS the
1425
- // central host (`auth.oxy.so`), so this is a same-host no-op-
1426
- // equivalent. When auto-detection bails (localhost/IP/single-label)
1427
- // there is no per-apex IdP and the step skips. Web only; on native
1428
- // `isWebBrowser()` gates it off, so native never runs an iframe.
1421
+ // The per-apex iframe mint itself lives in
1422
+ // `mintSessionViaPerApexIframe` (shared verbatim with the in-session
1423
+ // refresh handler so the two paths can never drift): it points the
1424
+ // iframe at `autoDetectAuthWebUrl()` and skips when there is no
1425
+ // per-apex IdP (localhost/IP/single-label/off-browser). Web only; on
1426
+ // native `isWebBrowser()` gates it off, so native never runs an
1427
+ // iframe.
1429
1428
  //
1430
1429
  // FIX-B: additionally skipped when `stored-session` already won.
1431
1430
  // FIX-D: bounded by `SILENT_IFRAME_TIMEOUT` (plus `iframe.onerror`
@@ -15,10 +15,10 @@
15
15
  *
16
16
  * 1. {@link createInSessionRefreshHandler} — an `AuthRefreshHandler` installed
17
17
  * on the owner client. It re-mints a fresh access token WITHOUT a page
18
- * reload by reusing the SAME durable per-apex silent-restore arms cold boot
19
- * uses (in order; first that yields a token wins). The linked client
20
- * (`createLinkedClient`) inherits the fix for free — its refresh delegates
21
- * back to the owner's `refreshAccessToken`.
18
+ * reload by composing the SAME silent-restore primitives cold boot uses
19
+ * ({@link mintSessionViaPerApexIframe}, {@link selectActiveRefreshAccount})
20
+ * not a copy. The linked client (`createLinkedClient`) inherits the fix for
21
+ * free: its refresh delegates back to the owner's `refreshAccessToken`.
22
22
  *
23
23
  * 2. {@link startTokenRefreshScheduler} — a proactive scheduler that refreshes
24
24
  * ~{@link TOKEN_REFRESH_LEAD_MS} before expiry (and on web tab-focus / native
@@ -35,10 +35,11 @@ import type {
35
35
  AuthRefreshHandler,
36
36
  AuthRefreshReason,
37
37
  } from '@oxyhq/core';
38
- import { autoDetectAuthWebUrl, logger as loggerUtil } from '@oxyhq/core';
38
+ import { logger as loggerUtil } from '@oxyhq/core';
39
39
  import { AppState, type AppStateStatus } from 'react-native';
40
40
  import { isWebBrowser } from '../hooks/useWebSSO';
41
41
  import { readActiveAuthuser } from '../utils/activeAuthuser';
42
+ import { mintSessionViaPerApexIframe, selectActiveRefreshAccount } from './silentSessionRestore';
42
43
 
43
44
  /**
44
45
  * Per-arm fail-fast budget (ms) for the web first-party `/auth/silent` iframe
@@ -67,114 +68,97 @@ const COOKIE_REFRESH_TIMEOUT = 4000;
67
68
  */
68
69
  export const TOKEN_REFRESH_LEAD_MS = 60_000;
69
70
 
70
- function debugRefresh(message: string, reason: AuthRefreshReason, error?: unknown): void {
71
- if (__DEV__) {
72
- loggerUtil.debug(
73
- message,
74
- { component: 'inSessionTokenRefresh', method: 'authRefreshHandler', reason },
75
- error,
76
- );
77
- }
78
- }
71
+ /**
72
+ * A single refresh arm: attempts one silent-restore mechanism and resolves to
73
+ * the freshly planted access token, or `null` to fall through to the next arm.
74
+ */
75
+ type RefreshArm = () => Promise<string | null>;
79
76
 
80
77
  /**
81
78
  * Build the in-session `AuthRefreshHandler` for the owner client.
82
79
  *
83
- * Arms (first to yield a fresh token wins; each is bounded and falls through on
84
- * failure). Every arm plants the fresh token internally, so on success we read
85
- * it back via `getAccessToken()`:
80
+ * Arms run in an explicit order; the first to plant a token wins. Each arm
81
+ * resolves to the planted token (read back via `getAccessToken()` the
82
+ * underlying SDK call plants it internally) or `null`. A throw in one arm is
83
+ * logged and treated as `null` so the chain continues.
86
84
  *
87
85
  * NATIVE (Expo): shared cross-app identity key re-mint
88
86
  * (`signInWithSharedIdentity` → challenge→sign→verify plants the tokens).
89
87
  * The ONLY silent native arm (mirrors cold boot's `shared-key-signin`); the
90
- * `/auth/silent` web iframe is NEVER attempted on native. Returns `null`
88
+ * `/auth/silent` web iframe is NEVER attempted on native. Resolves to `null`
91
89
  * when the device holds no shared identity (e.g. a password-only native
92
90
  * sign-in) so a genuinely dead session reconciles to logged-out rather than
93
91
  * staying a zombie.
94
92
  *
95
93
  * WEB, in order:
96
- * 1. First-party `/auth/silent` iframe at the per-apex IdP
97
- * (`silentSignIn` with `authWebUrlOverride = autoDetectAuthWebUrl()`).
98
- * The durable cross-apex path: the iframe reads the first-party
99
- * `fedcm_session` cookie on `auth.<apex>` and mints a fresh Oxy token. No
100
- * top-level navigation → works in a backgrounded tab. On a `*.oxy.so`
101
- * app the per-apex host IS the central host, so this also covers
102
- * same-apex.
94
+ * 1. Per-apex `/auth/silent` iframe {@link mintSessionViaPerApexIframe}
95
+ * (shared verbatim with cold boot). The durable cross-apex path; also
96
+ * covers `*.oxy.so` (the per-apex host IS the central host there).
103
97
  * 2. FedCM silent re-auth (Chrome) — `silentSignInWithFedCM`.
104
- * 3. Same-apex refresh cookie — `refreshAllSessions`. On `*.oxy.so` the
105
- * httpOnly `oxy_rt_${n}` cookies ride along; we plant the active
106
- * account's rotated token. On a cross-apex RP it returns `{accounts:[]}`
107
- * and is a clean no-op. Unlike the cold-boot cookie restore this does NOT
108
- * rebuild multi-session state — an in-session refresh only needs a fresh
109
- * bearer.
98
+ * 3. Same-apex refresh cookie — `refreshAllSessions` + the shared
99
+ * {@link selectActiveRefreshAccount}; plant the active account's rotated
100
+ * token. On a cross-apex RP it returns `{accounts:[]}` (clean no-op).
101
+ * Unlike the cold-boot cookie restore this does NOT rebuild multi-session
102
+ * state — an in-session refresh only needs a fresh bearer.
110
103
  *
111
- * NO RECURSION: none of these arms issue requests through the authed client's
104
+ * NO RECURSION: no arm issues a request through the authed client's
112
105
  * `refreshAccessToken` path. The iframe/FedCM transports are postMessage /
113
106
  * credential APIs; the follow-up `/session/user` fetch inside `silentSignIn`
114
107
  * runs against the just-planted FULL-TTL token (≫ the preflight lead), so it
115
- * never re-enters the refresh path; `refreshAllSessions` uses a raw `fetch` with
116
- * `credentials:'include'`.
108
+ * never re-enters the refresh path; `refreshAllSessions` uses a raw `fetch`.
117
109
  */
118
110
  export function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRefreshHandler {
119
- return async (reason: AuthRefreshReason): Promise<string | null> => {
120
- if (!isWebBrowser()) {
121
- try {
122
- const session = await oxyServices.signInWithSharedIdentity?.();
123
- if (session) {
124
- // `verifyChallenge` inside `signInWithSharedIdentity` already planted
125
- // the fresh tokens; read the planted access token back out.
126
- return oxyServices.getAccessToken();
127
- }
128
- } catch (error) {
129
- debugRefresh('Native shared-key in-session refresh failed', reason, error);
111
+ const runArm = async (label: string, reason: AuthRefreshReason, arm: RefreshArm): Promise<string | null> => {
112
+ try {
113
+ return await arm();
114
+ } catch (error) {
115
+ if (__DEV__) {
116
+ loggerUtil.debug(
117
+ `In-session refresh arm "${label}" failed (falling through)`,
118
+ { component: 'inSessionTokenRefresh', method: 'authRefreshHandler', reason },
119
+ error,
120
+ );
130
121
  }
131
122
  return null;
132
123
  }
124
+ };
133
125
 
134
- // WEB arm 1 first-party silent iframe at the per-apex IdP.
135
- try {
136
- const perApexAuthUrl = autoDetectAuthWebUrl();
137
- if (perApexAuthUrl) {
138
- const session = await oxyServices.silentSignIn?.({
139
- authWebUrlOverride: perApexAuthUrl,
140
- timeout: SILENT_IFRAME_REFRESH_TIMEOUT,
141
- });
142
- if (session) {
143
- return oxyServices.getAccessToken();
144
- }
126
+ const webArms: Array<[string, RefreshArm]> = [
127
+ ['silent-iframe', async () =>
128
+ (await mintSessionViaPerApexIframe(oxyServices, SILENT_IFRAME_REFRESH_TIMEOUT))
129
+ ? oxyServices.getAccessToken()
130
+ : null,
131
+ ],
132
+ ['fedcm-silent', async () => {
133
+ if (oxyServices.isFedCMSupported?.() !== true) {
134
+ return null;
145
135
  }
146
- } catch (error) {
147
- debugRefresh('Silent-iframe in-session refresh failed', reason, error);
148
- }
149
-
150
- // WEB arm 2 — FedCM silent re-auth (Chrome).
151
- try {
152
- if (oxyServices.isFedCMSupported?.() === true) {
153
- const session = await oxyServices.silentSignInWithFedCM?.();
154
- if (session) {
155
- return oxyServices.getAccessToken();
156
- }
136
+ return (await oxyServices.silentSignInWithFedCM?.()) ? oxyServices.getAccessToken() : null;
137
+ }],
138
+ ['refresh-cookie', async () => {
139
+ const snapshot = await oxyServices.refreshAllSessions({ timeout: COOKIE_REFRESH_TIMEOUT });
140
+ if (snapshot.accounts.length === 0) {
141
+ return null;
157
142
  }
158
- } catch (error) {
159
- debugRefresh('FedCM-silent in-session refresh failed', reason, error);
143
+ const active = selectActiveRefreshAccount(snapshot.accounts, readActiveAuthuser());
144
+ oxyServices.httpService.setTokens(active.accessToken);
145
+ return oxyServices.getAccessToken();
146
+ }],
147
+ ];
148
+
149
+ return async (reason: AuthRefreshReason): Promise<string | null> => {
150
+ if (!isWebBrowser()) {
151
+ return runArm('native-shared-key', reason, async () =>
152
+ (await oxyServices.signInWithSharedIdentity?.()) ? oxyServices.getAccessToken() : null,
153
+ );
160
154
  }
161
155
 
162
- // WEB arm 3 same-apex refresh cookie.
163
- try {
164
- const snapshot = await oxyServices.refreshAllSessions({ timeout: COOKIE_REFRESH_TIMEOUT });
165
- if (snapshot.accounts.length > 0) {
166
- const persistedAuthuser = readActiveAuthuser();
167
- const active =
168
- (persistedAuthuser !== null
169
- ? snapshot.accounts.find((account) => account.authuser === persistedAuthuser)
170
- : undefined) ?? snapshot.accounts[0];
171
- oxyServices.httpService.setTokens(active.accessToken);
172
- return oxyServices.getAccessToken();
156
+ for (const [label, arm] of webArms) {
157
+ const token = await runArm(label, reason, arm);
158
+ if (token) {
159
+ return token;
173
160
  }
174
- } catch (error) {
175
- debugRefresh('Refresh-cookie in-session refresh failed', reason, error);
176
161
  }
177
-
178
162
  return null;
179
163
  };
180
164
  }
@@ -194,14 +178,15 @@ export interface TokenRefreshSchedulerHandle {
194
178
  * current access token's `exp`, calling `httpService.refreshAccessToken`
195
179
  * ('preflight') — which runs the installed handler and is deduped + cooldown-
196
180
  * guarded. After every attempt it reschedules from the (possibly rotated) token.
197
- * It also reschedules whenever the token changes (`onTokensChanged`) and, on
198
- * web tab-focus / native app-foreground, refreshes immediately if already inside
199
- * the lead window (a long-hidden tab throttles timers, so the token can be
200
- * expired on return).
181
+ * It also reschedules whenever the token changes (`onTokensChanged` so a
182
+ * sign-out that clears the token cancels the timer) and, on web tab-focus /
183
+ * native app-foreground, refreshes immediately if already inside the lead window
184
+ * (a long-hidden tab throttles timers, so the token can be expired on return).
201
185
  *
202
- * No-ops cleanly when there is no token, an opaque/no-`exp` token, or the host
203
- * lacks `getAccessTokenExpiry` (older stubs) in those cases the reactive 401
204
- * path remains the only refresh trigger.
186
+ * The `exp` is derived directly from the JWT via `getAccessTokenExpiry()`. No-ops
187
+ * cleanly when there is no token, an opaque/no-`exp` token, or the host lacks
188
+ * `getAccessTokenExpiry` (older stubs) — the reactive 401 path stays the only
189
+ * refresh trigger in those cases.
205
190
  */
206
191
  export function startTokenRefreshScheduler(oxyServices: OxyServices): TokenRefreshSchedulerHandle {
207
192
  let disposed = false;