@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.
Files changed (190) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.assets.js +15 -0
  2. package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -1
  3. package/lib/commonjs/core/mixins/OxyServices.user.js +14 -13
  4. package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
  5. package/lib/commonjs/crypto/keyManager.js +164 -3
  6. package/lib/commonjs/crypto/keyManager.js.map +1 -1
  7. package/lib/commonjs/crypto/signatureService.js +26 -0
  8. package/lib/commonjs/crypto/signatureService.js.map +1 -1
  9. package/lib/commonjs/index.js.map +1 -1
  10. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  11. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  12. package/lib/commonjs/ui/components/OxyProvider.js +71 -24
  13. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  14. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js +1 -4
  15. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  16. package/lib/commonjs/ui/context/OxyContext.js +177 -4
  17. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  18. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +148 -49
  19. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  20. package/lib/commonjs/ui/context/hooks/useSessionManagement.js +22 -2
  21. package/lib/commonjs/ui/context/hooks/useSessionManagement.js.map +1 -1
  22. package/lib/commonjs/ui/hooks/mutations/index.js +28 -0
  23. package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -0
  24. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +314 -0
  25. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -0
  26. package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js +193 -0
  27. package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js.map +1 -0
  28. package/lib/commonjs/ui/hooks/queries/index.js +39 -0
  29. package/lib/commonjs/ui/hooks/queries/index.js.map +1 -0
  30. package/lib/commonjs/ui/hooks/queries/queryKeys.js +85 -0
  31. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -0
  32. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +145 -0
  33. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -0
  34. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +138 -0
  35. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -0
  36. package/lib/commonjs/ui/hooks/queryClient.js +117 -0
  37. package/lib/commonjs/ui/hooks/queryClient.js.map +1 -0
  38. package/lib/commonjs/ui/hooks/useIdentityMutations.js +111 -0
  39. package/lib/commonjs/ui/hooks/useIdentityMutations.js.map +1 -0
  40. package/lib/commonjs/ui/hooks/useProfileEditing.js +42 -58
  41. package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
  42. package/lib/commonjs/ui/hooks/useQueryClient.js +20 -0
  43. package/lib/commonjs/ui/hooks/useQueryClient.js.map +1 -0
  44. package/lib/commonjs/ui/hooks/useSessionManagement.js +22 -2
  45. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  46. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +43 -42
  47. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  48. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +63 -58
  49. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  50. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +6 -6
  51. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  52. package/lib/commonjs/ui/stores/accountStore.js +57 -42
  53. package/lib/commonjs/ui/stores/accountStore.js.map +1 -1
  54. package/lib/commonjs/ui/stores/authStore.js +4 -25
  55. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  56. package/lib/module/core/mixins/OxyServices.assets.js +15 -0
  57. package/lib/module/core/mixins/OxyServices.assets.js.map +1 -1
  58. package/lib/module/core/mixins/OxyServices.user.js +14 -13
  59. package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
  60. package/lib/module/crypto/keyManager.js +164 -3
  61. package/lib/module/crypto/keyManager.js.map +1 -1
  62. package/lib/module/crypto/signatureService.js +26 -0
  63. package/lib/module/crypto/signatureService.js.map +1 -1
  64. package/lib/module/index.js.map +1 -1
  65. package/lib/module/ui/components/GroupedSection.js +1 -1
  66. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  67. package/lib/module/ui/components/OxyProvider.js +72 -25
  68. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  69. package/lib/module/ui/components/profile/EditDisplayNameModal.js +1 -4
  70. package/lib/module/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  71. package/lib/module/ui/context/OxyContext.js +176 -4
  72. package/lib/module/ui/context/OxyContext.js.map +1 -1
  73. package/lib/module/ui/context/hooks/useAuthOperations.js +148 -49
  74. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  75. package/lib/module/ui/context/hooks/useSessionManagement.js +22 -2
  76. package/lib/module/ui/context/hooks/useSessionManagement.js.map +1 -1
  77. package/lib/module/ui/hooks/mutations/index.js +6 -0
  78. package/lib/module/ui/hooks/mutations/index.js.map +1 -0
  79. package/lib/module/ui/hooks/mutations/useAccountMutations.js +308 -0
  80. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -0
  81. package/lib/module/ui/hooks/mutations/useServicesMutations.js +185 -0
  82. package/lib/module/ui/hooks/mutations/useServicesMutations.js.map +1 -0
  83. package/lib/module/ui/hooks/queries/index.js +7 -0
  84. package/lib/module/ui/hooks/queries/index.js.map +1 -0
  85. package/lib/module/ui/hooks/queries/queryKeys.js +78 -0
  86. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -0
  87. package/lib/module/ui/hooks/queries/useAccountQueries.js +136 -0
  88. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -0
  89. package/lib/module/ui/hooks/queries/useServicesQueries.js +130 -0
  90. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -0
  91. package/lib/module/ui/hooks/queryClient.js +110 -0
  92. package/lib/module/ui/hooks/queryClient.js.map +1 -0
  93. package/lib/module/ui/hooks/useIdentityMutations.js +105 -0
  94. package/lib/module/ui/hooks/useIdentityMutations.js.map +1 -0
  95. package/lib/module/ui/hooks/useProfileEditing.js +43 -59
  96. package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
  97. package/lib/module/ui/hooks/useQueryClient.js +15 -0
  98. package/lib/module/ui/hooks/useQueryClient.js.map +1 -0
  99. package/lib/module/ui/hooks/useSessionManagement.js +22 -2
  100. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  101. package/lib/module/ui/screens/AccountOverviewScreen.js +43 -42
  102. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  103. package/lib/module/ui/screens/AccountSettingsScreen.js +63 -58
  104. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  105. package/lib/module/ui/screens/WelcomeNewUserScreen.js +6 -6
  106. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  107. package/lib/module/ui/stores/accountStore.js +57 -42
  108. package/lib/module/ui/stores/accountStore.js.map +1 -1
  109. package/lib/module/ui/stores/authStore.js +4 -25
  110. package/lib/module/ui/stores/authStore.js.map +1 -1
  111. package/lib/typescript/core/mixins/OxyServices.assets.d.ts +7 -1
  112. package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -1
  113. package/lib/typescript/core/mixins/OxyServices.user.d.ts +4 -5
  114. package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
  115. package/lib/typescript/core/mixins/index.d.ts +1 -1
  116. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  117. package/lib/typescript/crypto/keyManager.d.ts +19 -2
  118. package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
  119. package/lib/typescript/crypto/signatureService.d.ts +5 -0
  120. package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
  121. package/lib/typescript/index.d.ts +1 -1
  122. package/lib/typescript/index.d.ts.map +1 -1
  123. package/lib/typescript/models/interfaces.d.ts +21 -0
  124. package/lib/typescript/models/interfaces.d.ts.map +1 -1
  125. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  126. package/lib/typescript/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
  127. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  128. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  129. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  130. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts +3 -1
  131. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts.map +1 -1
  132. package/lib/typescript/ui/hooks/mutations/index.d.ts +3 -0
  133. package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -0
  134. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +25 -0
  135. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -0
  136. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts +23 -0
  137. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -0
  138. package/lib/typescript/ui/hooks/queries/index.d.ts +4 -0
  139. package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -0
  140. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +56 -0
  141. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -0
  142. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +41 -0
  143. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -0
  144. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts +34 -0
  145. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -0
  146. package/lib/typescript/ui/hooks/queryClient.d.ts +19 -0
  147. package/lib/typescript/ui/hooks/queryClient.d.ts.map +1 -0
  148. package/lib/typescript/ui/hooks/useIdentityMutations.d.ts +29 -0
  149. package/lib/typescript/ui/hooks/useIdentityMutations.d.ts.map +1 -0
  150. package/lib/typescript/ui/hooks/useProfileEditing.d.ts.map +1 -1
  151. package/lib/typescript/ui/hooks/useQueryClient.d.ts +7 -0
  152. package/lib/typescript/ui/hooks/useQueryClient.d.ts.map +1 -0
  153. package/lib/typescript/ui/hooks/useSessionManagement.d.ts +3 -1
  154. package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +1 -1
  155. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  156. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  157. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  158. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -1
  159. package/lib/typescript/ui/stores/authStore.d.ts +0 -4
  160. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  161. package/package.json +6 -5
  162. package/src/core/mixins/OxyServices.assets.ts +16 -1
  163. package/src/core/mixins/OxyServices.user.ts +17 -10
  164. package/src/crypto/keyManager.ts +177 -2
  165. package/src/crypto/signatureService.ts +30 -0
  166. package/src/index.ts +4 -1
  167. package/src/models/interfaces.ts +23 -0
  168. package/src/ui/components/GroupedSection.tsx +1 -1
  169. package/src/ui/components/OxyProvider.tsx +91 -37
  170. package/src/ui/components/profile/EditDisplayNameModal.tsx +1 -3
  171. package/src/ui/context/OxyContext.tsx +185 -2
  172. package/src/ui/context/hooks/useAuthOperations.ts +171 -58
  173. package/src/ui/context/hooks/useSessionManagement.ts +24 -1
  174. package/src/ui/hooks/mutations/index.ts +4 -0
  175. package/src/ui/hooks/mutations/useAccountMutations.ts +277 -0
  176. package/src/ui/hooks/mutations/useServicesMutations.ts +164 -0
  177. package/src/ui/hooks/queries/index.ts +5 -0
  178. package/src/ui/hooks/queries/queryKeys.ts +73 -0
  179. package/src/ui/hooks/queries/useAccountQueries.ts +126 -0
  180. package/src/ui/hooks/queries/useServicesQueries.ts +121 -0
  181. package/src/ui/hooks/queryClient.ts +112 -0
  182. package/src/ui/hooks/useIdentityMutations.ts +115 -0
  183. package/src/ui/hooks/useProfileEditing.ts +46 -60
  184. package/src/ui/hooks/useQueryClient.ts +17 -0
  185. package/src/ui/hooks/useSessionManagement.ts +24 -1
  186. package/src/ui/screens/AccountOverviewScreen.tsx +38 -46
  187. package/src/ui/screens/AccountSettingsScreen.tsx +54 -54
  188. package/src/ui/screens/WelcomeNewUserScreen.tsx +13 -12
  189. package/src/ui/stores/accountStore.ts +54 -43
  190. 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 {
@@ -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 { QueryClient, QueryClientProvider, focusManager } from '@tanstack/react-query';
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
- // Initialize React Query Client (use provided client or create a default one once)
36
- const queryClientRef = useRef<QueryClient | null>(null);
37
- if (!queryClientRef.current) {
38
- const defaultClient = new QueryClient({
39
- defaultOptions: {
40
- queries: {
41
- staleTime: 30_000,
42
- gcTime: 5 * 60_000,
43
- retry: 2,
44
- refetchOnReconnect: true,
45
- refetchOnWindowFocus: false,
46
- },
47
- mutations: {
48
- retry: 1,
49
- },
50
- },
51
- });
52
- queryClientRef.current = queryClient ?? defaultClient;
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
- const client = queryClientRef.current;
67
- if (!client) {
68
- throw new Error('QueryClient initialization failed');
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
- <QueryClientProvider client={client}>
76
- <OxyContextProvider
77
- oxyServices={oxyServices as any}
78
- baseURL={baseURL}
79
- storageKeyPrefix={storageKeyPrefix}
80
- onAuthStateChange={onAuthStateChange as any}
81
- >
82
- {children}
83
- <BottomSheetRouter />
84
- <Toaster />
85
- </OxyContextProvider>
86
- </QueryClientProvider>
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 { updateField, isSaving } = useProfileEditing();
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,