@oxyhq/services 13.2.0 → 14.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 +11 -17
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/AccountMenu.js +103 -65
- package/lib/commonjs/ui/components/AccountMenu.js.map +1 -1
- package/lib/commonjs/ui/components/AccountSwitcher.js +84 -126
- package/lib/commonjs/ui/components/AccountSwitcher.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileButton.js +81 -22
- package/lib/commonjs/ui/components/ProfileButton.js.map +1 -1
- package/lib/commonjs/ui/components/ProfileMenu.js +244 -142
- package/lib/commonjs/ui/components/ProfileMenu.js.map +1 -1
- package/lib/commonjs/ui/components/SignInModal.js +4 -4
- package/lib/commonjs/ui/components/SignInModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +506 -466
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +59 -57
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js +23 -42
- package/lib/commonjs/ui/context/inSessionTokenRefresh.js.map +1 -1
- package/lib/commonjs/ui/context/silentSessionRestore.js +12 -21
- package/lib/commonjs/ui/context/silentSessionRestore.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAuth.js +4 -3
- package/lib/commonjs/ui/hooks/useAuth.js.map +1 -1
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js +12 -7
- package/lib/commonjs/ui/hooks/useOxyAuthSession.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionManagement.js +17 -65
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js +288 -0
- package/lib/commonjs/ui/hooks/useSwitchableAccounts.js.map +1 -0
- package/lib/commonjs/ui/hooks/useWebSSO.js +12 -191
- package/lib/commonjs/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/commonjs/ui/index.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +2 -2
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/session/createSessionClient.js +28 -0
- package/lib/commonjs/ui/session/createSessionClient.js.map +1 -0
- package/lib/commonjs/ui/session/index.js +52 -0
- package/lib/commonjs/ui/session/index.js.map +1 -0
- package/lib/commonjs/ui/session/projectSessionState.js +86 -0
- package/lib/commonjs/ui/session/projectSessionState.js.map +1 -0
- package/lib/commonjs/ui/session/sessionClientHost.js +30 -0
- package/lib/commonjs/ui/session/sessionClientHost.js.map +1 -0
- package/lib/commonjs/ui/session/tokenTransport.js +77 -0
- package/lib/commonjs/ui/session/tokenTransport.js.map +1 -0
- package/lib/commonjs/ui/utils/activeAuthuser.js +35 -79
- package/lib/commonjs/ui/utils/activeAuthuser.js.map +1 -1
- package/lib/commonjs/ui/utils/sessionHelpers.js +1 -51
- package/lib/commonjs/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/commonjs/utils/deviceFlowSignIn.js +70 -15
- package/lib/commonjs/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/module/index.js +5 -5
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/AccountMenu.js +103 -65
- package/lib/module/ui/components/AccountMenu.js.map +1 -1
- package/lib/module/ui/components/AccountSwitcher.js +85 -127
- package/lib/module/ui/components/AccountSwitcher.js.map +1 -1
- package/lib/module/ui/components/ProfileButton.js +82 -23
- package/lib/module/ui/components/ProfileButton.js.map +1 -1
- package/lib/module/ui/components/ProfileMenu.js +247 -145
- package/lib/module/ui/components/ProfileMenu.js.map +1 -1
- package/lib/module/ui/components/SignInModal.js +4 -4
- package/lib/module/ui/components/SignInModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +510 -470
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +61 -60
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/inSessionTokenRefresh.js +24 -43
- package/lib/module/ui/context/inSessionTokenRefresh.js.map +1 -1
- package/lib/module/ui/context/silentSessionRestore.js +12 -20
- package/lib/module/ui/context/silentSessionRestore.js.map +1 -1
- package/lib/module/ui/hooks/useAuth.js +4 -3
- package/lib/module/ui/hooks/useAuth.js.map +1 -1
- package/lib/module/ui/hooks/useOxyAuthSession.js +12 -7
- package/lib/module/ui/hooks/useOxyAuthSession.js.map +1 -1
- package/lib/module/ui/hooks/useSessionManagement.js +18 -66
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/hooks/useSwitchableAccounts.js +283 -0
- package/lib/module/ui/hooks/useSwitchableAccounts.js.map +1 -0
- package/lib/module/ui/hooks/useWebSSO.js +13 -192
- package/lib/module/ui/hooks/useWebSSO.js.map +1 -1
- package/lib/module/ui/index.js +0 -1
- package/lib/module/ui/index.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +2 -2
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/session/createSessionClient.js +25 -0
- package/lib/module/ui/session/createSessionClient.js.map +1 -0
- package/lib/module/ui/session/index.js +17 -0
- package/lib/module/ui/session/index.js.map +1 -0
- package/lib/module/ui/session/projectSessionState.js +79 -0
- package/lib/module/ui/session/projectSessionState.js.map +1 -0
- package/lib/module/ui/session/sessionClientHost.js +26 -0
- package/lib/module/ui/session/sessionClientHost.js.map +1 -0
- package/lib/module/ui/session/tokenTransport.js +74 -0
- package/lib/module/ui/session/tokenTransport.js.map +1 -0
- package/lib/module/ui/utils/activeAuthuser.js +34 -76
- package/lib/module/ui/utils/activeAuthuser.js.map +1 -1
- package/lib/module/ui/utils/sessionHelpers.js +0 -49
- package/lib/module/ui/utils/sessionHelpers.js.map +1 -1
- package/lib/module/utils/deviceFlowSignIn.js +71 -16
- package/lib/module/utils/deviceFlowSignIn.js.map +1 -1
- package/lib/typescript/commonjs/index.d.ts +2 -3
- package/lib/typescript/commonjs/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts +4 -2
- package/lib/typescript/commonjs/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts +7 -8
- package/lib/typescript/commonjs/ui/components/AccountSwitcher.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts +11 -1
- package/lib/typescript/commonjs/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts +12 -9
- package/lib/typescript/commonjs/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts +16 -13
- package/lib/typescript/commonjs/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts +27 -3
- package/lib/typescript/commonjs/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts +22 -17
- package/lib/typescript/commonjs/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts +12 -18
- package/lib/typescript/commonjs/ui/context/silentSessionRestore.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
- package/lib/typescript/commonjs/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts +1 -1
- package/lib/typescript/commonjs/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts +2 -2
- package/lib/typescript/commonjs/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts +162 -0
- package/lib/typescript/commonjs/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts +13 -52
- package/lib/typescript/commonjs/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts +15 -0
- package/lib/typescript/commonjs/ui/session/createSessionClient.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts +15 -0
- package/lib/typescript/commonjs/ui/session/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts +45 -0
- package/lib/typescript/commonjs/ui/session/projectSessionState.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts +14 -0
- package/lib/typescript/commonjs/ui/session/sessionClientHost.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts +24 -0
- package/lib/typescript/commonjs/ui/session/tokenTransport.d.ts.map +1 -0
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts +21 -29
- package/lib/typescript/commonjs/ui/utils/activeAuthuser.d.ts.map +1 -1
- package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts +0 -19
- package/lib/typescript/commonjs/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts +51 -19
- package/lib/typescript/commonjs/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/lib/typescript/module/index.d.ts +2 -3
- package/lib/typescript/module/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountMenu.d.ts +4 -2
- package/lib/typescript/module/ui/components/AccountMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts +7 -8
- package/lib/typescript/module/ui/components/AccountSwitcher.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts +11 -1
- package/lib/typescript/module/ui/components/ProfileButton.d.ts.map +1 -1
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts +12 -9
- package/lib/typescript/module/ui/components/ProfileMenu.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/OxyContext.d.ts +16 -13
- package/lib/typescript/module/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts +27 -3
- package/lib/typescript/module/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts +22 -17
- package/lib/typescript/module/ui/context/inSessionTokenRefresh.d.ts.map +1 -1
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts +12 -18
- package/lib/typescript/module/ui/context/silentSessionRestore.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts +1 -0
- package/lib/typescript/module/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts +1 -1
- package/lib/typescript/module/ui/hooks/useAuth.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts +2 -2
- package/lib/typescript/module/ui/hooks/useOxyAuthSession.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts +162 -0
- package/lib/typescript/module/ui/hooks/useSwitchableAccounts.d.ts.map +1 -0
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts +13 -52
- package/lib/typescript/module/ui/hooks/useWebSSO.d.ts.map +1 -1
- package/lib/typescript/module/ui/index.d.ts.map +1 -1
- package/lib/typescript/module/ui/session/createSessionClient.d.ts +15 -0
- package/lib/typescript/module/ui/session/createSessionClient.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/index.d.ts +15 -0
- package/lib/typescript/module/ui/session/index.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/projectSessionState.d.ts +45 -0
- package/lib/typescript/module/ui/session/projectSessionState.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts +14 -0
- package/lib/typescript/module/ui/session/sessionClientHost.d.ts.map +1 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts +24 -0
- package/lib/typescript/module/ui/session/tokenTransport.d.ts.map +1 -0
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts +21 -29
- package/lib/typescript/module/ui/utils/activeAuthuser.d.ts.map +1 -1
- package/lib/typescript/module/ui/utils/sessionHelpers.d.ts +0 -19
- package/lib/typescript/module/ui/utils/sessionHelpers.d.ts.map +1 -1
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts +51 -19
- package/lib/typescript/module/utils/deviceFlowSignIn.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/index.ts +9 -9
- package/src/ui/components/AccountMenu.tsx +117 -70
- package/src/ui/components/AccountSwitcher.tsx +88 -134
- package/src/ui/components/ProfileButton.tsx +95 -17
- package/src/ui/components/ProfileMenu.tsx +263 -155
- package/src/ui/components/SignInModal.tsx +5 -5
- package/src/ui/context/OxyContext.tsx +560 -475
- package/src/ui/context/hooks/useAuthOperations.ts +72 -63
- package/src/ui/context/inSessionTokenRefresh.ts +23 -42
- package/src/ui/context/silentSessionRestore.ts +12 -26
- package/src/ui/hooks/useAuth.ts +4 -3
- package/src/ui/hooks/useOxyAuthSession.ts +13 -8
- package/src/ui/hooks/useSessionManagement.ts +16 -87
- package/src/ui/hooks/useSwitchableAccounts.ts +397 -0
- package/src/ui/hooks/useWebSSO.ts +13 -227
- package/src/ui/index.ts +0 -1
- package/src/ui/screens/OxyAuthScreen.tsx +2 -2
- package/src/ui/session/__tests__/createSessionClient.test.ts +41 -0
- package/src/ui/session/__tests__/projectSessionState.test.ts +114 -0
- package/src/ui/session/__tests__/sessionClientHost.test.ts +43 -0
- package/src/ui/session/__tests__/tokenTransport.test.ts +142 -0
- package/src/ui/session/createSessionClient.ts +21 -0
- package/src/ui/session/index.ts +19 -0
- package/src/ui/session/projectSessionState.ts +85 -0
- package/src/ui/session/sessionClientHost.ts +27 -0
- package/src/ui/session/tokenTransport.ts +76 -0
- package/src/ui/utils/activeAuthuser.ts +34 -76
- package/src/ui/utils/sessionHelpers.ts +0 -66
- package/src/utils/__tests__/deviceFlowSignIn.test.ts +243 -34
- package/src/utils/deviceFlowSignIn.ts +98 -21
- package/lib/commonjs/ui/components/accountMenuRows.js +0 -33
- package/lib/commonjs/ui/components/accountMenuRows.js.map +0 -1
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js +0 -280
- package/lib/commonjs/ui/hooks/useDeviceAccounts.js.map +0 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +0 -203
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +0 -1
- package/lib/commonjs/utils/silentGuardKey.js +0 -54
- package/lib/commonjs/utils/silentGuardKey.js.map +0 -1
- package/lib/module/ui/components/accountMenuRows.js +0 -29
- package/lib/module/ui/components/accountMenuRows.js.map +0 -1
- package/lib/module/ui/hooks/useDeviceAccounts.js +0 -276
- package/lib/module/ui/hooks/useDeviceAccounts.js.map +0 -1
- package/lib/module/ui/hooks/useSessionSocket.js +0 -199
- package/lib/module/ui/hooks/useSessionSocket.js.map +0 -1
- package/lib/module/utils/silentGuardKey.js +0 -49
- package/lib/module/utils/silentGuardKey.js.map +0 -1
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts +0 -34
- package/lib/typescript/commonjs/ui/components/accountMenuRows.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts +0 -133
- package/lib/typescript/commonjs/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts +0 -14
- package/lib/typescript/commonjs/ui/hooks/useSessionSocket.d.ts.map +0 -1
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts +0 -31
- package/lib/typescript/commonjs/utils/silentGuardKey.d.ts.map +0 -1
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts +0 -34
- package/lib/typescript/module/ui/components/accountMenuRows.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts +0 -133
- package/lib/typescript/module/ui/hooks/useDeviceAccounts.d.ts.map +0 -1
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts +0 -14
- package/lib/typescript/module/ui/hooks/useSessionSocket.d.ts.map +0 -1
- package/lib/typescript/module/utils/silentGuardKey.d.ts +0 -31
- package/lib/typescript/module/utils/silentGuardKey.d.ts.map +0 -1
- package/src/ui/components/accountMenuRows.ts +0 -48
- package/src/ui/hooks/useDeviceAccounts.ts +0 -344
- package/src/ui/hooks/useSessionSocket.ts +0 -233
- package/src/ui/utils/__tests__/activeAuthuser.test.ts +0 -75
- package/src/utils/__tests__/silentGuardKey.test.ts +0 -82
- package/src/utils/silentGuardKey.ts +0 -46
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useCallback
|
|
2
|
-
import type { ApiError, User } from '@oxyhq/core';
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import type { ApiError, SessionClient, User } from '@oxyhq/core';
|
|
3
3
|
import type { AuthState } from '../../stores/authStore';
|
|
4
4
|
import type { ClientSession, SessionLoginResponse } from '@oxyhq/core';
|
|
5
5
|
import { DeviceManager } from '@oxyhq/core';
|
|
@@ -10,9 +10,7 @@ import type { OxyServices } from '@oxyhq/core';
|
|
|
10
10
|
import { SignatureService } from '@oxyhq/core';
|
|
11
11
|
import { isWebBrowser } from '../../hooks/useWebSSO';
|
|
12
12
|
import {
|
|
13
|
-
clearActiveAuthuser,
|
|
14
13
|
clearSsoBounceState,
|
|
15
|
-
writeActiveAuthuser,
|
|
16
14
|
markSignedOut,
|
|
17
15
|
clearSignedOut,
|
|
18
16
|
} from '../../utils/activeAuthuser';
|
|
@@ -21,7 +19,6 @@ import { isCrossApexWeb, CrossApexDirectSignInError } from '../../../utils/cross
|
|
|
21
19
|
export interface UseAuthOperationsOptions {
|
|
22
20
|
oxyServices: OxyServices;
|
|
23
21
|
storage: StorageInterface | null;
|
|
24
|
-
sessions: ClientSession[];
|
|
25
22
|
activeSessionId: string | null;
|
|
26
23
|
setActiveSessionId: (sessionId: string | null) => void;
|
|
27
24
|
updateSessions: (sessions: ClientSession[], options?: { merge?: boolean }) => void;
|
|
@@ -35,7 +32,17 @@ export interface UseAuthOperationsOptions {
|
|
|
35
32
|
* expired session still recovers via a returning-user bounce. Best-effort.
|
|
36
33
|
*/
|
|
37
34
|
clearPriorSessionHint: () => Promise<void>;
|
|
35
|
+
/** Used only by `performSignIn`'s same-user duplicate-session dedup (legacy session-validate path; unrelated to the SessionClient device-account set). */
|
|
38
36
|
switchSession: (sessionId: string) => Promise<User>;
|
|
37
|
+
/**
|
|
38
|
+
* The Fase 3-A/3-B `SessionClient` (server-authoritative device account
|
|
39
|
+
* set). `logout` / `logoutAll` route SERVER-side revocation through
|
|
40
|
+
* `sessionClient.signOut(...)` instead of the bearer/cookie logout
|
|
41
|
+
* endpoints.
|
|
42
|
+
*/
|
|
43
|
+
sessionClient: SessionClient;
|
|
44
|
+
/** Reprojects `sessionClient.getState()` onto sessions/activeSessionId/user (Task 1's callback). Awaited after a partial `signOut` so the exposed state reflects the server truth before the call resolves. */
|
|
45
|
+
syncFromClient: () => Promise<void>;
|
|
39
46
|
applyLanguagePreference: (user: User) => Promise<void>;
|
|
40
47
|
onAuthStateChange?: (user: User | null) => void;
|
|
41
48
|
onError?: (error: ApiError) => void;
|
|
@@ -67,8 +74,13 @@ const LOGOUT_ALL_ERROR_CODE = 'LOGOUT_ALL_ERROR';
|
|
|
67
74
|
* best-effort storage write. The clear is invoked synchronously (so unit tests
|
|
68
75
|
* can assert it ran) but its async settle is detached; any rejection is logged,
|
|
69
76
|
* never thrown.
|
|
77
|
+
*
|
|
78
|
+
* Exported so `OxyContext`'s `syncFromClient` zero-account branch (a REMOTE
|
|
79
|
+
* full sign-out) can invoke the EXACT same cleanup as the LOCAL `logout` /
|
|
80
|
+
* `logoutAll` paths below — a remote sign-out must be indistinguishable from
|
|
81
|
+
* a local one to the next cold boot.
|
|
70
82
|
*/
|
|
71
|
-
function clearPriorSessionHintSafe(
|
|
83
|
+
export function clearPriorSessionHintSafe(
|
|
72
84
|
clearPriorSessionHint: () => Promise<void>,
|
|
73
85
|
logger?: (message: string, error?: unknown) => void,
|
|
74
86
|
): void {
|
|
@@ -83,7 +95,6 @@ function clearPriorSessionHintSafe(
|
|
|
83
95
|
*/
|
|
84
96
|
export const useAuthOperations = ({
|
|
85
97
|
oxyServices,
|
|
86
|
-
sessions,
|
|
87
98
|
activeSessionId,
|
|
88
99
|
setActiveSessionId,
|
|
89
100
|
updateSessions,
|
|
@@ -91,6 +102,8 @@ export const useAuthOperations = ({
|
|
|
91
102
|
clearSessionState,
|
|
92
103
|
clearPriorSessionHint,
|
|
93
104
|
switchSession,
|
|
105
|
+
sessionClient,
|
|
106
|
+
syncFromClient,
|
|
94
107
|
applyLanguagePreference,
|
|
95
108
|
onAuthStateChange,
|
|
96
109
|
onError,
|
|
@@ -100,10 +113,6 @@ export const useAuthOperations = ({
|
|
|
100
113
|
setAuthState,
|
|
101
114
|
logger,
|
|
102
115
|
}: UseAuthOperationsOptions): UseAuthOperationsResult => {
|
|
103
|
-
// Ref to avoid recreating callbacks when sessions change
|
|
104
|
-
const sessionsRef = useRef(sessions);
|
|
105
|
-
sessionsRef.current = sessions;
|
|
106
|
-
|
|
107
116
|
/**
|
|
108
117
|
* Internal function to perform challenge-response sign in.
|
|
109
118
|
*/
|
|
@@ -141,19 +150,22 @@ export const useAuthOperations = ({
|
|
|
141
150
|
|
|
142
151
|
// Deliberate sign-in re-enables automatic silent restore: clear the durable
|
|
143
152
|
// "deliberately signed out" flag so a prior sign-out no longer suppresses
|
|
144
|
-
// the
|
|
153
|
+
// the per-apex iframe cold-boot step.
|
|
145
154
|
clearSignedOut();
|
|
146
155
|
|
|
147
|
-
// Register this
|
|
148
|
-
//
|
|
149
|
-
// `
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
156
|
+
// Register this recovered account+session into the server-authoritative
|
|
157
|
+
// device-session set: `sessionClient.addCurrentAccount()` ->
|
|
158
|
+
// `POST /session/device/add` derives identity from the bearer
|
|
159
|
+
// `verifyChallenge` already planted internally, then `syncFromClient()`
|
|
160
|
+
// reprojects the resulting server state onto the exposed
|
|
161
|
+
// sessions/activeSessionId/user. Best-effort: a failure here must NEVER
|
|
162
|
+
// fail the sign-in itself — cold boot re-registers this account into the
|
|
163
|
+
// device set on the next load regardless.
|
|
164
|
+
try {
|
|
165
|
+
await sessionClient.addCurrentAccount();
|
|
166
|
+
await syncFromClient();
|
|
167
|
+
} catch (registrationError) {
|
|
168
|
+
logger?.('Failed to register sign-in into device session set', registrationError);
|
|
157
169
|
}
|
|
158
170
|
|
|
159
171
|
// Get full user data
|
|
@@ -216,8 +228,10 @@ export const useAuthOperations = ({
|
|
|
216
228
|
onAuthStateChange,
|
|
217
229
|
oxyServices,
|
|
218
230
|
saveActiveSessionId,
|
|
231
|
+
sessionClient,
|
|
219
232
|
setActiveSessionId,
|
|
220
233
|
switchSession,
|
|
234
|
+
syncFromClient,
|
|
221
235
|
updateSessions,
|
|
222
236
|
],
|
|
223
237
|
);
|
|
@@ -264,38 +278,37 @@ export const useAuthOperations = ({
|
|
|
264
278
|
|
|
265
279
|
try {
|
|
266
280
|
const sessionToLogout = targetSessionId || activeSessionId;
|
|
267
|
-
|
|
268
|
-
//
|
|
269
|
-
//
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
281
|
+
|
|
282
|
+
// Resolve the device account backing this session from the
|
|
283
|
+
// server-authoritative `SessionClient` state — SERVER revocation now
|
|
284
|
+
// goes through `sessionClient.signOut(...)` instead of the
|
|
285
|
+
// bearer/cookie logout endpoints.
|
|
286
|
+
const targetAccountId = sessionClient
|
|
287
|
+
.getState()
|
|
288
|
+
?.accounts.find((account) => account.sessionId === sessionToLogout)?.accountId;
|
|
289
|
+
if (!targetAccountId) {
|
|
290
|
+
throw new Error(`No device account found for session "${sessionToLogout}"`);
|
|
276
291
|
}
|
|
277
292
|
|
|
278
|
-
|
|
279
|
-
updateSessions(filteredSessions, { merge: false });
|
|
293
|
+
await sessionClient.signOut({ accountId: targetAccountId });
|
|
280
294
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
}
|
|
295
|
+
// The server has already decided what (if anything) remains active on
|
|
296
|
+
// this device; reproject that truth onto the exposed sessions /
|
|
297
|
+
// activeSessionId / user before deciding whether additional LOCAL
|
|
298
|
+
// teardown is needed.
|
|
299
|
+
const remainingAccounts = sessionClient.getState()?.accounts ?? [];
|
|
300
|
+
await syncFromClient();
|
|
301
|
+
|
|
302
|
+
if (sessionToLogout === activeSessionId && remainingAccounts.length === 0) {
|
|
303
|
+
// Genuine FULL sign-out (no sessions remain): clear the per-origin
|
|
304
|
+
// SSO bounce state so a fresh deliberate sign-in can re-probe, and
|
|
305
|
+
// SET the deliberately-signed-out flag so the silent cold-boot step
|
|
306
|
+
// (per-apex iframe) does not re-mint a session from a
|
|
307
|
+
// still-live IdP session on the next reload (mirrors `logoutAll`).
|
|
308
|
+
markSignedOut();
|
|
309
|
+
clearSsoBounceState();
|
|
310
|
+
clearPriorSessionHintSafe(clearPriorSessionHint, logger);
|
|
311
|
+
await clearSessionState();
|
|
299
312
|
}
|
|
300
313
|
} catch (error) {
|
|
301
314
|
const isInvalid = isInvalidSessionError(error);
|
|
@@ -324,10 +337,9 @@ export const useAuthOperations = ({
|
|
|
324
337
|
clearPriorSessionHint,
|
|
325
338
|
logger,
|
|
326
339
|
onError,
|
|
327
|
-
|
|
340
|
+
sessionClient,
|
|
328
341
|
setAuthState,
|
|
329
|
-
|
|
330
|
-
updateSessions,
|
|
342
|
+
syncFromClient,
|
|
331
343
|
],
|
|
332
344
|
);
|
|
333
345
|
|
|
@@ -343,15 +355,12 @@ export const useAuthOperations = ({
|
|
|
343
355
|
}
|
|
344
356
|
|
|
345
357
|
try {
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
//
|
|
349
|
-
//
|
|
350
|
-
|
|
351
|
-
await oxyServices.logoutAllSessions(activeSessionId);
|
|
358
|
+
// Server-side revocation of every account on this device now flows
|
|
359
|
+
// through the SessionClient (`POST /session/device/signout` with
|
|
360
|
+
// `{ all: true }`) — replaces the bearer `logoutAllSessions` +
|
|
361
|
+
// web-cookie `logoutAllSessionsViaCookie` pair.
|
|
362
|
+
await sessionClient.signOut({ all: true });
|
|
352
363
|
if (isWebBrowser()) {
|
|
353
|
-
await oxyServices.logoutAllSessionsViaCookie();
|
|
354
|
-
clearActiveAuthuser();
|
|
355
364
|
// Deliberate full sign-out: suppress automatic silent restore on the next
|
|
356
365
|
// cold boot so a still-live IdP session does not re-mint a session.
|
|
357
366
|
markSignedOut();
|
|
@@ -373,7 +382,7 @@ export const useAuthOperations = ({
|
|
|
373
382
|
});
|
|
374
383
|
throw error instanceof Error ? error : new Error('Logout all failed');
|
|
375
384
|
}
|
|
376
|
-
}, [activeSessionId, clearSessionState, clearPriorSessionHint, logger, onError,
|
|
385
|
+
}, [activeSessionId, clearSessionState, clearPriorSessionHint, logger, onError, sessionClient, setAuthState]);
|
|
377
386
|
|
|
378
387
|
return {
|
|
379
388
|
signIn,
|
|
@@ -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 composing the SAME silent-restore
|
|
19
|
-
* ({@link mintSessionViaPerApexIframe}
|
|
20
|
-
*
|
|
21
|
-
*
|
|
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
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
|
|
@@ -29,6 +29,14 @@
|
|
|
29
29
|
* (single in-flight `tokenRefreshPromise` + cooldown) — this module does not
|
|
30
30
|
* reimplement them, so the timer / foreground / per-request triggers collapse to
|
|
31
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.
|
|
32
40
|
*/
|
|
33
41
|
import type {
|
|
34
42
|
OxyServices,
|
|
@@ -38,8 +46,7 @@ import type {
|
|
|
38
46
|
import { logger as loggerUtil } from '@oxyhq/core';
|
|
39
47
|
import { AppState, type AppStateStatus } from 'react-native';
|
|
40
48
|
import { isWebBrowser } from '../hooks/useWebSSO';
|
|
41
|
-
import {
|
|
42
|
-
import { mintSessionViaPerApexIframe, selectActiveRefreshAccount } from './silentSessionRestore';
|
|
49
|
+
import { mintSessionViaPerApexIframe } from './silentSessionRestore';
|
|
43
50
|
|
|
44
51
|
/**
|
|
45
52
|
* Per-arm fail-fast budget (ms) for the web first-party `/auth/silent` iframe
|
|
@@ -51,14 +58,6 @@ import { mintSessionViaPerApexIframe, selectActiveRefreshAccount } from './silen
|
|
|
51
58
|
*/
|
|
52
59
|
const SILENT_IFRAME_REFRESH_TIMEOUT = 4000;
|
|
53
60
|
|
|
54
|
-
/**
|
|
55
|
-
* Per-arm fail-fast budget (ms) for the same-apex refresh-cookie arm
|
|
56
|
-
* (`refreshAllSessions`). On a cross-apex RP the `Domain=oxy.so` cookie never
|
|
57
|
-
* reaches `api.<apex>`, so this returns `{accounts:[]}` quickly; the bound only
|
|
58
|
-
* matters if that endpoint stalls.
|
|
59
|
-
*/
|
|
60
|
-
const COOKIE_REFRESH_TIMEOUT = 4000;
|
|
61
|
-
|
|
62
61
|
/**
|
|
63
62
|
* Lead time (ms) before access-token expiry at which the proactive scheduler
|
|
64
63
|
* refreshes. Mirrors `HttpService`'s per-request `TOKEN_REFRESH_LEAD_SECONDS`
|
|
@@ -90,22 +89,19 @@ type RefreshArm = () => Promise<string | null>;
|
|
|
90
89
|
* sign-in) so a genuinely dead session reconciles to logged-out rather than
|
|
91
90
|
* staying a zombie.
|
|
92
91
|
*
|
|
93
|
-
* WEB
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
*
|
|
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.
|
|
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).
|
|
103
99
|
*
|
|
104
100
|
* NO RECURSION: no arm issues a request through the authed client's
|
|
105
|
-
* `refreshAccessToken` path. The iframe
|
|
106
|
-
*
|
|
101
|
+
* `refreshAccessToken` path. The iframe transport is postMessage-based; the
|
|
102
|
+
* follow-up `/session/user` fetch inside `silentSignIn`
|
|
107
103
|
* runs against the just-planted FULL-TTL token (≫ the preflight lead), so it
|
|
108
|
-
* never re-enters the refresh path
|
|
104
|
+
* never re-enters the refresh path.
|
|
109
105
|
*/
|
|
110
106
|
export function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRefreshHandler {
|
|
111
107
|
const runArm = async (label: string, reason: AuthRefreshReason, arm: RefreshArm): Promise<string | null> => {
|
|
@@ -129,21 +125,6 @@ export function createInSessionRefreshHandler(oxyServices: OxyServices): AuthRef
|
|
|
129
125
|
? oxyServices.getAccessToken()
|
|
130
126
|
: null,
|
|
131
127
|
],
|
|
132
|
-
['fedcm-silent', async () => {
|
|
133
|
-
if (oxyServices.isFedCMSupported?.() !== true) {
|
|
134
|
-
return null;
|
|
135
|
-
}
|
|
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;
|
|
142
|
-
}
|
|
143
|
-
const active = selectActiveRefreshAccount(snapshot.accounts, readActiveAuthuser());
|
|
144
|
-
oxyServices.httpService.setTokens(active.accessToken);
|
|
145
|
-
return oxyServices.getAccessToken();
|
|
146
|
-
}],
|
|
147
128
|
];
|
|
148
129
|
|
|
149
130
|
return async (reason: AuthRefreshReason): Promise<string | null> => {
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Silent, no-reload session-restore
|
|
2
|
+
* Silent, no-reload session-restore PRIMITIVE — the single shared
|
|
3
3
|
* implementation used by BOTH cold boot (`OxyContext.restoreSessionsFromStorage`)
|
|
4
4
|
* and in-session token refresh (`createInSessionRefreshHandler`). Neither caller
|
|
5
|
-
* re-implements
|
|
5
|
+
* re-implements it; they compose it. Keeping one home avoids the two paths
|
|
6
6
|
* drifting on "how do we mint a first-party token without a page reload".
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* the
|
|
10
|
-
*
|
|
11
|
-
*
|
|
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.
|
|
12
18
|
*/
|
|
13
19
|
import type { OxyServices, SessionLoginResponse } from '@oxyhq/core';
|
|
14
20
|
import { autoDetectAuthWebUrl } from '@oxyhq/core';
|
|
@@ -45,23 +51,3 @@ export async function mintSessionViaPerApexIframe(
|
|
|
45
51
|
}
|
|
46
52
|
return session;
|
|
47
53
|
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Pick the active account from a `refreshAllSessions` snapshot: the persisted
|
|
51
|
-
* `authuser` slot when it still matches a returned account, otherwise the lowest
|
|
52
|
-
* `authuser` (the server sorts ascending, so `[0]`). Callers guarantee a
|
|
53
|
-
* non-empty list, so the result is always defined.
|
|
54
|
-
*
|
|
55
|
-
* Shared by cold-boot cookie restore and the in-session refresh cookie arm so
|
|
56
|
-
* the active-slot selection can never diverge between them.
|
|
57
|
-
*/
|
|
58
|
-
export function selectActiveRefreshAccount<T extends { authuser: number }>(
|
|
59
|
-
accounts: T[],
|
|
60
|
-
persistedAuthuser: number | null,
|
|
61
|
-
): T {
|
|
62
|
-
const matched =
|
|
63
|
-
persistedAuthuser !== null
|
|
64
|
-
? accounts.find((account) => account.authuser === persistedAuthuser)
|
|
65
|
-
: undefined;
|
|
66
|
-
return matched ?? accounts[0];
|
|
67
|
-
}
|
package/src/ui/hooks/useAuth.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
* ```
|
|
19
19
|
*
|
|
20
20
|
* Cross-domain SSO:
|
|
21
|
-
* - Web: Automatic via
|
|
21
|
+
* - Web: Automatic via the per-apex `/auth/silent` iframe + terminal `/sso` bounce (SDK cold boot)
|
|
22
22
|
* - Native: Automatic via shared Keychain/Account Manager
|
|
23
23
|
* - Manual sign-in: signIn() redirects to the IdP (web) or opens the auth sheet (native)
|
|
24
24
|
*/
|
|
@@ -150,8 +150,9 @@ export function useAuth(): UseAuthReturn {
|
|
|
150
150
|
const isIdentityProvider = isWebBrowser() &&
|
|
151
151
|
window.location.hostname === idpHostname;
|
|
152
152
|
|
|
153
|
-
// Web (not on IdP): use the tokenless redirect SSO flow.
|
|
154
|
-
//
|
|
153
|
+
// Web (not on IdP): use the tokenless redirect SSO flow. The silent
|
|
154
|
+
// cross-domain restore (per-apex `/auth/silent` iframe + `/sso` bounce)
|
|
155
|
+
// already ran on page load; an explicit click needs interactive auth.
|
|
155
156
|
if (isWebBrowser() && !publicKey && !isIdentityProvider) {
|
|
156
157
|
oxyServices.signInWithRedirect?.({
|
|
157
158
|
redirectUri: window.location.href,
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
24
24
|
import { Linking, Platform } from 'react-native';
|
|
25
25
|
import io, { type Socket } from 'socket.io-client';
|
|
26
|
-
import type { OxyServices, User } from '@oxyhq/core';
|
|
26
|
+
import type { OxyServices, SessionLoginResponse, User } from '@oxyhq/core';
|
|
27
27
|
import { createDebugLogger } from '@oxyhq/core';
|
|
28
28
|
import { completeDeviceFlowSignIn } from '../../utils/deviceFlowSignIn';
|
|
29
29
|
|
|
@@ -232,7 +232,7 @@ function generateSessionToken(): string {
|
|
|
232
232
|
export function useOxyAuthSession(
|
|
233
233
|
oxyServices: OxyServices,
|
|
234
234
|
clientId: string | null,
|
|
235
|
-
|
|
235
|
+
commitSession: ((session: SessionLoginResponse) => Promise<void>) | undefined,
|
|
236
236
|
options: UseOxyAuthSessionOptions = {},
|
|
237
237
|
): UseOxyAuthSessionResult {
|
|
238
238
|
const { onSignedIn } = options;
|
|
@@ -284,8 +284,13 @@ export function useOxyAuthSession(
|
|
|
284
284
|
// Without that exchange the SDK has no bearer token — the session is
|
|
285
285
|
// authorized server-side but the app never becomes authenticated and the UI
|
|
286
286
|
// sits "Waiting for authorization..." forever. Once `claimSessionByToken`
|
|
287
|
-
// plants the tokens in the HttpService, the
|
|
288
|
-
//
|
|
287
|
+
// plants the tokens in the HttpService, the claimed session is committed via
|
|
288
|
+
// `commitSession` (`useOxy().handleWebSession`) — the SAME path a fresh
|
|
289
|
+
// password sign-in or a silent cross-domain restore uses to register the
|
|
290
|
+
// account into the device's server-authoritative session set. It is NOT yet a
|
|
291
|
+
// member of that set, so
|
|
292
|
+
// `switchSession` (an account-SWITCH between accounts already on the
|
|
293
|
+
// device) is the wrong primitive here. Shared with both containers via
|
|
289
294
|
// `completeDeviceFlowSignIn` so the two paths cannot drift.
|
|
290
295
|
const handleAuthSuccess = useCallback(
|
|
291
296
|
async (sessionId: string, sessionToken: string) => {
|
|
@@ -293,14 +298,14 @@ export function useOxyAuthSession(
|
|
|
293
298
|
isProcessingRef.current = true;
|
|
294
299
|
|
|
295
300
|
try {
|
|
296
|
-
if (!
|
|
297
|
-
throw new Error('Session
|
|
301
|
+
if (!commitSession) {
|
|
302
|
+
throw new Error('Session commit unavailable');
|
|
298
303
|
}
|
|
299
304
|
const user = await completeDeviceFlowSignIn({
|
|
300
305
|
oxyServices,
|
|
301
306
|
sessionId,
|
|
302
307
|
sessionToken,
|
|
303
|
-
|
|
308
|
+
commitSession,
|
|
304
309
|
});
|
|
305
310
|
onSignedInRef.current?.(user);
|
|
306
311
|
} catch (err) {
|
|
@@ -309,7 +314,7 @@ export function useOxyAuthSession(
|
|
|
309
314
|
isProcessingRef.current = false;
|
|
310
315
|
}
|
|
311
316
|
},
|
|
312
|
-
[oxyServices,
|
|
317
|
+
[oxyServices, commitSession],
|
|
313
318
|
);
|
|
314
319
|
|
|
315
320
|
// Start polling for authorization.
|
|
@@ -2,14 +2,13 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
2
2
|
import { isDev, type ApiError, type User } from '@oxyhq/core';
|
|
3
3
|
import type { ClientSession } from '@oxyhq/core';
|
|
4
4
|
import { mergeSessions, normalizeAndSortSessions, sessionsArraysEqual } from '@oxyhq/core';
|
|
5
|
-
import { fetchSessionsWithFallback
|
|
5
|
+
import { fetchSessionsWithFallback } from '../utils/sessionHelpers';
|
|
6
6
|
import { getStorageKeys, type StorageInterface } from '../utils/storageHelpers';
|
|
7
7
|
import { handleAuthError, isInvalidSessionError } from '../utils/errorHandlers';
|
|
8
8
|
import type { OxyServices } from '@oxyhq/core';
|
|
9
9
|
import type { QueryClient } from '@tanstack/react-query';
|
|
10
10
|
import { clearQueryCache } from './queryClient';
|
|
11
11
|
import { isWebBrowser } from './useWebSSO';
|
|
12
|
-
import { writeActiveAuthuser } from '../utils/activeAuthuser';
|
|
13
12
|
|
|
14
13
|
export interface UseSessionManagementOptions {
|
|
15
14
|
oxyServices: OxyServices;
|
|
@@ -229,68 +228,18 @@ export const useSessionManagement = ({
|
|
|
229
228
|
};
|
|
230
229
|
}, []);
|
|
231
230
|
|
|
232
|
-
const findReplacementSession = useCallback(
|
|
233
|
-
async (sessionIds: string[]): Promise<User | null> => {
|
|
234
|
-
if (!sessionIds.length) {
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const validationResults = await validateSessionBatch(oxyServices, sessionIds, {
|
|
239
|
-
maxConcurrency: 3,
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
const validSession = validationResults.find((result) => result.valid);
|
|
243
|
-
if (!validSession) {
|
|
244
|
-
return null;
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
const validation = await oxyServices.validateSession(validSession.sessionId, {
|
|
248
|
-
useHeaderValidation: true,
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
if (!validation?.valid || !validation.user) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
const user = validation.user as User;
|
|
256
|
-
await activateSession(validSession.sessionId, user);
|
|
257
|
-
return user;
|
|
258
|
-
},
|
|
259
|
-
[activateSession, oxyServices],
|
|
260
|
-
);
|
|
261
|
-
|
|
262
231
|
const switchSession = useCallback(
|
|
263
232
|
async (sessionId: string): Promise<User> => {
|
|
264
233
|
try {
|
|
265
|
-
//
|
|
266
|
-
// `
|
|
267
|
-
//
|
|
268
|
-
// refresh
|
|
269
|
-
//
|
|
270
|
-
//
|
|
271
|
-
// path arrives here only after a bearer has been planted by
|
|
234
|
+
// On web, the bearer must already be in memory (planted by
|
|
235
|
+
// `claimSessionByToken`, a cold-boot silent-restore step, or a prior
|
|
236
|
+
// `SessionClient` sync) before validating — there is no client-side
|
|
237
|
+
// `oxy_rt` refresh-cookie slot to fall back on; the device account SET
|
|
238
|
+
// is server-authoritative via `SessionClient` now. The native path
|
|
239
|
+
// arrives here only after a bearer has been planted by
|
|
272
240
|
// `claimSessionByToken` or secure shared-session restore.
|
|
273
|
-
if (isWebBrowser()) {
|
|
274
|
-
|
|
275
|
-
const targetAuthuser = targetSession?.authuser;
|
|
276
|
-
if (typeof targetAuthuser === 'number') {
|
|
277
|
-
const refreshed = await oxyServices.refreshTokenViaCookie({ authuser: targetAuthuser });
|
|
278
|
-
if (refreshed === null) {
|
|
279
|
-
// Slot's refresh cookie is missing / expired / reused. Fall
|
|
280
|
-
// through to the invalid-session branch below by throwing the
|
|
281
|
-
// canonical invalid-session error.
|
|
282
|
-
throw new Error('Session is invalid or expired');
|
|
283
|
-
}
|
|
284
|
-
// Plant the slot's fresh access token; subsequent bearer calls
|
|
285
|
-
// (`validateSession`, `getCurrentUser`) will use it. The server
|
|
286
|
-
// also rotated the cookie at this point.
|
|
287
|
-
oxyServices.httpService.setTokens(refreshed.accessToken);
|
|
288
|
-
writeActiveAuthuser(targetAuthuser);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
if (!oxyServices.getAccessToken()) {
|
|
292
|
-
throw new Error('Session is invalid or expired');
|
|
293
|
-
}
|
|
241
|
+
if (isWebBrowser() && !oxyServices.getAccessToken()) {
|
|
242
|
+
throw new Error('Session is invalid or expired');
|
|
294
243
|
}
|
|
295
244
|
|
|
296
245
|
const validation = await oxyServices.validateSession(sessionId, { useHeaderValidation: true });
|
|
@@ -322,22 +271,12 @@ export const useSessionManagement = ({
|
|
|
322
271
|
const invalidSession = isInvalidSessionError(error);
|
|
323
272
|
|
|
324
273
|
if (invalidSession) {
|
|
274
|
+
// Server authority (`SessionClient` bootstrap/sync) reconciles which
|
|
275
|
+
// account is actually active — just drop the invalid session from
|
|
276
|
+
// the local view rather than guessing at a replacement.
|
|
325
277
|
updateSessions(sessionsRef.current.filter((session) => session.sessionId !== sessionId), {
|
|
326
278
|
merge: false,
|
|
327
279
|
});
|
|
328
|
-
if (sessionId === activeSessionIdRef.current) {
|
|
329
|
-
const otherSessionIds = sessionsRef.current
|
|
330
|
-
.filter(
|
|
331
|
-
(session) =>
|
|
332
|
-
session.sessionId !== sessionId && !removedSessionsRef.current.has(session.sessionId),
|
|
333
|
-
)
|
|
334
|
-
.map((session) => session.sessionId);
|
|
335
|
-
|
|
336
|
-
const replacementUser = await findReplacementSession(otherSessionIds);
|
|
337
|
-
if (replacementUser) {
|
|
338
|
-
return replacementUser;
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
280
|
}
|
|
342
281
|
|
|
343
282
|
handleAuthError(error, {
|
|
@@ -352,7 +291,6 @@ export const useSessionManagement = ({
|
|
|
352
291
|
},
|
|
353
292
|
[
|
|
354
293
|
activateSession,
|
|
355
|
-
findReplacementSession,
|
|
356
294
|
logger,
|
|
357
295
|
onError,
|
|
358
296
|
oxyServices,
|
|
@@ -389,18 +327,10 @@ export const useSessionManagement = ({
|
|
|
389
327
|
updateSessions(deviceSessions, { merge: true });
|
|
390
328
|
} catch (error) {
|
|
391
329
|
if (isInvalidSessionError(error)) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
!removedSessionsRef.current.has(session.sessionId),
|
|
397
|
-
)
|
|
398
|
-
.map((session) => session.sessionId);
|
|
399
|
-
|
|
400
|
-
const replacementUser = await findReplacementSession(otherSessions);
|
|
401
|
-
if (!replacementUser) {
|
|
402
|
-
await clearSessionState();
|
|
403
|
-
}
|
|
330
|
+
// Server authority (`SessionClient` bootstrap/sync) reconciles
|
|
331
|
+
// which account is actually active — just clear local state
|
|
332
|
+
// rather than guessing at a replacement session.
|
|
333
|
+
await clearSessionState();
|
|
404
334
|
return;
|
|
405
335
|
}
|
|
406
336
|
|
|
@@ -422,7 +352,6 @@ export const useSessionManagement = ({
|
|
|
422
352
|
},
|
|
423
353
|
[
|
|
424
354
|
clearSessionState,
|
|
425
|
-
findReplacementSession,
|
|
426
355
|
logger,
|
|
427
356
|
onError,
|
|
428
357
|
oxyServices,
|