@oxyhq/services 5.16.24 → 5.16.26

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 (39) 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 +280 -84
  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 +280 -84
  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 +15 -2
  28. package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
  29. package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
  30. package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
  31. package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
  32. package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
  33. package/package.json +1 -1
  34. package/src/core/mixins/OxyServices.user.ts +14 -4
  35. package/src/ui/context/OxyContext.tsx +310 -86
  36. package/src/ui/hooks/mutations/useAccountMutations.ts +8 -6
  37. package/src/ui/hooks/queries/useAccountQueries.ts +4 -2
  38. package/src/ui/hooks/useSessionSocket.ts +153 -155
  39. package/src/ui/screens/PrivacySettingsScreen.tsx +12 -6
@@ -103,7 +103,11 @@ export const OxyProvider = ({
103
103
  const setAuthState = useAuthStore.setState;
104
104
  const logger = useCallback((message, err) => {
105
105
  if (__DEV__) {
106
- console.warn(`[OxyContext] ${message}`, err);
106
+ if (err !== undefined) {
107
+ console.warn(`[OxyContext] ${message}`, err);
108
+ } else {
109
+ console.warn(`[OxyContext] ${message}`);
110
+ }
107
111
  }
108
112
  }, []);
109
113
  const storageKeys = useMemo(() => getStorageKeys(storageKeyPrefix), [storageKeyPrefix]);
@@ -284,15 +288,45 @@ export const OxyProvider = ({
284
288
  oxyServices.clearCache();
285
289
  }, [queryClient, storage, clearSessionState, logger, oxyServices]);
286
290
 
291
+ // Transfer code management functions (must be defined before deleteIdentityAndClearAccount)
292
+ const getAllPendingTransfers = useCallback(() => {
293
+ const pending = [];
294
+ transferCodesRef.current.forEach((data, transferId) => {
295
+ if (data.state === 'pending') {
296
+ pending.push({
297
+ transferId,
298
+ data
299
+ });
300
+ }
301
+ });
302
+ return pending;
303
+ }, []);
304
+ const getActiveTransferId = useCallback(() => {
305
+ return activeTransferIdRef.current;
306
+ }, []);
307
+
287
308
  // Delete identity and clear all account data
288
309
  // In accounts app, deleting identity means losing the account completely
289
310
  const deleteIdentityAndClearAccount = useCallback(async (skipBackup = false, force = false, userConfirmed = false) => {
311
+ // CRITICAL: Check for active transfers before deletion (unless force is true)
312
+ // This prevents accidental identity loss during transfer
313
+ if (!force) {
314
+ const pendingTransfers = getAllPendingTransfers();
315
+ if (pendingTransfers.length > 0) {
316
+ const activeTransferId = getActiveTransferId();
317
+ const hasActiveTransfer = activeTransferId && pendingTransfers.some(t => t.transferId === activeTransferId);
318
+ if (hasActiveTransfer) {
319
+ 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.');
320
+ }
321
+ }
322
+ }
323
+
290
324
  // First, clear all account data
291
325
  await clearAllAccountData();
292
326
 
293
327
  // Then delete the identity keys
294
328
  await KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
295
- }, [clearAllAccountData]);
329
+ }, [clearAllAccountData, getAllPendingTransfers, getActiveTransferId]);
296
330
 
297
331
  // Network reconnect sync - TanStack Query automatically retries mutations on reconnect
298
332
  // We only need to sync identity if it's not synced
@@ -300,6 +334,8 @@ export const OxyProvider = ({
300
334
  if (!storage) return;
301
335
  let wasOffline = false;
302
336
  let checkTimeout = null;
337
+ let lastReconnectionLog = 0;
338
+ const RECONNECTION_LOG_DEBOUNCE_MS = 5000; // 5 seconds
303
339
 
304
340
  // Circuit breaker and exponential backoff state
305
341
  const stateRef = {
@@ -336,41 +372,47 @@ export const OxyProvider = ({
336
372
 
337
373
  // If we were offline and now we're online, sync identity if needed
338
374
  if (wasOffline) {
339
- logger('Network reconnected, checking identity sync...');
375
+ const now = Date.now();
376
+ const timeSinceLastLog = now - lastReconnectionLog;
377
+ if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
378
+ logger('Network reconnected, checking identity sync...');
379
+ lastReconnectionLog = now;
340
380
 
341
- // Sync identity first (if not synced)
342
- try {
343
- const hasIdentityValue = await hasIdentity();
344
- if (hasIdentityValue) {
345
- // Check sync status directly - sync if not explicitly 'true'
346
- // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
347
- const syncStatus = await storage.getItem('oxy_identity_synced');
348
- if (syncStatus !== 'true') {
349
- await syncIdentity();
381
+ // Sync identity first (if not synced)
382
+ try {
383
+ const hasIdentityValue = await hasIdentity();
384
+ if (hasIdentityValue) {
385
+ // Check sync status directly - sync if not explicitly 'true'
386
+ // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
387
+ const syncStatus = await storage.getItem('oxy_identity_synced');
388
+ if (syncStatus !== 'true') {
389
+ await syncIdentity();
390
+ }
350
391
  }
351
- }
352
- } catch (syncError) {
353
- // Skip sync silently if username is required (expected when offline onboarding)
354
- if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
355
- if (__DEV__) {
356
- loggerUtil.debug('Sync skipped - username required', {
392
+ } catch (syncError) {
393
+ // Skip sync silently if username is required (expected when offline onboarding)
394
+ if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
395
+ if (__DEV__) {
396
+ loggerUtil.debug('Sync skipped - username required', {
397
+ component: 'OxyContext',
398
+ method: 'checkNetworkAndSync'
399
+ }, syncError);
400
+ }
401
+ // Don't log or show error - username will be set later
402
+ } else if (!isTimeoutOrNetworkError(syncError)) {
403
+ // Only log unexpected errors - timeouts/network issues are expected when offline
404
+ logger('Error syncing identity on reconnect', syncError);
405
+ } else if (__DEV__) {
406
+ loggerUtil.debug('Identity sync timeout (expected when offline)', {
357
407
  component: 'OxyContext',
358
408
  method: 'checkNetworkAndSync'
359
409
  }, syncError);
360
410
  }
361
- // Don't log or show error - username will be set later
362
- } else if (!isTimeoutOrNetworkError(syncError)) {
363
- // Only log unexpected errors - timeouts/network issues are expected when offline
364
- logger('Error syncing identity on reconnect', syncError);
365
- } else if (__DEV__) {
366
- loggerUtil.debug('Identity sync timeout (expected when offline)', {
367
- component: 'OxyContext',
368
- method: 'checkNetworkAndSync'
369
- }, syncError);
370
411
  }
371
412
  }
372
413
 
373
414
  // TanStack Query will automatically retry pending mutations
415
+ // Reset flag immediately after processing (whether logged or not)
374
416
  wasOffline = false;
375
417
  }
376
418
  } catch (error) {
@@ -513,18 +555,92 @@ export const OxyProvider = ({
513
555
  // The JWT token's userId field contains the MongoDB ObjectId
514
556
  const userId = oxyServices.getCurrentUserId() || user?.id;
515
557
 
516
- // Store transfer codes in memory for verification
517
- // Map: transferId -> { code, sourceDeviceId, publicKey, timestamp }
558
+ // Transfer code storage interface
559
+
560
+ // Store transfer codes in memory for verification (also persisted to storage)
561
+ // Map: transferId -> TransferCodeData
518
562
  const transferCodesRef = useRef(new Map());
563
+ const activeTransferIdRef = useRef(null);
564
+ const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
565
+ const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
566
+
567
+ // Load transfer codes from storage on startup
568
+ useEffect(() => {
569
+ if (!storage || !isStorageReady) return;
570
+ const loadTransferCodes = async () => {
571
+ try {
572
+ // Load transfer codes
573
+ const storedCodes = await storage.getItem(TRANSFER_CODES_STORAGE_KEY);
574
+ if (storedCodes) {
575
+ const parsed = JSON.parse(storedCodes);
576
+ const now = Date.now();
577
+ const fifteenMinutes = 15 * 60 * 1000;
578
+
579
+ // Only restore non-expired pending transfers
580
+ Object.entries(parsed).forEach(([transferId, data]) => {
581
+ if (data.state === 'pending' && now - data.timestamp < fifteenMinutes) {
582
+ transferCodesRef.current.set(transferId, data);
583
+ if (__DEV__) {
584
+ logger('Restored pending transfer from storage', {
585
+ transferId
586
+ });
587
+ }
588
+ }
589
+ });
590
+ }
591
+
592
+ // Load active transfer ID
593
+ const activeTransferId = await storage.getItem(ACTIVE_TRANSFER_STORAGE_KEY);
594
+ if (activeTransferId) {
595
+ // Verify it's still valid
596
+ const transferData = transferCodesRef.current.get(activeTransferId);
597
+ if (transferData && transferData.state === 'pending') {
598
+ activeTransferIdRef.current = activeTransferId;
599
+ if (__DEV__) {
600
+ logger('Restored active transfer ID from storage', {
601
+ transferId: activeTransferId
602
+ });
603
+ }
604
+ } else {
605
+ // Clear invalid active transfer
606
+ await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
607
+ }
608
+ }
609
+ } catch (error) {
610
+ if (__DEV__) {
611
+ logger('Failed to load transfer codes from storage', error);
612
+ }
613
+ }
614
+ };
615
+ loadTransferCodes();
616
+ }, [storage, isStorageReady, logger, storageKeyPrefix]);
617
+
618
+ // Persist transfer codes to storage whenever they change
619
+ const persistTransferCodes = useCallback(async () => {
620
+ if (!storage) return;
621
+ try {
622
+ const codesToStore = {};
623
+ transferCodesRef.current.forEach((value, key) => {
624
+ codesToStore[key] = value;
625
+ });
626
+ await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(codesToStore));
627
+ } catch (error) {
628
+ if (__DEV__) {
629
+ logger('Failed to persist transfer codes', error);
630
+ }
631
+ }
632
+ }, [storage, logger]);
519
633
 
520
634
  // Cleanup old transfer codes (older than 15 minutes)
521
635
  useEffect(() => {
522
- const cleanup = setInterval(() => {
636
+ const cleanup = setInterval(async () => {
523
637
  const now = Date.now();
524
638
  const fifteenMinutes = 15 * 60 * 1000;
639
+ let needsPersist = false;
525
640
  transferCodesRef.current.forEach((value, key) => {
526
641
  if (now - value.timestamp > fifteenMinutes) {
527
642
  transferCodesRef.current.delete(key);
643
+ needsPersist = true;
528
644
  if (__DEV__) {
529
645
  logger('Cleaned up expired transfer code', {
530
646
  transferId: key
@@ -532,19 +648,49 @@ export const OxyProvider = ({
532
648
  }
533
649
  }
534
650
  });
651
+
652
+ // Clear active transfer if it was deleted
653
+ if (activeTransferIdRef.current && !transferCodesRef.current.has(activeTransferIdRef.current)) {
654
+ activeTransferIdRef.current = null;
655
+ if (storage) {
656
+ try {
657
+ await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
658
+ } catch (error) {
659
+ // Ignore storage errors
660
+ }
661
+ }
662
+ }
663
+ if (needsPersist) {
664
+ await persistTransferCodes();
665
+ }
535
666
  }, 60000); // Check every minute
536
667
 
537
668
  return () => clearInterval(cleanup);
538
- }, [logger]);
669
+ }, [logger, persistTransferCodes, storage]);
539
670
 
540
671
  // Transfer code management functions
541
- const storeTransferCode = useCallback((transferId, code, sourceDeviceId, publicKey) => {
542
- transferCodesRef.current.set(transferId, {
672
+ const storeTransferCode = useCallback(async (transferId, code, sourceDeviceId, publicKey) => {
673
+ const transferData = {
543
674
  code,
544
675
  sourceDeviceId,
545
676
  publicKey,
546
- timestamp: Date.now()
547
- });
677
+ timestamp: Date.now(),
678
+ state: 'pending'
679
+ };
680
+ transferCodesRef.current.set(transferId, transferData);
681
+ activeTransferIdRef.current = transferId;
682
+
683
+ // Persist to storage
684
+ await persistTransferCodes();
685
+ if (storage) {
686
+ try {
687
+ await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, transferId);
688
+ } catch (error) {
689
+ if (__DEV__) {
690
+ logger('Failed to persist active transfer ID', error);
691
+ }
692
+ }
693
+ }
548
694
  if (__DEV__) {
549
695
  logger('Stored transfer code', {
550
696
  transferId,
@@ -552,18 +698,57 @@ export const OxyProvider = ({
552
698
  publicKey: publicKey.substring(0, 16) + '...'
553
699
  });
554
700
  }
555
- }, [logger]);
701
+ }, [logger, persistTransferCodes, storage]);
556
702
  const getTransferCode = useCallback(transferId => {
557
703
  return transferCodesRef.current.get(transferId) || null;
558
704
  }, []);
559
- const clearTransferCode = useCallback(transferId => {
705
+ const updateTransferState = useCallback(async (transferId, state) => {
706
+ const transferData = transferCodesRef.current.get(transferId);
707
+ if (transferData) {
708
+ transferData.state = state;
709
+ transferCodesRef.current.set(transferId, transferData);
710
+
711
+ // Clear active transfer if completed or failed
712
+ if (state === 'completed' || state === 'failed') {
713
+ if (activeTransferIdRef.current === transferId) {
714
+ activeTransferIdRef.current = null;
715
+ if (storage) {
716
+ try {
717
+ await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
718
+ } catch (error) {
719
+ // Ignore storage errors
720
+ }
721
+ }
722
+ }
723
+ }
724
+ await persistTransferCodes();
725
+ if (__DEV__) {
726
+ logger('Updated transfer state', {
727
+ transferId,
728
+ state
729
+ });
730
+ }
731
+ }
732
+ }, [logger, persistTransferCodes, storage]);
733
+ const clearTransferCode = useCallback(async transferId => {
560
734
  transferCodesRef.current.delete(transferId);
735
+ if (activeTransferIdRef.current === transferId) {
736
+ activeTransferIdRef.current = null;
737
+ if (storage) {
738
+ try {
739
+ await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
740
+ } catch (error) {
741
+ // Ignore storage errors
742
+ }
743
+ }
744
+ }
745
+ await persistTransferCodes();
561
746
  if (__DEV__) {
562
747
  logger('Cleared transfer code', {
563
748
  transferId
564
749
  });
565
750
  }
566
- }, [logger]);
751
+ }, [logger, persistTransferCodes, storage]);
567
752
  const refreshSessionsWithUser = useCallback(() => refreshSessions(userId), [refreshSessions, userId]);
568
753
  const handleSessionRemoved = useCallback(sessionId => {
569
754
  trackRemovedSession(sessionId);
@@ -573,14 +758,6 @@ export const OxyProvider = ({
573
758
  logout().catch(remoteError => logger('Failed to process remote sign out', remoteError));
574
759
  }, [logger, logout]);
575
760
  const handleIdentityTransferComplete = useCallback(async data => {
576
- if (__DEV__) {
577
- console.log('[OxyContext] handleIdentityTransferComplete called', {
578
- transferId: data.transferId,
579
- sourceDeviceId: data.sourceDeviceId,
580
- currentDeviceId,
581
- hasActiveSession: activeSessionId !== null
582
- });
583
- }
584
761
  try {
585
762
  logger('Received identity transfer complete notification', {
586
763
  transferId: data.transferId,
@@ -589,29 +766,14 @@ export const OxyProvider = ({
589
766
  hasActiveSession: activeSessionId !== null,
590
767
  publicKey: data.publicKey.substring(0, 16) + '...'
591
768
  });
592
-
593
- // Get stored transfer code for verification
594
769
  const storedTransfer = getTransferCode(data.transferId);
595
770
  if (!storedTransfer) {
596
- if (__DEV__) {
597
- console.warn('[OxyContext] Transfer code not found for transferId', {
598
- transferId: data.transferId
599
- });
600
- }
601
771
  logger('Transfer code not found for transferId', {
602
772
  transferId: data.transferId
603
773
  });
604
774
  toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
605
775
  return;
606
776
  }
607
- if (__DEV__) {
608
- console.log('[OxyContext] Found stored transfer code', {
609
- transferId: data.transferId,
610
- storedSourceDeviceId: storedTransfer.sourceDeviceId,
611
- receivedSourceDeviceId: data.sourceDeviceId,
612
- currentDeviceId
613
- });
614
- }
615
777
 
616
778
  // Verify publicKey matches first (most important check)
617
779
  const publicKeyMatches = data.publicKey === storedTransfer.publicKey;
@@ -668,13 +830,6 @@ export const OxyProvider = ({
668
830
  // Don't block - publicKey match is sufficient, code mismatch might be due to user error
669
831
  // Log warning but proceed
670
832
  }
671
- } else {
672
- // If transfer code is not provided, log info but proceed
673
- if (__DEV__) {
674
- logger('Transfer code not provided in completion notification, but publicKey matches - proceeding', {
675
- transferId: data.transferId
676
- });
677
- }
678
833
  }
679
834
 
680
835
  // Check if transfer is too old (safety timeout - 10 minutes)
@@ -691,13 +846,37 @@ export const OxyProvider = ({
691
846
  return;
692
847
  }
693
848
 
694
- // All verifications passed - automatically delete identity
695
- if (__DEV__) {
696
- console.log('[OxyContext] All verifications passed, deleting identity', {
849
+ // CRITICAL: Verify target device has identity before deleting on source device
850
+ // This ensures we never lose the identity
851
+ try {
852
+ logger('Verifying target device has identity before deletion', {
697
853
  transferId: data.transferId,
698
- sourceDeviceId: data.sourceDeviceId,
699
- currentDeviceId
854
+ publicKey: data.publicKey.substring(0, 16) + '...'
855
+ });
856
+
857
+ // Verify target device has active session with matching public key
858
+ const verifyResponse = await oxyServices.makeRequest('GET', `/api/identity/verify-transfer?publicKey=${encodeURIComponent(data.publicKey)}`, undefined, {
859
+ cache: false
860
+ });
861
+ if (!verifyResponse.verified || !verifyResponse.hasActiveSession) {
862
+ logger('Target device verification failed - identity will not be deleted', {
863
+ transferId: data.transferId,
864
+ verified: verifyResponse.verified,
865
+ hasActiveSession: verifyResponse.hasActiveSession
866
+ });
867
+ await updateTransferState(data.transferId, 'failed');
868
+ toast.error('Transfer verification failed: Target device does not have active session. Identity will not be deleted.');
869
+ return;
870
+ }
871
+ logger('Target device verification passed', {
872
+ transferId: data.transferId
700
873
  });
874
+ } catch (verifyError) {
875
+ // If verification fails, don't delete identity - it's safer to keep it
876
+ logger('Failed to verify target device - identity will not be deleted', verifyError);
877
+ await updateTransferState(data.transferId, 'failed');
878
+ toast.error('Transfer verification failed: Could not verify target device. Identity will not be deleted.');
879
+ return;
701
880
  }
702
881
  logger('All transfer verifications passed, deleting identity from source device', {
703
882
  transferId: data.transferId,
@@ -705,29 +884,43 @@ export const OxyProvider = ({
705
884
  publicKey: data.publicKey.substring(0, 16) + '...'
706
885
  });
707
886
  try {
708
- await deleteIdentityAndClearAccount(false, false, true);
709
- if (__DEV__) {
710
- console.log('[OxyContext] Identity deleted successfully');
887
+ // Verify identity still exists before deletion (safety check)
888
+ const identityStillExists = await KeyManager.hasIdentity();
889
+ if (!identityStillExists) {
890
+ logger('Identity already deleted - skipping deletion', {
891
+ transferId: data.transferId
892
+ });
893
+ await updateTransferState(data.transferId, 'completed');
894
+ await clearTransferCode(data.transferId);
895
+ return;
711
896
  }
897
+ await deleteIdentityAndClearAccount(false, false, true);
712
898
 
713
- // Clear the transfer code after successful deletion
714
- clearTransferCode(data.transferId);
899
+ // Verify identity was actually deleted
900
+ const identityDeleted = !(await KeyManager.hasIdentity());
901
+ if (!identityDeleted) {
902
+ logger('Identity deletion failed - identity still exists', {
903
+ transferId: data.transferId
904
+ });
905
+ await updateTransferState(data.transferId, 'failed');
906
+ throw new Error('Identity deletion failed - identity still exists');
907
+ }
908
+ await updateTransferState(data.transferId, 'completed');
909
+ await clearTransferCode(data.transferId);
715
910
  logger('Identity successfully deleted and transfer code cleared', {
716
911
  transferId: data.transferId
717
912
  });
718
913
  toast.success('Identity successfully transferred and removed from this device');
719
914
  } catch (deleteError) {
720
- if (__DEV__) {
721
- console.error('[OxyContext] Error deleting identity', deleteError);
722
- }
723
915
  logger('Error during identity deletion', deleteError);
724
- throw deleteError; // Re-throw to be caught by outer catch
916
+ await updateTransferState(data.transferId, 'failed');
917
+ throw deleteError;
725
918
  }
726
919
  } catch (error) {
727
920
  logger('Failed to delete identity after transfer', error);
728
921
  toast.error(error?.message || 'Failed to remove identity from this device. Please try again manually from Security Settings.');
729
922
  }
730
- }, [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, currentDeviceId, activeSessionId]);
923
+ }, [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, updateTransferState, currentDeviceId, activeSessionId, oxyServices]);
731
924
  useSessionSocket({
732
925
  userId,
733
926
  activeSessionId,
@@ -807,6 +1000,9 @@ export const OxyProvider = ({
807
1000
  storeTransferCode,
808
1001
  getTransferCode,
809
1002
  clearTransferCode,
1003
+ getAllPendingTransfers,
1004
+ getActiveTransferId,
1005
+ updateTransferState,
810
1006
  identitySyncState: {
811
1007
  isSynced: isIdentitySyncedStore ?? true,
812
1008
  isSyncing: isSyncing ?? false
@@ -826,7 +1022,7 @@ export const OxyProvider = ({
826
1022
  useFollow: useFollowHook,
827
1023
  showBottomSheet: showBottomSheetForContext,
828
1024
  openAvatarPicker
829
- }), [activeSessionId, currentDeviceId, createIdentity, importIdentity, signIn, hasIdentity, getPublicKey, isIdentitySynced, syncIdentity, deleteIdentityAndClearAccount, storeTransferCode, getTransferCode, clearTransferCode, 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]);
1025
+ }), [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]);
830
1026
  return /*#__PURE__*/_jsx(OxyContext.Provider, {
831
1027
  value: contextValue,
832
1028
  children: children