@oxyhq/services 5.16.26 → 5.16.27
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/ui/context/OxyContext.js +88 -196
- package/lib/commonjs/ui/context/OxyContext.js.map +1 -1
- package/lib/commonjs/ui/hooks/useTransferQueries.js +137 -0
- package/lib/commonjs/ui/hooks/useTransferQueries.js.map +1 -0
- package/lib/commonjs/ui/stores/transferStore.js +161 -0
- package/lib/commonjs/ui/stores/transferStore.js.map +1 -0
- package/lib/module/ui/context/OxyContext.js +88 -196
- package/lib/module/ui/context/OxyContext.js.map +1 -1
- package/lib/module/ui/hooks/useTransferQueries.js +132 -0
- package/lib/module/ui/hooks/useTransferQueries.js.map +1 -0
- package/lib/module/ui/stores/transferStore.js +155 -0
- package/lib/module/ui/stores/transferStore.js.map +1 -0
- package/lib/typescript/ui/context/OxyContext.d.ts.map +1 -1
- package/lib/typescript/ui/hooks/useTransferQueries.d.ts +36 -0
- package/lib/typescript/ui/hooks/useTransferQueries.d.ts.map +1 -0
- package/lib/typescript/ui/stores/transferStore.d.ts +39 -0
- package/lib/typescript/ui/stores/transferStore.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ui/context/OxyContext.tsx +86 -205
- package/src/ui/hooks/useTransferQueries.ts +154 -0
- package/src/ui/stores/transferStore.ts +201 -0
|
@@ -34,6 +34,8 @@ import { translate } from '../../i18n';
|
|
|
34
34
|
import { updateAvatarVisibility, updateProfileWithAvatar } from '../utils/avatarUtils';
|
|
35
35
|
import { useAccountStore } from '../stores/accountStore';
|
|
36
36
|
import { logger as loggerUtil } from '../../utils/loggerUtils';
|
|
37
|
+
import { useTransferStore, useTransferCodesForPersistence } from '../stores/transferStore';
|
|
38
|
+
import { useCheckPendingTransfers } from '../hooks/useTransferQueries';
|
|
37
39
|
|
|
38
40
|
export interface OxyContextState {
|
|
39
41
|
user: User | null;
|
|
@@ -387,20 +389,22 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
387
389
|
oxyServices.clearCache();
|
|
388
390
|
}, [queryClient, storage, clearSessionState, logger, oxyServices]);
|
|
389
391
|
|
|
392
|
+
// Extract Zustand store functions early (before they're used in callbacks)
|
|
393
|
+
const getAllPendingTransfersStore = useTransferStore((state) => state.getAllPendingTransfers);
|
|
394
|
+
const getActiveTransferIdStore = useTransferStore((state) => state.getActiveTransferId);
|
|
395
|
+
const storeTransferCodeStore = useTransferStore((state) => state.storeTransferCode);
|
|
396
|
+
const getTransferCodeStore = useTransferStore((state) => state.getTransferCode);
|
|
397
|
+
const updateTransferStateStore = useTransferStore((state) => state.updateTransferState);
|
|
398
|
+
const clearTransferCodeStore = useTransferStore((state) => state.clearTransferCode);
|
|
399
|
+
|
|
390
400
|
// Transfer code management functions (must be defined before deleteIdentityAndClearAccount)
|
|
391
401
|
const getAllPendingTransfers = useCallback(() => {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (data.state === 'pending') {
|
|
395
|
-
pending.push({ transferId, data });
|
|
396
|
-
}
|
|
397
|
-
});
|
|
398
|
-
return pending;
|
|
399
|
-
}, []);
|
|
402
|
+
return getAllPendingTransfersStore();
|
|
403
|
+
}, [getAllPendingTransfersStore]);
|
|
400
404
|
|
|
401
405
|
const getActiveTransferId = useCallback(() => {
|
|
402
|
-
return
|
|
403
|
-
}, []);
|
|
406
|
+
return getActiveTransferIdStore();
|
|
407
|
+
}, [getActiveTransferIdStore]);
|
|
404
408
|
|
|
405
409
|
// Delete identity and clear all account data
|
|
406
410
|
// In accounts app, deleting identity means losing the account completely
|
|
@@ -415,7 +419,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
415
419
|
const pendingTransfers = getAllPendingTransfers();
|
|
416
420
|
if (pendingTransfers.length > 0) {
|
|
417
421
|
const activeTransferId = getActiveTransferId();
|
|
418
|
-
const hasActiveTransfer = activeTransferId && pendingTransfers.some(t => t.transferId === activeTransferId);
|
|
422
|
+
const hasActiveTransfer = activeTransferId && pendingTransfers.some((t: { transferId: string; data: any }) => t.transferId === activeTransferId);
|
|
419
423
|
|
|
420
424
|
if (hasActiveTransfer) {
|
|
421
425
|
throw new Error(
|
|
@@ -510,6 +514,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
510
514
|
loggerUtil.debug('Identity sync timeout (expected when offline)', { component: 'OxyContext', method: 'checkNetworkAndSync' }, syncError as unknown);
|
|
511
515
|
}
|
|
512
516
|
}
|
|
517
|
+
|
|
518
|
+
// Check for pending transfers that may have completed while offline
|
|
519
|
+
// This is handled by useCheckPendingTransfers hook which runs automatically
|
|
520
|
+
// when authenticated and online
|
|
513
521
|
}
|
|
514
522
|
|
|
515
523
|
// TanStack Query will automatically retry pending mutations
|
|
@@ -551,7 +559,7 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
551
559
|
clearTimeout(checkTimeout);
|
|
552
560
|
}
|
|
553
561
|
};
|
|
554
|
-
}, [oxyServices, storage, syncIdentity, logger]);
|
|
562
|
+
}, [oxyServices, storage, syncIdentity, logger, hasIdentity, isAuthenticated]);
|
|
555
563
|
|
|
556
564
|
const { getDeviceSessions, logoutAllDeviceSessions, updateDeviceName } = useDeviceManagement({
|
|
557
565
|
oxyServices,
|
|
@@ -670,207 +678,110 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
670
678
|
// The JWT token's userId field contains the MongoDB ObjectId
|
|
671
679
|
const userId = oxyServices.getCurrentUserId() || user?.id;
|
|
672
680
|
|
|
673
|
-
//
|
|
674
|
-
interface TransferCodeData {
|
|
675
|
-
code: string;
|
|
676
|
-
sourceDeviceId: string | null;
|
|
677
|
-
publicKey: string;
|
|
678
|
-
timestamp: number;
|
|
679
|
-
state: 'pending' | 'completed' | 'failed';
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Store transfer codes in memory for verification (also persisted to storage)
|
|
683
|
-
// Map: transferId -> TransferCodeData
|
|
684
|
-
const transferCodesRef = useRef<Map<string, TransferCodeData>>(new Map());
|
|
685
|
-
const activeTransferIdRef = useRef<string | null>(null);
|
|
681
|
+
// Use Zustand store for transfer state management
|
|
686
682
|
const TRANSFER_CODES_STORAGE_KEY = `${storageKeyPrefix}_transfer_codes`;
|
|
687
683
|
const ACTIVE_TRANSFER_STORAGE_KEY = `${storageKeyPrefix}_active_transfer_id`;
|
|
684
|
+
const isRestored = useTransferStore((state) => state.isRestored);
|
|
685
|
+
const restoreFromStorage = useTransferStore((state) => state.restoreFromStorage);
|
|
686
|
+
const markRestored = useTransferStore((state) => state.markRestored);
|
|
687
|
+
const cleanupExpired = useTransferStore((state) => state.cleanupExpired);
|
|
688
688
|
|
|
689
|
-
// Load transfer codes from storage on startup
|
|
689
|
+
// Load transfer codes from storage on startup (only once)
|
|
690
690
|
useEffect(() => {
|
|
691
|
-
if (!storage || !isStorageReady) return;
|
|
691
|
+
if (!storage || !isStorageReady || isRestored) return;
|
|
692
692
|
|
|
693
693
|
const loadTransferCodes = async () => {
|
|
694
694
|
try {
|
|
695
695
|
// Load transfer codes
|
|
696
696
|
const storedCodes = await storage.getItem(TRANSFER_CODES_STORAGE_KEY);
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
697
|
+
const storedActiveTransferId = await storage.getItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
698
|
+
|
|
699
|
+
const parsedCodes = storedCodes ? JSON.parse(storedCodes) : {};
|
|
700
|
+
const activeTransferId = storedActiveTransferId || null;
|
|
701
|
+
|
|
702
|
+
// Restore to Zustand store (store handles validation and expiration)
|
|
703
|
+
restoreFromStorage(parsedCodes, activeTransferId);
|
|
704
|
+
markRestored();
|
|
705
|
+
|
|
706
|
+
if (__DEV__ && Object.keys(parsedCodes).length > 0) {
|
|
707
|
+
logger('Restored transfer codes from storage', {
|
|
708
|
+
count: Object.keys(parsedCodes).length,
|
|
709
|
+
hasActiveTransfer: !!activeTransferId,
|
|
710
710
|
});
|
|
711
711
|
}
|
|
712
|
-
|
|
713
|
-
// Load active transfer ID
|
|
714
|
-
const activeTransferId = await storage.getItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
715
|
-
if (activeTransferId) {
|
|
716
|
-
// Verify it's still valid
|
|
717
|
-
const transferData = transferCodesRef.current.get(activeTransferId);
|
|
718
|
-
if (transferData && transferData.state === 'pending') {
|
|
719
|
-
activeTransferIdRef.current = activeTransferId;
|
|
720
|
-
if (__DEV__) {
|
|
721
|
-
logger('Restored active transfer ID from storage', { transferId: activeTransferId });
|
|
722
|
-
}
|
|
723
|
-
} else {
|
|
724
|
-
// Clear invalid active transfer
|
|
725
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
712
|
} catch (error) {
|
|
729
713
|
if (__DEV__) {
|
|
730
714
|
logger('Failed to load transfer codes from storage', error);
|
|
731
715
|
}
|
|
716
|
+
// Mark as restored even on error to prevent retries
|
|
717
|
+
markRestored();
|
|
732
718
|
}
|
|
733
719
|
};
|
|
734
720
|
|
|
735
721
|
loadTransferCodes();
|
|
736
|
-
}, [storage, isStorageReady, logger, storageKeyPrefix]);
|
|
722
|
+
}, [storage, isStorageReady, isRestored, restoreFromStorage, markRestored, logger, storageKeyPrefix]);
|
|
737
723
|
|
|
738
|
-
// Persist transfer codes to storage whenever
|
|
739
|
-
const
|
|
740
|
-
if (!storage) return;
|
|
741
|
-
|
|
742
|
-
try {
|
|
743
|
-
const codesToStore: Record<string, TransferCodeData> = {};
|
|
744
|
-
transferCodesRef.current.forEach((value, key) => {
|
|
745
|
-
codesToStore[key] = value;
|
|
746
|
-
});
|
|
747
|
-
await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(codesToStore));
|
|
748
|
-
} catch (error) {
|
|
749
|
-
if (__DEV__) {
|
|
750
|
-
logger('Failed to persist transfer codes', error);
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
}, [storage, logger]);
|
|
754
|
-
|
|
755
|
-
// Cleanup old transfer codes (older than 15 minutes)
|
|
724
|
+
// Persist transfer codes to storage whenever store changes
|
|
725
|
+
const { transferCodes, activeTransferId } = useTransferCodesForPersistence();
|
|
756
726
|
useEffect(() => {
|
|
757
|
-
|
|
758
|
-
const now = Date.now();
|
|
759
|
-
const fifteenMinutes = 15 * 60 * 1000;
|
|
760
|
-
let needsPersist = false;
|
|
761
|
-
|
|
762
|
-
transferCodesRef.current.forEach((value, key) => {
|
|
763
|
-
if (now - value.timestamp > fifteenMinutes) {
|
|
764
|
-
transferCodesRef.current.delete(key);
|
|
765
|
-
needsPersist = true;
|
|
766
|
-
if (__DEV__) {
|
|
767
|
-
logger('Cleaned up expired transfer code', { transferId: key });
|
|
768
|
-
}
|
|
769
|
-
}
|
|
770
|
-
});
|
|
727
|
+
if (!storage || !isStorageReady || !isRestored) return;
|
|
771
728
|
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
729
|
+
const persistTransferCodes = async () => {
|
|
730
|
+
try {
|
|
731
|
+
await storage.setItem(TRANSFER_CODES_STORAGE_KEY, JSON.stringify(transferCodes));
|
|
732
|
+
|
|
733
|
+
if (activeTransferId) {
|
|
734
|
+
await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, activeTransferId);
|
|
735
|
+
} else {
|
|
736
|
+
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
737
|
+
}
|
|
738
|
+
} catch (error) {
|
|
739
|
+
if (__DEV__) {
|
|
740
|
+
logger('Failed to persist transfer codes', error);
|
|
781
741
|
}
|
|
782
742
|
}
|
|
743
|
+
};
|
|
783
744
|
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
745
|
+
persistTransferCodes();
|
|
746
|
+
}, [transferCodes, activeTransferId, storage, isStorageReady, isRestored, logger]);
|
|
747
|
+
|
|
748
|
+
// Cleanup expired transfer codes (every minute)
|
|
749
|
+
useEffect(() => {
|
|
750
|
+
const cleanup = setInterval(() => {
|
|
751
|
+
cleanupExpired();
|
|
787
752
|
}, 60000); // Check every minute
|
|
788
753
|
|
|
789
754
|
return () => clearInterval(cleanup);
|
|
790
|
-
}, [
|
|
755
|
+
}, [cleanupExpired]);
|
|
791
756
|
|
|
792
|
-
// Transfer code management functions
|
|
757
|
+
// Transfer code management functions using Zustand store
|
|
793
758
|
const storeTransferCode = useCallback(async (transferId: string, code: string, sourceDeviceId: string | null, publicKey: string) => {
|
|
794
|
-
|
|
795
|
-
code,
|
|
796
|
-
sourceDeviceId,
|
|
797
|
-
publicKey,
|
|
798
|
-
timestamp: Date.now(),
|
|
799
|
-
state: 'pending',
|
|
800
|
-
};
|
|
801
|
-
|
|
802
|
-
transferCodesRef.current.set(transferId, transferData);
|
|
803
|
-
activeTransferIdRef.current = transferId;
|
|
804
|
-
|
|
805
|
-
// Persist to storage
|
|
806
|
-
await persistTransferCodes();
|
|
807
|
-
if (storage) {
|
|
808
|
-
try {
|
|
809
|
-
await storage.setItem(ACTIVE_TRANSFER_STORAGE_KEY, transferId);
|
|
810
|
-
} catch (error) {
|
|
811
|
-
if (__DEV__) {
|
|
812
|
-
logger('Failed to persist active transfer ID', error);
|
|
813
|
-
}
|
|
814
|
-
}
|
|
815
|
-
}
|
|
759
|
+
storeTransferCodeStore(transferId, code, sourceDeviceId, publicKey);
|
|
816
760
|
|
|
817
761
|
if (__DEV__) {
|
|
818
762
|
logger('Stored transfer code', { transferId, sourceDeviceId, publicKey: publicKey.substring(0, 16) + '...' });
|
|
819
763
|
}
|
|
820
|
-
}, [logger,
|
|
764
|
+
}, [logger, storeTransferCodeStore]);
|
|
821
765
|
|
|
822
766
|
const getTransferCode = useCallback((transferId: string) => {
|
|
823
|
-
return
|
|
824
|
-
}, []);
|
|
767
|
+
return getTransferCodeStore(transferId);
|
|
768
|
+
}, [getTransferCodeStore]);
|
|
825
769
|
|
|
826
770
|
const updateTransferState = useCallback(async (transferId: string, state: 'pending' | 'completed' | 'failed') => {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
// Clear active transfer if completed or failed
|
|
833
|
-
if (state === 'completed' || state === 'failed') {
|
|
834
|
-
if (activeTransferIdRef.current === transferId) {
|
|
835
|
-
activeTransferIdRef.current = null;
|
|
836
|
-
if (storage) {
|
|
837
|
-
try {
|
|
838
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
839
|
-
} catch (error) {
|
|
840
|
-
// Ignore storage errors
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
}
|
|
845
|
-
|
|
846
|
-
await persistTransferCodes();
|
|
847
|
-
|
|
848
|
-
if (__DEV__) {
|
|
849
|
-
logger('Updated transfer state', { transferId, state });
|
|
850
|
-
}
|
|
771
|
+
updateTransferStateStore(transferId, state);
|
|
772
|
+
|
|
773
|
+
if (__DEV__) {
|
|
774
|
+
logger('Updated transfer state', { transferId, state });
|
|
851
775
|
}
|
|
852
|
-
}, [logger,
|
|
776
|
+
}, [logger, updateTransferStateStore]);
|
|
853
777
|
|
|
854
778
|
const clearTransferCode = useCallback(async (transferId: string) => {
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
if (activeTransferIdRef.current === transferId) {
|
|
858
|
-
activeTransferIdRef.current = null;
|
|
859
|
-
if (storage) {
|
|
860
|
-
try {
|
|
861
|
-
await storage.removeItem(ACTIVE_TRANSFER_STORAGE_KEY);
|
|
862
|
-
} catch (error) {
|
|
863
|
-
// Ignore storage errors
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
await persistTransferCodes();
|
|
779
|
+
clearTransferCodeStore(transferId);
|
|
869
780
|
|
|
870
781
|
if (__DEV__) {
|
|
871
782
|
logger('Cleared transfer code', { transferId });
|
|
872
783
|
}
|
|
873
|
-
}, [logger,
|
|
784
|
+
}, [logger, clearTransferCodeStore]);
|
|
874
785
|
|
|
875
786
|
const refreshSessionsWithUser = useCallback(
|
|
876
787
|
() => refreshSessions(userId),
|
|
@@ -889,6 +800,9 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
889
800
|
logout().catch((remoteError) => logger('Failed to process remote sign out', remoteError));
|
|
890
801
|
}, [logger, logout]);
|
|
891
802
|
|
|
803
|
+
// Check pending transfers when authenticated and online using TanStack Query
|
|
804
|
+
const { data: pendingTransferResults } = useCheckPendingTransfers();
|
|
805
|
+
|
|
892
806
|
const handleIdentityTransferComplete = useCallback(
|
|
893
807
|
async (data: { transferId: string; sourceDeviceId: string; publicKey: string; transferCode?: string; completedAt: string }) => {
|
|
894
808
|
try {
|
|
@@ -981,43 +895,10 @@ export const OxyProvider: React.FC<OxyContextProviderProps> = ({
|
|
|
981
895
|
return;
|
|
982
896
|
}
|
|
983
897
|
|
|
984
|
-
//
|
|
985
|
-
//
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
transferId: data.transferId,
|
|
989
|
-
publicKey: data.publicKey.substring(0, 16) + '...',
|
|
990
|
-
});
|
|
991
|
-
|
|
992
|
-
// Verify target device has active session with matching public key
|
|
993
|
-
const verifyResponse = await oxyServices.makeRequest<{ verified: boolean; hasActiveSession: boolean }>(
|
|
994
|
-
'GET',
|
|
995
|
-
`/api/identity/verify-transfer?publicKey=${encodeURIComponent(data.publicKey)}`,
|
|
996
|
-
undefined,
|
|
997
|
-
{ cache: false }
|
|
998
|
-
);
|
|
999
|
-
|
|
1000
|
-
if (!verifyResponse.verified || !verifyResponse.hasActiveSession) {
|
|
1001
|
-
logger('Target device verification failed - identity will not be deleted', {
|
|
1002
|
-
transferId: data.transferId,
|
|
1003
|
-
verified: verifyResponse.verified,
|
|
1004
|
-
hasActiveSession: verifyResponse.hasActiveSession,
|
|
1005
|
-
});
|
|
1006
|
-
await updateTransferState(data.transferId, 'failed');
|
|
1007
|
-
toast.error('Transfer verification failed: Target device does not have active session. Identity will not be deleted.');
|
|
1008
|
-
return;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
logger('Target device verification passed', {
|
|
1012
|
-
transferId: data.transferId,
|
|
1013
|
-
});
|
|
1014
|
-
} catch (verifyError: any) {
|
|
1015
|
-
// If verification fails, don't delete identity - it's safer to keep it
|
|
1016
|
-
logger('Failed to verify target device - identity will not be deleted', verifyError);
|
|
1017
|
-
await updateTransferState(data.transferId, 'failed');
|
|
1018
|
-
toast.error('Transfer verification failed: Could not verify target device. Identity will not be deleted.');
|
|
1019
|
-
return;
|
|
1020
|
-
}
|
|
898
|
+
// NOTE: Target device verification already happened server-side when notifyTransferComplete was called
|
|
899
|
+
// The server verified that the target device is authenticated and has the matching public key
|
|
900
|
+
// Additional client-side verification is not necessary and would require source device authentication
|
|
901
|
+
// which may not be available. The existing checks (public key match, transfer code, device ID) are sufficient.
|
|
1021
902
|
|
|
1022
903
|
logger('All transfer verifications passed, deleting identity from source device', {
|
|
1023
904
|
transferId: data.transferId,
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
|
|
2
|
+
import { useOxy } from '../context/OxyContext';
|
|
3
|
+
import { useTransferStore } from '../stores/transferStore';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Query keys for transfer-related queries
|
|
7
|
+
*/
|
|
8
|
+
export const transferQueryKeys = {
|
|
9
|
+
all: ['transfers'] as const,
|
|
10
|
+
completion: (transferId: string) => ['transfers', 'completion', transferId] as const,
|
|
11
|
+
pending: () => ['transfers', 'pending'] as const,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Hook to check if a transfer was completed
|
|
16
|
+
* Only runs when authenticated and transferId is provided
|
|
17
|
+
*/
|
|
18
|
+
export const useCheckTransferCompletion = (transferId: string | null, enabled: boolean = true) => {
|
|
19
|
+
const { oxyServices, isAuthenticated } = useOxy();
|
|
20
|
+
|
|
21
|
+
return useQuery({
|
|
22
|
+
queryKey: transferId ? transferQueryKeys.completion(transferId) : ['transfers', 'completion', 'null'],
|
|
23
|
+
queryFn: async () => {
|
|
24
|
+
if (!transferId || !oxyServices) {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const response = await oxyServices.makeRequest<{
|
|
30
|
+
completed: boolean;
|
|
31
|
+
transferId?: string;
|
|
32
|
+
sourceDeviceId?: string;
|
|
33
|
+
publicKey?: string;
|
|
34
|
+
transferCode?: string;
|
|
35
|
+
completedAt?: string;
|
|
36
|
+
}>(
|
|
37
|
+
'GET',
|
|
38
|
+
`/api/identity/check-transfer/${transferId}`,
|
|
39
|
+
undefined,
|
|
40
|
+
{ cache: false }
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
return response;
|
|
44
|
+
} catch (error: any) {
|
|
45
|
+
// Handle 401 errors gracefully - don't throw, just return null
|
|
46
|
+
if (error?.status === 401 || error?.message?.includes('401') || error?.message?.includes('authentication')) {
|
|
47
|
+
if (__DEV__) {
|
|
48
|
+
console.warn('[useCheckTransferCompletion] Authentication required, skipping check');
|
|
49
|
+
}
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
throw error;
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
enabled: enabled && !!transferId && isAuthenticated && !!oxyServices,
|
|
56
|
+
staleTime: 30 * 1000, // 30 seconds - completion status doesn't change frequently
|
|
57
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
58
|
+
retry: (failureCount, error: any) => {
|
|
59
|
+
// Don't retry on 401 errors
|
|
60
|
+
if (error?.status === 401 || error?.message?.includes('401')) {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return failureCount < 2;
|
|
64
|
+
},
|
|
65
|
+
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 5000),
|
|
66
|
+
});
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hook to check all pending transfers for completion
|
|
71
|
+
* Used when app comes back online
|
|
72
|
+
*/
|
|
73
|
+
export const useCheckPendingTransfers = () => {
|
|
74
|
+
const { oxyServices, isAuthenticated } = useOxy();
|
|
75
|
+
const getAllPendingTransfers = useTransferStore((state) => state.getAllPendingTransfers);
|
|
76
|
+
const pendingTransfers = getAllPendingTransfers();
|
|
77
|
+
|
|
78
|
+
return useQuery({
|
|
79
|
+
queryKey: [...transferQueryKeys.pending(), pendingTransfers.map(t => t.transferId).join(',')],
|
|
80
|
+
queryFn: async () => {
|
|
81
|
+
if (!oxyServices || pendingTransfers.length === 0) {
|
|
82
|
+
return [];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const results: Array<{
|
|
86
|
+
transferId: string;
|
|
87
|
+
completed: boolean;
|
|
88
|
+
data?: {
|
|
89
|
+
transferId?: string;
|
|
90
|
+
sourceDeviceId?: string;
|
|
91
|
+
publicKey?: string;
|
|
92
|
+
transferCode?: string;
|
|
93
|
+
completedAt?: string;
|
|
94
|
+
};
|
|
95
|
+
}> = [];
|
|
96
|
+
|
|
97
|
+
// Check each pending transfer
|
|
98
|
+
for (const { transferId, data } of pendingTransfers) {
|
|
99
|
+
try {
|
|
100
|
+
const response = await oxyServices.makeRequest<{
|
|
101
|
+
completed: boolean;
|
|
102
|
+
transferId?: string;
|
|
103
|
+
sourceDeviceId?: string;
|
|
104
|
+
publicKey?: string;
|
|
105
|
+
transferCode?: string;
|
|
106
|
+
completedAt?: string;
|
|
107
|
+
}>(
|
|
108
|
+
'GET',
|
|
109
|
+
`/api/identity/check-transfer/${transferId}`,
|
|
110
|
+
undefined,
|
|
111
|
+
{ cache: false }
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (response.completed && response.publicKey === data.publicKey) {
|
|
115
|
+
results.push({
|
|
116
|
+
transferId,
|
|
117
|
+
completed: true,
|
|
118
|
+
data: response,
|
|
119
|
+
});
|
|
120
|
+
} else {
|
|
121
|
+
results.push({
|
|
122
|
+
transferId,
|
|
123
|
+
completed: false,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
} catch (error: any) {
|
|
127
|
+
// Handle 401 errors gracefully - skip this transfer
|
|
128
|
+
if (error?.status === 401 || error?.message?.includes('401') || error?.message?.includes('authentication')) {
|
|
129
|
+
if (__DEV__) {
|
|
130
|
+
console.warn(`[useCheckPendingTransfers] Authentication required for transfer ${transferId}, skipping`);
|
|
131
|
+
}
|
|
132
|
+
results.push({
|
|
133
|
+
transferId,
|
|
134
|
+
completed: false,
|
|
135
|
+
});
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
// For other errors, mark as not completed
|
|
139
|
+
results.push({
|
|
140
|
+
transferId,
|
|
141
|
+
completed: false,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return results;
|
|
147
|
+
},
|
|
148
|
+
enabled: isAuthenticated && !!oxyServices && pendingTransfers.length > 0,
|
|
149
|
+
staleTime: 30 * 1000, // 30 seconds
|
|
150
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
151
|
+
retry: false, // Don't retry - we'll check again on next reconnect
|
|
152
|
+
});
|
|
153
|
+
};
|
|
154
|
+
|