@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.
- package/README.md +56 -0
- package/dist/cjs/WebOxyProvider.js +287 -0
- package/dist/cjs/hooks/mutations/index.js +23 -0
- package/dist/cjs/hooks/mutations/mutationFactory.js +126 -0
- package/dist/cjs/hooks/mutations/useAccountMutations.js +275 -0
- package/dist/cjs/hooks/mutations/useServicesMutations.js +149 -0
- package/dist/cjs/hooks/queries/index.js +35 -0
- package/dist/cjs/hooks/queries/queryKeys.js +82 -0
- package/dist/cjs/hooks/queries/useAccountQueries.js +141 -0
- package/dist/cjs/hooks/queries/useSecurityQueries.js +45 -0
- package/dist/cjs/hooks/queries/useServicesQueries.js +113 -0
- package/dist/cjs/hooks/queryClient.js +110 -0
- package/dist/cjs/hooks/useAssets.js +225 -0
- package/dist/cjs/hooks/useFileDownloadUrl.js +91 -0
- package/dist/cjs/hooks/useFileFiltering.js +81 -0
- package/dist/cjs/hooks/useFollow.js +159 -0
- package/dist/cjs/hooks/useFollow.types.js +4 -0
- package/dist/cjs/hooks/useQueryClient.js +16 -0
- package/dist/cjs/hooks/useSessionSocket.js +215 -0
- package/dist/cjs/hooks/useWebSSO.js +146 -0
- package/dist/cjs/index.js +115 -0
- package/dist/cjs/stores/accountStore.js +226 -0
- package/dist/cjs/stores/assetStore.js +192 -0
- package/dist/cjs/stores/authStore.js +47 -0
- package/dist/cjs/stores/followStore.js +154 -0
- package/dist/cjs/utils/authHelpers.js +154 -0
- package/dist/cjs/utils/avatarUtils.js +77 -0
- package/dist/cjs/utils/errorHandlers.js +128 -0
- package/dist/cjs/utils/sessionHelpers.js +90 -0
- package/dist/cjs/utils/storageHelpers.js +147 -0
- package/dist/esm/WebOxyProvider.js +282 -0
- package/dist/esm/hooks/mutations/index.js +10 -0
- package/dist/esm/hooks/mutations/mutationFactory.js +122 -0
- package/dist/esm/hooks/mutations/useAccountMutations.js +267 -0
- package/dist/esm/hooks/mutations/useServicesMutations.js +141 -0
- package/dist/esm/hooks/queries/index.js +14 -0
- package/dist/esm/hooks/queries/queryKeys.js +76 -0
- package/dist/esm/hooks/queries/useAccountQueries.js +131 -0
- package/dist/esm/hooks/queries/useSecurityQueries.js +40 -0
- package/dist/esm/hooks/queries/useServicesQueries.js +105 -0
- package/dist/esm/hooks/queryClient.js +104 -0
- package/dist/esm/hooks/useAssets.js +220 -0
- package/dist/esm/hooks/useFileDownloadUrl.js +86 -0
- package/dist/esm/hooks/useFileFiltering.js +78 -0
- package/dist/esm/hooks/useFollow.js +154 -0
- package/dist/esm/hooks/useFollow.types.js +3 -0
- package/dist/esm/hooks/useQueryClient.js +12 -0
- package/dist/esm/hooks/useSessionSocket.js +209 -0
- package/dist/esm/hooks/useWebSSO.js +143 -0
- package/dist/esm/index.js +48 -0
- package/dist/esm/stores/accountStore.js +219 -0
- package/dist/esm/stores/assetStore.js +180 -0
- package/dist/esm/stores/authStore.js +44 -0
- package/dist/esm/stores/followStore.js +151 -0
- package/dist/esm/utils/authHelpers.js +145 -0
- package/dist/esm/utils/avatarUtils.js +72 -0
- package/dist/esm/utils/errorHandlers.js +121 -0
- package/dist/esm/utils/sessionHelpers.js +84 -0
- package/dist/esm/utils/storageHelpers.js +108 -0
- package/dist/types/WebOxyProvider.d.ts +97 -0
- package/dist/types/hooks/mutations/index.d.ts +8 -0
- package/dist/types/hooks/mutations/mutationFactory.d.ts +75 -0
- package/dist/types/hooks/mutations/useAccountMutations.d.ts +68 -0
- package/dist/types/hooks/mutations/useServicesMutations.d.ts +22 -0
- package/dist/types/hooks/queries/index.d.ts +10 -0
- package/dist/types/hooks/queries/queryKeys.d.ts +64 -0
- package/dist/types/hooks/queries/useAccountQueries.d.ts +42 -0
- package/dist/types/hooks/queries/useSecurityQueries.d.ts +14 -0
- package/dist/types/hooks/queries/useServicesQueries.d.ts +31 -0
- package/dist/types/hooks/queryClient.d.ts +18 -0
- package/dist/types/hooks/useAssets.d.ts +34 -0
- package/dist/types/hooks/useFileDownloadUrl.d.ts +18 -0
- package/dist/types/hooks/useFileFiltering.d.ts +28 -0
- package/dist/types/hooks/useFollow.d.ts +61 -0
- package/dist/types/hooks/useFollow.types.d.ts +32 -0
- package/dist/types/hooks/useQueryClient.d.ts +6 -0
- package/dist/types/hooks/useSessionSocket.d.ts +13 -0
- package/dist/types/hooks/useWebSSO.d.ts +57 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/types/stores/accountStore.d.ts +33 -0
- package/dist/types/stores/assetStore.d.ts +53 -0
- package/dist/types/stores/authStore.d.ts +16 -0
- package/dist/types/stores/followStore.d.ts +24 -0
- package/dist/types/utils/authHelpers.d.ts +98 -0
- package/dist/types/utils/avatarUtils.d.ts +33 -0
- package/dist/types/utils/errorHandlers.d.ts +34 -0
- package/dist/types/utils/sessionHelpers.d.ts +63 -0
- package/dist/types/utils/storageHelpers.d.ts +27 -0
- package/package.json +71 -0
- package/src/WebOxyProvider.tsx +372 -0
- package/src/global.d.ts +1 -0
- package/src/hooks/mutations/index.ts +25 -0
- package/src/hooks/mutations/mutationFactory.ts +215 -0
- package/src/hooks/mutations/useAccountMutations.ts +344 -0
- package/src/hooks/mutations/useServicesMutations.ts +164 -0
- package/src/hooks/queries/index.ts +36 -0
- package/src/hooks/queries/queryKeys.ts +88 -0
- package/src/hooks/queries/useAccountQueries.ts +152 -0
- package/src/hooks/queries/useSecurityQueries.ts +64 -0
- package/src/hooks/queries/useServicesQueries.ts +126 -0
- package/src/hooks/queryClient.ts +112 -0
- package/src/hooks/useAssets.ts +291 -0
- package/src/hooks/useFileDownloadUrl.ts +118 -0
- package/src/hooks/useFileFiltering.ts +115 -0
- package/src/hooks/useFollow.ts +175 -0
- package/src/hooks/useFollow.types.ts +33 -0
- package/src/hooks/useQueryClient.ts +17 -0
- package/src/hooks/useSessionSocket.ts +233 -0
- package/src/hooks/useWebSSO.ts +187 -0
- package/src/index.ts +144 -0
- package/src/stores/accountStore.ts +296 -0
- package/src/stores/assetStore.ts +281 -0
- package/src/stores/authStore.ts +63 -0
- package/src/stores/followStore.ts +181 -0
- package/src/utils/authHelpers.ts +183 -0
- package/src/utils/avatarUtils.ts +103 -0
- package/src/utils/errorHandlers.ts +194 -0
- package/src/utils/sessionHelpers.ts +151 -0
- 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
|
+
};
|