Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ The configuration page of the NWC Service Provider extension is available at `/n
| relay | URL of the nostr relay for dispatching and receiving NWC events. Use public relays or a custom one. Specify `nostrclient` to connect to the [nostrclient extension](https://github.com/lnbits/nostrclient). | nostrclient |
| provider_key | Nostr secret key of the NWC Service Provider. | Random key generated on install |
| relay_alias | Relay URL to display in pairing URLs. Set if different from `relay`. | Empty (uses the `relay` value) |
| handle_missed_events | Number of seconds to look back for processing events missed while offline. Setting it to 0 disables this functionality. | 0 |
| handle_missed_events | Number of seconds to look back for processing events missed while offline. Setting it to 0 disables this functionality. | 120 |

> [!WARNING]
>
Expand Down
2 changes: 1 addition & 1 deletion migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,5 +117,5 @@ async def m006_default_config3(db):
VALUES ('handle_missed_events', :value)
ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value;
""",
{"value": "0"},
{"value": "120"},
)
48 changes: 48 additions & 0 deletions nwcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ def __init__(
# Periodic info event resend loop
self.info_event_task = None

# EOSE timeout task (fallback if relay never sends EOSE)
self.eose_timeout_task = None

# Subscription
self.sub = None
self.rate_limit: dict[str, RateLimit] = {}
Expand Down Expand Up @@ -270,10 +273,43 @@ async def _ratelimit(self, unit: str, max_sleep_time: int = 120) -> None:
await asyncio.sleep(limit.backoff)
limit.last_attempt_time = int(time.time())

async def _process_stale_events(self):
if not self.sub:
return
stales = self.sub.get_stale()
if not stales:
return
logger.debug(f"Processing {len(stales)} stale events (EOSE timeout)")
for stale in stales:
try:
await self._handle_request(stale)
except Exception as e:
logger.error(f"Error handling stale event: {e}")

async def _eose_timeout(self):
await asyncio.sleep(10)
if self._is_shutting_down():
return
if not self.sub:
return
if not (self.sub.requests_eose and self.sub.responses_eose):
logger.warning(
"EOSE not received within 10s timeout, "
"processing pending events anyway"
)
self.sub.requests_eose = True
self.sub.responses_eose = True
await self._process_stale_events()

async def _subscribe(self):
"""
[Re]Subscribe to receive nip 47 requests and responses from the relay
"""
# Cancel previous EOSE timeout if any
if self.eose_timeout_task:
self.eose_timeout_task.cancel()
self.eose_timeout_task = None

self.sub = MainSubscription()
# Create requests subscription
req_filter = {
Expand All @@ -293,6 +329,8 @@ async def _subscribe(self):
# Subscribe
await self._send(["REQ", self.sub.requests_sub_id, req_filter])
await self._send(["REQ", self.sub.responses_sub_id, res_filter])
# Start EOSE timeout guard
self.eose_timeout_task = asyncio.create_task(self._eose_timeout())

async def _on_connection(self, _):
"""
Expand Down Expand Up @@ -469,6 +507,10 @@ async def _on_eose_message(self, msg):
# service connection and do not have a response yet,
# are considered stale, we will process them now
if self.sub.requests_eose and self.sub.responses_eose:
# Cancel the EOSE timeout since we got both EOSEs
if self.eose_timeout_task:
self.eose_timeout_task.cancel()
self.eose_timeout_task = None
stales = self.sub.get_stale()
for stale in stales:
await self._handle_request(stale)
Expand Down Expand Up @@ -629,6 +671,12 @@ async def cleanup(self):
self.info_event_task.cancel()
except Exception as e:
logger.warning("Error closing info event loop: " + str(e))
# cancel eose timeout
try:
if self.eose_timeout_task:
self.eose_timeout_task.cancel()
except Exception as e:
logger.warning("Error closing eose timeout task: " + str(e))
# close the websocket
try:
if self.ws:
Expand Down
Loading