@oxyhq/services 5.16.24 → 5.16.25

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 (38) hide show
  1. package/lib/commonjs/core/mixins/OxyServices.user.js +14 -4
  2. package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +40 -75
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +7 -6
  6. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  7. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +4 -3
  8. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  9. package/lib/commonjs/ui/hooks/useSessionSocket.js +349 -328
  10. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  11. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +13 -6
  12. package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
  13. package/lib/module/core/mixins/OxyServices.user.js +14 -4
  14. package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
  15. package/lib/module/ui/context/OxyContext.js +40 -75
  16. package/lib/module/ui/context/OxyContext.js.map +1 -1
  17. package/lib/module/ui/hooks/mutations/useAccountMutations.js +7 -6
  18. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  19. package/lib/module/ui/hooks/queries/useAccountQueries.js +4 -3
  20. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  21. package/lib/module/ui/hooks/useSessionSocket.js +349 -328
  22. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  23. package/lib/module/ui/screens/PrivacySettingsScreen.js +13 -6
  24. package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
  25. package/lib/typescript/core/mixins/OxyServices.user.d.ts +2 -2
  26. package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
  27. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  28. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  29. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  30. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  31. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/core/mixins/OxyServices.user.ts +14 -4
  34. package/src/ui/context/OxyContext.tsx +39 -75
  35. package/src/ui/hooks/mutations/useAccountMutations.ts +8 -6
  36. package/src/ui/hooks/queries/useAccountQueries.ts +4 -2
  37. package/src/ui/hooks/useSessionSocket.ts +153 -155
  38. package/src/ui/screens/PrivacySettingsScreen.tsx +12 -6
@@ -8,6 +8,7 @@ var _react = require("react");
8
8
  var _socket = _interopRequireDefault(require("socket.io-client"));
9
9
  var _sonner = require("../../lib/sonner");
10
10
  var _loggerUtils = require("../../utils/loggerUtils");
11
+ var _TokenService = require("../../core/services/TokenService");
11
12
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
12
13
  function useSessionSocket({
13
14
  userId,
@@ -27,6 +28,9 @@ function useSessionSocket({
27
28
  const joinedRoomRef = (0, _react.useRef)(null);
28
29
  const accessTokenRef = (0, _react.useRef)(null);
29
30
  const handlersSetupRef = (0, _react.useRef)(false);
31
+ const lastRegisteredSocketIdRef = (0, _react.useRef)(null);
32
+ const getAccessTokenRef = (0, _react.useRef)(getAccessToken);
33
+ const getTransferCodeRef = (0, _react.useRef)(getTransferCode);
30
34
 
31
35
  // Store callbacks in refs to avoid re-joining when they change
32
36
  const refreshSessionsRef = (0, _react.useRef)(refreshSessions);
@@ -48,7 +52,9 @@ function useSessionSocket({
48
52
  onIdentityTransferCompleteRef.current = onIdentityTransferComplete;
49
53
  activeSessionIdRef.current = activeSessionId;
50
54
  currentDeviceIdRef.current = currentDeviceId;
51
- }, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, onIdentityTransferComplete, activeSessionId, currentDeviceId]);
55
+ getAccessTokenRef.current = getAccessToken;
56
+ getTransferCodeRef.current = getTransferCode;
57
+ }, [refreshSessions, logout, clearSessionState, onRemoteSignOut, onSessionRemoved, onIdentityTransferComplete, activeSessionId, currentDeviceId, getAccessToken, getTransferCode]);
52
58
  (0, _react.useEffect)(() => {
53
59
  if (!userId || !baseURL) {
54
60
  // Clean up if userId or baseURL becomes invalid
@@ -59,416 +65,431 @@ function useSessionSocket({
59
65
  }
60
66
  return;
61
67
  }
62
- const accessToken = getAccessToken();
63
- // Recreate socket if token changed or socket doesn't exist
64
- const tokenChanged = accessTokenRef.current !== accessToken;
65
- if (!socketRef.current || tokenChanged) {
66
- // Disconnect old socket if exists
67
- if (socketRef.current) {
68
- socketRef.current.disconnect();
69
- socketRef.current = null;
70
- }
71
-
72
- // Create new socket with authentication
73
- const socketOptions = {
74
- transports: ['websocket']
75
- };
76
- if (accessToken) {
77
- socketOptions.auth = {
78
- token: accessToken
79
- };
80
- if (__DEV__) {
81
- console.log('[useSessionSocket] Creating socket with auth token', {
82
- userId,
83
- hasToken: !!accessToken,
84
- tokenLength: accessToken.length
85
- });
86
- }
87
- } else {
88
- if (__DEV__) {
89
- console.warn('[useSessionSocket] No access token available for socket authentication');
90
- }
91
- }
92
- socketRef.current = (0, _socket.default)(baseURL, socketOptions);
93
- accessTokenRef.current = accessToken;
94
- joinedRoomRef.current = null; // Reset room tracking
95
- handlersSetupRef.current = false; // Reset handlers flag for new socket
96
- }
97
- const socket = socketRef.current;
98
68
 
99
- // Server auto-joins room on connection when authenticated, so we don't need to manually join
100
- // Just track that we're in the room
101
- if (!joinedRoomRef.current && socket.connected) {
102
- joinedRoomRef.current = `user:${userId}`;
103
- if (__DEV__) {
104
- console.log('[useSessionSocket] Socket connected, should be auto-joined to room', {
69
+ // Initialize socket with token refresh
70
+ const initializeSocket = async () => {
71
+ try {
72
+ // Refresh token if expiring soon before creating socket connection
73
+ await _TokenService.tokenService.refreshTokenIfNeeded();
74
+ } catch (error) {
75
+ // If refresh fails, log but continue with current token
76
+ _loggerUtils.logger.debug('Token refresh failed before socket connection', {
77
+ component: 'useSessionSocket',
105
78
  userId,
106
- room: `user:${userId}`
79
+ error
107
80
  });
108
81
  }
109
- }
82
+ const accessToken = getAccessTokenRef.current();
83
+ // Recreate socket if token changed or socket doesn't exist
84
+ const tokenChanged = accessTokenRef.current !== accessToken;
85
+ if (!socketRef.current || tokenChanged) {
86
+ // Disconnect old socket if exists
87
+ if (socketRef.current) {
88
+ socketRef.current.disconnect();
89
+ socketRef.current = null;
90
+ }
110
91
 
111
- // Set up event handlers (only once per socket instance)
112
- // Define handlers outside if block so they're always available
113
- const handleConnect = () => {
114
- const currentToken = getAccessToken();
115
- if (__DEV__) {
116
- console.log('[useSessionSocket] Socket connected', {
117
- socketId: socket.id,
118
- userId,
119
- room: `user:${userId}`,
120
- hasAuth: !!currentToken
121
- });
122
- _loggerUtils.logger.debug('Socket connected', {
123
- component: 'useSessionSocket',
124
- socketId: socket.id,
125
- userId
126
- });
92
+ // Create new socket with authentication
93
+ const socketOptions = {
94
+ transports: ['websocket']
95
+ };
96
+
97
+ // Get fresh token after potential refresh
98
+ const freshToken = getAccessTokenRef.current();
99
+ if (freshToken) {
100
+ socketOptions.auth = {
101
+ token: freshToken
102
+ };
103
+ } else {
104
+ _loggerUtils.logger.debug('No access token available for socket authentication', {
105
+ component: 'useSessionSocket',
106
+ userId
107
+ });
108
+ }
109
+ socketRef.current = (0, _socket.default)(baseURL, socketOptions);
110
+ accessTokenRef.current = freshToken;
111
+ joinedRoomRef.current = null; // Reset room tracking
112
+ handlersSetupRef.current = false; // Reset handlers flag for new socket
127
113
  }
128
- // Server auto-joins room on connection when authenticated
129
- // Just track that we're connected
130
- if (userId) {
114
+ const socket = socketRef.current;
115
+ if (!socket) return;
116
+ if (!joinedRoomRef.current && socket.connected) {
131
117
  joinedRoomRef.current = `user:${userId}`;
132
118
  }
133
- };
134
- const handleDisconnect = reason => {
135
- if (__DEV__) {
136
- console.log('[useSessionSocket] Socket disconnected:', reason);
119
+
120
+ // Set up event handlers (only once per socket instance)
121
+ // Define handlers - they reference socket from closure
122
+ const handleConnect = () => {
123
+ const currentToken = getAccessTokenRef.current();
124
+ if (__DEV__) {
125
+ console.log('[useSessionSocket] Socket connected', {
126
+ socketId: socket.id,
127
+ userId,
128
+ room: `user:${userId}`,
129
+ hasAuth: !!currentToken
130
+ });
131
+ _loggerUtils.logger.debug('Socket connected', {
132
+ component: 'useSessionSocket',
133
+ socketId: socket.id,
134
+ userId
135
+ });
136
+ }
137
+ // Server auto-joins room on connection when authenticated
138
+ // Just track that we're connected
139
+ if (userId) {
140
+ joinedRoomRef.current = `user:${userId}`;
141
+ }
142
+ };
143
+ const handleDisconnect = async reason => {
137
144
  _loggerUtils.logger.debug('Socket disconnected', {
138
145
  component: 'useSessionSocket',
139
146
  reason,
140
147
  userId
141
148
  });
142
- }
143
- joinedRoomRef.current = null; // Reset room tracking on disconnect
144
- };
145
- const handleError = error => {
146
- if (__DEV__) {
147
- console.error('[useSessionSocket] Socket error', error);
149
+ joinedRoomRef.current = null;
150
+
151
+ // If disconnected due to auth error, try to refresh token and reconnect
152
+ if (reason === 'io server disconnect' || reason.includes('auth') || reason.includes('Authentication')) {
153
+ try {
154
+ // Refresh token and reconnect
155
+ await _TokenService.tokenService.refreshTokenIfNeeded();
156
+ const freshToken = getAccessTokenRef.current();
157
+ if (freshToken && socketRef.current) {
158
+ // Update auth and reconnect
159
+ socketRef.current.auth = {
160
+ token: freshToken
161
+ };
162
+ socketRef.current.connect();
163
+ }
164
+ } catch (error) {
165
+ _loggerUtils.logger.debug('Failed to refresh token after disconnect', {
166
+ component: 'useSessionSocket',
167
+ userId,
168
+ error
169
+ });
170
+ }
171
+ }
172
+ };
173
+ const handleError = error => {
148
174
  _loggerUtils.logger.error('Socket error', error, {
149
175
  component: 'useSessionSocket',
150
176
  userId
151
177
  });
152
- }
153
- };
154
- const handleSessionUpdate = async data => {
155
- if (__DEV__) {
156
- console.log('[useSessionSocket] Received session_update event:', {
178
+ };
179
+ const handleConnectError = async error => {
180
+ _loggerUtils.logger.debug('Socket connection error', {
181
+ component: 'useSessionSocket',
182
+ userId,
183
+ error: error.message
184
+ });
185
+
186
+ // If error is due to expired/invalid token, try to refresh and reconnect
187
+ if (error.message.includes('Authentication') || error.message.includes('expired') || error.message.includes('token')) {
188
+ try {
189
+ await _TokenService.tokenService.refreshTokenIfNeeded();
190
+ const freshToken = getAccessTokenRef.current();
191
+ if (freshToken && socketRef.current) {
192
+ // Update auth and reconnect
193
+ socketRef.current.auth = {
194
+ token: freshToken
195
+ };
196
+ socketRef.current.connect();
197
+ }
198
+ } catch (refreshError) {
199
+ _loggerUtils.logger.debug('Failed to refresh token after connection error', {
200
+ component: 'useSessionSocket',
201
+ userId,
202
+ error: refreshError
203
+ });
204
+ }
205
+ }
206
+ };
207
+ const handleSessionUpdate = async data => {
208
+ _loggerUtils.logger.debug('Received session_update event', {
209
+ component: 'useSessionSocket',
157
210
  type: data.type,
158
211
  socketId: socket.id,
159
212
  socketConnected: socket.connected,
160
213
  roomId: joinedRoomRef.current
161
214
  });
162
- }
163
- const currentActiveSessionId = activeSessionIdRef.current;
164
- const currentDeviceId = currentDeviceIdRef.current;
215
+ const currentActiveSessionId = activeSessionIdRef.current;
216
+ const currentDeviceId = currentDeviceIdRef.current;
165
217
 
166
- // Handle different event types
167
- if (data.type === 'session_removed') {
168
- // Track removed session
169
- if (data.sessionId && onSessionRemovedRef.current) {
170
- onSessionRemovedRef.current(data.sessionId);
171
- }
172
-
173
- // If the removed sessionId matches the current activeSessionId, immediately clear state
174
- if (data.sessionId === currentActiveSessionId) {
175
- if (onRemoteSignOutRef.current) {
176
- onRemoteSignOutRef.current();
177
- } else {
178
- _sonner.toast.info('You have been signed out remotely.');
218
+ // Handle different event types
219
+ if (data.type === 'session_removed') {
220
+ // Track removed session
221
+ if (data.sessionId && onSessionRemovedRef.current) {
222
+ onSessionRemovedRef.current(data.sessionId);
179
223
  }
180
- // Use clearSessionState since session was already removed server-side
181
- // Await to ensure storage cleanup completes before continuing
182
- try {
183
- await clearSessionStateRef.current();
184
- } catch (error) {
185
- if (__DEV__) {
186
- _loggerUtils.logger.error('Failed to clear session state after session_removed', error instanceof Error ? error : new Error(String(error)), {
187
- component: 'useSessionSocket'
188
- });
224
+
225
+ // If the removed sessionId matches the current activeSessionId, immediately clear state
226
+ if (data.sessionId === currentActiveSessionId) {
227
+ if (onRemoteSignOutRef.current) {
228
+ onRemoteSignOutRef.current();
229
+ } else {
230
+ _sonner.toast.info('You have been signed out remotely.');
189
231
  }
190
- }
191
- } else {
192
- // Otherwise, just refresh the sessions list (with error handling)
193
- refreshSessionsRef.current().catch(error => {
194
- // Silently handle errors from refresh - they're expected if sessions were removed
195
- if (__DEV__) {
196
- _loggerUtils.logger.debug('Failed to refresh sessions after session_removed', {
197
- component: 'useSessionSocket'
198
- }, error);
232
+ // Use clearSessionState since session was already removed server-side
233
+ // Await to ensure storage cleanup completes before continuing
234
+ try {
235
+ await clearSessionStateRef.current();
236
+ } catch (error) {
237
+ if (__DEV__) {
238
+ _loggerUtils.logger.error('Failed to clear session state after session_removed', error instanceof Error ? error : new Error(String(error)), {
239
+ component: 'useSessionSocket'
240
+ });
241
+ }
199
242
  }
200
- });
201
- }
202
- } else if (data.type === 'device_removed') {
203
- // Track all removed sessions from this device
204
- if (data.sessionIds && onSessionRemovedRef.current) {
205
- for (const sessionId of data.sessionIds) {
206
- onSessionRemovedRef.current(sessionId);
207
- }
208
- }
209
-
210
- // If the removed deviceId matches the current device, immediately clear state
211
- if (data.deviceId && data.deviceId === currentDeviceId) {
212
- if (onRemoteSignOutRef.current) {
213
- onRemoteSignOutRef.current();
214
243
  } else {
215
- _sonner.toast.info('This device has been removed. You have been signed out.');
216
- }
217
- // Use clearSessionState since sessions were already removed server-side
218
- // Await to ensure storage cleanup completes before continuing
219
- try {
220
- await clearSessionStateRef.current();
221
- } catch (error) {
222
- if (__DEV__) {
223
- _loggerUtils.logger.error('Failed to clear session state after device_removed', error instanceof Error ? error : new Error(String(error)), {
224
- component: 'useSessionSocket'
225
- });
226
- }
244
+ // Otherwise, just refresh the sessions list (with error handling)
245
+ refreshSessionsRef.current().catch(error => {
246
+ // Silently handle errors from refresh - they're expected if sessions were removed
247
+ if (__DEV__) {
248
+ _loggerUtils.logger.debug('Failed to refresh sessions after session_removed', {
249
+ component: 'useSessionSocket'
250
+ }, error);
251
+ }
252
+ });
227
253
  }
228
- } else {
229
- // Otherwise, refresh sessions and device list (with error handling)
230
- refreshSessionsRef.current().catch(error => {
231
- // Silently handle errors from refresh - they're expected if sessions were removed
232
- if (__DEV__) {
233
- _loggerUtils.logger.debug('Failed to refresh sessions after device_removed', {
234
- component: 'useSessionSocket'
235
- }, error);
254
+ } else if (data.type === 'device_removed') {
255
+ // Track all removed sessions from this device
256
+ if (data.sessionIds && onSessionRemovedRef.current) {
257
+ for (const sessionId of data.sessionIds) {
258
+ onSessionRemovedRef.current(sessionId);
236
259
  }
237
- });
238
- }
239
- } else if (data.type === 'sessions_removed') {
240
- // Track all removed sessions
241
- if (data.sessionIds && onSessionRemovedRef.current) {
242
- for (const sessionId of data.sessionIds) {
243
- onSessionRemovedRef.current(sessionId);
244
260
  }
245
- }
246
261
 
247
- // If the current activeSessionId is in the removed sessionIds list, immediately clear state
248
- if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
249
- if (onRemoteSignOutRef.current) {
250
- onRemoteSignOutRef.current();
262
+ // If the removed deviceId matches the current device, immediately clear state
263
+ if (data.deviceId && data.deviceId === currentDeviceId) {
264
+ if (onRemoteSignOutRef.current) {
265
+ onRemoteSignOutRef.current();
266
+ } else {
267
+ _sonner.toast.info('This device has been removed. You have been signed out.');
268
+ }
269
+ // Use clearSessionState since sessions were already removed server-side
270
+ // Await to ensure storage cleanup completes before continuing
271
+ try {
272
+ await clearSessionStateRef.current();
273
+ } catch (error) {
274
+ if (__DEV__) {
275
+ _loggerUtils.logger.error('Failed to clear session state after device_removed', error instanceof Error ? error : new Error(String(error)), {
276
+ component: 'useSessionSocket'
277
+ });
278
+ }
279
+ }
251
280
  } else {
252
- _sonner.toast.info('You have been signed out remotely.');
281
+ // Otherwise, refresh sessions and device list (with error handling)
282
+ refreshSessionsRef.current().catch(error => {
283
+ // Silently handle errors from refresh - they're expected if sessions were removed
284
+ if (__DEV__) {
285
+ _loggerUtils.logger.debug('Failed to refresh sessions after device_removed', {
286
+ component: 'useSessionSocket'
287
+ }, error);
288
+ }
289
+ });
253
290
  }
254
- // Use clearSessionState since sessions were already removed server-side
255
- // Await to ensure storage cleanup completes before continuing
256
- try {
257
- await clearSessionStateRef.current();
258
- } catch (error) {
259
- if (__DEV__) {
260
- _loggerUtils.logger.error('Failed to clear session state after sessions_removed', error instanceof Error ? error : new Error(String(error)), {
261
- component: 'useSessionSocket'
262
- });
291
+ } else if (data.type === 'sessions_removed') {
292
+ // Track all removed sessions
293
+ if (data.sessionIds && onSessionRemovedRef.current) {
294
+ for (const sessionId of data.sessionIds) {
295
+ onSessionRemovedRef.current(sessionId);
263
296
  }
264
297
  }
265
- } else {
266
- // Otherwise, refresh sessions list (with error handling)
267
- refreshSessionsRef.current().catch(error => {
268
- // Silently handle errors from refresh - they're expected if sessions were removed
269
- if (__DEV__) {
270
- _loggerUtils.logger.debug('Failed to refresh sessions after sessions_removed', {
271
- component: 'useSessionSocket'
272
- }, error);
298
+
299
+ // If the current activeSessionId is in the removed sessionIds list, immediately clear state
300
+ if (data.sessionIds && currentActiveSessionId && data.sessionIds.includes(currentActiveSessionId)) {
301
+ if (onRemoteSignOutRef.current) {
302
+ onRemoteSignOutRef.current();
303
+ } else {
304
+ _sonner.toast.info('You have been signed out remotely.');
273
305
  }
274
- });
275
- }
276
- } else if (data.type === 'identity_transfer_complete') {
277
- // Handle identity transfer completion notification
278
- const transferData = data;
279
- if (__DEV__) {
280
- console.log('[useSessionSocket] Received identity_transfer_complete event', {
306
+ // Use clearSessionState since sessions were already removed server-side
307
+ // Await to ensure storage cleanup completes before continuing
308
+ try {
309
+ await clearSessionStateRef.current();
310
+ } catch (error) {
311
+ if (__DEV__) {
312
+ _loggerUtils.logger.error('Failed to clear session state after sessions_removed', error instanceof Error ? error : new Error(String(error)), {
313
+ component: 'useSessionSocket'
314
+ });
315
+ }
316
+ }
317
+ } else {
318
+ // Otherwise, refresh sessions list (with error handling)
319
+ refreshSessionsRef.current().catch(error => {
320
+ // Silently handle errors from refresh - they're expected if sessions were removed
321
+ if (__DEV__) {
322
+ _loggerUtils.logger.debug('Failed to refresh sessions after sessions_removed', {
323
+ component: 'useSessionSocket'
324
+ }, error);
325
+ }
326
+ });
327
+ }
328
+ } else if (data.type === 'identity_transfer_complete') {
329
+ // Handle identity transfer completion notification
330
+ const transferData = data;
331
+ _loggerUtils.logger.debug('Received identity_transfer_complete event', {
332
+ component: 'useSessionSocket',
281
333
  transferId: transferData.transferId,
282
334
  sourceDeviceId: transferData.sourceDeviceId,
283
335
  currentDeviceId,
284
- hasActiveSession: activeSessionIdRef.current !== null,
285
- socketConnected: socket.connected
336
+ activeSessionId: activeSessionIdRef.current,
337
+ socketConnected: socket.connected,
338
+ userId,
339
+ room: joinedRoomRef.current,
340
+ publicKey: transferData.publicKey.substring(0, 16) + '...'
286
341
  });
287
- }
288
- _loggerUtils.logger.debug('Received identity_transfer_complete event', {
289
- component: 'useSessionSocket',
290
- transferId: transferData.transferId,
291
- sourceDeviceId: transferData.sourceDeviceId,
292
- currentDeviceId,
293
- activeSessionId: activeSessionIdRef.current,
294
- socketConnected: socket.connected,
295
- userId,
296
- room: joinedRoomRef.current,
297
- publicKey: transferData.publicKey.substring(0, 16) + '...'
298
- });
299
342
 
300
- // Only call handler on the SOURCE device (the one that initiated the transfer)
301
- // The source device is identified by:
302
- // 1. Matching deviceId with sourceDeviceId, OR
303
- // 2. Having a stored transfer code for this transferId (most reliable check)
304
- const deviceIdMatches = transferData.sourceDeviceId && transferData.sourceDeviceId === currentDeviceId;
343
+ // CRITICAL: Only call handler on the SOURCE device (the one that initiated the transfer)
344
+ // The new device (target) should NEVER process this event - it would delete its own identity!
305
345
 
306
- // Check if this device has a stored transfer code (meaning it's the source device)
307
- const hasStoredTransferCode = getTransferCode && !!getTransferCode(transferData.transferId);
346
+ // Check if this device has a stored transfer code (most reliable check - only source device has this)
347
+ const hasStoredTransferCode = getTransferCodeRef.current && !!getTransferCodeRef.current(transferData.transferId);
308
348
 
309
- // Only call handler if this is the source device
310
- // We check both deviceId match AND stored transfer code to handle cases where
311
- // deviceId might be null (logged out device) but transfer code still exists
312
- const shouldCallHandler = !!transferData.transferId && (deviceIdMatches || hasStoredTransferCode);
313
- if (shouldCallHandler) {
314
- const matchReason = deviceIdMatches ? 'deviceId-exact' : activeSessionIdRef.current !== null ? 'active-session' : 'transferId-only';
315
- if (__DEV__) {
316
- console.log('[useSessionSocket] Matched source device, calling handler', {
349
+ // Also check deviceId match (exact match required)
350
+ const deviceIdMatches = transferData.sourceDeviceId && currentDeviceId && transferData.sourceDeviceId === currentDeviceId;
351
+
352
+ // ONLY call handler if BOTH conditions are met:
353
+ // 1. Has stored transfer code (definitive proof this is the source device)
354
+ // 2. DeviceId matches (additional verification)
355
+ // If deviceId is null/undefined, we still allow if stored code exists (logged out source device)
356
+ // But we NEVER process if no stored code exists (definitely not the source device)
357
+ const shouldCallHandler = !!transferData.transferId && hasStoredTransferCode && (deviceIdMatches || !currentDeviceId); // Allow if deviceId matches OR device is logged out (but has stored code)
358
+
359
+ if (shouldCallHandler) {
360
+ const matchReason = deviceIdMatches ? 'deviceId-exact-with-stored-code' : currentDeviceId ? 'deviceId-mismatch-but-has-stored-code' : 'logged-out-source-device-with-stored-code';
361
+ _loggerUtils.logger.debug('Matched source device, calling transfer complete handler', {
362
+ component: 'useSessionSocket',
317
363
  transferId: transferData.transferId,
318
- matchReason,
319
364
  sourceDeviceId: transferData.sourceDeviceId,
320
- currentDeviceId
365
+ currentDeviceId,
366
+ matchReason,
367
+ hasHandler: !!onIdentityTransferCompleteRef.current,
368
+ socketConnected: socket.connected,
369
+ socketId: socket.id
321
370
  });
322
- }
323
- _loggerUtils.logger.debug('Matched source device, calling transfer complete handler', {
324
- component: 'useSessionSocket',
325
- transferId: transferData.transferId,
326
- sourceDeviceId: transferData.sourceDeviceId,
327
- currentDeviceId,
328
- matchReason,
329
- hasHandler: !!onIdentityTransferCompleteRef.current,
330
- socketConnected: socket.connected,
331
- socketId: socket.id
332
- });
333
-
334
- // Call the handler - it will verify using stored transfer codes
335
- if (onIdentityTransferCompleteRef.current) {
336
- try {
337
- if (__DEV__) {
338
- console.log('[useSessionSocket] Calling onIdentityTransferComplete handler', {
371
+ if (onIdentityTransferCompleteRef.current) {
372
+ try {
373
+ _loggerUtils.logger.debug('Calling onIdentityTransferComplete handler', {
374
+ component: 'useSessionSocket',
339
375
  transferId: transferData.transferId
340
376
  });
341
- }
342
- _loggerUtils.logger.debug('Calling onIdentityTransferComplete handler', {
343
- component: 'useSessionSocket',
344
- transferId: transferData.transferId
345
- });
346
- onIdentityTransferCompleteRef.current({
347
- transferId: transferData.transferId,
348
- sourceDeviceId: transferData.sourceDeviceId,
349
- publicKey: transferData.publicKey,
350
- transferCode: transferData.transferCode,
351
- completedAt: transferData.completedAt
352
- });
353
- if (__DEV__) {
354
- console.log('[useSessionSocket] Handler called successfully', {
377
+ onIdentityTransferCompleteRef.current({
378
+ transferId: transferData.transferId,
379
+ sourceDeviceId: transferData.sourceDeviceId,
380
+ publicKey: transferData.publicKey,
381
+ transferCode: transferData.transferCode,
382
+ completedAt: transferData.completedAt
383
+ });
384
+ _loggerUtils.logger.debug('onIdentityTransferComplete handler called successfully', {
385
+ component: 'useSessionSocket',
386
+ transferId: transferData.transferId
387
+ });
388
+ } catch (error) {
389
+ _loggerUtils.logger.error('Error calling onIdentityTransferComplete handler', error instanceof Error ? error : new Error(String(error)), {
390
+ component: 'useSessionSocket',
355
391
  transferId: transferData.transferId
356
392
  });
357
393
  }
358
- _loggerUtils.logger.debug('onIdentityTransferComplete handler called successfully', {
359
- component: 'useSessionSocket',
360
- transferId: transferData.transferId
361
- });
362
- } catch (error) {
363
- if (__DEV__) {
364
- console.error('[useSessionSocket] Error calling handler', error);
365
- }
366
- _loggerUtils.logger.error('Error calling onIdentityTransferComplete handler', error instanceof Error ? error : new Error(String(error)), {
394
+ } else {
395
+ _loggerUtils.logger.debug('No onIdentityTransferComplete handler registered', {
367
396
  component: 'useSessionSocket',
368
397
  transferId: transferData.transferId
369
398
  });
370
399
  }
371
400
  } else {
372
- if (__DEV__) {
373
- console.warn('[useSessionSocket] No handler registered');
374
- }
375
- _loggerUtils.logger.debug('No onIdentityTransferComplete handler registered', {
401
+ _loggerUtils.logger.debug('Not the source device, ignoring transfer completion', {
376
402
  component: 'useSessionSocket',
377
- transferId: transferData.transferId
378
- });
379
- }
380
- } else {
381
- if (__DEV__) {
382
- console.log('[useSessionSocket] Not matched, ignoring', {
383
403
  sourceDeviceId: transferData.sourceDeviceId,
384
404
  currentDeviceId,
385
405
  hasActiveSession: activeSessionIdRef.current !== null
386
406
  });
387
407
  }
388
- _loggerUtils.logger.debug('Not the source device, ignoring transfer completion', {
389
- component: 'useSessionSocket',
390
- sourceDeviceId: transferData.sourceDeviceId,
391
- currentDeviceId,
392
- hasActiveSession: activeSessionIdRef.current !== null
408
+ } else {
409
+ // For other event types (e.g., session_created), refresh sessions (with error handling)
410
+ refreshSessionsRef.current().catch(error => {
411
+ // Log but don't throw - refresh errors shouldn't break the socket handler
412
+ if (__DEV__) {
413
+ _loggerUtils.logger.debug('Failed to refresh sessions after session_update', {
414
+ component: 'useSessionSocket'
415
+ }, error);
416
+ }
393
417
  });
394
- }
395
- } else {
396
- // For other event types (e.g., session_created), refresh sessions (with error handling)
397
- refreshSessionsRef.current().catch(error => {
398
- // Log but don't throw - refresh errors shouldn't break the socket handler
399
- if (__DEV__) {
400
- _loggerUtils.logger.debug('Failed to refresh sessions after session_update', {
401
- component: 'useSessionSocket'
402
- }, error);
403
- }
404
- });
405
418
 
406
- // If the current session was logged out (legacy behavior), handle it specially
407
- if (data.sessionId === currentActiveSessionId) {
408
- if (onRemoteSignOutRef.current) {
409
- onRemoteSignOutRef.current();
410
- } else {
411
- _sonner.toast.info('You have been signed out remotely.');
419
+ // If the current session was logged out (legacy behavior), handle it specially
420
+ if (data.sessionId === currentActiveSessionId) {
421
+ if (onRemoteSignOutRef.current) {
422
+ onRemoteSignOutRef.current();
423
+ } else {
424
+ _sonner.toast.info('You have been signed out remotely.');
425
+ }
426
+ // Use clearSessionState since session was already removed server-side
427
+ // Await to ensure storage cleanup completes before continuing
428
+ try {
429
+ await clearSessionStateRef.current();
430
+ } catch (error) {
431
+ _loggerUtils.logger.error('Failed to clear session state after session_update', error instanceof Error ? error : new Error(String(error)), {
432
+ component: 'useSessionSocket'
433
+ });
434
+ }
412
435
  }
413
- // Use clearSessionState since session was already removed server-side
414
- // Await to ensure storage cleanup completes before continuing
436
+ }
437
+ };
438
+
439
+ // Register event handlers (only once per socket instance)
440
+ // Track by socket.id to prevent duplicate registrations when socket reconnects
441
+ const currentSocketId = socket.id || 'pending';
442
+ if (!handlersSetupRef.current || lastRegisteredSocketIdRef.current !== currentSocketId) {
443
+ // Remove old handlers if socket changed (reconnection)
444
+ if (socketRef.current && handlersSetupRef.current && lastRegisteredSocketIdRef.current) {
415
445
  try {
416
- await clearSessionStateRef.current();
446
+ socketRef.current.off('connect', handleConnect);
447
+ socketRef.current.off('disconnect', handleDisconnect);
448
+ socketRef.current.off('error', handleError);
449
+ socketRef.current.off('session_update', handleSessionUpdate);
417
450
  } catch (error) {
418
- if (__DEV__) {
419
- console.error('Failed to clear session state after session_update:', error);
420
- }
451
+ // Ignore errors when removing handlers
421
452
  }
422
453
  }
423
- }
424
- };
425
454
 
426
- // Register event handlers (only once per socket instance)
427
- if (!handlersSetupRef.current) {
428
- socket.on('connect', handleConnect);
429
- socket.on('disconnect', handleDisconnect);
430
- socket.on('error', handleError);
431
- socket.on('session_update', handleSessionUpdate);
432
- handlersSetupRef.current = true;
433
- if (__DEV__) {
434
- console.log('[useSessionSocket] Event handlers set up', {
455
+ // Register handlers on current socket
456
+ socket.on('connect', handleConnect);
457
+ socket.on('disconnect', handleDisconnect);
458
+ socket.on('error', handleError);
459
+ socket.on('connect_error', handleConnectError);
460
+ socket.on('session_update', handleSessionUpdate);
461
+ handlersSetupRef.current = true;
462
+ lastRegisteredSocketIdRef.current = currentSocketId;
463
+ _loggerUtils.logger.debug('Event handlers set up', {
464
+ component: 'useSessionSocket',
435
465
  socketId: socket.id,
436
466
  userId
437
467
  });
438
468
  }
439
- }
440
-
441
- // Ensure socket is connected before proceeding
442
- if (!socket.connected) {
443
- if (__DEV__) {
444
- console.log('[useSessionSocket] Socket not connected, connecting...', {
445
- userId
446
- });
447
- _loggerUtils.logger.debug('Socket not connected, waiting for connection', {
469
+ if (!socket.connected) {
470
+ _loggerUtils.logger.debug('Socket not connected, connecting...', {
448
471
  component: 'useSessionSocket',
449
472
  userId
450
473
  });
474
+ socket.connect();
451
475
  }
452
- socket.connect();
453
- } else {
454
- if (__DEV__) {
455
- console.log('[useSessionSocket] Socket already connected', {
456
- socketId: socket.id,
457
- userId,
458
- connected: socket.connected
459
- });
460
- }
461
- }
476
+ };
477
+ initializeSocket();
462
478
  return () => {
463
479
  // Only clean up handlers if socket still exists and handlers were set up
464
480
  if (socketRef.current && handlersSetupRef.current) {
465
- socketRef.current.off('connect', handleConnect);
466
- socketRef.current.off('disconnect', handleDisconnect);
467
- socketRef.current.off('error', handleError);
468
- socketRef.current.off('session_update', handleSessionUpdate);
481
+ try {
482
+ socketRef.current.off('connect');
483
+ socketRef.current.off('disconnect');
484
+ socketRef.current.off('error');
485
+ socketRef.current.off('connect_error');
486
+ socketRef.current.off('session_update');
487
+ } catch (error) {
488
+ // Ignore errors when removing handlers
489
+ }
469
490
  handlersSetupRef.current = false;
470
491
  }
471
492
  };
472
- }, [userId, baseURL, getAccessToken]); // Depend on userId, baseURL, and getAccessToken
493
+ }, [userId, baseURL]); // Only depend on userId and baseURL - functions are in refs
473
494
  }
474
495
  //# sourceMappingURL=useSessionSocket.js.map