@oxyhq/services 5.17.5 → 5.17.8

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 (54) hide show
  1. package/lib/commonjs/crypto/keyManager.js +6 -161
  2. package/lib/commonjs/crypto/keyManager.js.map +1 -1
  3. package/lib/commonjs/ui/context/OxyContext.js +20 -543
  4. package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
  5. package/lib/commonjs/ui/context/OxyContextBase.js.map +1 -1
  6. package/lib/commonjs/ui/context/hooks/useAuthOperations.js +14 -331
  7. package/lib/commonjs/ui/context/hooks/useAuthOperations.js.map +1 -1
  8. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +8 -112
  9. package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  10. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +2 -27
  11. package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
  12. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js +2 -27
  13. package/lib/commonjs/ui/hooks/queries/useServicesQueries.js.map +1 -1
  14. package/lib/commonjs/ui/hooks/useSessionSocket.js +2 -88
  15. package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
  16. package/lib/module/crypto/keyManager.js +6 -161
  17. package/lib/module/crypto/keyManager.js.map +1 -1
  18. package/lib/module/ui/context/OxyContext.js +20 -543
  19. package/lib/module/ui/context/OxyContext.js.map +1 -1
  20. package/lib/module/ui/context/OxyContextBase.js.map +1 -1
  21. package/lib/module/ui/context/hooks/useAuthOperations.js +14 -330
  22. package/lib/module/ui/context/hooks/useAuthOperations.js.map +1 -1
  23. package/lib/module/ui/hooks/mutations/useAccountMutations.js +8 -112
  24. package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
  25. package/lib/module/ui/hooks/queries/useAccountQueries.js +2 -27
  26. package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
  27. package/lib/module/ui/hooks/queries/useServicesQueries.js +2 -27
  28. package/lib/module/ui/hooks/queries/useServicesQueries.js.map +1 -1
  29. package/lib/module/ui/hooks/useSessionSocket.js +2 -88
  30. package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
  31. package/lib/typescript/crypto/keyManager.d.ts +3 -20
  32. package/lib/typescript/crypto/keyManager.d.ts.map +1 -1
  33. package/lib/typescript/crypto/types.d.ts +4 -0
  34. package/lib/typescript/crypto/types.d.ts.map +1 -1
  35. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  36. package/lib/typescript/ui/context/OxyContextBase.d.ts +0 -37
  37. package/lib/typescript/ui/context/OxyContextBase.d.ts.map +1 -1
  38. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts +1 -20
  39. package/lib/typescript/ui/context/hooks/useAuthOperations.d.ts.map +1 -1
  40. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  41. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  42. package/lib/typescript/ui/hooks/queries/useServicesQueries.d.ts.map +1 -1
  43. package/lib/typescript/ui/hooks/useSessionSocket.d.ts +1 -14
  44. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  45. package/package.json +1 -1
  46. package/src/crypto/keyManager.ts +4 -170
  47. package/src/crypto/types.ts +4 -0
  48. package/src/ui/context/OxyContext.tsx +17 -588
  49. package/src/ui/context/OxyContextBase.tsx +2 -20
  50. package/src/ui/context/hooks/useAuthOperations.ts +22 -347
  51. package/src/ui/hooks/mutations/useAccountMutations.ts +12 -110
  52. package/src/ui/hooks/queries/useAccountQueries.ts +3 -27
  53. package/src/ui/hooks/queries/useServicesQueries.ts +3 -27
  54. package/src/ui/hooks/useSessionSocket.ts +2 -106
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
 
3
3
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
- import { Platform } from 'react-native';
5
4
  import { OxyServices } from '../../core';
6
5
  import { toast } from '../../lib/sonner';
7
6
  import { useAuthStore } from '../stores/authStore';
@@ -18,7 +17,6 @@ import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSh
18
17
  import { useQueryClient, useQuery } from '@tanstack/react-query';
19
18
  import { clearQueryCache } from '../hooks/queryClient';
20
19
  import { queryKeys } from '../hooks/queries/queryKeys';
21
- import { KeyManager } from '../../crypto';
22
20
  import { translate } from '../../i18n';
23
21
  import { updateAvatarVisibility, updateProfileWithAvatar } from '../utils/avatarUtils';
24
22
  import { useAccountStore } from '../stores/accountStore';
@@ -83,24 +81,14 @@ export const OxyProvider = ({
83
81
  error,
84
82
  loginSuccess,
85
83
  loginFailure,
86
- logoutStore,
87
- // Identity sync state and actions
88
- isIdentitySyncedStore,
89
- isSyncing,
90
- setIdentitySynced,
91
- setSyncing
84
+ logoutStore
92
85
  } = useAuthStore(useShallow(state => ({
93
86
  isAuthenticated: state.isAuthenticated,
94
87
  isLoading: state.isLoading,
95
88
  error: state.error,
96
89
  loginSuccess: state.loginSuccess,
97
90
  loginFailure: state.loginFailure,
98
- logoutStore: state.logout,
99
- // Identity sync state and actions
100
- isIdentitySyncedStore: state.isIdentitySynced,
101
- isSyncing: state.isSyncing,
102
- setIdentitySynced: state.setIdentitySynced,
103
- setSyncing: state.setSyncing
91
+ logoutStore: state.logout
104
92
  })));
105
93
  const [tokenReady, setTokenReady] = useState(true);
106
94
  const initializedRef = useRef(false);
@@ -123,45 +111,6 @@ export const OxyProvider = ({
123
111
  logger
124
112
  });
125
113
 
126
- // Identity integrity check and auto-restore on startup
127
- // Skip on web platform - identity storage is only available on native platforms
128
- useEffect(() => {
129
- if (!storage || !isStorageReady) return;
130
- if (Platform.OS === 'web') return; // Identity operations are native-only
131
-
132
- const checkAndRestoreIdentity = async () => {
133
- try {
134
- // Check if identity exists and verify integrity
135
- const hasIdentity = await KeyManager.hasIdentity();
136
- if (hasIdentity) {
137
- const isValid = await KeyManager.verifyIdentityIntegrity();
138
- if (!isValid) {
139
- // Try to restore from backup
140
- const restored = await KeyManager.restoreIdentityFromBackup();
141
- if (__DEV__) {
142
- logger(restored ? 'Identity restored from backup successfully' : 'Identity integrity check failed - user may need to restore from backup file');
143
- }
144
- } else {
145
- // Identity is valid - ensure backup is up to date
146
- await KeyManager.backupIdentity();
147
- }
148
- } else {
149
- // No identity - try to restore from backup
150
- const restored = await KeyManager.restoreIdentityFromBackup();
151
- if (restored && __DEV__) {
152
- logger('Identity restored from backup on startup');
153
- }
154
- }
155
- } catch (error) {
156
- if (__DEV__) {
157
- logger('Error during identity integrity check', error);
158
- }
159
- // Don't block app startup - user can recover with backup file
160
- }
161
- };
162
- checkAndRestoreIdentity();
163
- }, [storage, isStorageReady, logger]);
164
-
165
114
  // Offline queuing is now handled by TanStack Query mutations
166
115
  // No need for custom offline queue
167
116
 
@@ -229,15 +178,9 @@ export const OxyProvider = ({
229
178
  });
230
179
  const user = userData ?? null;
231
180
  const {
232
- createIdentity,
233
- importIdentity: importIdentityBase,
234
181
  signIn,
235
182
  logout,
236
- logoutAll,
237
- hasIdentity,
238
- getPublicKey,
239
- isIdentitySynced,
240
- syncIdentity: syncIdentityBase
183
+ logoutAll
241
184
  } = useAuthOperations({
242
185
  oxyServices,
243
186
  storage,
@@ -255,29 +198,10 @@ export const OxyProvider = ({
255
198
  loginFailure,
256
199
  logoutStore,
257
200
  setAuthState,
258
- setIdentitySynced,
259
- setSyncing,
260
201
  logger
261
202
  });
262
203
 
263
- // syncIdentity - TanStack Query handles offline mutations automatically
264
- const syncIdentity = useCallback(() => syncIdentityBase(), [syncIdentityBase]);
265
-
266
- // Wrapper for importIdentity to handle legacy calls gracefully
267
- const importIdentity = useCallback(async (backupData, password) => {
268
- // Handle legacy calls with single string argument (old recovery phrase signature)
269
- if (typeof backupData === 'string') {
270
- throw new Error('Recovery phrase import is no longer supported. Please use backup file import or QR code transfer instead.');
271
- }
272
-
273
- // Validate that password is provided
274
- if (!password || typeof password !== 'string') {
275
- throw new Error('Password is required for backup file import.');
276
- }
277
- return importIdentityBase(backupData, password);
278
- }, [importIdentityBase]);
279
-
280
- // Clear all account data when identity is lost (for accounts app)
204
+ // Clear all account data when logging out (for accounts app)
281
205
  // In accounts app, identity = account, so losing identity means losing everything
282
206
  const clearAllAccountData = useCallback(async () => {
283
207
  // Clear TanStack Query cache (in-memory)
@@ -295,19 +219,6 @@ export const OxyProvider = ({
295
219
  // Clear session state (sessions, activeSessionId, storage)
296
220
  await clearSessionState();
297
221
 
298
- // Clear identity sync state from storage
299
- if (storage) {
300
- try {
301
- await storage.removeItem('oxy_identity_synced');
302
- } catch (error) {
303
- logger('Failed to clear identity sync state', error);
304
- }
305
- }
306
-
307
- // Reset auth store identity sync state
308
- useAuthStore.getState().setIdentitySynced(false);
309
- useAuthStore.getState().setSyncing(false);
310
-
311
222
  // Reset account store
312
223
  useAccountStore.getState().reset();
313
224
 
@@ -315,162 +226,50 @@ export const OxyProvider = ({
315
226
  oxyServices.clearCache();
316
227
  }, [queryClient, storage, clearSessionState, logger, oxyServices]);
317
228
 
318
- // Transfer code management functions (must be defined before deleteIdentityAndClearAccount)
319
- const getAllPendingTransfers = useCallback(() => {
320
- const pending = [];
321
- transferCodesRef.current.forEach((data, transferId) => {
322
- if (data.state === 'pending') {
323
- pending.push({
324
- transferId,
325
- data
326
- });
327
- }
328
- });
329
- return pending;
330
- }, []);
331
- const getActiveTransferId = useCallback(() => {
332
- return activeTransferIdRef.current;
333
- }, []);
334
-
335
- // Delete identity and clear all account data
336
- // In accounts app, deleting identity means losing the account completely
337
- const deleteIdentityAndClearAccount = useCallback(async (skipBackup = false, force = false, userConfirmed = false) => {
338
- // CRITICAL: Check for active transfers before deletion (unless force is true)
339
- // This prevents accidental identity loss during transfer
340
- if (!force) {
341
- const pendingTransfers = getAllPendingTransfers();
342
- if (pendingTransfers.length > 0) {
343
- const activeTransferId = getActiveTransferId();
344
- const hasActiveTransfer = activeTransferId && pendingTransfers.some(t => t.transferId === activeTransferId);
345
- if (hasActiveTransfer) {
346
- throw new Error('Cannot delete identity: An active identity transfer is in progress. ' + 'Please wait for the transfer to complete or cancel it first. ' + 'If you proceed, you may lose access to your identity permanently.');
347
- }
348
- }
349
- }
350
-
351
- // First, clear all account data
352
- await clearAllAccountData();
353
-
354
- // Then delete the identity keys
355
- await KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
356
- }, [clearAllAccountData, getAllPendingTransfers, getActiveTransferId]);
357
-
358
- // Network reconnect sync - TanStack Query automatically retries mutations on reconnect
359
- // We only need to sync identity if it's not synced
229
+ // Network reconnect - TanStack Query automatically retries mutations on reconnect
230
+ // Network reconnect - TanStack Query automatically retries mutations on reconnect
360
231
  useEffect(() => {
361
232
  if (!storage) return;
362
233
  let wasOffline = false;
363
234
  let checkTimeout = null;
364
- let lastReconnectionLog = 0;
365
- const RECONNECTION_LOG_DEBOUNCE_MS = 5000; // 5 seconds
366
-
367
- // Circuit breaker and exponential backoff state
368
- const stateRef = {
369
- consecutiveFailures: 0,
370
- currentInterval: 10000,
371
- // Start with 10 seconds
372
- baseInterval: 10000,
373
- // Base interval in milliseconds
374
- maxInterval: 60000,
375
- // Maximum interval (60 seconds)
376
- maxFailures: 5 // Circuit breaker threshold
377
- };
378
235
  const scheduleNextCheck = () => {
379
236
  if (checkTimeout) {
380
237
  clearTimeout(checkTimeout);
381
238
  }
382
239
  checkTimeout = setTimeout(() => {
383
- checkNetworkAndSync();
384
- }, stateRef.currentInterval);
240
+ checkNetworkStatus();
241
+ }, 30000); // Check every 30 seconds
385
242
  };
386
- const checkNetworkAndSync = async () => {
243
+ const checkNetworkStatus = async () => {
387
244
  try {
388
245
  // Try a lightweight health check to see if we're online
389
- await oxyServices.healthCheck().catch(() => {
390
- wasOffline = true;
391
- throw new Error('Health check failed');
392
- });
393
-
394
- // Health check succeeded - reset circuit breaker and backoff
395
- if (stateRef.consecutiveFailures > 0) {
396
- stateRef.consecutiveFailures = 0;
397
- stateRef.currentInterval = stateRef.baseInterval;
398
- }
246
+ await oxyServices.healthCheck();
399
247
 
400
- // If we were offline and now we're online, sync identity if needed
248
+ // If we were offline and now we're online
401
249
  if (wasOffline) {
402
- const now = Date.now();
403
- const timeSinceLastLog = now - lastReconnectionLog;
404
- if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
405
- logger('Network reconnected, checking identity sync...');
406
- lastReconnectionLog = now;
407
-
408
- // Sync identity first (if not synced)
409
- try {
410
- const hasIdentityValue = await hasIdentity();
411
- if (hasIdentityValue) {
412
- // Check sync status directly - sync if not explicitly 'true'
413
- // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
414
- const syncStatus = await storage.getItem('oxy_identity_synced');
415
- if (syncStatus !== 'true') {
416
- await syncIdentity();
417
- }
418
- }
419
- } catch (syncError) {
420
- // Skip sync silently if username is required (expected when offline onboarding)
421
- if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
422
- if (__DEV__) {
423
- loggerUtil.debug('Sync skipped - username required', {
424
- component: 'OxyContext',
425
- method: 'checkNetworkAndSync'
426
- }, syncError);
427
- }
428
- // Don't log or show error - username will be set later
429
- } else if (!isTimeoutOrNetworkError(syncError)) {
430
- // Only log unexpected errors - timeouts/network issues are expected when offline
431
- logger('Error syncing identity on reconnect', syncError);
432
- } else if (__DEV__) {
433
- loggerUtil.debug('Identity sync timeout (expected when offline)', {
434
- component: 'OxyContext',
435
- method: 'checkNetworkAndSync'
436
- }, syncError);
437
- }
438
- }
439
- }
440
-
250
+ logger('Network reconnected');
441
251
  // TanStack Query will automatically retry pending mutations
442
- // Reset flag immediately after processing (whether logged or not)
443
252
  wasOffline = false;
444
253
  }
445
254
  } catch (error) {
446
255
  // Network check failed - we're offline
447
- wasOffline = true;
448
-
449
- // Increment failure count and apply exponential backoff
450
- stateRef.consecutiveFailures++;
451
-
452
- // Calculate new interval with exponential backoff, capped at maxInterval
453
- const backoffMultiplier = Math.min(Math.pow(2, stateRef.consecutiveFailures - 1), stateRef.maxInterval / stateRef.baseInterval);
454
- stateRef.currentInterval = Math.min(stateRef.baseInterval * backoffMultiplier, stateRef.maxInterval);
455
-
456
- // If we hit the circuit breaker threshold, use max interval
457
- if (stateRef.consecutiveFailures >= stateRef.maxFailures) {
458
- stateRef.currentInterval = stateRef.maxInterval;
256
+ if (!wasOffline && __DEV__) {
257
+ logger('Network appears offline');
459
258
  }
259
+ wasOffline = true;
460
260
  } finally {
461
- // Always schedule next check (will use updated interval)
462
261
  scheduleNextCheck();
463
262
  }
464
263
  };
465
264
 
466
265
  // Check immediately
467
- checkNetworkAndSync();
266
+ checkNetworkStatus();
468
267
  return () => {
469
268
  if (checkTimeout) {
470
269
  clearTimeout(checkTimeout);
471
270
  }
472
271
  };
473
- }, [oxyServices, storage, syncIdentity, logger]);
272
+ }, [oxyServices, storage, logger]);
474
273
  const {
475
274
  getDeviceSessions,
476
275
  logoutAllDeviceSessions,
@@ -579,171 +378,6 @@ export const OxyProvider = ({
579
378
 
580
379
  // Get userId from JWT token (MongoDB ObjectId) for socket room matching
581
380
  const userId = oxyServices.getCurrentUserId();
582
-
583
- // Transfer code storage interface
584
-
585
- // Store transfer codes in memory for verification (also persisted to storage)
586
- // Map: transferId -> TransferCodeData
587
- const transferCodesRef = useRef(new Map());
588
- const activeTransferIdRef = useRef(null);
589
- const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
590
- const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
591
-
592
- // Clear stale transfer codes on startup
593
- // Transfers are ephemeral and should not persist across app restarts
594
- useEffect(() => {
595
- if (!storage || !isStorageReady) return;
596
- const clearStaleTransfers = async () => {
597
- try {
598
- // Clear any stored transfer codes - they're only valid during active transfer sessions
599
- await storage.removeItem(TRANSFER_CODES_STORAGE_KEY);
600
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
601
- if (__DEV__) {
602
- logger('Cleared stale transfer codes on startup');
603
- }
604
- } catch (error) {
605
- if (__DEV__) {
606
- logger('Failed to clear stale transfer codes', error);
607
- }
608
- }
609
- };
610
- clearStaleTransfers();
611
- }, [storage, isStorageReady, logger, storageKeyPrefix]);
612
-
613
- // Persist transfer codes to storage whenever they change
614
- const persistTransferCodes = useCallback(async () => {
615
- if (!storage) return;
616
- try {
617
- const codesToStore = {};
618
- transferCodesRef.current.forEach((value, key) => {
619
- codesToStore[key] = value;
620
- });
621
- await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(codesToStore));
622
- } catch (error) {
623
- if (__DEV__) {
624
- logger('Failed to persist transfer codes', error);
625
- }
626
- }
627
- }, [storage, logger]);
628
-
629
- // Cleanup old transfer codes (older than 15 minutes)
630
- useEffect(() => {
631
- const cleanup = setInterval(async () => {
632
- const now = Date.now();
633
- const fifteenMinutes = 15 * 60 * 1000;
634
- let needsPersist = false;
635
- transferCodesRef.current.forEach((value, key) => {
636
- if (now - value.timestamp > fifteenMinutes) {
637
- transferCodesRef.current.delete(key);
638
- needsPersist = true;
639
- if (__DEV__) {
640
- logger('Cleaned up expired transfer code', {
641
- transferId: key
642
- });
643
- }
644
- }
645
- });
646
-
647
- // Clear active transfer if it was deleted
648
- if (activeTransferIdRef.current && !transferCodesRef.current.has(activeTransferIdRef.current)) {
649
- activeTransferIdRef.current = null;
650
- if (storage) {
651
- try {
652
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
653
- } catch (error) {
654
- // Ignore storage errors
655
- }
656
- }
657
- }
658
- if (needsPersist) {
659
- await persistTransferCodes();
660
- }
661
- }, 60000); // Check every minute
662
-
663
- return () => clearInterval(cleanup);
664
- }, [logger, persistTransferCodes, storage]);
665
-
666
- // Transfer code management functions
667
- const storeTransferCode = useCallback(async (transferId, code, sourceDeviceId, publicKey) => {
668
- const transferData = {
669
- code,
670
- sourceDeviceId,
671
- publicKey,
672
- timestamp: Date.now(),
673
- state: 'pending'
674
- };
675
- transferCodesRef.current.set(transferId, transferData);
676
- activeTransferIdRef.current = transferId;
677
-
678
- // Persist to storage
679
- await persistTransferCodes();
680
- if (storage) {
681
- try {
682
- await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, transferId);
683
- } catch (error) {
684
- if (__DEV__) {
685
- logger('Failed to persist active transfer ID', error);
686
- }
687
- }
688
- }
689
- if (__DEV__) {
690
- logger('Stored transfer code', {
691
- transferId,
692
- sourceDeviceId,
693
- publicKey: publicKey.substring(0, 16) + '...'
694
- });
695
- }
696
- }, [logger, persistTransferCodes, storage]);
697
- const getTransferCode = useCallback(transferId => {
698
- return transferCodesRef.current.get(transferId) || null;
699
- }, []);
700
- const updateTransferState = useCallback(async (transferId, state) => {
701
- const transferData = transferCodesRef.current.get(transferId);
702
- if (transferData) {
703
- transferData.state = state;
704
- transferCodesRef.current.set(transferId, transferData);
705
-
706
- // Clear active transfer if completed or failed
707
- if (state === 'completed' || state === 'failed') {
708
- if (activeTransferIdRef.current === transferId) {
709
- activeTransferIdRef.current = null;
710
- if (storage) {
711
- try {
712
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
713
- } catch (error) {
714
- // Ignore storage errors
715
- }
716
- }
717
- }
718
- }
719
- await persistTransferCodes();
720
- if (__DEV__) {
721
- logger('Updated transfer state', {
722
- transferId,
723
- state
724
- });
725
- }
726
- }
727
- }, [logger, persistTransferCodes, storage]);
728
- const clearTransferCode = useCallback(async transferId => {
729
- transferCodesRef.current.delete(transferId);
730
- if (activeTransferIdRef.current === transferId) {
731
- activeTransferIdRef.current = null;
732
- if (storage) {
733
- try {
734
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
735
- } catch (error) {
736
- // Ignore storage errors
737
- }
738
- }
739
- }
740
- await persistTransferCodes();
741
- if (__DEV__) {
742
- logger('Cleared transfer code', {
743
- transferId
744
- });
745
- }
746
- }, [logger, persistTransferCodes, storage]);
747
381
  const refreshSessionsWithUser = useCallback(() => refreshSessions(userId || undefined), [refreshSessions, userId]);
748
382
  const handleSessionRemoved = useCallback(sessionId => {
749
383
  trackRemovedSession(sessionId);
@@ -752,143 +386,6 @@ export const OxyProvider = ({
752
386
  toast.info('You have been signed out remotely.');
753
387
  logout().catch(remoteError => logger('Failed to process remote sign out', remoteError));
754
388
  }, [logger, logout]);
755
- const handleIdentityTransferComplete = useCallback(async data => {
756
- try {
757
- logger('Received identity transfer complete notification', {
758
- transferId: data.transferId,
759
- sourceDeviceId: data.sourceDeviceId,
760
- currentDeviceId,
761
- hasActiveSession: activeSessionId !== null,
762
- publicKey: data.publicKey.substring(0, 16) + '...'
763
- });
764
- const storedTransfer = getTransferCode(data.transferId);
765
- if (!storedTransfer) {
766
- logger('Transfer code not found for transferId', {
767
- transferId: data.transferId
768
- });
769
- toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
770
- return;
771
- }
772
-
773
- // Verify publicKey matches first (most important check)
774
- const publicKeyMatches = data.publicKey === storedTransfer.publicKey;
775
- if (!publicKeyMatches) {
776
- logger('Public key mismatch for transfer', {
777
- transferId: data.transferId,
778
- receivedPublicKey: data.publicKey.substring(0, 16) + '...',
779
- storedPublicKey: storedTransfer.publicKey.substring(0, 16) + '...'
780
- });
781
- toast.error('Transfer verification failed: Public key mismatch. Identity will not be deleted.');
782
- return;
783
- }
784
-
785
- // Verify deviceId matches - very lenient since publicKey is the critical check
786
- // If publicKey matches, we allow deletion even if deviceId doesn't match exactly
787
- // This handles cases where deviceId might not be available or slightly different
788
- const deviceIdMatches =
789
- // Exact match
790
- data.sourceDeviceId && data.sourceDeviceId === currentDeviceId ||
791
- // Stored sourceDeviceId matches current deviceId
792
- storedTransfer.sourceDeviceId && storedTransfer.sourceDeviceId === currentDeviceId;
793
-
794
- // If publicKey matches, we're very lenient with deviceId - only warn but don't block
795
- if (!deviceIdMatches && publicKeyMatches) {
796
- logger('Device ID mismatch for transfer, but publicKey matches - proceeding with deletion', {
797
- transferId: data.transferId,
798
- receivedDeviceId: data.sourceDeviceId,
799
- storedDeviceId: storedTransfer.sourceDeviceId,
800
- currentDeviceId,
801
- hasActiveSession: activeSessionId !== null
802
- });
803
- // Proceed with deletion - publicKey match is the critical verification
804
- } else if (!deviceIdMatches && !publicKeyMatches) {
805
- // Both don't match - this is suspicious, block deletion
806
- logger('Device ID and publicKey mismatch for transfer', {
807
- transferId: data.transferId,
808
- receivedDeviceId: data.sourceDeviceId,
809
- currentDeviceId
810
- });
811
- toast.error('Transfer verification failed: Device and key mismatch. Identity will not be deleted.');
812
- return;
813
- }
814
-
815
- // Verify transfer code matches (if provided)
816
- // Transfer code is optional - if not provided, we still proceed if publicKey matches
817
- if (data.transferCode) {
818
- const codeMatches = data.transferCode.toUpperCase() === storedTransfer.code.toUpperCase();
819
- if (!codeMatches) {
820
- logger('Transfer code mismatch, but publicKey matches - proceeding with deletion', {
821
- transferId: data.transferId,
822
- receivedCode: data.transferCode,
823
- storedCode: storedTransfer.code.substring(0, 2) + '****'
824
- });
825
- // Don't block - publicKey match is sufficient, code mismatch might be due to user error
826
- // Log warning but proceed
827
- }
828
- }
829
-
830
- // Check if transfer is too old (safety timeout - 10 minutes)
831
- const transferAge = Date.now() - storedTransfer.timestamp;
832
- const tenMinutes = 10 * 60 * 1000;
833
- if (transferAge > tenMinutes) {
834
- logger('Transfer confirmation received too late', {
835
- transferId: data.transferId,
836
- age: transferAge,
837
- ageMinutes: Math.round(transferAge / 60000)
838
- });
839
- toast.error('Transfer verification failed: Confirmation received too late. Identity will not be deleted.');
840
- clearTransferCode(data.transferId);
841
- return;
842
- }
843
-
844
- // NOTE: Target device verification already happened server-side when notifyTransferComplete was called
845
- // The server verified that the target device is authenticated and has the matching public key
846
- // Additional client-side verification is not necessary and would require source device authentication
847
- // which may not be available. The existing checks (public key match, transfer code, device ID) are sufficient.
848
-
849
- logger('All transfer verifications passed, deleting identity from source device', {
850
- transferId: data.transferId,
851
- sourceDeviceId: data.sourceDeviceId,
852
- publicKey: data.publicKey.substring(0, 16) + '...'
853
- });
854
- try {
855
- // Verify identity still exists before deletion (safety check)
856
- const identityStillExists = await KeyManager.hasIdentity();
857
- if (!identityStillExists) {
858
- logger('Identity already deleted - skipping deletion', {
859
- transferId: data.transferId
860
- });
861
- await updateTransferState(data.transferId, 'completed');
862
- await clearTransferCode(data.transferId);
863
- return;
864
- }
865
- await deleteIdentityAndClearAccount(false, false, true);
866
-
867
- // Verify identity was actually deleted
868
- const identityDeleted = !(await KeyManager.hasIdentity());
869
- if (!identityDeleted) {
870
- logger('Identity deletion failed - identity still exists', {
871
- transferId: data.transferId
872
- });
873
- await updateTransferState(data.transferId, 'failed');
874
- throw new Error('Identity deletion failed - identity still exists');
875
- }
876
- await updateTransferState(data.transferId, 'completed');
877
- await clearTransferCode(data.transferId);
878
- logger('Identity successfully deleted and transfer code cleared', {
879
- transferId: data.transferId
880
- });
881
- toast.success('Identity successfully transferred and removed from this device');
882
- } catch (deleteError) {
883
- logger('Error during identity deletion', deleteError);
884
- await updateTransferState(data.transferId, 'failed');
885
- throw deleteError;
886
- }
887
- } catch (error) {
888
- logger('Failed to delete identity after transfer', error);
889
- toast.error(error?.message || 'Failed to remove identity from this device. Please try again manually from Security Settings.');
890
- }
891
- }, [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, updateTransferState, currentDeviceId, activeSessionId, oxyServices]);
892
389
  useSessionSocket({
893
390
  userId,
894
391
  activeSessionId,
@@ -898,10 +395,8 @@ export const OxyProvider = ({
898
395
  clearSessionState,
899
396
  baseURL: oxyServices.getBaseURL(),
900
397
  getAccessToken: () => oxyServices.getAccessToken(),
901
- getTransferCode: getTransferCode,
902
398
  onRemoteSignOut: handleRemoteSignOut,
903
- onSessionRemoved: handleSessionRemoved,
904
- onIdentityTransferComplete: handleIdentityTransferComplete
399
+ onSessionRemoved: handleSessionRemoved
905
400
  });
906
401
  const switchSessionForContext = useCallback(async sessionId => {
907
402
  await switchSession(sessionId);
@@ -935,7 +430,6 @@ export const OxyProvider = ({
935
430
  await updateProfileWithAvatar({
936
431
  avatar: file.id
937
432
  }, oxyServices, activeSessionId, queryClient, {
938
- syncIdentity,
939
433
  deviceId: currentDeviceId || undefined
940
434
  });
941
435
  toast.success(translate(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
@@ -945,7 +439,7 @@ export const OxyProvider = ({
945
439
  }
946
440
  }
947
441
  });
948
- }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient, syncIdentity]);
442
+ }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient]);
949
443
  const contextValue = useMemo(() => ({
950
444
  user,
951
445
  sessions,
@@ -960,24 +454,7 @@ export const OxyProvider = ({
960
454
  currentLanguageMetadata,
961
455
  currentLanguageName,
962
456
  currentNativeLanguageName,
963
- createIdentity,
964
- importIdentity,
965
457
  signIn,
966
- hasIdentity,
967
- getPublicKey,
968
- isIdentitySynced,
969
- syncIdentity,
970
- deleteIdentityAndClearAccount,
971
- storeTransferCode,
972
- getTransferCode,
973
- clearTransferCode,
974
- getAllPendingTransfers,
975
- getActiveTransferId,
976
- updateTransferState,
977
- identitySyncState: {
978
- isSynced: isIdentitySyncedStore ?? true,
979
- isSyncing: isSyncing ?? false
980
- },
981
458
  logout,
982
459
  logoutAll,
983
460
  switchSession: switchSessionForContext,
@@ -993,7 +470,7 @@ export const OxyProvider = ({
993
470
  useFollow: useFollowHook,
994
471
  showBottomSheet: showBottomSheetForContext,
995
472
  openAvatarPicker
996
- }), [activeSessionId, currentDeviceId, createIdentity, importIdentity, signIn, hasIdentity, getPublicKey, isIdentitySynced, syncIdentity, deleteIdentityAndClearAccount, storeTransferCode, getTransferCode, clearTransferCode, getAllPendingTransfers, getActiveTransferId, updateTransferState, isIdentitySyncedStore, isSyncing, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, error, getDeviceSessions, isAuthenticated, isLoading, logout, logoutAll, logoutAllDeviceSessions, oxyServices, refreshSessionsWithUser, sessions, setLanguage, switchSessionForContext, tokenReady, isStorageReady, updateDeviceName, clearAllAccountData, useFollowHook, user, showBottomSheetForContext, openAvatarPicker]);
473
+ }), [activeSessionId, currentDeviceId, signIn, currentLanguage, currentLanguageMetadata, currentLanguageName, currentNativeLanguageName, error, getDeviceSessions, isAuthenticated, isLoading, logout, logoutAll, logoutAllDeviceSessions, oxyServices, refreshSessionsWithUser, sessions, setLanguage, switchSessionForContext, tokenReady, isStorageReady, updateDeviceName, clearAllAccountData, useFollowHook, user, showBottomSheetForContext, openAvatarPicker]);
997
474
  return /*#__PURE__*/_jsx(OxyContext.Provider, {
998
475
  value: contextValue,
999
476
  children: children