@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
@@ -27,7 +27,7 @@ import { showBottomSheet as globalShowBottomSheet } from '../navigation/bottomSh
27
27
  import { useQueryClient, useQuery } from '@tanstack/react-query';
28
28
  import { clearQueryCache } from '../hooks/queryClient';
29
29
  import { queryKeys } from '../hooks/queries/queryKeys';
30
- import { KeyManager, type BackupData } from '../../crypto';
30
+ import type { BackupData } from '../../crypto';
31
31
  import { translate } from '../../i18n';
32
32
  import { updateAvatarVisibility, updateProfileWithAvatar } from '../utils/avatarUtils';
33
33
  import { useAccountStore } from '../stores/accountStore';
@@ -103,11 +103,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
103
103
  loginSuccess,
104
104
  loginFailure,
105
105
  logoutStore,
106
- // Identity sync state and actions
107
- isIdentitySyncedStore,
108
- isSyncing,
109
- setIdentitySynced,
110
- setSyncing,
111
106
  } = useAuthStore(
112
107
  useShallow((state: AuthState) => ({
113
108
  isAuthenticated: state.isAuthenticated,
@@ -116,11 +111,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
116
111
  loginSuccess: state.loginSuccess,
117
112
  loginFailure: state.loginFailure,
118
113
  logoutStore: state.logout,
119
- // Identity sync state and actions
120
- isIdentitySyncedStore: state.isIdentitySynced,
121
- isSyncing: state.isSyncing,
122
- setIdentitySynced: state.setIdentitySynced,
123
- setSyncing: state.setSyncing,
124
114
  })),
125
115
  );
126
116
 
@@ -142,49 +132,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
142
132
 
143
133
  const { storage, isReady: isStorageReady } = useStorage({ onError, logger });
144
134
 
145
- // Identity integrity check and auto-restore on startup
146
- // Skip on web platform - identity storage is only available on native platforms
147
- useEffect(() => {
148
- if (!storage || !isStorageReady) return;
149
- if (Platform.OS === 'web') return; // Identity operations are native-only
150
-
151
- const checkAndRestoreIdentity = async () => {
152
- try {
153
- // Check if identity exists and verify integrity
154
- const hasIdentity = await KeyManager.hasIdentity();
155
- if (hasIdentity) {
156
- const isValid = await KeyManager.verifyIdentityIntegrity();
157
- if (!isValid) {
158
- // Try to restore from backup
159
- const restored = await KeyManager.restoreIdentityFromBackup();
160
- if (__DEV__) {
161
- logger(restored
162
- ? 'Identity restored from backup successfully'
163
- : 'Identity integrity check failed - user may need to restore from backup file'
164
- );
165
- }
166
- } else {
167
- // Identity is valid - ensure backup is up to date
168
- await KeyManager.backupIdentity();
169
- }
170
- } else {
171
- // No identity - try to restore from backup
172
- const restored = await KeyManager.restoreIdentityFromBackup();
173
- if (restored && __DEV__) {
174
- logger('Identity restored from backup on startup');
175
- }
176
- }
177
- } catch (error) {
178
- if (__DEV__) {
179
- logger('Error during identity integrity check', error);
180
- }
181
- // Don't block app startup - user can recover with backup file
182
- }
183
- };
184
-
185
- checkAndRestoreIdentity();
186
- }, [storage, isStorageReady, logger]);
187
-
188
135
  // Offline queuing is now handled by TanStack Query mutations
189
136
  // No need for custom offline queue
190
137
 
@@ -249,15 +196,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
249
196
  const user = userData ?? null;
250
197
 
251
198
  const {
252
- createIdentity,
253
- importIdentity: importIdentityBase,
254
199
  signIn,
255
200
  logout,
256
201
  logoutAll,
257
- hasIdentity,
258
- getPublicKey,
259
- isIdentitySynced,
260
- syncIdentity: syncIdentityBase,
261
202
  } = useAuthOperations({
262
203
  oxyServices,
263
204
  storage,
@@ -275,33 +216,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
275
216
  loginFailure,
276
217
  logoutStore,
277
218
  setAuthState,
278
- setIdentitySynced,
279
- setSyncing,
280
219
  logger,
281
220
  });
282
221
 
283
- // syncIdentity - TanStack Query handles offline mutations automatically
284
- const syncIdentity = useCallback(() => syncIdentityBase(), [syncIdentityBase]);
285
-
286
- // Wrapper for importIdentity to handle legacy calls gracefully
287
- const importIdentity = useCallback(
288
- async (backupData: BackupData | string, password?: string): Promise<{ synced: boolean }> => {
289
- // Handle legacy calls with single string argument (old recovery phrase signature)
290
- if (typeof backupData === 'string') {
291
- throw new Error('Recovery phrase import is no longer supported. Please use backup file import or QR code transfer instead.');
292
- }
293
-
294
- // Validate that password is provided
295
- if (!password || typeof password !== 'string') {
296
- throw new Error('Password is required for backup file import.');
297
- }
298
-
299
- return importIdentityBase(backupData, password);
300
- },
301
- [importIdentityBase]
302
- );
303
-
304
- // Clear all account data when identity is lost (for accounts app)
222
+ // Clear all account data when logging out (for accounts app)
305
223
  // In accounts app, identity = account, so losing identity means losing everything
306
224
  const clearAllAccountData = useCallback(async (): Promise<void> => {
307
225
  // Clear TanStack Query cache (in-memory)
@@ -319,19 +237,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
319
237
  // Clear session state (sessions, activeSessionId, storage)
320
238
  await clearSessionState();
321
239
 
322
- // Clear identity sync state from storage
323
- if (storage) {
324
- try {
325
- await storage.removeItem('oxy_identity_synced');
326
- } catch (error) {
327
- logger('Failed to clear identity sync state', error);
328
- }
329
- }
330
-
331
- // Reset auth store identity sync state
332
- useAuthStore.getState().setIdentitySynced(false);
333
- useAuthStore.getState().setSyncing(false);
334
-
335
240
  // Reset account store
336
241
  useAccountStore.getState().reset();
337
242
 
@@ -339,171 +244,54 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
339
244
  oxyServices.clearCache();
340
245
  }, [queryClient, storage, clearSessionState, logger, oxyServices]);
341
246
 
342
- // Transfer code management functions (must be defined before deleteIdentityAndClearAccount)
343
- const getAllPendingTransfers = useCallback(() => {
344
- const pending: Array<{ transferId: string; data: TransferCodeData }> = [];
345
- transferCodesRef.current.forEach((data, transferId) => {
346
- if (data.state === 'pending') {
347
- pending.push({ transferId, data });
348
- }
349
- });
350
- return pending;
351
- }, []);
352
-
353
- const getActiveTransferId = useCallback(() => {
354
- return activeTransferIdRef.current;
355
- }, []);
356
-
357
- // Delete identity and clear all account data
358
- // In accounts app, deleting identity means losing the account completely
359
- const deleteIdentityAndClearAccount = useCallback(async (
360
- skipBackup: boolean = false,
361
- force: boolean = false,
362
- userConfirmed: boolean = false
363
- ): Promise<void> => {
364
- // CRITICAL: Check for active transfers before deletion (unless force is true)
365
- // This prevents accidental identity loss during transfer
366
- if (!force) {
367
- const pendingTransfers = getAllPendingTransfers();
368
- if (pendingTransfers.length > 0) {
369
- const activeTransferId = getActiveTransferId();
370
- const hasActiveTransfer = activeTransferId && pendingTransfers.some(t => t.transferId === activeTransferId);
371
-
372
- if (hasActiveTransfer) {
373
- throw new Error(
374
- 'Cannot delete identity: An active identity transfer is in progress. ' +
375
- 'Please wait for the transfer to complete or cancel it first. ' +
376
- 'If you proceed, you may lose access to your identity permanently.'
377
- );
378
- }
379
- }
380
- }
381
-
382
- // First, clear all account data
383
- await clearAllAccountData();
384
-
385
- // Then delete the identity keys
386
- await KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
387
- }, [clearAllAccountData, getAllPendingTransfers, getActiveTransferId]);
388
-
389
- // Network reconnect sync - TanStack Query automatically retries mutations on reconnect
390
- // We only need to sync identity if it's not synced
247
+ // Network reconnect - TanStack Query automatically retries mutations on reconnect
248
+ // Network reconnect - TanStack Query automatically retries mutations on reconnect
391
249
  useEffect(() => {
392
250
  if (!storage) return;
393
251
 
394
252
  let wasOffline = false;
395
253
  let checkTimeout: NodeJS.Timeout | null = null;
396
- let lastReconnectionLog = 0;
397
- const RECONNECTION_LOG_DEBOUNCE_MS = 5000; // 5 seconds
398
-
399
- // Circuit breaker and exponential backoff state
400
- const stateRef = {
401
- consecutiveFailures: 0,
402
- currentInterval: 10000, // Start with 10 seconds
403
- baseInterval: 10000, // Base interval in milliseconds
404
- maxInterval: 60000, // Maximum interval (60 seconds)
405
- maxFailures: 5, // Circuit breaker threshold
406
- };
407
254
 
408
255
  const scheduleNextCheck = () => {
409
256
  if (checkTimeout) {
410
257
  clearTimeout(checkTimeout);
411
258
  }
412
259
  checkTimeout = setTimeout(() => {
413
- checkNetworkAndSync();
414
- }, stateRef.currentInterval);
260
+ checkNetworkStatus();
261
+ }, 30000); // Check every 30 seconds
415
262
  };
416
263
 
417
- const checkNetworkAndSync = async () => {
264
+ const checkNetworkStatus = async () => {
418
265
  try {
419
266
  // Try a lightweight health check to see if we're online
420
- await oxyServices.healthCheck().catch(() => {
421
- wasOffline = true;
422
- throw new Error('Health check failed');
423
- });
267
+ await oxyServices.healthCheck();
424
268
 
425
- // Health check succeeded - reset circuit breaker and backoff
426
- if (stateRef.consecutiveFailures > 0) {
427
- stateRef.consecutiveFailures = 0;
428
- stateRef.currentInterval = stateRef.baseInterval;
429
- }
430
-
431
- // If we were offline and now we're online, sync identity if needed
269
+ // If we were offline and now we're online
432
270
  if (wasOffline) {
433
- const now = Date.now();
434
- const timeSinceLastLog = now - lastReconnectionLog;
435
-
436
- if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
437
- logger('Network reconnected, checking identity sync...');
438
- lastReconnectionLog = now;
439
-
440
- // Sync identity first (if not synced)
441
- try {
442
- const hasIdentityValue = await hasIdentity();
443
- if (hasIdentityValue) {
444
- // Check sync status directly - sync if not explicitly 'true'
445
- // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
446
- const syncStatus = await storage.getItem('oxy_identity_synced');
447
- if (syncStatus !== 'true') {
448
- await syncIdentity();
449
- }
450
- }
451
- } catch (syncError: any) {
452
- // Skip sync silently if username is required (expected when offline onboarding)
453
- if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
454
- if (__DEV__) {
455
- loggerUtil.debug('Sync skipped - username required', { component: 'OxyContext', method: 'checkNetworkAndSync' }, syncError as unknown);
456
- }
457
- // Don't log or show error - username will be set later
458
- } else if (!isTimeoutOrNetworkError(syncError)) {
459
- // Only log unexpected errors - timeouts/network issues are expected when offline
460
- logger('Error syncing identity on reconnect', syncError);
461
- } else if (__DEV__) {
462
- loggerUtil.debug('Identity sync timeout (expected when offline)', { component: 'OxyContext', method: 'checkNetworkAndSync' }, syncError as unknown);
463
- }
464
- }
465
- }
466
-
271
+ logger('Network reconnected');
467
272
  // TanStack Query will automatically retry pending mutations
468
- // Reset flag immediately after processing (whether logged or not)
469
273
  wasOffline = false;
470
274
  }
471
275
  } catch (error) {
472
276
  // Network check failed - we're offline
473
- wasOffline = true;
474
-
475
- // Increment failure count and apply exponential backoff
476
- stateRef.consecutiveFailures++;
477
-
478
- // Calculate new interval with exponential backoff, capped at maxInterval
479
- const backoffMultiplier = Math.min(
480
- Math.pow(2, stateRef.consecutiveFailures - 1),
481
- stateRef.maxInterval / stateRef.baseInterval
482
- );
483
- stateRef.currentInterval = Math.min(
484
- stateRef.baseInterval * backoffMultiplier,
485
- stateRef.maxInterval
486
- );
487
-
488
- // If we hit the circuit breaker threshold, use max interval
489
- if (stateRef.consecutiveFailures >= stateRef.maxFailures) {
490
- stateRef.currentInterval = stateRef.maxInterval;
277
+ if (!wasOffline && __DEV__) {
278
+ logger('Network appears offline');
491
279
  }
280
+ wasOffline = true;
492
281
  } finally {
493
- // Always schedule next check (will use updated interval)
494
282
  scheduleNextCheck();
495
283
  }
496
284
  };
497
285
 
498
286
  // Check immediately
499
- checkNetworkAndSync();
287
+ checkNetworkStatus();
500
288
 
501
289
  return () => {
502
290
  if (checkTimeout) {
503
291
  clearTimeout(checkTimeout);
504
292
  }
505
293
  };
506
- }, [oxyServices, storage, syncIdentity, logger]);
294
+ }, [oxyServices, storage, logger]);
507
295
 
508
296
  const { getDeviceSessions, logoutAllDeviceSessions, updateDeviceName } = useDeviceManagement({
509
297
  oxyServices,
@@ -620,183 +408,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
620
408
  // Get userId from JWT token (MongoDB ObjectId) for socket room matching
621
409
  const userId = oxyServices.getCurrentUserId();
622
410
 
623
- // Transfer code storage interface
624
- interface TransferCodeData {
625
- code: string;
626
- sourceDeviceId: string | null;
627
- publicKey: string;
628
- timestamp: number;
629
- state: 'pending' | 'completed' | 'failed';
630
- }
631
-
632
- // Store transfer codes in memory for verification (also persisted to storage)
633
- // Map: transferId -> TransferCodeData
634
- const transferCodesRef = useRef<Map<string, TransferCodeData>>(new Map());
635
- const activeTransferIdRef = useRef<string | null>(null);
636
- const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
637
- const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
638
-
639
- // Clear stale transfer codes on startup
640
- // Transfers are ephemeral and should not persist across app restarts
641
- useEffect(() => {
642
- if (!storage || !isStorageReady) return;
643
-
644
- const clearStaleTransfers = async () => {
645
- try {
646
- // Clear any stored transfer codes - they're only valid during active transfer sessions
647
- await storage.removeItem(TRANSFER_CODES_STORAGE_KEY);
648
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
649
-
650
- if (__DEV__) {
651
- logger('Cleared stale transfer codes on startup');
652
- }
653
- } catch (error) {
654
- if (__DEV__) {
655
- logger('Failed to clear stale transfer codes', error);
656
- }
657
- }
658
- };
659
-
660
- clearStaleTransfers();
661
- }, [storage, isStorageReady, logger, storageKeyPrefix]);
662
-
663
- // Persist transfer codes to storage whenever they change
664
- const persistTransferCodes = useCallback(async () => {
665
- if (!storage) return;
666
-
667
- try {
668
- const codesToStore: Record<string, TransferCodeData> = {};
669
- transferCodesRef.current.forEach((value, key) => {
670
- codesToStore[key] = value;
671
- });
672
- await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(codesToStore));
673
- } catch (error) {
674
- if (__DEV__) {
675
- logger('Failed to persist transfer codes', error);
676
- }
677
- }
678
- }, [storage, logger]);
679
-
680
- // Cleanup old transfer codes (older than 15 minutes)
681
- useEffect(() => {
682
- const cleanup = setInterval(async () => {
683
- const now = Date.now();
684
- const fifteenMinutes = 15 * 60 * 1000;
685
- let needsPersist = false;
686
-
687
- transferCodesRef.current.forEach((value, key) => {
688
- if (now - value.timestamp > fifteenMinutes) {
689
- transferCodesRef.current.delete(key);
690
- needsPersist = true;
691
- if (__DEV__) {
692
- logger('Cleaned up expired transfer code', { transferId: key });
693
- }
694
- }
695
- });
696
-
697
- // Clear active transfer if it was deleted
698
- if (activeTransferIdRef.current && !transferCodesRef.current.has(activeTransferIdRef.current)) {
699
- activeTransferIdRef.current = null;
700
- if (storage) {
701
- try {
702
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
703
- } catch (error) {
704
- // Ignore storage errors
705
- }
706
- }
707
- }
708
-
709
- if (needsPersist) {
710
- await persistTransferCodes();
711
- }
712
- }, 60000); // Check every minute
713
-
714
- return () => clearInterval(cleanup);
715
- }, [logger, persistTransferCodes, storage]);
716
-
717
- // Transfer code management functions
718
- const storeTransferCode = useCallback(async (transferId: string, code: string, sourceDeviceId: string | null, publicKey: string) => {
719
- const transferData: TransferCodeData = {
720
- code,
721
- sourceDeviceId,
722
- publicKey,
723
- timestamp: Date.now(),
724
- state: 'pending',
725
- };
726
-
727
- transferCodesRef.current.set(transferId, transferData);
728
- activeTransferIdRef.current = transferId;
729
-
730
- // Persist to storage
731
- await persistTransferCodes();
732
- if (storage) {
733
- try {
734
- await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, transferId);
735
- } catch (error) {
736
- if (__DEV__) {
737
- logger('Failed to persist active transfer ID', error);
738
- }
739
- }
740
- }
741
-
742
- if (__DEV__) {
743
- logger('Stored transfer code', { transferId, sourceDeviceId, publicKey: publicKey.substring(0, 16) + '...' });
744
- }
745
- }, [logger, persistTransferCodes, storage]);
746
-
747
- const getTransferCode = useCallback((transferId: string) => {
748
- return transferCodesRef.current.get(transferId) || null;
749
- }, []);
750
-
751
- const updateTransferState = useCallback(async (transferId: string, state: 'pending' | 'completed' | 'failed') => {
752
- const transferData = transferCodesRef.current.get(transferId);
753
- if (transferData) {
754
- transferData.state = state;
755
- transferCodesRef.current.set(transferId, transferData);
756
-
757
- // Clear active transfer if completed or failed
758
- if (state === 'completed' || state === 'failed') {
759
- if (activeTransferIdRef.current === transferId) {
760
- activeTransferIdRef.current = null;
761
- if (storage) {
762
- try {
763
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
764
- } catch (error) {
765
- // Ignore storage errors
766
- }
767
- }
768
- }
769
- }
770
-
771
- await persistTransferCodes();
772
-
773
- if (__DEV__) {
774
- logger('Updated transfer state', { transferId, state });
775
- }
776
- }
777
- }, [logger, persistTransferCodes, storage]);
778
-
779
- const clearTransferCode = useCallback(async (transferId: string) => {
780
- transferCodesRef.current.delete(transferId);
781
-
782
- if (activeTransferIdRef.current === transferId) {
783
- activeTransferIdRef.current = null;
784
- if (storage) {
785
- try {
786
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
787
- } catch (error) {
788
- // Ignore storage errors
789
- }
790
- }
791
- }
792
-
793
- await persistTransferCodes();
794
-
795
- if (__DEV__) {
796
- logger('Cleared transfer code', { transferId });
797
- }
798
- }, [logger, persistTransferCodes, storage]);
799
-
800
411
  const refreshSessionsWithUser = useCallback(
801
412
  () => refreshSessions(userId || undefined),
802
413
  [refreshSessions, userId],
@@ -814,154 +425,6 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
814
425
  logout().catch((remoteError) => logger('Failed to process remote sign out', remoteError));
815
426
  }, [logger, logout]);
816
427
 
817
- const handleIdentityTransferComplete = useCallback(
818
- async (data: { transferId: string; sourceDeviceId: string; publicKey: string; transferCode?: string; completedAt: string }) => {
819
- try {
820
- logger('Received identity transfer complete notification', {
821
- transferId: data.transferId,
822
- sourceDeviceId: data.sourceDeviceId,
823
- currentDeviceId,
824
- hasActiveSession: activeSessionId !== null,
825
- publicKey: data.publicKey.substring(0, 16) + '...',
826
- });
827
-
828
- const storedTransfer = getTransferCode(data.transferId);
829
-
830
- if (!storedTransfer) {
831
- logger('Transfer code not found for transferId', {
832
- transferId: data.transferId,
833
- });
834
- toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
835
- return;
836
- }
837
-
838
- // Verify publicKey matches first (most important check)
839
- const publicKeyMatches = data.publicKey === storedTransfer.publicKey;
840
- if (!publicKeyMatches) {
841
- logger('Public key mismatch for transfer', {
842
- transferId: data.transferId,
843
- receivedPublicKey: data.publicKey.substring(0, 16) + '...',
844
- storedPublicKey: storedTransfer.publicKey.substring(0, 16) + '...',
845
- });
846
- toast.error('Transfer verification failed: Public key mismatch. Identity will not be deleted.');
847
- return;
848
- }
849
-
850
- // Verify deviceId matches - very lenient since publicKey is the critical check
851
- // If publicKey matches, we allow deletion even if deviceId doesn't match exactly
852
- // This handles cases where deviceId might not be available or slightly different
853
- const deviceIdMatches =
854
- // Exact match
855
- (data.sourceDeviceId && data.sourceDeviceId === currentDeviceId) ||
856
- // Stored sourceDeviceId matches current deviceId
857
- (storedTransfer.sourceDeviceId && storedTransfer.sourceDeviceId === currentDeviceId);
858
-
859
- // If publicKey matches, we're very lenient with deviceId - only warn but don't block
860
- if (!deviceIdMatches && publicKeyMatches) {
861
- logger('Device ID mismatch for transfer, but publicKey matches - proceeding with deletion', {
862
- transferId: data.transferId,
863
- receivedDeviceId: data.sourceDeviceId,
864
- storedDeviceId: storedTransfer.sourceDeviceId,
865
- currentDeviceId,
866
- hasActiveSession: activeSessionId !== null,
867
- });
868
- // Proceed with deletion - publicKey match is the critical verification
869
- } else if (!deviceIdMatches && !publicKeyMatches) {
870
- // Both don't match - this is suspicious, block deletion
871
- logger('Device ID and publicKey mismatch for transfer', {
872
- transferId: data.transferId,
873
- receivedDeviceId: data.sourceDeviceId,
874
- currentDeviceId,
875
- });
876
- toast.error('Transfer verification failed: Device and key mismatch. Identity will not be deleted.');
877
- return;
878
- }
879
-
880
- // Verify transfer code matches (if provided)
881
- // Transfer code is optional - if not provided, we still proceed if publicKey matches
882
- if (data.transferCode) {
883
- const codeMatches = data.transferCode.toUpperCase() === storedTransfer.code.toUpperCase();
884
- if (!codeMatches) {
885
- logger('Transfer code mismatch, but publicKey matches - proceeding with deletion', {
886
- transferId: data.transferId,
887
- receivedCode: data.transferCode,
888
- storedCode: storedTransfer.code.substring(0, 2) + '****',
889
- });
890
- // Don't block - publicKey match is sufficient, code mismatch might be due to user error
891
- // Log warning but proceed
892
- }
893
- }
894
-
895
- // Check if transfer is too old (safety timeout - 10 minutes)
896
- const transferAge = Date.now() - storedTransfer.timestamp;
897
- const tenMinutes = 10 * 60 * 1000;
898
- if (transferAge > tenMinutes) {
899
- logger('Transfer confirmation received too late', {
900
- transferId: data.transferId,
901
- age: transferAge,
902
- ageMinutes: Math.round(transferAge / 60000),
903
- });
904
- toast.error('Transfer verification failed: Confirmation received too late. Identity will not be deleted.');
905
- clearTransferCode(data.transferId);
906
- return;
907
- }
908
-
909
- // NOTE: Target device verification already happened server-side when notifyTransferComplete was called
910
- // The server verified that the target device is authenticated and has the matching public key
911
- // Additional client-side verification is not necessary and would require source device authentication
912
- // which may not be available. The existing checks (public key match, transfer code, device ID) are sufficient.
913
-
914
- logger('All transfer verifications passed, deleting identity from source device', {
915
- transferId: data.transferId,
916
- sourceDeviceId: data.sourceDeviceId,
917
- publicKey: data.publicKey.substring(0, 16) + '...',
918
- });
919
-
920
- try {
921
- // Verify identity still exists before deletion (safety check)
922
- const identityStillExists = await KeyManager.hasIdentity();
923
- if (!identityStillExists) {
924
- logger('Identity already deleted - skipping deletion', {
925
- transferId: data.transferId,
926
- });
927
- await updateTransferState(data.transferId, 'completed');
928
- await clearTransferCode(data.transferId);
929
- return;
930
- }
931
-
932
- await deleteIdentityAndClearAccount(false, false, true);
933
-
934
- // Verify identity was actually deleted
935
- const identityDeleted = !(await KeyManager.hasIdentity());
936
- if (!identityDeleted) {
937
- logger('Identity deletion failed - identity still exists', {
938
- transferId: data.transferId,
939
- });
940
- await updateTransferState(data.transferId, 'failed');
941
- throw new Error('Identity deletion failed - identity still exists');
942
- }
943
-
944
- await updateTransferState(data.transferId, 'completed');
945
- await clearTransferCode(data.transferId);
946
-
947
- logger('Identity successfully deleted and transfer code cleared', {
948
- transferId: data.transferId,
949
- });
950
-
951
- toast.success('Identity successfully transferred and removed from this device');
952
- } catch (deleteError: any) {
953
- logger('Error during identity deletion', deleteError);
954
- await updateTransferState(data.transferId, 'failed');
955
- throw deleteError;
956
- }
957
- } catch (error: any) {
958
- logger('Failed to delete identity after transfer', error);
959
- toast.error(error?.message || 'Failed to remove identity from this device. Please try again manually from Security Settings.');
960
- }
961
- },
962
- [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, updateTransferState, currentDeviceId, activeSessionId, oxyServices],
963
- );
964
-
965
428
  useSessionSocket({
966
429
  userId,
967
430
  activeSessionId,
@@ -971,10 +434,8 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
971
434
  clearSessionState,
972
435
  baseURL: oxyServices.getBaseURL(),
973
436
  getAccessToken: () => oxyServices.getAccessToken(),
974
- getTransferCode: getTransferCode,
975
437
  onRemoteSignOut: handleRemoteSignOut,
976
438
  onSessionRemoved: handleSessionRemoved,
977
- onIdentityTransferComplete: handleIdentityTransferComplete,
978
439
  });
979
440
 
980
441
  const switchSessionForContext = useCallback(
@@ -1016,7 +477,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1016
477
  oxyServices,
1017
478
  activeSessionId,
1018
479
  queryClient,
1019
- { syncIdentity, deviceId: currentDeviceId || undefined }
480
+ { deviceId: currentDeviceId || undefined }
1020
481
  );
1021
482
 
1022
483
  toast.success(translate(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
@@ -1026,7 +487,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1026
487
  },
1027
488
  },
1028
489
  });
1029
- }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient, syncIdentity]);
490
+ }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient]);
1030
491
 
1031
492
  const contextValue: OxyContextState = useMemo(() => ({
1032
493
  user,
@@ -1042,24 +503,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1042
503
  currentLanguageMetadata,
1043
504
  currentLanguageName,
1044
505
  currentNativeLanguageName,
1045
- createIdentity,
1046
- importIdentity,
1047
506
  signIn,
1048
- hasIdentity,
1049
- getPublicKey,
1050
- isIdentitySynced,
1051
- syncIdentity,
1052
- deleteIdentityAndClearAccount,
1053
- storeTransferCode,
1054
- getTransferCode,
1055
- clearTransferCode,
1056
- getAllPendingTransfers,
1057
- getActiveTransferId,
1058
- updateTransferState,
1059
- identitySyncState: {
1060
- isSynced: isIdentitySyncedStore ?? true,
1061
- isSyncing: isSyncing ?? false,
1062
- },
1063
507
  logout,
1064
508
  logoutAll,
1065
509
  switchSession: switchSessionForContext,
@@ -1078,22 +522,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
1078
522
  }), [
1079
523
  activeSessionId,
1080
524
  currentDeviceId,
1081
- createIdentity,
1082
- importIdentity,
1083
525
  signIn,
1084
- hasIdentity,
1085
- getPublicKey,
1086
- isIdentitySynced,
1087
- syncIdentity,
1088
- deleteIdentityAndClearAccount,
1089
- storeTransferCode,
1090
- getTransferCode,
1091
- clearTransferCode,
1092
- getAllPendingTransfers,
1093
- getActiveTransferId,
1094
- updateTransferState,
1095
- isIdentitySyncedStore,
1096
- isSyncing,
1097
526
  currentLanguage,
1098
527
  currentLanguageMetadata,
1099
528
  currentLanguageName,