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