Skip to content

Commit d93eedd

Browse files
authored
Merge pull request #619 from Fabcien/chronik_latest
Update to latest chronik and use it to handle mobile cases
2 parents 3f27322 + 947fb62 commit d93eedd

8 files changed

Lines changed: 101 additions & 132 deletions

File tree

paybutton/yarn.lock

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -748,29 +748,8 @@
748748
react-is "^19.1.1"
749749

750750
"@paybutton/react@link:../react":
751-
version "5.2.1"
752-
dependencies:
753-
"@emotion/react" "^11.14.0"
754-
"@emotion/styled" "^11.14.1"
755-
"@mui/material" "^7.3.4"
756-
"@types/crypto-js" "^4.2.1"
757-
"@types/jest" "^29.5.11"
758-
axios "1.12.0"
759-
bignumber.js "9.0.2"
760-
cashtab-connect "^1.1.0"
761-
chronik-client-cashtokens "^3.4.0"
762-
copy-to-clipboard "3.3.3"
763-
crypto-js "^4.2.0"
764-
decimal.js "^10.6.0"
765-
ecashaddrjs "^2.0.0"
766-
jest "^29.7.0"
767-
lodash "4.17.21"
768-
notistack "3.0.0"
769-
qrcode.react "3"
770-
react-number-format "^5.4.4"
771-
socket.io-client "4.7.4"
772-
ts-jest "^29.4.5"
773-
xecaddrjs "^0.0.1"
751+
version "0.0.0"
752+
uid ""
774753

775754
"@popperjs/core@^2.11.8":
776755
version "2.11.8"
@@ -1755,15 +1734,16 @@ chrome-trace-event@^1.0.2:
17551734
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b"
17561735
integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
17571736

1758-
chronik-client-cashtokens@^3.4.0:
1759-
version "3.4.0"
1760-
resolved "https://registry.yarnpkg.com/chronik-client-cashtokens/-/chronik-client-cashtokens-3.4.0.tgz#0c958c72c119867d924c0c446e0765381fd1b792"
1761-
integrity sha512-D2G8j+dhOlG0wr+DtgnMHHwCYzpa/XCzNvHhkkAfOOL3OaAoXONMxhbqx+N1y7B0L5ot69y8z/8KOEz9ARL3hg==
1737+
chronik-client@^4.1.0:
1738+
version "4.1.0"
1739+
resolved "https://registry.yarnpkg.com/chronik-client/-/chronik-client-4.1.0.tgz#f49a07565064335f1d576230bf77546a9bc29e8b"
1740+
integrity sha512-RA7gXMTmWW3ek29ACvHGfcJxGUBMxtcvMCCV7EJLywYCCYyCDmjdcTVIHEqEFF0QHj6F5PJi5nvA65cAtBnr5Q==
17621741
dependencies:
17631742
"@types/ws" "^8.2.1"
17641743
axios "^1.6.3"
1765-
ecashaddrjs "2.0.0"
1744+
ecashaddrjs "^2.0.0"
17661745
isomorphic-ws "^4.0.1"
1746+
long "^4.0.0"
17671747
protobufjs "^6.8.8"
17681748
ws "^8.3.0"
17691749

@@ -2006,7 +1986,7 @@ dunder-proto@^1.0.1:
20061986
es-errors "^1.3.0"
20071987
gopd "^1.2.0"
20081988

2009-
ecashaddrjs@2.0.0, ecashaddrjs@^1.0.7, ecashaddrjs@^2.0.0:
1989+
ecashaddrjs@^1.0.7, ecashaddrjs@^2.0.0:
20101990
version "2.0.0"
20111991
resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874"
20121992
integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw==

react/lib/components/Widget/Widget.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ import {
5353
MINIMUM_ALTPAYMENT_DOLLAR_AMOUNT,
5454
MINIMUM_ALTPAYMENT_CAD_AMOUNT,
5555
} from '../../altpayment'
56+
import { WsEndpoint } from 'chronik-client'
5657

5758

5859
export interface WidgetProps {
@@ -221,10 +222,15 @@ export const Widget: React.FunctionComponent<WidgetProps> = props => {
221222

222223

223224
// websockets if standalone
224-
const [internalTxsSocket, setInternalTxsSocket] = useState<Socket | undefined>(undefined)
225+
const [internalTxsSocket, setInternalTxsSocket] = useState<Socket | WsEndpoint | undefined>(undefined)
225226
const thisTxsSocket = txsSocket ?? internalTxsSocket
226227
const setThisTxsSocket =
227-
(setTxsSocket as ((s: Socket | undefined) => void) | undefined) ?? setInternalTxsSocket
228+
(setTxsSocket as ((s: Socket | WsEndpoint | undefined) => void) | undefined) ?? setInternalTxsSocket
229+
230+
// Type guard to check if socket is a Chronik WsEndpoint
231+
const isChronikWsEndpoint = (socket: Socket | WsEndpoint | undefined): socket is WsEndpoint => {
232+
return socket instanceof WsEndpoint;
233+
};
228234

229235
const [internalNewTxs, setInternalNewTxs] = useState<Transaction[] | undefined>()
230236
const thisNewTxs = newTxs ?? internalNewTxs
@@ -593,6 +599,48 @@ export const Widget: React.FunctionComponent<WidgetProps> = props => {
593599
}
594600
}, [to, thisUseAltpayment])
595601

602+
// Suspend/resume Chronik WebSocket based on page visibility
603+
useEffect(() => {
604+
if (typeof document === 'undefined' || isChild === true) {
605+
return;
606+
}
607+
608+
const handleVisibilityChange = async () => {
609+
if (!thisTxsSocket) {
610+
// Not initialized yet
611+
return;
612+
}
613+
614+
// Check if this is a Chronik WsEndpoint (has pause/resume methods)
615+
// vs a socket.io Socket (doesn't have these methods)
616+
if (!isChronikWsEndpoint(thisTxsSocket)) {
617+
return;
618+
}
619+
620+
if (document.hidden) {
621+
// Page went to background - suspend WebSocket
622+
try {
623+
thisTxsSocket.pause();
624+
} catch (error) {
625+
console.error('Error pausing WebSocket:', error);
626+
}
627+
} else {
628+
// Page came to foreground - resume WebSocket
629+
try {
630+
await thisTxsSocket.resume();
631+
} catch (error) {
632+
console.error('Error resuming WebSocket:', error);
633+
}
634+
}
635+
};
636+
637+
document.addEventListener('visibilitychange', handleVisibilityChange);
638+
639+
return () => {
640+
document.removeEventListener('visibilitychange', handleVisibilityChange);
641+
};
642+
}, [isChild, thisTxsSocket])
643+
596644
const tradeWithAltpayment = () => {
597645
setThisUseAltpayment(true)
598646
}

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/chronik.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ChronikClient, WsEndpoint, Tx, ConnectionStrategy} from 'chronik-client-cashtokens';
1+
import { ChronikClient, WsEndpoint, Tx, ConnectionStrategy} from 'chronik-client';
22
import { encodeCashAddress, decodeCashAddress } from 'ecashaddrjs'
33
import { AddressType } from 'ecashaddrjs/dist/types'
44
import xecaddr from 'xecaddrjs'

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

react/lib/util/socket.ts

Lines changed: 3 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,47 +2,10 @@ import { io, Socket } from 'socket.io-client';
22
import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../altpayment';
33
import config from '../paybutton-config.json';
44

5-
import { BroadcastTxData, CheckSuccessInfo, Transaction } from './types';
6-
import { getAddressDetails } from './api-client';
7-
import { getAddressPrefixed } from './address';
5+
import { CheckSuccessInfo, Transaction } from './types';
86
import { shouldTriggerOnSuccess } from './validate';
97
import { initializeChronikWebsocket } from './chronik';
10-
11-
const txsListener = (txsSocket: Socket, setNewTxs: Function, setDialogOpen?: Function, checkSuccessInfo?: CheckSuccessInfo): void => {
12-
txsSocket.on('incoming-txs', (broadcastedTxData: BroadcastTxData) => {
13-
const unconfirmedTxs = broadcastedTxData.txs.filter(
14-
tx => tx.confirmed === false,
15-
);
16-
if (
17-
broadcastedTxData.messageType === 'NewTx' &&
18-
unconfirmedTxs.length !== 0
19-
) {
20-
if (setDialogOpen !== undefined && checkSuccessInfo !== undefined) {
21-
for (const tx of unconfirmedTxs) {
22-
if (shouldTriggerOnSuccess(
23-
tx,
24-
checkSuccessInfo.currency,
25-
checkSuccessInfo.price,
26-
checkSuccessInfo.randomSatoshis,
27-
checkSuccessInfo.disablePaymentId,
28-
checkSuccessInfo.expectedPaymentId,
29-
checkSuccessInfo.expectedAmount,
30-
checkSuccessInfo.expectedOpReturn,
31-
checkSuccessInfo.currencyObj
32-
)) {
33-
setDialogOpen(true)
34-
setTimeout(() => {
35-
setNewTxs(unconfirmedTxs);
36-
}, 700);
37-
break
38-
}
39-
}
40-
} else {
41-
setNewTxs(unconfirmedTxs);
42-
}
43-
}
44-
});
45-
};
8+
import { WsEndpoint } from 'chronik-client';
469

4710
interface AltpaymentListenerParams {
4811
addressType: string
@@ -115,7 +78,7 @@ export const setupAltpaymentSocket = async (params: SetupAltpaymentSocketParams)
11578

11679
interface SetupTxsSocketParams {
11780
address: string
118-
txsSocket?: Socket
81+
txsSocket?: Socket | WsEndpoint
11982
apiBaseUrl?: string
12083
wsBaseUrl?: string
12184
setTxsSocket: Function
@@ -124,20 +87,6 @@ interface SetupTxsSocketParams {
12487
checkSuccessInfo?: CheckSuccessInfo
12588
}
12689

127-
export const setupTxsSocket = async (params: SetupTxsSocketParams): Promise<void> => {
128-
void getAddressDetails(params.address, params.apiBaseUrl);
129-
if (params.txsSocket !== undefined) {
130-
params.txsSocket.disconnect();
131-
params.setTxsSocket(undefined);
132-
}
133-
const newSocket = io(`${params.wsBaseUrl ?? config.wsBaseUrl}/addresses`, {
134-
forceNew: true,
135-
query: { addresses: [getAddressPrefixed(params.address)] },
136-
});
137-
params.setTxsSocket(newSocket);
138-
txsListener(newSocket, params.setNewTxs, params.setDialogOpen, params.checkSuccessInfo);
139-
}
140-
14190
export const setupChronikWebSocket = async (params: SetupTxsSocketParams): Promise<void> => {
14291
if (params.txsSocket !== undefined) {
14392
console.log(`Closing existing Chronik WebSocket for address: ${params.address}`);

react/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@
9595
"axios": "1.12.0",
9696
"bignumber.js": "9.0.2",
9797
"cashtab-connect": "^1.1.0",
98-
"chronik-client-cashtokens": "^3.4.0",
98+
"chronik-client": "^4.1.0",
9999
"copy-to-clipboard": "3.3.3",
100100
"crypto-js": "^4.2.0",
101101
"decimal.js": "^10.6.0",
@@ -118,7 +118,7 @@
118118
"resolutions": {
119119
"@types/react": "18.3.26",
120120
"@types/react-dom": "18.3.0",
121-
"chronik-client-cashtokens/ecashaddrjs": "^2.0.0",
121+
"chronik-client/ecashaddrjs": "^2.0.0",
122122
"webpack": "^5.0.0",
123123
"@babel/plugin-syntax-flow": "^7.14.5",
124124
"@babel/plugin-transform-react-jsx": "^7.14.9",

react/yarn.lock

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5984,15 +5984,16 @@ chrome-trace-event@^1.0.2:
59845984
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b"
59855985
integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
59865986

5987-
chronik-client-cashtokens@^3.4.0:
5988-
version "3.4.0"
5989-
resolved "https://registry.yarnpkg.com/chronik-client-cashtokens/-/chronik-client-cashtokens-3.4.0.tgz#0c958c72c119867d924c0c446e0765381fd1b792"
5990-
integrity sha512-D2G8j+dhOlG0wr+DtgnMHHwCYzpa/XCzNvHhkkAfOOL3OaAoXONMxhbqx+N1y7B0L5ot69y8z/8KOEz9ARL3hg==
5987+
chronik-client@^4.1.0:
5988+
version "4.1.0"
5989+
resolved "https://registry.yarnpkg.com/chronik-client/-/chronik-client-4.1.0.tgz#f49a07565064335f1d576230bf77546a9bc29e8b"
5990+
integrity sha512-RA7gXMTmWW3ek29ACvHGfcJxGUBMxtcvMCCV7EJLywYCCYyCDmjdcTVIHEqEFF0QHj6F5PJi5nvA65cAtBnr5Q==
59915991
dependencies:
59925992
"@types/ws" "^8.2.1"
59935993
axios "^1.6.3"
5994-
ecashaddrjs "2.0.0"
5994+
ecashaddrjs "^2.0.0"
59955995
isomorphic-ws "^4.0.1"
5996+
long "^4.0.0"
59965997
protobufjs "^6.8.8"
59975998
ws "^8.3.0"
59985999

@@ -7079,11 +7080,6 @@ eastasianwidth@^0.2.0:
70797080
resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb"
70807081
integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==
70817082

7082-
ecashaddrjs@2.0.0, ecashaddrjs@^2.0.0:
7083-
version "2.0.0"
7084-
resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874"
7085-
integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw==
7086-
70877083
ecashaddrjs@^1.0.7:
70887084
version "1.6.2"
70897085
resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-1.6.2.tgz#9fec1131bc006a874e4c2fa589e6fc880c40745e"
@@ -7092,6 +7088,11 @@ ecashaddrjs@^1.0.7:
70927088
big-integer "1.6.36"
70937089
bs58check "^3.0.1"
70947090

7091+
ecashaddrjs@^2.0.0:
7092+
version "2.0.0"
7093+
resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874"
7094+
integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw==
7095+
70957096
ee-first@1.1.1:
70967097
version "1.1.1"
70977098
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"

0 commit comments

Comments
 (0)