diff --git a/docs/devs/websockets.md b/docs/devs/websockets.md index 0638e4f28..9ea53a624 100644 --- a/docs/devs/websockets.md +++ b/docs/devs/websockets.md @@ -9,53 +9,10 @@ nav_order: 2 Websockets ================= -`websockets` are a great way to add a two way instant data channel between server and client. This example was taken from the `copilot` extension, we create a websocket endpoint which can be restricted by `id`, then can feed it data to broadcast to any client on the socket using the `updater(extension_id, data)` function (`extension` has been used in place of an extension name, wreplace to your own extension): +`websockets` are a great way to add a two way instant data channel between server and client. +LNbits has a useful in built websocket tool. With a websocket client connect to (obv change `somespecificid`) `wss://legend.lnbits.com/api/v1/ws/somespecificid` (you can use an online websocket tester). Now make a get to `https://legend.lnbits.com/api/v1/ws/somespecificid/somedata`. You can send data to that websocket by using `from lnbits.core.services import websocketUpdater` and the function `websocketUpdater("somespecificid", "somdata")`. -```sh -from fastapi import Request, WebSocket, WebSocketDisconnect - -class ConnectionManager: - def __init__(self): - self.active_connections: List[WebSocket] = [] - - async def connect(self, websocket: WebSocket, extension_id: str): - await websocket.accept() - websocket.id = extension_id - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, extension_id: str): - for connection in self.active_connections: - if connection.id == extension_id: - await connection.send_text(message) - - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - - -@extension_ext.websocket("/ws/{extension_id}", name="extension.websocket_by_id") -async def websocket_endpoint(websocket: WebSocket, extension_id: str): - await manager.connect(websocket, extension_id) - try: - while True: - data = await websocket.receive_text() - except WebSocketDisconnect: - manager.disconnect(websocket) - - -async def updater(extension_id, data): - extension = await get_extension(extension_id) - if not extension: - return - await manager.send_personal_message(f"{data}", extension_id) -``` Example vue-js function for listening to the websocket: @@ -67,16 +24,16 @@ initWs: async function () { document.domain + ':' + location.port + - '/extension/ws/' + - self.extension.id + '/api/v1/ws/' + + self.item.id } else { localUrl = 'ws://' + document.domain + ':' + location.port + - '/extension/ws/' + - self.extension.id + '/api/v1/ws/' + + self.item.id } this.ws = new WebSocket(localUrl) this.ws.addEventListener('message', async ({data}) => { diff --git a/lnbits/bolt11.py b/lnbits/bolt11.py index 08f1f1e59..32b43feb6 100644 --- a/lnbits/bolt11.py +++ b/lnbits/bolt11.py @@ -166,7 +166,7 @@ def lnencode(addr, privkey): if addr.amount: amount = Decimal(str(addr.amount)) # We can only send down to millisatoshi. - if amount * 10 ** 12 % 10: + if amount * 10**12 % 10: raise ValueError( "Cannot encode {}: too many decimal places".format(addr.amount) ) @@ -271,7 +271,7 @@ class LnAddr(object): def shorten_amount(amount): """Given an amount in bitcoin, shorten it""" # Convert to pico initially - amount = int(amount * 10 ** 12) + amount = int(amount * 10**12) units = ["p", "n", "u", "m", ""] for unit in units: if amount % 1000 == 0: @@ -290,7 +290,7 @@ def _unshorten_amount(amount: str) -> int: # * `u` (micro): multiply by 0.000001 # * `n` (nano): multiply by 0.000000001 # * `p` (pico): multiply by 0.000000000001 - units = {"p": 10 ** 12, "n": 10 ** 9, "u": 10 ** 6, "m": 10 ** 3} + units = {"p": 10**12, "n": 10**9, "u": 10**6, "m": 10**3} unit = str(amount)[-1] # BOLT #11: diff --git a/lnbits/core/services.py b/lnbits/core/services.py index 623f78139..beb0f97ad 100644 --- a/lnbits/core/services.py +++ b/lnbits/core/services.py @@ -329,12 +329,12 @@ async def perform_lnurlauth( sign_len = 6 + r_len + s_len signature = BytesIO() - signature.write(0x30 .to_bytes(1, "big", signed=False)) + signature.write(0x30.to_bytes(1, "big", signed=False)) signature.write((sign_len - 2).to_bytes(1, "big", signed=False)) - signature.write(0x02 .to_bytes(1, "big", signed=False)) + signature.write(0x02.to_bytes(1, "big", signed=False)) signature.write(r_len.to_bytes(1, "big", signed=False)) signature.write(r) - signature.write(0x02 .to_bytes(1, "big", signed=False)) + signature.write(0x02.to_bytes(1, "big", signed=False)) signature.write(s_len.to_bytes(1, "big", signed=False)) signature.write(s) diff --git a/lnbits/extensions/copilot/tasks.py b/lnbits/extensions/copilot/tasks.py index 48ad7813d..384070cdf 100644 --- a/lnbits/extensions/copilot/tasks.py +++ b/lnbits/extensions/copilot/tasks.py @@ -7,11 +7,11 @@ from starlette.exceptions import HTTPException from lnbits.core import db as core_db from lnbits.core.models import Payment +from lnbits.core.services import websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_copilot -from .views import updater async def wait_for_paid_invoices(): @@ -65,9 +65,11 @@ async def on_invoice_paid(payment: Payment) -> None: except (httpx.ConnectError, httpx.RequestError): await mark_webhook_sent(payment, -1) if payment.extra.get("comment"): - await updater(copilot.id, data, payment.extra.get("comment")) + await websocketUpdater( + copilot.id, str(data) + "-" + str(payment.extra.get("comment")) + ) - await updater(copilot.id, data, "none") + await websocketUpdater(copilot.id, str(data) + "-none") async def mark_webhook_sent(payment: Payment, status: int) -> None: diff --git a/lnbits/extensions/copilot/templates/copilot/compose.html b/lnbits/extensions/copilot/templates/copilot/compose.html index b4022ee0f..ea44114c1 100644 --- a/lnbits/extensions/copilot/templates/copilot/compose.html +++ b/lnbits/extensions/copilot/templates/copilot/compose.html @@ -238,7 +238,7 @@ document.domain + ':' + location.port + - '/copilot/ws/' + + '/api/v1/ws/' + self.copilot.id } else { localUrl = @@ -246,7 +246,7 @@ document.domain + ':' + location.port + - '/copilot/ws/' + + '/api/v1/ws/' + self.copilot.id } this.connection = new WebSocket(localUrl) diff --git a/lnbits/extensions/copilot/views.py b/lnbits/extensions/copilot/views.py index b4a2354a0..3b1ebf032 100644 --- a/lnbits/extensions/copilot/views.py +++ b/lnbits/extensions/copilot/views.py @@ -35,48 +35,3 @@ async def panel(request: Request): return copilot_renderer().TemplateResponse( "copilot/panel.html", {"request": request} ) - - -##################WEBSOCKET ROUTES######################## - - -class ConnectionManager: - def __init__(self): - self.active_connections: List[WebSocket] = [] - - async def connect(self, websocket: WebSocket, copilot_id: str): - await websocket.accept() - websocket.id = copilot_id # type: ignore - self.active_connections.append(websocket) - - def disconnect(self, websocket: WebSocket): - self.active_connections.remove(websocket) - - async def send_personal_message(self, message: str, copilot_id: str): - for connection in self.active_connections: - if connection.id == copilot_id: # type: ignore - await connection.send_text(message) - - async def broadcast(self, message: str): - for connection in self.active_connections: - await connection.send_text(message) - - -manager = ConnectionManager() - - -@copilot_ext.websocket("/ws/{copilot_id}", name="copilot.websocket_by_id") -async def websocket_endpoint(websocket: WebSocket, copilot_id: str): - await manager.connect(websocket, copilot_id) - try: - while True: - data = await websocket.receive_text() - except WebSocketDisconnect: - manager.disconnect(websocket) - - -async def updater(copilot_id, data, comment): - copilot = await get_copilot(copilot_id) - if not copilot: - return - await manager.send_personal_message(f"{data + '-' + comment}", copilot_id) diff --git a/lnbits/extensions/copilot/views_api.py b/lnbits/extensions/copilot/views_api.py index 46611a2ea..39d0f7fd9 100644 --- a/lnbits/extensions/copilot/views_api.py +++ b/lnbits/extensions/copilot/views_api.py @@ -5,6 +5,7 @@ from fastapi.param_functions import Query from fastapi.params import Depends from starlette.exceptions import HTTPException +from lnbits.core.services import websocketUpdater from lnbits.decorators import WalletTypeInfo, get_key_type, require_admin_key from . import copilot_ext @@ -16,7 +17,6 @@ from .crud import ( update_copilot, ) from .models import CreateCopilotData -from .views import updater #######################COPILOT########################## @@ -92,7 +92,7 @@ async def api_copilot_ws_relay( status_code=HTTPStatus.NOT_FOUND, detail="Copilot does not exist" ) try: - await updater(copilot_id, data, comment) + await websocketUpdater(copilot_id, str(data) + "-" + str(comment)) except: raise HTTPException(status_code=HTTPStatus.FORBIDDEN, detail="Not your copilot") return "" diff --git a/lnbits/extensions/lnurldevice/tasks.py b/lnbits/extensions/lnurldevice/tasks.py index d3248ad57..8ad9772ca 100644 --- a/lnbits/extensions/lnurldevice/tasks.py +++ b/lnbits/extensions/lnurldevice/tasks.py @@ -8,12 +8,11 @@ from fastapi import HTTPException from lnbits import bolt11 from lnbits.core.models import Payment -from lnbits.core.services import pay_invoice +from lnbits.core.services import pay_invoice, websocketUpdater from lnbits.helpers import get_current_extension_name from lnbits.tasks import register_invoice_listener from .crud import get_lnurldevice, get_lnurldevicepayment, update_lnurldevicepayment -from .views import updater async def wait_for_paid_invoices(): @@ -36,9 +35,8 @@ async def on_invoice_paid(payment: Payment) -> None: lnurldevicepayment = await update_lnurldevicepayment( lnurldevicepayment_id=payment.extra.get("id"), payhash="used" ) - return await updater( + return await websocketUpdater( lnurldevicepayment.deviceid, - lnurldevicepayment.pin, - lnurldevicepayment.payload, + str(lnurldevicepayment.pin) + "-" + str(lnurldevicepayment.payload), ) return diff --git a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html index 25dcf8c99..b2165590e 100644 --- a/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html +++ b/lnbits/extensions/lnurldevice/templates/lnurldevice/index.html @@ -157,9 +157,9 @@ unelevated color="primary" size="md" - @click="copyText(wslocation + '/lnurldevice/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')" - >{% raw %}{{wslocation}}/lnurldevice/ws/{{settingsDialog.data.id}}{% - endraw %} Click to copy URL + @click="copyText(wslocation + '/api/v1/ws/' + settingsDialog.data.id, 'Link copied to clipboard!')" + >{% raw %}{{wslocation}}/api/v1/ws/{{settingsDialog.data.id}}{% endraw + %} Click to copy URL