diff --git a/react/lib/components/PayButton/PayButton.tsx b/react/lib/components/PayButton/PayButton.tsx index bc6cef93..31d7535e 100644 --- a/react/lib/components/PayButton/PayButton.tsx +++ b/react/lib/components/PayButton/PayButton.tsx @@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Theme, ThemeName, ThemeProvider, useTheme } from '../../themes'; import Button, { ButtonProps } from '../Button/Button'; +import { Socket } from 'socket.io-client'; import { Transaction, @@ -14,9 +15,13 @@ import { CurrencyObject, generatePaymentId, getCurrencyObject, - isPropsTrue + isPropsTrue, + setupAltpaymentSocket, + setupTxsSocket, + CryptoCurrency } from '../../util'; import { PaymentDialog } from '../PaymentDialog'; +import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../../altpayment'; export interface PayButtonProps extends ButtonProps { to: string; amount?: number | string; @@ -55,10 +60,20 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { const [disabled, setDisabled] = useState(false); const [errorMsg, setErrorMsg] = useState(''); const [amount, setAmount] = useState(props.amount); + const [txsSocket, setTxsSocket] = useState(undefined); + const [altpaymentSocket, setAltpaymentSocket] = useState(undefined); + const [useAltpayment, setUseAltpayment] = useState(false); + const [coins, setCoins] = useState([]); + const [loadingPair, setLoadingPair] = useState(false); + const [coinPair, setCoinPair] = useState(); + const [loadingShift, setLoadingShift] = useState(false); + const [altpaymentShift, setAltpaymentShift] = useState(); + const [altpaymentError, setAltpaymentError] = useState(undefined); const [currencyObj, setCurrencyObj] = useState(); const [cryptoAmount, setCryptoAmount] = useState(); const [price, setPrice] = useState(0); + const [newTxs, setNewTxs] = useState(); const priceRef = useRef(price); const cryptoAmountRef = useRef(cryptoAmount); @@ -90,6 +105,9 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { } = Object.assign({}, PayButton.defaultProps, props); const [paymentId] = useState(!disablePaymentId ? generatePaymentId(8) : undefined); + const [addressType, setAddressType] = useState( + getCurrencyTypeFromAddress(to), + ); useEffect(() => { priceRef.current = price; @@ -154,6 +172,61 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { } }, [to]); + useEffect(() => { + if (dialogOpen === false) { + return + } + (async () => { + if (txsSocket === undefined) { + const expectedAmount = currencyObj ? currencyObj?.float : undefined + await setupTxsSocket({ + address: to, + txsSocket, + apiBaseUrl, + wsBaseUrl, + setTxsSocket, + setNewTxs, + setDialogOpen, + checkSuccessInfo: { + currency, + price, + randomSatoshis: randomSatoshis ?? false, + disablePaymentId, + expectedAmount, + expectedOpReturn: opReturn, + expectedPaymentId: paymentId, + currencyObj, + } + }) + } + if (altpaymentSocket === undefined && useAltpayment) { + await setupAltpaymentSocket({ + addressType, + altpaymentSocket, + wsBaseUrl, + setAltpaymentSocket, + setCoins, + setCoinPair, + setLoadingPair, + setAltpaymentShift, + setLoadingShift, + setAltpaymentError, + }) + } + })() + + return () => { + if (txsSocket !== undefined) { + txsSocket.disconnect(); + setTxsSocket(undefined); + } + if (altpaymentSocket !== undefined) { + altpaymentSocket.disconnect(); + setAltpaymentSocket(undefined); + } + } + }, [dialogOpen, useAltpayment]); + useEffect(() => { if (dialogOpen === false && props.amount && currency) { const obj = getCurrencyObject( @@ -236,6 +309,7 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { editable={editable} goalAmount={goalAmount} dialogOpen={dialogOpen} + setDialogOpen={setDialogOpen} onClose={handleCloseDialog} wsBaseUrl={wsBaseUrl} apiBaseUrl={apiBaseUrl} @@ -243,6 +317,28 @@ export const PayButton = (props: PayButtonProps): React.ReactElement => { disableAltpayment={disableAltpayment} contributionOffset={contributionOffset} autoClose={autoClose} + useAltpayment={useAltpayment} + setUseAltpayment={setUseAltpayment} + setTxsSocket={setTxsSocket} + txsSocket={txsSocket} + setAltpaymentSocket={setAltpaymentSocket} + altpaymentSocket={altpaymentSocket} + setCoins={setCoins} + coins={coins} + setCoinPair={setCoinPair} + coinPair={coinPair} + setLoadingPair={setLoadingPair} + loadingPair={loadingPair} + setAltpaymentShift={setAltpaymentShift} + altpaymentShift={altpaymentShift} + setLoadingShift={setLoadingShift} + loadingShift={loadingShift} + setAltpaymentError={setAltpaymentError} + altpaymentError={altpaymentError} + addressType={addressType} + setAddressType={setAddressType} + setNewTxs={setNewTxs} + newTxs={newTxs} disableSound={disableSound} transactionText={transactionText} /> diff --git a/react/lib/components/PaymentDialog/PaymentDialog.tsx b/react/lib/components/PaymentDialog/PaymentDialog.tsx index 6bbd4ac6..4414575f 100644 --- a/react/lib/components/PaymentDialog/PaymentDialog.tsx +++ b/react/lib/components/PaymentDialog/PaymentDialog.tsx @@ -4,6 +4,8 @@ import { Theme, ThemeName, ThemeProvider, useTheme } from '../../themes'; import Button, { ButtonProps } from '../Button/Button'; import { WidgetContainer } from '../Widget/WidgetContainer'; import { Currency, CurrencyObject, Transaction, isPropsTrue, isValidCashAddress, isValidXecAddress } from '../../util'; +import { Socket } from 'socket.io-client'; +import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../../altpayment'; export interface PaymentDialogProps extends ButtonProps { to: string; @@ -26,6 +28,7 @@ export interface PaymentDialogProps extends ButtonProps { disableEnforceFocus?: boolean; editable?: boolean; dialogOpen: boolean; + setDialogOpen: Function; disableScrollLock?: boolean; active?: boolean; container?: HTMLElement; @@ -35,9 +38,31 @@ export interface PaymentDialogProps extends ButtonProps { wsBaseUrl?: string; apiBaseUrl?: string; disableAltpayment?: boolean; - disableSound?: boolean; + contributionOffset?: number; + useAltpayment: boolean + setUseAltpayment: Function; + txsSocket?: Socket; + setTxsSocket: Function; + altpaymentSocket?: Socket; + setAltpaymentSocket: Function; + setCoins: Function; + coins: AltpaymentCoin[]; + setCoinPair: Function; + coinPair?: AltpaymentPair; + setLoadingPair: Function; + loadingPair: boolean; + setAltpaymentShift: Function; + altpaymentShift?: AltpaymentShift; + setLoadingShift: Function; + loadingShift: boolean; + setAltpaymentError: Function; + altpaymentError?: AltpaymentError; + addressType: Currency, + setAddressType: Function, + setNewTxs: Function, + newTxs?: Transaction[] autoClose?: boolean; - contributionOffset?:number; + disableSound?: boolean; transactionText?: string } @@ -70,6 +95,7 @@ export const PaymentDialog = ( disableEnforceFocus, editable, dialogOpen, + setDialogOpen, container, wsBaseUrl, apiBaseUrl, @@ -77,6 +103,28 @@ export const PaymentDialog = ( disableAltpayment, contributionOffset, autoClose, + useAltpayment, + setUseAltpayment, + setTxsSocket, + txsSocket, + setAltpaymentSocket, + altpaymentSocket, + setCoins, + coins, + setCoinPair, + coinPair, + setLoadingPair, + loadingPair, + setAltpaymentShift, + altpaymentShift, + setLoadingShift, + loadingShift, + setAltpaymentError, + altpaymentError, + addressType, + newTxs, + setNewTxs, + setAddressType, disableSound, transactionText, } = Object.assign({}, PaymentDialog.defaultProps, props); @@ -86,6 +134,9 @@ export const PaymentDialog = ( setSuccess(false); }; const handleSuccess = (transaction: Transaction): void => { + if (dialogOpen === false) { + setDialogOpen(true) + } setSuccess(true); onSuccess?.(transaction); setTimeout(() => { @@ -131,6 +182,7 @@ export const PaymentDialog = ( transitionDuration={{ enter: 300, exit: 300 }} > = props => { animation, randomSatoshis = false, editable, - setNewTxs, newTxs, + setNewTxs, apiBaseUrl, usdPrice, wsBaseUrl, hoverText = Button.defaultProps.hoverText, setAltpaymentShift, altpaymentShift, - useAltpayment, - setUseAltpayment, shiftCompleted, setShiftCompleted, disableAltpayment, - contributionOffset + contributionOffset, + useAltpayment, + setUseAltpayment, + setTxsSocket, + txsSocket, + setAltpaymentSocket, + altpaymentSocket, + addressType, + setAddressType, + coins, + setCoins, + coinPair, + setCoinPair, + loadingPair, + setLoadingPair, + loadingShift, + setLoadingShift, + altpaymentError, + setAltpaymentError, + isChild } = Object.assign({}, Widget.defaultProps, props); const [loading, setLoading] = useState(true); + + // Define controlled websocket constants if standalone widget + + const [internalTxsSocket, setInternalTxsSocket] = useState(undefined); + const thisTxsSocket = txsSocket ?? internalTxsSocket + const setThisTxsSocket = setTxsSocket ?? setInternalTxsSocket + + const [internalNewTxs, setInternalNewTxs] = useState(); + const thisNewTxs = newTxs ?? internalNewTxs + const setThisNewTxs = setNewTxs ?? setInternalNewTxs + + const [internalAltpaymentShift, setInternalAltpaymentShift] = useState(undefined); + const thisAltpaymentShift = altpaymentShift ?? internalAltpaymentShift; + const setThisAltpaymentShift = setAltpaymentShift ?? setInternalAltpaymentShift; + + const [internalUseAltpayment, setInternalUseAltpayment] = useState(false); + const thisUseAltpayment = useAltpayment ?? internalUseAltpayment; + const setThisUseAltpayment = setUseAltpayment ?? setInternalUseAltpayment; + + const [internalAltpaymentSocket, setInternalAltpaymentSocket] = useState(undefined); + const thisAltpaymentSocket = altpaymentSocket ?? internalAltpaymentSocket; + const setThisAltpaymentSocket = setAltpaymentSocket ?? setInternalAltpaymentSocket; + + const [internalShiftCompleted, setInternalShiftCompleted] = useState(false); + const thisShiftCompleted = shiftCompleted ?? internalShiftCompleted; + const setThisShiftCompleted = setShiftCompleted ?? setInternalShiftCompleted; + + const [internalCoins, setInternalCoins] = useState([]); + const thisCoins = coins ?? internalCoins; + const setThisCoins = setCoins ?? setInternalCoins; + + const [internalCoinPair, setInternalCoinPair] = useState(); + const thisCoinPair = coinPair ?? internalCoinPair; + const setThisCoinPair = setCoinPair ?? setInternalCoinPair; + + const [internalLoadingPair, setInternalLoadingPair] = useState(false); + const thisLoadingPair = loadingPair ?? internalLoadingPair; + const setThisLoadingPair = setLoadingPair ?? setInternalLoadingPair; + + const [internalLoadingShift, setInternalLoadingShift] = useState(false); + const thisLoadingShift = loadingShift ?? internalLoadingShift; + const setThisLoadingShift = setLoadingShift ?? setInternalLoadingShift; + + const [internalAltpaymentError, setInternalAltpaymentError] = useState(); + const thisAltpaymentError = altpaymentError ?? internalAltpaymentError; + const setThisAltpaymentError = setAltpaymentError ?? setInternalAltpaymentError; + + const [internalAddressType, setInternalAddressType] = useState(getCurrencyTypeFromAddress(to)); + const thisAddressType = addressType ?? internalAddressType; + const setThisAddressType = setAddressType ?? setInternalAddressType; + const [copied, setCopied] = useState(false); const [recentlyCopied, setRecentlyCopied] = useState(false); const [totalReceived, setTotalReceived] = useState( @@ -325,25 +407,15 @@ export const Widget: React.FunctionComponent = props => { const [errorMsg, setErrorMsg] = useState(''); const [goalText, setGoalText] = useState(''); const [goalPercent, setGoalPercent] = useState(0); - const [socket, setSocket] = useState(undefined); - const [addressType, setAddressType] = useState( - getCurrencyTypeFromAddress(to), - ); + const [altpaymentEditable, setAltpaymentEditable] = useState(false); const price = props.price ?? 0; const [url, setUrl] = useState(''); const [userEditedAmount, setUserEditedAmount] = useState(); - const [text, setText] = useState(`Send any amount of ${addressType}`); + const [text, setText] = useState(`Send any amount of ${thisAddressType}`); const [widgetButtonText, setWidgetButtonText] = useState('Send Payment'); const [opReturn, setOpReturn] = useState(); - const [altpaymentSocket, setAltpaymentSocket] = useState(undefined); - const [coins, setCoins] = useState([]); - const [loadingPair, setLoadingPair] = useState(false); - const [coinPair, setCoinPair] = useState(); - const [loadingShift, setLoadingShift] = useState(false); - const [altpaymentError, setAltpaymentError] = useState(undefined); - const [altpaymentEditable, setAltpaymentEditable] = useState(false); const [isAboveMinimumAltpaymentAmount, setIsAboveMinimumAltpaymentAmount] = useState(null); const theme = useTheme(props.theme, isValidXecAddress(to)); @@ -389,61 +461,48 @@ export const Widget: React.FunctionComponent = props => { }, [price]) useEffect(() => { - const setupAltpaymentSocket = async (): Promise => { - if (altpaymentSocket !== undefined) { - altpaymentSocket.disconnect(); - } - const newSocket = io(`${wsBaseUrl ?? config.wsBaseUrl}/altpayment`, { - forceNew: true, - }); - setAltpaymentSocket(newSocket); - altpaymentListener({ - addressType, - socket: newSocket, - setCoins, - setCoinPair, - setLoadingPair, - setAltpaymentShift, - setLoadingShift, - setAltpaymentError, - }) - } - - const setupTxsSocket = async (): Promise => { - void getAddressDetails(to, apiBaseUrl); - if (socket !== undefined) { - socket.disconnect(); - } - const newSocket = io(`${wsBaseUrl ?? config.wsBaseUrl}/addresses`, { - forceNew: true, - query: { addresses: [getAddressPrefixed(to)] }, - }); - setSocket(newSocket); - txsListener(newSocket, setNewTxs); - } - (async () => { - await setupTxsSocket() - if (useAltpayment) { - await setupAltpaymentSocket() - } else if (altpaymentSocket) { - altpaymentSocket.disconnect() + if (isChild !== true) { + await setupTxsSocket({ + address: to, + txsSocket: thisTxsSocket, + apiBaseUrl, + wsBaseUrl, + setTxsSocket: setThisTxsSocket, + setNewTxs: setThisNewTxs, + }) + if (thisUseAltpayment) { + await setupAltpaymentSocket({ + addressType: thisAddressType, + wsBaseUrl, + altpaymentSocket: thisAltpaymentSocket, + setAltpaymentSocket: setThisAltpaymentSocket, + setCoins: setThisCoins, + setCoinPair: setThisCoinPair, + setLoadingPair: setThisLoadingPair, + setAltpaymentShift: setThisAltpaymentShift, + setLoadingShift: setThisLoadingShift, + setAltpaymentError: setThisAltpaymentError, + }) + } } })() return () => { - if (socket !== undefined) { - socket.disconnect(); + if (thisTxsSocket !== undefined) { + thisTxsSocket.disconnect(); + setThisTxsSocket(undefined); } - if (altpaymentSocket !== undefined) { - altpaymentSocket.disconnect(); + if (thisAltpaymentSocket !== undefined) { + thisAltpaymentSocket.disconnect(); + setThisAltpaymentSocket(undefined); } } - }, [to, useAltpayment]); + }, [to, thisUseAltpayment]); const tradeWithAltpayment = () => { - if (setUseAltpayment) { - setUseAltpayment(true) + if (setThisUseAltpayment) { + setThisUseAltpayment(true) } } @@ -462,7 +521,7 @@ export const Widget: React.FunctionComponent = props => { setTotalReceived(balance); setLoading(false); })(); - }, [newTxs]); + }, [thisNewTxs]); useEffect(() => { const invalidAmount = @@ -515,13 +574,13 @@ export const Widget: React.FunctionComponent = props => { } } - if (userEditedAmount !== undefined && thisAmount && addressType) { + if (userEditedAmount !== undefined && thisAmount && thisAddressType) { const obj = getCurrencyObject(+thisAmount, currency, false); setThisCurrencyObject(obj); if (props.setCurrencyObject) { props.setCurrencyObject(obj); } - } else if (thisAmount && addressType) { + } else if (thisAmount && thisAddressType) { cleanAmount = +thisAmount; const obj = getCurrencyObject(cleanAmount, currency, randomSatoshis); setThisCurrencyObject(obj); @@ -535,27 +594,25 @@ export const Widget: React.FunctionComponent = props => { if (to === undefined) { return; } - const address = to; let url; - const addressType: Currency = getCurrencyTypeFromAddress(address); - setAddressType(addressType); - setWidgetButtonText(`Send with ${addressType} wallet`); + setThisAddressType(thisAddressType); + setWidgetButtonText(`Send with ${thisAddressType} wallet`); if (thisCurrencyObject && hasPrice) { const convertedAmount = thisCurrencyObject.float / price const convertedObj = price ? getCurrencyObject( convertedAmount, - addressType, + thisAddressType, randomSatoshis, ) : null; if (convertedObj) { setText( - `Send ${thisCurrencyObject.string} ${thisCurrencyObject.currency} = ${convertedObj.string} ${addressType}`, + `Send ${thisCurrencyObject.string} ${thisCurrencyObject.currency} = ${convertedObj.string} ${thisAddressType}`, ); - url = resolveUrl(addressType, convertedObj.float); + url = resolveUrl(thisAddressType, convertedObj.float); setUrl(url ?? ""); } } else { @@ -566,8 +623,8 @@ export const Widget: React.FunctionComponent = props => { setText(`Send ${thisCurrencyObject.string} ${currency}`); url = resolveUrl(currency, thisCurrencyObject?.float); } else { - setText(`Send any amount of ${addressType}`); - url = resolveUrl(addressType); + setText(`Send any amount of ${thisAddressType}`); + url = resolveUrl(thisAddressType); } setUrl(url ?? ""); } @@ -637,7 +694,7 @@ export const Widget: React.FunctionComponent = props => { }, [totalReceived, currency, goalAmount, price, hasPrice, contributionOffset]); const handleButtonClick = () => { - if (addressType === 'XEC') { + if (thisAddressType === 'XEC') { const hasExtension = getCashtabProviderStatus(); if (!hasExtension) { const webUrl = `https://cashtab.com/#/send?bip21=${url}`; @@ -769,28 +826,28 @@ export const Widget: React.FunctionComponent = props => { position="relative" > {// Altpayment region - useAltpayment && + thisUseAltpayment && } @@ -906,7 +963,7 @@ export const Widget: React.FunctionComponent = props => { onClick={isAboveMinimumAltpaymentAmount || altpaymentEditable ? tradeWithAltpayment : undefined} style={{cursor: isAboveMinimumAltpaymentAmount || altpaymentEditable ? 'pointer' : 'default'}} > - Don't have any {addressType}? + Don't have any {thisAddressType}? )} diff --git a/react/lib/components/Widget/WidgetContainer.tsx b/react/lib/components/Widget/WidgetContainer.tsx index 0d451654..8f82fff8 100644 --- a/react/lib/components/Widget/WidgetContainer.tsx +++ b/react/lib/components/Widget/WidgetContainer.tsx @@ -1,6 +1,6 @@ import { OptionsObject, SnackbarProvider, useSnackbar } from 'notistack'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; -import { AltpaymentShift, getAltpaymentClient } from '../../altpayment'; +import { getAltpaymentClient } from '../../altpayment'; import successSound from '../../assets/success.mp3.json'; @@ -22,7 +22,7 @@ import { import Widget, { WidgetProps } from './Widget'; export interface WidgetContainerProps - extends Omit { + extends Omit { active?: boolean; amount?: number; opReturn?: string; @@ -46,6 +46,7 @@ export interface WidgetContainerProps successText?: string; disableAltpayment?: boolean contributionOffset?: number + setNewTxs: Function disableSound?: boolean transactionText?: string } @@ -114,6 +115,12 @@ export const WidgetContainer: React.FunctionComponent = hoverText, disableAltpayment, contributionOffset, + altpaymentShift, + setAltpaymentShift, + newTxs, + setNewTxs, + txsSocket, + isChild, disableSound, transactionText, ...widgetProps @@ -136,9 +143,6 @@ export const WidgetContainer: React.FunctionComponent = const [success, setSuccess] = useState(false); const { enqueueSnackbar } = useSnackbar(); - const [newTxs, setNewTxs] = useState(); - const [useAltpayment, setUseAltpayment] = useState(false); - const [altpaymentShift, setAltpaymentShift] = useState(); const [shiftCompleted, setShiftCompleted] = useState(false); const paymentClient = getAltpaymentClient() @@ -170,7 +174,7 @@ export const WidgetContainer: React.FunctionComponent = const receivedAmount = resolveNumber(transaction.amount); const currencyTicker = getCurrencyTypeFromAddress(to); - if (await shouldTriggerOnSuccess( + if (shouldTriggerOnSuccess( transaction, currency, thisPrice, @@ -272,6 +276,7 @@ export const WidgetContainer: React.FunctionComponent = = success={success} disabled={disabled} editable={editable} - setNewTxs={setNewTxs} newTxs={newTxs} + setNewTxs={setNewTxs} + txsSocket={txsSocket} wsBaseUrl={wsBaseUrl} apiBaseUrl={apiBaseUrl} successText={successText} hoverText={hoverText} altpaymentShift={altpaymentShift} setAltpaymentShift={setAltpaymentShift} - useAltpayment={useAltpayment} - setUseAltpayment={setUseAltpayment} shiftCompleted={shiftCompleted} setShiftCompleted={setShiftCompleted} disableAltpayment={disableAltpayment} diff --git a/react/lib/tests/util/validate.test.ts b/react/lib/tests/util/validate.test.ts index e97452cb..79523efe 100644 --- a/react/lib/tests/util/validate.test.ts +++ b/react/lib/tests/util/validate.test.ts @@ -8,7 +8,7 @@ global.fetch = jest.fn(); describe('Validate Util Tests', () => { describe('shouldTriggerOnSuccess', () => { - + it('true when amount, opReturn, and paymentId match the ones received in transaction', async () => { const transaction: Transaction = { amount: '100.00', @@ -24,8 +24,8 @@ describe('Validate Util Tests', () => { const expectedPaymentId = '123'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -35,10 +35,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(true); }); - + it('true when paymentId is undefined and received paymentId is empty', async () => { const transaction: Transaction = { amount: '101.00', @@ -54,8 +54,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn message'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -65,10 +65,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(true); }); - + it('true when both expected and received paymentId are empty', async () => { const transaction: Transaction = { amount: '101.00', @@ -84,8 +84,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn message'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -95,10 +95,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(true); }); - + it('false when paymentId does not match received paymentId', async () => { const transaction: Transaction = { amount: '100.00', @@ -114,8 +114,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn message'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -125,10 +125,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(false); }); - + it('false when amount does not match received amount', async () => { const transaction: Transaction = { amount: '101.00', @@ -144,8 +144,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn message'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -155,10 +155,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(false); }); - + it('false when opReturn message does not match received message', async () => { const transaction: Transaction = { amount: '101.00', @@ -174,8 +174,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -185,10 +185,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(false); }); - + it('should ignore amount validation when expected amount is undefined', async () => { const transaction: Transaction = { amount: '101.00', @@ -204,8 +204,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -215,10 +215,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(true); }); - + it('should ignore paymentId validation when disablePaymentId is true', async () => { const transaction: Transaction = { amount: '101.00', @@ -234,8 +234,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -245,10 +245,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(true); }); - + it('false when opReturn is undefined and received opReturn message is not empty or undefined', async () => { const transaction: Transaction = { amount: '101.00', @@ -264,8 +264,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = undefined; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -275,10 +275,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(false); }); - + it('true when opReturn is in a different format than received opReturn message but rawMessage matches expected opReturn', async () => { const transaction: Transaction = { amount: '101.00', @@ -297,8 +297,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'classOf=2013 bullYears=2013|2017|2021'; const price = 1; const currency = 'XEC'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -308,10 +308,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(true); }); - + it('false when currency is USD and currencyObject is missing', async () => { const transaction: Transaction = { amount: '101.00', @@ -327,8 +327,8 @@ describe('Validate Util Tests', () => { const expectedOpReturn = 'test opReturn'; const price = 1; const currency = 'USD'; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -338,10 +338,10 @@ describe('Validate Util Tests', () => { expectedAmount, expectedOpReturn ); - + expect(result).toBe(false); }); - + it('true when currency is USD and currencyObject is provided', async () => { const transaction: Transaction = { amount: '3152585.12', @@ -362,8 +362,8 @@ describe('Validate Util Tests', () => { string: '$100', float: 100, }; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -374,7 +374,7 @@ describe('Validate Util Tests', () => { expectedOpReturn, currencyObject ); - + expect(result).toBe(true); }); @@ -398,8 +398,8 @@ describe('Validate Util Tests', () => { string: '$100', float: 100, }; - - const result = await shouldTriggerOnSuccess( + + const result = shouldTriggerOnSuccess( transaction, currency, price, @@ -410,9 +410,9 @@ describe('Validate Util Tests', () => { expectedOpReturn, currencyObject ); - + expect(result).toBe(true); }); }); }); - \ No newline at end of file + diff --git a/react/lib/util/socket.ts b/react/lib/util/socket.ts index b089ca2b..c522a527 100644 --- a/react/lib/util/socket.ts +++ b/react/lib/util/socket.ts @@ -1,10 +1,14 @@ -import { Socket } from 'socket.io-client'; +import { io, Socket } from 'socket.io-client'; import { AltpaymentCoin, AltpaymentError, AltpaymentPair, AltpaymentShift } from '../altpayment'; +import config from '../../../paybutton-config.json'; -import { BroadcastTxData } from './types'; +import { BroadcastTxData, CheckSuccessInfo } from './types'; +import { getAddressDetails } from './api-client'; +import { getAddressPrefixed } from './address'; +import { shouldTriggerOnSuccess } from './validate'; -export const txsListener = (socket: Socket, setNewTxs: Function): void => { - socket.on('incoming-txs', (broadcastedTxData: BroadcastTxData) => { +const txsListener = (txsSocket: Socket, setNewTxs: Function, setDialogOpen?: Function, checkSuccessInfo?: CheckSuccessInfo): void => { + txsSocket.on('incoming-txs', (broadcastedTxData: BroadcastTxData) => { const unconfirmedTxs = broadcastedTxData.txs.filter( tx => tx.confirmed === false, ); @@ -12,14 +16,36 @@ export const txsListener = (socket: Socket, setNewTxs: Function): void => { broadcastedTxData.messageType === 'NewTx' && unconfirmedTxs.length !== 0 ) { - setNewTxs(unconfirmedTxs); + if (setDialogOpen !== undefined && checkSuccessInfo !== undefined) { + for (const tx of unconfirmedTxs) { + if (shouldTriggerOnSuccess( + tx, + checkSuccessInfo.currency, + checkSuccessInfo.price, + checkSuccessInfo.randomSatoshis, + checkSuccessInfo.disablePaymentId, + checkSuccessInfo.expectedPaymentId, + checkSuccessInfo.expectedAmount, + checkSuccessInfo.expectedOpReturn, + checkSuccessInfo.currencyObj + )) { + setDialogOpen(true) + setTimeout(() => { + setNewTxs(unconfirmedTxs); + }, 700); + break + } + } + } else { + setNewTxs(unconfirmedTxs); + } } }); }; interface AltpaymentListenerParams { addressType: string - socket: Socket + altpaymentSocket: Socket setCoins: Function setCoinPair: Function setLoadingPair: Function @@ -29,25 +55,84 @@ interface AltpaymentListenerParams { } export const altpaymentListener = (params: AltpaymentListenerParams): void => { - params.socket.on('send-altpayment-coins-info', (coins: AltpaymentCoin[]) => { + params.altpaymentSocket.on('send-altpayment-coins-info', (coins: AltpaymentCoin[]) => { params.setCoins(coins.filter(c => c.coin !== params.addressType)) }) - params.socket.on('shift-creation-error', (error: AltpaymentError) => { + params.altpaymentSocket.on('shift-creation-error', (error: AltpaymentError) => { params.setAltpaymentError(error) params.setLoadingShift(false) return }); - params.socket.on('quote-creation-error', (error: AltpaymentError) => { + params.altpaymentSocket.on('quote-creation-error', (error: AltpaymentError) => { params.setAltpaymentError(error) params.setLoadingShift(false) return }); - params.socket.on('shift-created', (shift: AltpaymentShift) => { + params.altpaymentSocket.on('shift-created', (shift: AltpaymentShift) => { params.setAltpaymentShift(shift) params.setLoadingShift(false) }); - params.socket.on('send-altpayment-rate', (pair: AltpaymentPair) => { + params.altpaymentSocket.on('send-altpayment-rate', (pair: AltpaymentPair) => { params.setCoinPair(pair) params.setLoadingPair(false) }) }; + +interface SetupAltpaymentSocketParams { + addressType: string + altpaymentSocket?: Socket + wsBaseUrl?: string + setAltpaymentSocket: Function + setCoins: Function + setCoinPair: Function + setLoadingPair: Function + setAltpaymentShift: Function + setLoadingShift: Function + setAltpaymentError: Function +} + +export const setupAltpaymentSocket = async (params: SetupAltpaymentSocketParams): Promise => { + if (params.altpaymentSocket !== undefined) { + params.altpaymentSocket.disconnect(); + params.setAltpaymentSocket(undefined); + } + const newSocket = io(`${params.wsBaseUrl ?? config.wsBaseUrl}/altpayment`, { + forceNew: true, + }); + params.setAltpaymentSocket(newSocket); + altpaymentListener({ + addressType: params.addressType, + altpaymentSocket: newSocket, + setCoins: params.setCoins, + setCoinPair: params.setCoinPair, + setLoadingPair: params.setLoadingPair, + setAltpaymentShift: params.setAltpaymentShift, + setLoadingShift: params.setLoadingShift, + setAltpaymentError: params.setAltpaymentError, + }) +} + +interface SetupTxsSocketParams { + address: string + txsSocket?: Socket + apiBaseUrl?: string + wsBaseUrl?: string + setTxsSocket: Function + setNewTxs: Function + setDialogOpen?: Function + checkSuccessInfo?: CheckSuccessInfo +} + +export const setupTxsSocket = async (params: SetupTxsSocketParams): Promise => { + void getAddressDetails(params.address, params.apiBaseUrl); + if (params.txsSocket !== undefined) { + params.txsSocket.disconnect(); + params.setTxsSocket(undefined); + } + const newSocket = io(`${params.wsBaseUrl ?? config.wsBaseUrl}/addresses`, { + forceNew: true, + query: { addresses: [getAddressPrefixed(params.address)] }, + }); + params.setTxsSocket(newSocket); + txsListener(newSocket, params.setNewTxs, params.setDialogOpen, params.checkSuccessInfo); +} diff --git a/react/lib/util/types.ts b/react/lib/util/types.ts index d3a9611e..964826d8 100644 --- a/react/lib/util/types.ts +++ b/react/lib/util/types.ts @@ -1,3 +1,4 @@ +import BigNumber from "bignumber.js"; import { CRYPTO_CURRENCIES, FIAT_CURRENCIES } from "./constants" export type CurrencyObject = { @@ -143,3 +144,14 @@ export interface BroadcastTxData { txs: Transaction[] messageType: TxBroadcast } + +export interface CheckSuccessInfo { + currency: Currency + price: number + randomSatoshis: number | boolean + disablePaymentId?: boolean + expectedPaymentId?: string + expectedAmount?: BigNumber | number + expectedOpReturn?: string, + currencyObj?: CurrencyObject +} diff --git a/react/lib/util/validate.ts b/react/lib/util/validate.ts index 221c3e53..874af6f9 100644 --- a/react/lib/util/validate.ts +++ b/react/lib/util/validate.ts @@ -4,7 +4,7 @@ import { resolveNumber } from "./number"; import { Currency, CurrencyObject, Transaction } from "./types"; import { DECIMALS } from "./constants"; -export const shouldTriggerOnSuccess = async ( +export const shouldTriggerOnSuccess = ( transaction: Transaction, currency: string, price: number, @@ -19,9 +19,9 @@ export const shouldTriggerOnSuccess = async ( paymentId, rawMessage:rawOpReturn, message, - amount, - address } = transaction; - + amount, + address } = transaction; + let isAmountValid = true; if(expectedAmount) { @@ -41,19 +41,19 @@ export const shouldTriggerOnSuccess = async ( } } let isPaymentIdValid = true - let isOpReturnValid = true + let isOpReturnValid = true if(!randomSatoshis || randomSatoshis === 0){ const paymentIdsMatch = expectedPaymentId === paymentId; isPaymentIdValid = disablePaymentId ? true : paymentIdsMatch; - + const rawOpReturnIsEmptyOrUndefined = rawOpReturn === '' || rawOpReturn === undefined; const opReturn = rawOpReturnIsEmptyOrUndefined ? message : rawOpReturn const opReturnIsEmptyOrUndefined = opReturn === '' || opReturn === undefined; - + const opReturnsMatch = opReturn === expectedOpReturn; isOpReturnValid = expectedOpReturn ? opReturnsMatch : opReturnIsEmptyOrUndefined; } return isAmountValid && isPaymentIdValid && isOpReturnValid; -}; \ No newline at end of file +};