use payments/sse on the core wallet UI.

still fallback to the invoice polling (now with a 5 seconds interval
because less than that is too annoying).

this fixes issues with /lnurlwallet invoices not getting paid in time,
so we update the UI automatically when they do get paid.

(see https://t.me/lnbits/7069)
This commit is contained in:
fiatjaf 2020-10-15 00:18:56 -03:00
parent f02ec67f35
commit be7d36214a
4 changed files with 59 additions and 33 deletions

View File

@ -116,6 +116,7 @@ new Vue({
show: false,
status: 'pending',
paymentReq: null,
paymentHash: null,
minMax: [0, 2100000000000000],
lnurl: null,
data: {
@ -225,6 +226,7 @@ new Vue({
this.receive.show = true
this.receive.status = 'pending'
this.receive.paymentReq = null
this.receive.paymentHash = null
this.receive.data.amount = null
this.receive.data.memo = null
this.receive.paymentChecker = null
@ -241,17 +243,25 @@ new Vue({
this.parse.camera.show = false
},
closeReceiveDialog: function () {
var checker = this.receive.paymentChecker
setTimeout(() => {
clearInterval(checker)
clearInterval(this.receive.paymentChecker)
}, 10000)
},
closeParseDialog: function () {
var checker = this.parse.paymentChecker
setTimeout(() => {
clearInterval(checker)
clearInterval(this.parse.paymentChecker)
}, 10000)
},
onPaymentReceived: function (paymentHash) {
this.fetchPayments()
this.fetchBalance()
if (this.receive.paymentHash === paymentHash) {
this.receive.show = false
this.receive.paymentHash = null
clearInterval(this.receive.paymentChecker)
}
},
createInvoice: function () {
this.receive.status = 'loading'
LNbits.api
@ -264,6 +274,7 @@ new Vue({
.then(response => {
this.receive.status = 'success'
this.receive.paymentReq = response.data.payment_request
this.receive.paymentHash = response.data.payment_hash
if (response.data.lnurl_response !== null) {
if (response.data.lnurl_response === false) {
@ -274,7 +285,7 @@ new Vue({
// failure
this.$q.notify({
timeout: 5000,
type: 'negative',
type: 'warning',
message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
caption: response.data.lnurl_response
})
@ -283,7 +294,6 @@ new Vue({
// success
this.$q.notify({
timeout: 5000,
type: 'positive',
message: `Invoice sent to ${this.receive.lnurl.domain}!`,
spinner: true
})
@ -291,17 +301,14 @@ new Vue({
}
this.receive.paymentChecker = setInterval(() => {
LNbits.api
.getPayment(this.g.wallet, response.data.payment_hash)
.then(response => {
if (response.data.paid) {
this.fetchPayments()
this.fetchBalance()
this.receive.show = false
clearInterval(this.receive.paymentChecker)
}
})
}, 2000)
let hash = response.data.payment_hash
LNbits.api.getPayment(this.g.wallet, hash).then(response => {
if (response.data.paid) {
this.onPaymentReceived(hash)
}
})
}, 5000)
})
.catch(err => {
LNbits.utils.notifyApiError(err)
@ -354,6 +361,7 @@ new Vue({
this.receive.show = true
this.receive.status = 'pending'
this.receive.paymentReq = null
this.receive.paymentHash = null
this.receive.data.amount = data.maxWithdrawable / 1000
this.receive.data.memo = data.defaultDescription
this.receive.minMax = [
@ -475,7 +483,7 @@ new Vue({
message: `<a target="_blank" style="color: inherit" href="${response.data.success_action.url}">${response.data.success_action.url}</a>`,
caption: response.data.success_action.description,
html: true,
type: 'info',
type: 'positive',
timeout: 0,
closeBtn: true
})
@ -483,7 +491,7 @@ new Vue({
case 'message':
this.$q.notify({
message: response.data.success_action.message,
type: 'info',
type: 'positive',
timeout: 0,
closeBtn: true
})
@ -491,20 +499,18 @@ new Vue({
case 'aes':
LNbits.api
.getPayment(this.g.wallet, response.data.payment_hash)
.then(
({data: payment}) =>
console.log(payment) ||
decryptLnurlPayAES(
response.data.success_action,
payment.preimage
)
.then(({data: payment}) =>
decryptLnurlPayAES(
response.data.success_action,
payment.preimage
)
)
.then(value => {
this.$q.notify({
message: value,
caption: response.data.success_action.description,
html: true,
type: 'info',
type: 'positive',
timeout: 0,
closeBtn: true
})
@ -575,6 +581,7 @@ new Vue({
setTimeout(this.checkPendingPayments(), 1200)
},
mounted: function () {
// show disclaimer
if (
this.$refs.disclaimer &&
!this.$q.localStorage.getItem('lnbits.disclaimerShown')
@ -582,5 +589,10 @@ new Vue({
this.disclaimerDialog.show = true
this.$q.localStorage.set('lnbits.disclaimerShown', true)
}
// listen to incoming payments
LNbits.events.onInvoicePaid(this.g.wallet, payment =>
this.onPaymentReceived(payment.payment_hash)
)
}
})

View File

@ -228,7 +228,7 @@ async def api_payment(payment_hash):
@core_app.route("/api/v1/payments/sse", methods=["GET"])
@api_check_wallet_key("invoice")
@api_check_wallet_key("invoice", accept_querystring=True)
async def api_payments_sse():
g.db.close()
this_wallet_id = g.wallet.id
@ -238,12 +238,12 @@ async def api_payments_sse():
print("adding sse listener", send_payment)
sse_listeners.append(send_payment)
send_event, receive_event = trio.open_memory_channel(0)
send_event, event_to_send = trio.open_memory_channel(0)
async def payment_received() -> None:
async for payment in receive_payment:
if payment.wallet_id == this_wallet_id:
await send_event.send(("payment", payment))
await send_event.send(("payment-received", payment))
async def repeat_keepalive():
await trio.sleep(1)
@ -256,7 +256,7 @@ async def api_payments_sse():
async def send_events():
try:
async for typ, data in receive_event:
async for typ, data in event_to_send:
message = [f"event: {typ}".encode("utf-8")]
if data:

View File

@ -9,12 +9,13 @@ from lnbits.core.crud import get_user, get_wallet_for_key
from lnbits.settings import LNBITS_ALLOWED_USERS
def api_check_wallet_key(key_type: str = "invoice"):
def api_check_wallet_key(key_type: str = "invoice", accept_querystring=False):
def wrap(view):
@wraps(view)
async def wrapped_view(**kwargs):
try:
g.wallet = get_wallet_for_key(request.headers["X-Api-Key"], key_type)
key_value = request.headers.get("X-Api-Key") or request.args["api-key"]
g.wallet = get_wallet_for_key(key_value, key_type)
except KeyError:
return (
jsonify({"message": "`X-Api-Key` header missing."}),

View File

@ -63,6 +63,19 @@ window.LNbits = {
)
}
},
events: {
onInvoicePaid: function (wallet, cb) {
if (!this.pis) {
this.pis = new EventSource(
'/api/v1/payments/sse?api-key=' + wallet.inkey
)
}
this.pis.addEventListener('payment-received', ev =>
cb(JSON.parse(ev.data))
)
}
},
href: {
createWallet: function (walletName, userId) {
window.location.href =