Skip to content

Commit 515f06e

Browse files
fix: pusher state and polling strategy
1 parent 8b63c2c commit 515f06e

File tree

6 files changed

+1219
-181
lines changed

6 files changed

+1219
-181
lines changed

examples/nextjs-app/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"@headlessui/react": "^2.2.0",
1515
"@rainbow-me/rainbowkit": "^2.2.8",
1616
"@rozoai/intent-common": "0.1.8",
17-
"@rozoai/intent-pay": "0.1.13",
17+
"@rozoai/intent-pay": "0.1.14",
1818
"@tanstack/react-query": "^5.51.11",
1919
"@types/react-syntax-highlighter": "^15.5.13",
2020
"@wagmi/core": "^2.22.0",

examples/nextjs-app/src/app/basic/page.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import * as Tokens from "@rozoai/intent-common";
44
import {
55
baseEURC,
6-
ExternalPaymentOptions,
76
FeeType,
87
getChainById,
98
getChainName,
@@ -484,7 +483,6 @@ export default function DemoBasic() {
484483
}}
485484
feeType={FeeType.ExactOut}
486485
preferredSymbol={preferredSymbol}
487-
paymentOptions={[ExternalPaymentOptions.Stellar]}
488486
metadata={metadata}
489487
resetOnSuccess
490488
showProcessingPayout

packages/connectkit/bundle-analysis.html

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

packages/connectkit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@rozoai/intent-pay",
33
"private": false,
4-
"version": "0.1.13",
4+
"version": "0.1.14",
55
"author": "RozoAI",
66
"homepage": "https://github.com/RozoAI/intent-pay",
77
"license": "BSD-2-Clause",

packages/connectkit/src/components/Pages/Confirmation/index.tsx

Lines changed: 47 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,6 @@ const Confirmation: React.FC = () => {
6060
const [pusherPayoutTxHash, setPusherPayoutTxHash] = useState<
6161
string | undefined
6262
>(undefined);
63-
const [pusherPayoutTxHashUrl, setPusherPayoutTxHashUrl] = useState<
64-
string | undefined
65-
>(undefined);
6663

6764
// Track Pusher initialization and data activity for timeout logic
6865
const [pusherEnabled, setPusherEnabled] = useState<boolean>(true);
@@ -73,6 +70,7 @@ const Confirmation: React.FC = () => {
7370
const payoutCompletedRef = useRef<boolean>(false);
7471
const timeoutIdRef = useRef<NodeJS.Timeout | null>(null);
7572
const pusherEnabledRef = useRef<boolean>(true);
73+
const prevRozoPaymentIdRef = useRef<string | undefined>(undefined);
7674

7775
const showProcessingPayout = useMemo(() => {
7876
const { payParams, tokenMode } = paymentStateContext;
@@ -87,6 +85,16 @@ const Confirmation: React.FC = () => {
8785
return false;
8886
}, [paymentStateContext]);
8987

88+
// Compute Pusher payout URL at render time to avoid stale closure issues
89+
// (the onPayoutCompleted callback may have stale `order` reference)
90+
const computedPusherPayoutTxHashUrl = useMemo(() => {
91+
if (pusherPayoutTxHash && order) {
92+
const destChainId = getOrderDestChainId(order);
93+
return getChainExplorerTxUrl(destChainId, pusherPayoutTxHash);
94+
}
95+
return undefined;
96+
}, [pusherPayoutTxHash, order]);
97+
9098
const rozoPaymentId = useMemo(() => {
9199
const id = order?.externalId || paymentStateContext.rozoPaymentId;
92100
return id;
@@ -229,19 +237,9 @@ const Confirmation: React.FC = () => {
229237
payoutCompletedSent.current = payoutKey;
230238
payoutCompletedRef.current = true;
231239

232-
// Update local state for UI display
240+
// Update local state for UI display (URL computed at render time via useMemo)
233241
setPusherPayoutTxHash(payload.destination_txhash);
234242

235-
// Generate transaction URL if we have the order
236-
if (order) {
237-
const destChainId = getOrderDestChainId(order);
238-
const txUrl = getChainExplorerTxUrl(
239-
destChainId,
240-
payload.destination_txhash
241-
);
242-
setPusherPayoutTxHashUrl(txUrl);
243-
}
244-
245243
// Update payment state
246244
setPaymentPayoutCompleted(payload.destination_txhash, rozoPaymentId);
247245
triggerResize();
@@ -303,7 +301,7 @@ const Confirmation: React.FC = () => {
303301
);
304302
}
305303

306-
// Set up timeout to switch to polling after 1 minute if no data received
304+
// Set up timeout to switch to polling after 1 minute if payout not completed
307305
context.log("[CONFIRMATION] Setting up 1-minute timeout");
308306
timeoutIdRef.current = setTimeout(() => {
309307
context.log(
@@ -312,14 +310,12 @@ const Confirmation: React.FC = () => {
312310
"- checking conditions for polling switch"
313311
);
314312

315-
// Only switch to polling if payout hasn't been completed and Pusher is still enabled
316-
if (
317-
!pusherDataReceivedRef.current &&
318-
pusherEnabledRef.current &&
319-
!payoutCompletedRef.current
320-
) {
313+
// Switch to polling if payout hasn't been completed and Pusher is still enabled
314+
// Note: We check payoutCompletedRef, not pusherDataReceivedRef, because we may
315+
// receive payin data but still need to poll for payout if it doesn't arrive via Pusher
316+
if (pusherEnabledRef.current && !payoutCompletedRef.current) {
321317
context.log(
322-
"[CONFIRMATION] 1 minute elapsed with no Pusher data, switching to polling"
318+
"[CONFIRMATION] 1 minute elapsed without payout completion, switching to polling"
323319
);
324320

325321
// Unsubscribe from Pusher
@@ -334,7 +330,6 @@ const Confirmation: React.FC = () => {
334330
setPollingEnabled(true);
335331
} else {
336332
context.log("[CONFIRMATION] Timeout fired but conditions not met:", {
337-
pusherDataReceived: pusherDataReceivedRef.current,
338333
pusherEnabled: pusherEnabledRef.current,
339334
payoutCompleted: payoutCompletedRef.current,
340335
});
@@ -357,22 +352,30 @@ const Confirmation: React.FC = () => {
357352
// eslint-disable-next-line react-hooks/exhaustive-deps
358353
}, [rozoPaymentId, pusherEnabled]);
359354

360-
// Reset tracking when rozoPaymentId changes
355+
// Reset tracking when rozoPaymentId changes to a DIFFERENT value (not on initial mount)
361356
useEffect(() => {
362-
context.log("[CONFIRMATION] Resetting tracking for new payment ID");
363-
pusherInitializedTimeRef.current = null;
364-
pusherDataReceivedRef.current = false;
365-
payoutCompletedRef.current = false;
366-
pusherEnabledRef.current = true;
367-
setPusherEnabled(true);
368-
setPollingEnabled(false);
369-
if (timeoutIdRef.current) {
370-
context.log(
371-
"[CONFIRMATION] Clearing existing timeout on payment ID change"
372-
);
373-
clearTimeout(timeoutIdRef.current);
374-
timeoutIdRef.current = null;
357+
// Only reset if we're switching to a different payment ID, not on initial mount
358+
if (
359+
prevRozoPaymentIdRef.current &&
360+
prevRozoPaymentIdRef.current !== rozoPaymentId
361+
) {
362+
context.log("[CONFIRMATION] Resetting tracking for new payment ID");
363+
pusherInitializedTimeRef.current = null;
364+
pusherDataReceivedRef.current = false;
365+
payoutCompletedRef.current = false;
366+
pusherEnabledRef.current = true;
367+
setPusherEnabled(true);
368+
setPollingEnabled(false);
369+
if (timeoutIdRef.current) {
370+
context.log(
371+
"[CONFIRMATION] Clearing existing timeout on payment ID change"
372+
);
373+
clearTimeout(timeoutIdRef.current);
374+
timeoutIdRef.current = null;
375+
}
375376
}
377+
// Update the previous value for next comparison
378+
prevRozoPaymentIdRef.current = rozoPaymentId;
376379
// eslint-disable-next-line react-hooks/exhaustive-deps
377380
}, [rozoPaymentId]);
378381

@@ -519,10 +522,15 @@ const Confirmation: React.FC = () => {
519522
<ModalBody>
520523
{payoutLoading ? (
521524
<LoadingText>Processing payout...</LoadingText>
522-
) : (pusherPayoutTxHashUrl && pusherPayoutTxHash) ||
525+
) : (computedPusherPayoutTxHashUrl &&
526+
pusherPayoutTxHash) ||
523527
(payoutTxHashUrl && payoutTxHash) ? (
524528
<Link
525-
href={pusherPayoutTxHashUrl || payoutTxHashUrl || "#"}
529+
href={
530+
computedPusherPayoutTxHashUrl ||
531+
payoutTxHashUrl ||
532+
"#"
533+
}
526534
target="_blank"
527535
rel="noopener noreferrer"
528536
style={{ fontSize: 14, fontWeight: 400 }}

0 commit comments

Comments
 (0)