@oxyhq/services 5.16.0 → 5.16.2
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.assets.js +15 -0
- package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -1
- 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/index.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 +177 -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.assets.js +15 -0
- package/lib/module/core/mixins/OxyServices.assets.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/index.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 +176 -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.assets.d.ts +7 -1
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts.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 +1 -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/index.d.ts +1 -1
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/models/interfaces.d.ts +21 -0
- package/lib/typescript/models/interfaces.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 +6 -5
- package/src/core/mixins/OxyServices.assets.ts +16 -1
- 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/index.ts +4 -1
- package/src/models/interfaces.ts +23 -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
|
@@ -82,6 +82,36 @@ export interface AuthChallenge {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
export class SignatureService {
|
|
85
|
+
/**
|
|
86
|
+
* Generate a random challenge string (for offline use)
|
|
87
|
+
* Uses expo-crypto in React Native, crypto.randomBytes in Node.js
|
|
88
|
+
*/
|
|
89
|
+
static async generateChallenge(): Promise<string> {
|
|
90
|
+
if (isReactNative() || !isNodeJS()) {
|
|
91
|
+
// Use expo-crypto for React Native (expo-random is deprecated)
|
|
92
|
+
const Crypto = await initExpoCrypto();
|
|
93
|
+
const randomBytes = await Crypto.getRandomBytesAsync(32);
|
|
94
|
+
return Array.from(randomBytes)
|
|
95
|
+
.map((b: number) => b.toString(16).padStart(2, '0'))
|
|
96
|
+
.join('');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Node.js fallback
|
|
100
|
+
try {
|
|
101
|
+
// eslint-disable-next-line @typescript-eslint/no-implied-eval
|
|
102
|
+
const getCrypto = new Function('return require("crypto")');
|
|
103
|
+
const crypto = getCrypto();
|
|
104
|
+
return crypto.randomBytes(32).toString('hex');
|
|
105
|
+
} catch (error) {
|
|
106
|
+
// Fallback to expo-crypto if Node crypto fails
|
|
107
|
+
const Crypto = await initExpoCrypto();
|
|
108
|
+
const randomBytes = await Crypto.getRandomBytesAsync(32);
|
|
109
|
+
return Array.from(randomBytes)
|
|
110
|
+
.map((b: number) => b.toString(16).padStart(2, '0'))
|
|
111
|
+
.join('');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
85
115
|
/**
|
|
86
116
|
* Hash a message using SHA-256
|
|
87
117
|
*/
|
package/src/index.ts
CHANGED
|
@@ -100,7 +100,10 @@ export type {
|
|
|
100
100
|
AssetDeleteSummary,
|
|
101
101
|
AssetUploadProgress,
|
|
102
102
|
AssetUpdateVisibilityRequest,
|
|
103
|
-
AssetUpdateVisibilityResponse
|
|
103
|
+
AssetUpdateVisibilityResponse,
|
|
104
|
+
// Account storage usage
|
|
105
|
+
AccountStorageCategoryUsage,
|
|
106
|
+
AccountStorageUsageResponse
|
|
104
107
|
} from './models/interfaces';
|
|
105
108
|
|
|
106
109
|
export type {
|
package/src/models/interfaces.ts
CHANGED
|
@@ -384,6 +384,29 @@ export interface AssetUpdateVisibilityResponse {
|
|
|
384
384
|
};
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
/**
|
|
388
|
+
* Account storage usage (server-side usage, not local AsyncStorage)
|
|
389
|
+
*/
|
|
390
|
+
export interface AccountStorageCategoryUsage {
|
|
391
|
+
bytes: number;
|
|
392
|
+
count: number;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
export interface AccountStorageUsageResponse {
|
|
396
|
+
plan: 'basic' | 'pro' | 'business';
|
|
397
|
+
totalUsedBytes: number;
|
|
398
|
+
totalLimitBytes: number;
|
|
399
|
+
categories: {
|
|
400
|
+
documents: AccountStorageCategoryUsage;
|
|
401
|
+
mail: AccountStorageCategoryUsage;
|
|
402
|
+
photosVideos: AccountStorageCategoryUsage;
|
|
403
|
+
recordings: AccountStorageCategoryUsage;
|
|
404
|
+
family: AccountStorageCategoryUsage;
|
|
405
|
+
other: AccountStorageCategoryUsage;
|
|
406
|
+
};
|
|
407
|
+
updatedAt: string;
|
|
408
|
+
}
|
|
409
|
+
|
|
387
410
|
export interface AssetUploadProgress {
|
|
388
411
|
fileId: string;
|
|
389
412
|
uploaded: number;
|
|
@@ -27,7 +27,7 @@ const GroupedSectionComponent = ({ items }: GroupedSectionProps) => {
|
|
|
27
27
|
return (
|
|
28
28
|
<View style={{ width: '100%' }}>
|
|
29
29
|
{items.map((item, index) => (
|
|
30
|
-
<View key={item.id} style={{ marginBottom: index < items.length - 1 ? 4 : 0 }}>
|
|
30
|
+
<View key={`${item.id}-${index}`} style={{ marginBottom: index < items.length - 1 ? 4 : 0 }}>
|
|
31
31
|
<GroupedItem
|
|
32
32
|
icon={item.icon}
|
|
33
33
|
iconColor={item.iconColor}
|
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { useEffect, useRef, type FC } from 'react';
|
|
1
|
+
import { useEffect, useRef, useState, type FC } from 'react';
|
|
2
2
|
import { AppState } from 'react-native';
|
|
3
3
|
import { GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
4
4
|
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
|
5
5
|
import { KeyboardProvider } from 'react-native-keyboard-controller';
|
|
6
6
|
import type { OxyProviderProps } from '../types/navigation';
|
|
7
7
|
import { OxyContextProvider } from '../context/OxyContext';
|
|
8
|
-
import {
|
|
8
|
+
import { QueryClientProvider, focusManager, onlineManager } from '@tanstack/react-query';
|
|
9
9
|
import { setupFonts } from './FontLoader';
|
|
10
10
|
import BottomSheetRouter from './BottomSheetRouter';
|
|
11
11
|
import { Toaster } from '../../lib/sonner';
|
|
12
|
+
import { createQueryClient } from '../hooks/queryClient';
|
|
13
|
+
import { useStorage } from '../hooks/useStorage';
|
|
12
14
|
|
|
13
15
|
// Initialize fonts automatically
|
|
14
16
|
setupFonts();
|
|
@@ -26,31 +28,32 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
26
28
|
onAuthStateChange,
|
|
27
29
|
storageKeyPrefix,
|
|
28
30
|
baseURL,
|
|
29
|
-
queryClient,
|
|
31
|
+
queryClient: providedQueryClient,
|
|
30
32
|
}) => {
|
|
31
33
|
// contextOnly is retained for backwards compatibility while the UI-only
|
|
32
34
|
// bottom sheet experience is removed. At the moment both modes behave the same.
|
|
33
35
|
void contextOnly;
|
|
34
36
|
|
|
35
|
-
//
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
37
|
+
// Get storage for query persistence
|
|
38
|
+
const { storage, isReady: isStorageReady } = useStorage();
|
|
39
|
+
|
|
40
|
+
// Initialize React Query Client with persistence
|
|
41
|
+
const queryClientRef = useRef<ReturnType<typeof createQueryClient> | null>(null);
|
|
42
|
+
const [queryClient, setQueryClient] = useState<ReturnType<typeof createQueryClient> | null>(null);
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (providedQueryClient) {
|
|
46
|
+
queryClientRef.current = providedQueryClient;
|
|
47
|
+
setQueryClient(providedQueryClient);
|
|
48
|
+
} else if (isStorageReady) {
|
|
49
|
+
// Create query client with persistence once storage is ready
|
|
50
|
+
if (!queryClientRef.current) {
|
|
51
|
+
const client = createQueryClient(storage);
|
|
52
|
+
queryClientRef.current = client;
|
|
53
|
+
setQueryClient(client);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}, [providedQueryClient, isStorageReady, storage]);
|
|
54
57
|
|
|
55
58
|
// Hook React Query focus manager into React Native AppState
|
|
56
59
|
useEffect(() => {
|
|
@@ -62,28 +65,79 @@ const OxyProvider: FC<OxyProviderProps> = ({
|
|
|
62
65
|
};
|
|
63
66
|
}, []);
|
|
64
67
|
|
|
68
|
+
// Setup network status monitoring for offline detection
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
let cleanup: (() => void) | undefined;
|
|
71
|
+
|
|
72
|
+
const setupNetworkMonitoring = async () => {
|
|
73
|
+
try {
|
|
74
|
+
// For React Native, try to use NetInfo
|
|
75
|
+
if (typeof window === 'undefined' || (typeof navigator !== 'undefined' && navigator.product === 'ReactNative')) {
|
|
76
|
+
try {
|
|
77
|
+
const NetInfo = await import('@react-native-community/netinfo');
|
|
78
|
+
const state = await NetInfo.default.fetch();
|
|
79
|
+
onlineManager.setOnline(state.isConnected ?? true);
|
|
80
|
+
|
|
81
|
+
const unsubscribe = NetInfo.default.addEventListener((state: { isConnected: boolean | null }) => {
|
|
82
|
+
onlineManager.setOnline(state.isConnected ?? true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
cleanup = () => unsubscribe();
|
|
86
|
+
} catch {
|
|
87
|
+
// NetInfo not available, default to online
|
|
88
|
+
onlineManager.setOnline(true);
|
|
89
|
+
}
|
|
90
|
+
} else {
|
|
91
|
+
// For web, use navigator.onLine
|
|
92
|
+
onlineManager.setOnline(navigator.onLine);
|
|
93
|
+
const handleOnline = () => onlineManager.setOnline(true);
|
|
94
|
+
const handleOffline = () => onlineManager.setOnline(false);
|
|
95
|
+
|
|
96
|
+
window.addEventListener('online', handleOnline);
|
|
97
|
+
window.addEventListener('offline', handleOffline);
|
|
98
|
+
|
|
99
|
+
cleanup = () => {
|
|
100
|
+
window.removeEventListener('online', handleOnline);
|
|
101
|
+
window.removeEventListener('offline', handleOffline);
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
} catch (error) {
|
|
105
|
+
// Default to online if detection fails
|
|
106
|
+
onlineManager.setOnline(true);
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
setupNetworkMonitoring();
|
|
111
|
+
|
|
112
|
+
return () => {
|
|
113
|
+
cleanup?.();
|
|
114
|
+
};
|
|
115
|
+
}, []);
|
|
116
|
+
|
|
65
117
|
// Ensure we have a valid QueryClient
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
118
|
+
if (!queryClient) {
|
|
119
|
+
// Return loading state or fallback
|
|
120
|
+
return null;
|
|
69
121
|
}
|
|
70
122
|
|
|
71
123
|
return (
|
|
72
124
|
<SafeAreaProvider>
|
|
73
125
|
<GestureHandlerRootView style={{ flex: 1 }}>
|
|
74
126
|
<KeyboardProvider>
|
|
75
|
-
|
|
76
|
-
<
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
127
|
+
{queryClient && (
|
|
128
|
+
<QueryClientProvider client={queryClient}>
|
|
129
|
+
<OxyContextProvider
|
|
130
|
+
oxyServices={oxyServices as any}
|
|
131
|
+
baseURL={baseURL}
|
|
132
|
+
storageKeyPrefix={storageKeyPrefix}
|
|
133
|
+
onAuthStateChange={onAuthStateChange as any}
|
|
134
|
+
>
|
|
135
|
+
{children}
|
|
136
|
+
<BottomSheetRouter />
|
|
137
|
+
<Toaster />
|
|
138
|
+
</OxyContextProvider>
|
|
139
|
+
</QueryClientProvider>
|
|
140
|
+
)}
|
|
87
141
|
</KeyboardProvider>
|
|
88
142
|
</GestureHandlerRootView>
|
|
89
143
|
</SafeAreaProvider>
|
|
@@ -36,7 +36,7 @@ export const EditDisplayNameModal: React.FC<EditDisplayNameModalProps> = ({
|
|
|
36
36
|
const colorScheme = useColorScheme();
|
|
37
37
|
const themeStyles = useThemeStyles(theme || 'light', colorScheme);
|
|
38
38
|
const colors = themeStyles.colors;
|
|
39
|
-
const {
|
|
39
|
+
const { saveProfile, isSaving } = useProfileEditing();
|
|
40
40
|
|
|
41
41
|
const [displayName, setDisplayName] = useState(initialDisplayName);
|
|
42
42
|
const [lastName, setLastName] = useState(initialLastName);
|
|
@@ -48,8 +48,6 @@ export const EditDisplayNameModal: React.FC<EditDisplayNameModalProps> = ({
|
|
|
48
48
|
}
|
|
49
49
|
}, [visible, initialDisplayName, initialLastName]);
|
|
50
50
|
|
|
51
|
-
const { saveProfile } = useProfileEditing();
|
|
52
|
-
|
|
53
51
|
const handleSave = async () => {
|
|
54
52
|
const updates = {
|
|
55
53
|
displayName,
|
|
@@ -26,6 +26,9 @@ 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';
|
|
29
32
|
|
|
30
33
|
export interface OxyContextState {
|
|
31
34
|
user: User | null;
|
|
@@ -34,6 +37,7 @@ export interface OxyContextState {
|
|
|
34
37
|
isAuthenticated: boolean;
|
|
35
38
|
isLoading: boolean;
|
|
36
39
|
isTokenReady: boolean;
|
|
40
|
+
isStorageReady: boolean;
|
|
37
41
|
error: string | null;
|
|
38
42
|
currentLanguage: string;
|
|
39
43
|
currentLanguageMetadata: ReturnType<typeof useLanguageManagement>['metadata'];
|
|
@@ -48,6 +52,7 @@ export interface OxyContextState {
|
|
|
48
52
|
getPublicKey: () => Promise<string | null>;
|
|
49
53
|
isIdentitySynced: () => Promise<boolean>;
|
|
50
54
|
syncIdentity: () => Promise<User>;
|
|
55
|
+
deleteIdentityAndClearAccount: (skipBackup?: boolean, force?: boolean, userConfirmed?: boolean) => Promise<void>;
|
|
51
56
|
|
|
52
57
|
// Identity sync state (reactive, from Zustand store)
|
|
53
58
|
identitySyncState: {
|
|
@@ -73,6 +78,8 @@ export interface OxyContextState {
|
|
|
73
78
|
>;
|
|
74
79
|
logoutAllDeviceSessions: () => Promise<void>;
|
|
75
80
|
updateDeviceName: (deviceName: string) => Promise<void>;
|
|
81
|
+
clearSessionState: () => Promise<void>;
|
|
82
|
+
clearAllAccountData: () => Promise<void>;
|
|
76
83
|
oxyServices: OxyServices;
|
|
77
84
|
useFollow?: UseFollowHook;
|
|
78
85
|
showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
|
|
@@ -182,7 +189,55 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
182
189
|
|
|
183
190
|
const storageKeys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
|
|
184
191
|
|
|
185
|
-
const { storage } = useStorage({ onError, logger });
|
|
192
|
+
const { storage, isReady: isStorageReady } = useStorage({ onError, logger });
|
|
193
|
+
|
|
194
|
+
// Identity integrity check and auto-restore on startup
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (!storage || !isStorageReady) return;
|
|
197
|
+
|
|
198
|
+
const checkAndRestoreIdentity = async () => {
|
|
199
|
+
try {
|
|
200
|
+
const { KeyManager } = await import('../../crypto/index.js');
|
|
201
|
+
// Check if identity exists and verify integrity
|
|
202
|
+
const hasIdentity = await KeyManager.hasIdentity();
|
|
203
|
+
if (hasIdentity) {
|
|
204
|
+
const isValid = await KeyManager.verifyIdentityIntegrity();
|
|
205
|
+
if (!isValid) {
|
|
206
|
+
// Try to restore from backup
|
|
207
|
+
const restored = await KeyManager.restoreIdentityFromBackup();
|
|
208
|
+
if (restored) {
|
|
209
|
+
if (__DEV__) {
|
|
210
|
+
logger('Identity restored from backup successfully');
|
|
211
|
+
}
|
|
212
|
+
} else {
|
|
213
|
+
if (__DEV__) {
|
|
214
|
+
logger('Identity integrity check failed - user may need to restore from recovery phrase');
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
// Identity is valid - ensure backup is up to date
|
|
219
|
+
await KeyManager.backupIdentity();
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// No identity - try to restore from backup
|
|
223
|
+
const restored = await KeyManager.restoreIdentityFromBackup();
|
|
224
|
+
if (restored && __DEV__) {
|
|
225
|
+
logger('Identity restored from backup on startup');
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (__DEV__) {
|
|
230
|
+
logger('Error during identity integrity check', error);
|
|
231
|
+
}
|
|
232
|
+
// Don't block app startup - user can recover with recovery phrase
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
checkAndRestoreIdentity();
|
|
237
|
+
}, [storage, isStorageReady, logger]);
|
|
238
|
+
|
|
239
|
+
// Offline queuing is now handled by TanStack Query mutations
|
|
240
|
+
// No need for custom offline queue
|
|
186
241
|
|
|
187
242
|
const {
|
|
188
243
|
currentLanguage,
|
|
@@ -198,6 +253,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
198
253
|
logger,
|
|
199
254
|
});
|
|
200
255
|
|
|
256
|
+
const queryClient = useQueryClient();
|
|
257
|
+
|
|
201
258
|
const {
|
|
202
259
|
sessions,
|
|
203
260
|
activeSessionId,
|
|
@@ -220,6 +277,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
220
277
|
setAuthError: (message) => setAuthState({ error: message }),
|
|
221
278
|
logger,
|
|
222
279
|
setTokenReady,
|
|
280
|
+
queryClient,
|
|
223
281
|
});
|
|
224
282
|
|
|
225
283
|
const {
|
|
@@ -231,7 +289,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
231
289
|
hasIdentity,
|
|
232
290
|
getPublicKey,
|
|
233
291
|
isIdentitySynced,
|
|
234
|
-
syncIdentity,
|
|
292
|
+
syncIdentity: syncIdentityBase,
|
|
235
293
|
} = useAuthOperations({
|
|
236
294
|
oxyServices,
|
|
237
295
|
storage,
|
|
@@ -254,6 +312,124 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
254
312
|
logger,
|
|
255
313
|
});
|
|
256
314
|
|
|
315
|
+
// syncIdentity - TanStack Query handles offline mutations automatically
|
|
316
|
+
const syncIdentity = useCallback(async () => {
|
|
317
|
+
return await syncIdentityBase();
|
|
318
|
+
}, [syncIdentityBase]);
|
|
319
|
+
|
|
320
|
+
// Clear all account data when identity is lost (for accounts app)
|
|
321
|
+
// In accounts app, identity = account, so losing identity means losing everything
|
|
322
|
+
const clearAllAccountData = useCallback(async (): Promise<void> => {
|
|
323
|
+
// Clear TanStack Query cache (in-memory)
|
|
324
|
+
queryClient.clear();
|
|
325
|
+
|
|
326
|
+
// Clear persisted query cache
|
|
327
|
+
if (storage) {
|
|
328
|
+
try {
|
|
329
|
+
await clearQueryCache(storage);
|
|
330
|
+
} catch (error) {
|
|
331
|
+
if (logger) {
|
|
332
|
+
logger('Failed to clear persisted query cache', error);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Clear session state (sessions, activeSessionId, storage)
|
|
338
|
+
await clearSessionState();
|
|
339
|
+
|
|
340
|
+
// Clear identity sync state from storage
|
|
341
|
+
if (storage) {
|
|
342
|
+
try {
|
|
343
|
+
await storage.removeItem('oxy_identity_synced');
|
|
344
|
+
} catch (error) {
|
|
345
|
+
if (logger) {
|
|
346
|
+
logger('Failed to clear identity sync state', error);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// Reset auth store identity sync state
|
|
352
|
+
useAuthStore.getState().setIdentitySynced(false);
|
|
353
|
+
useAuthStore.getState().setSyncing(false);
|
|
354
|
+
|
|
355
|
+
// Reset account store
|
|
356
|
+
useAccountStore.getState().reset();
|
|
357
|
+
|
|
358
|
+
// Clear HTTP service cache
|
|
359
|
+
oxyServices.clearCache();
|
|
360
|
+
}, [queryClient, storage, clearSessionState, logger, oxyServices]);
|
|
361
|
+
|
|
362
|
+
// Delete identity and clear all account data
|
|
363
|
+
// In accounts app, deleting identity means losing the account completely
|
|
364
|
+
const deleteIdentityAndClearAccount = useCallback(async (
|
|
365
|
+
skipBackup: boolean = false,
|
|
366
|
+
force: boolean = false,
|
|
367
|
+
userConfirmed: boolean = false
|
|
368
|
+
): Promise<void> => {
|
|
369
|
+
// First, clear all account data
|
|
370
|
+
await clearAllAccountData();
|
|
371
|
+
|
|
372
|
+
// Then delete the identity keys
|
|
373
|
+
const { KeyManager } = await import('../../crypto/keyManager.js');
|
|
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,
|