@oxyhq/services 5.13.27 → 5.13.29

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 (30) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.devices.js +14 -0
  2. package/lib/commonjs/core/mixins/OxyServices.devices.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +8 -0
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/useSessionSocket.js +57 -10
  6. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  7. package/lib/commonjs/ui/screens/AccountSettingsScreen.js +20 -1
  8. package/lib/commonjs/ui/screens/AccountSettingsScreen.js.map +1 -1
  9. package/lib/module/core/mixins/OxyServices.devices.js +14 -0
  10. package/lib/module/core/mixins/OxyServices.devices.js.map +1 -1
  11. package/lib/module/ui/context/OxyContext.js +8 -0
  12. package/lib/module/ui/context/OxyContext.js.map +1 -1
  13. package/lib/module/ui/hooks/useSessionSocket.js +57 -10
  14. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  15. package/lib/module/ui/screens/AccountSettingsScreen.js +20 -1
  16. package/lib/module/ui/screens/AccountSettingsScreen.js.map +1 -1
  17. package/lib/typescript/core/mixins/OxyServices.devices.d.ts +10 -0
  18. package/lib/typescript/core/mixins/OxyServices.devices.d.ts.map +1 -1
  19. package/lib/typescript/core/mixins/index.d.ts +6 -0
  20. package/lib/typescript/core/mixins/index.d.ts.map +1 -1
  21. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  22. package/lib/typescript/ui/hooks/useSessionSocket.d.ts +2 -1
  23. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  24. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts +3 -1
  25. package/lib/typescript/ui/screens/AccountSettingsScreen.d.ts.map +1 -1
  26. package/package.json +1 -1
  27. package/src/core/mixins/OxyServices.devices.ts +19 -0
  28. package/src/ui/context/OxyContext.tsx +8 -0
  29. package/src/ui/hooks/useSessionSocket.ts +64 -11
  30. package/src/ui/screens/AccountSettingsScreen.tsx +20 -1
@@ -1,5 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { BaseScreenProps } from '../navigation/types';
3
- declare const _default: React.NamedExoticComponent<BaseScreenProps>;
3
+ declare const _default: React.NamedExoticComponent<BaseScreenProps & {
4
+ initialField?: string;
5
+ }>;
4
6
  export default _default;
5
7
  //# sourceMappingURL=AccountSettingsScreen.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"AccountSettingsScreen.d.ts","sourceRoot":"","sources":["../../../../src/ui/screens/AccountSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAcjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;;AAuhE3D,wBAAiD"}
1
+ {"version":3,"file":"AccountSettingsScreen.d.ts","sourceRoot":"","sources":["../../../../src/ui/screens/AccountSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAcjF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;;mBAqBc,MAAM;;AAqhE/E,wBAAiD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxyhq/services",
3
- "version": "5.13.27",
3
+ "version": "5.13.29",
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",
@@ -98,6 +98,25 @@ export function OxyServicesDevicesMixin<T extends typeof OxyServicesBase>(Base:
98
98
  throw this.handleError(error);
99
99
  }
100
100
  }
101
+
102
+ /**
103
+ * Get security information (TOTP status, backup codes count)
104
+ * @returns Security information object
105
+ */
106
+ async getSecurityInfo(): Promise<{
107
+ twoFactorEnabled: boolean;
108
+ totpCreatedAt: string | null;
109
+ backupCodesCount: number;
110
+ recoveryEmail: string | null;
111
+ }> {
112
+ try {
113
+ return await this.makeRequest('GET', '/api/devices/security', undefined, {
114
+ cache: false,
115
+ });
116
+ } catch (error) {
117
+ throw this.handleError(error);
118
+ }
119
+ }
101
120
  };
102
121
  }
103
122
 
@@ -894,10 +894,18 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
894
894
  }
895
895
  }, [bottomSheetRef]);
896
896
 
897
+ // Get current deviceId from active session
898
+ const currentDeviceId = useMemo(() => {
899
+ if (!activeSessionId || !sessions.length) return null;
900
+ const activeSession = sessions.find(s => s.sessionId === activeSessionId);
901
+ return activeSession?.deviceId || null;
902
+ }, [activeSessionId, sessions]);
903
+
897
904
  // Integrate socket for real-time session updates
898
905
  useSessionSocket({
899
906
  userId: user?.id,
900
907
  activeSessionId,
908
+ currentDeviceId,
901
909
  refreshSessions,
902
910
  logout,
903
911
  baseURL: oxyServices.getBaseURL(),
@@ -5,13 +5,14 @@ import { toast } from '../../lib/sonner';
5
5
  interface UseSessionSocketProps {
6
6
  userId: string | null | undefined;
7
7
  activeSessionId: string | null | undefined;
8
+ currentDeviceId: string | null | undefined;
8
9
  refreshSessions: () => Promise<void>;
9
10
  logout: () => Promise<void>;
10
11
  baseURL: string;
11
12
  onRemoteSignOut?: () => void;
12
13
  }
13
14
 
14
- export function useSessionSocket({ userId, activeSessionId, refreshSessions, logout, baseURL, onRemoteSignOut }: UseSessionSocketProps) {
15
+ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, baseURL, onRemoteSignOut }: UseSessionSocketProps) {
15
16
  const socketRef = useRef<any>(null);
16
17
  const joinedRoomRef = useRef<string | null>(null);
17
18
 
@@ -20,6 +21,7 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
20
21
  const logoutRef = useRef(logout);
21
22
  const onRemoteSignOutRef = useRef(onRemoteSignOut);
22
23
  const activeSessionIdRef = useRef(activeSessionId);
24
+ const currentDeviceIdRef = useRef(currentDeviceId);
23
25
 
24
26
  // Update refs when callbacks change
25
27
  useEffect(() => {
@@ -27,7 +29,8 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
27
29
  logoutRef.current = logout;
28
30
  onRemoteSignOutRef.current = onRemoteSignOut;
29
31
  activeSessionIdRef.current = activeSessionId;
30
- }, [refreshSessions, logout, onRemoteSignOut, activeSessionId]);
32
+ currentDeviceIdRef.current = currentDeviceId;
33
+ }, [refreshSessions, logout, onRemoteSignOut, activeSessionId, currentDeviceId]);
31
34
 
32
35
  useEffect(() => {
33
36
  if (!userId || !baseURL) {
@@ -71,22 +74,72 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
71
74
  }
72
75
  };
73
76
 
74
- const handleSessionUpdate = (data: { type: string; sessionId: string }) => {
77
+ const handleSessionUpdate = (data: {
78
+ type: string;
79
+ sessionId?: string;
80
+ deviceId?: string;
81
+ sessionIds?: string[]
82
+ }) => {
75
83
  if (__DEV__) {
76
84
  console.log('Received session_update:', data);
77
85
  }
78
86
 
79
- // Use refs to get latest callback versions
80
- refreshSessionsRef.current();
87
+ const currentActiveSessionId = activeSessionIdRef.current;
88
+ const currentDeviceId = currentDeviceIdRef.current;
81
89
 
82
- // If the current session was logged out, handle it specially
83
- if (data.sessionId === activeSessionIdRef.current) {
84
- if (onRemoteSignOutRef.current) {
85
- onRemoteSignOutRef.current();
90
+ // Handle different event types
91
+ if (data.type === 'session_removed') {
92
+ // If the removed sessionId matches the current activeSessionId, immediately logout
93
+ if (data.sessionId === currentActiveSessionId) {
94
+ if (onRemoteSignOutRef.current) {
95
+ onRemoteSignOutRef.current();
96
+ } else {
97
+ toast.info('You have been signed out remotely.');
98
+ }
99
+ logoutRef.current();
86
100
  } else {
87
- toast.info('You have been signed out remotely.');
101
+ // Otherwise, just refresh the sessions list
102
+ refreshSessionsRef.current();
103
+ }
104
+ } else if (data.type === 'device_removed') {
105
+ // If the removed deviceId matches the current device, immediately logout
106
+ if (data.deviceId && data.deviceId === currentDeviceId) {
107
+ if (onRemoteSignOutRef.current) {
108
+ onRemoteSignOutRef.current();
109
+ } else {
110
+ toast.info('This device has been removed. You have been signed out.');
111
+ }
112
+ logoutRef.current();
113
+ } else {
114
+ // Otherwise, refresh sessions and device list
115
+ refreshSessionsRef.current();
116
+ }
117
+ } else if (data.type === 'sessions_removed') {
118
+ // If the current activeSessionId is in the removed sessionIds list, immediately logout
119
+ if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
120
+ if (onRemoteSignOutRef.current) {
121
+ onRemoteSignOutRef.current();
122
+ } else {
123
+ toast.info('You have been signed out remotely.');
124
+ }
125
+ logoutRef.current();
126
+ } else {
127
+ // Otherwise, refresh sessions list
128
+ refreshSessionsRef.current();
129
+ }
130
+ } else {
131
+ // For other event types (e.g., session_created), refresh sessions
132
+ refreshSessionsRef.current();
133
+
134
+ // If the current session was logged out (legacy behavior), handle it specially
135
+ if (data.sessionId === currentActiveSessionId) {
136
+ if (onRemoteSignOutRef.current) {
137
+ onRemoteSignOutRef.current();
138
+ } else {
139
+ toast.info('You have been signed out remotely.');
140
+ }
141
+ logoutRef.current();
88
142
  }
89
- logoutRef.current();
90
143
  }
91
144
  };
92
145
 
@@ -33,11 +33,12 @@ const locationSearchCache = new TTLCache<any[]>(60 * 60 * 1000); // 1 hour cache
33
33
  registerCacheForCleanup(linkMetadataCache);
34
34
  registerCacheForCleanup(locationSearchCache);
35
35
 
36
- const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
36
+ const AccountSettingsScreen: React.FC<BaseScreenProps & { initialField?: string }> = ({
37
37
  onClose,
38
38
  theme,
39
39
  goBack,
40
40
  navigate,
41
+ initialField,
41
42
  }) => {
42
43
  const { user: userFromContext, oxyServices, isLoading: authLoading, isAuthenticated, showBottomSheet, activeSessionId } = useOxy();
43
44
  const { t } = useI18n();
@@ -241,6 +242,24 @@ const AccountSettingsScreen: React.FC<BaseScreenProps> = ({
241
242
  }
242
243
  }, [user, avatarFileId, isUpdatingAvatar, optimisticAvatarId]);
243
244
 
245
+ // Set initial editing field if provided via props (e.g., from navigation)
246
+ // Use a ref to track if we've already set the initial field to avoid loops
247
+ const hasSetInitialFieldRef = useRef(false);
248
+ const previousInitialFieldRef = useRef<string | undefined>(undefined);
249
+ useEffect(() => {
250
+ // If initialField changed, reset the flag
251
+ if (previousInitialFieldRef.current !== initialField) {
252
+ hasSetInitialFieldRef.current = false;
253
+ previousInitialFieldRef.current = initialField;
254
+ }
255
+
256
+ // Set the editing field if initialField is provided and we haven't set it yet
257
+ if (initialField && !hasSetInitialFieldRef.current) {
258
+ setEditingField(initialField);
259
+ hasSetInitialFieldRef.current = true;
260
+ }
261
+ }, [initialField]);
262
+
244
263
  const handleSave = async () => {
245
264
  if (!user) return;
246
265