@oxyhq/services 5.16.1 → 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 (175) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.user.js +14 -13
  2. package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
  3. package/lib/commonjs/crypto/keyManager.js +164 -3
  4. package/lib/commonjs/crypto/keyManager.js.map +1 -1
  5. package/lib/commonjs/crypto/signatureService.js +26 -0
  6. package/lib/commonjs/crypto/signatureService.js.map +1 -1
  7. package/lib/commonjs/ui/components/GroupedSection.js +1 -1
  8. package/lib/commonjs/ui/components/GroupedSection.js.map +1 -1
  9. package/lib/commonjs/ui/components/OxyProvider.js +71 -24
  10. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  11. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js +1 -4
  12. package/lib/commonjs/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  13. package/lib/commonjs/ui/context/OxyContext.js +177 -4
  14. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  15. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +148 -49
  16. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  17. package/lib/commonjs/ui/context/hooks/useSessionManagement.js +22 -2
  18. package/lib/commonjs/ui/context/hooks/useSessionManagement.js.map +1 -1
  19. package/lib/commonjs/ui/hooks/mutations/index.js +28 -0
  20. package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -0
  21. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +314 -0
  22. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -0
  23. package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js +193 -0
  24. package/lib/commonjs/ui/hooks/mutations/useServicesMutations.js.map +1 -0
  25. package/lib/commonjs/ui/hooks/queries/index.js +39 -0
  26. package/lib/commonjs/ui/hooks/queries/index.js.map +1 -0
  27. package/lib/commonjs/ui/hooks/queries/queryKeys.js +85 -0
  28. package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -0
  29. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +145 -0
  30. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -0
  31. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +138 -0
  32. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -0
  33. package/lib/commonjs/ui/hooks/queryClient.js +117 -0
  34. package/lib/commonjs/ui/hooks/queryClient.js.map +1 -0
  35. package/lib/commonjs/ui/hooks/useIdentityMutations.js +111 -0
  36. package/lib/commonjs/ui/hooks/useIdentityMutations.js.map +1 -0
  37. package/lib/commonjs/ui/hooks/useProfileEditing.js +42 -58
  38. package/lib/commonjs/ui/hooks/useProfileEditing.js.map +1 -1
  39. package/lib/commonjs/ui/hooks/useQueryClient.js +20 -0
  40. package/lib/commonjs/ui/hooks/useQueryClient.js.map +1 -0
  41. package/lib/commonjs/ui/hooks/useSessionManagement.js +22 -2
  42. package/lib/commonjs/ui/hooks/useSessionManagement.js.map +1 -1
  43. package/lib/commonjs/ui/screens/AccountOverviewScreen.js +43 -42
  44. package/lib/commonjs/ui/screens/AccountOverviewScreen.js.map +1 -1
  45. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +63 -58
  46. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  47. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js +6 -6
  48. package/lib/commonjs/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  49. package/lib/commonjs/ui/stores/accountStore.js +57 -42
  50. package/lib/commonjs/ui/stores/accountStore.js.map +1 -1
  51. package/lib/commonjs/ui/stores/authStore.js +4 -25
  52. package/lib/commonjs/ui/stores/authStore.js.map +1 -1
  53. package/lib/module/core/mixins/OxyServices.user.js +14 -13
  54. package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
  55. package/lib/module/crypto/keyManager.js +164 -3
  56. package/lib/module/crypto/keyManager.js.map +1 -1
  57. package/lib/module/crypto/signatureService.js +26 -0
  58. package/lib/module/crypto/signatureService.js.map +1 -1
  59. package/lib/module/ui/components/GroupedSection.js +1 -1
  60. package/lib/module/ui/components/GroupedSection.js.map +1 -1
  61. package/lib/module/ui/components/OxyProvider.js +72 -25
  62. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  63. package/lib/module/ui/components/profile/EditDisplayNameModal.js +1 -4
  64. package/lib/module/ui/components/profile/EditDisplayNameModal.js.map +1 -1
  65. package/lib/module/ui/context/OxyContext.js +176 -4
  66. package/lib/module/ui/context/OxyContext.js.map +1 -1
  67. package/lib/module/ui/context/hooks/useAuthOperations.js +148 -49
  68. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  69. package/lib/module/ui/context/hooks/useSessionManagement.js +22 -2
  70. package/lib/module/ui/context/hooks/useSessionManagement.js.map +1 -1
  71. package/lib/module/ui/hooks/mutations/index.js +6 -0
  72. package/lib/module/ui/hooks/mutations/index.js.map +1 -0
  73. package/lib/module/ui/hooks/mutations/useAccountMutations.js +308 -0
  74. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -0
  75. package/lib/module/ui/hooks/mutations/useServicesMutations.js +185 -0
  76. package/lib/module/ui/hooks/mutations/useServicesMutations.js.map +1 -0
  77. package/lib/module/ui/hooks/queries/index.js +7 -0
  78. package/lib/module/ui/hooks/queries/index.js.map +1 -0
  79. package/lib/module/ui/hooks/queries/queryKeys.js +78 -0
  80. package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -0
  81. package/lib/module/ui/hooks/queries/useAccountQueries.js +136 -0
  82. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -0
  83. package/lib/module/ui/hooks/queries/useServicesQueries.js +130 -0
  84. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -0
  85. package/lib/module/ui/hooks/queryClient.js +110 -0
  86. package/lib/module/ui/hooks/queryClient.js.map +1 -0
  87. package/lib/module/ui/hooks/useIdentityMutations.js +105 -0
  88. package/lib/module/ui/hooks/useIdentityMutations.js.map +1 -0
  89. package/lib/module/ui/hooks/useProfileEditing.js +43 -59
  90. package/lib/module/ui/hooks/useProfileEditing.js.map +1 -1
  91. package/lib/module/ui/hooks/useQueryClient.js +15 -0
  92. package/lib/module/ui/hooks/useQueryClient.js.map +1 -0
  93. package/lib/module/ui/hooks/useSessionManagement.js +22 -2
  94. package/lib/module/ui/hooks/useSessionManagement.js.map +1 -1
  95. package/lib/module/ui/screens/AccountOverviewScreen.js +43 -42
  96. package/lib/module/ui/screens/AccountOverviewScreen.js.map +1 -1
  97. package/lib/module/ui/screens/AccountSettingsScreen.js +63 -58
  98. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  99. package/lib/module/ui/screens/WelcomeNewUserScreen.js +6 -6
  100. package/lib/module/ui/screens/WelcomeNewUserScreen.js.map +1 -1
  101. package/lib/module/ui/stores/accountStore.js +57 -42
  102. package/lib/module/ui/stores/accountStore.js.map +1 -1
  103. package/lib/module/ui/stores/authStore.js +4 -25
  104. package/lib/module/ui/stores/authStore.js.map +1 -1
  105. package/lib/typescript/core/mixins/OxyServices.user.d.ts +4 -5
  106. package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
  107. package/lib/typescript/core/mixins/index.d.ts +0 -1
  108. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  109. package/lib/typescript/crypto/keyManager.d.ts +19 -2
  110. package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
  111. package/lib/typescript/crypto/signatureService.d.ts +5 -0
  112. package/lib/typescript/crypto/signatureService.d.ts.map +1 -1
  113. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  114. package/lib/typescript/ui/components/profile/EditDisplayNameModal.d.ts.map +1 -1
  115. package/lib/typescript/ui/context/OxyContext.d.ts +4 -0
  116. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  117. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  118. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts +3 -1
  119. package/lib/typescript/ui/context/hooks/useSessionManagement.d.ts.map +1 -1
  120. package/lib/typescript/ui/hooks/mutations/index.d.ts +3 -0
  121. package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -0
  122. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +25 -0
  123. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -0
  124. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts +23 -0
  125. package/lib/typescript/ui/hooks/mutations/useServicesMutations.d.ts.map +1 -0
  126. package/lib/typescript/ui/hooks/queries/index.d.ts +4 -0
  127. package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -0
  128. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +56 -0
  129. package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -0
  130. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +41 -0
  131. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -0
  132. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts +34 -0
  133. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -0
  134. package/lib/typescript/ui/hooks/queryClient.d.ts +19 -0
  135. package/lib/typescript/ui/hooks/queryClient.d.ts.map +1 -0
  136. package/lib/typescript/ui/hooks/useIdentityMutations.d.ts +29 -0
  137. package/lib/typescript/ui/hooks/useIdentityMutations.d.ts.map +1 -0
  138. package/lib/typescript/ui/hooks/useProfileEditing.d.ts.map +1 -1
  139. package/lib/typescript/ui/hooks/useQueryClient.d.ts +7 -0
  140. package/lib/typescript/ui/hooks/useQueryClient.d.ts.map +1 -0
  141. package/lib/typescript/ui/hooks/useSessionManagement.d.ts +3 -1
  142. package/lib/typescript/ui/hooks/useSessionManagement.d.ts.map +1 -1
  143. package/lib/typescript/ui/screens/AccountOverviewScreen.d.ts.map +1 -1
  144. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  145. package/lib/typescript/ui/screens/WelcomeNewUserScreen.d.ts.map +1 -1
  146. package/lib/typescript/ui/stores/accountStore.d.ts.map +1 -1
  147. package/lib/typescript/ui/stores/authStore.d.ts +0 -4
  148. package/lib/typescript/ui/stores/authStore.d.ts.map +1 -1
  149. package/package.json +5 -4
  150. package/src/core/mixins/OxyServices.user.ts +17 -10
  151. package/src/crypto/keyManager.ts +177 -2
  152. package/src/crypto/signatureService.ts +30 -0
  153. package/src/ui/components/GroupedSection.tsx +1 -1
  154. package/src/ui/components/OxyProvider.tsx +91 -37
  155. package/src/ui/components/profile/EditDisplayNameModal.tsx +1 -3
  156. package/src/ui/context/OxyContext.tsx +185 -2
  157. package/src/ui/context/hooks/useAuthOperations.ts +171 -58
  158. package/src/ui/context/hooks/useSessionManagement.ts +24 -1
  159. package/src/ui/hooks/mutations/index.ts +4 -0
  160. package/src/ui/hooks/mutations/useAccountMutations.ts +277 -0
  161. package/src/ui/hooks/mutations/useServicesMutations.ts +164 -0
  162. package/src/ui/hooks/queries/index.ts +5 -0
  163. package/src/ui/hooks/queries/queryKeys.ts +73 -0
  164. package/src/ui/hooks/queries/useAccountQueries.ts +126 -0
  165. package/src/ui/hooks/queries/useServicesQueries.ts +121 -0
  166. package/src/ui/hooks/queryClient.ts +112 -0
  167. package/src/ui/hooks/useIdentityMutations.ts +115 -0
  168. package/src/ui/hooks/useProfileEditing.ts +46 -60
  169. package/src/ui/hooks/useQueryClient.ts +17 -0
  170. package/src/ui/hooks/useSessionManagement.ts +24 -1
  171. package/src/ui/screens/AccountOverviewScreen.tsx +38 -46
  172. package/src/ui/screens/AccountSettingsScreen.tsx +54 -54
  173. package/src/ui/screens/WelcomeNewUserScreen.tsx +13 -12
  174. package/src/ui/stores/accountStore.ts +54 -43
  175. package/src/ui/stores/authStore.ts +3 -17
@@ -0,0 +1,121 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import type { ClientSession } from '../../../models/session';
3
+ import { queryKeys } from './queryKeys';
4
+ import { useOxy } from '../../context/OxyContext';
5
+ import { fetchSessionsWithFallback, mapSessionsToClient } from '../../context/utils/sessionHelpers';
6
+
7
+ /**
8
+ * Get all active sessions for the current user
9
+ */
10
+ export const useSessions = (userId?: string, options?: { enabled?: boolean }) => {
11
+ const { oxyServices, activeSessionId } = useOxy();
12
+
13
+ return useQuery({
14
+ queryKey: queryKeys.sessions.list(userId),
15
+ queryFn: async () => {
16
+ if (!activeSessionId) {
17
+ throw new Error('No active session');
18
+ }
19
+
20
+ const sessions = await fetchSessionsWithFallback(oxyServices, activeSessionId, {
21
+ fallbackDeviceId: undefined,
22
+ fallbackUserId: userId,
23
+ });
24
+
25
+ return mapSessionsToClient(sessions, activeSessionId);
26
+ },
27
+ enabled: (options?.enabled !== false) && !!activeSessionId,
28
+ staleTime: 2 * 60 * 1000, // 2 minutes (sessions change frequently)
29
+ gcTime: 10 * 60 * 1000, // 10 minutes
30
+ });
31
+ };
32
+
33
+ /**
34
+ * Get specific session by ID
35
+ */
36
+ export const useSession = (sessionId: string | null, options?: { enabled?: boolean }) => {
37
+ const { oxyServices } = useOxy();
38
+
39
+ return useQuery({
40
+ queryKey: queryKeys.sessions.detail(sessionId || ''),
41
+ queryFn: async () => {
42
+ if (!sessionId) {
43
+ throw new Error('Session ID is required');
44
+ }
45
+
46
+ const validation = await oxyServices.validateSession(sessionId, { useHeaderValidation: true });
47
+ if (!validation?.valid || !validation.user) {
48
+ throw new Error('Session not found or invalid');
49
+ }
50
+
51
+ const now = new Date();
52
+ return {
53
+ sessionId,
54
+ deviceId: '', // Device ID not available from validation response
55
+ expiresAt: validation.expiresAt || new Date(now.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString(),
56
+ lastActive: validation.lastActivity || now.toISOString(),
57
+ userId: validation.user.id?.toString() ?? '',
58
+ isCurrent: false,
59
+ } as ClientSession;
60
+ },
61
+ enabled: (options?.enabled !== false) && !!sessionId,
62
+ staleTime: 2 * 60 * 1000,
63
+ gcTime: 10 * 60 * 1000,
64
+ });
65
+ };
66
+
67
+ /**
68
+ * Get device sessions for the current active session
69
+ */
70
+ export const useDeviceSessions = (options?: { enabled?: boolean }) => {
71
+ const { oxyServices, activeSessionId } = useOxy();
72
+
73
+ return useQuery({
74
+ queryKey: queryKeys.sessions.active(),
75
+ queryFn: async () => {
76
+ if (!activeSessionId) {
77
+ throw new Error('No active session');
78
+ }
79
+
80
+ return await oxyServices.getDeviceSessions(activeSessionId);
81
+ },
82
+ enabled: (options?.enabled !== false) && !!activeSessionId,
83
+ staleTime: 2 * 60 * 1000,
84
+ gcTime: 10 * 60 * 1000,
85
+ });
86
+ };
87
+
88
+ /**
89
+ * Get user devices
90
+ */
91
+ export const useUserDevices = (options?: { enabled?: boolean }) => {
92
+ const { oxyServices, isAuthenticated } = useOxy();
93
+
94
+ return useQuery({
95
+ queryKey: queryKeys.devices.list(),
96
+ queryFn: async () => {
97
+ return await oxyServices.getUserDevices();
98
+ },
99
+ enabled: (options?.enabled !== false) && isAuthenticated,
100
+ staleTime: 5 * 60 * 1000,
101
+ gcTime: 30 * 60 * 1000,
102
+ });
103
+ };
104
+
105
+ /**
106
+ * Get security information
107
+ */
108
+ export const useSecurityInfo = (options?: { enabled?: boolean }) => {
109
+ const { oxyServices, isAuthenticated } = useOxy();
110
+
111
+ return useQuery({
112
+ queryKey: [...queryKeys.devices.all, 'security'],
113
+ queryFn: async () => {
114
+ return await oxyServices.getSecurityInfo();
115
+ },
116
+ enabled: (options?.enabled !== false) && isAuthenticated,
117
+ staleTime: 5 * 60 * 1000,
118
+ gcTime: 30 * 60 * 1000,
119
+ });
120
+ };
121
+
@@ -0,0 +1,112 @@
1
+ import { QueryClient } from '@tanstack/react-query';
2
+ import type { StorageInterface } from '../utils/storageHelpers';
3
+
4
+ const QUERY_CACHE_KEY = 'oxy_query_cache';
5
+ const QUERY_CACHE_VERSION = '1';
6
+
7
+ /**
8
+ * Custom persistence adapter for TanStack Query using our StorageInterface
9
+ */
10
+ export const createPersistenceAdapter = (storage: StorageInterface) => {
11
+ return {
12
+ persistClient: async (client: any) => {
13
+ try {
14
+ const serialized = JSON.stringify({
15
+ clientState: client,
16
+ timestamp: Date.now(),
17
+ version: QUERY_CACHE_VERSION,
18
+ });
19
+ await storage.setItem(QUERY_CACHE_KEY, serialized);
20
+ } catch (error) {
21
+ if (__DEV__) {
22
+ console.warn('[QueryClient] Failed to persist cache:', error);
23
+ }
24
+ }
25
+ },
26
+ restoreClient: async () => {
27
+ try {
28
+ const cached = await storage.getItem(QUERY_CACHE_KEY);
29
+ if (!cached) return undefined;
30
+
31
+ const parsed = JSON.parse(cached);
32
+
33
+ // Check version compatibility
34
+ if (parsed.version !== QUERY_CACHE_VERSION) {
35
+ // Clear old cache on version mismatch
36
+ await storage.removeItem(QUERY_CACHE_KEY);
37
+ return undefined;
38
+ }
39
+
40
+ // Check if cache is too old (30 days)
41
+ const maxAge = 30 * 24 * 60 * 60 * 1000;
42
+ if (parsed.timestamp && Date.now() - parsed.timestamp > maxAge) {
43
+ await storage.removeItem(QUERY_CACHE_KEY);
44
+ return undefined;
45
+ }
46
+
47
+ return parsed.clientState;
48
+ } catch (error) {
49
+ if (__DEV__) {
50
+ console.warn('[QueryClient] Failed to restore cache:', error);
51
+ }
52
+ return undefined;
53
+ }
54
+ },
55
+ removeClient: async () => {
56
+ try {
57
+ await storage.removeItem(QUERY_CACHE_KEY);
58
+ } catch (error) {
59
+ if (__DEV__) {
60
+ console.warn('[QueryClient] Failed to remove cache:', error);
61
+ }
62
+ }
63
+ },
64
+ };
65
+ };
66
+
67
+ /**
68
+ * Create a QueryClient with offline-first configuration
69
+ */
70
+ export const createQueryClient = (storage?: StorageInterface | null): QueryClient => {
71
+ const client = new QueryClient({
72
+ defaultOptions: {
73
+ queries: {
74
+ // Data is fresh for 5 minutes
75
+ staleTime: 5 * 60 * 1000,
76
+ // Keep unused data in cache for 30 minutes
77
+ gcTime: 30 * 60 * 1000,
78
+ // Retry 3 times with exponential backoff
79
+ retry: 3,
80
+ retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
81
+ // Refetch on reconnect
82
+ refetchOnReconnect: true,
83
+ // Don't refetch on window focus (better for mobile)
84
+ refetchOnWindowFocus: false,
85
+ // Offline-first: use cache when offline
86
+ networkMode: 'offlineFirst',
87
+ },
88
+ mutations: {
89
+ // Retry once for mutations
90
+ retry: 1,
91
+ // Offline-first: queue mutations when offline
92
+ networkMode: 'offlineFirst',
93
+ },
94
+ },
95
+ });
96
+
97
+ // Note: Persistence is handled by TanStack Query's built-in persistence
98
+ // For now, we rely on the query client's default behavior with networkMode: 'offlineFirst'
99
+ // The cache will be available in memory and queries will use cached data when offline
100
+ // Full persistence to AsyncStorage can be added later with @tanstack/react-query-persist-client if needed
101
+
102
+ return client;
103
+ };
104
+
105
+ /**
106
+ * Clear persisted query cache
107
+ */
108
+ export const clearQueryCache = async (storage: StorageInterface): Promise<void> => {
109
+ const adapter = createPersistenceAdapter(storage);
110
+ await adapter.removeClient();
111
+ };
112
+
@@ -0,0 +1,115 @@
1
+ /**
2
+ * TanStack Query mutations for identity operations
3
+ * Provides offline-first mutations for identity creation, import, and sync
4
+ * Never deletes identity on errors - preserves user data
5
+ */
6
+
7
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
8
+ import type { User } from '../../models/interfaces';
9
+
10
+ export interface CreateIdentityResult {
11
+ recoveryPhrase: string[];
12
+ synced: boolean;
13
+ }
14
+
15
+ export interface ImportIdentityResult {
16
+ synced: boolean;
17
+ }
18
+
19
+ /**
20
+ * Hook for creating a new identity with offline support
21
+ * Never deletes identity on error - preserves user data
22
+ */
23
+ export function useCreateIdentity(
24
+ createIdentityFn: () => Promise<CreateIdentityResult>
25
+ ) {
26
+ const queryClient = useQueryClient();
27
+
28
+ return useMutation({
29
+ mutationFn: createIdentityFn,
30
+ onSuccess: (data) => {
31
+ // Invalidate user queries to refetch after identity creation
32
+ queryClient.invalidateQueries({ queryKey: ['user'] });
33
+ queryClient.invalidateQueries({ queryKey: ['identity'] });
34
+ },
35
+ onError: (error) => {
36
+ // Never delete identity on error - just log it
37
+ // User can recover using recovery phrase
38
+ if (__DEV__) {
39
+ console.warn('[useCreateIdentity] Identity creation error (identity may still exist):', error);
40
+ }
41
+ },
42
+ retry: false, // Don't retry identity creation
43
+ networkMode: 'offlineFirst',
44
+ });
45
+ }
46
+
47
+ /**
48
+ * Hook for importing an identity from recovery phrase
49
+ * Never deletes identity on error - preserves user data
50
+ */
51
+ export function useImportIdentity(
52
+ importIdentityFn: (phrase: string) => Promise<ImportIdentityResult>
53
+ ) {
54
+ const queryClient = useQueryClient();
55
+
56
+ return useMutation({
57
+ mutationFn: importIdentityFn,
58
+ onSuccess: (data) => {
59
+ queryClient.invalidateQueries({ queryKey: ['user'] });
60
+ queryClient.invalidateQueries({ queryKey: ['identity'] });
61
+ },
62
+ onError: (error) => {
63
+ // Never delete identity on error - just log it
64
+ if (__DEV__) {
65
+ console.warn('[useImportIdentity] Identity import error (identity may still exist):', error);
66
+ }
67
+ },
68
+ retry: false,
69
+ networkMode: 'offlineFirst',
70
+ });
71
+ }
72
+
73
+ /**
74
+ * Hook for syncing identity with server
75
+ * Never deletes identity on error - only logs and allows retry
76
+ */
77
+ export function useSyncIdentity(
78
+ syncIdentityFn: () => Promise<User>
79
+ ) {
80
+ const queryClient = useQueryClient();
81
+
82
+ return useMutation({
83
+ mutationFn: syncIdentityFn,
84
+ onSuccess: (user) => {
85
+ // Update user cache
86
+ queryClient.setQueryData(['user', 'current'], user);
87
+ queryClient.invalidateQueries({ queryKey: ['user'] });
88
+ queryClient.invalidateQueries({ queryKey: ['identity'] });
89
+ },
90
+ onError: (error) => {
91
+ // Never delete identity on error - just log it
92
+ // User can retry sync later or use recovery phrase
93
+ if (__DEV__) {
94
+ console.warn('[useSyncIdentity] Sync failed, but identity is preserved:', error);
95
+ }
96
+ },
97
+ retry: (failureCount, error: any) => {
98
+ // Retry up to 2 times for sync operations
99
+ // Don't retry if it's a network error - user can retry when online
100
+ const errorMessage = error instanceof Error ? error.message : String(error);
101
+ const isNetworkError =
102
+ errorMessage.includes('Network') ||
103
+ errorMessage.includes('Failed to fetch') ||
104
+ error?.code === 'NETWORK_ERROR';
105
+
106
+ if (isNetworkError) {
107
+ return false; // Don't retry network errors - user will retry when online
108
+ }
109
+
110
+ return failureCount < 2;
111
+ },
112
+ networkMode: 'offlineFirst',
113
+ });
114
+ }
115
+
@@ -1,8 +1,7 @@
1
- import { useState, useCallback } from 'react';
2
- import { useOxy } from '../context/OxyContext';
3
- import { useAuthStore } from '../stores/authStore';
4
- import { toast } from '../../lib/sonner';
1
+ import { useCallback } from 'react';
5
2
  import { useI18n } from './useI18n';
3
+ import { useUpdateProfile } from './mutations/useAccountMutations';
4
+ import { useAuthStore } from '../stores/authStore';
6
5
 
7
6
  export interface ProfileUpdateData {
8
7
  displayName?: string;
@@ -33,74 +32,61 @@ export interface ProfileUpdateData {
33
32
  * Provides functions to update profile fields and handle saving
34
33
  */
35
34
  export const useProfileEditing = () => {
36
- const { oxyServices, activeSessionId } = useOxy();
37
- const updateUser = useAuthStore((state) => state.updateUser);
38
35
  const { t } = useI18n();
39
- const [isSaving, setIsSaving] = useState(false);
36
+ const updateProfileMutation = useUpdateProfile();
40
37
 
41
38
  /**
42
- * Save profile updates to the server
39
+ * Save profile updates to the server using TanStack Query
43
40
  */
44
41
  const saveProfile = useCallback(async (updates: ProfileUpdateData) => {
45
- if (!oxyServices) {
46
- toast.error(t('editProfile.toasts.serviceUnavailable') || 'Service not available');
47
- return false;
48
- }
49
-
50
- try {
51
- setIsSaving(true);
42
+ // Prepare update object
43
+ const updateData: Record<string, any> = {};
52
44
 
53
- // Prepare update object
54
- const updateData: Record<string, any> = {};
55
-
56
- if (updates.username !== undefined) {
57
- updateData.username = updates.username;
58
- }
59
- if (updates.email !== undefined) {
60
- updateData.email = updates.email;
61
- }
62
- if (updates.bio !== undefined) {
63
- updateData.bio = updates.bio;
64
- }
65
- if (updates.location !== undefined || updates.locations !== undefined) {
66
- updateData.location = updates.locations && updates.locations.length > 0
67
- ? updates.locations[0].name
68
- : updates.location || '';
69
- if (updates.locations) {
70
- updateData.locations = updates.locations;
71
- }
72
- }
73
- if (updates.links !== undefined) {
74
- updateData.links = updates.links;
75
- }
76
- if (updates.linksMetadata !== undefined) {
77
- updateData.linksMetadata = updates.linksMetadata;
78
- }
79
- if (updates.avatar !== undefined) {
80
- updateData.avatar = updates.avatar;
45
+ if (updates.username !== undefined) {
46
+ updateData.username = updates.username;
47
+ }
48
+ if (updates.email !== undefined) {
49
+ updateData.email = updates.email;
50
+ }
51
+ if (updates.bio !== undefined) {
52
+ updateData.bio = updates.bio;
53
+ }
54
+ if (updates.location !== undefined || updates.locations !== undefined) {
55
+ updateData.location = updates.locations && updates.locations.length > 0
56
+ ? updates.locations[0].name
57
+ : updates.location || '';
58
+ if (updates.locations) {
59
+ updateData.locations = updates.locations;
81
60
  }
61
+ }
62
+ if (updates.links !== undefined) {
63
+ updateData.links = updates.links;
64
+ }
65
+ if (updates.linksMetadata !== undefined) {
66
+ updateData.linksMetadata = updates.linksMetadata;
67
+ }
68
+ if (updates.avatar !== undefined) {
69
+ updateData.avatar = updates.avatar;
70
+ }
82
71
 
83
- // Handle name field
84
- if (updates.displayName !== undefined || updates.lastName !== undefined) {
85
- const currentUser = useAuthStore.getState().user;
86
- const currentName = currentUser?.name;
87
- updateData.name = {
88
- first: updates.displayName ?? (typeof currentName === 'object' ? currentName?.first : '') ?? '',
89
- last: updates.lastName ?? (typeof currentName === 'object' ? currentName?.last : '') ?? '',
90
- };
91
- }
72
+ // Handle name field
73
+ if (updates.displayName !== undefined || updates.lastName !== undefined) {
74
+ const currentUser = useAuthStore.getState().user;
75
+ const currentName = currentUser?.name;
76
+ updateData.name = {
77
+ first: updates.displayName ?? (typeof currentName === 'object' ? currentName?.first : '') ?? '',
78
+ last: updates.lastName ?? (typeof currentName === 'object' ? currentName?.last : '') ?? '',
79
+ };
80
+ }
92
81
 
93
- await updateUser(updateData, oxyServices);
94
- toast.success(t('editProfile.toasts.profileUpdated') || 'Profile updated successfully');
82
+ try {
83
+ await updateProfileMutation.mutateAsync(updateData);
95
84
  return true;
96
85
  } catch (error: any) {
97
- console.error('Failed to update profile:', error);
98
- toast.error(error?.message || t('editProfile.toasts.updateFailed') || 'Failed to update profile');
86
+ // Error toast is handled by the mutation
99
87
  return false;
100
- } finally {
101
- setIsSaving(false);
102
88
  }
103
- }, [oxyServices, updateUser, t]);
89
+ }, [updateProfileMutation, t]);
104
90
 
105
91
  /**
106
92
  * Update a single profile field
@@ -138,7 +124,7 @@ export const useProfileEditing = () => {
138
124
  return {
139
125
  saveProfile,
140
126
  updateField,
141
- isSaving,
127
+ isSaving: updateProfileMutation.isPending,
142
128
  };
143
129
  };
144
130
 
@@ -0,0 +1,17 @@
1
+ import { useQueryClient as useTanStackQueryClient } from '@tanstack/react-query';
2
+ import type { QueryClient } from '@tanstack/react-query';
3
+
4
+ /**
5
+ * Custom hook to access the QueryClient
6
+ * Provides type safety and ensures client is available
7
+ */
8
+ export const useQueryClient = (): QueryClient => {
9
+ const queryClient = useTanStackQueryClient();
10
+
11
+ if (!queryClient) {
12
+ throw new Error('QueryClient is not available. Make sure OxyProvider is wrapping your app.');
13
+ }
14
+
15
+ return queryClient;
16
+ };
17
+
@@ -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 './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> => {
@@ -27,6 +27,7 @@ import { getDisplayName, getShortDisplayName } from '../utils/user-utils';
27
27
  import { useColorScheme } from '../hooks/use-color-scheme';
28
28
  import { normalizeTheme } from '../utils/themeUtils';
29
29
  import { useOxy } from '../context/OxyContext';
30
+ import { useUsersBySessions } from '../hooks/queries/useAccountQueries';
30
31
  import {
31
32
  SCREEN_PADDING_HORIZONTAL,
32
33
  SECTION_GAP,
@@ -133,53 +134,44 @@ const AccountOverviewScreen: React.FC<BaseScreenProps> = ({
133
134
  ), [sessions, activeSessionId, user?.id]
134
135
  );
135
136
 
136
- // Load user profiles for additional accounts
137
- React.useEffect(() => {
138
- const loadAdditionalAccountsData = async () => {
139
- if (!oxyServices || additionalAccounts.length === 0) {
140
- setAdditionalAccountsData([]);
141
- return;
142
- }
143
-
144
- setLoadingAdditionalAccounts(true);
145
- try {
146
- const accountsData = await Promise.all(
147
- additionalAccounts.map(async (session) => {
148
- try {
149
- const userProfile = await oxyServices.getUserBySession(session.sessionId);
150
- return {
151
- id: session.sessionId,
152
- sessionId: session.sessionId,
153
- username: userProfile.username,
154
- email: userProfile.email,
155
- name: userProfile.name,
156
- avatar: userProfile.avatar,
157
- userProfile
158
- };
159
- } catch (error) {
160
- console.error(`Failed to load profile for session ${session.sessionId}:`, error);
161
- return {
162
- id: session.sessionId,
163
- sessionId: session.sessionId,
164
- username: 'Unknown User',
165
- email: 'No email available',
166
- avatar: null,
167
- userProfile: null
168
- };
169
- }
170
- })
171
- );
172
- setAdditionalAccountsData(accountsData);
173
- } catch (error) {
174
- console.error('Failed to load additional accounts:', error);
175
- setAdditionalAccountsData([]);
176
- } finally {
177
- setLoadingAdditionalAccounts(false);
178
- }
179
- };
137
+ // Load user profiles for additional accounts using TanStack Query
138
+ const sessionIds = additionalAccounts.map(s => s.sessionId);
139
+ const { data: usersData, isLoading: isLoadingUsers } = useUsersBySessions(sessionIds, {
140
+ enabled: additionalAccounts.length > 0
141
+ });
180
142
 
181
- loadAdditionalAccountsData();
182
- }, [sessions, activeSessionId, user?.id, oxyServices]);
143
+ React.useEffect(() => {
144
+ if (usersData && usersData.length > 0) {
145
+ const accountsData = usersData.map(({ sessionId, user: userProfile }) => {
146
+ if (!userProfile) {
147
+ return {
148
+ id: sessionId,
149
+ sessionId,
150
+ username: 'Unknown User',
151
+ email: 'No email available',
152
+ avatar: null,
153
+ userProfile: null
154
+ };
155
+ }
156
+ return {
157
+ id: sessionId,
158
+ sessionId,
159
+ username: userProfile.username,
160
+ email: userProfile.email,
161
+ name: userProfile.name,
162
+ avatar: userProfile.avatar,
163
+ userProfile
164
+ };
165
+ });
166
+ setAdditionalAccountsData(accountsData);
167
+ setLoadingAdditionalAccounts(false);
168
+ } else if (additionalAccounts.length === 0) {
169
+ setAdditionalAccountsData([]);
170
+ setLoadingAdditionalAccounts(false);
171
+ } else if (!isLoadingUsers) {
172
+ setLoadingAdditionalAccounts(false);
173
+ }
174
+ }, [usersData, additionalAccounts.length, isLoadingUsers]);
183
175
 
184
176
  // Feature settings (with mock values)
185
177
  const features = {