mirror of
https://github.com/lnbits/lnbits.git
synced 2025-09-27 20:36:16 +02:00
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:
@@ -116,6 +116,7 @@ new Vue({
|
|||||||
show: false,
|
show: false,
|
||||||
status: 'pending',
|
status: 'pending',
|
||||||
paymentReq: null,
|
paymentReq: null,
|
||||||
|
paymentHash: null,
|
||||||
minMax: [0, 2100000000000000],
|
minMax: [0, 2100000000000000],
|
||||||
lnurl: null,
|
lnurl: null,
|
||||||
data: {
|
data: {
|
||||||
@@ -225,6 +226,7 @@ new Vue({
|
|||||||
this.receive.show = true
|
this.receive.show = true
|
||||||
this.receive.status = 'pending'
|
this.receive.status = 'pending'
|
||||||
this.receive.paymentReq = null
|
this.receive.paymentReq = null
|
||||||
|
this.receive.paymentHash = null
|
||||||
this.receive.data.amount = null
|
this.receive.data.amount = null
|
||||||
this.receive.data.memo = null
|
this.receive.data.memo = null
|
||||||
this.receive.paymentChecker = null
|
this.receive.paymentChecker = null
|
||||||
@@ -241,17 +243,25 @@ new Vue({
|
|||||||
this.parse.camera.show = false
|
this.parse.camera.show = false
|
||||||
},
|
},
|
||||||
closeReceiveDialog: function () {
|
closeReceiveDialog: function () {
|
||||||
var checker = this.receive.paymentChecker
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
clearInterval(checker)
|
clearInterval(this.receive.paymentChecker)
|
||||||
}, 10000)
|
}, 10000)
|
||||||
},
|
},
|
||||||
closeParseDialog: function () {
|
closeParseDialog: function () {
|
||||||
var checker = this.parse.paymentChecker
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
clearInterval(checker)
|
clearInterval(this.parse.paymentChecker)
|
||||||
}, 10000)
|
}, 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 () {
|
createInvoice: function () {
|
||||||
this.receive.status = 'loading'
|
this.receive.status = 'loading'
|
||||||
LNbits.api
|
LNbits.api
|
||||||
@@ -264,6 +274,7 @@ new Vue({
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
this.receive.status = 'success'
|
this.receive.status = 'success'
|
||||||
this.receive.paymentReq = response.data.payment_request
|
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 !== null) {
|
||||||
if (response.data.lnurl_response === false) {
|
if (response.data.lnurl_response === false) {
|
||||||
@@ -274,7 +285,7 @@ new Vue({
|
|||||||
// failure
|
// failure
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
type: 'negative',
|
type: 'warning',
|
||||||
message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
|
message: `${this.receive.lnurl.domain} lnurl-withdraw call failed.`,
|
||||||
caption: response.data.lnurl_response
|
caption: response.data.lnurl_response
|
||||||
})
|
})
|
||||||
@@ -283,7 +294,6 @@ new Vue({
|
|||||||
// success
|
// success
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
timeout: 5000,
|
timeout: 5000,
|
||||||
type: 'positive',
|
|
||||||
message: `Invoice sent to ${this.receive.lnurl.domain}!`,
|
message: `Invoice sent to ${this.receive.lnurl.domain}!`,
|
||||||
spinner: true
|
spinner: true
|
||||||
})
|
})
|
||||||
@@ -291,17 +301,14 @@ new Vue({
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.receive.paymentChecker = setInterval(() => {
|
this.receive.paymentChecker = setInterval(() => {
|
||||||
LNbits.api
|
let hash = response.data.payment_hash
|
||||||
.getPayment(this.g.wallet, response.data.payment_hash)
|
|
||||||
.then(response => {
|
LNbits.api.getPayment(this.g.wallet, hash).then(response => {
|
||||||
if (response.data.paid) {
|
if (response.data.paid) {
|
||||||
this.fetchPayments()
|
this.onPaymentReceived(hash)
|
||||||
this.fetchBalance()
|
}
|
||||||
this.receive.show = false
|
})
|
||||||
clearInterval(this.receive.paymentChecker)
|
}, 5000)
|
||||||
}
|
|
||||||
})
|
|
||||||
}, 2000)
|
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
LNbits.utils.notifyApiError(err)
|
LNbits.utils.notifyApiError(err)
|
||||||
@@ -354,6 +361,7 @@ new Vue({
|
|||||||
this.receive.show = true
|
this.receive.show = true
|
||||||
this.receive.status = 'pending'
|
this.receive.status = 'pending'
|
||||||
this.receive.paymentReq = null
|
this.receive.paymentReq = null
|
||||||
|
this.receive.paymentHash = null
|
||||||
this.receive.data.amount = data.maxWithdrawable / 1000
|
this.receive.data.amount = data.maxWithdrawable / 1000
|
||||||
this.receive.data.memo = data.defaultDescription
|
this.receive.data.memo = data.defaultDescription
|
||||||
this.receive.minMax = [
|
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>`,
|
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,
|
caption: response.data.success_action.description,
|
||||||
html: true,
|
html: true,
|
||||||
type: 'info',
|
type: 'positive',
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
closeBtn: true
|
closeBtn: true
|
||||||
})
|
})
|
||||||
@@ -483,7 +491,7 @@ new Vue({
|
|||||||
case 'message':
|
case 'message':
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
message: response.data.success_action.message,
|
message: response.data.success_action.message,
|
||||||
type: 'info',
|
type: 'positive',
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
closeBtn: true
|
closeBtn: true
|
||||||
})
|
})
|
||||||
@@ -491,20 +499,18 @@ new Vue({
|
|||||||
case 'aes':
|
case 'aes':
|
||||||
LNbits.api
|
LNbits.api
|
||||||
.getPayment(this.g.wallet, response.data.payment_hash)
|
.getPayment(this.g.wallet, response.data.payment_hash)
|
||||||
.then(
|
.then(({data: payment}) =>
|
||||||
({data: payment}) =>
|
decryptLnurlPayAES(
|
||||||
console.log(payment) ||
|
response.data.success_action,
|
||||||
decryptLnurlPayAES(
|
payment.preimage
|
||||||
response.data.success_action,
|
)
|
||||||
payment.preimage
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.then(value => {
|
.then(value => {
|
||||||
this.$q.notify({
|
this.$q.notify({
|
||||||
message: value,
|
message: value,
|
||||||
caption: response.data.success_action.description,
|
caption: response.data.success_action.description,
|
||||||
html: true,
|
html: true,
|
||||||
type: 'info',
|
type: 'positive',
|
||||||
timeout: 0,
|
timeout: 0,
|
||||||
closeBtn: true
|
closeBtn: true
|
||||||
})
|
})
|
||||||
@@ -575,6 +581,7 @@ new Vue({
|
|||||||
setTimeout(this.checkPendingPayments(), 1200)
|
setTimeout(this.checkPendingPayments(), 1200)
|
||||||
},
|
},
|
||||||
mounted: function () {
|
mounted: function () {
|
||||||
|
// show disclaimer
|
||||||
if (
|
if (
|
||||||
this.$refs.disclaimer &&
|
this.$refs.disclaimer &&
|
||||||
!this.$q.localStorage.getItem('lnbits.disclaimerShown')
|
!this.$q.localStorage.getItem('lnbits.disclaimerShown')
|
||||||
@@ -582,5 +589,10 @@ new Vue({
|
|||||||
this.disclaimerDialog.show = true
|
this.disclaimerDialog.show = true
|
||||||
this.$q.localStorage.set('lnbits.disclaimerShown', true)
|
this.$q.localStorage.set('lnbits.disclaimerShown', true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// listen to incoming payments
|
||||||
|
LNbits.events.onInvoicePaid(this.g.wallet, payment =>
|
||||||
|
this.onPaymentReceived(payment.payment_hash)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@@ -228,7 +228,7 @@ async def api_payment(payment_hash):
|
|||||||
|
|
||||||
|
|
||||||
@core_app.route("/api/v1/payments/sse", methods=["GET"])
|
@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():
|
async def api_payments_sse():
|
||||||
g.db.close()
|
g.db.close()
|
||||||
this_wallet_id = g.wallet.id
|
this_wallet_id = g.wallet.id
|
||||||
@@ -238,12 +238,12 @@ async def api_payments_sse():
|
|||||||
print("adding sse listener", send_payment)
|
print("adding sse listener", send_payment)
|
||||||
sse_listeners.append(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 def payment_received() -> None:
|
||||||
async for payment in receive_payment:
|
async for payment in receive_payment:
|
||||||
if payment.wallet_id == this_wallet_id:
|
if payment.wallet_id == this_wallet_id:
|
||||||
await send_event.send(("payment", payment))
|
await send_event.send(("payment-received", payment))
|
||||||
|
|
||||||
async def repeat_keepalive():
|
async def repeat_keepalive():
|
||||||
await trio.sleep(1)
|
await trio.sleep(1)
|
||||||
@@ -256,7 +256,7 @@ async def api_payments_sse():
|
|||||||
|
|
||||||
async def send_events():
|
async def send_events():
|
||||||
try:
|
try:
|
||||||
async for typ, data in receive_event:
|
async for typ, data in event_to_send:
|
||||||
message = [f"event: {typ}".encode("utf-8")]
|
message = [f"event: {typ}".encode("utf-8")]
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
|
@@ -9,12 +9,13 @@ from lnbits.core.crud import get_user, get_wallet_for_key
|
|||||||
from lnbits.settings import LNBITS_ALLOWED_USERS
|
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):
|
def wrap(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
async def wrapped_view(**kwargs):
|
async def wrapped_view(**kwargs):
|
||||||
try:
|
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:
|
except KeyError:
|
||||||
return (
|
return (
|
||||||
jsonify({"message": "`X-Api-Key` header missing."}),
|
jsonify({"message": "`X-Api-Key` header missing."}),
|
||||||
|
@@ -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: {
|
href: {
|
||||||
createWallet: function (walletName, userId) {
|
createWallet: function (walletName, userId) {
|
||||||
window.location.href =
|
window.location.href =
|
||||||
|
Reference in New Issue
Block a user