@oxyhq/services 5.17.7 → 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 +22 -582
  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 -581
  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 -630
  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';
@@ -56,30 +54,6 @@ const loadUseFollowHook = () => {
56
54
  return cachedUseFollowHook;
57
55
  }
58
56
  };
59
-
60
- // Shared storage key for identity cache - used by accounts app for instant routing
61
- // Uses expo-secure-store for consistency with identity storage (KeyManager)
62
- const IDENTITY_CACHE_KEY = 'oxy_identity_exists_cache';
63
-
64
- // Helper to update identity cache in SecureStore
65
- const updateIdentityCache = async exists => {
66
- if (Platform.OS === 'web') return;
67
- try {
68
- const SecureStore = await import('expo-secure-store');
69
- await SecureStore.setItemAsync(IDENTITY_CACHE_KEY, exists ? 'true' : 'false');
70
- } catch {
71
- // Silently fail - cache is just an optimization
72
- }
73
- };
74
- const clearIdentityCache = async () => {
75
- if (Platform.OS === 'web') return;
76
- try {
77
- const SecureStore = await import('expo-secure-store');
78
- await SecureStore.deleteItemAsync(IDENTITY_CACHE_KEY);
79
- } catch {
80
- // Silently fail
81
- }
82
- };
83
57
  export const OxyProvider = ({
84
58
  children,
85
59
  oxyServices: providedOxyServices,
@@ -107,24 +81,14 @@ export const OxyProvider = ({
107
81
  error,
108
82
  loginSuccess,
109
83
  loginFailure,
110
- logoutStore,
111
- // Identity sync state and actions
112
- isIdentitySyncedStore,
113
- isSyncing,
114
- setIdentitySynced,
115
- setSyncing
84
+ logoutStore
116
85
  } = useAuthStore(useShallow(state => ({
117
86
  isAuthenticated: state.isAuthenticated,
118
87
  isLoading: state.isLoading,
119
88
  error: state.error,
120
89
  loginSuccess: state.loginSuccess,
121
90
  loginFailure: state.loginFailure,
122
- logoutStore: state.logout,
123
- // Identity sync state and actions
124
- isIdentitySyncedStore: state.isIdentitySynced,
125
- isSyncing: state.isSyncing,
126
- setIdentitySynced: state.setIdentitySynced,
127
- setSyncing: state.setSyncing
91
+ logoutStore: state.logout
128
92
  })));
129
93
  const [tokenReady, setTokenReady] = useState(true);
130
94
  const initializedRef = useRef(false);
@@ -147,45 +111,6 @@ export const OxyProvider = ({
147
111
  logger
148
112
  });
149
113
 
150
- // Identity integrity check and auto-restore on startup
151
- // Skip on web platform - identity storage is only available on native platforms
152
- useEffect(() => {
153
- if (!storage || !isStorageReady) return;
154
- if (Platform.OS === 'web') return; // Identity operations are native-only
155
-
156
- const checkAndRestoreIdentity = async () => {
157
- try {
158
- // Check if identity exists and verify integrity
159
- const hasIdentity = await KeyManager.hasIdentity();
160
- if (hasIdentity) {
161
- const isValid = await KeyManager.verifyIdentityIntegrity();
162
- if (!isValid) {
163
- // Try to restore from backup
164
- const restored = await KeyManager.restoreIdentityFromBackup();
165
- if (__DEV__) {
166
- logger(restored ? 'Identity restored from backup successfully' : 'Identity integrity check failed - user may need to restore from backup file');
167
- }
168
- } else {
169
- // Identity is valid - ensure backup is up to date
170
- await KeyManager.backupIdentity();
171
- }
172
- } else {
173
- // No identity - try to restore from backup
174
- const restored = await KeyManager.restoreIdentityFromBackup();
175
- if (restored && __DEV__) {
176
- logger('Identity restored from backup on startup');
177
- }
178
- }
179
- } catch (error) {
180
- if (__DEV__) {
181
- logger('Error during identity integrity check', error);
182
- }
183
- // Don't block app startup - user can recover with backup file
184
- }
185
- };
186
- checkAndRestoreIdentity();
187
- }, [storage, isStorageReady, logger]);
188
-
189
114
  // Offline queuing is now handled by TanStack Query mutations
190
115
  // No need for custom offline queue
191
116
 
@@ -253,15 +178,9 @@ export const OxyProvider = ({
253
178
  });
254
179
  const user = userData ?? null;
255
180
  const {
256
- createIdentity,
257
- importIdentity: importIdentityBase,
258
181
  signIn,
259
182
  logout,
260
- logoutAll,
261
- hasIdentity,
262
- getPublicKey,
263
- isIdentitySynced,
264
- syncIdentity: syncIdentityBase
183
+ logoutAll
265
184
  } = useAuthOperations({
266
185
  oxyServices,
267
186
  storage,
@@ -279,40 +198,10 @@ export const OxyProvider = ({
279
198
  loginFailure,
280
199
  logoutStore,
281
200
  setAuthState,
282
- setIdentitySynced,
283
- setSyncing,
284
201
  logger
285
202
  });
286
203
 
287
- // syncIdentity - TanStack Query handles offline mutations automatically
288
- const syncIdentity = useCallback(() => syncIdentityBase(), [syncIdentityBase]);
289
-
290
- // Wrapper for createIdentity to update identity cache
291
- const wrappedCreateIdentity = useCallback(async () => {
292
- const result = await createIdentity();
293
- // Update cache for instant routing on next app launch
294
- updateIdentityCache(true);
295
- return result;
296
- }, [createIdentity]);
297
-
298
- // Wrapper for importIdentity to handle legacy calls and update cache
299
- const importIdentity = useCallback(async (backupData, password) => {
300
- // Handle legacy calls with single string argument (old recovery phrase signature)
301
- if (typeof backupData === 'string') {
302
- throw new Error('Recovery phrase import is no longer supported. Please use backup file import or QR code transfer instead.');
303
- }
304
-
305
- // Validate that password is provided
306
- if (!password || typeof password !== 'string') {
307
- throw new Error('Password is required for backup file import.');
308
- }
309
- const result = await importIdentityBase(backupData, password);
310
- // Update cache for instant routing on next app launch
311
- updateIdentityCache(true);
312
- return result;
313
- }, [importIdentityBase]);
314
-
315
- // Clear all account data when identity is lost (for accounts app)
204
+ // Clear all account data when logging out (for accounts app)
316
205
  // In accounts app, identity = account, so losing identity means losing everything
317
206
  const clearAllAccountData = useCallback(async () => {
318
207
  // Clear TanStack Query cache (in-memory)
@@ -330,19 +219,6 @@ export const OxyProvider = ({
330
219
  // Clear session state (sessions, activeSessionId, storage)
331
220
  await clearSessionState();
332
221
 
333
- // Clear identity sync state from storage
334
- if (storage) {
335
- try {
336
- await storage.removeItem('oxy_identity_synced');
337
- } catch (error) {
338
- logger('Failed to clear identity sync state', error);
339
- }
340
- }
341
-
342
- // Reset auth store identity sync state
343
- useAuthStore.getState().setIdentitySynced(false);
344
- useAuthStore.getState().setSyncing(false);
345
-
346
222
  // Reset account store
347
223
  useAccountStore.getState().reset();
348
224
 
@@ -350,165 +226,50 @@ export const OxyProvider = ({
350
226
  oxyServices.clearCache();
351
227
  }, [queryClient, storage, clearSessionState, logger, oxyServices]);
352
228
 
353
- // Transfer code management functions (must be defined before deleteIdentityAndClearAccount)
354
- const getAllPendingTransfers = useCallback(() => {
355
- const pending = [];
356
- transferCodesRef.current.forEach((data, transferId) => {
357
- if (data.state === 'pending') {
358
- pending.push({
359
- transferId,
360
- data
361
- });
362
- }
363
- });
364
- return pending;
365
- }, []);
366
- const getActiveTransferId = useCallback(() => {
367
- return activeTransferIdRef.current;
368
- }, []);
369
-
370
- // Delete identity and clear all account data
371
- // In accounts app, deleting identity means losing the account completely
372
- const deleteIdentityAndClearAccount = useCallback(async (skipBackup = false, force = false, userConfirmed = false) => {
373
- // CRITICAL: Check for active transfers before deletion (unless force is true)
374
- // This prevents accidental identity loss during transfer
375
- if (!force) {
376
- const pendingTransfers = getAllPendingTransfers();
377
- if (pendingTransfers.length > 0) {
378
- const activeTransferId = getActiveTransferId();
379
- const hasActiveTransfer = activeTransferId && pendingTransfers.some(t => t.transferId === activeTransferId);
380
- if (hasActiveTransfer) {
381
- 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.');
382
- }
383
- }
384
- }
385
-
386
- // First, clear all account data
387
- await clearAllAccountData();
388
-
389
- // Then delete the identity keys
390
- await KeyManager.deleteIdentity(skipBackup, force, userConfirmed);
391
-
392
- // Clear identity cache for instant routing on next app launch
393
- clearIdentityCache();
394
- }, [clearAllAccountData, getAllPendingTransfers, getActiveTransferId]);
395
-
396
- // Network reconnect sync - TanStack Query automatically retries mutations on reconnect
397
- // 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
398
231
  useEffect(() => {
399
232
  if (!storage) return;
400
233
  let wasOffline = false;
401
234
  let checkTimeout = null;
402
- let lastReconnectionLog = 0;
403
- const RECONNECTION_LOG_DEBOUNCE_MS = 5000; // 5 seconds
404
-
405
- // Circuit breaker and exponential backoff state
406
- const stateRef = {
407
- consecutiveFailures: 0,
408
- currentInterval: 10000,
409
- // Start with 10 seconds
410
- baseInterval: 10000,
411
- // Base interval in milliseconds
412
- maxInterval: 60000,
413
- // Maximum interval (60 seconds)
414
- maxFailures: 5 // Circuit breaker threshold
415
- };
416
235
  const scheduleNextCheck = () => {
417
236
  if (checkTimeout) {
418
237
  clearTimeout(checkTimeout);
419
238
  }
420
239
  checkTimeout = setTimeout(() => {
421
- checkNetworkAndSync();
422
- }, stateRef.currentInterval);
240
+ checkNetworkStatus();
241
+ }, 30000); // Check every 30 seconds
423
242
  };
424
- const checkNetworkAndSync = async () => {
243
+ const checkNetworkStatus = async () => {
425
244
  try {
426
245
  // Try a lightweight health check to see if we're online
427
- await oxyServices.healthCheck().catch(() => {
428
- wasOffline = true;
429
- throw new Error('Health check failed');
430
- });
431
-
432
- // Health check succeeded - reset circuit breaker and backoff
433
- if (stateRef.consecutiveFailures > 0) {
434
- stateRef.consecutiveFailures = 0;
435
- stateRef.currentInterval = stateRef.baseInterval;
436
- }
246
+ await oxyServices.healthCheck();
437
247
 
438
- // If we were offline and now we're online, sync identity if needed
248
+ // If we were offline and now we're online
439
249
  if (wasOffline) {
440
- const now = Date.now();
441
- const timeSinceLastLog = now - lastReconnectionLog;
442
- if (timeSinceLastLog >= RECONNECTION_LOG_DEBOUNCE_MS) {
443
- logger('Network reconnected, checking identity sync...');
444
- lastReconnectionLog = now;
445
-
446
- // Sync identity first (if not synced)
447
- try {
448
- const hasIdentityValue = await hasIdentity();
449
- if (hasIdentityValue) {
450
- // Check sync status directly - sync if not explicitly 'true'
451
- // undefined = not synced yet, 'false' = explicitly not synced, 'true' = synced
452
- const syncStatus = await storage.getItem('oxy_identity_synced');
453
- if (syncStatus !== 'true') {
454
- await syncIdentity();
455
- }
456
- }
457
- } catch (syncError) {
458
- // Skip sync silently if username is required (expected when offline onboarding)
459
- if (syncError?.code === 'USERNAME_REQUIRED' || syncError?.message === 'USERNAME_REQUIRED') {
460
- if (__DEV__) {
461
- loggerUtil.debug('Sync skipped - username required', {
462
- component: 'OxyContext',
463
- method: 'checkNetworkAndSync'
464
- }, syncError);
465
- }
466
- // Don't log or show error - username will be set later
467
- } else if (!isTimeoutOrNetworkError(syncError)) {
468
- // Only log unexpected errors - timeouts/network issues are expected when offline
469
- logger('Error syncing identity on reconnect', syncError);
470
- } else if (__DEV__) {
471
- loggerUtil.debug('Identity sync timeout (expected when offline)', {
472
- component: 'OxyContext',
473
- method: 'checkNetworkAndSync'
474
- }, syncError);
475
- }
476
- }
477
- }
478
-
250
+ logger('Network reconnected');
479
251
  // TanStack Query will automatically retry pending mutations
480
- // Reset flag immediately after processing (whether logged or not)
481
252
  wasOffline = false;
482
253
  }
483
254
  } catch (error) {
484
255
  // Network check failed - we're offline
485
- wasOffline = true;
486
-
487
- // Increment failure count and apply exponential backoff
488
- stateRef.consecutiveFailures++;
489
-
490
- // Calculate new interval with exponential backoff, capped at maxInterval
491
- const backoffMultiplier = Math.min(Math.pow(2, stateRef.consecutiveFailures - 1), stateRef.maxInterval / stateRef.baseInterval);
492
- stateRef.currentInterval = Math.min(stateRef.baseInterval * backoffMultiplier, stateRef.maxInterval);
493
-
494
- // If we hit the circuit breaker threshold, use max interval
495
- if (stateRef.consecutiveFailures >= stateRef.maxFailures) {
496
- stateRef.currentInterval = stateRef.maxInterval;
256
+ if (!wasOffline && __DEV__) {
257
+ logger('Network appears offline');
497
258
  }
259
+ wasOffline = true;
498
260
  } finally {
499
- // Always schedule next check (will use updated interval)
500
261
  scheduleNextCheck();
501
262
  }
502
263
  };
503
264
 
504
265
  // Check immediately
505
- checkNetworkAndSync();
266
+ checkNetworkStatus();
506
267
  return () => {
507
268
  if (checkTimeout) {
508
269
  clearTimeout(checkTimeout);
509
270
  }
510
271
  };
511
- }, [oxyServices, storage, syncIdentity, logger]);
272
+ }, [oxyServices, storage, logger]);
512
273
  const {
513
274
  getDeviceSessions,
514
275
  logoutAllDeviceSessions,
@@ -617,171 +378,6 @@ export const OxyProvider = ({
617
378
 
618
379
  // Get userId from JWT token (MongoDB ObjectId) for socket room matching
619
380
  const userId = oxyServices.getCurrentUserId();
620
-
621
- // Transfer code storage interface
622
-
623
- // Store transfer codes in memory for verification (also persisted to storage)
624
- // Map: transferId -> TransferCodeData
625
- const transferCodesRef = useRef(new Map());
626
- const activeTransferIdRef = useRef(null);
627
- const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
628
- const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
629
-
630
- // Clear stale transfer codes on startup
631
- // Transfers are ephemeral and should not persist across app restarts
632
- useEffect(() => {
633
- if (!storage || !isStorageReady) return;
634
- const clearStaleTransfers = async () => {
635
- try {
636
- // Clear any stored transfer codes - they're only valid during active transfer sessions
637
- await storage.removeItem(TRANSFER_CODES_STORAGE_KEY);
638
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
639
- if (__DEV__) {
640
- logger('Cleared stale transfer codes on startup');
641
- }
642
- } catch (error) {
643
- if (__DEV__) {
644
- logger('Failed to clear stale transfer codes', error);
645
- }
646
- }
647
- };
648
- clearStaleTransfers();
649
- }, [storage, isStorageReady, logger, storageKeyPrefix]);
650
-
651
- // Persist transfer codes to storage whenever they change
652
- const persistTransferCodes = useCallback(async () => {
653
- if (!storage) return;
654
- try {
655
- const codesToStore = {};
656
- transferCodesRef.current.forEach((value, key) => {
657
- codesToStore[key] = value;
658
- });
659
- await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(codesToStore));
660
- } catch (error) {
661
- if (__DEV__) {
662
- logger('Failed to persist transfer codes', error);
663
- }
664
- }
665
- }, [storage, logger]);
666
-
667
- // Cleanup old transfer codes (older than 15 minutes)
668
- useEffect(() => {
669
- const cleanup = setInterval(async () => {
670
- const now = Date.now();
671
- const fifteenMinutes = 15 * 60 * 1000;
672
- let needsPersist = false;
673
- transferCodesRef.current.forEach((value, key) => {
674
- if (now - value.timestamp > fifteenMinutes) {
675
- transferCodesRef.current.delete(key);
676
- needsPersist = true;
677
- if (__DEV__) {
678
- logger('Cleaned up expired transfer code', {
679
- transferId: key
680
- });
681
- }
682
- }
683
- });
684
-
685
- // Clear active transfer if it was deleted
686
- if (activeTransferIdRef.current && !transferCodesRef.current.has(activeTransferIdRef.current)) {
687
- activeTransferIdRef.current = null;
688
- if (storage) {
689
- try {
690
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
691
- } catch (error) {
692
- // Ignore storage errors
693
- }
694
- }
695
- }
696
- if (needsPersist) {
697
- await persistTransferCodes();
698
- }
699
- }, 60000); // Check every minute
700
-
701
- return () => clearInterval(cleanup);
702
- }, [logger, persistTransferCodes, storage]);
703
-
704
- // Transfer code management functions
705
- const storeTransferCode = useCallback(async (transferId, code, sourceDeviceId, publicKey) => {
706
- const transferData = {
707
- code,
708
- sourceDeviceId,
709
- publicKey,
710
- timestamp: Date.now(),
711
- state: 'pending'
712
- };
713
- transferCodesRef.current.set(transferId, transferData);
714
- activeTransferIdRef.current = transferId;
715
-
716
- // Persist to storage
717
- await persistTransferCodes();
718
- if (storage) {
719
- try {
720
- await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, transferId);
721
- } catch (error) {
722
- if (__DEV__) {
723
- logger('Failed to persist active transfer ID', error);
724
- }
725
- }
726
- }
727
- if (__DEV__) {
728
- logger('Stored transfer code', {
729
- transferId,
730
- sourceDeviceId,
731
- publicKey: publicKey.substring(0, 16) + '...'
732
- });
733
- }
734
- }, [logger, persistTransferCodes, storage]);
735
- const getTransferCode = useCallback(transferId => {
736
- return transferCodesRef.current.get(transferId) || null;
737
- }, []);
738
- const updateTransferState = useCallback(async (transferId, state) => {
739
- const transferData = transferCodesRef.current.get(transferId);
740
- if (transferData) {
741
- transferData.state = state;
742
- transferCodesRef.current.set(transferId, transferData);
743
-
744
- // Clear active transfer if completed or failed
745
- if (state === 'completed' || state === 'failed') {
746
- if (activeTransferIdRef.current === transferId) {
747
- activeTransferIdRef.current = null;
748
- if (storage) {
749
- try {
750
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
751
- } catch (error) {
752
- // Ignore storage errors
753
- }
754
- }
755
- }
756
- }
757
- await persistTransferCodes();
758
- if (__DEV__) {
759
- logger('Updated transfer state', {
760
- transferId,
761
- state
762
- });
763
- }
764
- }
765
- }, [logger, persistTransferCodes, storage]);
766
- const clearTransferCode = useCallback(async transferId => {
767
- transferCodesRef.current.delete(transferId);
768
- if (activeTransferIdRef.current === transferId) {
769
- activeTransferIdRef.current = null;
770
- if (storage) {
771
- try {
772
- await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
773
- } catch (error) {
774
- // Ignore storage errors
775
- }
776
- }
777
- }
778
- await persistTransferCodes();
779
- if (__DEV__) {
780
- logger('Cleared transfer code', {
781
- transferId
782
- });
783
- }
784
- }, [logger, persistTransferCodes, storage]);
785
381
  const refreshSessionsWithUser = useCallback(() => refreshSessions(userId || undefined), [refreshSessions, userId]);
786
382
  const handleSessionRemoved = useCallback(sessionId => {
787
383
  trackRemovedSession(sessionId);
@@ -790,143 +386,6 @@ export const OxyProvider = ({
790
386
  toast.info('You have been signed out remotely.');
791
387
  logout().catch(remoteError => logger('Failed to process remote sign out', remoteError));
792
388
  }, [logger, logout]);
793
- const handleIdentityTransferComplete = useCallback(async data => {
794
- try {
795
- logger('Received identity transfer complete notification', {
796
- transferId: data.transferId,
797
- sourceDeviceId: data.sourceDeviceId,
798
- currentDeviceId,
799
- hasActiveSession: activeSessionId !== null,
800
- publicKey: data.publicKey.substring(0, 16) + '...'
801
- });
802
- const storedTransfer = getTransferCode(data.transferId);
803
- if (!storedTransfer) {
804
- logger('Transfer code not found for transferId', {
805
- transferId: data.transferId
806
- });
807
- toast.error('Transfer verification failed: Code not found. Identity will not be deleted.');
808
- return;
809
- }
810
-
811
- // Verify publicKey matches first (most important check)
812
- const publicKeyMatches = data.publicKey === storedTransfer.publicKey;
813
- if (!publicKeyMatches) {
814
- logger('Public key mismatch for transfer', {
815
- transferId: data.transferId,
816
- receivedPublicKey: data.publicKey.substring(0, 16) + '...',
817
- storedPublicKey: storedTransfer.publicKey.substring(0, 16) + '...'
818
- });
819
- toast.error('Transfer verification failed: Public key mismatch. Identity will not be deleted.');
820
- return;
821
- }
822
-
823
- // Verify deviceId matches - very lenient since publicKey is the critical check
824
- // If publicKey matches, we allow deletion even if deviceId doesn't match exactly
825
- // This handles cases where deviceId might not be available or slightly different
826
- const deviceIdMatches =
827
- // Exact match
828
- data.sourceDeviceId && data.sourceDeviceId === currentDeviceId ||
829
- // Stored sourceDeviceId matches current deviceId
830
- storedTransfer.sourceDeviceId && storedTransfer.sourceDeviceId === currentDeviceId;
831
-
832
- // If publicKey matches, we're very lenient with deviceId - only warn but don't block
833
- if (!deviceIdMatches && publicKeyMatches) {
834
- logger('Device ID mismatch for transfer, but publicKey matches - proceeding with deletion', {
835
- transferId: data.transferId,
836
- receivedDeviceId: data.sourceDeviceId,
837
- storedDeviceId: storedTransfer.sourceDeviceId,
838
- currentDeviceId,
839
- hasActiveSession: activeSessionId !== null
840
- });
841
- // Proceed with deletion - publicKey match is the critical verification
842
- } else if (!deviceIdMatches && !publicKeyMatches) {
843
- // Both don't match - this is suspicious, block deletion
844
- logger('Device ID and publicKey mismatch for transfer', {
845
- transferId: data.transferId,
846
- receivedDeviceId: data.sourceDeviceId,
847
- currentDeviceId
848
- });
849
- toast.error('Transfer verification failed: Device and key mismatch. Identity will not be deleted.');
850
- return;
851
- }
852
-
853
- // Verify transfer code matches (if provided)
854
- // Transfer code is optional - if not provided, we still proceed if publicKey matches
855
- if (data.transferCode) {
856
- const codeMatches = data.transferCode.toUpperCase() === storedTransfer.code.toUpperCase();
857
- if (!codeMatches) {
858
- logger('Transfer code mismatch, but publicKey matches - proceeding with deletion', {
859
- transferId: data.transferId,
860
- receivedCode: data.transferCode,
861
- storedCode: storedTransfer.code.substring(0, 2) + '****'
862
- });
863
- // Don't block - publicKey match is sufficient, code mismatch might be due to user error
864
- // Log warning but proceed
865
- }
866
- }
867
-
868
- // Check if transfer is too old (safety timeout - 10 minutes)
869
- const transferAge = Date.now() - storedTransfer.timestamp;
870
- const tenMinutes = 10 * 60 * 1000;
871
- if (transferAge > tenMinutes) {
872
- logger('Transfer confirmation received too late', {
873
- transferId: data.transferId,
874
- age: transferAge,
875
- ageMinutes: Math.round(transferAge / 60000)
876
- });
877
- toast.error('Transfer verification failed: Confirmation received too late. Identity will not be deleted.');
878
- clearTransferCode(data.transferId);
879
- return;
880
- }
881
-
882
- // NOTE: Target device verification already happened server-side when notifyTransferComplete was called
883
- // The server verified that the target device is authenticated and has the matching public key
884
- // Additional client-side verification is not necessary and would require source device authentication
885
- // which may not be available. The existing checks (public key match, transfer code, device ID) are sufficient.
886
-
887
- logger('All transfer verifications passed, deleting identity from source device', {
888
- transferId: data.transferId,
889
- sourceDeviceId: data.sourceDeviceId,
890
- publicKey: data.publicKey.substring(0, 16) + '...'
891
- });
892
- try {
893
- // Verify identity still exists before deletion (safety check)
894
- const identityStillExists = await KeyManager.hasIdentity();
895
- if (!identityStillExists) {
896
- logger('Identity already deleted - skipping deletion', {
897
- transferId: data.transferId
898
- });
899
- await updateTransferState(data.transferId, 'completed');
900
- await clearTransferCode(data.transferId);
901
- return;
902
- }
903
- await deleteIdentityAndClearAccount(false, false, true);
904
-
905
- // Verify identity was actually deleted
906
- const identityDeleted = !(await KeyManager.hasIdentity());
907
- if (!identityDeleted) {
908
- logger('Identity deletion failed - identity still exists', {
909
- transferId: data.transferId
910
- });
911
- await updateTransferState(data.transferId, 'failed');
912
- throw new Error('Identity deletion failed - identity still exists');
913
- }
914
- await updateTransferState(data.transferId, 'completed');
915
- await clearTransferCode(data.transferId);
916
- logger('Identity successfully deleted and transfer code cleared', {
917
- transferId: data.transferId
918
- });
919
- toast.success('Identity successfully transferred and removed from this device');
920
- } catch (deleteError) {
921
- logger('Error during identity deletion', deleteError);
922
- await updateTransferState(data.transferId, 'failed');
923
- throw deleteError;
924
- }
925
- } catch (error) {
926
- logger('Failed to delete identity after transfer', error);
927
- toast.error(error?.message || 'Failed to remove identity from this device. Please try again manually from Security Settings.');
928
- }
929
- }, [deleteIdentityAndClearAccount, logger, getTransferCode, clearTransferCode, updateTransferState, currentDeviceId, activeSessionId, oxyServices]);
930
389
  useSessionSocket({
931
390
  userId,
932
391
  activeSessionId,
@@ -936,10 +395,8 @@ export const OxyProvider = ({
936
395
  clearSessionState,
937
396
  baseURL: oxyServices.getBaseURL(),
938
397
  getAccessToken: () => oxyServices.getAccessToken(),
939
- getTransferCode: getTransferCode,
940
398
  onRemoteSignOut: handleRemoteSignOut,
941
- onSessionRemoved: handleSessionRemoved,
942
- onIdentityTransferComplete: handleIdentityTransferComplete
399
+ onSessionRemoved: handleSessionRemoved
943
400
  });
944
401
  const switchSessionForContext = useCallback(async sessionId => {
945
402
  await switchSession(sessionId);
@@ -973,7 +430,6 @@ export const OxyProvider = ({
973
430
  await updateProfileWithAvatar({
974
431
  avatar: file.id
975
432
  }, oxyServices, activeSessionId, queryClient, {
976
- syncIdentity,
977
433
  deviceId: currentDeviceId || undefined
978
434
  });
979
435
  toast.success(translate(currentLanguage, 'editProfile.toasts.avatarUpdated') || 'Avatar updated');
@@ -983,7 +439,7 @@ export const OxyProvider = ({
983
439
  }
984
440
  }
985
441
  });
986
- }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient, syncIdentity]);
442
+ }, [oxyServices, currentLanguage, showBottomSheetForContext, activeSessionId, queryClient]);
987
443
  const contextValue = useMemo(() => ({
988
444
  user,
989
445
  sessions,
@@ -998,24 +454,7 @@ export const OxyProvider = ({
998
454
  currentLanguageMetadata,
999
455
  currentLanguageName,
1000
456
  currentNativeLanguageName,
1001
- createIdentity: wrappedCreateIdentity,
1002
- importIdentity,
1003
457
  signIn,
1004
- hasIdentity,
1005
- getPublicKey,
1006
- isIdentitySynced,
1007
- syncIdentity,
1008
- deleteIdentityAndClearAccount,
1009
- storeTransferCode,
1010
- getTransferCode,
1011
- clearTransferCode,
1012
- getAllPendingTransfers,
1013
- getActiveTransferId,
1014
- updateTransferState,
1015
- identitySyncState: {
1016
- isSynced: isIdentitySyncedStore ?? true,
1017
- isSyncing: isSyncing ?? false
1018
- },
1019
458
  logout,
1020
459
  logoutAll,
1021
460
  switchSession: switchSessionForContext,
@@ -1031,7 +470,7 @@ export const OxyProvider = ({
1031
470
  useFollow: useFollowHook,
1032
471
  showBottomSheet: showBottomSheetForContext,
1033
472
  openAvatarPicker
1034
- }), [activeSessionId, currentDeviceId, wrappedCreateIdentity, 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]);
1035
474
  return /*#__PURE__*/_jsx(OxyContext.Provider, {
1036
475
  value: contextValue,
1037
476
  children: children