diff --git a/helpers.py b/helpers.py new file mode 100644 index 0000000..bcf5c02 --- /dev/null +++ b/helpers.py @@ -0,0 +1,19 @@ +from bech32 import bech32_decode, convertbits + + +def normalize_public_key(pubkey: str) -> str: + if pubkey.startswith("npub1"): + _, decoded_data = bech32_decode(pubkey) + if not decoded_data: + raise ValueError("Public Key is not valid npub") + + decoded_data_bits = convertbits(decoded_data, 5, 8, False) + if not decoded_data_bits: + raise ValueError("Public Key is not valid npub") + return bytes(decoded_data_bits).hex() + + # check if valid hex + if len(pubkey) != 64: + raise ValueError("Public Key is not valid hex") + int(pubkey, 16) + return pubkey diff --git a/models.py b/models.py index 899359f..1456d83 100644 --- a/models.py +++ b/models.py @@ -58,7 +58,7 @@ class TestMessage(BaseModel): class TestMessageResponse(BaseModel): private_key: str public_key: str - event: Event + event_json: str # class nostrKeys(BaseModel): # pubkey: str diff --git a/templates/nostrclient/index.html b/templates/nostrclient/index.html index 593419a..90859ae 100644 --- a/templates/nostrclient/index.html +++ b/templates/nostrclient/index.html @@ -159,7 +159,7 @@ filled rows="3" type="textarea" - label="Test Message" + label="Test Message *" > @@ -181,7 +181,7 @@
- This is the recipient of the message + This is the recipient of the message. Field required.
@@ -189,6 +189,7 @@
+ + +
+
+ Sent Data: +
+
+ +
+
+
@@ -258,9 +278,12 @@ nostrrelayLinks: [], filter: '', testData: { + wsConnection: null, senderPrivateKey: null, recieverPublicKey: null, - message: null + message: null, + sentData: '', + receivedData: '' }, relayTable: { columns: [ @@ -368,6 +391,64 @@ LNbits.utils.notifyApiError(error) }) }, + sendTestMessage: async function(){ + try { + const {data} = await LNbits.api.request( + 'PUT', + '/nostrclient/api/v1/relay/test?usr=' + this.g.user.id, + this.g.user.wallets[0].adminkey, + { + sender_private_key: this.testData.senderPrivateKey, + reciever_public_key: this.testData.recieverPublicKey, + message: this.testData.message + } + ) + console.log('### data', data) + this.testData.senderPrivateKey = data.private_key + this.$q.localStorage.set('lnbits.nostrclient.senderPrivateKey', data.private_key || '') + const event = JSON.parse(data.event_json)[1] + console.log('### event', event) + this.sendDataToWebSocket(data.event_json) + } catch (error) { + LNbits.utils.notifyApiError(error) + } + }, + + sendDataToWebSocket: async function (data){ + try { + if (!this.testData.wsConnection) { + this.connectToWebsocket() + } + this.testData.wsConnection.send(data) + this.testData.sentData = data + '\n' + this.testData.sentData + } catch (error) { + this.$q.notify({ + timeout: 5000, + type: 'warning', + message: 'Failed to connect to websocket', + caption: `${error}` + }) + } + }, + connectToWebsocket: function () { + const scheme = location.protocol === 'http:' ? 'ws' : 'wss' + const port = location.port ? `:${location.port}` : '' + const wsUrl = `${scheme}://${document.domain}${port}/nostrclient/api/v1/relay` + this.testData.wsConnection = new WebSocket(wsUrl) + wsConnection.onmessage = async e => { + // const data = JSON.parse(e.data) + console.log('### onmessage', e.data) + } + wsConnection.onerror = async e => { + // const data = JSON.parse(e.data) + console.log('### onerror', e.data) + } + wsConnection.onclose = async e => { + // const data = JSON.parse(e.data) + console.log('### onclose', e.data) + } + + }, exportlnurldeviceCSV: function () { var self = this LNbits.utils.exportCSV(self.relayTable.columns, this.nostrLinks) @@ -377,6 +458,7 @@ var self = this this.getRelays() setInterval(this.getRelays, 5000) + this.testData.senderPrivateKey = this.$q.localStorage.getItem('lnbits.nostrclient.senderPrivateKey') || '' } }) diff --git a/views_api.py b/views_api.py index 3287351..e7c5f06 100644 --- a/views_api.py +++ b/views_api.py @@ -1,4 +1,5 @@ import asyncio +import json from http import HTTPStatus from typing import Optional @@ -11,7 +12,9 @@ from lnbits.helpers import urlsafe_short_hash from . import nostrclient_ext, scheduled_tasks from .crud import add_relay, delete_relay, get_relays +from .helpers import normalize_public_key from .models import Relay, RelayList, TestMessage, TestMessageResponse +from .nostr.key import EncryptedDirectMessage, PrivateKey from .services import NostrRouter, nostr from .tasks import init_relays @@ -78,9 +81,21 @@ async def api_delete_relay(relay: Relay) -> None: @nostrclient_ext.put( "/api/v1/relay/test", status_code=HTTPStatus.OK, dependencies=[Depends(check_admin)] ) -async def api_test_endpoint(test_message: TestMessage) -> TestMessageResponse: +async def api_test_endpoint(data: TestMessage) -> TestMessageResponse: try: - print("### api_test_endpoint", test_message) + to_public_key = normalize_public_key(data.reciever_public_key) + + pk = bytes.fromhex(data.sender_private_key) if data.sender_private_key else None + private_key = PrivateKey(pk) + + dm = EncryptedDirectMessage( + recipient_pubkey=to_public_key, cleartext_content=data.message + ) + private_key.sign_event(dm) + + print("### api_test_endpoint", data) + + return TestMessageResponse(private_key=private_key.hex(), public_key=to_public_key, event_json=dm.to_message()) except (ValueError, AssertionError) as ex: raise HTTPException( status_code=HTTPStatus.BAD_REQUEST,