@oxyhq/services 5.16.4 → 5.16.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/core/OxyServices.base.js +3 -1
- package/lib/commonjs/core/OxyServices.base.js.map +1 -1
- package/lib/commonjs/core/mixins/OxyServices.assets.js +20 -330
- package/lib/commonjs/core/mixins/OxyServices.assets.js.map +1 -1
- package/lib/commonjs/index.js +156 -0
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +45 -7
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/index.js +60 -20
- package/lib/commonjs/ui/hooks/mutations/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +230 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/index.js +96 -30
- package/lib/commonjs/ui/hooks/queries/index.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/queryKeys.js +5 -0
- package/lib/commonjs/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +75 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +50 -2
- package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useAssets.js +8 -29
- package/lib/commonjs/ui/hooks/useAssets.js.map +1 -1
- package/lib/commonjs/ui/screens/FileManagementScreen.js +14 -10
- package/lib/commonjs/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/module/core/OxyServices.base.js +3 -1
- package/lib/module/core/OxyServices.base.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.assets.js +20 -331
- package/lib/module/core/mixins/OxyServices.assets.js.map +1 -1
- package/lib/module/index.js +17 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +45 -7
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/mutations/index.js +12 -3
- package/lib/module/ui/hooks/mutations/index.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +227 -0
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/index.js +15 -4
- package/lib/module/ui/hooks/queries/index.js.map +1 -1
- package/lib/module/ui/hooks/queries/queryKeys.js +5 -0
- package/lib/module/ui/hooks/queries/queryKeys.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +73 -0
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/queries/useServicesQueries.js +50 -2
- package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
- package/lib/module/ui/hooks/useAssets.js +8 -29
- package/lib/module/ui/hooks/useAssets.js.map +1 -1
- package/lib/module/ui/screens/FileManagementScreen.js +12 -10
- package/lib/module/ui/screens/FileManagementScreen.js.map +1 -1
- package/lib/typescript/core/OxyServices.base.d.ts.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts +1 -70
- package/lib/typescript/core/mixins/OxyServices.assets.d.ts.map +1 -1
- package/lib/typescript/core/mixins/index.d.ts +4 -14
- package/lib/typescript/core/mixins/index.d.ts.map +1 -1
- package/lib/typescript/index.d.ts +2 -0
- package/lib/typescript/index.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/index.d.ts +8 -2
- package/lib/typescript/ui/hooks/mutations/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts +19 -0
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/index.d.ts +9 -3
- package/lib/typescript/ui/hooks/queries/index.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/queryKeys.d.ts +4 -0
- package/lib/typescript/ui/hooks/queries/queryKeys.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts +6 -0
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useAssets.d.ts.map +1 -1
- package/lib/typescript/ui/screens/FileManagementScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/OxyServices.base.ts +5 -1
- package/src/core/mixins/OxyServices.assets.ts +21 -338
- package/src/index.ts +49 -2
- package/src/ui/context/OxyContext.tsx +49 -7
- package/src/ui/hooks/mutations/index.ts +24 -3
- package/src/ui/hooks/mutations/useAccountMutations.ts +205 -0
- package/src/ui/hooks/queries/index.ts +29 -4
- package/src/ui/hooks/queries/queryKeys.ts +6 -0
- package/src/ui/hooks/queries/useAccountQueries.ts +69 -0
- package/src/ui/hooks/queries/useServicesQueries.ts +49 -2
- package/src/ui/hooks/useAssets.ts +8 -28
- package/src/ui/screens/FileManagementScreen.tsx +10 -11
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Mutation Hooks
|
|
3
|
+
*
|
|
4
|
+
* TanStack Query mutation hooks for updating Oxy services data.
|
|
5
|
+
* All mutations handle authentication, error handling, and query invalidation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// Account mutation hooks
|
|
9
|
+
export {
|
|
10
|
+
useUpdateProfile,
|
|
11
|
+
useUploadAvatar,
|
|
12
|
+
useUpdateAccountSettings,
|
|
13
|
+
useUpdatePrivacySettings,
|
|
14
|
+
useUploadFile,
|
|
15
|
+
} from './useAccountMutations';
|
|
16
|
+
|
|
17
|
+
// Service mutation hooks (sessions, devices)
|
|
18
|
+
export {
|
|
19
|
+
useSwitchSession,
|
|
20
|
+
useLogoutSession,
|
|
21
|
+
useLogoutAll,
|
|
22
|
+
useUpdateDeviceName,
|
|
23
|
+
useRemoveDevice,
|
|
24
|
+
} from './useServicesMutations';
|
|
4
25
|
|
|
@@ -275,3 +275,208 @@ export const useUpdateAccountSettings = () => {
|
|
|
275
275
|
});
|
|
276
276
|
};
|
|
277
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Update privacy settings with optimistic updates and authentication handling
|
|
280
|
+
*/
|
|
281
|
+
export const useUpdatePrivacySettings = () => {
|
|
282
|
+
const { oxyServices, activeSessionId, user, syncIdentity } = useOxy();
|
|
283
|
+
const queryClient = useQueryClient();
|
|
284
|
+
|
|
285
|
+
return useMutation({
|
|
286
|
+
mutationFn: async ({ settings, userId }: { settings: Record<string, any>; userId?: string }) => {
|
|
287
|
+
const targetUserId = userId || user?.id;
|
|
288
|
+
if (!targetUserId) {
|
|
289
|
+
throw new Error('User ID is required');
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Ensure we have a valid token before making the request
|
|
293
|
+
if (!oxyServices.hasValidToken() && activeSessionId) {
|
|
294
|
+
try {
|
|
295
|
+
// Try to get token for the session
|
|
296
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
297
|
+
} catch (tokenError) {
|
|
298
|
+
// If getting token fails, might be an offline session - try syncing
|
|
299
|
+
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
300
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
301
|
+
try {
|
|
302
|
+
await syncIdentity();
|
|
303
|
+
// Retry getting token after sync
|
|
304
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
305
|
+
} catch (syncError) {
|
|
306
|
+
throw new Error('Session needs to be synced. Please try again.');
|
|
307
|
+
}
|
|
308
|
+
} else {
|
|
309
|
+
throw tokenError;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
return await oxyServices.updatePrivacySettings(settings, targetUserId);
|
|
316
|
+
} catch (error: any) {
|
|
317
|
+
const errorMessage = error?.message || '';
|
|
318
|
+
const status = error?.status || error?.response?.status;
|
|
319
|
+
|
|
320
|
+
// Handle authentication errors
|
|
321
|
+
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
322
|
+
// Try to sync session and get token
|
|
323
|
+
if (activeSessionId) {
|
|
324
|
+
try {
|
|
325
|
+
await syncIdentity();
|
|
326
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
327
|
+
// Retry the update after getting token
|
|
328
|
+
return await oxyServices.updatePrivacySettings(settings, targetUserId);
|
|
329
|
+
} catch (retryError) {
|
|
330
|
+
throw new Error('Authentication failed. Please sign in again.');
|
|
331
|
+
}
|
|
332
|
+
} else {
|
|
333
|
+
throw new Error('No active session. Please sign in.');
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// TanStack Query will automatically retry on network errors
|
|
338
|
+
throw error;
|
|
339
|
+
}
|
|
340
|
+
},
|
|
341
|
+
// Optimistic update
|
|
342
|
+
onMutate: async ({ settings, userId }) => {
|
|
343
|
+
const targetUserId = userId || user?.id;
|
|
344
|
+
if (!targetUserId) return;
|
|
345
|
+
|
|
346
|
+
// Cancel outgoing refetches
|
|
347
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
|
|
348
|
+
await queryClient.cancelQueries({ queryKey: queryKeys.accounts.current() });
|
|
349
|
+
|
|
350
|
+
// Snapshot previous values
|
|
351
|
+
const previousPrivacySettings = queryClient.getQueryData(queryKeys.privacy.settings(targetUserId));
|
|
352
|
+
const previousUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
353
|
+
|
|
354
|
+
// Optimistically update privacy settings
|
|
355
|
+
if (previousPrivacySettings) {
|
|
356
|
+
queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), {
|
|
357
|
+
...previousPrivacySettings,
|
|
358
|
+
...settings,
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Also update user query if available
|
|
363
|
+
if (previousUser) {
|
|
364
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), {
|
|
365
|
+
...previousUser,
|
|
366
|
+
privacySettings: {
|
|
367
|
+
...previousUser.privacySettings,
|
|
368
|
+
...settings,
|
|
369
|
+
},
|
|
370
|
+
});
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return { previousPrivacySettings, previousUser };
|
|
374
|
+
},
|
|
375
|
+
// On error, rollback
|
|
376
|
+
onError: (error, { userId }, context) => {
|
|
377
|
+
const targetUserId = userId || user?.id;
|
|
378
|
+
if (context?.previousPrivacySettings && targetUserId) {
|
|
379
|
+
queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), context.previousPrivacySettings);
|
|
380
|
+
}
|
|
381
|
+
if (context?.previousUser) {
|
|
382
|
+
queryClient.setQueryData(queryKeys.accounts.current(), context.previousUser);
|
|
383
|
+
}
|
|
384
|
+
toast.error(error instanceof Error ? error.message : 'Failed to update privacy settings');
|
|
385
|
+
},
|
|
386
|
+
// On success, invalidate and refetch
|
|
387
|
+
onSuccess: (data, { userId }) => {
|
|
388
|
+
const targetUserId = userId || user?.id;
|
|
389
|
+
if (targetUserId) {
|
|
390
|
+
queryClient.setQueryData(queryKeys.privacy.settings(targetUserId), data);
|
|
391
|
+
}
|
|
392
|
+
// Also update account query if it contains privacy settings
|
|
393
|
+
const currentUser = queryClient.getQueryData<User>(queryKeys.accounts.current());
|
|
394
|
+
if (currentUser) {
|
|
395
|
+
queryClient.setQueryData<User>(queryKeys.accounts.current(), {
|
|
396
|
+
...currentUser,
|
|
397
|
+
privacySettings: data,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
invalidateAccountQueries(queryClient);
|
|
401
|
+
},
|
|
402
|
+
// Always refetch after error or success
|
|
403
|
+
onSettled: (data, error, { userId }) => {
|
|
404
|
+
const targetUserId = userId || user?.id;
|
|
405
|
+
if (targetUserId) {
|
|
406
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.privacy.settings(targetUserId) });
|
|
407
|
+
}
|
|
408
|
+
queryClient.invalidateQueries({ queryKey: queryKeys.accounts.current() });
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Upload file with authentication handling and progress tracking
|
|
415
|
+
*/
|
|
416
|
+
export const useUploadFile = () => {
|
|
417
|
+
const { oxyServices, activeSessionId, syncIdentity } = useOxy();
|
|
418
|
+
|
|
419
|
+
return useMutation({
|
|
420
|
+
mutationFn: async ({
|
|
421
|
+
file,
|
|
422
|
+
visibility,
|
|
423
|
+
metadata,
|
|
424
|
+
onProgress
|
|
425
|
+
}: {
|
|
426
|
+
file: File;
|
|
427
|
+
visibility?: 'private' | 'public' | 'unlisted';
|
|
428
|
+
metadata?: Record<string, any>;
|
|
429
|
+
onProgress?: (progress: number) => void;
|
|
430
|
+
}) => {
|
|
431
|
+
// Ensure we have a valid token before making the request
|
|
432
|
+
if (!oxyServices.hasValidToken() && activeSessionId) {
|
|
433
|
+
try {
|
|
434
|
+
// Try to get token for the session
|
|
435
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
436
|
+
} catch (tokenError) {
|
|
437
|
+
// If getting token fails, might be an offline session - try syncing
|
|
438
|
+
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
439
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
440
|
+
try {
|
|
441
|
+
await syncIdentity();
|
|
442
|
+
// Retry getting token after sync
|
|
443
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
444
|
+
} catch (syncError) {
|
|
445
|
+
throw new Error('Session needs to be synced. Please try again.');
|
|
446
|
+
}
|
|
447
|
+
} else {
|
|
448
|
+
throw tokenError;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
try {
|
|
454
|
+
return await oxyServices.assetUpload(file as any, visibility, metadata, onProgress);
|
|
455
|
+
} catch (error: any) {
|
|
456
|
+
const errorMessage = error?.message || '';
|
|
457
|
+
const status = error?.status || error?.response?.status;
|
|
458
|
+
|
|
459
|
+
// Handle authentication errors
|
|
460
|
+
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
461
|
+
// Try to sync session and get token
|
|
462
|
+
if (activeSessionId) {
|
|
463
|
+
try {
|
|
464
|
+
await syncIdentity();
|
|
465
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
466
|
+
// Retry the upload after getting token
|
|
467
|
+
return await oxyServices.assetUpload(file as any, visibility, metadata, onProgress);
|
|
468
|
+
} catch (retryError) {
|
|
469
|
+
throw new Error('Authentication failed. Please sign in again.');
|
|
470
|
+
}
|
|
471
|
+
} else {
|
|
472
|
+
throw new Error('No active session. Please sign in.');
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// TanStack Query will automatically retry on network errors
|
|
477
|
+
throw error;
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
|
|
@@ -1,5 +1,30 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
|
|
8
|
+
// Account and user query hooks
|
|
9
|
+
export {
|
|
10
|
+
useUserProfile,
|
|
11
|
+
useUserProfiles,
|
|
12
|
+
useCurrentUser,
|
|
13
|
+
useUserById,
|
|
14
|
+
useUserByUsername,
|
|
15
|
+
useUsersBySessions,
|
|
16
|
+
usePrivacySettings,
|
|
17
|
+
} from './useAccountQueries';
|
|
18
|
+
|
|
19
|
+
// Service query hooks (sessions, devices, security)
|
|
20
|
+
export {
|
|
21
|
+
useSessions,
|
|
22
|
+
useSession,
|
|
23
|
+
useDeviceSessions,
|
|
24
|
+
useUserDevices,
|
|
25
|
+
useSecurityInfo,
|
|
26
|
+
} from './useServicesQueries';
|
|
27
|
+
|
|
28
|
+
// Query keys and invalidation helpers (for advanced usage)
|
|
29
|
+
export { queryKeys, invalidateAccountQueries, invalidateUserQueries, invalidateSessionQueries } from './queryKeys';
|
|
5
30
|
|
|
@@ -48,6 +48,12 @@ export const queryKeys = {
|
|
|
48
48
|
details: () => [...queryKeys.devices.all, 'detail'] as const,
|
|
49
49
|
detail: (deviceId: string) => [...queryKeys.devices.details(), deviceId] as const,
|
|
50
50
|
},
|
|
51
|
+
|
|
52
|
+
// Privacy settings queries
|
|
53
|
+
privacy: {
|
|
54
|
+
all: ['privacy'] as const,
|
|
55
|
+
settings: (userId?: string) => [...queryKeys.privacy.all, 'settings', userId || 'current'] as const,
|
|
56
|
+
},
|
|
51
57
|
} as const;
|
|
52
58
|
|
|
53
59
|
/**
|
|
@@ -124,3 +124,72 @@ export const useUsersBySessions = (sessionIds: string[], options?: { enabled?: b
|
|
|
124
124
|
});
|
|
125
125
|
};
|
|
126
126
|
|
|
127
|
+
/**
|
|
128
|
+
* Get privacy settings for a user
|
|
129
|
+
*/
|
|
130
|
+
export const usePrivacySettings = (userId?: string, options?: { enabled?: boolean }) => {
|
|
131
|
+
const { oxyServices, activeSessionId, syncIdentity, user } = useOxy();
|
|
132
|
+
const targetUserId = userId || user?.id;
|
|
133
|
+
|
|
134
|
+
return useQuery({
|
|
135
|
+
queryKey: queryKeys.privacy.settings(userId),
|
|
136
|
+
queryFn: async () => {
|
|
137
|
+
if (!targetUserId) {
|
|
138
|
+
throw new Error('User ID is required');
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Ensure we have a valid token before making the request
|
|
142
|
+
if (!oxyServices.hasValidToken() && activeSessionId) {
|
|
143
|
+
try {
|
|
144
|
+
// Try to get token for the session
|
|
145
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
146
|
+
} catch (tokenError) {
|
|
147
|
+
// If getting token fails, might be an offline session - try syncing
|
|
148
|
+
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
149
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
150
|
+
try {
|
|
151
|
+
await syncIdentity();
|
|
152
|
+
// Retry getting token after sync
|
|
153
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
154
|
+
} catch (syncError) {
|
|
155
|
+
throw new Error('Session needs to be synced. Please try again.');
|
|
156
|
+
}
|
|
157
|
+
} else {
|
|
158
|
+
throw tokenError;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
try {
|
|
164
|
+
return await oxyServices.getPrivacySettings(targetUserId);
|
|
165
|
+
} catch (error: any) {
|
|
166
|
+
const errorMessage = error?.message || '';
|
|
167
|
+
const status = error?.status || error?.response?.status;
|
|
168
|
+
|
|
169
|
+
// Handle authentication errors
|
|
170
|
+
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
171
|
+
// Try to sync session and get token
|
|
172
|
+
if (activeSessionId) {
|
|
173
|
+
try {
|
|
174
|
+
await syncIdentity();
|
|
175
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
176
|
+
// Retry the request after getting token
|
|
177
|
+
return await oxyServices.getPrivacySettings(targetUserId);
|
|
178
|
+
} catch (retryError) {
|
|
179
|
+
throw new Error('Authentication failed. Please sign in again.');
|
|
180
|
+
}
|
|
181
|
+
} else {
|
|
182
|
+
throw new Error('No active session. Please sign in.');
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// TanStack Query will automatically retry on network errors
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
},
|
|
190
|
+
enabled: (options?.enabled !== false) && !!targetUserId,
|
|
191
|
+
staleTime: 2 * 60 * 1000, // 2 minutes
|
|
192
|
+
gcTime: 10 * 60 * 1000, // 10 minutes
|
|
193
|
+
});
|
|
194
|
+
};
|
|
195
|
+
|
|
@@ -89,12 +89,59 @@ export const useDeviceSessions = (options?: { enabled?: boolean }) => {
|
|
|
89
89
|
* Get user devices
|
|
90
90
|
*/
|
|
91
91
|
export const useUserDevices = (options?: { enabled?: boolean }) => {
|
|
92
|
-
const { oxyServices, isAuthenticated } = useOxy();
|
|
92
|
+
const { oxyServices, isAuthenticated, activeSessionId, syncIdentity } = useOxy();
|
|
93
93
|
|
|
94
94
|
return useQuery({
|
|
95
95
|
queryKey: queryKeys.devices.list(),
|
|
96
96
|
queryFn: async () => {
|
|
97
|
-
|
|
97
|
+
// Ensure we have a valid token before making the request
|
|
98
|
+
if (!oxyServices.hasValidToken() && activeSessionId) {
|
|
99
|
+
try {
|
|
100
|
+
// Try to get token for the session
|
|
101
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
102
|
+
} catch (tokenError) {
|
|
103
|
+
// If getting token fails, might be an offline session - try syncing
|
|
104
|
+
const errorMessage = tokenError instanceof Error ? tokenError.message : String(tokenError);
|
|
105
|
+
if (errorMessage.includes('AUTH_REQUIRED_OFFLINE_SESSION') || errorMessage.includes('offline')) {
|
|
106
|
+
try {
|
|
107
|
+
await syncIdentity();
|
|
108
|
+
// Retry getting token after sync
|
|
109
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
110
|
+
} catch (syncError) {
|
|
111
|
+
throw new Error('Session needs to be synced. Please try again.');
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
throw tokenError;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
return await oxyServices.getUserDevices();
|
|
121
|
+
} catch (error: any) {
|
|
122
|
+
const errorMessage = error?.message || '';
|
|
123
|
+
const status = error?.status || error?.response?.status;
|
|
124
|
+
|
|
125
|
+
// Handle authentication errors
|
|
126
|
+
if (status === 401 || errorMessage.includes('Authentication required') || errorMessage.includes('Invalid or missing authorization header')) {
|
|
127
|
+
// Try to sync session and get token
|
|
128
|
+
if (activeSessionId) {
|
|
129
|
+
try {
|
|
130
|
+
await syncIdentity();
|
|
131
|
+
await oxyServices.getTokenBySession(activeSessionId);
|
|
132
|
+
// Retry the request after getting token
|
|
133
|
+
return await oxyServices.getUserDevices();
|
|
134
|
+
} catch (retryError) {
|
|
135
|
+
throw new Error('Authentication failed. Please sign in again.');
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
throw new Error('No active session. Please sign in.');
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// TanStack Query will automatically retry on network errors
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
98
145
|
},
|
|
99
146
|
enabled: (options?.enabled !== false) && isAuthenticated,
|
|
100
147
|
staleTime: 5 * 60 * 1000,
|
|
@@ -58,43 +58,23 @@ export const useAssets = () => {
|
|
|
58
58
|
clearErrors();
|
|
59
59
|
setUploading(true);
|
|
60
60
|
|
|
61
|
-
//
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
// Initialize progress tracking
|
|
65
|
-
const initialProgress: AssetUploadProgress = {
|
|
66
|
-
fileId: '', // Will be set after init
|
|
67
|
-
uploaded: 0,
|
|
68
|
-
total: file.size,
|
|
69
|
-
percentage: 0,
|
|
70
|
-
status: 'uploading'
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Upload with progress callback (visibility undefined, metadata, then onProgress)
|
|
74
|
-
const result = await oxyInstance.assetUpload(file as any, undefined, metadata, (percentage: number) => {
|
|
75
|
-
if (initialProgress.fileId) {
|
|
76
|
-
setUploadProgress(initialProgress.fileId, {
|
|
77
|
-
...initialProgress,
|
|
78
|
-
uploaded: Math.round((percentage / 100) * file.size),
|
|
79
|
-
percentage,
|
|
80
|
-
status: percentage < 100 ? 'uploading' : 'processing'
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
});
|
|
61
|
+
// Upload file (progress tracking simplified for now)
|
|
62
|
+
const result = await oxyInstance.assetUpload(file as any, undefined, metadata);
|
|
84
63
|
|
|
85
64
|
// Update progress with final status
|
|
86
|
-
if (result
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
fileId
|
|
65
|
+
if (result?.file) {
|
|
66
|
+
const fileId = result.file.id;
|
|
67
|
+
setUploadProgress(fileId, {
|
|
68
|
+
fileId,
|
|
90
69
|
uploaded: file.size,
|
|
70
|
+
total: file.size,
|
|
91
71
|
percentage: 100,
|
|
92
72
|
status: 'complete'
|
|
93
73
|
});
|
|
94
74
|
|
|
95
75
|
// Remove progress after a short delay
|
|
96
76
|
setTimeout(() => {
|
|
97
|
-
removeUploadProgress(
|
|
77
|
+
removeUploadProgress(fileId);
|
|
98
78
|
}, 2000);
|
|
99
79
|
}
|
|
100
80
|
|
|
@@ -14,6 +14,7 @@ import {
|
|
|
14
14
|
Alert,
|
|
15
15
|
} from 'react-native';
|
|
16
16
|
import { Image as ExpoImage } from 'expo-image';
|
|
17
|
+
import * as DocumentPicker from 'expo-document-picker';
|
|
17
18
|
import type { FileManagementScreenProps } from '../types/fileManagement';
|
|
18
19
|
import { toast } from '../../lib/sonner';
|
|
19
20
|
import { Ionicons } from '@expo/vector-icons';
|
|
@@ -28,13 +29,13 @@ import { useThemeStyles } from '../hooks/useThemeStyles';
|
|
|
28
29
|
import { useColorScheme } from '../hooks/use-color-scheme';
|
|
29
30
|
import { normalizeTheme } from '../utils/themeUtils';
|
|
30
31
|
import { useOxy } from '../context/OxyContext';
|
|
32
|
+
import { useUploadFile } from '../hooks/mutations/useAccountMutations';
|
|
31
33
|
import {
|
|
32
34
|
confirmAction,
|
|
33
35
|
convertDocumentPickerAssetToFile,
|
|
34
36
|
formatFileSize,
|
|
35
37
|
getFileIcon,
|
|
36
38
|
getSafeDownloadUrl,
|
|
37
|
-
uploadFileRaw
|
|
38
39
|
} from '../utils/fileManagement';
|
|
39
40
|
import { FileViewer } from '../components/fileManagement/FileViewer';
|
|
40
41
|
import { FileDetailsModal } from '../components/fileManagement/FileDetailsModal';
|
|
@@ -115,6 +116,7 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
115
116
|
}) => {
|
|
116
117
|
// Use useOxy() hook for OxyContext values
|
|
117
118
|
const { user, oxyServices } = useOxy();
|
|
119
|
+
const uploadFileMutation = useUploadFile();
|
|
118
120
|
const files = useFiles();
|
|
119
121
|
// Ensure containerWidth is a number (TypeScript guard)
|
|
120
122
|
const safeContainerWidth: number = typeof containerWidth === 'number' ? containerWidth : 400;
|
|
@@ -528,7 +530,11 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
528
530
|
};
|
|
529
531
|
useFileStore.getState().addFile(optimisticFile, { prepend: true });
|
|
530
532
|
|
|
531
|
-
|
|
533
|
+
// Use the mutation hook with authentication handling
|
|
534
|
+
const result = await uploadFileMutation.mutateAsync({
|
|
535
|
+
file: raw,
|
|
536
|
+
visibility: defaultVisibility,
|
|
537
|
+
});
|
|
532
538
|
|
|
533
539
|
// Attempt to refresh file list incrementally – fetch single file metadata if API allows
|
|
534
540
|
if (result?.file || result?.files?.[0]) {
|
|
@@ -764,15 +770,8 @@ const FileManagementScreen: React.FC<FileManagementScreenProps> = ({
|
|
|
764
770
|
try {
|
|
765
771
|
setIsPickingDocument(true);
|
|
766
772
|
|
|
767
|
-
//
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
if (!DocumentPicker || !DocumentPicker.getDocumentAsync) {
|
|
771
|
-
toast.error('File picker not available. Please install expo-document-picker');
|
|
772
|
-
return;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// Use getDocumentAsync directly - it will handle platform availability
|
|
773
|
+
// Use expo-document-picker (works on all platforms including web)
|
|
774
|
+
// On web, it uses the native file input and provides File objects directly
|
|
776
775
|
const result = await DocumentPicker.getDocumentAsync({
|
|
777
776
|
type: '*/*',
|
|
778
777
|
multiple: true,
|