From 30d818c4d467940d1d16c2e0965460bb37e29e3c Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Sun, 25 Jan 2026 14:37:56 -0500 Subject: [PATCH 1/2] fix(nostr): update NostrSend type to Promise with error handling The NostrSend type was incorrectly typed as returning void when it actually returns Promise. This caused async errors to be silently swallowed. Changes: - Update NostrSend type signature to return Promise - Make NostrSender._nostrSend default to async function - Add .catch() error handling in NostrSender.Send() to log failures - Add logging to track event publishing status Co-Authored-By: Claude Opus 4.5 --- src/services/nostr/nostrPool.ts | 9 +++++---- src/services/nostr/sender.ts | 18 +++++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/services/nostr/nostrPool.ts b/src/services/nostr/nostrPool.ts index d41da382..d5e47d7f 100644 --- a/src/services/nostr/nostrPool.ts +++ b/src/services/nostr/nostrPool.ts @@ -16,7 +16,7 @@ export type SendDataContent = { type: "content", content: string, pub: string } export type SendDataEvent = { type: "event", event: UnsignedEvent, encrypt?: { toPub: string } } export type SendData = SendDataContent | SendDataEvent export type SendInitiator = { type: 'app', appId: string } | { type: 'client', clientId: string } -export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => void +export type NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => Promise export type LinkedProviderInfo = { pubkey: string, clientId: string, relayUrl: string } export type AppInfo = { appId: string, publicKey: string, privateKey: string, name: string, provider?: LinkedProviderInfo } @@ -203,21 +203,22 @@ export class NostrPool { const signed = finalizeEvent(event, Buffer.from(keys.privateKey, 'hex')) let sent = false const log = getLogger({ appName: keys.name }) - // const r = relays ? relays : this.getServiceRelays() + this.log(`📤 Publishing Kind ${event.kind} event to ${relays.length} relay(s): ${relays.join(', ')}`) const pool = new SimplePool() await Promise.all(pool.publish(relays, signed).map(async p => { try { await p sent = true } catch (e: any) { - console.log(e) + this.log(ERROR, `Failed to publish Kind ${event.kind} event:`, e.message || e) log(e) } })) if (!sent) { + this.log(ERROR, `Failed to send Kind ${event.kind} event to any relay`) log("failed to send event") } else { - //log("sent event") + this.log(`✅ Kind ${event.kind} event published successfully (id: ${signed.id.slice(0, 16)}...)`) } } diff --git a/src/services/nostr/sender.ts b/src/services/nostr/sender.ts index 1fd336a5..8437b9af 100644 --- a/src/services/nostr/sender.ts +++ b/src/services/nostr/sender.ts @@ -1,7 +1,7 @@ import { NostrSend, SendData, SendInitiator } from "./nostrPool.js" -import { getLogger } from "../helpers/logger.js" +import { ERROR, getLogger } from "../helpers/logger.js" export class NostrSender { - private _nostrSend: NostrSend = () => { throw new Error('nostr send not initialized yet') } + private _nostrSend: NostrSend = async () => { throw new Error('nostr send not initialized yet') } private isReady: boolean = false private onReadyCallbacks: (() => void)[] = [] private pendingSends: { initiator: SendInitiator, data: SendData, relays?: string[] | undefined }[] = [] @@ -12,7 +12,12 @@ export class NostrSender { this.isReady = true this.onReadyCallbacks.forEach(cb => cb()) this.onReadyCallbacks = [] - this.pendingSends.forEach(send => this._nostrSend(send.initiator, send.data, send.relays)) + // Process pending sends with proper error handling + this.pendingSends.forEach(send => { + this._nostrSend(send.initiator, send.data, send.relays).catch(e => { + this.log(ERROR, "failed to send pending event", e.message || e) + }) + }) this.pendingSends = [] } OnReady(callback: () => void) { @@ -22,13 +27,16 @@ export class NostrSender { this.onReadyCallbacks.push(callback) } } - Send(initiator: SendInitiator, data: SendData, relays?: string[] | undefined) { + Send(initiator: SendInitiator, data: SendData, relays?: string[] | undefined): void { if (!this.isReady) { this.log("tried to send before nostr was ready, caching request") this.pendingSends.push({ initiator, data, relays }) return } - this._nostrSend(initiator, data, relays) + // Fire and forget but log errors + this._nostrSend(initiator, data, relays).catch(e => { + this.log(ERROR, "failed to send event", e.message || e) + }) } IsReady() { return this.isReady -- 2.53.0 From 748a2d3ed6a3a24d861c8c6f2b9b908f9098f4e1 Mon Sep 17 00:00:00 2001 From: Patrick Mulligan Date: Sun, 25 Jan 2026 14:38:24 -0500 Subject: [PATCH 2/2] fix(handlers): await NostrSend calls throughout codebase Update all NostrSend call sites to properly handle the async nature of the function now that it returns Promise. Changes: - handler.ts: Add async to sendResponse, await nostrSend calls - debitManager.ts: Add logging for Kind 21002 response sending - nostrMiddleware.ts: Update nostrSend signature - tlvFilesStorageProcessor.ts: Update nostrSend signature - webRTC/index.ts: Add async/await for nostrSend calls This ensures Kind 21002 (ndebit) responses are properly sent to wallet clients, fixing the "Debit request failed" issue in ShockWallet. Co-Authored-By: Claude Opus 4.5 --- src/nostrMiddleware.ts | 2 +- src/services/main/debitManager.ts | 3 ++- src/services/nostr/handler.ts | 4 ++-- src/services/storage/tlv/tlvFilesStorageProcessor.ts | 2 +- src/services/webRTC/index.ts | 4 ++-- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/nostrMiddleware.ts b/src/nostrMiddleware.ts index 034dbce8..4dd3b281 100644 --- a/src/nostrMiddleware.ts +++ b/src/nostrMiddleware.ts @@ -105,7 +105,7 @@ export default (serverMethods: Types.ServerMethods, mainHandler: Main, nostrSett return { Stop: () => { mainHandler.adminManager.setNostrConnected(false); return nostr.Stop }, - Send: (...args) => nostr.Send(...args), + Send: async (...args) => nostr.Send(...args), Ping: () => nostr.Ping(), Reset: (settings: NostrSettings) => nostr.Reset(settings) } diff --git a/src/services/main/debitManager.ts b/src/services/main/debitManager.ts index 28579d31..53375217 100644 --- a/src/services/main/debitManager.ts +++ b/src/services/main/debitManager.ts @@ -153,13 +153,14 @@ export class DebitManager { } notifyPaymentSuccess = (debitRes: NdebitSuccess, event: { pub: string, id: string, appId: string }) => { + this.logger("✅ [DEBIT REQUEST] Payment successful, sending OK response to", event.pub.slice(0, 16) + "...", "for event", event.id.slice(0, 16) + "...") this.sendDebitResponse(debitRes, event) } sendDebitResponse = (debitRes: NdebitFailure | NdebitSuccess, event: { pub: string, id: string, appId: string }) => { + this.logger("📤 [DEBIT RESPONSE] Sending Kind 21002 response:", JSON.stringify(debitRes), "to", event.pub.slice(0, 16) + "...") const e = newNdebitResponse(JSON.stringify(debitRes), event) this.storage.NostrSender().Send({ type: 'app', appId: event.appId }, { type: 'event', event: e, encrypt: { toPub: event.pub } }) - } payNdebitInvoice = async (event: NostrEvent, pointerdata: NdebitData): Promise => { diff --git a/src/services/nostr/handler.ts b/src/services/nostr/handler.ts index d1fa46ec..b9ecfe70 100644 --- a/src/services/nostr/handler.ts +++ b/src/services/nostr/handler.ts @@ -132,12 +132,12 @@ const handleNostrSettings = (settings: NostrSettings) => { send(event) }) } */ -const sendToNostr: NostrSend = (initiator, data, relays) => { +const sendToNostr: NostrSend = async (initiator, data, relays) => { if (!subProcessHandler) { getLogger({ component: "nostrMiddleware" })(ERROR, "nostr was not initialized") return } - subProcessHandler.Send(initiator, data, relays) + await subProcessHandler.Send(initiator, data, relays) } send({ type: 'ready' }) diff --git a/src/services/storage/tlv/tlvFilesStorageProcessor.ts b/src/services/storage/tlv/tlvFilesStorageProcessor.ts index 2b4189e4..caccb949 100644 --- a/src/services/storage/tlv/tlvFilesStorageProcessor.ts +++ b/src/services/storage/tlv/tlvFilesStorageProcessor.ts @@ -126,7 +126,7 @@ class TlvFilesStorageProcessor { throw new Error('Unknown metric type: ' + t) } }) - this.wrtc.attachNostrSend((initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => { + this.wrtc.attachNostrSend(async (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => { this.sendResponse({ success: true, type: 'nostrSend', diff --git a/src/services/webRTC/index.ts b/src/services/webRTC/index.ts index 8ee8d884..a2cc90af 100644 --- a/src/services/webRTC/index.ts +++ b/src/services/webRTC/index.ts @@ -27,11 +27,11 @@ export default class webRTC { attachNostrSend(f: NostrSend) { this._nostrSend = f } - private nostrSend: NostrSend = (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => { + private nostrSend: NostrSend = async (initiator: SendInitiator, data: SendData, relays?: string[] | undefined) => { if (!this._nostrSend) { throw new Error("No nostrSend attached") } - this._nostrSend(initiator, data, relays) + await this._nostrSend(initiator, data, relays) } private sendCandidate = (u: WebRtcUserInfo, candidate: string) => { -- 2.53.0