@oxyhq/services 5.17.8 → 5.17.9
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/crypto/index.js +0 -23
- package/lib/commonjs/crypto/index.js.map +1 -1
- package/lib/commonjs/index.js +0 -15
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/components/Icon.js.map +1 -1
- package/lib/commonjs/ui/components/IconButton/utils.js.map +1 -1
- package/lib/commonjs/ui/components/TextField/Adornment/utils.js.map +1 -1
- package/lib/commonjs/ui/components/TextField/helpers.js.map +1 -1
- package/lib/commonjs/ui/components/TouchableRipple/utils.js.map +1 -1
- package/lib/commonjs/ui/components/Typography/AnimatedText.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +23 -15
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +63 -111
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js +0 -1
- package/lib/commonjs/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +52 -15
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/utils/avatarUtils.js +2 -32
- package/lib/commonjs/ui/utils/avatarUtils.js.map +1 -1
- package/lib/module/crypto/index.js +4 -6
- package/lib/module/crypto/index.js.map +1 -1
- package/lib/module/index.js +6 -3
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/components/Icon.js.map +1 -1
- package/lib/module/ui/components/IconButton/utils.js.map +1 -1
- package/lib/module/ui/components/TextField/Adornment/utils.js.map +1 -1
- package/lib/module/ui/components/TextField/helpers.js.map +1 -1
- package/lib/module/ui/components/TouchableRipple/utils.js.map +1 -1
- package/lib/module/ui/components/Typography/AnimatedText.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +23 -15
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +63 -111
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/screens/OxyAuthScreen.js +0 -1
- package/lib/module/ui/screens/OxyAuthScreen.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +52 -15
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/utils/avatarUtils.js +2 -32
- package/lib/module/ui/utils/avatarUtils.js.map +1 -1
- package/lib/typescript/crypto/index.d.ts +2 -5
- package/lib/typescript/crypto/index.d.ts.map +1 -1
- package/lib/typescript/crypto/types.d.ts +2 -2
- package/lib/typescript/index.d.ts +4 -2
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/IconButton/utils.d.ts +1 -1
- package/lib/typescript/ui/components/TextField/Adornment/utils.d.ts +1 -1
- package/lib/typescript/ui/components/TextField/Adornment/utils.d.ts.map +1 -1
- package/lib/typescript/ui/components/TextField/helpers.d.ts +6 -6
- package/lib/typescript/ui/components/types.d.ts +0 -4
- package/lib/typescript/ui/components/types.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContextBase.d.ts +2 -2
- package/lib/typescript/ui/context/OxyContextBase.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +9 -5
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +27 -4
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/utils/avatarUtils.d.ts +0 -2
- package/lib/typescript/ui/utils/avatarUtils.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/crypto/index.ts +3 -11
- package/src/crypto/types.ts +2 -2
- package/src/index.ts +6 -11
- package/src/ui/components/Icon.tsx +1 -1
- package/src/ui/components/IconButton/utils.ts +1 -1
- package/src/ui/components/TextField/Adornment/utils.ts +2 -2
- package/src/ui/components/TextField/helpers.tsx +8 -8
- package/src/ui/components/TouchableRipple/utils.ts +2 -2
- package/src/ui/components/Typography/AnimatedText.tsx +2 -2
- package/src/ui/components/types.tsx +0 -6
- package/src/ui/context/OxyContext.tsx +23 -14
- package/src/ui/context/OxyContextBase.tsx +4 -4
- package/src/ui/context/hooks/useAuthOperations.ts +83 -134
- package/src/ui/screens/OxyAuthScreen.tsx +1 -1
- package/src/ui/stores/authStore.ts +57 -18
- package/src/ui/utils/avatarUtils.ts +4 -36
- package/lib/commonjs/crypto/keyManager.js +0 -356
- package/lib/commonjs/crypto/keyManager.js.map +0 -1
- package/lib/commonjs/crypto/signatureService.js +0 -269
- package/lib/commonjs/crypto/signatureService.js.map +0 -1
- package/lib/module/crypto/keyManager.js +0 -353
- package/lib/module/crypto/keyManager.js.map +0 -1
- package/lib/module/crypto/signatureService.js +0 -266
- package/lib/module/crypto/signatureService.js.map +0 -1
- package/lib/typescript/crypto/keyManager.d.ts +0 -80
- package/lib/typescript/crypto/keyManager.d.ts.map +0 -1
- package/lib/typescript/crypto/signatureService.d.ts +0 -77
- package/lib/typescript/crypto/signatureService.d.ts.map +0 -1
- package/src/crypto/keyManager.ts +0 -379
- package/src/crypto/signatureService.ts +0 -301
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { useCallback } from 'react';
|
|
2
2
|
import type { ApiError, User } from '../../../models/interfaces';
|
|
3
3
|
import type { AuthState } from '../../stores/authStore';
|
|
4
|
-
import type { ClientSession } from '../../../models/session';
|
|
5
|
-
import { DeviceManager } from '../../../utils/deviceManager';
|
|
4
|
+
import type { ClientSession, SessionLoginResponse } from '../../../models/session';
|
|
6
5
|
import { fetchSessionsWithFallback } from '../../utils/sessionHelpers';
|
|
7
6
|
import { handleAuthError, isInvalidSessionError } from '../../utils/errorHandlers';
|
|
8
7
|
import type { StorageInterface } from '../../utils/storageHelpers';
|
|
9
8
|
import type { OxyServices } from '../../../core';
|
|
10
|
-
import { KeyManager, SignatureService } from '../../../crypto';
|
|
11
9
|
|
|
12
10
|
export interface UseAuthOperationsOptions {
|
|
13
11
|
oxyServices: OxyServices;
|
|
@@ -30,22 +28,25 @@ export interface UseAuthOperationsOptions {
|
|
|
30
28
|
}
|
|
31
29
|
|
|
32
30
|
export interface UseAuthOperationsResult {
|
|
33
|
-
/**
|
|
34
|
-
|
|
31
|
+
/** Complete sign-in after external challenge-response (Accounts app signs, Services completes session) */
|
|
32
|
+
completeSignIn: (sessionResponse: SessionLoginResponse) => Promise<User>;
|
|
35
33
|
/** Logout from current session */
|
|
36
34
|
logout: (targetSessionId?: string) => Promise<void>;
|
|
37
35
|
/** Logout from all sessions */
|
|
38
36
|
logoutAll: () => Promise<void>;
|
|
39
37
|
}
|
|
40
38
|
|
|
41
|
-
const LOGIN_ERROR_CODE = 'LOGIN_ERROR';
|
|
42
39
|
const LOGOUT_ERROR_CODE = 'LOGOUT_ERROR';
|
|
43
40
|
const LOGOUT_ALL_ERROR_CODE = 'LOGOUT_ALL_ERROR';
|
|
44
41
|
|
|
45
42
|
|
|
46
43
|
/**
|
|
47
|
-
* Authentication operations
|
|
48
|
-
*
|
|
44
|
+
* Authentication operations for Services SDK.
|
|
45
|
+
*
|
|
46
|
+
* ARCHITECTURE:
|
|
47
|
+
* - Services SDK only handles tokens/sessions (NOT identity)
|
|
48
|
+
* - Accounts app handles identity (KeyManager, SignatureService, challenge signing)
|
|
49
|
+
* - completeSignIn() accepts already-verified session from Accounts app
|
|
49
50
|
*/
|
|
50
51
|
export const useAuthOperations = ({
|
|
51
52
|
oxyServices,
|
|
@@ -68,148 +69,82 @@ export const useAuthOperations = ({
|
|
|
68
69
|
}: UseAuthOperationsOptions): UseAuthOperationsResult => {
|
|
69
70
|
|
|
70
71
|
/**
|
|
71
|
-
*
|
|
72
|
+
* Complete sign-in after external authentication.
|
|
73
|
+
*
|
|
74
|
+
* This is called by Accounts app AFTER it has:
|
|
75
|
+
* 1. Requested challenge from backend
|
|
76
|
+
* 2. Signed challenge locally with private key
|
|
77
|
+
* 3. Verified challenge with backend to get sessionResponse
|
|
78
|
+
*
|
|
79
|
+
* Services SDK only stores tokens and manages session state.
|
|
80
|
+
*
|
|
81
|
+
* @param sessionResponse - Session data from verifyChallenge API call
|
|
72
82
|
*/
|
|
73
|
-
const
|
|
74
|
-
async (
|
|
75
|
-
|
|
76
|
-
const deviceFingerprint = JSON.stringify(deviceFingerprintObj);
|
|
77
|
-
const deviceInfo = await DeviceManager.getDeviceInfo();
|
|
78
|
-
const defaultDeviceName = deviceInfo.deviceName || DeviceManager.getDefaultDeviceName();
|
|
79
|
-
const finalDeviceName = deviceName || defaultDeviceName;
|
|
80
|
-
|
|
81
|
-
// Look up user by public key
|
|
82
|
-
const userLookup = await oxyServices.getUserByPublicKey(publicKey);
|
|
83
|
-
const userId = userLookup.id;
|
|
84
|
-
|
|
85
|
-
// Request challenge
|
|
86
|
-
const challengeResponse = await oxyServices.requestChallenge(userId);
|
|
87
|
-
const challenge = challengeResponse.challenge;
|
|
88
|
-
|
|
89
|
-
// Sign the challenge
|
|
90
|
-
const { challenge: signature, timestamp } = await SignatureService.signChallenge(challenge);
|
|
91
|
-
|
|
92
|
-
// Verify and create session
|
|
93
|
-
let sessionResponse;
|
|
94
|
-
try {
|
|
95
|
-
sessionResponse = await oxyServices.verifyChallenge(
|
|
96
|
-
userId,
|
|
97
|
-
challenge,
|
|
98
|
-
signature,
|
|
99
|
-
timestamp,
|
|
100
|
-
finalDeviceName,
|
|
101
|
-
deviceFingerprint,
|
|
102
|
-
);
|
|
103
|
-
} catch (verifyError) {
|
|
104
|
-
if (__DEV__) {
|
|
105
|
-
console.error('[useAuthOperations] verifyChallenge failed:', {
|
|
106
|
-
error: verifyError,
|
|
107
|
-
userId,
|
|
108
|
-
challengeLength: challenge?.length,
|
|
109
|
-
signatureLength: signature?.length,
|
|
110
|
-
timestamp,
|
|
111
|
-
timeSinceChallenge: Date.now() - timestamp,
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
throw verifyError;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Store tokens
|
|
118
|
-
oxyServices.setTokens(sessionResponse.accessToken, sessionResponse.refreshToken);
|
|
83
|
+
const completeSignIn = useCallback(
|
|
84
|
+
async (sessionResponse: SessionLoginResponse): Promise<User> => {
|
|
85
|
+
if (!storage) throw new Error('Storage not initialized');
|
|
119
86
|
|
|
120
|
-
|
|
121
|
-
const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
87
|
+
setAuthState({ isLoading: true, error: null });
|
|
122
88
|
|
|
123
|
-
// Fetch device sessions
|
|
124
|
-
let allDeviceSessions: ClientSession[] = [];
|
|
125
89
|
try {
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
fallbackUserId: fullUser.id,
|
|
129
|
-
logger,
|
|
130
|
-
});
|
|
131
|
-
} catch (error) {
|
|
132
|
-
if (__DEV__) {
|
|
133
|
-
console.warn('Failed to fetch device sessions after login:', error);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
90
|
+
// Store tokens (Services' only responsibility)
|
|
91
|
+
oxyServices.setTokens(sessionResponse.accessToken, sessionResponse.refreshToken);
|
|
136
92
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
(session) =>
|
|
140
|
-
session.userId?.toString() === fullUser.id?.toString() &&
|
|
141
|
-
session.sessionId !== sessionResponse.sessionId,
|
|
142
|
-
);
|
|
93
|
+
// Get full user data
|
|
94
|
+
const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
143
95
|
|
|
144
|
-
|
|
145
|
-
|
|
96
|
+
// Fetch device sessions
|
|
97
|
+
let allDeviceSessions: ClientSession[] = [];
|
|
146
98
|
try {
|
|
147
|
-
await oxyServices
|
|
148
|
-
|
|
99
|
+
allDeviceSessions = await fetchSessionsWithFallback(oxyServices, sessionResponse.sessionId, {
|
|
100
|
+
fallbackDeviceId: sessionResponse.deviceId,
|
|
101
|
+
fallbackUserId: fullUser.id,
|
|
102
|
+
logger,
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
149
105
|
if (__DEV__) {
|
|
150
|
-
console.warn('Failed to
|
|
106
|
+
console.warn('Failed to fetch device sessions after login:', error);
|
|
151
107
|
}
|
|
152
108
|
}
|
|
153
|
-
await switchSession(existingSession.sessionId);
|
|
154
|
-
updateSessions(
|
|
155
|
-
allDeviceSessions.filter((session) => session.sessionId !== sessionResponse.sessionId),
|
|
156
|
-
{ merge: false },
|
|
157
|
-
);
|
|
158
|
-
onAuthStateChange?.(fullUser);
|
|
159
|
-
return fullUser;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
setActiveSessionId(sessionResponse.sessionId);
|
|
163
|
-
await saveActiveSessionId(sessionResponse.sessionId);
|
|
164
|
-
updateSessions(allDeviceSessions, { merge: true });
|
|
165
|
-
|
|
166
|
-
await applyLanguagePreference(fullUser);
|
|
167
|
-
loginSuccess();
|
|
168
|
-
onAuthStateChange?.(fullUser);
|
|
169
|
-
|
|
170
|
-
return fullUser;
|
|
171
|
-
},
|
|
172
|
-
[
|
|
173
|
-
applyLanguagePreference,
|
|
174
|
-
logger,
|
|
175
|
-
loginSuccess,
|
|
176
|
-
onAuthStateChange,
|
|
177
|
-
oxyServices,
|
|
178
|
-
saveActiveSessionId,
|
|
179
|
-
setActiveSessionId,
|
|
180
|
-
switchSession,
|
|
181
|
-
updateSessions,
|
|
182
|
-
],
|
|
183
|
-
);
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Sign in with existing identity on device
|
|
188
|
-
*/
|
|
189
|
-
const signIn = useCallback(
|
|
190
|
-
async (deviceName?: string): Promise<User> => {
|
|
191
|
-
if (!storage) throw new Error('Storage not initialized');
|
|
192
109
|
|
|
193
|
-
|
|
110
|
+
// Check for existing session for same user
|
|
111
|
+
const existingSession = allDeviceSessions.find(
|
|
112
|
+
(session) =>
|
|
113
|
+
session.userId?.toString() === fullUser.id?.toString() &&
|
|
114
|
+
session.sessionId !== sessionResponse.sessionId,
|
|
115
|
+
);
|
|
194
116
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
117
|
+
if (existingSession) {
|
|
118
|
+
// Logout duplicate session
|
|
119
|
+
try {
|
|
120
|
+
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
121
|
+
} catch (logoutError) {
|
|
122
|
+
if (__DEV__) {
|
|
123
|
+
console.warn('Failed to logout duplicate session:', logoutError);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
await switchSession(existingSession.sessionId);
|
|
127
|
+
updateSessions(
|
|
128
|
+
allDeviceSessions.filter((session) => session.sessionId !== sessionResponse.sessionId),
|
|
129
|
+
{ merge: false },
|
|
130
|
+
);
|
|
131
|
+
onAuthStateChange?.(fullUser);
|
|
132
|
+
return fullUser;
|
|
200
133
|
}
|
|
201
134
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
throw new Error('Identity is not registered. Please register your identity in the Accounts app before signing in.');
|
|
206
|
-
}
|
|
135
|
+
setActiveSessionId(sessionResponse.sessionId);
|
|
136
|
+
await saveActiveSessionId(sessionResponse.sessionId);
|
|
137
|
+
updateSessions(allDeviceSessions, { merge: true });
|
|
207
138
|
|
|
208
|
-
|
|
139
|
+
await applyLanguagePreference(fullUser);
|
|
140
|
+
loginSuccess();
|
|
141
|
+
onAuthStateChange?.(fullUser);
|
|
142
|
+
|
|
143
|
+
return fullUser;
|
|
209
144
|
} catch (error) {
|
|
210
145
|
const message = handleAuthError(error, {
|
|
211
146
|
defaultMessage: 'Sign in failed',
|
|
212
|
-
code:
|
|
147
|
+
code: 'COMPLETE_SIGNIN_ERROR',
|
|
213
148
|
onError,
|
|
214
149
|
setAuthError: (msg: string) => setAuthState({ error: msg }),
|
|
215
150
|
logger,
|
|
@@ -220,7 +155,21 @@ export const useAuthOperations = ({
|
|
|
220
155
|
setAuthState({ isLoading: false });
|
|
221
156
|
}
|
|
222
157
|
},
|
|
223
|
-
[
|
|
158
|
+
[
|
|
159
|
+
storage,
|
|
160
|
+
setAuthState,
|
|
161
|
+
oxyServices,
|
|
162
|
+
logger,
|
|
163
|
+
saveActiveSessionId,
|
|
164
|
+
setActiveSessionId,
|
|
165
|
+
switchSession,
|
|
166
|
+
updateSessions,
|
|
167
|
+
applyLanguagePreference,
|
|
168
|
+
loginSuccess,
|
|
169
|
+
onAuthStateChange,
|
|
170
|
+
onError,
|
|
171
|
+
loginFailure,
|
|
172
|
+
],
|
|
224
173
|
);
|
|
225
174
|
|
|
226
175
|
|
|
@@ -304,7 +253,7 @@ export const useAuthOperations = ({
|
|
|
304
253
|
}, [activeSessionId, clearSessionState, logger, onError, oxyServices, setAuthState]);
|
|
305
254
|
|
|
306
255
|
return {
|
|
307
|
-
|
|
256
|
+
completeSignIn,
|
|
308
257
|
logout,
|
|
309
258
|
logoutAll,
|
|
310
259
|
};
|
|
@@ -60,7 +60,7 @@ const OxyAuthScreen: React.FC<BaseScreenProps> = ({
|
|
|
60
60
|
}) => {
|
|
61
61
|
const themeValue = (theme === 'light' || theme === 'dark') ? theme : 'light';
|
|
62
62
|
const colors = useThemeColors(themeValue);
|
|
63
|
-
const { oxyServices,
|
|
63
|
+
const { oxyServices, switchSession } = useOxy();
|
|
64
64
|
|
|
65
65
|
const [authSession, setAuthSession] = useState<AuthSession | null>(null);
|
|
66
66
|
const [isLoading, setIsLoading] = useState(true);
|
|
@@ -1,44 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth Store (Services SDK)
|
|
3
|
+
*
|
|
4
|
+
* === ARCHITECTURE: Services owns sessions/tokens, NOT identity ===
|
|
5
|
+
*
|
|
6
|
+
* This store manages authentication state:
|
|
7
|
+
* - isAuthenticated: Valid tokens AND online (REQUIRES NETWORK)
|
|
8
|
+
* - isOnline: Network connectivity state
|
|
9
|
+
* - isLoading: Auth operation in progress
|
|
10
|
+
* - error: Last auth error
|
|
11
|
+
*
|
|
12
|
+
* CRITICAL RULES:
|
|
13
|
+
* ✓ isAuthenticated = true ONLY when:
|
|
14
|
+
* 1. Valid access/refresh tokens exist
|
|
15
|
+
* 2. Network is available (isOnline = true)
|
|
16
|
+
*
|
|
17
|
+
* ✗ isAuthenticated must be false when:
|
|
18
|
+
* - Offline (even if tokens exist)
|
|
19
|
+
* - No valid tokens
|
|
20
|
+
* - Tokens expired
|
|
21
|
+
*
|
|
22
|
+
* WHY: Authentication requires server validation. Without network,
|
|
23
|
+
* we cannot verify tokens are still valid or refresh them.
|
|
24
|
+
*/
|
|
25
|
+
|
|
1
26
|
import { create } from 'zustand';
|
|
2
27
|
|
|
3
28
|
export interface AuthState {
|
|
4
29
|
isAuthenticated: boolean;
|
|
30
|
+
isOnline: boolean;
|
|
5
31
|
isLoading: boolean;
|
|
6
32
|
error: string | null;
|
|
7
33
|
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
isSyncing: boolean;
|
|
11
|
-
|
|
34
|
+
// Actions
|
|
35
|
+
setOnline: (online: boolean) => void;
|
|
12
36
|
loginSuccess: () => void;
|
|
13
37
|
loginFailure: (error: string) => void;
|
|
14
38
|
logout: () => void;
|
|
15
39
|
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
setSyncing: (syncing: boolean) => void;
|
|
40
|
+
// Helpers
|
|
41
|
+
canAuthenticate: () => boolean;
|
|
19
42
|
}
|
|
20
43
|
|
|
21
|
-
export const useAuthStore = create<AuthState>((set
|
|
44
|
+
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
22
45
|
isAuthenticated: false,
|
|
46
|
+
isOnline: true, // Assume online initially
|
|
23
47
|
isLoading: false,
|
|
24
48
|
error: null,
|
|
25
49
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
50
|
+
setOnline: (online: boolean) => {
|
|
51
|
+
set({ isOnline: online });
|
|
52
|
+
// If we go offline, we can't be authenticated
|
|
53
|
+
if (!online) {
|
|
54
|
+
set({ isAuthenticated: false });
|
|
55
|
+
}
|
|
56
|
+
},
|
|
29
57
|
|
|
30
58
|
loginSuccess: () => set({
|
|
31
59
|
isLoading: false,
|
|
32
|
-
isAuthenticated:
|
|
33
|
-
|
|
60
|
+
isAuthenticated: get().isOnline, // Only authenticated if online
|
|
61
|
+
error: null,
|
|
34
62
|
}),
|
|
35
|
-
|
|
63
|
+
|
|
64
|
+
loginFailure: (error: string) => set({
|
|
65
|
+
isLoading: false,
|
|
66
|
+
isAuthenticated: false,
|
|
67
|
+
error
|
|
68
|
+
}),
|
|
69
|
+
|
|
36
70
|
logout: () => set({
|
|
37
71
|
isAuthenticated: false,
|
|
38
|
-
|
|
72
|
+
error: null,
|
|
39
73
|
}),
|
|
40
74
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
75
|
+
/**
|
|
76
|
+
* Check if user can authenticate
|
|
77
|
+
* Requires both valid tokens (checked by caller) and network
|
|
78
|
+
*/
|
|
79
|
+
canAuthenticate: () => {
|
|
80
|
+
const state = get();
|
|
81
|
+
return state.isOnline;
|
|
82
|
+
},
|
|
44
83
|
}));
|
|
@@ -61,7 +61,6 @@ export function refreshAvatarInStore(
|
|
|
61
61
|
* @param oxyServices - OxyServices instance
|
|
62
62
|
* @param activeSessionId - Active session ID
|
|
63
63
|
* @param queryClient - TanStack Query client
|
|
64
|
-
* @param syncIdentity - Optional sync identity function for handling auth errors
|
|
65
64
|
* @returns Promise that resolves with updated user data
|
|
66
65
|
*/
|
|
67
66
|
export async function updateProfileWithAvatar(
|
|
@@ -69,29 +68,15 @@ export async function updateProfileWithAvatar(
|
|
|
69
68
|
oxyServices: OxyServices,
|
|
70
69
|
activeSessionId: string | null,
|
|
71
70
|
queryClient: QueryClient,
|
|
72
|
-
options?: {
|
|
71
|
+
options?: { deviceId?: string }
|
|
73
72
|
): Promise<User> {
|
|
74
|
-
const {
|
|
73
|
+
const { deviceId } = options || {};
|
|
75
74
|
// Ensure we have a valid token before making the request
|
|
76
75
|
if (!oxyServices.hasValidToken() && activeSessionId) {
|
|
77
76
|
try {
|
|
78
77
|
await oxyServices.getTokenBySession(activeSessionId, deviceId);
|
|
79
78
|
} catch (tokenError) {
|
|
80
|
-
|
|
81
|
-
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
82
|
-
if (syncIdentity) {
|
|
83
|
-
try {
|
|
84
|
-
await syncIdentity();
|
|
85
|
-
await oxyServices.getTokenBySession(activeSessionId, deviceId);
|
|
86
|
-
} catch (syncError) {
|
|
87
|
-
throw new Error('Session needs to be synced. Please try again.');
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
throw tokenError;
|
|
91
|
-
}
|
|
92
|
-
} else {
|
|
93
|
-
throw tokenError;
|
|
94
|
-
}
|
|
79
|
+
throw tokenError;
|
|
95
80
|
}
|
|
96
81
|
}
|
|
97
82
|
|
|
@@ -120,24 +105,7 @@ export async function updateProfileWithAvatar(
|
|
|
120
105
|
|
|
121
106
|
// Handle authentication errors
|
|
122
107
|
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
123
|
-
|
|
124
|
-
try {
|
|
125
|
-
await syncIdentity();
|
|
126
|
-
await oxyServices.getTokenBySession(activeSessionId, deviceId);
|
|
127
|
-
// Retry the update after getting token
|
|
128
|
-
return await updateProfileWithAvatar(
|
|
129
|
-
updates,
|
|
130
|
-
oxyServices,
|
|
131
|
-
activeSessionId,
|
|
132
|
-
queryClient,
|
|
133
|
-
{ syncIdentity, deviceId }
|
|
134
|
-
);
|
|
135
|
-
} catch (retryError) {
|
|
136
|
-
throw new Error('Authentication failed. Please sign in again.');
|
|
137
|
-
}
|
|
138
|
-
} else {
|
|
139
|
-
throw new Error('No active session. Please sign in.');
|
|
140
|
-
}
|
|
108
|
+
throw new Error('Authentication failed. Please sign in again.');
|
|
141
109
|
}
|
|
142
110
|
|
|
143
111
|
throw error;
|