@oxyhq/services 14.0.1 → 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 +25 -10
- 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 +25 -10
- 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 +20 -9
- 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 +20 -9
- 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 +49 -6
- package/src/ui/session/__tests__/tokenTransport.test.ts +37 -49
- package/src/ui/session/authStore.ts +164 -0
- package/src/ui/session/createSessionClient.ts +32 -11
- 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,5 +1,5 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
|
-
import type { ApiError, SessionClient, User } from '@oxyhq/core';
|
|
2
|
+
import type { ApiError, AuthStateStore, 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';
|
|
@@ -8,30 +8,24 @@ import { handleAuthError, isInvalidSessionError } from '../../utils/errorHandler
|
|
|
8
8
|
import type { StorageInterface } from '../../utils/storageHelpers';
|
|
9
9
|
import type { OxyServices } from '@oxyhq/core';
|
|
10
10
|
import { SignatureService } from '@oxyhq/core';
|
|
11
|
-
import { isWebBrowser } from '../../hooks/useWebSSO';
|
|
12
|
-
import {
|
|
13
|
-
clearSsoBounceState,
|
|
14
|
-
markSignedOut,
|
|
15
|
-
clearSignedOut,
|
|
16
|
-
} from '../../utils/activeAuthuser';
|
|
17
|
-
import { isCrossApexWeb, CrossApexDirectSignInError } from '../../../utils/crossApex';
|
|
18
11
|
|
|
19
12
|
export interface UseAuthOperationsOptions {
|
|
20
13
|
oxyServices: OxyServices;
|
|
21
14
|
storage: StorageInterface | null;
|
|
15
|
+
/**
|
|
16
|
+
* The device-first persisted auth-state store. On EXPLICIT full sign-out the
|
|
17
|
+
* session blob is cleared (`store.clear()`) so a reload's cold boot finds no
|
|
18
|
+
* refresh family to restore; on sign-in a returned rotating refresh token is
|
|
19
|
+
* persisted so the next boot warm-restores without a redirect. The long-lived
|
|
20
|
+
* device token SURVIVES sign-out (the device is unchanged), so it is never
|
|
21
|
+
* cleared here.
|
|
22
|
+
*/
|
|
23
|
+
store: AuthStateStore;
|
|
22
24
|
activeSessionId: string | null;
|
|
23
25
|
setActiveSessionId: (sessionId: string | null) => void;
|
|
24
26
|
updateSessions: (sessions: ClientSession[], options?: { merge?: boolean }) => void;
|
|
25
27
|
saveActiveSessionId: (sessionId: string) => Promise<void>;
|
|
26
28
|
clearSessionState: () => Promise<void>;
|
|
27
|
-
/**
|
|
28
|
-
* Clear the durable returning-user hint (`storageKeys.priorSession`). Called
|
|
29
|
-
* ONLY on EXPLICIT full sign-out — alongside `clearSsoBounceState()` — so the
|
|
30
|
-
* next cold boot treats this device as a first-time anonymous visitor (no
|
|
31
|
-
* forced `/sso` bounce). NEVER called on the passive token-expiry path, so an
|
|
32
|
-
* expired session still recovers via a returning-user bounce. Best-effort.
|
|
33
|
-
*/
|
|
34
|
-
clearPriorSessionHint: () => Promise<void>;
|
|
35
29
|
/** Used only by `performSignIn`'s same-user duplicate-session dedup (legacy session-validate path; unrelated to the SessionClient device-account set). */
|
|
36
30
|
switchSession: (sessionId: string) => Promise<User>;
|
|
37
31
|
/**
|
|
@@ -67,25 +61,19 @@ const LOGOUT_ERROR_CODE = 'LOGOUT_ERROR';
|
|
|
67
61
|
const LOGOUT_ALL_ERROR_CODE = 'LOGOUT_ALL_ERROR';
|
|
68
62
|
|
|
69
63
|
/**
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
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.
|
|
64
|
+
* Clear the persisted refresh-token family on an explicit full sign-out.
|
|
65
|
+
* Best-effort and non-blocking: sign-out must never fail because a storage
|
|
66
|
+
* write threw. Exported so `OxyContext`'s zero-account branch (a REMOTE full
|
|
67
|
+
* sign-out pushed over the socket) runs the EXACT same cleanup as the local
|
|
68
|
+
* `logout` / `logoutAll` paths — a remote sign-out is indistinguishable from a
|
|
69
|
+
* local one to the next cold boot.
|
|
82
70
|
*/
|
|
83
|
-
export function
|
|
84
|
-
|
|
71
|
+
export function clearPersistedAuthSafe(
|
|
72
|
+
store: AuthStateStore,
|
|
85
73
|
logger?: (message: string, error?: unknown) => void,
|
|
86
74
|
): void {
|
|
87
|
-
|
|
88
|
-
logger?.('Failed to clear
|
|
75
|
+
store.clear().catch((clearError) => {
|
|
76
|
+
logger?.('Failed to clear persisted auth state on sign-out', clearError);
|
|
89
77
|
});
|
|
90
78
|
}
|
|
91
79
|
|
|
@@ -95,12 +83,12 @@ export function clearPriorSessionHintSafe(
|
|
|
95
83
|
*/
|
|
96
84
|
export const useAuthOperations = ({
|
|
97
85
|
oxyServices,
|
|
86
|
+
store,
|
|
98
87
|
activeSessionId,
|
|
99
88
|
setActiveSessionId,
|
|
100
89
|
updateSessions,
|
|
101
90
|
saveActiveSessionId,
|
|
102
91
|
clearSessionState,
|
|
103
|
-
clearPriorSessionHint,
|
|
104
92
|
switchSession,
|
|
105
93
|
sessionClient,
|
|
106
94
|
syncFromClient,
|
|
@@ -148,21 +136,40 @@ export const useAuthOperations = ({
|
|
|
148
136
|
deviceFingerprint,
|
|
149
137
|
);
|
|
150
138
|
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
// the
|
|
154
|
-
|
|
139
|
+
// Persist the per-origin refresh family so a reload warm-restores this
|
|
140
|
+
// session without a redirect. `verifyChallenge` plants the access token
|
|
141
|
+
// internally; when the (trusted-lane) response also carries a rotating
|
|
142
|
+
// refresh token, persist the durable blob. Best-effort — a failed persist
|
|
143
|
+
// never fails the sign-in (native additionally re-mints via the shared
|
|
144
|
+
// keychain on the next cold boot). `SessionLoginResponse` does not type
|
|
145
|
+
// `refreshToken`; read it defensively from the runtime payload.
|
|
146
|
+
const refreshToken = (sessionResponse as { refreshToken?: string }).refreshToken;
|
|
147
|
+
if (refreshToken) {
|
|
148
|
+
try {
|
|
149
|
+
const deviceToken = (await store.loadDeviceToken()) ?? undefined;
|
|
150
|
+
await store.save({
|
|
151
|
+
sessionId: sessionResponse.sessionId,
|
|
152
|
+
refreshToken,
|
|
153
|
+
userId: sessionResponse.user.id,
|
|
154
|
+
...(deviceToken ? { deviceToken } : {}),
|
|
155
|
+
...(sessionResponse.accessToken ? { accessToken: sessionResponse.accessToken } : {}),
|
|
156
|
+
...(sessionResponse.expiresAt ? { expiresAt: sessionResponse.expiresAt } : {}),
|
|
157
|
+
});
|
|
158
|
+
} catch (persistError) {
|
|
159
|
+
logger?.('Failed to persist auth state after sign-in', persistError);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
155
162
|
|
|
156
163
|
// Register this recovered account+session into the server-authoritative
|
|
157
|
-
// device-session set: `
|
|
158
|
-
// `POST /session/device/add`
|
|
159
|
-
// `verifyChallenge` already planted
|
|
160
|
-
// reprojects the resulting server state onto the
|
|
161
|
-
// sessions/activeSessionId/user. Best-effort: a failure here must
|
|
162
|
-
// fail the sign-in itself — cold boot re-registers this account into
|
|
163
|
-
// device set on the next load regardless.
|
|
164
|
+
// device-session set AND make it active: `registerAndActivate()` adds via
|
|
165
|
+
// `POST /session/device/add` (identity derived from the bearer
|
|
166
|
+
// `verifyChallenge` already planted) then switches to it, and
|
|
167
|
+
// `syncFromClient()` reprojects the resulting server state onto the
|
|
168
|
+
// exposed sessions/activeSessionId/user. Best-effort: a failure here must
|
|
169
|
+
// NEVER fail the sign-in itself — cold boot re-registers this account into
|
|
170
|
+
// the device set on the next load regardless.
|
|
164
171
|
try {
|
|
165
|
-
await sessionClient.
|
|
172
|
+
await sessionClient.registerAndActivate(sessionResponse.user.id);
|
|
166
173
|
await syncFromClient();
|
|
167
174
|
} catch (registrationError) {
|
|
168
175
|
logger?.('Failed to register sign-in into device session set', registrationError);
|
|
@@ -230,6 +237,7 @@ export const useAuthOperations = ({
|
|
|
230
237
|
saveActiveSessionId,
|
|
231
238
|
sessionClient,
|
|
232
239
|
setActiveSessionId,
|
|
240
|
+
store,
|
|
233
241
|
switchSession,
|
|
234
242
|
syncFromClient,
|
|
235
243
|
updateSessions,
|
|
@@ -241,13 +249,6 @@ export const useAuthOperations = ({
|
|
|
241
249
|
*/
|
|
242
250
|
const signIn = useCallback(
|
|
243
251
|
async (publicKey: string, deviceName?: string): Promise<User> => {
|
|
244
|
-
// On a cross-apex web RP a direct public-key sign-in mints a bearer against
|
|
245
|
-
// the Oxy API but establishes no `fedcm_session`, so the session would be
|
|
246
|
-
// lost on reload. Refuse it and direct the app to the durable IdP popup
|
|
247
|
-
// ("Continue with Oxy"). Native and same-apex `*.oxy.so` are unaffected.
|
|
248
|
-
if (isCrossApexWeb()) {
|
|
249
|
-
throw new CrossApexDirectSignInError();
|
|
250
|
-
}
|
|
251
252
|
setAuthState({ isLoading: true, error: null });
|
|
252
253
|
|
|
253
254
|
try {
|
|
@@ -300,23 +301,18 @@ export const useAuthOperations = ({
|
|
|
300
301
|
await syncFromClient();
|
|
301
302
|
|
|
302
303
|
if (sessionToLogout === activeSessionId && remainingAccounts.length === 0) {
|
|
303
|
-
// Genuine FULL sign-out (no sessions remain): clear the
|
|
304
|
-
//
|
|
305
|
-
//
|
|
306
|
-
|
|
307
|
-
// still-live IdP session on the next reload (mirrors `logoutAll`).
|
|
308
|
-
markSignedOut();
|
|
309
|
-
clearSsoBounceState();
|
|
310
|
-
clearPriorSessionHintSafe(clearPriorSessionHint, logger);
|
|
304
|
+
// Genuine FULL sign-out (no sessions remain): clear the persisted
|
|
305
|
+
// refresh family so a reload's cold boot finds nothing to restore,
|
|
306
|
+
// then tear down local state.
|
|
307
|
+
clearPersistedAuthSafe(store, logger);
|
|
311
308
|
await clearSessionState();
|
|
312
309
|
}
|
|
313
310
|
} catch (error) {
|
|
314
311
|
const isInvalid = isInvalidSessionError(error);
|
|
315
312
|
|
|
316
313
|
if (isInvalid && targetSessionId === activeSessionId) {
|
|
317
|
-
// The active session is invalid → full sign-out; clear
|
|
318
|
-
|
|
319
|
-
clearPriorSessionHintSafe(clearPriorSessionHint, logger);
|
|
314
|
+
// The active session is invalid → full sign-out; clear persisted state.
|
|
315
|
+
clearPersistedAuthSafe(store, logger);
|
|
320
316
|
await clearSessionState();
|
|
321
317
|
return;
|
|
322
318
|
}
|
|
@@ -334,7 +330,7 @@ export const useAuthOperations = ({
|
|
|
334
330
|
[
|
|
335
331
|
activeSessionId,
|
|
336
332
|
clearSessionState,
|
|
337
|
-
|
|
333
|
+
store,
|
|
338
334
|
logger,
|
|
339
335
|
onError,
|
|
340
336
|
sessionClient,
|
|
@@ -360,17 +356,10 @@ export const useAuthOperations = ({
|
|
|
360
356
|
// `{ all: true }`) — replaces the bearer `logoutAllSessions` +
|
|
361
357
|
// web-cookie `logoutAllSessionsViaCookie` pair.
|
|
362
358
|
await sessionClient.signOut({ all: true });
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}
|
|
368
|
-
// logoutAll is ALWAYS a full sign-out: clear the per-origin SSO bounce
|
|
369
|
-
// state (web-guarded internally) so a fresh sign-in can re-probe, and drop
|
|
370
|
-
// the durable returning-user hint so the next cold boot is treated as a
|
|
371
|
-
// first-time anonymous visitor (no forced `/sso` bounce after sign-out).
|
|
372
|
-
clearSsoBounceState();
|
|
373
|
-
clearPriorSessionHintSafe(clearPriorSessionHint, logger);
|
|
359
|
+
// logoutAll is ALWAYS a full sign-out: clear the persisted refresh family
|
|
360
|
+
// so the next cold boot finds no session to restore, then tear down local
|
|
361
|
+
// state. The long-lived device token survives (the device is unchanged).
|
|
362
|
+
clearPersistedAuthSafe(store, logger);
|
|
374
363
|
await clearSessionState();
|
|
375
364
|
} catch (error) {
|
|
376
365
|
handleAuthError(error, {
|
|
@@ -382,7 +371,7 @@ export const useAuthOperations = ({
|
|
|
382
371
|
});
|
|
383
372
|
throw error instanceof Error ? error : new Error('Logout all failed');
|
|
384
373
|
}
|
|
385
|
-
}, [activeSessionId, clearSessionState,
|
|
374
|
+
}, [activeSessionId, clearSessionState, store, logger, onError, sessionClient, setAuthState]);
|
|
386
375
|
|
|
387
376
|
return {
|
|
388
377
|
signIn,
|
package/src/ui/hooks/useAuth.ts
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
import { useCallback, useState } from 'react';
|
|
27
27
|
import { useOxy } from '../context/OxyContext';
|
|
28
28
|
import type { User } from '@oxyhq/core';
|
|
29
|
-
import { isWebBrowser } from '
|
|
29
|
+
import { isWebBrowser } from '../utils/isWebBrowser';
|
|
30
|
+
import { showSignInModal } from '../components/SignInModal';
|
|
30
31
|
|
|
31
32
|
export interface AuthState {
|
|
32
33
|
/** Current authenticated user, null if not authenticated */
|
|
@@ -140,35 +141,24 @@ export function useAuth(): UseAuthReturn {
|
|
|
140
141
|
} = useOxy();
|
|
141
142
|
|
|
142
143
|
const signIn = useCallback(async (publicKey?: string): Promise<User> => {
|
|
143
|
-
//
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
window.location.hostname === idpHostname;
|
|
152
|
-
|
|
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.
|
|
156
|
-
if (isWebBrowser() && !publicKey && !isIdentityProvider) {
|
|
157
|
-
oxyServices.signInWithRedirect?.({
|
|
158
|
-
redirectUri: window.location.href,
|
|
159
|
-
});
|
|
144
|
+
// Web (no key): open the in-app "Sign in with Oxy" modal. There is NO
|
|
145
|
+
// automatic navigation to any login page — the device-first cold boot
|
|
146
|
+
// already restored a session if one existed; an explicit click presents the
|
|
147
|
+
// SDK sign-in surface (password / QR device flow / add account).
|
|
148
|
+
if (isWebBrowser() && !publicKey) {
|
|
149
|
+
showSignInModal();
|
|
150
|
+
// Resolves when the modal commits a session; the caller typically reacts
|
|
151
|
+
// to `isAuthenticated` rather than this promise.
|
|
160
152
|
return new Promise<User>(() => undefined);
|
|
161
153
|
}
|
|
162
154
|
|
|
163
|
-
// Native:
|
|
164
|
-
// If public key provided, use it directly
|
|
155
|
+
// Native: use the cryptographic identity directly when a public key is given.
|
|
165
156
|
if (publicKey) {
|
|
166
157
|
return oxySignIn(publicKey);
|
|
167
158
|
}
|
|
168
159
|
|
|
169
|
-
//
|
|
160
|
+
// Native with an existing keychain identity: sign in with it.
|
|
170
161
|
const hasExisting = await hasIdentity();
|
|
171
|
-
|
|
172
162
|
if (hasExisting) {
|
|
173
163
|
const existingKey = await getPublicKey();
|
|
174
164
|
if (existingKey) {
|
|
@@ -176,27 +166,16 @@ export function useAuth(): UseAuthReturn {
|
|
|
176
166
|
}
|
|
177
167
|
}
|
|
178
168
|
|
|
179
|
-
//
|
|
169
|
+
// Native with no identity: open the auth sheet (password / QR device flow).
|
|
180
170
|
if (showBottomSheet) {
|
|
181
171
|
showBottomSheet('OxyAuth');
|
|
182
|
-
// Return a promise that resolves when auth completes
|
|
183
172
|
return new Promise((_, reject) => {
|
|
184
173
|
reject(new Error('Please complete sign-in in the auth sheet'));
|
|
185
174
|
});
|
|
186
175
|
}
|
|
187
176
|
|
|
188
|
-
// Web fallback: navigate to login page on auth domain
|
|
189
|
-
if (isWebBrowser()) {
|
|
190
|
-
const authBase = authWebUrl || 'https://accounts.oxy.so';
|
|
191
|
-
const loginUrl = window.location.hostname.includes('oxy.so')
|
|
192
|
-
? '/login'
|
|
193
|
-
: `${authBase}/login`;
|
|
194
|
-
window.location.href = loginUrl;
|
|
195
|
-
return new Promise(() => {}); // Never resolves, page will redirect
|
|
196
|
-
}
|
|
197
|
-
|
|
198
177
|
throw new Error('No authentication method available');
|
|
199
|
-
}, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet
|
|
178
|
+
}, [oxySignIn, hasIdentity, getPublicKey, showBottomSheet]);
|
|
200
179
|
|
|
201
180
|
const signOut = useCallback(async (): Promise<void> => {
|
|
202
181
|
await logout();
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `usePasswordSignIn` — the first-party password sign-in state machine.
|
|
3
|
+
*
|
|
4
|
+
* Drives the in-app "Sign in with Oxy" password flow shared by the web
|
|
5
|
+
* `SignInModal` and the native `OxyAuthScreen`: identifier → password → optional
|
|
6
|
+
* two-factor. It is a thin UI state machine over the context methods
|
|
7
|
+
* `signInWithPassword` + `completeTwoFactorSignIn` (which own the network call,
|
|
8
|
+
* the persisted-refresh commit, and the device-set registration) — so both
|
|
9
|
+
* surfaces present an identical flow without re-implementing any transport.
|
|
10
|
+
*
|
|
11
|
+
* On a committed session `onSignedIn` fires (the modal/screen closes); the
|
|
12
|
+
* device-first cold boot / SessionClient projection then drive the app into the
|
|
13
|
+
* authenticated state.
|
|
14
|
+
*/
|
|
15
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
16
|
+
import { useOxy } from '../context/OxyContext';
|
|
17
|
+
import { handleAuthError } from '../utils/errorHandlers';
|
|
18
|
+
|
|
19
|
+
export type PasswordSignInStep = 'identifier' | 'password' | 'twoFactor';
|
|
20
|
+
|
|
21
|
+
export interface UsePasswordSignInOptions {
|
|
22
|
+
/** Fired once a session has been committed (the surface should close). */
|
|
23
|
+
onSignedIn?: () => void;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface UsePasswordSignInResult {
|
|
27
|
+
step: PasswordSignInStep;
|
|
28
|
+
identifier: string;
|
|
29
|
+
setIdentifier: (value: string) => void;
|
|
30
|
+
password: string;
|
|
31
|
+
setPassword: (value: string) => void;
|
|
32
|
+
/** The current 2FA input — a TOTP code or a backup code, per `useBackupCode`. */
|
|
33
|
+
code: string;
|
|
34
|
+
setCode: (value: string) => void;
|
|
35
|
+
useBackupCode: boolean;
|
|
36
|
+
setUseBackupCode: (value: boolean) => void;
|
|
37
|
+
error: string | null;
|
|
38
|
+
isSubmitting: boolean;
|
|
39
|
+
/** Advance identifier → password (validates a non-empty identifier). */
|
|
40
|
+
submitIdentifier: () => void;
|
|
41
|
+
/** Submit the password: commits a one-step session, or advances to 2FA. */
|
|
42
|
+
submitPassword: () => Promise<void>;
|
|
43
|
+
/** Submit the 2FA code / backup code and commit the session. */
|
|
44
|
+
submitTwoFactor: () => Promise<void>;
|
|
45
|
+
/** Step back one screen (2FA → password → identifier), clearing the error. */
|
|
46
|
+
back: () => void;
|
|
47
|
+
/** Reset every field back to the identifier step. */
|
|
48
|
+
reset: () => void;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function usePasswordSignIn(options: UsePasswordSignInOptions = {}): UsePasswordSignInResult {
|
|
52
|
+
const { signInWithPassword, completeTwoFactorSignIn } = useOxy();
|
|
53
|
+
const { onSignedIn } = options;
|
|
54
|
+
|
|
55
|
+
const [step, setStep] = useState<PasswordSignInStep>('identifier');
|
|
56
|
+
const [identifier, setIdentifier] = useState('');
|
|
57
|
+
const [password, setPassword] = useState('');
|
|
58
|
+
const [code, setCode] = useState('');
|
|
59
|
+
const [useBackupCode, setUseBackupCode] = useState(false);
|
|
60
|
+
const [error, setError] = useState<string | null>(null);
|
|
61
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
62
|
+
const [loginToken, setLoginToken] = useState<string | null>(null);
|
|
63
|
+
|
|
64
|
+
// Synchronous in-flight guard. `isSubmitting` state drives the UI, but a rapid
|
|
65
|
+
// double-tap fires both handlers in the SAME tick — before React re-renders —
|
|
66
|
+
// so a state read would still see `false` on the second call and double-fire
|
|
67
|
+
// the network request (rate-limit + race). This ref is set/checked
|
|
68
|
+
// synchronously, so the second call is a true no-op.
|
|
69
|
+
const submittingRef = useRef(false);
|
|
70
|
+
|
|
71
|
+
const surfaceError = useCallback((err: unknown, defaultMessage: string): void => {
|
|
72
|
+
setError(handleAuthError(err, { defaultMessage, code: 'PASSWORD_SIGN_IN_ERROR' }));
|
|
73
|
+
}, []);
|
|
74
|
+
|
|
75
|
+
const submitIdentifier = useCallback(() => {
|
|
76
|
+
if (!identifier.trim()) {
|
|
77
|
+
setError('Enter your username or email');
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
setError(null);
|
|
81
|
+
setStep('password');
|
|
82
|
+
}, [identifier]);
|
|
83
|
+
|
|
84
|
+
const submitPassword = useCallback(async () => {
|
|
85
|
+
// A second call while one is already in flight is a no-op (double-tap guard).
|
|
86
|
+
if (submittingRef.current) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!password) {
|
|
90
|
+
setError('Enter your password');
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
setError(null);
|
|
94
|
+
submittingRef.current = true;
|
|
95
|
+
setIsSubmitting(true);
|
|
96
|
+
try {
|
|
97
|
+
const result = await signInWithPassword(identifier.trim(), password);
|
|
98
|
+
if (result.status === '2fa_required') {
|
|
99
|
+
setLoginToken(result.loginToken);
|
|
100
|
+
setCode('');
|
|
101
|
+
setUseBackupCode(false);
|
|
102
|
+
setStep('twoFactor');
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
onSignedIn?.();
|
|
106
|
+
} catch (err) {
|
|
107
|
+
surfaceError(err, 'Sign in failed');
|
|
108
|
+
} finally {
|
|
109
|
+
submittingRef.current = false;
|
|
110
|
+
setIsSubmitting(false);
|
|
111
|
+
}
|
|
112
|
+
}, [identifier, password, signInWithPassword, onSignedIn, surfaceError]);
|
|
113
|
+
|
|
114
|
+
const submitTwoFactor = useCallback(async () => {
|
|
115
|
+
// A second call while one is already in flight is a no-op (double-tap guard).
|
|
116
|
+
if (submittingRef.current) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!loginToken) {
|
|
120
|
+
setError('Your sign-in session expired. Start again.');
|
|
121
|
+
setStep('password');
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
if (!code.trim()) {
|
|
125
|
+
setError(useBackupCode ? 'Enter a backup code' : 'Enter your 6-digit code');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
setError(null);
|
|
129
|
+
submittingRef.current = true;
|
|
130
|
+
setIsSubmitting(true);
|
|
131
|
+
try {
|
|
132
|
+
await completeTwoFactorSignIn({
|
|
133
|
+
loginToken,
|
|
134
|
+
...(useBackupCode ? { backupCode: code.trim() } : { token: code.trim() }),
|
|
135
|
+
});
|
|
136
|
+
onSignedIn?.();
|
|
137
|
+
} catch (err) {
|
|
138
|
+
surfaceError(err, 'Verification failed');
|
|
139
|
+
} finally {
|
|
140
|
+
submittingRef.current = false;
|
|
141
|
+
setIsSubmitting(false);
|
|
142
|
+
}
|
|
143
|
+
}, [loginToken, code, useBackupCode, completeTwoFactorSignIn, onSignedIn, surfaceError]);
|
|
144
|
+
|
|
145
|
+
const back = useCallback(() => {
|
|
146
|
+
setError(null);
|
|
147
|
+
setStep((current) => {
|
|
148
|
+
if (current === 'twoFactor') {
|
|
149
|
+
setLoginToken(null);
|
|
150
|
+
setCode('');
|
|
151
|
+
return 'password';
|
|
152
|
+
}
|
|
153
|
+
if (current === 'password') {
|
|
154
|
+
setPassword('');
|
|
155
|
+
return 'identifier';
|
|
156
|
+
}
|
|
157
|
+
return current;
|
|
158
|
+
});
|
|
159
|
+
}, []);
|
|
160
|
+
|
|
161
|
+
const reset = useCallback(() => {
|
|
162
|
+
setStep('identifier');
|
|
163
|
+
setIdentifier('');
|
|
164
|
+
setPassword('');
|
|
165
|
+
setCode('');
|
|
166
|
+
setUseBackupCode(false);
|
|
167
|
+
setLoginToken(null);
|
|
168
|
+
setError(null);
|
|
169
|
+
submittingRef.current = false;
|
|
170
|
+
setIsSubmitting(false);
|
|
171
|
+
}, []);
|
|
172
|
+
|
|
173
|
+
return useMemo(
|
|
174
|
+
() => ({
|
|
175
|
+
step,
|
|
176
|
+
identifier,
|
|
177
|
+
setIdentifier,
|
|
178
|
+
password,
|
|
179
|
+
setPassword,
|
|
180
|
+
code,
|
|
181
|
+
setCode,
|
|
182
|
+
useBackupCode,
|
|
183
|
+
setUseBackupCode,
|
|
184
|
+
error,
|
|
185
|
+
isSubmitting,
|
|
186
|
+
submitIdentifier,
|
|
187
|
+
submitPassword,
|
|
188
|
+
submitTwoFactor,
|
|
189
|
+
back,
|
|
190
|
+
reset,
|
|
191
|
+
}),
|
|
192
|
+
[
|
|
193
|
+
step,
|
|
194
|
+
identifier,
|
|
195
|
+
password,
|
|
196
|
+
code,
|
|
197
|
+
useBackupCode,
|
|
198
|
+
error,
|
|
199
|
+
isSubmitting,
|
|
200
|
+
submitIdentifier,
|
|
201
|
+
submitPassword,
|
|
202
|
+
submitTwoFactor,
|
|
203
|
+
back,
|
|
204
|
+
reset,
|
|
205
|
+
],
|
|
206
|
+
);
|
|
207
|
+
}
|
|
@@ -24,7 +24,6 @@ export interface ProfileUpdateData {
|
|
|
24
24
|
username?: string;
|
|
25
25
|
email?: string;
|
|
26
26
|
bio?: string;
|
|
27
|
-
location?: string;
|
|
28
27
|
locations?: ProfileLocation[];
|
|
29
28
|
links?: string[];
|
|
30
29
|
linksMetadata?: ProfileLinkMetadata[];
|
|
@@ -67,13 +66,8 @@ export const useProfileEditing = () => {
|
|
|
67
66
|
if (updates.bio !== undefined) {
|
|
68
67
|
updateData.bio = updates.bio;
|
|
69
68
|
}
|
|
70
|
-
if (updates.
|
|
71
|
-
updateData.
|
|
72
|
-
? updates.locations[0].name
|
|
73
|
-
: updates.location || '';
|
|
74
|
-
if (updates.locations) {
|
|
75
|
-
updateData.locations = updates.locations;
|
|
76
|
-
}
|
|
69
|
+
if (updates.locations !== undefined) {
|
|
70
|
+
updateData.locations = updates.locations;
|
|
77
71
|
}
|
|
78
72
|
if (updates.links !== undefined) {
|
|
79
73
|
updateData.links = updates.links;
|
|
@@ -8,7 +8,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
|
-
import { isWebBrowser } from '
|
|
11
|
+
import { isWebBrowser } from '../utils/isWebBrowser';
|
|
12
12
|
|
|
13
13
|
export interface UseSessionManagementOptions {
|
|
14
14
|
oxyServices: OxyServices;
|
|
@@ -26,6 +26,7 @@ export type RouteName =
|
|
|
26
26
|
| 'UserLinks'
|
|
27
27
|
| 'HistoryView'
|
|
28
28
|
| 'SavesCollections'
|
|
29
|
+
| 'EditProfile' // Profile-editing hub: one row per editable field
|
|
29
30
|
| 'EditProfileField' // Dedicated screen for editing a single profile field
|
|
30
31
|
| 'LearnMoreUsernames' // Informational screen about usernames
|
|
31
32
|
| 'TrustCenter'
|
|
@@ -67,6 +68,7 @@ const screenLoaders: Record<RouteName, () => ComponentType<BaseScreenProps>> = {
|
|
|
67
68
|
UserLinks: () => require('../screens/UserLinksScreen').default,
|
|
68
69
|
HistoryView: () => require('../screens/HistoryViewScreen').default,
|
|
69
70
|
SavesCollections: () => require('../screens/SavesCollectionsScreen').default,
|
|
71
|
+
EditProfile: () => require('../screens/EditProfileScreen').default,
|
|
70
72
|
EditProfileField: () => require('../screens/EditProfileFieldScreen').default,
|
|
71
73
|
// Informational screens
|
|
72
74
|
LearnMoreUsernames: () => require('../screens/LearnMoreUsernamesScreen').default,
|