Skip to content

Commit e92eb8f

Browse files
committed
Limit the polling when moving the widget to foreground
Now that chronik is properly paused and resumed, the retries are no longer necessary: if the event fired before the reconnect, it will be caught by the first polling round and otherwise by the chronik websocket event. We still keep a single retry for the very unlikely case where the polling is not successful but the event fires before the websocket reconnected. A single 2s delay is used for this case and should not be used in practice, it's only a safety net. The timeout is cleared upon success or visibility change, and worst case it's only an extra api call then a no-op. Test Plan: Check payment on mobile still works as expected.
1 parent 1ab0422 commit e92eb8f

2 files changed

Lines changed: 25 additions & 34 deletions

File tree

react/lib/components/Widget/WidgetContainer.tsx

Lines changed: 24 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import {
2121
DEFAULT_DONATION_RATE,
2222
POLL_TX_HISTORY_LOOKBACK,
2323
POLL_REQUEST_DELAY,
24-
POLL_MAX_RETRY,
2524
} from '../../util';
2625
import { getAddressDetails } from '../../util/api-client';
2726

@@ -161,7 +160,6 @@ export const WidgetContainer: React.FunctionComponent<WidgetContainerProps> =
161160
const [thisPrice, setThisPrice] = useState(0);
162161
const [usdPrice, setUsdPrice] = useState(0);
163162
const [success, setSuccess] = useState(false);
164-
const [retryCount, setRetryCount] = useState(0);
165163
const { enqueueSnackbar } = useSnackbar();
166164

167165
const [shiftCompleted, setShiftCompleted] = useState(false);
@@ -319,8 +317,15 @@ export const WidgetContainer: React.FunctionComponent<WidgetContainerProps> =
319317

320318
let wasHidden = document.hidden;
321319
let hiddenTimestamp = 0;
320+
let retryTimeoutId: NodeJS.Timeout | null = null;
322321

323322
const handleVisibilityChange = async () => {
323+
// Clear any pending retry timeout
324+
if (retryTimeoutId) {
325+
clearTimeout(retryTimeoutId);
326+
retryTimeoutId = null;
327+
}
328+
324329
if (document.hidden) {
325330
wasHidden = true;
326331
hiddenTimestamp = Date.now();
@@ -352,46 +357,33 @@ export const WidgetContainer: React.FunctionComponent<WidgetContainerProps> =
352357
const checkCompleted = await checkForTransactions();
353358

354359
// If check completed successfully but payment hasn't succeeded yet,
355-
// trigger retries. We might be missing the payment transaction.
360+
// Schedule a single retry after 2 seconds. This is only there to handle
361+
// the case where the transaction is discovered after the app has been
362+
// foregrounded, but before the chronik websocket is resumed. 2 seconds
363+
// should be plenty for this case which is not expected to happen under
364+
// normal circumstances.
365+
// Note that we can't check success at this stage because it is captured
366+
// and we would use a stale value. So we run the timeout unconditionally
367+
// and let the useEffect cancel it if success turns true. Worst case it
368+
// does an API call and then it's a no-op.
356369
if (checkCompleted && !success) {
357-
// Start retries
358-
setRetryCount(1);
370+
retryTimeoutId = setTimeout(async () => {
371+
await checkForTransactions();
372+
retryTimeoutId = null;
373+
}, POLL_REQUEST_DELAY);
359374
}
360375
};
361376

362377
document.addEventListener('visibilitychange', handleVisibilityChange);
363378

364379
return () => {
365380
document.removeEventListener('visibilitychange', handleVisibilityChange);
366-
};
367-
}, [to, thisPaymentId, success, disablePaymentId]);
368-
369-
// Retry mechanism: check every second if payment hasn't succeeded yet
370-
useEffect(() => {
371-
372-
if (retryCount === 0 || success || retryCount >= POLL_MAX_RETRY) {
373-
// Retry up to 5 times or until the payment succeeds. If the payment tx
374-
// is not found within this time period, something has gone wrong.
375-
return;
376-
}
377-
378-
const intervalId = setInterval(async () => {
379-
if (success) {
380-
// Stop retries upon success
381-
setRetryCount(0);
382-
return;
381+
if (retryTimeoutId) {
382+
clearTimeout(retryTimeoutId);
383+
retryTimeoutId = null;
383384
}
384-
385-
await checkForTransactions();
386-
387-
// Increment retry count for next attempt (regardless of success/error)
388-
setRetryCount(prev => prev + 1);
389-
}, POLL_REQUEST_DELAY);
390-
391-
return () => {
392-
clearInterval(intervalId);
393385
};
394-
}, [retryCount, success]);
386+
}, [to, thisPaymentId, success, disablePaymentId, checkForTransactions]);
395387

396388
return (
397389
<React.Fragment>

react/lib/util/constants.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,4 @@ export const DEFAULT_MINIMUM_DONATION_AMOUNT: { [key: string]: number } = {
4040
};
4141

4242
export const POLL_TX_HISTORY_LOOKBACK = 5 // request last 5 txs
43-
export const POLL_REQUEST_DELAY = 1000 // 1s
44-
export const POLL_MAX_RETRY = 5
43+
export const POLL_REQUEST_DELAY = 2000 // 2s

0 commit comments

Comments
 (0)