Improve stability (#25)
* chore: increase log level * feat: secure relays endpoint * feat: add config for the extension * chore: update `min_lnbits_version` * chore: improve logging * fix: decrypt logic * chore: improve logs
This commit is contained in:
parent
16ae9d15a1
commit
a119c3836a
8 changed files with 220 additions and 193 deletions
|
|
@ -2,6 +2,6 @@
|
|||
"name": "Nostr Client",
|
||||
"short_description": "Nostr client for extensions",
|
||||
"tile": "/nostrclient/static/images/nostr-bitcoin.png",
|
||||
"contributors": ["calle"],
|
||||
"min_lnbits_version": "0.11.0"
|
||||
"contributors": ["calle", "motorina0"],
|
||||
"min_lnbits_version": "0.12.0"
|
||||
}
|
||||
|
|
|
|||
40
crud.py
40
crud.py
|
|
@ -1,7 +1,9 @@
|
|||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
import json
|
||||
|
||||
from . import db
|
||||
from .models import Relay
|
||||
from .models import Config, Relay
|
||||
|
||||
|
||||
async def get_relays() -> List[Relay]:
|
||||
|
|
@ -25,3 +27,37 @@ async def add_relay(relay: Relay) -> None:
|
|||
|
||||
async def delete_relay(relay: Relay) -> None:
|
||||
await db.execute("DELETE FROM nostrclient.relays WHERE url = ?", (relay.url,))
|
||||
|
||||
|
||||
######################CONFIG#######################
|
||||
async def create_config() -> Config:
|
||||
config = Config()
|
||||
await db.execute(
|
||||
"""
|
||||
INSERT INTO nostrclient.config (json_data)
|
||||
VALUES (?)
|
||||
""",
|
||||
(json.dumps(config.dict())),
|
||||
)
|
||||
row = await db.fetchone(
|
||||
"SELECT json_data FROM nostrclient.config", ()
|
||||
)
|
||||
return json.loads(row[0], object_hook=lambda d: Config(**d))
|
||||
|
||||
|
||||
async def update_config(config: Config) -> Optional[Config]:
|
||||
await db.execute(
|
||||
"""UPDATE nostrclient.config SET json_data = ?""",
|
||||
(json.dumps(config.dict())),
|
||||
)
|
||||
row = await db.fetchone(
|
||||
"SELECT json_data FROM nostrclient.config", ()
|
||||
)
|
||||
return json.loads(row[0], object_hook=lambda d: Config(**d))
|
||||
|
||||
|
||||
async def get_config() -> Optional[Config]:
|
||||
row = await db.fetchone(
|
||||
"SELECT json_data FROM nostrclient.config", ()
|
||||
)
|
||||
return json.loads(row[0], object_hook=lambda d: Config(**d)) if row else None
|
||||
|
|
|
|||
|
|
@ -11,3 +11,15 @@ async def m001_initial(db):
|
|||
);
|
||||
"""
|
||||
)
|
||||
|
||||
|
||||
async def m002_create_config_table(db):
|
||||
"""
|
||||
Allow the extension to persist and retrieve any number of config values.
|
||||
"""
|
||||
|
||||
await db.execute(
|
||||
"""CREATE TABLE nostrclient.config (
|
||||
json_data TEXT NOT NULL
|
||||
);"""
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,3 +42,8 @@ class TestMessageResponse(BaseModel):
|
|||
private_key: str
|
||||
public_key: str
|
||||
event_json: str
|
||||
|
||||
|
||||
class Config(BaseModel):
|
||||
private_ws: bool = True
|
||||
public_ws: bool = False
|
||||
|
|
|
|||
|
|
@ -72,6 +72,7 @@ class RelayManager:
|
|||
|
||||
def close_subscription(self, id: str):
|
||||
try:
|
||||
logger.info(f"Closing subscription: '{id}'.")
|
||||
with self._subscriptions_lock:
|
||||
if id in self._cached_subscriptions:
|
||||
self._cached_subscriptions.pop(id)
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class NostrRouter:
|
|||
pass
|
||||
|
||||
try:
|
||||
await self.websocket.close()
|
||||
await self.websocket.close(reason="Websocket connection closed")
|
||||
except Exception as _:
|
||||
pass
|
||||
|
||||
|
|
@ -113,7 +113,7 @@ class NostrRouter:
|
|||
def _handle_notices(self):
|
||||
while len(NostrRouter.received_subscription_notices):
|
||||
my_event = NostrRouter.received_subscription_notices.pop(0)
|
||||
logger.info(f"[Relay '{my_event.url}'] Notice: '{my_event.content}']")
|
||||
logger.debug(f"[Relay '{my_event.url}'] Notice: '{my_event.content}']")
|
||||
# Note: we don't send it to the user because
|
||||
# we don't know who should receive it
|
||||
nostr_client.relay_manager.handle_notice(my_event)
|
||||
|
|
@ -136,6 +136,7 @@ class NostrRouter:
|
|||
|
||||
def _handle_client_req(self, json_data):
|
||||
subscription_id = json_data[1]
|
||||
logger.info(f"New subscription: '{subscription_id}'")
|
||||
subscription_id_rewritten = urlsafe_short_hash()
|
||||
self.original_subscription_ids[subscription_id_rewritten] = subscription_id
|
||||
filters = json_data[2:]
|
||||
|
|
@ -154,5 +155,6 @@ class NostrRouter:
|
|||
if subscription_id_rewritten:
|
||||
self.original_subscription_ids.pop(subscription_id_rewritten)
|
||||
nostr_client.relay_manager.close_subscription(subscription_id_rewritten)
|
||||
logger.info(f"Unsubscribe from '{subscription_id_rewritten}'. Original id: '{subscription_id}.'")
|
||||
else:
|
||||
logger.debug(f"Failed to unsubscribe from '{subscription_id}.'")
|
||||
logger.info(f"Failed to unsubscribe from '{subscription_id}.'")
|
||||
|
|
|
|||
|
|
@ -4,38 +4,24 @@
|
|||
<div class="col-12 col-md-7 q-gutter-y-md">
|
||||
<q-card>
|
||||
<q-form @submit="addRelay">
|
||||
<div class="row q-pa-md">
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="relayToAdd"
|
||||
dense
|
||||
filled
|
||||
label="Relay URL"
|
||||
></q-input>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-7 q-pa-md">
|
||||
<q-input outlined v-model="relayToAdd" dense filled label="Relay URL"></q-input>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-btn-dropdown
|
||||
unelevated
|
||||
split
|
||||
color="primary"
|
||||
class="float-right"
|
||||
type="submit"
|
||||
label="Add Relay X"
|
||||
>
|
||||
<q-item
|
||||
v-for="relay in predefinedRelays"
|
||||
:key="relay"
|
||||
@click="addCustomRelay(relay)"
|
||||
clickable
|
||||
v-close-popup
|
||||
>
|
||||
<div class="col-6 col-md-3 q-pa-md">
|
||||
<q-btn-dropdown unelevated split color="primary" class="float-left" type="submit" label="Add Relay Y">
|
||||
<q-item v-for="relay in predefinedRelays" :key="relay" @click="addCustomRelay(relay)" clickable
|
||||
v-close-popup>
|
||||
<q-item-section>
|
||||
<q-item-label><span v-text="relay"></span></q-item-label>
|
||||
</q-item-section>
|
||||
</q-item>
|
||||
</q-btn-dropdown>
|
||||
</div>
|
||||
<div class="col-6 col-md-2 q-pa-md">
|
||||
<q-btn unelevated @click="config.showDialog = true" color="primary" icon="settings"
|
||||
class="float-right"></q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
|
|
@ -46,36 +32,18 @@
|
|||
<h5 class="text-subtitle1 q-my-none">Nostrclient</h5>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-input
|
||||
borderless
|
||||
dense
|
||||
debounce="300"
|
||||
v-model="filter"
|
||||
placeholder="Search"
|
||||
>
|
||||
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
||||
<template v-slot:append>
|
||||
<q-icon name="search"></q-icon>
|
||||
</template>
|
||||
</q-input>
|
||||
</div>
|
||||
</div>
|
||||
<q-table
|
||||
flat
|
||||
dense
|
||||
:data="nostrrelayLinks"
|
||||
row-key="id"
|
||||
:columns="relayTable.columns"
|
||||
:pagination.sync="relayTable.pagination"
|
||||
:filter="filter"
|
||||
>
|
||||
<q-table flat dense :data="nostrrelayLinks" row-key="id" :columns="relayTable.columns"
|
||||
:pagination.sync="relayTable.pagination" :filter="filter">
|
||||
<template v-slot:header="props">
|
||||
<q-tr :props="props">
|
||||
<q-th
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
auto-width
|
||||
>
|
||||
<q-th v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||
<div v-if="col.name == 'id'"></div>
|
||||
<div v-else>{{ col.label }}</div>
|
||||
</q-th>
|
||||
|
|
@ -84,12 +52,7 @@
|
|||
|
||||
<template v-slot:body="props">
|
||||
<q-tr :props="props">
|
||||
<q-td
|
||||
v-for="col in props.cols"
|
||||
:key="col.name"
|
||||
:props="props"
|
||||
auto-width
|
||||
>
|
||||
<q-td v-for="col in props.cols" :key="col.name" :props="props" auto-width>
|
||||
<div v-if="col.name == 'connected'">
|
||||
<div v-if="col.value">🟢</div>
|
||||
<div v-else>🔴</div>
|
||||
|
|
@ -98,29 +61,17 @@
|
|||
<div>
|
||||
⬆️ <span v-text="col.value.sentEvents"></span> ⬇️
|
||||
<span v-text="col.value.receveidEvents"></span>
|
||||
<span
|
||||
@click="showLogDataDialog(col.value.errorList)"
|
||||
class="cursor-pointer"
|
||||
>
|
||||
<span @click="showLogDataDialog(col.value.errorList)" class="cursor-pointer">
|
||||
⚠️ <span v-text="col.value.errorCount"> </span>
|
||||
</span>
|
||||
<span
|
||||
@click="showLogDataDialog(col.value.noticeList)"
|
||||
class="cursor-pointer float-right"
|
||||
>
|
||||
<span @click="showLogDataDialog(col.value.noticeList)" class="cursor-pointer float-right">
|
||||
ⓘ
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="col.name == 'delete'">
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="md"
|
||||
@click="showDeleteRelayDialog(props.row.url)"
|
||||
icon="cancel"
|
||||
color="pink"
|
||||
></q-btn>
|
||||
<q-btn flat dense size="md" @click="showDeleteRelayDialog(props.row.url)" icon="cancel"
|
||||
color="pink"></q-btn>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div>{{ col.value }}</div>
|
||||
|
|
@ -136,32 +87,15 @@
|
|||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="text-weight-bold">
|
||||
<q-btn
|
||||
flat
|
||||
dense
|
||||
size="0.6rem"
|
||||
class="q-px-none q-mx-none"
|
||||
color="grey"
|
||||
icon="content_copy"
|
||||
@click="copyText(`wss://${host}/nostrclient/api/v1/relay`)"
|
||||
><q-tooltip>Copy address</q-tooltip></q-btn
|
||||
>
|
||||
<q-btn flat dense size="0.6rem" class="q-px-none q-mx-none" color="grey" icon="content_copy"
|
||||
@click="copyText(`wss://${host}/nostrclient/api/v1/relay`)"><q-tooltip>Copy address</q-tooltip></q-btn>
|
||||
Your endpoint:
|
||||
<q-badge
|
||||
outline
|
||||
class="q-ml-sm text-subtitle2"
|
||||
:label="`wss://${host}/nostrclient/api/v1/relay`"
|
||||
/>
|
||||
<q-badge outline class="q-ml-sm text-subtitle2" :label="`wss://${host}/nostrclient/api/v1/relay`" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
<q-expansion-item
|
||||
group="advanced"
|
||||
icon="settings"
|
||||
label="Test this endpoint"
|
||||
@click="toggleTestPanel"
|
||||
>
|
||||
<q-expansion-item group="advanced" icon="settings" label="Test this endpoint" @click="toggleTestPanel">
|
||||
<q-separator></q-separator>
|
||||
<q-card-section>
|
||||
<div class="row">
|
||||
|
|
@ -169,13 +103,8 @@
|
|||
<span>Sender Private Key:</span>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="testData.senderPrivateKey"
|
||||
dense
|
||||
filled
|
||||
label="Private Key (optional)"
|
||||
></q-input>
|
||||
<q-input outlined v-model="testData.senderPrivateKey" dense filled
|
||||
label="Private Key (optional)"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-sm q-mb-lg">
|
||||
|
|
@ -184,8 +113,7 @@
|
|||
<q-badge color="yellow" text-color="black">
|
||||
<span>
|
||||
No not use your real private key! Leave empty for a randomly
|
||||
generated key.</span
|
||||
>
|
||||
generated key.</span>
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -194,13 +122,7 @@
|
|||
<span>Sender Public Key:</span>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="testData.senderPublicKey"
|
||||
dense
|
||||
readonly
|
||||
filled
|
||||
></q-input>
|
||||
<q-input outlined v-model="testData.senderPublicKey" dense readonly filled></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-md">
|
||||
|
|
@ -208,15 +130,8 @@
|
|||
<span>Test Message:</span>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="testData.message"
|
||||
dense
|
||||
filled
|
||||
rows="3"
|
||||
type="textarea"
|
||||
label="Test Message *"
|
||||
></q-input>
|
||||
<q-input outlined v-model="testData.message" dense filled rows="3" type="textarea"
|
||||
label="Test Message *"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-md">
|
||||
|
|
@ -224,35 +139,22 @@
|
|||
<span>Receiver Public Key:</span>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="testData.recieverPublicKey"
|
||||
dense
|
||||
filled
|
||||
label="Public Key (hex or npub) *"
|
||||
></q-input>
|
||||
<q-input outlined v-model="testData.recieverPublicKey" dense filled
|
||||
label="Public Key (hex or npub) *"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-sm q-mb-lg">
|
||||
<div class="col-3"></div>
|
||||
<div class="col-9">
|
||||
<q-badge color="yellow" text-color="black">
|
||||
<span
|
||||
>This is the recipient of the message. Field required.</span
|
||||
>
|
||||
<span>This is the recipient of the message. Field required.</span>
|
||||
</q-badge>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<q-btn
|
||||
:disabled="!testData.recieverPublicKey || !testData.message"
|
||||
@click="sendTestMessage"
|
||||
unelevated
|
||||
color="primary"
|
||||
class="float-right"
|
||||
>Send Message</q-btn
|
||||
>
|
||||
<q-btn :disabled="!testData.recieverPublicKey || !testData.message" @click="sendTestMessage" unelevated
|
||||
color="primary" class="float-right">Send Message</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
|
@ -264,14 +166,7 @@
|
|||
<span>Sent Data:</span>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="testData.sentData"
|
||||
dense
|
||||
filled
|
||||
rows="5"
|
||||
type="textarea"
|
||||
></q-input>
|
||||
<q-input outlined v-model="testData.sentData" dense filled rows="5" type="textarea"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row q-mt-md">
|
||||
|
|
@ -279,14 +174,7 @@
|
|||
<span>Received Data:</span>
|
||||
</div>
|
||||
<div class="col-9">
|
||||
<q-input
|
||||
outlined
|
||||
v-model="testData.receivedData"
|
||||
dense
|
||||
filled
|
||||
rows="5"
|
||||
type="textarea"
|
||||
></q-input>
|
||||
<q-input outlined v-model="testData.receivedData" dense filled rows="5" type="textarea"></q-input>
|
||||
</div>
|
||||
</div>
|
||||
</q-card-section>
|
||||
|
|
@ -305,12 +193,8 @@
|
|||
</p>
|
||||
|
||||
<p>
|
||||
<q-badge
|
||||
outline
|
||||
class="q-ml-sm text-subtitle2"
|
||||
color="primary"
|
||||
:label="`wss://${host}/nostrclient/api/v1/relay`"
|
||||
/>
|
||||
<q-badge outline class="q-ml-sm text-subtitle2" color="primary"
|
||||
:label="`wss://${host}/nostrclient/api/v1/relay`" />
|
||||
</p>
|
||||
Only Admin users can manage this extension.
|
||||
<q-card-section></q-card-section>
|
||||
|
|
@ -320,21 +204,27 @@
|
|||
|
||||
<q-dialog v-model="logData.show" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl">
|
||||
<q-input
|
||||
filled
|
||||
dense
|
||||
v-model.trim="logData.data"
|
||||
type="textarea"
|
||||
rows="25"
|
||||
cols="200"
|
||||
label="Log Data"
|
||||
></q-input>
|
||||
<q-input filled dense v-model.trim="logData.data" type="textarea" rows="25" cols="200" label="Log Data"></q-input>
|
||||
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Close</q-btn>
|
||||
</div>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
|
||||
<q-dialog v-model="config.showDialog" position="top">
|
||||
<q-card class="q-pa-lg q-pt-xl lnbits__dialog-card">
|
||||
<q-form @submit="updateConfig" class="q-gutter-md">
|
||||
<q-toggle label="Expose Private Websocket" color="secodary" v-model="config.data.private_ws"></q-toggle>
|
||||
<br />
|
||||
<q-toggle label="Expose Public Websocket" color="secodary" v-model="config.data.public_ws"></q-toggle>
|
||||
<div class="row q-mt-lg">
|
||||
<q-btn unelevated color="primary" type="submit">Update</q-btn>
|
||||
<q-btn v-close-popup flat color="grey" class="q-ml-auto">Cancel</q-btn>
|
||||
</div>
|
||||
</q-form>
|
||||
</q-card>
|
||||
</q-dialog>
|
||||
</div>
|
||||
{% endraw %} {% endblock %} {% block scripts %} {{ window_vars(user) }}
|
||||
|
||||
|
|
@ -380,6 +270,10 @@
|
|||
show: false,
|
||||
data: null
|
||||
},
|
||||
config: {
|
||||
showDialog: false,
|
||||
data: {},
|
||||
},
|
||||
testData: {
|
||||
show: false,
|
||||
wsConnection: null,
|
||||
|
|
@ -522,6 +416,34 @@
|
|||
LNbits.utils.notifyApiError(error)
|
||||
})
|
||||
},
|
||||
getConfig: async function () {
|
||||
try {
|
||||
const { data } = await LNbits.api
|
||||
.request(
|
||||
'GET',
|
||||
'/nostrclient/api/v1/config',
|
||||
this.g.user.wallets[0].adminkey
|
||||
)
|
||||
this.config.data = data
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
},
|
||||
updateConfig: async function () {
|
||||
try {
|
||||
const { data } = await LNbits.api.request(
|
||||
'PUT',
|
||||
'/nostrclient/api/v1/config',
|
||||
this.g.user.wallets[0].adminkey,
|
||||
this.config.data
|
||||
)
|
||||
this.config.data = data
|
||||
|
||||
} catch (error) {
|
||||
LNbits.utils.notifyApiError(error)
|
||||
}
|
||||
this.config.showDialog = false
|
||||
},
|
||||
toggleTestPanel: async function () {
|
||||
if (this.testData.show) {
|
||||
await this.hideTestPannel()
|
||||
|
|
@ -642,9 +564,9 @@
|
|||
},
|
||||
sleep: ms => new Promise(r => setTimeout(r, ms))
|
||||
},
|
||||
created: function () {
|
||||
var self = this
|
||||
created: async function () {
|
||||
this.getRelays()
|
||||
await this.getConfig()
|
||||
setInterval(this.getRelays, 5000)
|
||||
}
|
||||
})
|
||||
|
|
|
|||
63
views_api.py
63
views_api.py
|
|
@ -7,12 +7,12 @@ from loguru import logger
|
|||
from starlette.exceptions import HTTPException
|
||||
|
||||
from lnbits.decorators import check_admin
|
||||
from lnbits.helpers import urlsafe_short_hash
|
||||
from lnbits.helpers import decrypt_internal_message, urlsafe_short_hash
|
||||
|
||||
from . import nostr_client, nostrclient_ext, scheduled_tasks
|
||||
from .crud import add_relay, delete_relay, get_relays
|
||||
from .crud import add_relay, create_config, delete_relay, get_config, get_relays, update_config
|
||||
from .helpers import normalize_public_key
|
||||
from .models import Relay, TestMessage, TestMessageResponse
|
||||
from .models import Config, Relay, TestMessage, TestMessageResponse
|
||||
from .nostr.key import EncryptedDirectMessage, PrivateKey
|
||||
from .router import NostrRouter
|
||||
|
||||
|
|
@ -20,7 +20,7 @@ from .router import NostrRouter
|
|||
all_routers: list[NostrRouter] = []
|
||||
|
||||
|
||||
@nostrclient_ext.get("/api/v1/relays")
|
||||
@nostrclient_ext.get("/api/v1/relays", dependencies=[Depends(check_admin)])
|
||||
async def api_get_relays() -> List[Relay]:
|
||||
relays = []
|
||||
for url, r in nostr_client.relay_manager.relays.items():
|
||||
|
|
@ -133,9 +133,27 @@ async def api_stop():
|
|||
return {"success": True}
|
||||
|
||||
|
||||
@nostrclient_ext.websocket("/api/v1/relay")
|
||||
async def ws_relay(websocket: WebSocket) -> None:
|
||||
@nostrclient_ext.websocket("/api/v1/{id}")
|
||||
async def ws_relay(id: str, websocket: WebSocket) -> None:
|
||||
"""Relay multiplexer: one client (per endpoint) <-> multiple relays"""
|
||||
|
||||
logger.info("New websocket connection at: '/api/v1/relay'")
|
||||
try:
|
||||
config = await get_config()
|
||||
|
||||
if not config.private_ws and not config.public_ws:
|
||||
raise ValueError("Websocket connections not accepted.")
|
||||
|
||||
if id == "relay":
|
||||
if not config.public_ws:
|
||||
raise ValueError("Public websocket connections not accepted.")
|
||||
else:
|
||||
if not config.private_ws:
|
||||
raise ValueError("Private websocket connections not accepted.")
|
||||
if decrypt_internal_message(id) != "relay":
|
||||
raise ValueError("Invalid websocket endpoint.")
|
||||
|
||||
|
||||
await websocket.accept()
|
||||
router = NostrRouter(websocket)
|
||||
router.start()
|
||||
|
|
@ -146,6 +164,37 @@ async def ws_relay(websocket: WebSocket) -> None:
|
|||
while router.connected:
|
||||
await asyncio.sleep(10)
|
||||
|
||||
try:
|
||||
await router.stop()
|
||||
all_routers.remove(router)
|
||||
except Exception as e:
|
||||
logger.debug(e)
|
||||
|
||||
all_routers.remove(router)
|
||||
logger.info("Closed websocket connection at: '/api/v1/relay'")
|
||||
except ValueError as ex:
|
||||
logger.warning(ex)
|
||||
await websocket.close(reason=str(ex))
|
||||
except Exception as ex:
|
||||
logger.warning(ex)
|
||||
await websocket.close(reason="Websocket connection unexpected closed")
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.INTERNAL_SERVER_ERROR,
|
||||
detail="Cannot accept websocket connection",
|
||||
)
|
||||
|
||||
|
||||
@nostrclient_ext.get("/api/v1/config", dependencies=[Depends(check_admin)])
|
||||
async def api_get_relays() -> Config:
|
||||
config = await get_config()
|
||||
if not config:
|
||||
await create_config()
|
||||
|
||||
return config
|
||||
|
||||
@nostrclient_ext.put("/api/v1/config", dependencies=[Depends(check_admin)])
|
||||
async def api_update_config(
|
||||
data: Config
|
||||
):
|
||||
config = await update_config(data)
|
||||
assert config
|
||||
return config.dict()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue