@oxyhq/services 5.16.1 → 5.16.3
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/mixins/OxyServices.user.js +14 -13
- package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/commonjs/crypto/keyManager.js +164 -3
- package/lib/commonjs/crypto/keyManager.js.map +1 -1
- package/lib/commonjs/crypto/signatureService.js +26 -0
- package/lib/commonjs/crypto/signatureService.js.map +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js +1 -1
- package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
- package/lib/commonjs/ui/components/OxyProvider.js +71 -24
- package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
- package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js +1 -4
- package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +175 -4
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js +148 -49
- package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/commonjs/ui/context/hooks/useSessionManagement.js +22 -2
- package/lib/commonjs/ui/context/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/index.js +28 -0
- package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -0
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +314 -0
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -0
- package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js +193 -0
- package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js.map +1 -0
- package/lib/commonjs/ui/hooks/queries/index.js +39 -0
- package/lib/commonjs/ui/hooks/queries/index.js.map +1 -0
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +85 -0
- package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -0
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +145 -0
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -0
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +138 -0
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -0
- package/lib/commonjs/ui/hooks/queryClient.js +117 -0
- package/lib/commonjs/ui/hooks/queryClient.js.map +1 -0
- package/lib/commonjs/ui/hooks/useIdentityMutations.js +111 -0
- package/lib/commonjs/ui/hooks/useIdentityMutations.js.map +1 -0
- package/lib/commonjs/ui/hooks/useProfileEditing.js +42 -58
- package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/commonjs/ui/hooks/useQueryClient.js +20 -0
- package/lib/commonjs/ui/hooks/useQueryClient.js.map +1 -0
- package/lib/commonjs/ui/hooks/useSessionManagement.js +22 -2
- package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js +43 -42
- package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js +63 -58
- package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +6 -6
- package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/commonjs/ui/stores/accountStore.js +57 -42
- package/lib/commonjs/ui/stores/accountStore.js.map +1 -1
- package/lib/commonjs/ui/stores/authStore.js +4 -25
- package/lib/commonjs/ui/stores/authStore.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.user.js +14 -13
- package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/module/crypto/keyManager.js +164 -3
- package/lib/module/crypto/keyManager.js.map +1 -1
- package/lib/module/crypto/signatureService.js +26 -0
- package/lib/module/crypto/signatureService.js.map +1 -1
- package/lib/module/ui/components/GroupedSection.js +1 -1
- package/lib/module/ui/components/GroupedSection.js.map +1 -1
- package/lib/module/ui/components/OxyProvider.js +72 -25
- package/lib/module/ui/components/OxyProvider.js.map +1 -1
- package/lib/module/ui/components/profile/EditDisplayNameModal.js +1 -4
- package/lib/module/ui/components/profile/EditDisplayNameModal.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +174 -4
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/context/hooks/useAuthOperations.js +148 -49
- package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
- package/lib/module/ui/context/hooks/useSessionManagement.js +22 -2
- package/lib/module/ui/context/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/hooks/mutations/index.js +6 -0
- package/lib/module/ui/hooks/mutations/index.js.map +1 -0
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +308 -0
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -0
- package/lib/module/ui/hooks/mutations/useServicesMutations.js +185 -0
- package/lib/module/ui/hooks/mutations/useServicesMutations.js.map +1 -0
- package/lib/module/ui/hooks/queries/index.js +7 -0
- package/lib/module/ui/hooks/queries/index.js.map +1 -0
- package/lib/module/ui/hooks/queries/queryKeys.js +78 -0
- package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -0
- package/lib/module/ui/hooks/queries/useAccountQueries.js +136 -0
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -0
- package/lib/module/ui/hooks/queries/useServicesQueries.js +130 -0
- package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -0
- package/lib/module/ui/hooks/queryClient.js +110 -0
- package/lib/module/ui/hooks/queryClient.js.map +1 -0
- package/lib/module/ui/hooks/useIdentityMutations.js +105 -0
- package/lib/module/ui/hooks/useIdentityMutations.js.map +1 -0
- package/lib/module/ui/hooks/useProfileEditing.js +43 -59
- package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
- package/lib/module/ui/hooks/useQueryClient.js +15 -0
- package/lib/module/ui/hooks/useQueryClient.js.map +1 -0
- package/lib/module/ui/hooks/useSessionManagement.js +22 -2
- package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
- package/lib/module/ui/screens/AccountOverviewScreen.js +43 -42
- package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
- package/lib/module/ui/screens/AccountSettingsScreen.js +63 -58
- package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
- package/lib/module/ui/screens/WelcomeNewUserScreen.js +6 -6
- package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
- package/lib/module/ui/stores/accountStore.js +57 -42
- package/lib/module/ui/stores/accountStore.js.map +1 -1
- package/lib/module/ui/stores/authStore.js +4 -25
- package/lib/module/ui/stores/authStore.js.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.user.d.ts +4 -5
- package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +0 -1
- package/lib/typescript/core/mixins/index.d.ts.map +1 -1
- package/lib/typescript/crypto/keyManager.d.ts +19 -2
- package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
- package/lib/typescript/crypto/signatureService.d.ts +5 -0
- package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
- package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
- package/lib/typescript/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
- package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts +3 -1
- package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/index.d.ts +3 -0
- package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +25 -0
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts +23 -0
- package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/queries/index.d.ts +4 -0
- package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +56 -0
- package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +41 -0
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts +34 -0
- package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/queryClient.d.ts +19 -0
- package/lib/typescript/ui/hooks/queryClient.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/useIdentityMutations.d.ts +29 -0
- package/lib/typescript/ui/hooks/useIdentityMutations.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/useProfileEditing.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useQueryClient.d.ts +7 -0
- package/lib/typescript/ui/hooks/useQueryClient.d.ts.map +1 -0
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts +3 -1
- package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
- package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
- package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -1
- package/lib/typescript/ui/stores/authStore.d.ts +0 -4
- package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
- package/package.json +5 -4
- package/src/core/mixins/OxyServices.user.ts +17 -10
- package/src/crypto/keyManager.ts +177 -2
- package/src/crypto/signatureService.ts +30 -0
- package/src/ui/components/GroupedSection.tsx +1 -1
- package/src/ui/components/OxyProvider.tsx +91 -37
- package/src/ui/components/profile/EditDisplayNameModal.tsx +1 -3
- package/src/ui/context/OxyContext.tsx +185 -2
- package/src/ui/context/hooks/useAuthOperations.ts +171 -58
- package/src/ui/context/hooks/useSessionManagement.ts +24 -1
- package/src/ui/hooks/mutations/index.ts +4 -0
- package/src/ui/hooks/mutations/useAccountMutations.ts +277 -0
- package/src/ui/hooks/mutations/useServicesMutations.ts +164 -0
- package/src/ui/hooks/queries/index.ts +5 -0
- package/src/ui/hooks/queries/queryKeys.ts +73 -0
- package/src/ui/hooks/queries/useAccountQueries.ts +126 -0
- package/src/ui/hooks/queries/useServicesQueries.ts +121 -0
- package/src/ui/hooks/queryClient.ts +112 -0
- package/src/ui/hooks/useIdentityMutations.ts +115 -0
- package/src/ui/hooks/useProfileEditing.ts +46 -60
- package/src/ui/hooks/useQueryClient.ts +17 -0
- package/src/ui/hooks/useSessionManagement.ts +24 -1
- package/src/ui/screens/AccountOverviewScreen.tsx +38 -46
- package/src/ui/screens/AccountSettingsScreen.tsx +54 -54
- package/src/ui/screens/WelcomeNewUserScreen.tsx +13 -12
- package/src/ui/stores/accountStore.ts +54 -43
- package/src/ui/stores/authStore.ts +3 -17
|
@@ -26,6 +26,10 @@ import { getStorageKeys } from '../utils/storageHelpers';
|
|
|
26
26
|
import { isInvalidSessionError } from '../utils/errorHandlers';
|
|
27
27
|
import type { RouteName } from '../navigation/routes';
|
|
28
28
|
import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSheetManager';
|
|
29
|
+
import { useQueryClient } from '@tanstack/react-query';
|
|
30
|
+
import { clearQueryCache } from '../hooks/queryClient';
|
|
31
|
+
import { useAccountStore } from '../stores/accountStore';
|
|
32
|
+
import { KeyManager } from '../../crypto/keyManager';
|
|
29
33
|
|
|
30
34
|
export interface OxyContextState {
|
|
31
35
|
user: User | null;
|
|
@@ -34,6 +38,7 @@ export interface OxyContextState {
|
|
|
34
38
|
isAuthenticated: boolean;
|
|
35
39
|
isLoading: boolean;
|
|
36
40
|
isTokenReady: boolean;
|
|
41
|
+
isStorageReady: boolean;
|
|
37
42
|
error: string | null;
|
|
38
43
|
currentLanguage: string;
|
|
39
44
|
currentLanguageMetadata: ReturnType<typeof useLanguageManagement>['metadata'];
|
|
@@ -48,6 +53,7 @@ export interface OxyContextState {
|
|
|
48
53
|
getPublicKey: () => Promise<string | null>;
|
|
49
54
|
isIdentitySynced: () => Promise<boolean>;
|
|
50
55
|
syncIdentity: () => Promise<User>;
|
|
56
|
+
deleteIdentityAndClearAccount: (skipBackup?: boolean, force?: boolean, userConfirmed?: boolean) => Promise<void>;
|
|
51
57
|
|
|
52
58
|
// Identity sync state (reactive, from Zustand store)
|
|
53
59
|
identitySyncState: {
|
|
@@ -73,6 +79,8 @@ export interface OxyContextState {
|
|
|
73
79
|
>;
|
|
74
80
|
logoutAllDeviceSessions: () => Promise<void>;
|
|
75
81
|
updateDeviceName: (deviceName: string) => Promise<void>;
|
|
82
|
+
clearSessionState: () => Promise<void>;
|
|
83
|
+
clearAllAccountData: () => Promise<void>;
|
|
76
84
|
oxyServices: OxyServices;
|
|
77
85
|
useFollow?: UseFollowHook;
|
|
78
86
|
showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
|
|
@@ -182,7 +190,55 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
182
190
|
|
|
183
191
|
const storageKeys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
184
192
|
|
|
185
|
-
const { storage } = useStorage({ onError, logger });
|
|
193
|
+
const { storage, isReady: isStorageReady } = useStorage({ onError, logger });
|
|
194
|
+
|
|
195
|
+
// Identity integrity check and auto-restore on startup
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!storage || !isStorageReady) return;
|
|
198
|
+
|
|
199
|
+
const checkAndRestoreIdentity = async () => {
|
|
200
|
+
try {
|
|
201
|
+
const { KeyManager } = await import('../../crypto/index.js');
|
|
202
|
+
// Check if identity exists and verify integrity
|
|
203
|
+
const hasIdentity = await KeyManager.hasIdentity();
|
|
204
|
+
if (hasIdentity) {
|
|
205
|
+
const isValid = await KeyManager.verifyIdentityIntegrity();
|
|
206
|
+
if (!isValid) {
|
|
207
|
+
// Try to restore from backup
|
|
208
|
+
const restored = await KeyManager.restoreIdentityFromBackup();
|
|
209
|
+
if (restored) {
|
|
210
|
+
if (__DEV__) {
|
|
211
|
+
logger('Identity restored from backup successfully');
|
|
212
|
+
}
|
|
213
|
+
} else {
|
|
214
|
+
if (__DEV__) {
|
|
215
|
+
logger('Identity integrity check failed - user may need to restore from recovery phrase');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else {
|
|
219
|
+
// Identity is valid - ensure backup is up to date
|
|
220
|
+
await KeyManager.backupIdentity();
|
|
221
|
+
}
|
|
222
|
+
} else {
|
|
223
|
+
// No identity - try to restore from backup
|
|
224
|
+
const restored = await KeyManager.restoreIdentityFromBackup();
|
|
225
|
+
if (restored && __DEV__) {
|
|
226
|
+
logger('Identity restored from backup on startup');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
} catch (error) {
|
|
230
|
+
if (__DEV__) {
|
|
231
|
+
logger('Error during identity integrity check', error);
|
|
232
|
+
}
|
|
233
|
+
// Don't block app startup - user can recover with recovery phrase
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
checkAndRestoreIdentity();
|
|
238
|
+
}, [storage, isStorageReady, logger]);
|
|
239
|
+
|
|
240
|
+
// Offline queuing is now handled by TanStack Query mutations
|
|
241
|
+
// No need for custom offline queue
|
|
186
242
|
|
|
187
243
|
const {
|
|
188
244
|
currentLanguage,
|
|
@@ -198,6 +254,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
198
254
|
logger,
|
|
199
255
|
});
|
|
200
256
|
|
|
257
|
+
const queryClient = useQueryClient();
|
|
258
|
+
|
|
201
259
|
const {
|
|
202
260
|
sessions,
|
|
203
261
|
activeSessionId,
|
|
@@ -220,6 +278,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
220
278
|
setAuthError: (message) => setAuthState({ error: message }),
|
|
221
279
|
logger,
|
|
222
280
|
setTokenReady,
|
|
281
|
+
queryClient,
|
|
223
282
|
});
|
|
224
283
|
|
|
225
284
|
const {
|
|
@@ -231,7 +290,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
231
290
|
hasIdentity,
|
|
232
291
|
getPublicKey,
|
|
233
292
|
isIdentitySynced,
|
|
234
|
-
syncIdentity,
|
|
293
|
+
syncIdentity: syncIdentityBase,
|
|
235
294
|
} = useAuthOperations({
|
|
236
295
|
oxyServices,
|
|
237
296
|
storage,
|
|
@@ -254,6 +313,123 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
254
313
|
logger,
|
|
255
314
|
});
|
|
256
315
|
|
|
316
|
+
// syncIdentity - TanStack Query handles offline mutations automatically
|
|
317
|
+
const syncIdentity = useCallback(async () => {
|
|
318
|
+
return await syncIdentityBase();
|
|
319
|
+
}, [syncIdentityBase]);
|
|
320
|
+
|
|
321
|
+
// Clear all account data when identity is lost (for accounts app)
|
|
322
|
+
// In accounts app, identity = account, so losing identity means losing everything
|
|
323
|
+
const clearAllAccountData = useCallback(async (): Promise<void> => {
|
|
324
|
+
// Clear TanStack Query cache (in-memory)
|
|
325
|
+
queryClient.clear();
|
|
326
|
+
|
|
327
|
+
// Clear persisted query cache
|
|
328
|
+
if (storage) {
|
|
329
|
+
try {
|
|
330
|
+
await clearQueryCache(storage);
|
|
331
|
+
} catch (error) {
|
|
332
|
+
if (logger) {
|
|
333
|
+
logger('Failed to clear persisted query cache', error);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Clear session state (sessions, activeSessionId, storage)
|
|
339
|
+
await clearSessionState();
|
|
340
|
+
|
|
341
|
+
// Clear identity sync state from storage
|
|
342
|
+
if (storage) {
|
|
343
|
+
try {
|
|
344
|
+
await storage.removeItem('oxy_identity_synced');
|
|
345
|
+
} catch (error) {
|
|
346
|
+
if (logger) {
|
|
347
|
+
logger('Failed to clear identity sync state', error);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Reset auth store identity sync state
|
|
353
|
+
useAuthStore.getState().setIdentitySynced(false);
|
|
354
|
+
useAuthStore.getState().setSyncing(false);
|
|
355
|
+
|
|
356
|
+
// Reset account store
|
|
357
|
+
useAccountStore.getState().reset();
|
|
358
|
+
|
|
359
|
+
// Clear HTTP service cache
|
|
360
|
+
oxyServices.clearCache();
|
|
361
|
+
}, [queryClient, storage, clearSessionState, logger, oxyServices]);
|
|
362
|
+
|
|
363
|
+
// Delete identity and clear all account data
|
|
364
|
+
// In accounts app, deleting identity means losing the account completely
|
|
365
|
+
const deleteIdentityAndClearAccount = useCallback(async (
|
|
366
|
+
skipBackup: boolean = false,
|
|
367
|
+
force: boolean = false,
|
|
368
|
+
userConfirmed: boolean = false
|
|
369
|
+
): Promise<void> => {
|
|
370
|
+
// First, clear all account data
|
|
371
|
+
await clearAllAccountData();
|
|
372
|
+
|
|
373
|
+
// Then delete the identity keys
|
|
374
|
+
await KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
|
|
375
|
+
}, [clearAllAccountData]);
|
|
376
|
+
|
|
377
|
+
// Network reconnect sync - TanStack Query automatically retries mutations on reconnect
|
|
378
|
+
// We only need to sync identity if it's not synced
|
|
379
|
+
useEffect(() => {
|
|
380
|
+
if (!storage) return;
|
|
381
|
+
|
|
382
|
+
let wasOffline = false;
|
|
383
|
+
let checkInterval: NodeJS.Timeout | null = null;
|
|
384
|
+
|
|
385
|
+
const checkNetworkAndSync = async () => {
|
|
386
|
+
try {
|
|
387
|
+
// Try a lightweight health check to see if we're online
|
|
388
|
+
await oxyServices.healthCheck().catch(() => {
|
|
389
|
+
wasOffline = true;
|
|
390
|
+
return;
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// If we were offline and now we're online, sync identity if needed
|
|
394
|
+
if (wasOffline) {
|
|
395
|
+
if (__DEV__ && logger) {
|
|
396
|
+
logger('Network reconnected, checking identity sync...');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Sync identity first (if not synced)
|
|
400
|
+
try {
|
|
401
|
+
const isSynced = await storage.getItem('oxy_identity_synced');
|
|
402
|
+
if (isSynced === 'false') {
|
|
403
|
+
await syncIdentity();
|
|
404
|
+
}
|
|
405
|
+
} catch (syncError) {
|
|
406
|
+
if (__DEV__ && logger) {
|
|
407
|
+
logger('Error syncing identity on reconnect', syncError);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// TanStack Query will automatically retry pending mutations
|
|
412
|
+
wasOffline = false;
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
// Network check failed - we're offline
|
|
416
|
+
wasOffline = true;
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
|
|
420
|
+
// Check immediately
|
|
421
|
+
checkNetworkAndSync();
|
|
422
|
+
|
|
423
|
+
// Check periodically (every 10 seconds when app is active)
|
|
424
|
+
checkInterval = setInterval(checkNetworkAndSync, 10000);
|
|
425
|
+
|
|
426
|
+
return () => {
|
|
427
|
+
if (checkInterval) {
|
|
428
|
+
clearInterval(checkInterval);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
}, [oxyServices, storage, syncIdentity, logger]);
|
|
432
|
+
|
|
257
433
|
const { getDeviceSessions, logoutAllDeviceSessions, updateDeviceName } = useDeviceManagement({
|
|
258
434
|
oxyServices,
|
|
259
435
|
activeSessionId,
|
|
@@ -404,6 +580,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
404
580
|
isAuthenticated,
|
|
405
581
|
isLoading,
|
|
406
582
|
isTokenReady: tokenReady,
|
|
583
|
+
isStorageReady,
|
|
407
584
|
error,
|
|
408
585
|
currentLanguage,
|
|
409
586
|
currentLanguageMetadata,
|
|
@@ -416,6 +593,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
416
593
|
getPublicKey,
|
|
417
594
|
isIdentitySynced,
|
|
418
595
|
syncIdentity,
|
|
596
|
+
deleteIdentityAndClearAccount,
|
|
419
597
|
identitySyncState: {
|
|
420
598
|
isSynced: isIdentitySyncedStore ?? true,
|
|
421
599
|
isSyncing: isSyncing ?? false,
|
|
@@ -429,6 +607,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
429
607
|
getDeviceSessions,
|
|
430
608
|
logoutAllDeviceSessions,
|
|
431
609
|
updateDeviceName,
|
|
610
|
+
clearSessionState,
|
|
611
|
+
clearAllAccountData,
|
|
432
612
|
oxyServices,
|
|
433
613
|
useFollow: useFollowHook,
|
|
434
614
|
showBottomSheet: showBottomSheetForContext,
|
|
@@ -441,6 +621,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
441
621
|
getPublicKey,
|
|
442
622
|
isIdentitySynced,
|
|
443
623
|
syncIdentity,
|
|
624
|
+
deleteIdentityAndClearAccount,
|
|
444
625
|
isIdentitySyncedStore,
|
|
445
626
|
isSyncing,
|
|
446
627
|
currentLanguage,
|
|
@@ -460,7 +641,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
460
641
|
setLanguage,
|
|
461
642
|
switchSessionForContext,
|
|
462
643
|
tokenReady,
|
|
644
|
+
isStorageReady,
|
|
463
645
|
updateDeviceName,
|
|
646
|
+
clearAllAccountData,
|
|
464
647
|
useFollowHook,
|
|
465
648
|
user,
|
|
466
649
|
showBottomSheetForContext,
|
|
@@ -85,7 +85,7 @@ export const useAuthOperations = ({
|
|
|
85
85
|
}: UseAuthOperationsOptions): UseAuthOperationsResult => {
|
|
86
86
|
|
|
87
87
|
/**
|
|
88
|
-
* Internal function to perform challenge-response sign in
|
|
88
|
+
* Internal function to perform challenge-response sign in (works offline)
|
|
89
89
|
*/
|
|
90
90
|
const performSignIn = useCallback(
|
|
91
91
|
async (publicKey: string): Promise<User> => {
|
|
@@ -94,8 +94,36 @@ export const useAuthOperations = ({
|
|
|
94
94
|
const deviceInfo = await DeviceManager.getDeviceInfo();
|
|
95
95
|
const deviceName = deviceInfo.deviceName || DeviceManager.getDefaultDeviceName();
|
|
96
96
|
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
let challenge: string;
|
|
98
|
+
let isOffline = false;
|
|
99
|
+
|
|
100
|
+
// Try to request challenge from server (online)
|
|
101
|
+
try {
|
|
102
|
+
const challengeResponse = await oxyServices.requestChallenge(publicKey);
|
|
103
|
+
challenge = challengeResponse.challenge;
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Network error - generate challenge locally for offline sign-in
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
107
|
+
const isNetworkError =
|
|
108
|
+
errorMessage.includes('Network') ||
|
|
109
|
+
errorMessage.includes('network') ||
|
|
110
|
+
errorMessage.includes('Failed to fetch') ||
|
|
111
|
+
errorMessage.includes('fetch failed') ||
|
|
112
|
+
(error as any)?.code === 'NETWORK_ERROR' ||
|
|
113
|
+
(error as any)?.status === 0;
|
|
114
|
+
|
|
115
|
+
if (isNetworkError) {
|
|
116
|
+
if (__DEV__ && logger) {
|
|
117
|
+
logger('Network unavailable, performing offline sign-in');
|
|
118
|
+
}
|
|
119
|
+
// Generate challenge locally
|
|
120
|
+
challenge = await SignatureService.generateChallenge();
|
|
121
|
+
isOffline = true;
|
|
122
|
+
} else {
|
|
123
|
+
// Re-throw non-network errors
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
99
127
|
|
|
100
128
|
// Note: Biometric authentication check should be handled by the app layer
|
|
101
129
|
// (e.g., accounts app) before calling signIn. The biometric preference is stored
|
|
@@ -104,66 +132,129 @@ export const useAuthOperations = ({
|
|
|
104
132
|
// Sign the challenge
|
|
105
133
|
const { challenge: signature, timestamp } = await SignatureService.signChallenge(challenge);
|
|
106
134
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
publicKey,
|
|
110
|
-
challenge,
|
|
111
|
-
signature,
|
|
112
|
-
timestamp,
|
|
113
|
-
deviceName,
|
|
114
|
-
deviceFingerprint,
|
|
115
|
-
);
|
|
116
|
-
|
|
117
|
-
// Get token for the session
|
|
118
|
-
await oxyServices.getTokenBySession(sessionResponse.sessionId);
|
|
119
|
-
|
|
120
|
-
// Get full user data
|
|
121
|
-
const fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
122
|
-
await applyLanguagePreference(fullUser);
|
|
123
|
-
loginSuccess(fullUser);
|
|
135
|
+
let fullUser: User;
|
|
136
|
+
let sessionResponse: SessionLoginResponse;
|
|
124
137
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
fallbackDeviceId: sessionResponse.deviceId,
|
|
130
|
-
fallbackUserId: fullUser.id,
|
|
131
|
-
logger,
|
|
132
|
-
});
|
|
133
|
-
} catch (error) {
|
|
134
|
-
if (__DEV__) {
|
|
135
|
-
console.warn('Failed to fetch device sessions after login:', error);
|
|
138
|
+
if (isOffline) {
|
|
139
|
+
// Offline sign-in: create local session and minimal user object
|
|
140
|
+
if (__DEV__ && logger) {
|
|
141
|
+
logger('Creating offline session');
|
|
136
142
|
}
|
|
137
|
-
}
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
(
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
144
|
+
// Generate a local session ID
|
|
145
|
+
const localSessionId = `offline_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
146
|
+
const localDeviceId = `device_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
147
|
+
const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(); // 7 days
|
|
148
|
+
|
|
149
|
+
// Create minimal user object with publicKey as id
|
|
150
|
+
fullUser = {
|
|
151
|
+
id: publicKey, // Use publicKey as id (per migration document)
|
|
152
|
+
publicKey,
|
|
153
|
+
username: '',
|
|
154
|
+
privacySettings: {},
|
|
155
|
+
} as User;
|
|
156
|
+
|
|
157
|
+
sessionResponse = {
|
|
158
|
+
sessionId: localSessionId,
|
|
159
|
+
deviceId: localDeviceId,
|
|
160
|
+
expiresAt,
|
|
161
|
+
user: {
|
|
162
|
+
id: publicKey,
|
|
163
|
+
username: '',
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// Store offline session locally
|
|
168
|
+
const offlineSession: ClientSession = {
|
|
169
|
+
sessionId: localSessionId,
|
|
170
|
+
deviceId: localDeviceId,
|
|
171
|
+
expiresAt,
|
|
172
|
+
lastActive: new Date().toISOString(),
|
|
173
|
+
userId: publicKey,
|
|
174
|
+
isCurrent: true,
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
setActiveSessionId(localSessionId);
|
|
178
|
+
await saveActiveSessionId(localSessionId);
|
|
179
|
+
updateSessions([offlineSession], { merge: true });
|
|
180
|
+
|
|
181
|
+
// Mark session as offline for later sync
|
|
182
|
+
if (storage) {
|
|
183
|
+
await storage.setItem(`oxy_session_${localSessionId}_offline`, 'true');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (__DEV__ && logger) {
|
|
187
|
+
logger('Offline sign-in successful');
|
|
188
|
+
}
|
|
189
|
+
} else {
|
|
190
|
+
// Online sign-in: use normal flow
|
|
191
|
+
// Verify and create session
|
|
192
|
+
sessionResponse = await oxyServices.verifyChallenge(
|
|
193
|
+
publicKey,
|
|
194
|
+
challenge,
|
|
195
|
+
signature,
|
|
196
|
+
timestamp,
|
|
197
|
+
deviceName,
|
|
198
|
+
deviceFingerprint,
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
// Get token for the session
|
|
202
|
+
await oxyServices.getTokenBySession(sessionResponse.sessionId);
|
|
145
203
|
|
|
146
|
-
|
|
147
|
-
|
|
204
|
+
// Get full user data
|
|
205
|
+
fullUser = await oxyServices.getUserBySession(sessionResponse.sessionId);
|
|
206
|
+
|
|
207
|
+
// Ensure id is set to publicKey (per migration document)
|
|
208
|
+
if (fullUser.id !== fullUser.publicKey) {
|
|
209
|
+
fullUser.id = fullUser.publicKey;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Fetch device sessions
|
|
213
|
+
let allDeviceSessions: ClientSession[] = [];
|
|
148
214
|
try {
|
|
149
|
-
await oxyServices
|
|
150
|
-
|
|
215
|
+
allDeviceSessions = await fetchSessionsWithFallback(oxyServices, sessionResponse.sessionId, {
|
|
216
|
+
fallbackDeviceId: sessionResponse.deviceId,
|
|
217
|
+
fallbackUserId: fullUser.id,
|
|
218
|
+
logger,
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
151
221
|
if (__DEV__) {
|
|
152
|
-
console.warn('Failed to
|
|
222
|
+
console.warn('Failed to fetch device sessions after login:', error);
|
|
153
223
|
}
|
|
154
224
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
225
|
+
|
|
226
|
+
// Check for existing session for same user
|
|
227
|
+
const existingSession = allDeviceSessions.find(
|
|
228
|
+
(session) =>
|
|
229
|
+
session.userId?.toString() === fullUser.id?.toString() &&
|
|
230
|
+
session.sessionId !== sessionResponse.sessionId,
|
|
159
231
|
);
|
|
160
|
-
|
|
161
|
-
|
|
232
|
+
|
|
233
|
+
if (existingSession) {
|
|
234
|
+
// Logout duplicate session
|
|
235
|
+
try {
|
|
236
|
+
await oxyServices.logoutSession(sessionResponse.sessionId, sessionResponse.sessionId);
|
|
237
|
+
} catch (logoutError) {
|
|
238
|
+
if (__DEV__) {
|
|
239
|
+
console.warn('Failed to logout duplicate session:', logoutError);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
await switchSession(existingSession.sessionId);
|
|
243
|
+
updateSessions(
|
|
244
|
+
allDeviceSessions.filter((session) => session.sessionId !== sessionResponse.sessionId),
|
|
245
|
+
{ merge: false },
|
|
246
|
+
);
|
|
247
|
+
onAuthStateChange?.(fullUser);
|
|
248
|
+
return fullUser;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
setActiveSessionId(sessionResponse.sessionId);
|
|
252
|
+
await saveActiveSessionId(sessionResponse.sessionId);
|
|
253
|
+
updateSessions(allDeviceSessions, { merge: true });
|
|
162
254
|
}
|
|
163
255
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
updateSessions(allDeviceSessions, { merge: true });
|
|
256
|
+
await applyLanguagePreference(fullUser);
|
|
257
|
+
loginSuccess(fullUser);
|
|
167
258
|
onAuthStateChange?.(fullUser);
|
|
168
259
|
|
|
169
260
|
return fullUser;
|
|
@@ -178,6 +269,7 @@ export const useAuthOperations = ({
|
|
|
178
269
|
setActiveSessionId,
|
|
179
270
|
switchSession,
|
|
180
271
|
updateSessions,
|
|
272
|
+
storage,
|
|
181
273
|
],
|
|
182
274
|
);
|
|
183
275
|
|
|
@@ -224,10 +316,27 @@ export const useAuthOperations = ({
|
|
|
224
316
|
};
|
|
225
317
|
}
|
|
226
318
|
} catch (error) {
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
319
|
+
// CRITICAL: Never delete identity on error - it may have been successfully created
|
|
320
|
+
// Only log the error and let the user recover using their recovery phrase
|
|
321
|
+
// Identity deletion should ONLY happen when explicitly requested by the user
|
|
322
|
+
if (__DEV__ && logger) {
|
|
323
|
+
logger('Error during identity creation (identity may still exist):', error);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Check if identity was actually created (keys exist)
|
|
327
|
+
const hasIdentity = await KeyManager.hasIdentity().catch(() => false);
|
|
328
|
+
if (hasIdentity) {
|
|
329
|
+
// Identity exists - don't delete it! Just mark as not synced
|
|
330
|
+
await storage.setItem('oxy_identity_synced', 'false').catch(() => {});
|
|
331
|
+
setIdentitySynced(false);
|
|
332
|
+
if (__DEV__ && logger) {
|
|
333
|
+
logger('Identity was created but sync failed - user can sync later using recovery phrase');
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
// No identity exists - this was a generation failure, safe to clean up sync flag
|
|
337
|
+
await storage.removeItem('oxy_identity_synced').catch(() => {});
|
|
338
|
+
setIdentitySynced(false);
|
|
339
|
+
}
|
|
231
340
|
|
|
232
341
|
const message = handleAuthError(error, {
|
|
233
342
|
defaultMessage: 'Failed to create identity',
|
|
@@ -258,6 +367,7 @@ export const useAuthOperations = ({
|
|
|
258
367
|
|
|
259
368
|
/**
|
|
260
369
|
* Sync local identity with server (call when online)
|
|
370
|
+
* TanStack Query handles offline mutations automatically
|
|
261
371
|
*/
|
|
262
372
|
const syncIdentity = useCallback(
|
|
263
373
|
async (): Promise<User> => {
|
|
@@ -275,7 +385,6 @@ export const useAuthOperations = ({
|
|
|
275
385
|
// Check if already synced
|
|
276
386
|
const alreadySynced = await storage.getItem('oxy_identity_synced');
|
|
277
387
|
if (alreadySynced === 'true') {
|
|
278
|
-
// Already synced, just sign in
|
|
279
388
|
setIdentitySynced(true);
|
|
280
389
|
return await performSignIn(publicKey);
|
|
281
390
|
}
|
|
@@ -294,7 +403,11 @@ export const useAuthOperations = ({
|
|
|
294
403
|
setIdentitySynced(true);
|
|
295
404
|
|
|
296
405
|
// Sign in
|
|
297
|
-
|
|
406
|
+
const user = await performSignIn(publicKey);
|
|
407
|
+
|
|
408
|
+
// TanStack Query will automatically retry any pending mutations
|
|
409
|
+
|
|
410
|
+
return user;
|
|
298
411
|
} catch (error) {
|
|
299
412
|
const message = handleAuthError(error, {
|
|
300
413
|
defaultMessage: 'Failed to sync identity',
|
|
@@ -6,6 +6,8 @@ import { fetchSessionsWithFallback, mapSessionsToClient, validateSessionBatch }
|
|
|
6
6
|
import { getStorageKeys, type StorageInterface } from '../utils/storageHelpers';
|
|
7
7
|
import { handleAuthError, isInvalidSessionError } from '../utils/errorHandlers';
|
|
8
8
|
import type { OxyServices } from '../../../core';
|
|
9
|
+
import type { QueryClient } from '@tanstack/react-query';
|
|
10
|
+
import { clearQueryCache } from '../../hooks/queryClient.js';
|
|
9
11
|
|
|
10
12
|
export interface UseSessionManagementOptions {
|
|
11
13
|
oxyServices: OxyServices;
|
|
@@ -19,6 +21,7 @@ export interface UseSessionManagementOptions {
|
|
|
19
21
|
setAuthError?: (message: string | null) => void;
|
|
20
22
|
logger?: (message: string, error?: unknown) => void;
|
|
21
23
|
setTokenReady?: (ready: boolean) => void;
|
|
24
|
+
queryClient?: QueryClient | null;
|
|
22
25
|
}
|
|
23
26
|
|
|
24
27
|
export interface UseSessionManagementResult {
|
|
@@ -55,6 +58,7 @@ export const useSessionManagement = ({
|
|
|
55
58
|
setAuthError,
|
|
56
59
|
logger,
|
|
57
60
|
setTokenReady,
|
|
61
|
+
queryClient,
|
|
58
62
|
}: UseSessionManagementOptions): UseSessionManagementResult => {
|
|
59
63
|
const [sessions, setSessions] = useState<ClientSession[]>([]);
|
|
60
64
|
const [activeSessionId, setActiveSessionId] = useState<string | null>(null);
|
|
@@ -140,6 +144,8 @@ export const useSessionManagement = ({
|
|
|
140
144
|
try {
|
|
141
145
|
await storage.removeItem(storageKeys.activeSessionId);
|
|
142
146
|
await storage.removeItem(storageKeys.sessionIds);
|
|
147
|
+
// Clear identity sync state
|
|
148
|
+
await storage.removeItem('oxy_identity_synced').catch(() => {});
|
|
143
149
|
} catch (error) {
|
|
144
150
|
handleAuthError(error, {
|
|
145
151
|
defaultMessage: CLEAR_STORAGE_ERROR,
|
|
@@ -155,9 +161,26 @@ export const useSessionManagement = ({
|
|
|
155
161
|
setSessions([]);
|
|
156
162
|
setActiveSessionId(null);
|
|
157
163
|
logoutStore();
|
|
164
|
+
|
|
165
|
+
// Clear TanStack Query cache (in-memory)
|
|
166
|
+
if (queryClient) {
|
|
167
|
+
queryClient.clear();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Clear persisted query cache
|
|
171
|
+
if (storage) {
|
|
172
|
+
try {
|
|
173
|
+
await clearQueryCache(storage);
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (logger) {
|
|
176
|
+
logger('Failed to clear persisted query cache', error);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
158
181
|
await clearSessionStorage();
|
|
159
182
|
onAuthStateChange?.(null);
|
|
160
|
-
}, [clearSessionStorage, logoutStore, onAuthStateChange]);
|
|
183
|
+
}, [clearSessionStorage, logoutStore, onAuthStateChange, queryClient, storage, logger]);
|
|
161
184
|
|
|
162
185
|
const activateSession = useCallback(
|
|
163
186
|
async (sessionId: string, user: User): Promise<void> => {
|