@oxyhq/auth 1.0.0

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 (119) hide show
  1. package/README.md +56 -0
  2. package/dist/cjs/WebOxyProvider.js +287 -0
  3. package/dist/cjs/hooks/mutations/index.js +23 -0
  4. package/dist/cjs/hooks/mutations/mutationFactory.js +126 -0
  5. package/dist/cjs/hooks/mutations/useAccountMutations.js +275 -0
  6. package/dist/cjs/hooks/mutations/useServicesMutations.js +149 -0
  7. package/dist/cjs/hooks/queries/index.js +35 -0
  8. package/dist/cjs/hooks/queries/queryKeys.js +82 -0
  9. package/dist/cjs/hooks/queries/useAccountQueries.js +141 -0
  10. package/dist/cjs/hooks/queries/useSecurityQueries.js +45 -0
  11. package/dist/cjs/hooks/queries/useServicesQueries.js +113 -0
  12. package/dist/cjs/hooks/queryClient.js +110 -0
  13. package/dist/cjs/hooks/useAssets.js +225 -0
  14. package/dist/cjs/hooks/useFileDownloadUrl.js +91 -0
  15. package/dist/cjs/hooks/useFileFiltering.js +81 -0
  16. package/dist/cjs/hooks/useFollow.js +159 -0
  17. package/dist/cjs/hooks/useFollow.types.js +4 -0
  18. package/dist/cjs/hooks/useQueryClient.js +16 -0
  19. package/dist/cjs/hooks/useSessionSocket.js +215 -0
  20. package/dist/cjs/hooks/useWebSSO.js +146 -0
  21. package/dist/cjs/index.js +115 -0
  22. package/dist/cjs/stores/accountStore.js +226 -0
  23. package/dist/cjs/stores/assetStore.js +192 -0
  24. package/dist/cjs/stores/authStore.js +47 -0
  25. package/dist/cjs/stores/followStore.js +154 -0
  26. package/dist/cjs/utils/authHelpers.js +154 -0
  27. package/dist/cjs/utils/avatarUtils.js +77 -0
  28. package/dist/cjs/utils/errorHandlers.js +128 -0
  29. package/dist/cjs/utils/sessionHelpers.js +90 -0
  30. package/dist/cjs/utils/storageHelpers.js +147 -0
  31. package/dist/esm/WebOxyProvider.js +282 -0
  32. package/dist/esm/hooks/mutations/index.js +10 -0
  33. package/dist/esm/hooks/mutations/mutationFactory.js +122 -0
  34. package/dist/esm/hooks/mutations/useAccountMutations.js +267 -0
  35. package/dist/esm/hooks/mutations/useServicesMutations.js +141 -0
  36. package/dist/esm/hooks/queries/index.js +14 -0
  37. package/dist/esm/hooks/queries/queryKeys.js +76 -0
  38. package/dist/esm/hooks/queries/useAccountQueries.js +131 -0
  39. package/dist/esm/hooks/queries/useSecurityQueries.js +40 -0
  40. package/dist/esm/hooks/queries/useServicesQueries.js +105 -0
  41. package/dist/esm/hooks/queryClient.js +104 -0
  42. package/dist/esm/hooks/useAssets.js +220 -0
  43. package/dist/esm/hooks/useFileDownloadUrl.js +86 -0
  44. package/dist/esm/hooks/useFileFiltering.js +78 -0
  45. package/dist/esm/hooks/useFollow.js +154 -0
  46. package/dist/esm/hooks/useFollow.types.js +3 -0
  47. package/dist/esm/hooks/useQueryClient.js +12 -0
  48. package/dist/esm/hooks/useSessionSocket.js +209 -0
  49. package/dist/esm/hooks/useWebSSO.js +143 -0
  50. package/dist/esm/index.js +48 -0
  51. package/dist/esm/stores/accountStore.js +219 -0
  52. package/dist/esm/stores/assetStore.js +180 -0
  53. package/dist/esm/stores/authStore.js +44 -0
  54. package/dist/esm/stores/followStore.js +151 -0
  55. package/dist/esm/utils/authHelpers.js +145 -0
  56. package/dist/esm/utils/avatarUtils.js +72 -0
  57. package/dist/esm/utils/errorHandlers.js +121 -0
  58. package/dist/esm/utils/sessionHelpers.js +84 -0
  59. package/dist/esm/utils/storageHelpers.js +108 -0
  60. package/dist/types/WebOxyProvider.d.ts +97 -0
  61. package/dist/types/hooks/mutations/index.d.ts +8 -0
  62. package/dist/types/hooks/mutations/mutationFactory.d.ts +75 -0
  63. package/dist/types/hooks/mutations/useAccountMutations.d.ts +68 -0
  64. package/dist/types/hooks/mutations/useServicesMutations.d.ts +22 -0
  65. package/dist/types/hooks/queries/index.d.ts +10 -0
  66. package/dist/types/hooks/queries/queryKeys.d.ts +64 -0
  67. package/dist/types/hooks/queries/useAccountQueries.d.ts +42 -0
  68. package/dist/types/hooks/queries/useSecurityQueries.d.ts +14 -0
  69. package/dist/types/hooks/queries/useServicesQueries.d.ts +31 -0
  70. package/dist/types/hooks/queryClient.d.ts +18 -0
  71. package/dist/types/hooks/useAssets.d.ts +34 -0
  72. package/dist/types/hooks/useFileDownloadUrl.d.ts +18 -0
  73. package/dist/types/hooks/useFileFiltering.d.ts +28 -0
  74. package/dist/types/hooks/useFollow.d.ts +61 -0
  75. package/dist/types/hooks/useFollow.types.d.ts +32 -0
  76. package/dist/types/hooks/useQueryClient.d.ts +6 -0
  77. package/dist/types/hooks/useSessionSocket.d.ts +13 -0
  78. package/dist/types/hooks/useWebSSO.d.ts +57 -0
  79. package/dist/types/index.d.ts +46 -0
  80. package/dist/types/stores/accountStore.d.ts +33 -0
  81. package/dist/types/stores/assetStore.d.ts +53 -0
  82. package/dist/types/stores/authStore.d.ts +16 -0
  83. package/dist/types/stores/followStore.d.ts +24 -0
  84. package/dist/types/utils/authHelpers.d.ts +98 -0
  85. package/dist/types/utils/avatarUtils.d.ts +33 -0
  86. package/dist/types/utils/errorHandlers.d.ts +34 -0
  87. package/dist/types/utils/sessionHelpers.d.ts +63 -0
  88. package/dist/types/utils/storageHelpers.d.ts +27 -0
  89. package/package.json +71 -0
  90. package/src/WebOxyProvider.tsx +372 -0
  91. package/src/global.d.ts +1 -0
  92. package/src/hooks/mutations/index.ts +25 -0
  93. package/src/hooks/mutations/mutationFactory.ts +215 -0
  94. package/src/hooks/mutations/useAccountMutations.ts +344 -0
  95. package/src/hooks/mutations/useServicesMutations.ts +164 -0
  96. package/src/hooks/queries/index.ts +36 -0
  97. package/src/hooks/queries/queryKeys.ts +88 -0
  98. package/src/hooks/queries/useAccountQueries.ts +152 -0
  99. package/src/hooks/queries/useSecurityQueries.ts +64 -0
  100. package/src/hooks/queries/useServicesQueries.ts +126 -0
  101. package/src/hooks/queryClient.ts +112 -0
  102. package/src/hooks/useAssets.ts +291 -0
  103. package/src/hooks/useFileDownloadUrl.ts +118 -0
  104. package/src/hooks/useFileFiltering.ts +115 -0
  105. package/src/hooks/useFollow.ts +175 -0
  106. package/src/hooks/useFollow.types.ts +33 -0
  107. package/src/hooks/useQueryClient.ts +17 -0
  108. package/src/hooks/useSessionSocket.ts +233 -0
  109. package/src/hooks/useWebSSO.ts +187 -0
  110. package/src/index.ts +144 -0
  111. package/src/stores/accountStore.ts +296 -0
  112. package/src/stores/assetStore.ts +281 -0
  113. package/src/stores/authStore.ts +63 -0
  114. package/src/stores/followStore.ts +181 -0
  115. package/src/utils/authHelpers.ts +183 -0
  116. package/src/utils/avatarUtils.ts +103 -0
  117. package/src/utils/errorHandlers.ts +194 -0
  118. package/src/utils/sessionHelpers.ts +151 -0
  119. package/src/utils/storageHelpers.ts +130 -0
@@ -0,0 +1,267 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { queryKeys, invalidateAccountQueries, invalidateUserQueries } from '../queries/queryKeys';
3
+ import { useWebOxy } from '../../WebOxyProvider';
4
+ import { toast } from 'sonner';
5
+ import { refreshAvatarInStore } from '../../utils/avatarUtils';
6
+ import { useAuthStore } from '../../stores/authStore';
7
+ import { authenticatedApiCall } from '../../utils/authHelpers';
8
+ /**
9
+ * Update user profile with optimistic updates and offline queue support
10
+ */
11
+ export const useUpdateProfile = () => {
12
+ const { oxyServices, activeSessionId, user } = useWebOxy();
13
+ const queryClient = useQueryClient();
14
+ return useMutation({
15
+ mutationFn: async (updates) => {
16
+ return authenticatedApiCall(oxyServices, activeSessionId, () => oxyServices.updateProfile(updates));
17
+ },
18
+ // Optimistic update
19
+ onMutate: async (updates) => {
20
+ // Cancel outgoing refetches
21
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
22
+ // Snapshot previous value
23
+ const previousUser = queryClient.getQueryData(queryKeys.accounts.current());
24
+ // Optimistically update
25
+ if (previousUser) {
26
+ queryClient.setQueryData(queryKeys.accounts.current(), {
27
+ ...previousUser,
28
+ ...updates,
29
+ });
30
+ // Also update profile query if sessionId is available
31
+ if (activeSessionId) {
32
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), {
33
+ ...previousUser,
34
+ ...updates,
35
+ });
36
+ }
37
+ }
38
+ return { previousUser };
39
+ },
40
+ // On error, rollback
41
+ onError: (error, updates, context) => {
42
+ if (context?.previousUser) {
43
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
44
+ if (activeSessionId) {
45
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), context.previousUser);
46
+ }
47
+ }
48
+ toast.error(error instanceof Error ? error.message : 'Failed to update profile');
49
+ },
50
+ // On success, invalidate and refetch
51
+ onSuccess: (data, updates) => {
52
+ // Update cache with server response
53
+ queryClient.setQueryData(queryKeys.accounts.current(), data);
54
+ if (activeSessionId) {
55
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
56
+ }
57
+ // Update authStore so frontend components see the changes immediately
58
+ useAuthStore.getState().setUser(data);
59
+ // If avatar was updated, refresh accountStore with cache-busted URL
60
+ if (updates.avatar && activeSessionId && oxyServices) {
61
+ refreshAvatarInStore(activeSessionId, updates.avatar, oxyServices);
62
+ }
63
+ // Invalidate all related queries to refresh everywhere
64
+ invalidateUserQueries(queryClient);
65
+ invalidateAccountQueries(queryClient);
66
+ },
67
+ });
68
+ };
69
+ /**
70
+ * Upload avatar with progress tracking and offline queue support
71
+ */
72
+ export const useUploadAvatar = () => {
73
+ const { oxyServices, activeSessionId } = useWebOxy();
74
+ const queryClient = useQueryClient();
75
+ return useMutation({
76
+ mutationFn: async (file) => {
77
+ return authenticatedApiCall(oxyServices, activeSessionId, async () => {
78
+ // Upload file first
79
+ const uploadResult = await oxyServices.assetUpload(file, 'public');
80
+ const fileId = uploadResult?.file?.id || uploadResult?.id || uploadResult;
81
+ if (!fileId || typeof fileId !== 'string') {
82
+ throw new Error('Failed to get file ID from upload result');
83
+ }
84
+ // Update profile with file ID
85
+ return await oxyServices.updateProfile({ avatar: fileId });
86
+ });
87
+ },
88
+ onMutate: async (file) => {
89
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
90
+ const previousUser = queryClient.getQueryData(queryKeys.accounts.current());
91
+ // Optimistically set a temporary avatar (using file URI as placeholder)
92
+ if (previousUser) {
93
+ const optimisticUser = {
94
+ ...previousUser,
95
+ avatar: file.uri, // Temporary, will be replaced with fileId
96
+ };
97
+ queryClient.setQueryData(queryKeys.accounts.current(), optimisticUser);
98
+ if (activeSessionId) {
99
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), optimisticUser);
100
+ }
101
+ }
102
+ return { previousUser };
103
+ },
104
+ onError: (error, file, context) => {
105
+ if (context?.previousUser) {
106
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
107
+ if (activeSessionId) {
108
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), context.previousUser);
109
+ }
110
+ }
111
+ toast.error(error instanceof Error ? error.message : 'Failed to upload avatar');
112
+ },
113
+ onSuccess: (data) => {
114
+ queryClient.setQueryData(queryKeys.accounts.current(), data);
115
+ if (activeSessionId) {
116
+ queryClient.setQueryData(queryKeys.users.profile(activeSessionId), data);
117
+ }
118
+ // Update authStore so frontend components see the changes immediately
119
+ useAuthStore.getState().setUser(data);
120
+ // Refresh accountStore with cache-busted URL if avatar was updated
121
+ if (data?.avatar && activeSessionId && oxyServices) {
122
+ refreshAvatarInStore(activeSessionId, data.avatar, oxyServices);
123
+ }
124
+ // Invalidate all related queries to refresh everywhere
125
+ invalidateUserQueries(queryClient);
126
+ invalidateAccountQueries(queryClient);
127
+ toast.success('Avatar updated successfully');
128
+ },
129
+ });
130
+ };
131
+ /**
132
+ * Update account settings
133
+ */
134
+ export const useUpdateAccountSettings = () => {
135
+ const { oxyServices, activeSessionId } = useWebOxy();
136
+ const queryClient = useQueryClient();
137
+ return useMutation({
138
+ mutationFn: async (settings) => {
139
+ return await oxyServices.updateProfile({ privacySettings: settings });
140
+ },
141
+ onMutate: async (settings) => {
142
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.settings() });
143
+ const previousUser = queryClient.getQueryData(queryKeys.accounts.current());
144
+ if (previousUser) {
145
+ queryClient.setQueryData(queryKeys.accounts.current(), {
146
+ ...previousUser,
147
+ privacySettings: {
148
+ ...previousUser.privacySettings,
149
+ ...settings,
150
+ },
151
+ });
152
+ }
153
+ return { previousUser };
154
+ },
155
+ onError: (error, settings, context) => {
156
+ if (context?.previousUser) {
157
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
158
+ }
159
+ toast.error(error instanceof Error ? error.message : 'Failed to update settings');
160
+ },
161
+ onSuccess: (data) => {
162
+ queryClient.setQueryData(queryKeys.accounts.current(), data);
163
+ // Update authStore so frontend components see the changes immediately
164
+ useAuthStore.getState().setUser(data);
165
+ invalidateAccountQueries(queryClient);
166
+ toast.success('Settings updated successfully');
167
+ },
168
+ onSettled: () => {
169
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.settings() });
170
+ },
171
+ });
172
+ };
173
+ /**
174
+ * Update privacy settings with optimistic updates and authentication handling
175
+ */
176
+ export const useUpdatePrivacySettings = () => {
177
+ const { oxyServices, activeSessionId, user } = useWebOxy();
178
+ const queryClient = useQueryClient();
179
+ return useMutation({
180
+ mutationFn: async ({ settings, userId }) => {
181
+ const targetUserId = userId || user?.id;
182
+ if (!targetUserId) {
183
+ throw new Error('User ID is required');
184
+ }
185
+ return authenticatedApiCall(oxyServices, activeSessionId, () => oxyServices.updatePrivacySettings(settings, targetUserId));
186
+ },
187
+ // Optimistic update
188
+ onMutate: async ({ settings, userId }) => {
189
+ const targetUserId = userId || user?.id;
190
+ if (!targetUserId)
191
+ return;
192
+ // Cancel outgoing refetches
193
+ await queryClient.cancelQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
194
+ await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
195
+ // Snapshot previous values
196
+ const previousPrivacySettings = queryClient.getQueryData(queryKeys.privacy.settings(targetUserId));
197
+ const previousUser = queryClient.getQueryData(queryKeys.accounts.current());
198
+ // Optimistically update privacy settings
199
+ if (previousPrivacySettings) {
200
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), {
201
+ ...previousPrivacySettings,
202
+ ...settings,
203
+ });
204
+ }
205
+ // Also update user query if available
206
+ if (previousUser) {
207
+ queryClient.setQueryData(queryKeys.accounts.current(), {
208
+ ...previousUser,
209
+ privacySettings: {
210
+ ...previousUser.privacySettings,
211
+ ...settings,
212
+ },
213
+ });
214
+ }
215
+ return { previousPrivacySettings, previousUser };
216
+ },
217
+ // On error, rollback
218
+ onError: (error, { userId }, context) => {
219
+ const targetUserId = userId || user?.id;
220
+ if (context?.previousPrivacySettings && targetUserId) {
221
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), context.previousPrivacySettings);
222
+ }
223
+ if (context?.previousUser) {
224
+ queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
225
+ }
226
+ toast.error(error instanceof Error ? error.message : 'Failed to update privacy settings');
227
+ },
228
+ // On success, invalidate and refetch
229
+ onSuccess: (data, { userId }) => {
230
+ const targetUserId = userId || user?.id;
231
+ if (targetUserId) {
232
+ queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), data);
233
+ }
234
+ // Also update account query if it contains privacy settings
235
+ const currentUser = queryClient.getQueryData(queryKeys.accounts.current());
236
+ if (currentUser) {
237
+ const updatedUser = {
238
+ ...currentUser,
239
+ privacySettings: data,
240
+ };
241
+ queryClient.setQueryData(queryKeys.accounts.current(), updatedUser);
242
+ // Update authStore so frontend components see the changes immediately
243
+ useAuthStore.getState().setUser(updatedUser);
244
+ }
245
+ invalidateAccountQueries(queryClient);
246
+ },
247
+ // Always refetch after error or success
248
+ onSettled: (data, error, { userId }) => {
249
+ const targetUserId = userId || user?.id;
250
+ if (targetUserId) {
251
+ queryClient.invalidateQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
252
+ }
253
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.current() });
254
+ },
255
+ });
256
+ };
257
+ /**
258
+ * Upload file with authentication handling and progress tracking
259
+ */
260
+ export const useUploadFile = () => {
261
+ const { oxyServices, activeSessionId } = useWebOxy();
262
+ return useMutation({
263
+ mutationFn: async ({ file, visibility, metadata, onProgress }) => {
264
+ return authenticatedApiCall(oxyServices, activeSessionId, () => oxyServices.assetUpload(file, visibility, metadata, onProgress));
265
+ },
266
+ });
267
+ };
@@ -0,0 +1,141 @@
1
+ import { useMutation, useQueryClient } from '@tanstack/react-query';
2
+ import { queryKeys, invalidateSessionQueries } from '../queries/queryKeys';
3
+ import { useWebOxy } from '../../WebOxyProvider';
4
+ import { toast } from 'sonner';
5
+ /**
6
+ * Switch active session
7
+ */
8
+ export const useSwitchSession = () => {
9
+ const { switchSession, activeSessionId } = useWebOxy();
10
+ const queryClient = useQueryClient();
11
+ return useMutation({
12
+ mutationFn: async (sessionId) => {
13
+ return await switchSession(sessionId);
14
+ },
15
+ onSuccess: (user) => {
16
+ // Invalidate all session queries
17
+ invalidateSessionQueries(queryClient);
18
+ // Update current user query
19
+ queryClient.setQueryData(queryKeys.accounts.current(), user);
20
+ // Invalidate account queries
21
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.all });
22
+ },
23
+ onError: (error) => {
24
+ toast.error(error instanceof Error ? error.message : 'Failed to switch session');
25
+ },
26
+ });
27
+ };
28
+ /**
29
+ * Logout from a session
30
+ */
31
+ export const useLogoutSession = () => {
32
+ const { oxyServices, activeSessionId, sessions } = useWebOxy();
33
+ const queryClient = useQueryClient();
34
+ return useMutation({
35
+ mutationFn: async (targetSessionId) => {
36
+ if (!activeSessionId) {
37
+ throw new Error('No active session');
38
+ }
39
+ const sessionToLogout = targetSessionId || activeSessionId;
40
+ await oxyServices.logoutSession(activeSessionId, sessionToLogout);
41
+ return sessionToLogout;
42
+ },
43
+ onMutate: async (targetSessionId) => {
44
+ // Cancel outgoing queries
45
+ await queryClient.cancelQueries({ queryKey: queryKeys.sessions.all });
46
+ // Snapshot previous sessions
47
+ const previousSessions = queryClient.getQueryData(queryKeys.sessions.list());
48
+ // Optimistically remove session
49
+ if (previousSessions) {
50
+ const sessionToLogout = targetSessionId || activeSessionId;
51
+ const updatedSessions = previousSessions.filter((s) => s.sessionId !== sessionToLogout);
52
+ queryClient.setQueryData(queryKeys.sessions.list(), updatedSessions);
53
+ }
54
+ return { previousSessions };
55
+ },
56
+ onError: (error, targetSessionId, context) => {
57
+ // Rollback on error
58
+ if (context?.previousSessions) {
59
+ queryClient.setQueryData(queryKeys.sessions.list(), context.previousSessions);
60
+ }
61
+ toast.error(error instanceof Error ? error.message : 'Failed to logout');
62
+ },
63
+ onSuccess: () => {
64
+ // Invalidate all session queries
65
+ invalidateSessionQueries(queryClient);
66
+ },
67
+ onSettled: () => {
68
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
69
+ },
70
+ });
71
+ };
72
+ /**
73
+ * Logout from all sessions
74
+ */
75
+ export const useLogoutAll = () => {
76
+ const { oxyServices, activeSessionId, clearSessionState } = useWebOxy();
77
+ const queryClient = useQueryClient();
78
+ return useMutation({
79
+ mutationFn: async () => {
80
+ if (!activeSessionId) {
81
+ throw new Error('No active session');
82
+ }
83
+ await oxyServices.logoutAllSessions(activeSessionId);
84
+ await clearSessionState();
85
+ },
86
+ onSuccess: () => {
87
+ // Clear all queries
88
+ queryClient.clear();
89
+ toast.success('Logged out from all sessions');
90
+ },
91
+ onError: (error) => {
92
+ toast.error(error instanceof Error ? error.message : 'Failed to logout from all sessions');
93
+ },
94
+ });
95
+ };
96
+ /**
97
+ * Update device name
98
+ */
99
+ export const useUpdateDeviceName = () => {
100
+ const { oxyServices, activeSessionId } = useWebOxy();
101
+ const queryClient = useQueryClient();
102
+ return useMutation({
103
+ mutationFn: async (deviceName) => {
104
+ if (!activeSessionId) {
105
+ throw new Error('No active session');
106
+ }
107
+ return await oxyServices.updateDeviceName(activeSessionId, deviceName);
108
+ },
109
+ onSuccess: () => {
110
+ // Invalidate device and session queries
111
+ queryClient.invalidateQueries({ queryKey: queryKeys.devices.all });
112
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
113
+ toast.success('Device name updated');
114
+ },
115
+ onError: (error) => {
116
+ toast.error(error instanceof Error ? error.message : 'Failed to update device name');
117
+ },
118
+ });
119
+ };
120
+ /**
121
+ * Remove a device
122
+ */
123
+ export const useRemoveDevice = () => {
124
+ const { oxyServices } = useWebOxy();
125
+ const queryClient = useQueryClient();
126
+ return useMutation({
127
+ mutationFn: async (deviceId) => {
128
+ await oxyServices.removeDevice(deviceId);
129
+ return deviceId;
130
+ },
131
+ onSuccess: () => {
132
+ // Invalidate device queries
133
+ queryClient.invalidateQueries({ queryKey: queryKeys.devices.all });
134
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
135
+ toast.success('Device removed');
136
+ },
137
+ onError: (error) => {
138
+ toast.error(error instanceof Error ? error.message : 'Failed to remove device');
139
+ },
140
+ });
141
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Query Hooks
3
+ *
4
+ * TanStack Query hooks for fetching Oxy services data.
5
+ * All hooks follow the same pattern with optional `enabled` parameter.
6
+ */
7
+ // Account and user query hooks
8
+ export { useUserProfile, useUserProfiles, useCurrentUser, useUserById, useUserByUsername, useUsersBySessions, usePrivacySettings, } from './useAccountQueries';
9
+ // Service query hooks (sessions, devices, security)
10
+ export { useSessions, useSession, useDeviceSessions, useUserDevices, useSecurityInfo, } from './useServicesQueries';
11
+ // Security activity query hooks
12
+ export { useSecurityActivity, useRecentSecurityActivity, } from './useSecurityQueries';
13
+ // Query keys and invalidation helpers (for advanced usage)
14
+ export { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from './queryKeys';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Centralized query keys for TanStack Query
3
+ *
4
+ * Following best practices:
5
+ * - Use arrays for hierarchical keys
6
+ * - Include all parameters in the key
7
+ * - Use consistent naming conventions
8
+ */
9
+ export const queryKeys = {
10
+ // Account queries
11
+ accounts: {
12
+ all: ['accounts'],
13
+ lists: () => [...queryKeys.accounts.all, 'list'],
14
+ list: (sessionIds) => [...queryKeys.accounts.lists(), sessionIds],
15
+ details: () => [...queryKeys.accounts.all, 'detail'],
16
+ detail: (sessionId) => [...queryKeys.accounts.details(), sessionId],
17
+ current: () => [...queryKeys.accounts.all, 'current'],
18
+ settings: () => [...queryKeys.accounts.all, 'settings'],
19
+ },
20
+ // User queries
21
+ users: {
22
+ all: ['users'],
23
+ lists: () => [...queryKeys.users.all, 'list'],
24
+ list: (userIds) => [...queryKeys.users.lists(), userIds],
25
+ details: () => [...queryKeys.users.all, 'detail'],
26
+ detail: (userId) => [...queryKeys.users.details(), userId],
27
+ profile: (sessionId) => [...queryKeys.users.details(), sessionId, 'profile'],
28
+ },
29
+ // Session queries
30
+ sessions: {
31
+ all: ['sessions'],
32
+ lists: () => [...queryKeys.sessions.all, 'list'],
33
+ list: (userId) => [...queryKeys.sessions.lists(), userId],
34
+ details: () => [...queryKeys.sessions.all, 'detail'],
35
+ detail: (sessionId) => [...queryKeys.sessions.details(), sessionId],
36
+ active: () => [...queryKeys.sessions.all, 'active'],
37
+ device: (deviceId) => [...queryKeys.sessions.all, 'device', deviceId],
38
+ },
39
+ // Device queries
40
+ devices: {
41
+ all: ['devices'],
42
+ lists: () => [...queryKeys.devices.all, 'list'],
43
+ list: (userId) => [...queryKeys.devices.lists(), userId],
44
+ details: () => [...queryKeys.devices.all, 'detail'],
45
+ detail: (deviceId) => [...queryKeys.devices.details(), deviceId],
46
+ },
47
+ // Privacy settings queries
48
+ privacy: {
49
+ all: ['privacy'],
50
+ settings: (userId) => [...queryKeys.privacy.all, 'settings', userId || 'current'],
51
+ },
52
+ // Security activity queries
53
+ security: {
54
+ all: ['security'],
55
+ activity: (limit, offset, eventType) => [...queryKeys.security.all, 'activity', limit, offset, eventType],
56
+ recent: (limit) => [...queryKeys.security.all, 'recent', limit],
57
+ },
58
+ };
59
+ /**
60
+ * Helper to invalidate all account-related queries
61
+ */
62
+ export const invalidateAccountQueries = (queryClient) => {
63
+ queryClient.invalidateQueries({ queryKey: queryKeys.accounts.all });
64
+ };
65
+ /**
66
+ * Helper to invalidate all user-related queries
67
+ */
68
+ export const invalidateUserQueries = (queryClient) => {
69
+ queryClient.invalidateQueries({ queryKey: queryKeys.users.all });
70
+ };
71
+ /**
72
+ * Helper to invalidate all session-related queries
73
+ */
74
+ export const invalidateSessionQueries = (queryClient) => {
75
+ queryClient.invalidateQueries({ queryKey: queryKeys.sessions.all });
76
+ };
@@ -0,0 +1,131 @@
1
+ import { useQuery, useQueries } from '@tanstack/react-query';
2
+ import { queryKeys } from './queryKeys';
3
+ import { useWebOxy } from '../../WebOxyProvider';
4
+ import { authenticatedApiCall } from '../../utils/authHelpers';
5
+ /**
6
+ * Get user profile by session ID
7
+ */
8
+ export const useUserProfile = (sessionId, options) => {
9
+ const { oxyServices } = useWebOxy();
10
+ return useQuery({
11
+ queryKey: queryKeys.users.profile(sessionId || ''),
12
+ queryFn: async () => {
13
+ if (!sessionId) {
14
+ throw new Error('Session ID is required');
15
+ }
16
+ return await oxyServices.getUserBySession(sessionId);
17
+ },
18
+ enabled: (options?.enabled !== false) && !!sessionId,
19
+ staleTime: 5 * 60 * 1000, // 5 minutes
20
+ gcTime: 30 * 60 * 1000, // 30 minutes
21
+ });
22
+ };
23
+ /**
24
+ * Get multiple user profiles by session IDs (batch query)
25
+ */
26
+ export const useUserProfiles = (sessionIds, options) => {
27
+ const { oxyServices } = useWebOxy();
28
+ return useQueries({
29
+ queries: sessionIds.map((sessionId) => ({
30
+ queryKey: queryKeys.users.profile(sessionId),
31
+ queryFn: async () => {
32
+ const results = await oxyServices.getUsersBySessions([sessionId]);
33
+ return results[0]?.user || null;
34
+ },
35
+ enabled: (options?.enabled !== false) && !!sessionId,
36
+ staleTime: 5 * 60 * 1000,
37
+ gcTime: 30 * 60 * 1000,
38
+ })),
39
+ });
40
+ };
41
+ /**
42
+ * Get current authenticated user
43
+ */
44
+ export const useCurrentUser = (options) => {
45
+ const { oxyServices, activeSessionId, isAuthenticated } = useWebOxy();
46
+ return useQuery({
47
+ queryKey: queryKeys.accounts.current(),
48
+ queryFn: async () => {
49
+ if (!activeSessionId) {
50
+ throw new Error('No active session');
51
+ }
52
+ return await oxyServices.getUserBySession(activeSessionId);
53
+ },
54
+ enabled: (options?.enabled !== false) && isAuthenticated && !!activeSessionId,
55
+ staleTime: 1 * 60 * 1000, // 1 minute for current user
56
+ gcTime: 30 * 60 * 1000,
57
+ });
58
+ };
59
+ /**
60
+ * Get user by ID
61
+ */
62
+ export const useUserById = (userId, options) => {
63
+ const { oxyServices } = useWebOxy();
64
+ return useQuery({
65
+ queryKey: queryKeys.users.detail(userId || ''),
66
+ queryFn: async () => {
67
+ if (!userId) {
68
+ throw new Error('User ID is required');
69
+ }
70
+ return await oxyServices.getUserById(userId);
71
+ },
72
+ enabled: (options?.enabled !== false) && !!userId,
73
+ staleTime: 5 * 60 * 1000,
74
+ gcTime: 30 * 60 * 1000,
75
+ });
76
+ };
77
+ /**
78
+ * Get user profile by username
79
+ */
80
+ export const useUserByUsername = (username, options) => {
81
+ const { oxyServices } = useWebOxy();
82
+ return useQuery({
83
+ queryKey: [...queryKeys.users.details(), 'username', username || ''],
84
+ queryFn: async () => {
85
+ if (!username) {
86
+ throw new Error('Username is required');
87
+ }
88
+ return await oxyServices.getProfileByUsername(username);
89
+ },
90
+ enabled: (options?.enabled !== false) && !!username,
91
+ staleTime: 5 * 60 * 1000,
92
+ gcTime: 30 * 60 * 1000,
93
+ });
94
+ };
95
+ /**
96
+ * Batch get users by session IDs (optimized single API call)
97
+ */
98
+ export const useUsersBySessions = (sessionIds, options) => {
99
+ const { oxyServices } = useWebOxy();
100
+ return useQuery({
101
+ queryKey: queryKeys.accounts.list(sessionIds),
102
+ queryFn: async () => {
103
+ if (sessionIds.length === 0) {
104
+ return [];
105
+ }
106
+ return await oxyServices.getUsersBySessions(sessionIds);
107
+ },
108
+ enabled: (options?.enabled !== false) && sessionIds.length > 0,
109
+ staleTime: 5 * 60 * 1000,
110
+ gcTime: 30 * 60 * 1000,
111
+ });
112
+ };
113
+ /**
114
+ * Get privacy settings for a user
115
+ */
116
+ export const usePrivacySettings = (userId, options) => {
117
+ const { oxyServices, activeSessionId, user } = useWebOxy();
118
+ const targetUserId = userId || user?.id;
119
+ return useQuery({
120
+ queryKey: queryKeys.privacy.settings(targetUserId),
121
+ queryFn: async () => {
122
+ if (!targetUserId) {
123
+ throw new Error('User ID is required');
124
+ }
125
+ return authenticatedApiCall(oxyServices, activeSessionId, () => oxyServices.getPrivacySettings(targetUserId));
126
+ },
127
+ enabled: (options?.enabled !== false) && !!targetUserId,
128
+ staleTime: 2 * 60 * 1000, // 2 minutes
129
+ gcTime: 10 * 60 * 1000, // 10 minutes
130
+ });
131
+ };
@@ -0,0 +1,40 @@
1
+ import { useQuery } from '@tanstack/react-query';
2
+ import { queryKeys } from './queryKeys';
3
+ import { useWebOxy } from '../../WebOxyProvider';
4
+ /**
5
+ * Get user's security activity with pagination
6
+ */
7
+ export const useSecurityActivity = (options) => {
8
+ const { oxyServices, activeSessionId } = useWebOxy();
9
+ return useQuery({
10
+ queryKey: queryKeys.security.activity(options?.limit, options?.offset, options?.eventType),
11
+ queryFn: async () => {
12
+ if (!activeSessionId) {
13
+ throw new Error('No active session');
14
+ }
15
+ const response = await oxyServices.getSecurityActivity(options?.limit, options?.offset, options?.eventType);
16
+ return response;
17
+ },
18
+ enabled: (options?.enabled !== false) && !!activeSessionId,
19
+ staleTime: 5 * 60 * 1000, // 5 minutes
20
+ gcTime: 10 * 60 * 1000, // 10 minutes
21
+ });
22
+ };
23
+ /**
24
+ * Get recent security activity (convenience hook)
25
+ */
26
+ export const useRecentSecurityActivity = (limit = 10) => {
27
+ const { oxyServices, activeSessionId } = useWebOxy();
28
+ return useQuery({
29
+ queryKey: queryKeys.security.recent(limit),
30
+ queryFn: async () => {
31
+ if (!activeSessionId) {
32
+ throw new Error('No active session');
33
+ }
34
+ return await oxyServices.getRecentSecurityActivity(limit);
35
+ },
36
+ enabled: !!activeSessionId,
37
+ staleTime: 5 * 60 * 1000, // 5 minutes
38
+ gcTime: 10 * 60 * 1000, // 10 minutes
39
+ });
40
+ };