diff --git a/models.py b/models.py index 937c6c5..977d0e6 100644 --- a/models.py +++ b/models.py @@ -47,6 +47,7 @@ class TestMessageResponse(BaseModel): class Config(BaseModel): private_ws: bool = True public_ws: bool = False + private_ws_endpoint: str | None = None class UserConfig(BaseModel): diff --git a/templates/nostrclient/index.html b/templates/nostrclient/index.html index ca44f1b..c5fda7b 100644 --- a/templates/nostrclient/index.html +++ b/templates/nostrclient/index.html @@ -4,8 +4,8 @@
-
-
+
+
-
+
@@ -36,14 +35,22 @@
-
+
+ Test endpoint + >Settings
@@ -142,7 +149,7 @@
Nostrclient
-
+
Nostrclient
- - + + + + + +
Test Endpoint
+ + +
+ -
-
- Sender Private Key: -
-
- -
-
-
-
-
- - - No not use your real private key! Leave empty for a randomly - generated key. - -
-
-
-
- Sender Public Key: -
-
+

+ Send an encrypted direct message through your relay multiplexer to + verify it's working. The message will be sent from a sender key to a + recipient's public key (npub). +

+ +
+ + +
-
-
-
-
- Test Message: -
-
- -
-
-
-
- Receiver Public Key: -
-
-
-
-
-
-
- - This is the recipient of the message. Field required. - -
-
-
-
+ + + + + +
Send Message + label="Send Test Message" + icon="send" + >
- - -
-
- Sent Data: -
-
- -
+ + + +
+
Results
+ + +
-
-
- Received Data: -
-
- -
+ + + + +
+{{ testData.sentData }}
+
+
+
+ + + + +
+{{ testData.receivedData }}
+
+
+
+ +
+ How to verify: Check your Nostr client (Damus, + Amethyst, etc.) for the test message in your DMs.
- - + +
@@ -418,13 +426,15 @@
Nostrclient Extension
}, testData: { show: false, + sending: false, wsConnection: null, senderPrivateKey: null, senderPublicKey: null, recieverPublicKey: null, message: null, sentData: '', - receivedData: '' + receivedData: '', + connectionStatus: null }, relayTable: { columns: [ @@ -570,42 +580,44 @@
Nostrclient Extension
} this.config.showDialog = false }, - toggleTestPanel: async function () { - if (this.testData.show) { - await this.hideTestPannel() - } else { - await this.showTestPanel() - } - }, - showTestPanel: async function () { + openTestDialog: async function () { + // Refresh config to get latest private_ws_endpoint + await this.getConfig() this.testData = { show: true, + sending: false, wsConnection: null, senderPrivateKey: this.$q.localStorage.getItem( 'lnbits.nostrclient.senderPrivateKey' ) || '', + senderPublicKey: null, recieverPublicKey: null, message: null, sentData: '', - receivedData: '' + receivedData: '', + connectionStatus: null } await this.closeWebsocket() this.connectToWebsocket() }, - hideTestPannel: async function () { + closeTestDialog: async function () { await this.closeWebsocket() this.testData = { show: false, + sending: false, wsConnection: null, senderPrivateKey: null, + senderPublicKey: null, recieverPublicKey: null, message: null, sentData: '', - receivedData: '' + receivedData: '', + connectionStatus: null } }, sendTestMessage: async function () { + this.testData.sending = true try { const {data} = await LNbits.api.request( 'PUT', @@ -631,8 +643,14 @@
Nostrclient Extension
{kinds: [4], '#p': [event.pubkey]} ]) this.testData.wsConnection.send(subscription) + this.$q.notify({ + type: 'positive', + message: 'Test message sent successfully' + }) } catch (error) { LNbits.utils.notifyApiError(error) + } finally { + this.testData.sending = false } }, @@ -643,9 +661,13 @@
Nostrclient Extension
await this.sleep(500) } this.testData.wsConnection.send(data) - const separator = '='.repeat(80) - this.testData.sentData = - data + `\n\n${separator}\n` + this.testData.sentData + try { + const parsed = JSON.parse(data) + const formatted = JSON.stringify(parsed, null, 2) + this.testData.sentData += formatted + '\n\n' + } catch { + this.testData.sentData += data + '\n\n' + } } catch (error) { this.$q.notify({ timeout: 5000, @@ -658,17 +680,45 @@
Nostrclient Extension
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` + // Use private endpoint if available, otherwise fall back to public + const endpoint = + this.config.data.private_ws && this.config.data.private_ws_endpoint + ? this.config.data.private_ws_endpoint + : 'relay' + const wsUrl = `${scheme}://${document.domain}${port}/nostrclient/api/v1/${endpoint}` this.testData.wsConnection = new WebSocket(wsUrl) - const updateReciveData = async e => { - const separator = '='.repeat(80) - this.testData.receivedData = - e.data + `\n\n${separator}\n` + this.testData.receivedData + + this.testData.wsConnection.onopen = () => { + this.testData.receivedData = '[Connected to WebSocket]\n' + this.testData.connectionStatus = 'connected' + } + + this.testData.wsConnection.onmessage = e => { + if (e.data) { + try { + const parsed = JSON.parse(e.data) + const formatted = JSON.stringify(parsed, null, 2) + this.testData.receivedData += formatted + '\n\n' + } catch { + this.testData.receivedData += e.data + '\n' + } + } + } + + this.testData.wsConnection.onerror = () => { + this.testData.receivedData += + '[WebSocket error - check Settings to enable Private or Public WebSocket]\n' + this.testData.connectionStatus = 'error' } - this.testData.wsConnection.onmessage = updateReciveData - this.testData.wsConnection.onerror = updateReciveData - this.testData.wsConnection.onclose = updateReciveData + this.testData.wsConnection.onclose = e => { + const reason = + e.reason || (e.code === 1006 ? 'Connection refused (403)' : '') + this.testData.receivedData += `[WebSocket closed${reason ? ': ' + reason : ''}]\n` + if (this.testData.connectionStatus !== 'connected') { + this.testData.connectionStatus = 'error' + } + } }, closeWebsocket: async function () { try { diff --git a/views_api.py b/views_api.py index 4470680..7ce765e 100644 --- a/views_api.py +++ b/views_api.py @@ -3,7 +3,11 @@ from fastapi import APIRouter, Depends, HTTPException, WebSocket from lnbits.decorators import check_admin -from lnbits.helpers import decrypt_internal_message, urlsafe_short_hash +from lnbits.helpers import ( + decrypt_internal_message, + encrypt_internal_message, + urlsafe_short_hash, +) from loguru import logger from .crud import ( @@ -169,6 +173,8 @@ async def api_get_config() -> Config: if not config: config = await create_config(owner_id="admin") assert config, "Failed to create config" + # Add private WebSocket endpoint for admin use + config.private_ws_endpoint = encrypt_internal_message("relay", urlsafe=True) return config