@oxyhq/services 5.13.27 → 5.13.30

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 +51 -3
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/useSessionSocket.js +99 -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 +51 -3
  12. package/lib/module/ui/context/OxyContext.js.map +1 -1
  13. package/lib/module/ui/hooks/useSessionSocket.js +99 -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 +3 -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 +56 -2
  29. package/src/ui/hooks/useSessionSocket.ts +106 -11
  30. package/src/ui/screens/AccountSettingsScreen.tsx +20 -1
@@ -5,13 +5,15 @@ 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;
13
+ onSessionRemoved?: (sessionId: string) => void;
12
14
  }
13
15
 
14
- export function useSessionSocket({ userId, activeSessionId, refreshSessions, logout, baseURL, onRemoteSignOut }: UseSessionSocketProps) {
16
+ export function useSessionSocket({ userId, activeSessionId, currentDeviceId, refreshSessions, logout, baseURL, onRemoteSignOut, onSessionRemoved }: UseSessionSocketProps) {
15
17
  const socketRef = useRef<any>(null);
16
18
  const joinedRoomRef = useRef<string | null>(null);
17
19
 
@@ -19,15 +21,19 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
19
21
  const refreshSessionsRef = useRef(refreshSessions);
20
22
  const logoutRef = useRef(logout);
21
23
  const onRemoteSignOutRef = useRef(onRemoteSignOut);
24
+ const onSessionRemovedRef = useRef(onSessionRemoved);
22
25
  const activeSessionIdRef = useRef(activeSessionId);
26
+ const currentDeviceIdRef = useRef(currentDeviceId);
23
27
 
24
28
  // Update refs when callbacks change
25
29
  useEffect(() => {
26
30
  refreshSessionsRef.current = refreshSessions;
27
31
  logoutRef.current = logout;
28
32
  onRemoteSignOutRef.current = onRemoteSignOut;
33
+ onSessionRemovedRef.current = onSessionRemoved;
29
34
  activeSessionIdRef.current = activeSessionId;
30
- }, [refreshSessions, logout, onRemoteSignOut, activeSessionId]);
35
+ currentDeviceIdRef.current = currentDeviceId;
36
+ }, [refreshSessions, logout, onRemoteSignOut, onSessionRemoved, activeSessionId, currentDeviceId]);
31
37
 
32
38
  useEffect(() => {
33
39
  if (!userId || !baseURL) {
@@ -71,22 +77,111 @@ export function useSessionSocket({ userId, activeSessionId, refreshSessions, log
71
77
  }
72
78
  };
73
79
 
74
- const handleSessionUpdate = (data: { type: string; sessionId: string }) => {
80
+ const handleSessionUpdate = (data: {
81
+ type: string;
82
+ sessionId?: string;
83
+ deviceId?: string;
84
+ sessionIds?: string[]
85
+ }) => {
75
86
  if (__DEV__) {
76
87
  console.log('Received session_update:', data);
77
88
  }
78
89
 
79
- // Use refs to get latest callback versions
80
- refreshSessionsRef.current();
90
+ const currentActiveSessionId = activeSessionIdRef.current;
91
+ const currentDeviceId = currentDeviceIdRef.current;
81
92
 
82
- // If the current session was logged out, handle it specially
83
- if (data.sessionId === activeSessionIdRef.current) {
84
- if (onRemoteSignOutRef.current) {
85
- onRemoteSignOutRef.current();
93
+ // Handle different event types
94
+ if (data.type === 'session_removed') {
95
+ // Track removed session
96
+ if (data.sessionId && onSessionRemovedRef.current) {
97
+ onSessionRemovedRef.current(data.sessionId);
98
+ }
99
+
100
+ // If the removed sessionId matches the current activeSessionId, immediately logout
101
+ if (data.sessionId === currentActiveSessionId) {
102
+ if (onRemoteSignOutRef.current) {
103
+ onRemoteSignOutRef.current();
104
+ } else {
105
+ toast.info('You have been signed out remotely.');
106
+ }
107
+ logoutRef.current();
108
+ } else {
109
+ // Otherwise, just refresh the sessions list (with error handling)
110
+ refreshSessionsRef.current().catch((error) => {
111
+ // Silently handle errors from refresh - they're expected if sessions were removed
112
+ if (__DEV__) {
113
+ console.debug('Failed to refresh sessions after session_removed:', error);
114
+ }
115
+ });
116
+ }
117
+ } else if (data.type === 'device_removed') {
118
+ // Track all removed sessions from this device
119
+ if (data.sessionIds && onSessionRemovedRef.current) {
120
+ for (const sessionId of data.sessionIds) {
121
+ onSessionRemovedRef.current(sessionId);
122
+ }
123
+ }
124
+
125
+ // If the removed deviceId matches the current device, immediately logout
126
+ if (data.deviceId && data.deviceId === currentDeviceId) {
127
+ if (onRemoteSignOutRef.current) {
128
+ onRemoteSignOutRef.current();
129
+ } else {
130
+ toast.info('This device has been removed. You have been signed out.');
131
+ }
132
+ logoutRef.current();
86
133
  } else {
87
- toast.info('You have been signed out remotely.');
134
+ // Otherwise, refresh sessions and device list (with error handling)
135
+ refreshSessionsRef.current().catch((error) => {
136
+ // Silently handle errors from refresh - they're expected if sessions were removed
137
+ if (__DEV__) {
138
+ console.debug('Failed to refresh sessions after device_removed:', error);
139
+ }
140
+ });
141
+ }
142
+ } else if (data.type === 'sessions_removed') {
143
+ // Track all removed sessions
144
+ if (data.sessionIds && onSessionRemovedRef.current) {
145
+ for (const sessionId of data.sessionIds) {
146
+ onSessionRemovedRef.current(sessionId);
147
+ }
148
+ }
149
+
150
+ // If the current activeSessionId is in the removed sessionIds list, immediately logout
151
+ if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
152
+ if (onRemoteSignOutRef.current) {
153
+ onRemoteSignOutRef.current();
154
+ } else {
155
+ toast.info('You have been signed out remotely.');
156
+ }
157
+ logoutRef.current();
158
+ } else {
159
+ // Otherwise, refresh sessions list (with error handling)
160
+ refreshSessionsRef.current().catch((error) => {
161
+ // Silently handle errors from refresh - they're expected if sessions were removed
162
+ if (__DEV__) {
163
+ console.debug('Failed to refresh sessions after sessions_removed:', error);
164
+ }
165
+ });
166
+ }
167
+ } else {
168
+ // For other event types (e.g., session_created), refresh sessions (with error handling)
169
+ refreshSessionsRef.current().catch((error) => {
170
+ // Log but don't throw - refresh errors shouldn't break the socket handler
171
+ if (__DEV__) {
172
+ console.debug('Failed to refresh sessions after session_update:', error);
173
+ }
174
+ });
175
+
176
+ // If the current session was logged out (legacy behavior), handle it specially
177
+ if (data.sessionId === currentActiveSessionId) {
178
+ if (onRemoteSignOutRef.current) {
179
+ onRemoteSignOutRef.current();
180
+ } else {
181
+ toast.info('You have been signed out remotely.');
182
+ }
183
+ logoutRef.current();
88
184
  }
89
- logoutRef.current();
90
185
  }
91
186
  };
92
187
 
@@ -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