Compare commits

...
4 changed files with 145 additions and 10 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -1
View File
@@ -674,5 +674,7 @@ window.localisation.en = {
paid: 'Paid', paid: 'Paid',
funding_source_retries: 'Max Retries', funding_source_retries: 'Max Retries',
funding_source_retries_desc: funding_source_retries_desc:
'Maximum number of retries for funding sources, before it falls back to VoidWallet.' 'Maximum number of retries for funding sources, before it falls back to VoidWallet.',
ws_connection_warning:
'Lost connection to the websocket. Wait while page tries to reconnect.'
} }
+127 -8
View File
@@ -179,14 +179,79 @@ window.LNbits = {
}, },
events: { events: {
onInvoicePaid(wallet, cb) { onInvoicePaid(wallet, cb) {
ws = new WebSocket(`${websocketUrl}/${wallet.inkey}`) const reconnectInterval = 5000 // 5 seconds
ws.onmessage = ev => { const maxRetries = 12 // ~60 seconds
const data = JSON.parse(ev.data) const websocketUrlFull = `${websocketUrl}/${wallet.inkey}`
if (data.payment) {
cb(data) if (!g.activeWebsockets) g.activeWebsockets = {}
if (!g.disconnectedWallets) g.disconnectedWallets = new Set()
const updateConnectionWarning = () => {
g.connectionWarning = g.disconnectedWallets.size > 0
}
const connect = (retryCount = 0) => {
try {
const ws = new WebSocket(websocketUrlFull)
g.activeWebsockets[wallet.id] = ws
console.debug(
`Connecting WebSocket for wallet ${wallet.name}... (attempt ${retryCount + 1})`
)
ws.onopen = () => {
console.debug(`WebSocket connected for wallet ${wallet.name}`)
g.disconnectedWallets.delete(wallet.id)
updateConnectionWarning()
}
ws.onmessage = ev => {
try {
const data = JSON.parse(ev.data)
if (data.payment) cb(data)
} catch (err) {
console.error('Error parsing WebSocket message:', err)
}
}
ws.onerror = err => {
console.error(`WebSocket error for wallet ${wallet.name}:`, err)
ws.close()
}
ws.onclose = () => {
g.disconnectedWallets.add(wallet.id)
updateConnectionWarning()
if (retryCount < maxRetries) {
console.warn(
`WebSocket closed for wallet ${wallet.name}. Reconnecting in ${reconnectInterval / 1000}s...`
)
setTimeout(() => connect(retryCount + 1), reconnectInterval)
} else {
console.error(
`WebSocket for wallet ${wallet.name} failed after ${maxRetries} attempts. Stopping retries.`
)
}
}
} catch (err) {
console.error(
`WebSocket connection failed for wallet ${wallet.name}:`,
err
)
g.disconnectedWallets.add(wallet.id)
updateConnectionWarning()
if (retryCount < maxRetries) {
setTimeout(() => connect(retryCount + 1), reconnectInterval)
} else {
console.error(
`[LNbits] Stopped retrying wallet ${wallet.name} after ${maxRetries} attempts.`
)
}
} }
} }
return ws.onclose
connect()
} }
}, },
map: { map: {
@@ -459,7 +524,9 @@ if (!window.g) {
langs: [], langs: [],
walletEventListeners: [], walletEventListeners: [],
updatePayments: false, updatePayments: false,
updatePaymentsHash: '' updatePaymentsHash: '',
wsDisconnectionNotification: false,
wsReconnectedNotification: false
}) })
} }
@@ -494,7 +561,8 @@ window.windowMixin = {
bgimageChoice: this.$q.localStorage.has('lnbits.backgroundImage') bgimageChoice: this.$q.localStorage.has('lnbits.backgroundImage')
? this.$q.localStorage.getItem('lnbits.backgroundImage') ? this.$q.localStorage.getItem('lnbits.backgroundImage')
: USE_DEFAULT_BGIMAGE, : USE_DEFAULT_BGIMAGE,
...WINDOW_SETTINGS ...WINDOW_SETTINGS,
silentConnectionCleanup: false
} }
}, },
@@ -802,6 +870,57 @@ window.windowMixin = {
mounted() { mounted() {
if (this.g.user) { if (this.g.user) {
this.paymentEvents() this.paymentEvents()
// Retry timer reference (for cleanup)
this.connectionRetryTimer = setInterval(() => {
if (this.g.disconnectedWallets && this.g.disconnectedWallets.size > 0) {
console.warn('Some wallets are still disconnected. Retrying...')
this.paymentEvents()
}
}, 60000)
}
},
beforeUnmount() {
this.silentConnectionCleanup = true
// Prevent background timers when component is destroyed
if (this.connectionRetryTimer) {
clearInterval(this.connectionRetryTimer)
this.connectionRetryTimer = null
}
},
watch: {
'g.connectionWarning': {
handler(newVal, oldVal) {
if (this.silentConnectionCleanup) return
if (newVal && !g.wsDisconnectionNotification) {
this.$q.notify({
message: 'Some wallet connections are lost. Retrying...',
color: 'negative',
icon: null,
position: 'top',
timeout: 5000
})
g.wsDisconnectionNotification = true
g.wsReconnectedNotification = false
} else if (
oldVal === true &&
newVal === false &&
!g.wsReconnectedNotification
) {
this.$q.notify({
message: 'All wallet connections restored.',
color: 'positive',
icon: null,
position: 'top',
timeout: 3000
})
g.wsReconnectedNotification = true
g.wsDisconnectionNotification = false
}
},
immediate: false
} }
} }
} }
+14
View File
@@ -129,6 +129,20 @@
>{% endblock %} {% block toolbar_subtitle %} >{% endblock %} {% block toolbar_subtitle %}
<q-badge v-if="g.user && g.user.super_user">Super User</q-badge> <q-badge v-if="g.user && g.user.super_user">Super User</q-badge>
<q-badge v-else-if="g.user && g.user.admin">Admin User</q-badge> <q-badge v-else-if="g.user && g.user.admin">Admin User</q-badge>
<q-badge
v-if="g.connectionWarning"
color="red"
class="inline-block q-ml-sm"
>
<q-icon
name="wifi_tethering_off"
color="white"
size="12px"
></q-icon>
<q-tooltip
><span v-text="$t('ws_connection_warning')"></span
></q-tooltip>
</q-badge>
{% endblock %} {% endblock %}
</q-toolbar-title> </q-toolbar-title>
{% block beta %} {% if LNBITS_CUSTOM_BADGE is not none and {% block beta %} {% if LNBITS_CUSTOM_BADGE is not none and