@oxyhq/services 5.17.1 → 5.17.3

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 (36) hide show
  1. package/lib/commonjs/ui/components/OxyProvider.js +5 -12
  2. package/lib/commonjs/ui/components/OxyProvider.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +34 -15
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/context/OxyContextBase.js +21 -0
  6. package/lib/commonjs/ui/context/OxyContextBase.js.map +1 -0
  7. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +8 -8
  8. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  9. package/lib/commonjs/ui/hooks/queries/useSecurityQueries.js +3 -3
  10. package/lib/commonjs/ui/hooks/queries/useSecurityQueries.js.map +1 -1
  11. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +6 -6
  12. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
  13. package/lib/module/ui/components/OxyProvider.js +5 -12
  14. package/lib/module/ui/components/OxyProvider.js.map +1 -1
  15. package/lib/module/ui/context/OxyContext.js +23 -13
  16. package/lib/module/ui/context/OxyContext.js.map +1 -1
  17. package/lib/module/ui/context/OxyContextBase.js +16 -0
  18. package/lib/module/ui/context/OxyContextBase.js.map +1 -0
  19. package/lib/module/ui/hooks/queries/useAccountQueries.js +1 -1
  20. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  21. package/lib/module/ui/hooks/queries/useSecurityQueries.js +1 -1
  22. package/lib/module/ui/hooks/queries/useSecurityQueries.js.map +1 -1
  23. package/lib/module/ui/hooks/queries/useServicesQueries.js +1 -1
  24. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
  25. package/lib/typescript/ui/components/OxyProvider.d.ts.map +1 -1
  26. package/lib/typescript/ui/context/OxyContext.d.ts +2 -94
  27. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  28. package/lib/typescript/ui/context/OxyContextBase.d.ts +99 -0
  29. package/lib/typescript/ui/context/OxyContextBase.d.ts.map +1 -0
  30. package/package.json +1 -1
  31. package/src/ui/components/OxyProvider.tsx +7 -13
  32. package/src/ui/context/OxyContext.tsx +27 -88
  33. package/src/ui/context/OxyContextBase.tsx +95 -0
  34. package/src/ui/hooks/queries/useAccountQueries.ts +1 -1
  35. package/src/ui/hooks/queries/useSecurityQueries.ts +1 -1
  36. package/src/ui/hooks/queries/useServicesQueries.ts +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.17.1",
3
+ "version": "5.17.3",
4
4
  "description": "Reusable OxyHQ module to handle authentication, user management, karma system, device-based session management and more 🚀",
5
5
  "main": "lib/commonjs/index.js",
6
6
  "module": "lib/module/index.js",
@@ -35,21 +35,18 @@ const OxyProvider: FC<OxyProviderProps> = ({
35
35
 
36
36
  // Initialize React Query Client (in-memory; persistence handled separately if needed)
37
37
  const queryClientRef = useRef<ReturnType<typeof createQueryClient> | null>(null);
38
- const [queryClient, setQueryClient] = useState<ReturnType<typeof createQueryClient> | null>(null);
38
+ const [queryClient, setQueryClient] = useState<ReturnType<typeof createQueryClient>>(
39
+ () => providedQueryClient ?? createQueryClient(),
40
+ );
39
41
 
40
42
  useEffect(() => {
41
- if (providedQueryClient) {
43
+ if (providedQueryClient && queryClientRef.current !== providedQueryClient) {
42
44
  queryClientRef.current = providedQueryClient;
43
45
  setQueryClient(providedQueryClient);
44
- return;
45
- }
46
-
47
- if (!queryClientRef.current) {
48
- const client = createQueryClient();
49
- queryClientRef.current = client;
50
- setQueryClient(client);
46
+ } else if (!queryClientRef.current) {
47
+ queryClientRef.current = queryClient;
51
48
  }
52
- }, [providedQueryClient]);
49
+ }, [providedQueryClient, queryClient]);
53
50
 
54
51
  // Hook React Query focus manager into React Native AppState
55
52
  useEffect(() => {
@@ -110,9 +107,6 @@ const OxyProvider: FC<OxyProviderProps> = ({
110
107
  };
111
108
  }, []);
112
109
 
113
- // Ensure we have a valid QueryClient
114
- if (!queryClient) return null;
115
-
116
110
  return (
117
111
  <SafeAreaProvider>
118
112
  <GestureHandlerRootView style={{ flex: 1 }}>
@@ -1,17 +1,14 @@
1
1
  import type React from 'react';
2
2
  import {
3
- createContext,
4
3
  useCallback,
5
- useContext,
6
4
  useEffect,
7
5
  useMemo,
8
6
  useRef,
9
7
  useState,
10
- type ReactNode,
11
8
  } from 'react';
12
9
  import { Platform } from 'react-native';
13
10
  import { OxyServices } from '../../core';
14
- import type { User, ApiError } from '../../models/interfaces';
11
+ import type { User } from '../../models/interfaces';
15
12
  import type { ClientSession } from '../../models/session';
16
13
  import { toast } from '../../lib/sonner';
17
14
  import { useAuthStore, type AuthState } from '../stores/authStore';
@@ -27,88 +24,25 @@ import { getStorageKeys } from '../utils/storageHelpers';
27
24
  import { isInvalidSessionError, isTimeoutOrNetworkError } from '../utils/errorHandlers';
28
25
  import type { RouteName } from '../navigation/routes';
29
26
  import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSheetManager';
30
- import { useQueryClient } from '@tanstack/react-query';
31
- import { useCurrentUser } from '../hooks/queries';
27
+ import { useQueryClient, useQuery } from '@tanstack/react-query';
32
28
  import { clearQueryCache } from '../hooks/queryClient';
29
+ import { queryKeys } from '../hooks/queries/queryKeys';
33
30
  import { KeyManager, type BackupData } from '../../crypto';
34
31
  import { translate } from '../../i18n';
35
32
  import { updateAvatarVisibility, updateProfileWithAvatar } from '../utils/avatarUtils';
36
33
  import { useAccountStore } from '../stores/accountStore';
37
34
  import { logger as loggerUtil } from '../../utils/loggerUtils';
38
35
 
39
- export interface OxyContextState {
40
- user: User | null;
41
- sessions: ClientSession[];
42
- activeSessionId: string | null;
43
- currentDeviceId: string | null;
44
- isAuthenticated: boolean;
45
- isLoading: boolean;
46
- isTokenReady: boolean;
47
- isStorageReady: boolean;
48
- error: string | null;
49
- currentLanguage: string;
50
- currentLanguageMetadata: ReturnType<typeof useLanguageManagement>['metadata'];
51
- currentLanguageName: string;
52
- currentNativeLanguageName: string;
53
-
54
- // Identity management (public key authentication - offline-first)
55
- createIdentity: () => Promise<{ synced: boolean }>;
56
- importIdentity: (backupData: BackupData, password: string) => Promise<{ synced: boolean }>;
57
- signIn: (deviceName?: string) => Promise<User>;
58
- hasIdentity: () => Promise<boolean>;
59
- getPublicKey: () => Promise<string | null>;
60
- isIdentitySynced: () => Promise<boolean>;
61
- syncIdentity: () => Promise<User>;
62
- deleteIdentityAndClearAccount: (skipBackup?: boolean, force?: boolean, userConfirmed?: boolean) => Promise<void>;
63
- storeTransferCode: (transferId: string, code: string, sourceDeviceId: string | null, publicKey: string) => Promise<void>;
64
- getTransferCode: (transferId: string) => { code: string; sourceDeviceId: string | null; publicKey: string; timestamp: number; state: 'pending' | 'completed' | 'failed' } | null;
65
- clearTransferCode: (transferId: string) => Promise<void>;
66
- getAllPendingTransfers: () => Array<{ transferId: string; data: { code: string; sourceDeviceId: string | null; publicKey: string; timestamp: number; state: 'pending' | 'completed' | 'failed' } }>;
67
- getActiveTransferId: () => string | null;
68
- updateTransferState: (transferId: string, state: 'pending' | 'completed' | 'failed') => Promise<void>;
69
-
70
- // Identity sync state (reactive, from Zustand store)
71
- identitySyncState: {
72
- isSynced: boolean;
73
- isSyncing: boolean;
74
- };
75
-
76
- // Session management
77
- logout: (targetSessionId?: string) => Promise<void>;
78
- logoutAll: () => Promise<void>;
79
- switchSession: (sessionId: string) => Promise<void>;
80
- removeSession: (sessionId: string) => Promise<void>;
81
- refreshSessions: () => Promise<void>;
82
- setLanguage: (languageId: string) => Promise<void>;
83
- getDeviceSessions: () => Promise<
84
- Array<{
85
- sessionId: string;
86
- deviceId: string;
87
- deviceName?: string;
88
- lastActive?: string;
89
- expiresAt?: string;
90
- }>
91
- >;
92
- logoutAllDeviceSessions: () => Promise<void>;
93
- updateDeviceName: (deviceName: string) => Promise<void>;
94
- clearSessionState: () => Promise<void>;
95
- clearAllAccountData: () => Promise<void>;
96
- oxyServices: OxyServices;
97
- useFollow?: UseFollowHook;
98
- showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
99
- openAvatarPicker: () => void;
100
- }
101
-
102
- const OxyContext = createContext<OxyContextState | null>(null);
103
-
104
- export interface OxyContextProviderProps {
105
- children: ReactNode;
106
- oxyServices?: OxyServices;
107
- baseURL?: string;
108
- storageKeyPrefix?: string;
109
- onAuthStateChange?: (user: User | null) => void;
110
- onError?: (error: ApiError) => void;
111
- }
36
+ // Import context, types and useOxy from base file to avoid circular dependencies
37
+ import {
38
+ OxyContext,
39
+ useOxy,
40
+ type OxyContextState,
41
+ type OxyContextProviderProps,
42
+ } from './OxyContextBase';
43
+
44
+ // Re-export for backwards compatibility
45
+ export { OxyContext, useOxy, type OxyContextState, type OxyContextProviderProps };
112
46
 
113
47
  let cachedUseFollowHook: UseFollowHook | null = null;
114
48
 
@@ -297,7 +231,20 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
297
231
 
298
232
  // Get current user from query (no persistent cache - always fetch fresh)
299
233
  // Services never caches profile - always fetch from backend
300
- const { data: userData } = useCurrentUser({ enabled: isAuthenticated && !!activeSessionId });
234
+ // Note: We use useQuery directly here instead of useCurrentUser to avoid
235
+ // circular dependency (useCurrentUser calls useOxy, but we're inside the provider)
236
+ const { data: userData } = useQuery({
237
+ queryKey: queryKeys.accounts.current(),
238
+ queryFn: async () => {
239
+ if (!activeSessionId) {
240
+ throw new Error('No active session');
241
+ }
242
+ return await oxyServices.getUserBySession(activeSessionId);
243
+ },
244
+ enabled: isAuthenticated && !!activeSessionId,
245
+ staleTime: 0, // Always fetch fresh - Services never caches profile
246
+ gcTime: 0, // No garbage collection time - always fetch fresh
247
+ });
301
248
  const user = userData ?? null;
302
249
 
303
250
  const {
@@ -1206,12 +1153,4 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1206
1153
 
1207
1154
  export const OxyContextProvider = OxyProvider;
1208
1155
 
1209
- export const useOxy = (): OxyContextState => {
1210
- const context = useContext(OxyContext);
1211
- if (!context) {
1212
- throw new Error('useOxy must be used within an OxyContextProvider');
1213
- }
1214
- return context;
1215
- };
1216
-
1217
1156
  export default OxyContext;
@@ -0,0 +1,95 @@
1
+ import { createContext, useContext } from 'react';
2
+ import type { ReactNode } from 'react';
3
+ import type { OxyServices } from '../../core';
4
+ import type { User, ApiError } from '../../models/interfaces';
5
+ import type { ClientSession } from '../../models/session';
6
+ import type { UseFollowHook } from '../hooks/useFollow.types';
7
+ import type { useLanguageManagement } from '../hooks/useLanguageManagement';
8
+ import type { RouteName } from '../navigation/routes';
9
+ import type { BackupData } from '../../crypto';
10
+
11
+ export interface OxyContextState {
12
+ user: User | null;
13
+ sessions: ClientSession[];
14
+ activeSessionId: string | null;
15
+ currentDeviceId: string | null;
16
+ isAuthenticated: boolean;
17
+ isLoading: boolean;
18
+ isTokenReady: boolean;
19
+ isStorageReady: boolean;
20
+ error: string | null;
21
+ currentLanguage: string;
22
+ currentLanguageMetadata: ReturnType<typeof useLanguageManagement>['metadata'];
23
+ currentLanguageName: string;
24
+ currentNativeLanguageName: string;
25
+
26
+ // Identity management (public key authentication - offline-first)
27
+ createIdentity: () => Promise<{ synced: boolean }>;
28
+ importIdentity: (backupData: BackupData, password: string) => Promise<{ synced: boolean }>;
29
+ signIn: (deviceName?: string) => Promise<User>;
30
+ hasIdentity: () => Promise<boolean>;
31
+ getPublicKey: () => Promise<string | null>;
32
+ isIdentitySynced: () => Promise<boolean>;
33
+ syncIdentity: () => Promise<User>;
34
+ deleteIdentityAndClearAccount: (skipBackup?: boolean, force?: boolean, userConfirmed?: boolean) => Promise<void>;
35
+ storeTransferCode: (transferId: string, code: string, sourceDeviceId: string | null, publicKey: string) => Promise<void>;
36
+ getTransferCode: (transferId: string) => { code: string; sourceDeviceId: string | null; publicKey: string; timestamp: number; state: 'pending' | 'completed' | 'failed' } | null;
37
+ clearTransferCode: (transferId: string) => Promise<void>;
38
+ getAllPendingTransfers: () => Array<{ transferId: string; data: { code: string; sourceDeviceId: string | null; publicKey: string; timestamp: number; state: 'pending' | 'completed' | 'failed' } }>;
39
+ getActiveTransferId: () => string | null;
40
+ updateTransferState: (transferId: string, state: 'pending' | 'completed' | 'failed') => Promise<void>;
41
+
42
+ // Identity sync state (reactive, from Zustand store)
43
+ identitySyncState: {
44
+ isSynced: boolean;
45
+ isSyncing: boolean;
46
+ };
47
+
48
+ // Session management
49
+ logout: (targetSessionId?: string) => Promise<void>;
50
+ logoutAll: () => Promise<void>;
51
+ switchSession: (sessionId: string) => Promise<void>;
52
+ removeSession: (sessionId: string) => Promise<void>;
53
+ refreshSessions: () => Promise<void>;
54
+ setLanguage: (languageId: string) => Promise<void>;
55
+ getDeviceSessions: () => Promise<
56
+ Array<{
57
+ sessionId: string;
58
+ deviceId: string;
59
+ deviceName?: string;
60
+ lastActive?: string;
61
+ expiresAt?: string;
62
+ }>
63
+ >;
64
+ logoutAllDeviceSessions: () => Promise<void>;
65
+ updateDeviceName: (deviceName: string) => Promise<void>;
66
+ clearSessionState: () => Promise<void>;
67
+ clearAllAccountData: () => Promise<void>;
68
+ oxyServices: OxyServices;
69
+ useFollow?: UseFollowHook;
70
+ showBottomSheet?: (screenOrConfig: RouteName | { screen: RouteName; props?: Record<string, unknown> }) => void;
71
+ openAvatarPicker: () => void;
72
+ }
73
+
74
+ export const OxyContext = createContext<OxyContextState | null>(null);
75
+
76
+ export interface OxyContextProviderProps {
77
+ children: ReactNode;
78
+ oxyServices?: OxyServices;
79
+ baseURL?: string;
80
+ storageKeyPrefix?: string;
81
+ onAuthStateChange?: (user: User | null) => void;
82
+ onError?: (error: ApiError) => void;
83
+ }
84
+
85
+ /**
86
+ * Hook to access the Oxy context.
87
+ * Must be used within an OxyContextProvider.
88
+ */
89
+ export const useOxy = (): OxyContextState => {
90
+ const context = useContext(OxyContext);
91
+ if (!context) {
92
+ throw new Error('useOxy must be used within an OxyContextProvider');
93
+ }
94
+ return context;
95
+ };
@@ -2,7 +2,7 @@ import { useQuery, useQueries } from '@tanstack/react-query';
2
2
  import type { User } from '../../../models/interfaces';
3
3
  import type { OxyServices } from '../../../core';
4
4
  import { queryKeys } from './queryKeys';
5
- import { useOxy } from '../../context/OxyContext';
5
+ import { useOxy } from '../../context/OxyContextBase';
6
6
 
7
7
  /**
8
8
  * Get user profile by session ID
@@ -1,6 +1,6 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
2
  import { queryKeys } from './queryKeys';
3
- import { useOxy } from '../../context/OxyContext';
3
+ import { useOxy } from '../../context/OxyContextBase';
4
4
  import type { SecurityActivity, SecurityEventType } from '../../../models/interfaces';
5
5
 
6
6
  /**
@@ -1,7 +1,7 @@
1
1
  import { useQuery } from '@tanstack/react-query';
2
2
  import type { ClientSession } from '../../../models/session';
3
3
  import { queryKeys } from './queryKeys';
4
- import { useOxy } from '../../context/OxyContext';
4
+ import { useOxy } from '../../context/OxyContextBase';
5
5
  import { fetchSessionsWithFallback, mapSessionsToClient } from '../../utils/sessionHelpers';
6
6
 
7
7
  /**