feat: ws bridge
This commit is contained in:
@@ -39,6 +39,10 @@ WASM_EXTENSION_CORE_STATIC_ASSETS = {
|
|||||||
"static/vendor/quasar.umd.prod.js",
|
"static/vendor/quasar.umd.prod.js",
|
||||||
"text/javascript; charset=utf-8",
|
"text/javascript; charset=utf-8",
|
||||||
),
|
),
|
||||||
|
"qrcode.vue.browser.js": (
|
||||||
|
"static/vendor/qrcode.vue.browser.js",
|
||||||
|
"text/javascript; charset=utf-8",
|
||||||
|
),
|
||||||
"vue.global.prod.js": (
|
"vue.global.prod.js": (
|
||||||
"static/vendor/vue.global.prod.js",
|
"static/vendor/vue.global.prod.js",
|
||||||
"text/javascript; charset=utf-8",
|
"text/javascript; charset=utf-8",
|
||||||
|
|||||||
@@ -37,6 +37,8 @@
|
|||||||
;(() => {
|
;(() => {
|
||||||
const bridge = {{ bridge | tojson | safe }}
|
const bridge = {{ bridge | tojson | safe }}
|
||||||
let bridgePort = null
|
let bridgePort = null
|
||||||
|
const allowedPaymentHashes = new Set()
|
||||||
|
const paymentSubscriptions = new Map()
|
||||||
|
|
||||||
function extensionFrameWindow() {
|
function extensionFrameWindow() {
|
||||||
return extensionFrame()?.contentWindow
|
return extensionFrame()?.contentWindow
|
||||||
@@ -49,8 +51,7 @@
|
|||||||
function loadExtensionFrame() {
|
function loadExtensionFrame() {
|
||||||
const frame = extensionFrame()
|
const frame = extensionFrame()
|
||||||
if (!frame) {
|
if (!frame) {
|
||||||
bridgePort?.close()
|
closeBridgePort()
|
||||||
bridgePort = null
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (frame.dataset.loaded === 'true') return
|
if (frame.dataset.loaded === 'true') return
|
||||||
@@ -123,6 +124,7 @@
|
|||||||
typeof data === 'object' && data.detail ? data.detail : text
|
typeof data === 'object' && data.detail ? data.detail : text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
rememberPaymentHashes(data)
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,6 +142,126 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function rememberPaymentHashes(value) {
|
||||||
|
if (!value || typeof value !== 'object') return
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
value.forEach(rememberPaymentHashes)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, item] of Object.entries(value)) {
|
||||||
|
if (
|
||||||
|
['paymentHash', 'payment_hash'].includes(key) &&
|
||||||
|
isPaymentHash(item)
|
||||||
|
) {
|
||||||
|
allowedPaymentHashes.add(item)
|
||||||
|
}
|
||||||
|
rememberPaymentHashes(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPaymentHash(value) {
|
||||||
|
return typeof value === 'string' && /^[a-f0-9]{64}$/i.test(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function websocketUrl(path) {
|
||||||
|
const url = new URL(window.location.href)
|
||||||
|
url.protocol = url.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||||
|
url.pathname = path
|
||||||
|
url.search = ''
|
||||||
|
url.hash = ''
|
||||||
|
return url.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendBridgeEvent(message) {
|
||||||
|
if (!bridgePort) return
|
||||||
|
bridgePort.postMessage({
|
||||||
|
type: 'lnbits-extension:event',
|
||||||
|
...message
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePaymentSubscription(subscriptionId) {
|
||||||
|
const subscription = paymentSubscriptions.get(subscriptionId)
|
||||||
|
if (!subscription) return
|
||||||
|
paymentSubscriptions.delete(subscriptionId)
|
||||||
|
try {
|
||||||
|
subscription.socket.close()
|
||||||
|
} catch (_error) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closePaymentSubscriptions() {
|
||||||
|
for (const subscriptionId of Array.from(paymentSubscriptions.keys())) {
|
||||||
|
closePaymentSubscription(subscriptionId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeBridgePort() {
|
||||||
|
closePaymentSubscriptions()
|
||||||
|
bridgePort?.close()
|
||||||
|
bridgePort = null
|
||||||
|
}
|
||||||
|
|
||||||
|
function subscribePayment(message) {
|
||||||
|
const subscriptionId = String(message.subscriptionId || '')
|
||||||
|
const paymentHash = String(message.paymentHash || '')
|
||||||
|
|
||||||
|
if (!subscriptionId || !isPaymentHash(paymentHash)) {
|
||||||
|
throw new Error('Invalid payment subscription.')
|
||||||
|
}
|
||||||
|
if (!allowedPaymentHashes.has(paymentHash)) {
|
||||||
|
throw new Error('Payment subscription is not allowed.')
|
||||||
|
}
|
||||||
|
|
||||||
|
closePaymentSubscription(subscriptionId)
|
||||||
|
|
||||||
|
const socket = new WebSocket(
|
||||||
|
websocketUrl(`/api/v1/ws/${encodeURIComponent(paymentHash)}`)
|
||||||
|
)
|
||||||
|
paymentSubscriptions.set(subscriptionId, {paymentHash, socket})
|
||||||
|
|
||||||
|
socket.addEventListener('message', event => {
|
||||||
|
let data = event.data
|
||||||
|
try {
|
||||||
|
data = JSON.parse(event.data)
|
||||||
|
} catch (_error) {}
|
||||||
|
|
||||||
|
sendBridgeEvent({
|
||||||
|
event: 'payment.update',
|
||||||
|
subscriptionId,
|
||||||
|
paymentHash,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
|
||||||
|
if (
|
||||||
|
data &&
|
||||||
|
typeof data === 'object' &&
|
||||||
|
(data.pending === false ||
|
||||||
|
['success', 'settled', 'paid'].includes(String(data.status || '')))
|
||||||
|
) {
|
||||||
|
sendBridgeEvent({
|
||||||
|
event: 'payment.settled',
|
||||||
|
subscriptionId,
|
||||||
|
paymentHash,
|
||||||
|
data
|
||||||
|
})
|
||||||
|
closePaymentSubscription(subscriptionId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
socket.addEventListener('error', () => {
|
||||||
|
sendBridgeEvent({
|
||||||
|
event: 'payment.error',
|
||||||
|
subscriptionId,
|
||||||
|
paymentHash
|
||||||
|
})
|
||||||
|
closePaymentSubscription(subscriptionId)
|
||||||
|
})
|
||||||
|
socket.addEventListener('close', () => {
|
||||||
|
paymentSubscriptions.delete(subscriptionId)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function handleBridgeRequest(message, reply) {
|
async function handleBridgeRequest(message, reply) {
|
||||||
if (!message || message.type !== 'lnbits-extension:request') return
|
if (!message || message.type !== 'lnbits-extension:request') return
|
||||||
|
|
||||||
@@ -174,6 +296,24 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.action === 'payment.subscribe') {
|
||||||
|
subscribePayment(message)
|
||||||
|
sendResponse(reply, message.id, {
|
||||||
|
ok: true,
|
||||||
|
data: {ok: true}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.action === 'payment.unsubscribe') {
|
||||||
|
closePaymentSubscription(String(message.subscriptionId || ''))
|
||||||
|
sendResponse(reply, message.id, {
|
||||||
|
ok: true,
|
||||||
|
data: {ok: true}
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error('Unknown extension bridge action.')
|
throw new Error('Unknown extension bridge action.')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
sendResponse(reply, message.id, {
|
sendResponse(reply, message.id, {
|
||||||
@@ -191,7 +331,7 @@
|
|||||||
const port = event.ports?.[0]
|
const port = event.ports?.[0]
|
||||||
if (!port) return
|
if (!port) return
|
||||||
|
|
||||||
bridgePort?.close()
|
closeBridgePort()
|
||||||
bridgePort = port
|
bridgePort = port
|
||||||
bridgePort.addEventListener('message', portEvent => {
|
bridgePort.addEventListener('message', portEvent => {
|
||||||
handleBridgeRequest(portEvent.data, response => {
|
handleBridgeRequest(portEvent.data, response => {
|
||||||
|
|||||||
Reference in New Issue
Block a user