@oxyhq/services 5.13.0 → 5.13.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/core/OxyServices.js +7 -7
- package/lib/commonjs/core/OxyServices.js.map +1 -1
- package/lib/commonjs/i18n/index.js +37 -1
- package/lib/commonjs/i18n/index.js.map +1 -1
- package/lib/commonjs/i18n/locales/ar-SA.json +128 -0
- package/lib/commonjs/i18n/locales/ca-ES.json +128 -0
- package/lib/commonjs/i18n/locales/de-DE.json +128 -0
- package/lib/commonjs/i18n/locales/en-US.json +85 -12
- package/lib/commonjs/i18n/locales/es-ES.json +58 -6
- package/lib/commonjs/i18n/locales/fr-FR.json +128 -0
- package/lib/commonjs/i18n/locales/it-IT.json +128 -0
- package/lib/commonjs/i18n/locales/ja-JP.json +127 -0
- package/lib/commonjs/i18n/locales/ko-KR.json +128 -0
- package/lib/commonjs/i18n/locales/pt-PT.json +128 -0
- package/lib/commonjs/i18n/locales/zh-CN.json +128 -0
- package/lib/commonjs/ui/components/FontLoader.js +22 -42
- package/lib/commonjs/ui/components/FontLoader.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +5 -8
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/StepBasedScreen.js +64 -44
- package/lib/commonjs/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js +14 -35
- package/lib/commonjs/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/commonjs/ui/components/internal/PinInput.js +2 -2
- package/lib/commonjs/ui/components/internal/PinInput.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +434 -321
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/SignInScreen.js +43 -39
- package/lib/commonjs/ui/screens/SignInScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +139 -125
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js +2 -4
- package/lib/commonjs/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js +45 -25
- package/lib/commonjs/ui/screens/steps/RecoverRequestStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js +88 -53
- package/lib/commonjs/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js +79 -58
- package/lib/commonjs/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js +61 -52
- package/lib/commonjs/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js +220 -31
- package/lib/commonjs/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInTotpStep.js +77 -50
- package/lib/commonjs/ui/screens/steps/SignInTotpStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js +527 -66
- package/lib/commonjs/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js +55 -30
- package/lib/commonjs/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js +64 -46
- package/lib/commonjs/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js +84 -146
- package/lib/commonjs/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
- package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js +113 -34
- package/lib/commonjs/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +16 -20
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/commonjs/ui/styles/authStyles.js +2 -1
- package/lib/commonjs/ui/styles/authStyles.js.map +1 -1
- package/lib/commonjs/ui/styles/index.js +11 -0
- package/lib/commonjs/ui/styles/index.js.map +1 -1
- package/lib/commonjs/ui/styles/spacing.js +51 -0
- package/lib/commonjs/ui/styles/spacing.js.map +1 -0
- package/lib/commonjs/utils/validationUtils.js +1 -1
- package/lib/module/core/OxyServices.js +7 -7
- package/lib/module/core/OxyServices.js.map +1 -1
- package/lib/module/i18n/index.js +37 -1
- package/lib/module/i18n/index.js.map +1 -1
- package/lib/module/i18n/locales/ar-SA.json +128 -0
- package/lib/module/i18n/locales/ca-ES.json +128 -0
- package/lib/module/i18n/locales/de-DE.json +128 -0
- package/lib/module/i18n/locales/en-US.json +85 -12
- package/lib/module/i18n/locales/es-ES.json +58 -6
- package/lib/module/i18n/locales/fr-FR.json +128 -0
- package/lib/module/i18n/locales/it-IT.json +128 -0
- package/lib/module/i18n/locales/ja-JP.json +127 -0
- package/lib/module/i18n/locales/ko-KR.json +128 -0
- package/lib/module/i18n/locales/pt-PT.json +128 -0
- package/lib/module/i18n/locales/zh-CN.json +128 -0
- package/lib/module/ui/components/FontLoader.js +23 -43
- package/lib/module/ui/components/FontLoader.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +6 -8
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/StepBasedScreen.js +65 -45
- package/lib/module/ui/components/StepBasedScreen.js.map +1 -1
- package/lib/module/ui/components/internal/GroupedPillButtons.js +14 -35
- package/lib/module/ui/components/internal/GroupedPillButtons.js.map +1 -1
- package/lib/module/ui/components/internal/PinInput.js +2 -2
- package/lib/module/ui/components/internal/PinInput.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +434 -321
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/ui/screens/SignInScreen.js +44 -40
- package/lib/module/ui/screens/SignInScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +138 -126
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/screens/internal/SignInUsernameStep.js +2 -4
- package/lib/module/ui/screens/internal/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverRequestStep.js +45 -25
- package/lib/module/ui/screens/steps/RecoverRequestStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js +89 -54
- package/lib/module/ui/screens/steps/RecoverResetPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverSuccessStep.js +80 -59
- package/lib/module/ui/screens/steps/RecoverSuccessStep.js.map +1 -1
- package/lib/module/ui/screens/steps/RecoverVerifyStep.js +62 -53
- package/lib/module/ui/screens/steps/RecoverVerifyStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInPasswordStep.js +221 -32
- package/lib/module/ui/screens/steps/SignInPasswordStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInTotpStep.js +78 -51
- package/lib/module/ui/screens/steps/SignInTotpStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignInUsernameStep.js +530 -68
- package/lib/module/ui/screens/steps/SignInUsernameStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpIdentityStep.js +55 -30
- package/lib/module/ui/screens/steps/SignUpIdentityStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpSecurityStep.js +65 -47
- package/lib/module/ui/screens/steps/SignUpSecurityStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpSummaryStep.js +84 -146
- package/lib/module/ui/screens/steps/SignUpSummaryStep.js.map +1 -1
- package/lib/module/ui/screens/steps/SignUpWelcomeStep.js +114 -35
- package/lib/module/ui/screens/steps/SignUpWelcomeStep.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +16 -20
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/module/ui/styles/authStyles.js +2 -1
- package/lib/module/ui/styles/authStyles.js.map +1 -1
- package/lib/module/ui/styles/index.js +1 -0
- package/lib/module/ui/styles/index.js.map +1 -1
- package/lib/module/ui/styles/spacing.js +48 -0
- package/lib/module/ui/styles/spacing.js.map +1 -0
- package/lib/module/utils/validationUtils.js +1 -1
- package/lib/typescript/core/OxyServices.d.ts +4 -2
- package/lib/typescript/core/OxyServices.d.ts.map +1 -1
- package/lib/typescript/i18n/index.d.ts.map +1 -1
- package/lib/typescript/ui/components/FontLoader.d.ts +3 -3
- package/lib/typescript/ui/components/FontLoader.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts +2 -2
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/StepBasedScreen.d.ts.map +1 -1
- package/lib/typescript/ui/components/internal/GroupedPillButtons.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/screens/SignInScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverRequestStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverResetPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverSuccessStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/RecoverVerifyStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts +2 -0
- package/lib/typescript/ui/screens/steps/SignInPasswordStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInTotpStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignInUsernameStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpIdentityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpSecurityStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpSummaryStep.d.ts.map +1 -1
- package/lib/typescript/ui/screens/steps/SignUpWelcomeStep.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +7 -3
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/lib/typescript/ui/styles/authStyles.d.ts +1 -0
- package/lib/typescript/ui/styles/authStyles.d.ts.map +1 -1
- package/lib/typescript/ui/styles/index.d.ts +1 -0
- package/lib/typescript/ui/styles/index.d.ts.map +1 -1
- package/lib/typescript/ui/styles/spacing.d.ts +43 -0
- package/lib/typescript/ui/styles/spacing.d.ts.map +1 -0
- package/lib/typescript/utils/validationUtils.d.ts +1 -1
- package/package.json +1 -1
- package/src/core/OxyServices.ts +10 -8
- package/src/i18n/index.ts +36 -0
- package/src/i18n/locales/ar-SA.json +128 -0
- package/src/i18n/locales/ca-ES.json +128 -0
- package/src/i18n/locales/de-DE.json +128 -0
- package/src/i18n/locales/en-US.json +85 -12
- package/src/i18n/locales/es-ES.json +58 -6
- package/src/i18n/locales/fr-FR.json +128 -0
- package/src/i18n/locales/it-IT.json +128 -0
- package/src/i18n/locales/ja-JP.json +127 -0
- package/src/i18n/locales/ko-KR.json +128 -0
- package/src/i18n/locales/pt-PT.json +128 -0
- package/src/i18n/locales/zh-CN.json +128 -0
- package/src/ui/components/FontLoader.tsx +17 -37
- package/src/ui/components/OxyProvider.tsx +14 -13
- package/src/ui/components/StepBasedScreen.tsx +66 -43
- package/src/ui/components/internal/GroupedPillButtons.tsx +15 -31
- package/src/ui/components/internal/PinInput.tsx +2 -2
- package/src/ui/context/OxyContext.tsx +404 -285
- package/src/ui/screens/FileManagementScreen.tsx +15 -15
- package/src/ui/screens/SignInScreen.tsx +59 -36
- package/src/ui/screens/WelcomeNewUserScreen.tsx +102 -91
- package/src/ui/screens/internal/SignInUsernameStep.tsx +1 -1
- package/src/ui/screens/steps/RecoverRequestStep.tsx +34 -24
- package/src/ui/screens/steps/RecoverResetPasswordStep.tsx +65 -36
- package/src/ui/screens/steps/RecoverSuccessStep.tsx +71 -47
- package/src/ui/screens/steps/RecoverVerifyStep.tsx +60 -50
- package/src/ui/screens/steps/SignInPasswordStep.tsx +191 -29
- package/src/ui/screens/steps/SignInTotpStep.tsx +68 -34
- package/src/ui/screens/steps/SignInUsernameStep.tsx +586 -57
- package/src/ui/screens/steps/SignUpIdentityStep.tsx +49 -35
- package/src/ui/screens/steps/SignUpSecurityStep.tsx +56 -39
- package/src/ui/screens/steps/SignUpSummaryStep.tsx +99 -89
- package/src/ui/screens/steps/SignUpWelcomeStep.tsx +88 -20
- package/src/ui/stores/authStore.ts +15 -19
- package/src/ui/styles/authStyles.ts +2 -1
- package/src/ui/styles/index.ts +1 -0
- package/src/ui/styles/spacing.ts +46 -0
- package/src/utils/validationUtils.ts +1 -1
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
import { createContext, useContext, useEffect, useCallback, useMemo, useRef, useState } from 'react';
|
|
4
|
-
import { View, Text } from 'react-native';
|
|
5
4
|
import { OxyServices } from '../../core';
|
|
6
5
|
import { DeviceManager } from '../../utils/deviceManager';
|
|
7
6
|
import { useSessionSocket } from '../hooks/useSessionSocket';
|
|
@@ -12,6 +11,26 @@ import { useAuthStore } from '../stores/authStore';
|
|
|
12
11
|
// NOTE: We intentionally avoid importing useFollow here to prevent a require cycle.
|
|
13
12
|
// If consumers relied on `const { useFollow } = useOxy()`, we provide a lazy proxy below.
|
|
14
13
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
// Empty follow hook fallback
|
|
15
|
+
const createEmptyFollowHook = () => {
|
|
16
|
+
const emptyResult = {
|
|
17
|
+
isFollowing: false,
|
|
18
|
+
isLoading: false,
|
|
19
|
+
error: null,
|
|
20
|
+
toggleFollow: async () => {},
|
|
21
|
+
setFollowStatus: () => {},
|
|
22
|
+
fetchStatus: async () => {},
|
|
23
|
+
clearError: () => {},
|
|
24
|
+
followerCount: null,
|
|
25
|
+
followingCount: null,
|
|
26
|
+
isLoadingCounts: false,
|
|
27
|
+
fetchUserCounts: async () => {},
|
|
28
|
+
setFollowerCount: () => {},
|
|
29
|
+
setFollowingCount: () => {}
|
|
30
|
+
};
|
|
31
|
+
return () => emptyResult;
|
|
32
|
+
};
|
|
33
|
+
|
|
15
34
|
// Create the context with default values
|
|
16
35
|
const OxyContext = /*#__PURE__*/createContext(null);
|
|
17
36
|
|
|
@@ -106,6 +125,9 @@ export const OxyProvider = ({
|
|
|
106
125
|
const [storage, setStorage] = useState(null);
|
|
107
126
|
const [currentLanguage, setCurrentLanguage] = useState('en-US');
|
|
108
127
|
|
|
128
|
+
// Storage keys (memoized to prevent infinite loops) - declared early for use in helpers
|
|
129
|
+
const keys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
130
|
+
|
|
109
131
|
// Normalize language codes to BCP-47 (e.g., en-US)
|
|
110
132
|
const normalizeLanguageCode = useCallback(lang => {
|
|
111
133
|
if (!lang) return null;
|
|
@@ -125,21 +147,52 @@ export const OxyProvider = ({
|
|
|
125
147
|
};
|
|
126
148
|
return map[lang] || lang;
|
|
127
149
|
}, []);
|
|
128
|
-
// Add a new state to track token restoration
|
|
129
|
-
const [tokenReady, setTokenReady] = useState(false);
|
|
130
150
|
|
|
131
|
-
//
|
|
132
|
-
const
|
|
151
|
+
// Helper to apply language preference from user/server
|
|
152
|
+
const applyLanguagePreference = useCallback(async user => {
|
|
153
|
+
const userLanguage = user?.language;
|
|
154
|
+
if (!userLanguage || !storage) return;
|
|
155
|
+
try {
|
|
156
|
+
const serverLang = normalizeLanguageCode(userLanguage) || userLanguage;
|
|
157
|
+
await storage.setItem(keys.language, serverLang);
|
|
158
|
+
setCurrentLanguage(serverLang);
|
|
159
|
+
} catch (e) {
|
|
160
|
+
if (__DEV__) {
|
|
161
|
+
console.warn('Failed to apply server language preference', e);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}, [storage, keys.language, normalizeLanguageCode]);
|
|
165
|
+
|
|
166
|
+
// Helper to map server sessions to client sessions
|
|
167
|
+
const mapServerSessionsToClient = useCallback((serverSessions, fallbackUserId) => {
|
|
168
|
+
return serverSessions.map(s => ({
|
|
169
|
+
sessionId: s.sessionId,
|
|
170
|
+
deviceId: s.deviceId,
|
|
171
|
+
expiresAt: s.expiresAt || new Date().toISOString(),
|
|
172
|
+
lastActive: s.lastActive || new Date().toISOString(),
|
|
173
|
+
userId: s.userId || fallbackUserId
|
|
174
|
+
}));
|
|
175
|
+
}, []);
|
|
176
|
+
|
|
177
|
+
// Token ready state - start optimistically so children render immediately
|
|
178
|
+
const [tokenReady, setTokenReady] = useState(true);
|
|
133
179
|
|
|
134
|
-
// Clear all storage
|
|
180
|
+
// Clear all storage
|
|
135
181
|
const clearAllStorage = useCallback(async () => {
|
|
136
182
|
if (!storage) return;
|
|
137
183
|
try {
|
|
138
184
|
await storage.removeItem(keys.activeSessionId);
|
|
139
185
|
} catch (err) {
|
|
140
|
-
|
|
186
|
+
if (__DEV__) {
|
|
187
|
+
console.error('Clear storage error:', err);
|
|
188
|
+
}
|
|
189
|
+
onError?.({
|
|
190
|
+
message: 'Failed to clear storage',
|
|
191
|
+
code: 'STORAGE_ERROR',
|
|
192
|
+
status: 500
|
|
193
|
+
});
|
|
141
194
|
}
|
|
142
|
-
}, [storage, keys]);
|
|
195
|
+
}, [storage, keys, onError]);
|
|
143
196
|
|
|
144
197
|
// Initialize storage
|
|
145
198
|
useEffect(() => {
|
|
@@ -148,25 +201,28 @@ export const OxyProvider = ({
|
|
|
148
201
|
const platformStorage = await getStorage();
|
|
149
202
|
setStorage(platformStorage);
|
|
150
203
|
} catch (error) {
|
|
151
|
-
|
|
204
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to initialize storage';
|
|
152
205
|
useAuthStore.setState({
|
|
153
|
-
error:
|
|
206
|
+
error: errorMessage
|
|
207
|
+
});
|
|
208
|
+
onError?.({
|
|
209
|
+
message: errorMessage,
|
|
210
|
+
code: 'STORAGE_INIT_ERROR',
|
|
211
|
+
status: 500
|
|
154
212
|
});
|
|
155
213
|
}
|
|
156
214
|
};
|
|
157
215
|
initStorage();
|
|
158
|
-
}, []);
|
|
216
|
+
}, [onError]);
|
|
159
217
|
|
|
160
218
|
// Initialize authentication state
|
|
219
|
+
// Note: We don't set isLoading during initialization to avoid showing spinners
|
|
220
|
+
// Children render immediately and can check isTokenReady/isAuthenticated themselves
|
|
161
221
|
useEffect(() => {
|
|
162
222
|
const initAuth = async () => {
|
|
163
223
|
if (!storage) return;
|
|
164
|
-
|
|
165
|
-
isLoading: true
|
|
166
|
-
});
|
|
224
|
+
// Don't set isLoading during initialization - let it happen in background
|
|
167
225
|
try {
|
|
168
|
-
setTokenReady(false);
|
|
169
|
-
|
|
170
226
|
// Load saved language preference
|
|
171
227
|
const savedLanguageRaw = await storage.getItem(keys.language);
|
|
172
228
|
const savedLanguage = normalizeLanguageCode(savedLanguageRaw) || savedLanguageRaw;
|
|
@@ -191,67 +247,49 @@ export const OxyProvider = ({
|
|
|
191
247
|
username: fullUser.username,
|
|
192
248
|
avatar: fullUser.avatar
|
|
193
249
|
});
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
250
|
+
await applyLanguagePreference(fullUser);
|
|
251
|
+
|
|
252
|
+
// Get all device sessions to support multiple accounts
|
|
253
|
+
try {
|
|
254
|
+
const deviceSessions = await oxyServices.getDeviceSessions(storedActiveSessionId);
|
|
255
|
+
const allDeviceSessions = deviceSessions.map(ds => ({
|
|
256
|
+
sessionId: ds.sessionId,
|
|
257
|
+
deviceId: ds.deviceId,
|
|
258
|
+
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
259
|
+
lastActive: ds.lastActive || new Date().toISOString(),
|
|
260
|
+
userId: ds.user?.id || ds.userId || fullUser.id
|
|
261
|
+
}));
|
|
262
|
+
setSessions(allDeviceSessions);
|
|
263
|
+
} catch (e) {
|
|
264
|
+
// Fallback to user sessions
|
|
265
|
+
if (__DEV__) {
|
|
266
|
+
console.warn('Failed to get device sessions on init, falling back to user sessions:', e);
|
|
202
267
|
}
|
|
268
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
269
|
+
setSessions(mapServerSessionsToClient(serverSessions, fullUser.id));
|
|
203
270
|
}
|
|
204
|
-
const serverSessions = await oxyServices.getSessionsBySessionId(storedActiveSessionId);
|
|
205
|
-
const clientSessions = serverSessions.map(s => ({
|
|
206
|
-
sessionId: s.sessionId,
|
|
207
|
-
deviceId: s.deviceId,
|
|
208
|
-
expiresAt: s.expiresAt || new Date().toISOString(),
|
|
209
|
-
lastActive: s.lastActive || new Date().toISOString(),
|
|
210
|
-
userId: s.userId || fullUser.id
|
|
211
|
-
}));
|
|
212
|
-
setSessions(clientSessions);
|
|
213
271
|
onAuthStateChange?.(fullUser);
|
|
214
272
|
} else {
|
|
215
273
|
await clearAllStorage();
|
|
216
274
|
}
|
|
217
275
|
} catch (e) {
|
|
218
|
-
|
|
276
|
+
if (__DEV__) {
|
|
277
|
+
console.error('Session validation error', e);
|
|
278
|
+
}
|
|
219
279
|
await clearAllStorage();
|
|
220
280
|
}
|
|
221
281
|
}
|
|
222
282
|
setTokenReady(true);
|
|
223
283
|
} catch (e) {
|
|
224
|
-
|
|
284
|
+
if (__DEV__) {
|
|
285
|
+
console.error('Auth init error', e);
|
|
286
|
+
}
|
|
225
287
|
await clearAllStorage();
|
|
226
|
-
|
|
227
|
-
useAuthStore.setState({
|
|
228
|
-
isLoading: false
|
|
229
|
-
});
|
|
288
|
+
setTokenReady(true);
|
|
230
289
|
}
|
|
231
290
|
};
|
|
232
291
|
initAuth();
|
|
233
|
-
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage]);
|
|
234
|
-
|
|
235
|
-
// Remove invalid session - refresh sessions from backend
|
|
236
|
-
const removeInvalidSession = useCallback(async sessionId => {
|
|
237
|
-
// Remove from local state
|
|
238
|
-
const filteredSessions = sessions.filter(s => s.sessionId !== sessionId);
|
|
239
|
-
setSessions(filteredSessions);
|
|
240
|
-
|
|
241
|
-
// If there are other sessions, switch to the first one
|
|
242
|
-
if (filteredSessions.length > 0) {
|
|
243
|
-
await switchToSession(filteredSessions[0].sessionId);
|
|
244
|
-
} else {
|
|
245
|
-
// No valid sessions left
|
|
246
|
-
setActiveSessionId(null);
|
|
247
|
-
logoutStore();
|
|
248
|
-
setMinimalUser(null);
|
|
249
|
-
await storage?.removeItem(keys.activeSessionId);
|
|
250
|
-
if (onAuthStateChange) {
|
|
251
|
-
onAuthStateChange(null);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
}, [sessions, storage, keys, onAuthStateChange, logoutStore]);
|
|
292
|
+
}, [storage, oxyServices, keys, onAuthStateChange, loginSuccess, clearAllStorage, applyLanguagePreference, mapServerSessionsToClient]);
|
|
255
293
|
|
|
256
294
|
// Save active session ID to storage (only session ID, no user data)
|
|
257
295
|
const saveActiveSessionId = useCallback(async sessionId => {
|
|
@@ -262,15 +300,23 @@ export const OxyProvider = ({
|
|
|
262
300
|
// Switch to a different session
|
|
263
301
|
const switchToSession = useCallback(async sessionId => {
|
|
264
302
|
try {
|
|
265
|
-
|
|
266
|
-
|
|
303
|
+
// Don't set isLoading - session switches should happen silently in background
|
|
304
|
+
// Validate session first before attempting to switch
|
|
305
|
+
const validation = await oxyServices.validateSession(sessionId, {
|
|
306
|
+
useHeaderValidation: true
|
|
267
307
|
});
|
|
308
|
+
if (!validation.valid) {
|
|
309
|
+
// Session is invalid, remove it from the sessions list
|
|
310
|
+
setSessions(prevSessions => prevSessions.filter(s => s.sessionId !== sessionId));
|
|
311
|
+
throw new Error('Session is invalid or expired');
|
|
312
|
+
}
|
|
268
313
|
|
|
269
314
|
// Get access token for this session
|
|
270
315
|
await oxyServices.getTokenBySession(sessionId);
|
|
316
|
+
setTokenReady(true);
|
|
271
317
|
|
|
272
|
-
// Load full user data
|
|
273
|
-
const fullUser = await oxyServices.getUserBySession(sessionId);
|
|
318
|
+
// Load full user data - use user from validation if available, otherwise fetch
|
|
319
|
+
const fullUser = validation.user || (await oxyServices.getUserBySession(sessionId));
|
|
274
320
|
setActiveSessionId(sessionId);
|
|
275
321
|
loginSuccess(fullUser);
|
|
276
322
|
setMinimalUser({
|
|
@@ -279,30 +325,88 @@ export const OxyProvider = ({
|
|
|
279
325
|
avatar: fullUser.avatar
|
|
280
326
|
});
|
|
281
327
|
await saveActiveSessionId(sessionId);
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
328
|
+
await applyLanguagePreference(fullUser);
|
|
329
|
+
|
|
330
|
+
// Refresh all device sessions after switching
|
|
331
|
+
// Preserve existing sessions from other users to avoid losing accounts
|
|
332
|
+
try {
|
|
333
|
+
const deviceSessions = await oxyServices.getDeviceSessions(sessionId);
|
|
334
|
+
const allDeviceSessions = deviceSessions.map(ds => ({
|
|
335
|
+
sessionId: ds.sessionId,
|
|
336
|
+
deviceId: ds.deviceId,
|
|
337
|
+
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
338
|
+
lastActive: ds.lastActive || new Date().toISOString(),
|
|
339
|
+
userId: ds.user?.id || ds.userId || fullUser.id
|
|
340
|
+
}));
|
|
341
|
+
// Merge with existing sessions to preserve other accounts
|
|
342
|
+
setSessions(prevSessions => {
|
|
343
|
+
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
344
|
+
const newSessions = allDeviceSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
345
|
+
// Combine existing sessions with new ones, prioritizing new data for existing sessions
|
|
346
|
+
const sessionMap = new Map(prevSessions.map(s => [s.sessionId, s]));
|
|
347
|
+
allDeviceSessions.forEach(s => sessionMap.set(s.sessionId, s));
|
|
348
|
+
return Array.from(sessionMap.values());
|
|
349
|
+
});
|
|
350
|
+
} catch (error) {
|
|
351
|
+
// Fallback to user sessions - merge with existing to preserve other accounts
|
|
352
|
+
if (__DEV__) {
|
|
353
|
+
console.warn('Failed to get device sessions after switch, falling back to user sessions:', error);
|
|
290
354
|
}
|
|
355
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(sessionId);
|
|
356
|
+
const userSessions = mapServerSessionsToClient(serverSessions, fullUser.id);
|
|
357
|
+
// Merge with existing sessions to preserve other accounts
|
|
358
|
+
setSessions(prevSessions => {
|
|
359
|
+
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
360
|
+
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
361
|
+
// Combine existing sessions with new ones, prioritizing new data for existing sessions
|
|
362
|
+
const sessionMap = new Map(prevSessions.map(s => [s.sessionId, s]));
|
|
363
|
+
userSessions.forEach(s => sessionMap.set(s.sessionId, s));
|
|
364
|
+
return Array.from(sessionMap.values());
|
|
365
|
+
});
|
|
291
366
|
}
|
|
292
|
-
|
|
293
|
-
onAuthStateChange(fullUser);
|
|
294
|
-
}
|
|
367
|
+
onAuthStateChange?.(fullUser);
|
|
295
368
|
} catch (error) {
|
|
296
|
-
|
|
369
|
+
// Check if the error is due to invalid/expired session
|
|
370
|
+
const isInvalidSession = error?.response?.status === 401 || error?.message?.includes('Invalid or expired session') || error?.message?.includes('Session is invalid');
|
|
371
|
+
if (isInvalidSession) {
|
|
372
|
+
// Remove invalid session from the sessions list
|
|
373
|
+
setSessions(prevSessions => prevSessions.filter(s => s.sessionId !== sessionId));
|
|
374
|
+
|
|
375
|
+
// If this was the active session, try to switch to another valid session
|
|
376
|
+
if (sessionId === activeSessionId && sessions.length > 1) {
|
|
377
|
+
const otherSessions = sessions.filter(s => s.sessionId !== sessionId);
|
|
378
|
+
for (const otherSession of otherSessions) {
|
|
379
|
+
try {
|
|
380
|
+
const otherValidation = await oxyServices.validateSession(otherSession.sessionId, {
|
|
381
|
+
useHeaderValidation: true
|
|
382
|
+
});
|
|
383
|
+
if (otherValidation.valid) {
|
|
384
|
+
await switchToSession(otherSession.sessionId);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
// Continue to next session
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to switch session';
|
|
395
|
+
if (__DEV__) {
|
|
396
|
+
console.error('Switch session error:', error);
|
|
397
|
+
}
|
|
297
398
|
useAuthStore.setState({
|
|
298
|
-
error:
|
|
399
|
+
error: errorMessage
|
|
299
400
|
});
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
401
|
+
onError?.({
|
|
402
|
+
message: errorMessage,
|
|
403
|
+
code: isInvalidSession ? 'INVALID_SESSION' : 'SESSION_SWITCH_ERROR',
|
|
404
|
+
status: isInvalidSession ? 401 : 500
|
|
303
405
|
});
|
|
406
|
+
setTokenReady(false);
|
|
407
|
+
throw error; // Re-throw so calling code can handle it
|
|
304
408
|
}
|
|
305
|
-
}, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId]);
|
|
409
|
+
}, [oxyServices, onAuthStateChange, loginSuccess, saveActiveSessionId, applyLanguagePreference, mapServerSessionsToClient, onError, activeSessionId, sessions]);
|
|
306
410
|
|
|
307
411
|
// Login method - only store session ID, retrieve data from backend
|
|
308
412
|
const login = useCallback(async (username, password, deviceName) => {
|
|
@@ -312,59 +416,100 @@ export const OxyProvider = ({
|
|
|
312
416
|
error: null
|
|
313
417
|
});
|
|
314
418
|
try {
|
|
315
|
-
// Get device fingerprint for enhanced device identification
|
|
316
419
|
const deviceFingerprint = DeviceManager.getDeviceFingerprint();
|
|
317
|
-
|
|
318
|
-
// Get or generate persistent device info
|
|
319
420
|
const deviceInfo = await DeviceManager.getDeviceInfo();
|
|
320
|
-
console.log('Auth - Using device fingerprint:', deviceFingerprint);
|
|
321
|
-
console.log('Auth - Using device ID:', deviceInfo.deviceId);
|
|
322
421
|
const response = await oxyServices.signIn(username, password, deviceName || deviceInfo.deviceName || DeviceManager.getDefaultDeviceName(), deviceFingerprint);
|
|
323
422
|
|
|
324
423
|
// Handle MFA requirement
|
|
325
|
-
if (response && response.mfaRequired) {
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
throw
|
|
424
|
+
if (response && 'mfaRequired' in response && response.mfaRequired) {
|
|
425
|
+
const mfaError = new Error('Multi-factor authentication required');
|
|
426
|
+
mfaError.code = 'MFA_REQUIRED';
|
|
427
|
+
mfaError.mfaToken = response.mfaToken;
|
|
428
|
+
mfaError.expiresAt = response.expiresAt;
|
|
429
|
+
throw mfaError;
|
|
331
430
|
}
|
|
431
|
+
const sessionResponse = response;
|
|
432
|
+
await oxyServices.getTokenBySession(sessionResponse.sessionId);
|
|
433
|
+
const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
332
434
|
|
|
333
|
-
//
|
|
334
|
-
|
|
335
|
-
|
|
435
|
+
// Get all device sessions to check for duplicates BEFORE setting the new session as active
|
|
436
|
+
// This returns all sessions on the device, not just for the current user
|
|
437
|
+
let allDeviceSessions = [];
|
|
438
|
+
try {
|
|
439
|
+
const deviceSessions = await oxyServices.getDeviceSessions(sessionResponse.sessionId);
|
|
440
|
+
|
|
441
|
+
// Map device sessions to client format
|
|
442
|
+
// Device sessions include user info, so we can map them directly
|
|
443
|
+
allDeviceSessions = deviceSessions.map(ds => ({
|
|
444
|
+
sessionId: ds.sessionId,
|
|
445
|
+
deviceId: ds.deviceId || sessionResponse.deviceId,
|
|
446
|
+
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
447
|
+
lastActive: ds.lastActive || new Date().toISOString(),
|
|
448
|
+
userId: ds.user?.id || ds.userId || ds.user?._id?.toString() || fullUser.id
|
|
449
|
+
}));
|
|
450
|
+
} catch (error) {
|
|
451
|
+
// Fallback to user sessions if device sessions fail
|
|
452
|
+
if (__DEV__) {
|
|
453
|
+
console.warn('Failed to get device sessions, falling back to user sessions:', error);
|
|
454
|
+
}
|
|
455
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(sessionResponse.sessionId);
|
|
456
|
+
const userSessions = mapServerSessionsToClient(serverSessions, fullUser.id);
|
|
336
457
|
|
|
337
|
-
|
|
338
|
-
|
|
458
|
+
// Merge with existing sessions to preserve other accounts
|
|
459
|
+
const existingSessionIds = new Set((sessions || []).map(s => s.sessionId));
|
|
460
|
+
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
461
|
+
allDeviceSessions = [...(sessions || []), ...newSessions];
|
|
462
|
+
}
|
|
339
463
|
|
|
340
|
-
//
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
464
|
+
// Check if this user is already signed in with another session on this device
|
|
465
|
+
// Compare userId as string to handle both string and ObjectId formats
|
|
466
|
+
const userUserId = fullUser.id?.toString();
|
|
467
|
+
const existingSession = allDeviceSessions.find(s => s.userId?.toString() === userUserId && s.sessionId !== sessionResponse.sessionId);
|
|
468
|
+
if (existingSession) {
|
|
469
|
+
// User is already signed in on this device, switch to existing session instead
|
|
470
|
+
// Logout the newly created session to clean it up
|
|
471
|
+
try {
|
|
472
|
+
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
473
|
+
} catch (logoutError) {
|
|
474
|
+
if (__DEV__) {
|
|
475
|
+
console.warn('Failed to logout duplicate session:', logoutError);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// Switch to the existing session
|
|
480
|
+
await switchToSession(existingSession.sessionId);
|
|
481
|
+
loginSuccess(fullUser);
|
|
482
|
+
setMinimalUser(sessionResponse.user);
|
|
483
|
+
|
|
484
|
+
// Update sessions list (excluding the duplicate we just created)
|
|
485
|
+
setSessions(allDeviceSessions.filter(s => s.sessionId !== sessionResponse.sessionId));
|
|
486
|
+
onAuthStateChange?.(fullUser);
|
|
487
|
+
return fullUser;
|
|
357
488
|
}
|
|
489
|
+
|
|
490
|
+
// No duplicate found, proceed with the new session
|
|
491
|
+
setActiveSessionId(sessionResponse.sessionId);
|
|
492
|
+
await saveActiveSessionId(sessionResponse.sessionId);
|
|
493
|
+
loginSuccess(fullUser);
|
|
494
|
+
setMinimalUser(sessionResponse.user);
|
|
495
|
+
setSessions(allDeviceSessions);
|
|
496
|
+
onAuthStateChange?.(fullUser);
|
|
358
497
|
return fullUser;
|
|
359
498
|
} catch (error) {
|
|
360
|
-
|
|
499
|
+
const errorMessage = error instanceof Error ? error.message : 'Login failed';
|
|
500
|
+
loginFailure(errorMessage);
|
|
501
|
+
onError?.({
|
|
502
|
+
message: errorMessage,
|
|
503
|
+
code: 'LOGIN_ERROR',
|
|
504
|
+
status: 401
|
|
505
|
+
});
|
|
361
506
|
throw error;
|
|
362
507
|
} finally {
|
|
363
508
|
useAuthStore.setState({
|
|
364
509
|
isLoading: false
|
|
365
510
|
});
|
|
366
511
|
}
|
|
367
|
-
}, [storage, oxyServices, saveActiveSessionId, loginSuccess,
|
|
512
|
+
}, [storage, oxyServices, saveActiveSessionId, loginSuccess, onAuthStateChange, loginFailure, mapServerSessionsToClient, onError, sessions, switchToSession]);
|
|
368
513
|
|
|
369
514
|
// Logout method
|
|
370
515
|
const logout = useCallback(async targetSessionId => {
|
|
@@ -394,74 +539,59 @@ export const OxyProvider = ({
|
|
|
394
539
|
}
|
|
395
540
|
}
|
|
396
541
|
} catch (error) {
|
|
397
|
-
|
|
542
|
+
const errorMessage = error instanceof Error ? error.message : 'Logout failed';
|
|
543
|
+
if (__DEV__) {
|
|
544
|
+
console.error('Logout error:', error);
|
|
545
|
+
}
|
|
398
546
|
useAuthStore.setState({
|
|
399
|
-
error:
|
|
547
|
+
error: errorMessage
|
|
548
|
+
});
|
|
549
|
+
onError?.({
|
|
550
|
+
message: errorMessage,
|
|
551
|
+
code: 'LOGOUT_ERROR',
|
|
552
|
+
status: 500
|
|
400
553
|
});
|
|
401
554
|
}
|
|
402
|
-
}, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore,
|
|
555
|
+
}, [activeSessionId, oxyServices, sessions, switchToSession, logoutStore, storage, keys.activeSessionId, onAuthStateChange, onError]);
|
|
403
556
|
|
|
404
557
|
// Logout all sessions
|
|
405
558
|
const logoutAll = useCallback(async () => {
|
|
406
|
-
console.log('logoutAll called with activeSessionId:', activeSessionId);
|
|
407
559
|
if (!activeSessionId) {
|
|
408
|
-
|
|
560
|
+
const error = new Error('No active session found');
|
|
409
561
|
useAuthStore.setState({
|
|
410
|
-
error:
|
|
562
|
+
error: error.message
|
|
411
563
|
});
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
useAuthStore.setState({
|
|
417
|
-
error: 'Service not available'
|
|
564
|
+
onError?.({
|
|
565
|
+
message: error.message,
|
|
566
|
+
code: 'NO_SESSION_ERROR',
|
|
567
|
+
status: 404
|
|
418
568
|
});
|
|
419
|
-
throw
|
|
569
|
+
throw error;
|
|
420
570
|
}
|
|
421
571
|
try {
|
|
422
|
-
console.log('Calling oxyServices.logoutAllSessions with sessionId:', activeSessionId);
|
|
423
572
|
await oxyServices.logoutAllSessions(activeSessionId);
|
|
424
|
-
console.log('logoutAllSessions completed successfully');
|
|
425
|
-
|
|
426
|
-
// Clear all local data
|
|
427
573
|
setSessions([]);
|
|
428
574
|
setActiveSessionId(null);
|
|
429
575
|
logoutStore();
|
|
430
576
|
setMinimalUser(null);
|
|
431
577
|
await clearAllStorage();
|
|
432
|
-
|
|
433
|
-
if (onAuthStateChange) {
|
|
434
|
-
onAuthStateChange(null);
|
|
435
|
-
console.log('Auth state change callback called');
|
|
436
|
-
}
|
|
578
|
+
onAuthStateChange?.(null);
|
|
437
579
|
} catch (error) {
|
|
438
|
-
|
|
580
|
+
const errorMessage = error instanceof Error ? error.message : 'Logout all failed';
|
|
439
581
|
useAuthStore.setState({
|
|
440
|
-
error:
|
|
582
|
+
error: errorMessage
|
|
583
|
+
});
|
|
584
|
+
onError?.({
|
|
585
|
+
message: errorMessage,
|
|
586
|
+
code: 'LOGOUT_ALL_ERROR',
|
|
587
|
+
status: 500
|
|
441
588
|
});
|
|
442
589
|
throw error;
|
|
443
590
|
}
|
|
444
|
-
}, [activeSessionId, oxyServices, logoutStore,
|
|
591
|
+
}, [activeSessionId, oxyServices, logoutStore, clearAllStorage, onAuthStateChange, onError]);
|
|
445
592
|
|
|
446
|
-
//
|
|
447
|
-
|
|
448
|
-
const restoreToken = async () => {
|
|
449
|
-
if (activeSessionId && oxyServices) {
|
|
450
|
-
try {
|
|
451
|
-
await oxyServices.getTokenBySession(activeSessionId);
|
|
452
|
-
setTokenReady(true);
|
|
453
|
-
} catch (err) {
|
|
454
|
-
// If token restoration fails, force logout
|
|
455
|
-
await logout();
|
|
456
|
-
setTokenReady(false);
|
|
457
|
-
}
|
|
458
|
-
} else {
|
|
459
|
-
setTokenReady(true); // No session, so token is not needed
|
|
460
|
-
}
|
|
461
|
-
};
|
|
462
|
-
restoreToken();
|
|
463
|
-
// Only run when activeSessionId or oxyServices changes
|
|
464
|
-
}, [activeSessionId, oxyServices, logout]);
|
|
593
|
+
// Token restoration is handled in initAuth and switchToSession
|
|
594
|
+
// No separate effect needed - children render immediately with isTokenReady available
|
|
465
595
|
|
|
466
596
|
// Sign up method
|
|
467
597
|
const signUp = useCallback(async (username, email, password) => {
|
|
@@ -471,23 +601,24 @@ export const OxyProvider = ({
|
|
|
471
601
|
error: null
|
|
472
602
|
});
|
|
473
603
|
try {
|
|
474
|
-
|
|
475
|
-
const response = await oxyServices.signUp(username, email, password);
|
|
476
|
-
console.log('SignUp successful:', response);
|
|
477
|
-
|
|
478
|
-
// Now log the user in to create a session
|
|
479
|
-
// This will handle the session creation and device registration
|
|
604
|
+
await oxyServices.signUp(username, email, password);
|
|
480
605
|
const user = await login(username, password);
|
|
481
606
|
return user;
|
|
482
607
|
} catch (error) {
|
|
483
|
-
|
|
608
|
+
const errorMessage = error instanceof Error ? error.message : 'Sign up failed';
|
|
609
|
+
loginFailure(errorMessage);
|
|
610
|
+
onError?.({
|
|
611
|
+
message: errorMessage,
|
|
612
|
+
code: 'SIGNUP_ERROR',
|
|
613
|
+
status: 400
|
|
614
|
+
});
|
|
484
615
|
throw error;
|
|
485
616
|
} finally {
|
|
486
617
|
useAuthStore.setState({
|
|
487
618
|
isLoading: false
|
|
488
619
|
});
|
|
489
620
|
}
|
|
490
|
-
}, [storage, oxyServices, login, loginFailure]);
|
|
621
|
+
}, [storage, oxyServices, login, loginFailure, onError]);
|
|
491
622
|
|
|
492
623
|
// Complete MFA login by verifying TOTP
|
|
493
624
|
const completeMfaLogin = useCallback(async (mfaToken, code) => {
|
|
@@ -512,116 +643,127 @@ export const OxyProvider = ({
|
|
|
512
643
|
username: fullUser.username,
|
|
513
644
|
avatar: fullUser.avatar
|
|
514
645
|
});
|
|
515
|
-
|
|
516
|
-
if (fullUser?.language) {
|
|
517
|
-
try {
|
|
518
|
-
const serverLang = normalizeLanguageCode(fullUser.language) || fullUser.language;
|
|
519
|
-
await storage.setItem(keys.language, serverLang);
|
|
520
|
-
setCurrentLanguage(serverLang);
|
|
521
|
-
} catch (e) {
|
|
522
|
-
console.warn('Failed to apply server language on MFA login', e);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
646
|
+
await applyLanguagePreference(fullUser);
|
|
525
647
|
|
|
526
|
-
//
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
} catch (e) {
|
|
542
|
-
console.warn('Failed to apply server language on MFA login', e);
|
|
648
|
+
// Get all device sessions to support multiple accounts
|
|
649
|
+
try {
|
|
650
|
+
const deviceSessions = await oxyServices.getDeviceSessions(response.sessionId);
|
|
651
|
+
const allDeviceSessions = deviceSessions.map(ds => ({
|
|
652
|
+
sessionId: ds.sessionId,
|
|
653
|
+
deviceId: ds.deviceId,
|
|
654
|
+
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
655
|
+
lastActive: ds.lastActive || new Date().toISOString(),
|
|
656
|
+
userId: ds.user?.id || ds.userId || fullUser.id
|
|
657
|
+
}));
|
|
658
|
+
setSessions(allDeviceSessions);
|
|
659
|
+
} catch (error) {
|
|
660
|
+
// Fallback to user sessions if device sessions fail
|
|
661
|
+
if (__DEV__) {
|
|
662
|
+
console.warn('Failed to get device sessions for MFA, falling back to user sessions:', error);
|
|
543
663
|
}
|
|
664
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(response.sessionId);
|
|
665
|
+
const userSessions = mapServerSessionsToClient(serverSessions, fullUser.id);
|
|
666
|
+
|
|
667
|
+
// Merge with existing sessions to preserve other accounts
|
|
668
|
+
setSessions(prevSessions => {
|
|
669
|
+
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
670
|
+
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
671
|
+
return [...prevSessions, ...newSessions];
|
|
672
|
+
});
|
|
544
673
|
}
|
|
545
|
-
|
|
674
|
+
onAuthStateChange?.(fullUser);
|
|
546
675
|
return fullUser;
|
|
547
676
|
} catch (error) {
|
|
548
|
-
|
|
677
|
+
const errorMessage = error instanceof Error ? error.message : 'MFA verification failed';
|
|
678
|
+
loginFailure(errorMessage);
|
|
679
|
+
onError?.({
|
|
680
|
+
message: errorMessage,
|
|
681
|
+
code: 'MFA_ERROR',
|
|
682
|
+
status: 401
|
|
683
|
+
});
|
|
549
684
|
throw error;
|
|
550
685
|
} finally {
|
|
551
686
|
useAuthStore.setState({
|
|
552
687
|
isLoading: false
|
|
553
688
|
});
|
|
554
689
|
}
|
|
555
|
-
}, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange]);
|
|
690
|
+
}, [storage, oxyServices, loginSuccess, loginFailure, saveActiveSessionId, onAuthStateChange, applyLanguagePreference, onError]);
|
|
556
691
|
|
|
557
|
-
// Switch session method
|
|
692
|
+
// Switch session method (wrapper for consistency)
|
|
558
693
|
const switchSession = useCallback(async sessionId => {
|
|
559
694
|
await switchToSession(sessionId);
|
|
560
695
|
}, [switchToSession]);
|
|
561
696
|
|
|
562
|
-
// Remove session method
|
|
697
|
+
// Remove session method (wrapper for consistency)
|
|
563
698
|
const removeSession = useCallback(async sessionId => {
|
|
564
699
|
await logout(sessionId);
|
|
565
700
|
}, [logout]);
|
|
566
701
|
|
|
567
702
|
// Refresh sessions method
|
|
568
703
|
const refreshSessions = useCallback(async () => {
|
|
569
|
-
|
|
570
|
-
if (!activeSessionId) {
|
|
571
|
-
console.log('refreshSessions: No activeSessionId, returning');
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
704
|
+
if (!activeSessionId) return;
|
|
574
705
|
try {
|
|
575
|
-
|
|
576
|
-
const
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
expiresAt: serverSession.expiresAt || new Date().toISOString(),
|
|
584
|
-
lastActive: serverSession.lastActive || new Date().toISOString(),
|
|
585
|
-
userId: serverSession.userId || user?.id
|
|
706
|
+
// Get all device sessions to support multiple accounts
|
|
707
|
+
const deviceSessions = await oxyServices.getDeviceSessions(activeSessionId);
|
|
708
|
+
const allDeviceSessions = deviceSessions.map(ds => ({
|
|
709
|
+
sessionId: ds.sessionId,
|
|
710
|
+
deviceId: ds.deviceId,
|
|
711
|
+
expiresAt: ds.expiresAt || new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
|
712
|
+
lastActive: ds.lastActive || new Date().toISOString(),
|
|
713
|
+
userId: ds.user?.id || ds.userId || user?.id
|
|
586
714
|
}));
|
|
587
|
-
|
|
588
|
-
setSessions(updatedSessions);
|
|
589
|
-
console.log('refreshSessions: Sessions updated in state');
|
|
715
|
+
setSessions(allDeviceSessions);
|
|
590
716
|
} catch (error) {
|
|
591
|
-
|
|
717
|
+
// Fallback to user sessions if device sessions fail
|
|
718
|
+
// Merge with existing sessions to preserve other accounts
|
|
719
|
+
if (__DEV__) {
|
|
720
|
+
console.warn('Failed to refresh device sessions, falling back to user sessions:', error);
|
|
721
|
+
}
|
|
722
|
+
try {
|
|
723
|
+
const serverSessions = await oxyServices.getSessionsBySessionId(activeSessionId);
|
|
724
|
+
const userSessions = mapServerSessionsToClient(serverSessions, user?.id);
|
|
725
|
+
// Merge with existing sessions to preserve other accounts
|
|
726
|
+
setSessions(prevSessions => {
|
|
727
|
+
const existingSessionIds = new Set(prevSessions.map(s => s.sessionId));
|
|
728
|
+
const newSessions = userSessions.filter(s => !existingSessionIds.has(s.sessionId));
|
|
729
|
+
// Combine existing sessions with new ones, prioritizing new data for existing sessions
|
|
730
|
+
const sessionMap = new Map(prevSessions.map(s => [s.sessionId, s]));
|
|
731
|
+
userSessions.forEach(s => sessionMap.set(s.sessionId, s));
|
|
732
|
+
return Array.from(sessionMap.values());
|
|
733
|
+
});
|
|
734
|
+
} catch (fallbackError) {
|
|
735
|
+
if (__DEV__) {
|
|
736
|
+
console.error('Refresh sessions error:', fallbackError);
|
|
737
|
+
}
|
|
592
738
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
continue; // Try next session
|
|
739
|
+
// If the current session is invalid, try to find another valid session
|
|
740
|
+
if (sessions.length > 1) {
|
|
741
|
+
const otherSessions = sessions.filter(s => s.sessionId !== activeSessionId);
|
|
742
|
+
for (const session of otherSessions) {
|
|
743
|
+
try {
|
|
744
|
+
const validation = await oxyServices.validateSession(session.sessionId, {
|
|
745
|
+
useHeaderValidation: true
|
|
746
|
+
});
|
|
747
|
+
if (validation.valid) {
|
|
748
|
+
await switchToSession(session.sessionId);
|
|
749
|
+
return;
|
|
750
|
+
}
|
|
751
|
+
} catch {
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
609
754
|
}
|
|
610
755
|
}
|
|
611
|
-
}
|
|
612
756
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (onAuthStateChange) {
|
|
621
|
-
onAuthStateChange(null);
|
|
757
|
+
// No valid sessions found, clear all
|
|
758
|
+
setSessions([]);
|
|
759
|
+
setActiveSessionId(null);
|
|
760
|
+
logoutStore();
|
|
761
|
+
setMinimalUser(null);
|
|
762
|
+
await clearAllStorage();
|
|
763
|
+
onAuthStateChange?.(null);
|
|
622
764
|
}
|
|
623
765
|
}
|
|
624
|
-
}, [activeSessionId, oxyServices, user?.id, sessions, switchToSession, logoutStore,
|
|
766
|
+
}, [activeSessionId, oxyServices, user?.id, sessions, switchToSession, logoutStore, clearAllStorage, onAuthStateChange, mapServerSessionsToClient]);
|
|
625
767
|
|
|
626
768
|
// Device management methods
|
|
627
769
|
const getDeviceSessions = useCallback(async () => {
|
|
@@ -629,58 +771,67 @@ export const OxyProvider = ({
|
|
|
629
771
|
try {
|
|
630
772
|
return await oxyServices.getDeviceSessions(activeSessionId);
|
|
631
773
|
} catch (error) {
|
|
632
|
-
|
|
774
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to get device sessions';
|
|
775
|
+
onError?.({
|
|
776
|
+
message: errorMessage,
|
|
777
|
+
code: 'GET_DEVICE_SESSIONS_ERROR',
|
|
778
|
+
status: 500
|
|
779
|
+
});
|
|
633
780
|
throw error;
|
|
634
781
|
}
|
|
635
|
-
}, [activeSessionId, oxyServices]);
|
|
782
|
+
}, [activeSessionId, oxyServices, onError]);
|
|
636
783
|
const logoutAllDeviceSessions = useCallback(async () => {
|
|
637
784
|
if (!activeSessionId) throw new Error('No active session');
|
|
638
785
|
try {
|
|
639
786
|
await oxyServices.logoutAllDeviceSessions(activeSessionId);
|
|
640
|
-
|
|
641
|
-
// Clear all local sessions since we logged out from all devices
|
|
642
787
|
setSessions([]);
|
|
643
788
|
setActiveSessionId(null);
|
|
644
789
|
logoutStore();
|
|
645
790
|
setMinimalUser(null);
|
|
646
791
|
await clearAllStorage();
|
|
647
|
-
|
|
648
|
-
onAuthStateChange(null);
|
|
649
|
-
}
|
|
792
|
+
onAuthStateChange?.(null);
|
|
650
793
|
} catch (error) {
|
|
651
|
-
|
|
794
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to logout all device sessions';
|
|
795
|
+
onError?.({
|
|
796
|
+
message: errorMessage,
|
|
797
|
+
code: 'LOGOUT_ALL_DEVICES_ERROR',
|
|
798
|
+
status: 500
|
|
799
|
+
});
|
|
652
800
|
throw error;
|
|
653
801
|
}
|
|
654
|
-
}, [activeSessionId, oxyServices, logoutStore,
|
|
802
|
+
}, [activeSessionId, oxyServices, logoutStore, clearAllStorage, onAuthStateChange, onError]);
|
|
655
803
|
const updateDeviceName = useCallback(async deviceName => {
|
|
656
804
|
if (!activeSessionId) throw new Error('No active session');
|
|
657
805
|
try {
|
|
658
806
|
await oxyServices.updateDeviceName(activeSessionId, deviceName);
|
|
659
|
-
|
|
660
|
-
// Update local device info
|
|
661
807
|
await DeviceManager.updateDeviceName(deviceName);
|
|
662
808
|
} catch (error) {
|
|
663
|
-
|
|
809
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to update device name';
|
|
810
|
+
onError?.({
|
|
811
|
+
message: errorMessage,
|
|
812
|
+
code: 'UPDATE_DEVICE_NAME_ERROR',
|
|
813
|
+
status: 500
|
|
814
|
+
});
|
|
664
815
|
throw error;
|
|
665
816
|
}
|
|
666
|
-
}, [activeSessionId, oxyServices]);
|
|
817
|
+
}, [activeSessionId, oxyServices, onError]);
|
|
667
818
|
|
|
668
819
|
// Language management method
|
|
669
820
|
const setLanguage = useCallback(async languageId => {
|
|
670
821
|
if (!storage) throw new Error('Storage not initialized');
|
|
671
822
|
try {
|
|
672
|
-
// Save language preference
|
|
673
823
|
await storage.setItem(keys.language, languageId);
|
|
674
824
|
setCurrentLanguage(languageId);
|
|
675
|
-
console.log(`Language changed to ${languageId}`);
|
|
676
|
-
|
|
677
|
-
// TODO: Here you can add any additional logic needed for app-wide language updates
|
|
678
|
-
// such as updating i18n configuration, refreshing translations, etc.
|
|
679
825
|
} catch (error) {
|
|
680
|
-
|
|
826
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to save language preference';
|
|
827
|
+
onError?.({
|
|
828
|
+
message: errorMessage,
|
|
829
|
+
code: 'LANGUAGE_SAVE_ERROR',
|
|
830
|
+
status: 500
|
|
831
|
+
});
|
|
681
832
|
throw error;
|
|
682
833
|
}
|
|
683
|
-
}, [storage, keys.language]);
|
|
834
|
+
}, [storage, keys.language, onError]);
|
|
684
835
|
|
|
685
836
|
// Bottom sheet control methods
|
|
686
837
|
const showBottomSheet = useCallback(screenOrConfig => {
|
|
@@ -695,9 +846,8 @@ export const OxyProvider = ({
|
|
|
695
846
|
} else if (bottomSheetRef.current.present) {
|
|
696
847
|
if (__DEV__) console.log('Presenting bottom sheet');
|
|
697
848
|
bottomSheetRef.current.present();
|
|
698
|
-
} else {
|
|
849
|
+
} else if (__DEV__) {
|
|
699
850
|
console.warn('No expand or present method available on bottomSheetRef');
|
|
700
|
-
if (__DEV__) console.log('Available methods on bottomSheetRef.current:', Object.keys(bottomSheetRef.current));
|
|
701
851
|
}
|
|
702
852
|
|
|
703
853
|
// Then navigate to the specified screen if provided
|
|
@@ -715,10 +865,8 @@ export const OxyProvider = ({
|
|
|
715
865
|
}
|
|
716
866
|
}, 100);
|
|
717
867
|
}
|
|
718
|
-
} else {
|
|
719
|
-
console.warn('bottomSheetRef is not available');
|
|
720
|
-
console.warn('To fix this, ensure you pass a bottomSheetRef to OxyProvider:');
|
|
721
|
-
console.warn('<OxyProvider baseURL="..." bottomSheetRef={yourBottomSheetRef}>');
|
|
868
|
+
} else if (__DEV__) {
|
|
869
|
+
console.warn('bottomSheetRef is not available. Pass a bottomSheetRef to OxyProvider.');
|
|
722
870
|
}
|
|
723
871
|
}, [bottomSheetRef]);
|
|
724
872
|
const hideBottomSheet = useCallback(() => {
|
|
@@ -751,39 +899,15 @@ export const OxyProvider = ({
|
|
|
751
899
|
if (mod && typeof mod.useFollow === 'function') {
|
|
752
900
|
return mod.useFollow(userId);
|
|
753
901
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
error: null,
|
|
759
|
-
toggleFollow: async () => {},
|
|
760
|
-
setFollowStatus: () => {},
|
|
761
|
-
fetchStatus: async () => {},
|
|
762
|
-
clearError: () => {},
|
|
763
|
-
followerCount: null,
|
|
764
|
-
followingCount: null,
|
|
765
|
-
isLoadingCounts: false,
|
|
766
|
-
fetchUserCounts: async () => {},
|
|
767
|
-
setFollowerCount: () => {},
|
|
768
|
-
setFollowingCount: () => {}
|
|
769
|
-
};
|
|
902
|
+
if (__DEV__) {
|
|
903
|
+
console.warn('useFollow module did not export a function as expected');
|
|
904
|
+
}
|
|
905
|
+
return createEmptyFollowHook()(userId);
|
|
770
906
|
} catch (e) {
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
error: null,
|
|
776
|
-
toggleFollow: async () => {},
|
|
777
|
-
setFollowStatus: () => {},
|
|
778
|
-
fetchStatus: async () => {},
|
|
779
|
-
clearError: () => {},
|
|
780
|
-
followerCount: null,
|
|
781
|
-
followingCount: null,
|
|
782
|
-
isLoadingCounts: false,
|
|
783
|
-
fetchUserCounts: async () => {},
|
|
784
|
-
setFollowerCount: () => {},
|
|
785
|
-
setFollowingCount: () => {}
|
|
786
|
-
};
|
|
907
|
+
if (__DEV__) {
|
|
908
|
+
console.warn('Failed to dynamically load useFollow hook:', e);
|
|
909
|
+
}
|
|
910
|
+
return createEmptyFollowHook()(userId);
|
|
787
911
|
}
|
|
788
912
|
};
|
|
789
913
|
const contextValue = useMemo(() => ({
|
|
@@ -793,6 +917,7 @@ export const OxyProvider = ({
|
|
|
793
917
|
activeSessionId,
|
|
794
918
|
isAuthenticated,
|
|
795
919
|
isLoading,
|
|
920
|
+
isTokenReady: tokenReady,
|
|
796
921
|
error,
|
|
797
922
|
currentLanguage,
|
|
798
923
|
login,
|
|
@@ -816,21 +941,9 @@ export const OxyProvider = ({
|
|
|
816
941
|
// Only depend on user ID, not the entire user object
|
|
817
942
|
minimalUser?.id, sessions.length,
|
|
818
943
|
// Only depend on sessions count, not the entire array
|
|
819
|
-
activeSessionId, isAuthenticated, isLoading, error, currentLanguage, login, logout, logoutAll, signUp, completeMfaLogin, switchSession, removeSession, refreshSessions, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, oxyServices, bottomSheetRef, showBottomSheet, hideBottomSheet]);
|
|
820
|
-
|
|
821
|
-
//
|
|
822
|
-
if (!tokenReady) {
|
|
823
|
-
return /*#__PURE__*/_jsx(View, {
|
|
824
|
-
style: {
|
|
825
|
-
flex: 1,
|
|
826
|
-
justifyContent: 'center',
|
|
827
|
-
alignItems: 'center'
|
|
828
|
-
},
|
|
829
|
-
children: /*#__PURE__*/_jsx(Text, {
|
|
830
|
-
children: "Loading authentication..."
|
|
831
|
-
})
|
|
832
|
-
});
|
|
833
|
-
}
|
|
944
|
+
activeSessionId, isAuthenticated, isLoading, tokenReady, error, currentLanguage, login, logout, logoutAll, signUp, completeMfaLogin, switchSession, removeSession, refreshSessions, setLanguage, getDeviceSessions, logoutAllDeviceSessions, updateDeviceName, oxyServices, bottomSheetRef, showBottomSheet, hideBottomSheet]);
|
|
945
|
+
|
|
946
|
+
// Always render children - let the consuming app decide how to handle token loading state
|
|
834
947
|
return /*#__PURE__*/_jsx(OxyContext.Provider, {
|
|
835
948
|
value: contextValue,
|
|
836
949
|
children: children
|