@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.
- package/lib/commonjs/core/mixins/OxyServices.user.js +14 -4
- package/lib/commonjs/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/commonjs/ui/context/OxyContext.js +280 -84
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js +7 -6
- package/lib/commonjs/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js +4 -3
- package/lib/commonjs/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/commonjs/ui/hooks/useSessionSocket.js +349 -328
- package/lib/commonjs/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js +13 -6
- package/lib/commonjs/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/module/core/mixins/OxyServices.user.js +14 -4
- package/lib/module/core/mixins/OxyServices.user.js.map +1 -1
- package/lib/module/ui/context/OxyContext.js +280 -84
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/mutations/useAccountMutations.js +7 -6
- package/lib/module/ui/hooks/mutations/useAccountMutations.js.map +1 -1
- package/lib/module/ui/hooks/queries/useAccountQueries.js +4 -3
- package/lib/module/ui/hooks/queries/useAccountQueries.js.map +1 -1
- package/lib/module/ui/hooks/useSessionSocket.js +349 -328
- package/lib/module/ui/hooks/useSessionSocket.js.map +1 -1
- package/lib/module/ui/screens/PrivacySettingsScreen.js +13 -6
- package/lib/module/ui/screens/PrivacySettingsScreen.js.map +1 -1
- package/lib/typescript/core/mixins/OxyServices.user.d.ts +2 -2
- package/lib/typescript/core/mixins/OxyServices.user.d.ts.map +1 -1
- package/lib/typescript/ui/context/OxyContext.d.ts +15 -2
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/mutations/useAccountMutations.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/queries/useAccountQueries.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useSessionSocket.d.ts.map +1 -1
- package/lib/typescript/ui/screens/PrivacySettingsScreen.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/core/mixins/OxyServices.user.ts +14 -4
- package/src/ui/context/OxyContext.tsx +310 -86
- package/src/ui/hooks/mutations/useAccountMutations.ts +8 -6
- package/src/ui/hooks/queries/useAccountQueries.ts +4 -2
- package/src/ui/hooks/useSessionSocket.ts +153 -155
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
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
|
-
//
|
|
517
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
695
|
-
|
|
696
|
-
|
|
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
|
-
|
|
699
|
-
|
|
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
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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
|
-
//
|
|
714
|
-
|
|
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
|
-
|
|
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
|