Skip to content

Commit e2515c1

Browse files
Merge pull request #1378 from multiversx/rt/fix/ws-connect-and-fallback
Fixed websocket connection and falback mechanism
2 parents 4824979 + c13297a commit e2515c1

8 files changed

+216
-138
lines changed

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
- [Fixed websocket connection and falback mechanism](https://github.com/multiversx/mx-sdk-dapp/pull/1378)
11+
1012
## [[v3.2.4](https://github.com/multiversx/mx-sdk-dapp/pull/1376)] - 2025-02-17
1113

1214
- [Added a warning toast when an unconfirmed guardian change took place](https://github.com/multiversx/mx-sdk-dapp/pull/1375)
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,10 @@
1-
import { useCallback, useEffect, useRef } from 'react';
1+
import { useCallback } from 'react';
22
import { TRANSACTIONS_STATUS_POLLING_INTERVAL_MS } from 'constants/transactionStatus';
3-
import {
4-
websocketConnection,
5-
WebsocketConnectionStatusEnum
6-
} from '../../../websocketListener/websocketConnection';
3+
import { useWebsocketPollingFallback } from 'hooks/transactions/useTransactionsTracker/useWebsocketPollingFallback';
74
import { extractSessionId } from '../../helpers/extractSessionId';
85
import { timestampIsOlderThan } from '../../helpers/timestampIsOlderThan';
9-
import { useGetPollingInterval } from '../../useGetPollingInterval';
106
import { useGetBatches } from '../useGetBatches';
117
import { useVerifyBatchStatus } from './useVerifyBatchStatus';
12-
138
/**
149
* Fallback mechanism to check batches in case of ws connection failure
1510
* Resolves the toast by checking the status of each transaction in batch after a certain time (90seconds)
@@ -20,10 +15,6 @@ export const useCheckBatchesOnWsFailureFallback = (props?: {
2015
}) => {
2116
const { batchTransactionsArray } = useGetBatches();
2217
const { verifyBatchStatus } = useVerifyBatchStatus(props);
23-
const pollingInterval = useGetPollingInterval();
24-
const pollingIntervalTimer = useRef<NodeJS.Timeout | null>(null);
25-
const isWebsocketCompleted =
26-
websocketConnection.status === WebsocketConnectionStatusEnum.COMPLETED;
2718

2819
const checkAllBatches = useCallback(async () => {
2920
for (const { batchId } of batchTransactionsArray) {
@@ -45,28 +36,7 @@ export const useCheckBatchesOnWsFailureFallback = (props?: {
4536
}
4637
}, [batchTransactionsArray, verifyBatchStatus]);
4738

48-
useEffect(() => {
49-
if (isWebsocketCompleted) {
50-
// Do not setInterval if we already subscribe to websocket event
51-
if (pollingIntervalTimer.current) {
52-
clearInterval(pollingIntervalTimer.current);
53-
}
54-
55-
return;
56-
}
57-
58-
if (pollingIntervalTimer.current) {
59-
return;
60-
}
61-
62-
pollingIntervalTimer.current = setInterval(() => {
63-
checkAllBatches();
64-
}, pollingInterval);
65-
66-
return () => {
67-
if (pollingIntervalTimer.current) {
68-
clearInterval(pollingIntervalTimer.current);
69-
}
70-
};
71-
}, [checkAllBatches]);
39+
useWebsocketPollingFallback({
40+
onPoll: checkAllBatches
41+
});
7242
};

src/hooks/transactions/batch/tracker/useCheckHangingBatchesFallback.ts

+6-17
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { useCallback, useEffect, useRef } from 'react';
1+
import { useCallback } from 'react';
22
import {
33
TRANSACTIONS_STATUS_DROP_INTERVAL_MS,
44
TRANSACTIONS_STATUS_POLLING_INTERVAL_MS
55
} from 'constants/transactionStatus';
6+
import { useWebsocketPollingFallback } from 'hooks/transactions/useTransactionsTracker/useWebsocketPollingFallback';
67
import { removeBatchTransactions } from 'services/transactions';
78
import { getTransactionsStatus } from 'utils/transactions/batch/getTransactionsStatus';
89
import { sequentialToFlatArray } from 'utils/transactions/batch/sequentialToFlatArray';
@@ -21,7 +22,6 @@ export const useCheckHangingBatchesFallback = (props?: {
2122
}) => {
2223
const { batchTransactionsArray } = useGetBatches();
2324
const updateBatch = useUpdateTrackedTransactions();
24-
const pollingIntervalTimer = useRef<NodeJS.Timeout | null>(null);
2525
const onSuccess = props?.onSuccess;
2626
const onFail = props?.onFail;
2727

@@ -67,19 +67,8 @@ export const useCheckHangingBatchesFallback = (props?: {
6767
}
6868
}, [batchTransactionsArray, updateBatch, onSuccess, onFail]);
6969

70-
useEffect(() => {
71-
if (pollingIntervalTimer.current) {
72-
return;
73-
}
74-
75-
pollingIntervalTimer.current = setInterval(() => {
76-
checkHangingBatches();
77-
}, TRANSACTIONS_STATUS_POLLING_INTERVAL_MS);
78-
79-
return () => {
80-
if (pollingIntervalTimer.current) {
81-
clearInterval(pollingIntervalTimer.current);
82-
}
83-
};
84-
}, [checkHangingBatches]);
70+
useWebsocketPollingFallback({
71+
onPoll: checkHangingBatches,
72+
pollingInterval: TRANSACTIONS_STATUS_POLLING_INTERVAL_MS
73+
});
8574
};
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useCallback } from 'react';
22
import { getTransactionsByHashes as defaultGetTxByHash } from 'apiCalls/transactions';
33
import { TRANSACTIONS_STATUS_POLLING_INTERVAL_MS } from 'constants/transactionStatus';
44
import { TransactionsTrackerType } from 'types/transactionsTracker.types';
55
import { timestampIsOlderThan } from '../helpers/timestampIsOlderThan';
6-
import { useGetPendingTransactions } from '../useGetPendingTransactions';
76
import { useCheckTransactionStatus } from '../useCheckTransactionStatus';
8-
7+
import { useGetPendingTransactions } from '../useGetPendingTransactions';
8+
import { useWebsocketPollingFallback } from './useWebsocketPollingFallback';
99
/**
1010
* Fallback mechanism to check hanging transactions
1111
* Resolves the toast and set the status to failed for each transaction after a certain time (90 seconds)
@@ -15,12 +15,10 @@ export const useCheckHangingTransactionsFallback = (
1515
) => {
1616
const { pendingTransactionsArray } = useGetPendingTransactions();
1717
const checkTransactionStatus = useCheckTransactionStatus();
18-
const pollingIntervalTimer = useRef<NodeJS.Timeout | null>(null);
19-
2018
const getTransactionsByHash =
2119
props?.getTransactionsByHash ?? defaultGetTxByHash;
2220

23-
const checkHangingTransactions = async () => {
21+
const checkHangingTransactions = useCallback(async () => {
2422
for (const [sessionId] of pendingTransactionsArray) {
2523
if (
2624
!timestampIsOlderThan(
@@ -36,21 +34,15 @@ export const useCheckHangingTransactionsFallback = (
3634
...props
3735
});
3836
}
39-
};
37+
}, [
38+
pendingTransactionsArray,
39+
checkTransactionStatus,
40+
getTransactionsByHash,
41+
props
42+
]);
4043

41-
useEffect(() => {
42-
if (pollingIntervalTimer.current) {
43-
return;
44-
}
45-
46-
pollingIntervalTimer.current = setInterval(() => {
47-
checkHangingTransactions();
48-
}, TRANSACTIONS_STATUS_POLLING_INTERVAL_MS);
49-
50-
return () => {
51-
if (pollingIntervalTimer.current) {
52-
clearInterval(pollingIntervalTimer.current);
53-
}
54-
};
55-
}, []);
44+
useWebsocketPollingFallback({
45+
onPoll: checkHangingTransactions,
46+
pollingInterval: TRANSACTIONS_STATUS_POLLING_INTERVAL_MS
47+
});
5648
};
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
import { useEffect, useRef } from 'react';
1+
import { useCallback } from 'react';
22
import { getTransactionsByHashes as defaultGetTxByHash } from 'apiCalls/transactions';
33
import { TransactionsTrackerType } from 'types/transactionsTracker.types';
4-
import {
5-
websocketConnection,
6-
WebsocketConnectionStatusEnum
7-
} from '../../websocketListener/websocketConnection';
8-
import { useGetPollingInterval } from '../useGetPollingInterval';
94
import { useCheckTransactionStatus } from '../useCheckTransactionStatus';
5+
import { useWebsocketPollingFallback } from './useWebsocketPollingFallback';
106

117
/**
128
* Fallback mechanism to check the transaction in case of ws connection failure
@@ -16,39 +12,17 @@ export const useCheckTransactionOnWsFailureFallback = (
1612
props?: TransactionsTrackerType
1713
) => {
1814
const checkTransactionStatus = useCheckTransactionStatus();
19-
const pollingInterval = useGetPollingInterval();
20-
const pollingIntervalTimer = useRef<NodeJS.Timeout | null>(null);
21-
const isWebsocketCompleted =
22-
websocketConnection.status === WebsocketConnectionStatusEnum.COMPLETED;
23-
2415
const getTransactionsByHash =
2516
props?.getTransactionsByHash ?? defaultGetTxByHash;
2617

27-
useEffect(() => {
28-
if (isWebsocketCompleted) {
29-
// Do not setInterval if we already subscribe to websocket event
30-
if (pollingIntervalTimer.current) {
31-
clearInterval(pollingIntervalTimer.current);
32-
}
33-
34-
return;
35-
}
36-
37-
if (pollingIntervalTimer.current) {
38-
return;
39-
}
40-
41-
pollingIntervalTimer.current = setInterval(() => {
42-
checkTransactionStatus({
43-
getTransactionsByHash,
44-
...props
45-
});
46-
}, pollingInterval);
18+
const checkTransaction = useCallback(() => {
19+
checkTransactionStatus({
20+
getTransactionsByHash,
21+
...props
22+
});
23+
}, [checkTransactionStatus, getTransactionsByHash, props]);
4724

48-
return () => {
49-
if (pollingIntervalTimer.current) {
50-
clearInterval(pollingIntervalTimer.current);
51-
}
52-
};
53-
}, [checkTransactionStatus]);
25+
useWebsocketPollingFallback({
26+
onPoll: checkTransaction
27+
});
5428
};
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// src/hooks/transactions/useTransactionsTracker/usePollingWithWebsocketFallback.ts
2+
import { useEffect, useRef, useState } from 'react';
3+
import { accountSelector } from 'reduxStore/selectors';
4+
import { store } from 'reduxStore/store';
5+
import { useGetAccount } from '../../account/useGetAccount';
6+
import {
7+
websocketConnection,
8+
WebsocketConnectionStatusEnum
9+
} from '../../websocketListener/websocketConnection';
10+
import { useGetPollingInterval } from '../useGetPollingInterval';
11+
12+
interface UseWebsocketPollingFallbackParamsType {
13+
onPoll: () => void | Promise<void>;
14+
pollingInterval?: number;
15+
}
16+
17+
/**
18+
* A hook that handles polling with websocket fallback mechanism.
19+
* It will start polling when:
20+
* 1. There is an address present
21+
* 2. Websocket is not in COMPLETED state
22+
*
23+
* It will stop polling when:
24+
* 1. Address is removed
25+
* 2. Websocket becomes COMPLETED
26+
*/
27+
export const useWebsocketPollingFallback = ({
28+
onPoll,
29+
pollingInterval
30+
}: UseWebsocketPollingFallbackParamsType) => {
31+
const defaultPollingInterval = useGetPollingInterval();
32+
const usedPollingInterval = pollingInterval ?? defaultPollingInterval;
33+
const pollingIntervalTimer = useRef<NodeJS.Timeout | null>(null);
34+
const { address } = useGetAccount();
35+
const [websocketStatus, setWebsocketStatus] = useState(
36+
websocketConnection.status
37+
);
38+
39+
const clearFallbackTimer = () => {
40+
if (pollingIntervalTimer.current) {
41+
clearInterval(pollingIntervalTimer.current);
42+
pollingIntervalTimer.current = null;
43+
}
44+
};
45+
46+
useEffect(() => {
47+
const unsubscribe = websocketConnection.subscribe((status) => {
48+
console.info('Websocket status changed:', status);
49+
setWebsocketStatus(status);
50+
});
51+
52+
return () => unsubscribe();
53+
}, []);
54+
55+
useEffect(() => {
56+
const isWebsocketCompleted =
57+
websocketStatus === WebsocketConnectionStatusEnum.COMPLETED;
58+
59+
if (!address || isWebsocketCompleted) {
60+
clearFallbackTimer();
61+
return;
62+
}
63+
64+
if (pollingIntervalTimer.current) {
65+
return;
66+
}
67+
68+
console.info('Websocket connection failed. Starting polling fallback...');
69+
pollingIntervalTimer.current = setInterval(() => {
70+
const currentStatus = websocketConnection.status;
71+
const isNowCompleted =
72+
currentStatus === WebsocketConnectionStatusEnum.COMPLETED;
73+
74+
// Get the address from store directly to avoid closure issues related to hooks
75+
const { address: currentAddress } = accountSelector(store.getState());
76+
77+
if (!currentAddress || isNowCompleted) {
78+
clearFallbackTimer();
79+
return;
80+
}
81+
82+
onPoll();
83+
}, usedPollingInterval);
84+
85+
return () => {
86+
clearFallbackTimer();
87+
};
88+
}, [address, websocketStatus, onPoll, usedPollingInterval]);
89+
90+
return clearFallbackTimer;
91+
};

0 commit comments

Comments
 (0)