@oxyhq/services 14.1.0 → 15.0.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.
- package/lib/commonjs/index.js +0 -17
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/SignInAccountChooser.js +189 -0
- package/lib/commonjs/ui/components/SignInAccountChooser.js.map +1 -0
- package/lib/commonjs/ui/components/SignInModal.js +350 -131
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +340 -1547
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +61 -64
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +14 -33
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js +170 -0
- package/lib/commonjs/ui/hooks/usePasswordSignIn.js.map +1 -0
- package/lib/commonjs/ui/hooks/useProfileEditing.js +2 -5
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +2 -2
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/navigation/routes.js +1 -0
- package/lib/commonjs/ui/navigation/routes.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js +93 -68
- package/lib/commonjs/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/EditProfileScreen.js +187 -0
- package/lib/commonjs/ui/screens/EditProfileScreen.js.map +1 -0
- package/lib/commonjs/ui/screens/ManageAccountScreen.js +3 -7
- package/lib/commonjs/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +231 -95
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/ProfileScreen.js +15 -2
- package/lib/commonjs/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/linkFormat.js +38 -0
- package/lib/commonjs/ui/screens/linkFormat.js.map +1 -0
- package/lib/commonjs/ui/session/authStore.js +146 -0
- package/lib/commonjs/ui/session/authStore.js.map +1 -0
- package/lib/commonjs/ui/session/createSessionClient.js +23 -17
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -1
- package/lib/commonjs/ui/session/index.js +13 -7
- package/lib/commonjs/ui/session/index.js.map +1 -1
- package/lib/commonjs/ui/session/tokenTransport.js +19 -31
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -1
- package/lib/commonjs/ui/utils/isWebBrowser.js +13 -0
- package/lib/commonjs/ui/utils/isWebBrowser.js.map +1 -0
- package/lib/commonjs/utils/deviceFlowSignIn.js +13 -16
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +0 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/SignInAccountChooser.js +183 -0
- package/lib/module/ui/components/SignInAccountChooser.js.map +1 -0
- package/lib/module/ui/components/SignInModal.js +352 -134
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +342 -1551
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +60 -63
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +14 -33
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/usePasswordSignIn.js +165 -0
- package/lib/module/ui/hooks/usePasswordSignIn.js.map +1 -0
- package/lib/module/ui/hooks/useProfileEditing.js +2 -5
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/navigation/routes.js +1 -0
- package/lib/module/ui/navigation/routes.js.map +1 -1
- package/lib/module/ui/screens/EditProfileFieldScreen.js +91 -66
- package/lib/module/ui/screens/EditProfileFieldScreen.js.map +1 -1
- package/lib/module/ui/screens/EditProfileScreen.js +182 -0
- package/lib/module/ui/screens/EditProfileScreen.js.map +1 -0
- package/lib/module/ui/screens/ManageAccountScreen.js +3 -7
- package/lib/module/ui/screens/ManageAccountScreen.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +233 -97
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/screens/ProfileScreen.js +15 -2
- package/lib/module/ui/screens/ProfileScreen.js.map +1 -1
- package/lib/module/ui/screens/linkFormat.js +31 -0
- package/lib/module/ui/screens/linkFormat.js.map +1 -0
- package/lib/module/ui/session/authStore.js +143 -0
- package/lib/module/ui/session/authStore.js.map +1 -0
- package/lib/module/ui/session/createSessionClient.js +22 -16
- package/lib/module/ui/session/createSessionClient.js.map +1 -1
- package/lib/module/ui/session/index.js +9 -9
- package/lib/module/ui/session/index.js.map +1 -1
- package/lib/module/ui/session/tokenTransport.js +20 -33
- package/lib/module/ui/session/tokenTransport.js.map +1 -1
- package/lib/module/ui/utils/isWebBrowser.js +11 -0
- package/lib/module/ui/utils/isWebBrowser.js.map +1 -0
- package/lib/module/utils/deviceFlowSignIn.js +14 -17
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +0 -1
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts +29 -0
- package/lib/typescript/commonjs/ui/components/SignInAccountChooser.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts +15 -10
- package/lib/typescript/commonjs/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +36 -42
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +18 -23
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +0 -2
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts +31 -0
- package/lib/typescript/commonjs/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts +0 -1
- package/lib/typescript/commonjs/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/commonjs/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts +14 -0
- package/lib/typescript/commonjs/ui/screens/EditProfileScreen.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/commonjs/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts +13 -12
- package/lib/typescript/commonjs/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts +23 -0
- package/lib/typescript/commonjs/ui/screens/linkFormat.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/authStore.d.ts +33 -0
- package/lib/typescript/commonjs/ui/session/authStore.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +19 -15
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/index.d.ts +9 -9
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +13 -19
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts +9 -0
- package/lib/typescript/commonjs/ui/utils/isWebBrowser.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +0 -1
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts +29 -0
- package/lib/typescript/module/ui/components/SignInAccountChooser.d.ts.map +1 -0
- package/lib/typescript/module/ui/components/SignInModal.d.ts +15 -10
- package/lib/typescript/module/ui/components/SignInModal.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +36 -42
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +18 -23
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +0 -2
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts +31 -0
- package/lib/typescript/module/ui/hooks/usePasswordSignIn.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts +0 -1
- package/lib/typescript/module/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts +1 -1
- package/lib/typescript/module/ui/navigation/routes.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileFieldScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts +14 -0
- package/lib/typescript/module/ui/screens/EditProfileScreen.d.ts.map +1 -0
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts +1 -1
- package/lib/typescript/module/ui/screens/ManageAccountScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts +13 -12
- package/lib/typescript/module/ui/screens/OxyAuthScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/ProfileScreen.d.ts.map +1 -1
- package/lib/typescript/module/ui/screens/linkFormat.d.ts +23 -0
- package/lib/typescript/module/ui/screens/linkFormat.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/authStore.d.ts +33 -0
- package/lib/typescript/module/ui/session/authStore.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +19 -15
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/index.d.ts +9 -9
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +13 -19
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/isWebBrowser.d.ts +9 -0
- package/lib/typescript/module/ui/utils/isWebBrowser.d.ts.map +1 -0
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +15 -13
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +0 -4
- package/src/ui/components/SignInAccountChooser.tsx +162 -0
- package/src/ui/components/SignInModal.tsx +309 -139
- package/src/ui/context/OxyContext.tsx +532 -1785
- package/src/ui/context/hooks/useAuthOperations.ts +65 -76
- package/src/ui/hooks/useAuth.ts +14 -35
- package/src/ui/hooks/usePasswordSignIn.ts +207 -0
- package/src/ui/hooks/useProfileEditing.ts +2 -8
- package/src/ui/hooks/useSessionManagement.ts +1 -1
- package/src/ui/navigation/routes.ts +2 -0
- package/src/ui/screens/EditProfileFieldScreen.tsx +102 -66
- package/src/ui/screens/EditProfileScreen.tsx +155 -0
- package/src/ui/screens/ManageAccountScreen.tsx +3 -7
- package/src/ui/screens/OxyAuthScreen.tsx +259 -112
- package/src/ui/screens/ProfileScreen.tsx +15 -2
- package/src/ui/screens/__tests__/linkFormat.test.ts +73 -0
- package/src/ui/screens/linkFormat.ts +37 -0
- package/src/ui/session/__tests__/createSessionClient.test.ts +22 -7
- package/src/ui/session/__tests__/tokenTransport.test.ts +37 -49
- package/src/ui/session/authStore.ts +164 -0
- package/src/ui/session/createSessionClient.ts +30 -17
- package/src/ui/session/index.ts +10 -9
- package/src/ui/session/tokenTransport.ts +26 -36
- package/src/ui/utils/isWebBrowser.ts +8 -0
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +50 -131
- package/src/utils/deviceFlowSignIn.ts +19 -26
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js +0 -243
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +0 -1
- package/lib/commonjs/ui/context/silentSessionRestore.js +0 -56
- package/lib/commonjs/ui/context/silentSessionRestore.js.map +0 -1
- package/lib/commonjs/ui/hooks/useWebSSO.js +0 -28
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +0 -1
- package/lib/commonjs/ui/session/projectSessionState.js +0 -86
- package/lib/commonjs/ui/session/projectSessionState.js.map +0 -1
- package/lib/commonjs/ui/session/sessionClientHost.js +0 -30
- package/lib/commonjs/ui/session/sessionClientHost.js.map +0 -1
- package/lib/commonjs/ui/utils/activeAuthuser.js +0 -142
- package/lib/commonjs/ui/utils/activeAuthuser.js.map +0 -1
- package/lib/commonjs/utils/crossApex.js +0 -74
- package/lib/commonjs/utils/crossApex.js.map +0 -1
- package/lib/module/ui/context/inSessionTokenRefresh.js +0 -238
- package/lib/module/ui/context/inSessionTokenRefresh.js.map +0 -1
- package/lib/module/ui/context/silentSessionRestore.js +0 -53
- package/lib/module/ui/context/silentSessionRestore.js.map +0 -1
- package/lib/module/ui/hooks/useWebSSO.js +0 -24
- package/lib/module/ui/hooks/useWebSSO.js.map +0 -1
- package/lib/module/ui/session/projectSessionState.js +0 -79
- package/lib/module/ui/session/projectSessionState.js.map +0 -1
- package/lib/module/ui/session/sessionClientHost.js +0 -26
- package/lib/module/ui/session/sessionClientHost.js.map +0 -1
- package/lib/module/ui/utils/activeAuthuser.js +0 -134
- package/lib/module/ui/utils/activeAuthuser.js.map +0 -1
- package/lib/module/utils/crossApex.js +0 -69
- package/lib/module/utils/crossApex.js.map +0 -1
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +0 -106
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +0 -36
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +0 -19
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +0 -45
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +0 -14
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +0 -67
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/crossApex.d.ts +0 -55
- package/lib/typescript/commonjs/utils/crossApex.d.ts.map +0 -1
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +0 -106
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +0 -1
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +0 -36
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +0 -19
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +0 -1
- package/lib/typescript/module/ui/session/projectSessionState.d.ts +0 -45
- package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +0 -1
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts +0 -14
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +0 -1
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +0 -67
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +0 -1
- package/lib/typescript/module/utils/crossApex.d.ts +0 -55
- package/lib/typescript/module/utils/crossApex.d.ts.map +0 -1
- package/src/ui/context/inSessionTokenRefresh.ts +0 -264
- package/src/ui/context/silentSessionRestore.ts +0 -53
- package/src/ui/hooks/useWebSSO.ts +0 -23
- package/src/ui/session/__tests__/projectSessionState.test.ts +0 -114
- package/src/ui/session/__tests__/sessionClientHost.test.ts +0 -43
- package/src/ui/session/projectSessionState.ts +0 -85
- package/src/ui/session/sessionClientHost.ts +0 -27
- package/src/ui/utils/activeAuthuser.ts +0 -142
- package/src/utils/crossApex.ts +0 -75
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* In-session access-token refresh for the React Native / Expo SDK.
|
|
3
|
-
*
|
|
4
|
-
* THE GAP THIS CLOSES: the services `OxyContext` owns the session but never
|
|
5
|
-
* installed an `authRefreshHandler` on the owner `HttpService`, so
|
|
6
|
-
* `HttpService.refreshAccessToken` short-circuited to `null` — there was NO
|
|
7
|
-
* in-session token refresh on the RN path at all. A 15-minute access token
|
|
8
|
-
* expired with the tab/app open and nothing re-minted one; cross-apex RP feeds
|
|
9
|
-
* (mention.earth, …) then 401'd in a loop while `isAuthenticated` stayed `true`
|
|
10
|
-
* (a zombie logged-in state), because the `Domain=oxy.so` refresh cookie can't
|
|
11
|
-
* reach `api.<apex>`. (`AuthManager`, which installs a refresh handler on the
|
|
12
|
-
* web path, is only ever constructed by `WebOxyProvider` in `@oxyhq/auth`.)
|
|
13
|
-
*
|
|
14
|
-
* THE FIX, two cooperating pieces both wired from `OxyContext`:
|
|
15
|
-
*
|
|
16
|
-
* 1. {@link createInSessionRefreshHandler} — an `AuthRefreshHandler` installed
|
|
17
|
-
* on the owner client. It re-mints a fresh access token WITHOUT a page
|
|
18
|
-
* reload by composing the SAME silent-restore primitive cold boot uses
|
|
19
|
-
* ({@link mintSessionViaPerApexIframe}) — not a copy. The linked client
|
|
20
|
-
* (`createLinkedClient`) inherits the fix for free: its refresh delegates
|
|
21
|
-
* back to the owner's `refreshAccessToken`.
|
|
22
|
-
*
|
|
23
|
-
* 2. {@link startTokenRefreshScheduler} — a proactive scheduler that refreshes
|
|
24
|
-
* ~{@link TOKEN_REFRESH_LEAD_MS} before expiry (and on web tab-focus / native
|
|
25
|
-
* app-foreground), so the common case never even reaches the reactive
|
|
26
|
-
* 401-then-recover flash.
|
|
27
|
-
*
|
|
28
|
-
* Concurrency/cooldown/dedup are owned by `HttpService.refreshAccessToken`
|
|
29
|
-
* (single in-flight `tokenRefreshPromise` + cooldown) — this module does not
|
|
30
|
-
* reimplement them, so the timer / foreground / per-request triggers collapse to
|
|
31
|
-
* one network attempt (no refresh storm).
|
|
32
|
-
*
|
|
33
|
-
* NOTE (session-sync cutover, Task 5): the web arm chain used to fall through
|
|
34
|
-
* to a same-apex `oxy_rt` refresh-cookie arm (`refreshAllSessions` +
|
|
35
|
-
* `selectActiveRefreshAccount`). That arm is DELETED — the device account set
|
|
36
|
-
* is now server-authoritative via `SessionClient` (`@oxyhq/core`), which
|
|
37
|
-
* `OxyContext` bootstraps/reprojects independently of in-session token
|
|
38
|
-
* refresh. `selectActiveRefreshAccount` had no other caller and was deleted
|
|
39
|
-
* with it.
|
|
40
|
-
*/
|
|
41
|
-
import type {
|
|
42
|
-
OxyServices,
|
|
43
|
-
AuthRefreshHandler,
|
|
44
|
-
AuthRefreshReason,
|
|
45
|
-
} from '@oxyhq/core';
|
|
46
|
-
import { logger as loggerUtil } from '@oxyhq/core';
|
|
47
|
-
import { AppState, type AppStateStatus } from 'react-native';
|
|
48
|
-
import { isWebBrowser } from '../hooks/useWebSSO';
|
|
49
|
-
import { mintSessionViaPerApexIframe } from './silentSessionRestore';
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Per-arm fail-fast budget (ms) for the web first-party `/auth/silent` iframe
|
|
53
|
-
* arm. Slightly more generous than the cold-boot iframe budget (2.5s) because an
|
|
54
|
-
* in-session refresh is NOT in the first-paint critical path — a couple hundred
|
|
55
|
-
* ms of extra headroom for the same-origin handshake is worth a higher success
|
|
56
|
-
* rate. `silentSignIn` still fail-fasts on `iframe.onerror`, so a hard failure
|
|
57
|
-
* returns well before this.
|
|
58
|
-
*/
|
|
59
|
-
const SILENT_IFRAME_REFRESH_TIMEOUT = 4000;
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Lead time (ms) before access-token expiry at which the proactive scheduler
|
|
63
|
-
* refreshes. Mirrors `HttpService`'s per-request `TOKEN_REFRESH_LEAD_SECONDS`
|
|
64
|
-
* (60s) so the scheduled refresh and the request-time preflight refresh use the
|
|
65
|
-
* same window — the scheduler simply fires it during idle/background instead of
|
|
66
|
-
* waiting for the next request.
|
|
67
|
-
*/
|
|
68
|
-
export const TOKEN_REFRESH_LEAD_MS = 60_000;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* A single refresh arm: attempts one silent-restore mechanism and resolves to
|
|
72
|
-
* the freshly planted access token, or `null` to fall through to the next arm.
|
|
73
|
-
*/
|
|
74
|
-
type RefreshArm = () => Promise<string | null>;
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Build the in-session `AuthRefreshHandler` for the owner client.
|
|
78
|
-
*
|
|
79
|
-
* Arms run in an explicit order; the first to plant a token wins. Each arm
|
|
80
|
-
* resolves to the planted token (read back via `getAccessToken()` — the
|
|
81
|
-
* underlying SDK call plants it internally) or `null`. A throw in one arm is
|
|
82
|
-
* logged and treated as `null` so the chain continues.
|
|
83
|
-
*
|
|
84
|
-
* NATIVE (Expo): shared cross-app identity key re-mint
|
|
85
|
-
* (`signInWithSharedIdentity` → challenge→sign→verify plants the tokens).
|
|
86
|
-
* The ONLY silent native arm (mirrors cold boot's `shared-key-signin`); the
|
|
87
|
-
* `/auth/silent` web iframe is NEVER attempted on native. Resolves to `null`
|
|
88
|
-
* when the device holds no shared identity (e.g. a password-only native
|
|
89
|
-
* sign-in) so a genuinely dead session reconciles to logged-out rather than
|
|
90
|
-
* staying a zombie.
|
|
91
|
-
*
|
|
92
|
-
* WEB: Per-apex `/auth/silent` iframe — {@link mintSessionViaPerApexIframe}
|
|
93
|
-
* (shared verbatim with cold boot). The durable cross-apex path; also
|
|
94
|
-
* covers `*.oxy.so` (the per-apex host IS the central host there). There
|
|
95
|
-
* is no FedCM arm — FedCM was removed from the client sign-in/refresh path
|
|
96
|
-
* entirely (Chrome-only; see `CrossDomainAuth`'s doc comment in
|
|
97
|
-
* `@oxyhq/core` for the production sign-in loop that motivated the
|
|
98
|
-
* removal).
|
|
99
|
-
*
|
|
100
|
-
* NO RECURSION: no arm issues a request through the authed client's
|
|
101
|
-
* `refreshAccessToken` path. The iframe transport is postMessage-based; the
|
|
102
|
-
* follow-up `/session/user` fetch inside `silentSignIn`
|
|
103
|
-
* runs against the just-planted FULL-TTL token (≫ the preflight lead), so it
|
|
104
|
-
* never re-enters the refresh path.
|
|
105
|
-
*/
|
|
106
|
-
export function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRefreshHandler {
|
|
107
|
-
const runArm = async (label: string, reason: AuthRefreshReason, arm: RefreshArm): Promise<string | null> => {
|
|
108
|
-
try {
|
|
109
|
-
return await arm();
|
|
110
|
-
} catch (error) {
|
|
111
|
-
if (__DEV__) {
|
|
112
|
-
loggerUtil.debug(
|
|
113
|
-
`In-session refresh arm "${label}" failed (falling through)`,
|
|
114
|
-
{ component: 'inSessionTokenRefresh', method: 'authRefreshHandler', reason },
|
|
115
|
-
error,
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
return null;
|
|
119
|
-
}
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
const webArms: Array<[string, RefreshArm]> = [
|
|
123
|
-
['silent-iframe', async () =>
|
|
124
|
-
(await mintSessionViaPerApexIframe(oxyServices, SILENT_IFRAME_REFRESH_TIMEOUT))
|
|
125
|
-
? oxyServices.getAccessToken()
|
|
126
|
-
: null,
|
|
127
|
-
],
|
|
128
|
-
];
|
|
129
|
-
|
|
130
|
-
return async (reason: AuthRefreshReason): Promise<string | null> => {
|
|
131
|
-
if (!isWebBrowser()) {
|
|
132
|
-
return runArm('native-shared-key', reason, async () =>
|
|
133
|
-
(await oxyServices.signInWithSharedIdentity?.()) ? oxyServices.getAccessToken() : null,
|
|
134
|
-
);
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
for (const [label, arm] of webArms) {
|
|
138
|
-
const token = await runArm(label, reason, arm);
|
|
139
|
-
if (token) {
|
|
140
|
-
return token;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return null;
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
/**
|
|
148
|
-
* Handle returned by {@link startTokenRefreshScheduler}; call `dispose()` to tear
|
|
149
|
-
* down the timer and the foreground listener.
|
|
150
|
-
*/
|
|
151
|
-
export interface TokenRefreshSchedulerHandle {
|
|
152
|
-
dispose(): void;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
/**
|
|
156
|
-
* Start the proactive in-session refresh scheduler against `oxyServices`.
|
|
157
|
-
*
|
|
158
|
-
* Schedules a single timer to fire {@link TOKEN_REFRESH_LEAD_MS} before the
|
|
159
|
-
* current access token's `exp`, calling `httpService.refreshAccessToken`
|
|
160
|
-
* ('preflight') — which runs the installed handler and is deduped + cooldown-
|
|
161
|
-
* guarded. After every attempt it reschedules from the (possibly rotated) token.
|
|
162
|
-
* It also reschedules whenever the token changes (`onTokensChanged` — so a
|
|
163
|
-
* sign-out that clears the token cancels the timer) and, on web tab-focus /
|
|
164
|
-
* native app-foreground, refreshes immediately if already inside the lead window
|
|
165
|
-
* (a long-hidden tab throttles timers, so the token can be expired on return).
|
|
166
|
-
*
|
|
167
|
-
* The `exp` is derived directly from the JWT via `getAccessTokenExpiry()`. No-ops
|
|
168
|
-
* cleanly when there is no token, an opaque/no-`exp` token, or the host lacks
|
|
169
|
-
* `getAccessTokenExpiry` (older stubs) — the reactive 401 path stays the only
|
|
170
|
-
* refresh trigger in those cases.
|
|
171
|
-
*/
|
|
172
|
-
export function startTokenRefreshScheduler(oxyServices: OxyServices): TokenRefreshSchedulerHandle {
|
|
173
|
-
let disposed = false;
|
|
174
|
-
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
175
|
-
|
|
176
|
-
const clearTimer = (): void => {
|
|
177
|
-
if (timer !== null) {
|
|
178
|
-
clearTimeout(timer);
|
|
179
|
-
timer = null;
|
|
180
|
-
}
|
|
181
|
-
};
|
|
182
|
-
|
|
183
|
-
const runRefresh = (): void => {
|
|
184
|
-
// `refreshAccessToken` is deduped + cooldown-guarded internally, so this is
|
|
185
|
-
// safe to call from the timer, the foreground listener, and request-time
|
|
186
|
-
// preflight concurrently — they collapse to one network attempt.
|
|
187
|
-
const refresh = oxyServices.httpService.refreshAccessToken?.('preflight');
|
|
188
|
-
if (!refresh) {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
void refresh
|
|
192
|
-
.catch(() => null)
|
|
193
|
-
.finally(() => {
|
|
194
|
-
if (!disposed) {
|
|
195
|
-
schedule();
|
|
196
|
-
}
|
|
197
|
-
});
|
|
198
|
-
};
|
|
199
|
-
|
|
200
|
-
const schedule = (): void => {
|
|
201
|
-
clearTimer();
|
|
202
|
-
if (disposed || !oxyServices.getAccessToken()) {
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const expSeconds = oxyServices.getAccessTokenExpiry?.() ?? null;
|
|
206
|
-
if (expSeconds === null) {
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
const fireInMs = expSeconds * 1000 - Date.now() - TOKEN_REFRESH_LEAD_MS;
|
|
210
|
-
timer = setTimeout(runRefresh, Math.max(fireInMs, 0));
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
const onForeground = (): void => {
|
|
214
|
-
if (disposed || !oxyServices.getAccessToken()) {
|
|
215
|
-
return;
|
|
216
|
-
}
|
|
217
|
-
const expSeconds = oxyServices.getAccessTokenExpiry?.() ?? null;
|
|
218
|
-
if (expSeconds === null) {
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
const remainingMs = expSeconds * 1000 - Date.now();
|
|
222
|
-
if (remainingMs <= TOKEN_REFRESH_LEAD_MS) {
|
|
223
|
-
runRefresh();
|
|
224
|
-
} else {
|
|
225
|
-
schedule();
|
|
226
|
-
}
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
const unsubscribeTokens = oxyServices.onTokensChanged(() => {
|
|
230
|
-
if (!disposed) {
|
|
231
|
-
schedule();
|
|
232
|
-
}
|
|
233
|
-
});
|
|
234
|
-
|
|
235
|
-
let removeForeground: (() => void) | null = null;
|
|
236
|
-
if (isWebBrowser() && typeof document !== 'undefined') {
|
|
237
|
-
const handler = (): void => {
|
|
238
|
-
if (document.visibilityState === 'visible') {
|
|
239
|
-
onForeground();
|
|
240
|
-
}
|
|
241
|
-
};
|
|
242
|
-
document.addEventListener('visibilitychange', handler);
|
|
243
|
-
removeForeground = () => document.removeEventListener('visibilitychange', handler);
|
|
244
|
-
} else if (!isWebBrowser() && AppState && typeof AppState.addEventListener === 'function') {
|
|
245
|
-
const subscription = AppState.addEventListener('change', (state: AppStateStatus) => {
|
|
246
|
-
if (state === 'active') {
|
|
247
|
-
onForeground();
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
removeForeground = () => subscription.remove();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
schedule();
|
|
254
|
-
|
|
255
|
-
return {
|
|
256
|
-
dispose(): void {
|
|
257
|
-
disposed = true;
|
|
258
|
-
clearTimer();
|
|
259
|
-
unsubscribeTokens();
|
|
260
|
-
removeForeground?.();
|
|
261
|
-
removeForeground = null;
|
|
262
|
-
},
|
|
263
|
-
};
|
|
264
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Silent, no-reload session-restore PRIMITIVE — the single shared
|
|
3
|
-
* implementation used by BOTH cold boot (`OxyContext.restoreSessionsFromStorage`)
|
|
4
|
-
* and in-session token refresh (`createInSessionRefreshHandler`). Neither caller
|
|
5
|
-
* re-implements it; they compose it. Keeping one home avoids the two paths
|
|
6
|
-
* drifting on "how do we mint a first-party token without a page reload".
|
|
7
|
-
*
|
|
8
|
-
* Platform-agnostic at the type level and returns plain data; the callers own
|
|
9
|
-
* the side effects that differ between them (cold boot COMMITS the recovered
|
|
10
|
-
* session into provider state; refresh only reads the freshly planted bearer).
|
|
11
|
-
*
|
|
12
|
-
* NOTE (session-sync cutover, Task 5): this file used to also export
|
|
13
|
-
* `selectActiveRefreshAccount`, which picked the active account out of a
|
|
14
|
-
* `refreshAllSessions` (`oxy_rt` refresh-cookie) snapshot. Deleted — its only
|
|
15
|
-
* caller was the in-session refresh handler's now-deleted `refresh-cookie`
|
|
16
|
-
* arm. The device account SET is server-authoritative via `SessionClient`
|
|
17
|
-
* (`@oxyhq/core`) now.
|
|
18
|
-
*/
|
|
19
|
-
import type { OxyServices, SessionLoginResponse } from '@oxyhq/core';
|
|
20
|
-
import { autoDetectAuthWebUrl } from '@oxyhq/core';
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Mint a fresh first-party session via the PER-APEX `/auth/silent` iframe — the
|
|
24
|
-
* durable cross-domain restore path that works WITHOUT a top-level navigation
|
|
25
|
-
* (so it succeeds under Safari ITP / Firefox TCP and in a backgrounded tab).
|
|
26
|
-
*
|
|
27
|
-
* The instance is configured with the CENTRAL auth URL, so we explicitly point
|
|
28
|
-
* the iframe at the per-apex host (`auth.<rp-apex>`) via `autoDetectAuthWebUrl()`
|
|
29
|
-
* + `silentSignIn`'s `authWebUrlOverride`. On a `*.oxy.so` app the per-apex host
|
|
30
|
-
* IS the central host, so this also covers same-apex. When auto-detection bails
|
|
31
|
-
* (localhost / IP / single-label / off-browser) there is no per-apex IdP and we
|
|
32
|
-
* return `null`. `silentSignIn` plants the access token internally on success.
|
|
33
|
-
*
|
|
34
|
-
* @returns the recovered session (token already planted) when complete, else
|
|
35
|
-
* `null` (no per-apex IdP, no session, or an incomplete iframe response).
|
|
36
|
-
*/
|
|
37
|
-
export async function mintSessionViaPerApexIframe(
|
|
38
|
-
oxyServices: OxyServices,
|
|
39
|
-
timeoutMs: number,
|
|
40
|
-
): Promise<SessionLoginResponse | null> {
|
|
41
|
-
const perApexAuthUrl = autoDetectAuthWebUrl();
|
|
42
|
-
if (!perApexAuthUrl) {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
const session = await oxyServices.silentSignIn?.({
|
|
46
|
-
authWebUrlOverride: perApexAuthUrl,
|
|
47
|
-
timeout: timeoutMs,
|
|
48
|
-
});
|
|
49
|
-
if (!session?.user || !session.sessionId) {
|
|
50
|
-
return null;
|
|
51
|
-
}
|
|
52
|
-
return session;
|
|
53
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Platform detection helper for the web auth/session-sync surfaces.
|
|
3
|
-
*
|
|
4
|
-
* This module previously hosted a FedCM-based `useWebSSO` hook (Federated
|
|
5
|
-
* Credential Management — Chrome-only). It was removed: FedCM is no longer
|
|
6
|
-
* used anywhere in the client sign-in/cold-boot path (see `CrossDomainAuth`'s
|
|
7
|
-
* doc comment in `@oxyhq/core` for the production sign-in loop that motivated
|
|
8
|
-
* the removal). Cross-domain silent SSO is owned entirely by the
|
|
9
|
-
* `silent-iframe` cold-boot step (per-apex `/auth/silent`) plus the terminal
|
|
10
|
-
* `/sso` bounce in `OxyContext`.
|
|
11
|
-
*
|
|
12
|
-
* `isWebBrowser` is kept here (rather than moved) so every existing consumer
|
|
13
|
-
* import path stays valid.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Check if we're running in a web browser environment (not React Native)
|
|
18
|
-
*/
|
|
19
|
-
export function isWebBrowser(): boolean {
|
|
20
|
-
return typeof window !== 'undefined' &&
|
|
21
|
-
typeof document !== 'undefined' &&
|
|
22
|
-
typeof document.documentElement !== 'undefined';
|
|
23
|
-
}
|
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
import type { DeviceSessionState } from '@oxyhq/contracts';
|
|
2
|
-
import type { User } from '@oxyhq/core';
|
|
3
|
-
import {
|
|
4
|
-
accountIdsOf,
|
|
5
|
-
activeSessionIdOf,
|
|
6
|
-
activeUserOf,
|
|
7
|
-
deviceStateToClientSessions,
|
|
8
|
-
} from '../projectSessionState';
|
|
9
|
-
|
|
10
|
-
function makeUser(id: string): User {
|
|
11
|
-
return {
|
|
12
|
-
id,
|
|
13
|
-
publicKey: `pk-${id}`,
|
|
14
|
-
username: `user-${id}`,
|
|
15
|
-
name: {},
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// DeviceSessionState.updatedAt is an epoch-ms number on the wire (see
|
|
20
|
-
// packages/contracts/src/deviceSession.ts: `updatedAt: z.number()`), not an
|
|
21
|
-
// ISO string.
|
|
22
|
-
const UPDATED_AT_MS = Date.UTC(2026, 6, 1, 0, 0, 0, 0);
|
|
23
|
-
const UPDATED_AT_ISO = new Date(UPDATED_AT_MS).toISOString();
|
|
24
|
-
|
|
25
|
-
const state: DeviceSessionState = {
|
|
26
|
-
deviceId: 'device-1',
|
|
27
|
-
accounts: [
|
|
28
|
-
{ accountId: 'a1', sessionId: 'sess-a1', authuser: 0 },
|
|
29
|
-
{ accountId: 'a2', sessionId: 'sess-a2', authuser: 1 },
|
|
30
|
-
],
|
|
31
|
-
activeAccountId: 'a2',
|
|
32
|
-
revision: 1,
|
|
33
|
-
updatedAt: UPDATED_AT_MS,
|
|
34
|
-
};
|
|
35
|
-
|
|
36
|
-
const usersById = new Map<string, User>([
|
|
37
|
-
['a1', makeUser('a1')],
|
|
38
|
-
['a2', makeUser('a2')],
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
describe('projectSessionState', () => {
|
|
42
|
-
describe('activeSessionIdOf', () => {
|
|
43
|
-
test('returns the active account sessionId', () => {
|
|
44
|
-
expect(activeSessionIdOf(state)).toBe('sess-a2');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test('returns null for null state', () => {
|
|
48
|
-
expect(activeSessionIdOf(null)).toBeNull();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test('returns null when activeAccountId is null', () => {
|
|
52
|
-
expect(activeSessionIdOf({ ...state, activeAccountId: null })).toBeNull();
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
describe('deviceStateToClientSessions', () => {
|
|
57
|
-
test('maps every account in order with isCurrent + authuser', () => {
|
|
58
|
-
const sessions = deviceStateToClientSessions(state, usersById);
|
|
59
|
-
expect(sessions).toHaveLength(2);
|
|
60
|
-
expect(sessions[0]).toEqual({
|
|
61
|
-
sessionId: 'sess-a1',
|
|
62
|
-
deviceId: 'device-1',
|
|
63
|
-
expiresAt: UPDATED_AT_ISO,
|
|
64
|
-
lastActive: UPDATED_AT_ISO,
|
|
65
|
-
userId: 'a1',
|
|
66
|
-
isCurrent: false,
|
|
67
|
-
authuser: 0,
|
|
68
|
-
});
|
|
69
|
-
expect(sessions[1]).toEqual({
|
|
70
|
-
sessionId: 'sess-a2',
|
|
71
|
-
deviceId: 'device-1',
|
|
72
|
-
expiresAt: UPDATED_AT_ISO,
|
|
73
|
-
lastActive: UPDATED_AT_ISO,
|
|
74
|
-
userId: 'a2',
|
|
75
|
-
isCurrent: true,
|
|
76
|
-
authuser: 1,
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
test('still projects a session for an account absent from usersById', () => {
|
|
81
|
-
const sessions = deviceStateToClientSessions(state, new Map());
|
|
82
|
-
expect(sessions).toHaveLength(2);
|
|
83
|
-
expect(sessions.map((session) => session.userId)).toEqual(['a1', 'a2']);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
describe('activeUserOf', () => {
|
|
88
|
-
test('returns the active account user', () => {
|
|
89
|
-
expect(activeUserOf(state, usersById)).toEqual(makeUser('a2'));
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
test('returns null for null state', () => {
|
|
93
|
-
expect(activeUserOf(null, usersById)).toBeNull();
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
test('returns null when activeAccountId is null', () => {
|
|
97
|
-
expect(activeUserOf({ ...state, activeAccountId: null }, usersById)).toBeNull();
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
test('returns null when the active account id is absent from usersById', () => {
|
|
101
|
-
expect(activeUserOf(state, new Map())).toBeNull();
|
|
102
|
-
});
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
describe('accountIdsOf', () => {
|
|
106
|
-
test('returns every account id in order', () => {
|
|
107
|
-
expect(accountIdsOf(state)).toEqual(['a1', 'a2']);
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test('returns [] for null state', () => {
|
|
111
|
-
expect(accountIdsOf(null)).toEqual([]);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
});
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { createSessionClientHost } from '../sessionClientHost';
|
|
2
|
-
|
|
3
|
-
function fakeOxy() {
|
|
4
|
-
const listeners = new Set<(t: string | null) => void>();
|
|
5
|
-
return {
|
|
6
|
-
makeRequest: jest.fn().mockResolvedValue({ ok: true }),
|
|
7
|
-
getBaseURL: jest.fn().mockReturnValue('https://api.oxy.so'),
|
|
8
|
-
getAccessToken: jest.fn().mockReturnValue('tok'),
|
|
9
|
-
setTokens: jest.fn(),
|
|
10
|
-
onTokensChanged: jest.fn((l: (t: string | null) => void) => { listeners.add(l); return () => listeners.delete(l); }),
|
|
11
|
-
_emit: (t: string | null) => listeners.forEach((l) => l(t)),
|
|
12
|
-
};
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
test('delegates REST + token methods to oxyServices', async () => {
|
|
16
|
-
const oxy = fakeOxy();
|
|
17
|
-
const host = createSessionClientHost(oxy as never);
|
|
18
|
-
await host.makeRequest('GET', '/session/device/state', undefined, { cache: false });
|
|
19
|
-
expect(oxy.makeRequest).toHaveBeenCalledWith('GET', '/session/device/state', undefined, { cache: false });
|
|
20
|
-
expect(host.getBaseURL()).toBe('https://api.oxy.so');
|
|
21
|
-
expect(host.getAccessToken()).toBe('tok');
|
|
22
|
-
host.setTokens('new');
|
|
23
|
-
expect(oxy.setTokens).toHaveBeenCalledWith('new');
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
test('getCurrentAccountId reflects setCurrentAccountId', () => {
|
|
27
|
-
const host = createSessionClientHost(fakeOxy() as never);
|
|
28
|
-
expect(host.getCurrentAccountId()).toBeNull();
|
|
29
|
-
host.setCurrentAccountId('u1');
|
|
30
|
-
expect(host.getCurrentAccountId()).toBe('u1');
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('onTokensChanged forwards to oxyServices and unsubscribes', () => {
|
|
34
|
-
const oxy = fakeOxy();
|
|
35
|
-
const host = createSessionClientHost(oxy as never);
|
|
36
|
-
const cb = jest.fn();
|
|
37
|
-
const unsub = host.onTokensChanged(cb);
|
|
38
|
-
oxy._emit(null);
|
|
39
|
-
expect(cb).toHaveBeenCalledWith(null);
|
|
40
|
-
unsub();
|
|
41
|
-
oxy._emit('x');
|
|
42
|
-
expect(cb).toHaveBeenCalledTimes(1);
|
|
43
|
-
});
|
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
import type { DeviceSessionState } from '@oxyhq/contracts';
|
|
2
|
-
import type { ClientSession, User } from '@oxyhq/core';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Pure projection helpers: `DeviceSessionState` (the device-scoped
|
|
6
|
-
* multi-account session-sync state produced by `@oxyhq/core`'s
|
|
7
|
-
* `SessionClient`) -> the shapes `OxyContext` renders today
|
|
8
|
-
* (`ClientSession[]`, an active session id, an active `User`).
|
|
9
|
-
*
|
|
10
|
-
* No I/O. The caller fetches profiles via
|
|
11
|
-
* `oxyServices.getUsersByIds(accountIdsOf(state))` and builds `usersById`
|
|
12
|
-
* from the result before calling `deviceStateToClientSessions` /
|
|
13
|
-
* `activeUserOf`.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Maps every `SessionAccount` in `state.accounts` to a `ClientSession`.
|
|
18
|
-
*
|
|
19
|
-
* `DeviceSessionState` carries no per-account `expiresAt` / `lastActive` —
|
|
20
|
-
* both are set to `state.updatedAt` (converted to an ISO-8601 string; the
|
|
21
|
-
* wire value is an epoch-ms number) as a provisional value. Fase 3-B
|
|
22
|
-
* rewrites `ClientSession` to make `expiresAt`/`lastActive` optional (they
|
|
23
|
-
* are not derivable from `DeviceSessionState`).
|
|
24
|
-
*
|
|
25
|
-
* `usersById` is accepted for signature symmetry with `activeUserOf` even
|
|
26
|
-
* though `ClientSession` only stores `userId` — a session is still
|
|
27
|
-
* projected for an account whose id is absent from `usersById` (no
|
|
28
|
-
* placeholder user is fabricated).
|
|
29
|
-
*/
|
|
30
|
-
export function deviceStateToClientSessions(
|
|
31
|
-
state: DeviceSessionState,
|
|
32
|
-
usersById: Map<string, User>,
|
|
33
|
-
): ClientSession[] {
|
|
34
|
-
const provisionalTimestamp = new Date(state.updatedAt).toISOString();
|
|
35
|
-
return state.accounts.map((account) => ({
|
|
36
|
-
sessionId: account.sessionId,
|
|
37
|
-
deviceId: state.deviceId,
|
|
38
|
-
// provisional: Fase 3-B rewrites ClientSession to make expiresAt/lastActive
|
|
39
|
-
// optional (they are not in DeviceSessionState)
|
|
40
|
-
expiresAt: provisionalTimestamp,
|
|
41
|
-
lastActive: provisionalTimestamp,
|
|
42
|
-
userId: account.accountId,
|
|
43
|
-
isCurrent: account.accountId === state.activeAccountId,
|
|
44
|
-
authuser: account.authuser,
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* The active account's `sessionId`, or `null` when there is no state or no
|
|
50
|
-
* active account is set.
|
|
51
|
-
*/
|
|
52
|
-
export function activeSessionIdOf(state: DeviceSessionState | null): string | null {
|
|
53
|
-
if (state === null || state.activeAccountId === null) {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
56
|
-
const activeAccountId = state.activeAccountId;
|
|
57
|
-
const activeAccount = state.accounts.find((account) => account.accountId === activeAccountId);
|
|
58
|
-
return activeAccount?.sessionId ?? null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* The active account's `User`, resolved from `usersById`. `null` when there
|
|
63
|
-
* is no state, no active account is set, or the active account id is absent
|
|
64
|
-
* from `usersById`.
|
|
65
|
-
*/
|
|
66
|
-
export function activeUserOf(
|
|
67
|
-
state: DeviceSessionState | null,
|
|
68
|
-
usersById: Map<string, User>,
|
|
69
|
-
): User | null {
|
|
70
|
-
if (state === null || state.activeAccountId === null) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
return usersById.get(state.activeAccountId) ?? null;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* All account ids in `state`, suitable for an `oxyServices.getUsersByIds(...)`
|
|
78
|
-
* fetch. `[]` for `null` state.
|
|
79
|
-
*/
|
|
80
|
-
export function accountIdsOf(state: DeviceSessionState | null): string[] {
|
|
81
|
-
if (state === null) {
|
|
82
|
-
return [];
|
|
83
|
-
}
|
|
84
|
-
return state.accounts.map((account) => account.accountId);
|
|
85
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
import type { OxyServices, SessionClientHost } from '@oxyhq/core';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Thin `SessionClientHost` adapter over an `OxyServices` instance.
|
|
5
|
-
*
|
|
6
|
-
* `SessionClient` (in `@oxyhq/core`) is host-agnostic: it only needs a REST +
|
|
7
|
-
* token surface. `OxyServices` already exposes all of that except
|
|
8
|
-
* `getCurrentAccountId`, which has no direct equivalent — the adapter holds a
|
|
9
|
-
* mutable ref set by the caller (`OxyContext`, in Fase 3-B) via
|
|
10
|
-
* `setCurrentAccountId`.
|
|
11
|
-
*/
|
|
12
|
-
export function createSessionClientHost(
|
|
13
|
-
oxyServices: OxyServices,
|
|
14
|
-
): SessionClientHost & { setCurrentAccountId(id: string | null): void } {
|
|
15
|
-
let currentAccountId: string | null = null;
|
|
16
|
-
return {
|
|
17
|
-
makeRequest: (method, url, data, options) => oxyServices.makeRequest(method, url, data, options),
|
|
18
|
-
getBaseURL: () => oxyServices.getBaseURL(),
|
|
19
|
-
getAccessToken: () => oxyServices.getAccessToken(),
|
|
20
|
-
onTokensChanged: (listener) => oxyServices.onTokensChanged(listener),
|
|
21
|
-
setTokens: (accessToken) => oxyServices.setTokens(accessToken),
|
|
22
|
-
getCurrentAccountId: () => currentAccountId,
|
|
23
|
-
setCurrentAccountId: (id) => {
|
|
24
|
-
currentAccountId = id;
|
|
25
|
-
},
|
|
26
|
-
};
|
|
27
|
-
}
|