Skip to content
This repository has been archived by the owner on Nov 17, 2023. It is now read-only.

Commit

Permalink
fix(ui): improve lnurl success/error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
mrfelton committed May 26, 2020
1 parent bad6762 commit 5debf94
Show file tree
Hide file tree
Showing 22 changed files with 251 additions and 140 deletions.
109 changes: 74 additions & 35 deletions electron/lnurl/service.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,22 +30,28 @@ export default class LnurlService {
this.mainWindow = mainWindow

this.withdrawParams = {}
this.isWithdrawalProcessing = false
this.isWithdrawProcessing = false

this.channelParams = {}
this.isChannelProcessing = false

ipcMain.on('lnurlFinalizeWithdraw', this.onFinishWithdrawal)
ipcMain.on('lnurlFinalizeChannel', this.onFinishChannel)
ipcMain.on('lnurlFinishWithdraw', this.onFinishWithdraw)
ipcMain.on('lnurlCancelWithdraw', this.onCancelWithdraw)

ipcMain.on('lnurlFinishChannel', this.onFinishChannel)
ipcMain.on('lnurlCancelChannel', this.onCancelChannel)
}

/**
* terminate - De-initializer routine. Must be called when particular
* `LnurlService` instance is of no use anymore.
*/
terminate() {
ipcMain.off('lnurlFinalizeWithdraw', this.onFinishWithdrawal)
ipcMain.off('lnurlFinalizeChannel', this.onFinishChannel)
ipcMain.off('lnurlFinishWithdraw', this.onFinishWithdraw)
ipcMain.off('lnurlCancelWithdraw', this.onCancelWithdraw)

ipcMain.off('lnurlFinishChannel', this.onFinishChannel)
ipcMain.off('lnurlCancelChannel', this.onCancelChannel)
}

/**
Expand Down Expand Up @@ -75,7 +81,7 @@ export default class LnurlService {
switch (res.tag) {
case 'withdrawRequest':
this.withdrawParams = res
await this.startWithdrawal(lnurl)
await this.startWithdraw(lnurl)
break

case 'channelRequest':
Expand All @@ -93,25 +99,25 @@ export default class LnurlService {
}

/**
* startWithdrawal - Initiates lnurl withdrawal process by sending query to
* startWithdraw - Initiates lnurl withdrawal process by sending query to
* renderer process to generate LN invoice.
*
* @param {string} lnurl decoded lnurl
*/
async startWithdrawal(lnurl) {
if (this.isWithdrawalProcessing) {
async startWithdraw(lnurl) {
const { status, reason, maxWithdrawable, defaultDescription } = this.withdrawParams
const service = getServiceName(lnurl)

if (this.isWithdrawProcessing) {
mainLog.warn('Error processing lnurl withdraw request: busy')
this.send('lnurlWithdrawalBusy')
this.send('lnurlWithdrawError', { service, reason: 'service busy' })
return
}
this.isWithdrawalProcessing = true

const { status, reason, maxWithdrawable, defaultDescription } = this.withdrawParams
const service = getServiceName(lnurl)
this.isWithdrawProcessing = true

if (status === LNURL_STATUS_ERROR) {
const withdrawParams = { status, reason, service }
this.isWithdrawalProcessing = false
this.isWithdrawProcessing = false
mainLog.error('Unable to process lnurl withdraw request: %o', withdrawParams)
this.send('lnurlWithdrawError', withdrawParams)
return
Expand All @@ -123,30 +129,46 @@ export default class LnurlService {
}

/**
* onFinishChannel - Finalizes an lnurl-withdraw request.
* resetWithdraw - Resets lnurl-withdraw state.
*/
resetWithdraw = () => {
this.isWithdrawProcessing = false
this.withdrawParams = {}
}

/**
* onCancelWithdraw - Cancels an lnurl-withdraw request.
*/
onCancelWithdraw = () => {
this.resetWithdraw()
mainLog.info('Cancelling lnurl withdrawal request: %o', this.withdrawParams)
}

/**
* onFinishWithdraw - Finalizes an lnurl-withdraw request.
*
* @param {object} event Event
* @param {object} data Data
*/
onFinishWithdrawal = async (event, { paymentRequest }) => {
onFinishWithdraw = async (event, { paymentRequest }) => {
mainLog.info('Finishing lnurl withdraw request: %o', this.withdrawParams)
const { callback, secret, service } = this.withdrawParams
try {
const { callback, secret } = this.withdrawParams
if (callback && secret && paymentRequest) {
const { data } = await makeWithdrawRequest({ callback, secret, invoice: paymentRequest })

if (data.status === LNURL_STATUS_ERROR) {
mainLog.warn('Unable to complete lnurl withdraw request: %o', data)
this.send('lnurlWithdrawError', data)
return
mainLog.warn('Got error from lnurl withdraw request: %s', data)
throw new Error(data.reason)
}

mainLog.info('Completed withdraw request: %o', data)
this.send('lnurlWithdrawSuccess', { service })
}
} catch (e) {
mainLog.warn('Unable to complete lnurl withdrawal request: %s', e)
mainLog.warn('Unable to complete lnurl withdraw request: %s', e.message)
this.send('lnurlWithdrawError', { service, reason: e.message })
} finally {
this.isWithdrawalProcessing = false
this.withdrawParams = {}
this.resetWithdraw()
}
}

Expand All @@ -155,15 +177,15 @@ export default class LnurlService {
* process to initiate channel connect.
*/
async startChannel() {
const { status, uri } = this.channelParams

if (this.isChannelProcessing) {
mainLog.warn('Error processing lnurl channel request: busy')
this.send('lnurlChannelBusy')
this.send('lnurlChannelError', { uri, reason: 'service busy' })
return
}
this.isChannelProcessing = true

const { status, uri } = this.channelParams

if (status === LNURL_STATUS_ERROR) {
const channelParams = { status, uri }
this.isChannelProcessing = false
Expand All @@ -177,31 +199,48 @@ export default class LnurlService {
this.send('lnurlChannelRequest', channelParams)
}

/**
* resetChannel - Resets lnurl-withdraw state.
*/
resetChannel = () => {
this.isChannelProcessing = false
this.channelParams = {}
}

/**
* onCancelChannel - Cancels an lnurl-channel request.
*/
onCancelChannel = () => {
this.resetChannel()
mainLog.info('Cancelling lnurl channel request: %o', this.channelParams)
}

/**
* onFinishChannel - Finalizes an lnurl-channel request.
*
* @param {object} event Event
* @param {object} data Data
*/
onFinishChannel = async (event, { pubkey }) => {
mainLog.info('Finishing lnurl channel request: %o', this.channelParams)
const { callback, secret, uri } = this.channelParams
try {
const { callback, secret, uri } = this.channelParams
if (callback && secret && pubkey) {
const { data } = await makeChannelRequest({ callback, secret, pubkey, isPrivate: true })

if (data.status === LNURL_STATUS_ERROR) {
mainLog.warn('Unable to complete lnurl channel request: %o', data)
this.send('lnurlChannelError', { ...data, uri })
return
mainLog.warn('Got error from lnurl channel request: %s', data)
throw new Error(data.reason)
}

mainLog.info('Completed channel request: %o', data)
this.send('lnurlChannelSuccess', { uri })
}
} catch (e) {
mainLog.warn('Unable to complete lnurl channel request: %s', e)
mainLog.warn('Unable to complete lnurl channel request: %s', e.message)
this.send('lnurlChannelError', { uri, reason: e.message })
} finally {
this.isChannelProcessing = false
this.channelParams = {}
this.resetChannel()
}
}
}
20 changes: 10 additions & 10 deletions renderer/components/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import LnurlChannelPrompt from 'containers/Channels/LnurlChannelPrompt'
import createScheduler from '@zap/utils/scheduler'
import Wallet from 'containers/Wallet'
import Activity from 'containers/Activity'
import LnurlWithdrawalPrompt from 'containers/Pay/LnurlWithdrawalPrompt'
import LnurlWithdrawPrompt from 'containers/Pay/LnurlWithdrawPrompt'

// Bitcoin blocks come on average every 10 mins
// but we poll a lot more frequently to make UI a little bit more responsive
Expand Down Expand Up @@ -43,9 +43,9 @@ const App = ({
lnurlChannelParams,
lnurlWithdrawParams,
finishLnurlChannel,
finishLnurlWithdrawal,
finishLnurlWithdraw,
willShowLnurlChannelPrompt,
willShowLnurlWithdrawalPrompt,
willShowLnurlWithdrawPrompt,
}) => {
/**
* App scheduler / polling service setup. Add new app-wide polls here
Expand Down Expand Up @@ -96,8 +96,8 @@ const App = ({
// initialize backup service in forceUseTokens mode to avoid
// launching it for wallets that don't have backup setup
initBackupService()
if (!willShowLnurlWithdrawalPrompt) {
finishLnurlWithdrawal()
if (!willShowLnurlWithdrawPrompt) {
finishLnurlWithdraw()
}
if (!willShowLnurlChannelPrompt) {
finishLnurlChannel()
Expand All @@ -112,11 +112,11 @@ const App = ({
setIsWalletOpen,
updateAutopilotNodeScores,
finishLnurlChannel,
finishLnurlWithdrawal,
finishLnurlWithdraw,
lnurlChannelParams,
lnurlWithdrawParams,
willShowLnurlChannelPrompt,
willShowLnurlWithdrawalPrompt,
willShowLnurlWithdrawPrompt,
])

// Open the pay form when a payment link is used.
Expand All @@ -136,7 +136,7 @@ const App = ({
<Flex as="article" flexDirection="column" width={1}>
<Wallet />
<Activity />
{willShowLnurlWithdrawalPrompt && <LnurlWithdrawalPrompt />}
{willShowLnurlWithdrawPrompt && <LnurlWithdrawPrompt />}
{willShowLnurlChannelPrompt && <LnurlChannelPrompt />}
</Flex>
)
Expand All @@ -148,7 +148,7 @@ App.propTypes = {
fetchSuggestedNodes: PropTypes.func.isRequired,
fetchTransactions: PropTypes.func.isRequired,
finishLnurlChannel: PropTypes.func.isRequired,
finishLnurlWithdrawal: PropTypes.func.isRequired,
finishLnurlWithdraw: PropTypes.func.isRequired,
initActivityHistory: PropTypes.func.isRequired,
initBackupService: PropTypes.func.isRequired,
initTickers: PropTypes.func.isRequired,
Expand All @@ -161,7 +161,7 @@ App.propTypes = {
setModals: PropTypes.func.isRequired,
updateAutopilotNodeScores: PropTypes.func.isRequired,
willShowLnurlChannelPrompt: PropTypes.bool,
willShowLnurlWithdrawalPrompt: PropTypes.bool,
willShowLnurlWithdrawPrompt: PropTypes.bool,
}

export default App
8 changes: 4 additions & 4 deletions renderer/components/Channels/LnurlChannelPrompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import { Dialog, Heading, Button, DialogOverlay, Text } from 'components/UI'
import { Form } from 'components/Form'
import messages from './messages'

const LnurlChannelPrompt = ({ params, onOk, onCancel }) => {
const { service, uri } = params
const LnurlChannelPrompt = ({ params, onOk, onCancel, onClose }) => {
const { uri } = params
const buttons = (
<>
<Button type="submit" variant="normal">
Expand All @@ -32,11 +32,10 @@ const LnurlChannelPrompt = ({ params, onOk, onCancel }) => {
return (
<DialogOverlay alignItems="center" justifyContent="center">
<Form onSubmit={handleSubmit}>
<Dialog buttons={buttons} header={header} onClose={onCancel} width={640}>
<Dialog buttons={buttons} header={header} onClose={onClose} width={640}>
<Text color="gray">
<FormattedMessage values={{ uri }} {...messages.lnurl_channel_prompt_dialog_body} />
</Text>
<Text>{service}</Text>
</Dialog>
</Form>
</DialogOverlay>
Expand All @@ -45,6 +44,7 @@ const LnurlChannelPrompt = ({ params, onOk, onCancel }) => {

LnurlChannelPrompt.propTypes = {
onCancel: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onOk: PropTypes.func.isRequired,
params: PropTypes.object.isRequired,
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { Dialog, Heading, Button, DialogOverlay, Text } from 'components/UI'
import { Form } from 'components/Form'
import messages from './messages'

const LnurlWithdrawalPrompt = ({ params, onOk, onCancel }) => {
const LnurlWithdrawPrompt = ({ params, onOk, onCancel, onClose }) => {
const { service, amount } = params
const buttons = (
<>
Expand All @@ -32,7 +32,7 @@ const LnurlWithdrawalPrompt = ({ params, onOk, onCancel }) => {
return (
<DialogOverlay alignItems="center" justifyContent="center">
<Form onSubmit={handleSubmit}>
<Dialog buttons={buttons} header={header} onClose={onCancel} width={640}>
<Dialog buttons={buttons} header={header} onClose={onClose} width={640}>
<Text color="gray">
<FormattedMessage
values={{ amount: amount / 1000 }}
Expand All @@ -46,10 +46,11 @@ const LnurlWithdrawalPrompt = ({ params, onOk, onCancel }) => {
)
}

LnurlWithdrawalPrompt.propTypes = {
LnurlWithdrawPrompt.propTypes = {
onCancel: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
onOk: PropTypes.func.isRequired,
params: PropTypes.object.isRequired,
}

export default LnurlWithdrawalPrompt
export default LnurlWithdrawPrompt
2 changes: 1 addition & 1 deletion renderer/components/Pay/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ export PayPanelHeader from './PayPanelHeader'
export PaySummary from './PaySummary'
export PaySummaryLightning from './PaySummaryLightning'
export PaySummaryOnChain from './PaySummaryOnChain'
export LnurlWithdrawalPrompt from './LnurlWithdrawalPrompt'
export LnurlWithdrawPrompt from './LnurlWithdrawPrompt'
6 changes: 3 additions & 3 deletions renderer/containers/App/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { updateAutopilotNodeScores } from 'reducers/autopilot'
import { initActivityHistory } from 'reducers/activity'
import { fetchTransactions } from 'reducers/transaction'
import { appSelectors } from 'reducers/app'
import { finishLnurlWithdrawal, paySelectors } from 'reducers/pay'
import { finishLnurlWithdraw, paySelectors } from 'reducers/pay'
import { initBackupService } from 'reducers/backup'
import { setModals, modalSelectors } from 'reducers/modal'
import { channelsSelectors, finishLnurlChannel, fetchSuggestedNodes } from 'reducers/channels'
Expand All @@ -21,7 +21,7 @@ const mapStateToProps = state => ({
redirectPayReq: state.pay.redirectPayReq,
modals: modalSelectors.getModalState(state),
lnurlWithdrawParams: paySelectors.lnurlWithdrawParams(state),
willShowLnurlWithdrawalPrompt: paySelectors.willShowLnurlWithdrawalPrompt(state),
willShowLnurlWithdrawPrompt: paySelectors.willShowLnurlWithdrawPrompt(state),
willShowLnurlChannelPrompt: channelsSelectors.willShowLnurlChannelPrompt(state),
})

Expand All @@ -37,7 +37,7 @@ const mapDispatchToProps = {
initBackupService,
fetchSuggestedNodes,
finishLnurlChannel,
finishLnurlWithdrawal,
finishLnurlWithdraw,
}

const ConnectedApp = connect(mapStateToProps, mapDispatchToProps)(App)
Expand Down
8 changes: 7 additions & 1 deletion renderer/containers/Channels/LnurlChannelPrompt.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { connect } from 'react-redux'
import { finishLnurlChannel, channelsSelectors, declineLnurlChannel } from 'reducers/channels'
import {
finishLnurlChannel,
channelsSelectors,
clearLnurlChannel,
declineLnurlChannel,
} from 'reducers/channels'
import { LnurlChannelPrompt } from 'components/Channels'

const mapStateToProps = state => ({
Expand All @@ -9,6 +14,7 @@ const mapStateToProps = state => ({
const mapDispatchToProps = {
onOk: finishLnurlChannel,
onCancel: declineLnurlChannel,
onClose: clearLnurlChannel,
}

export default connect(mapStateToProps, mapDispatchToProps)(LnurlChannelPrompt)
Loading

0 comments on commit 5debf94

Please sign in to comment.