import { nostrService } from './nostr' import { createSubscription } from '@/types/nostr-tools-extended' import { getPrimaryRelaySync } from './config' import type { Event } from 'nostr-tools' export interface ContentDeliveryStatus { messageEventId: string | null published: boolean verifiedOnRelay: boolean retrievable: boolean error?: string } /** * Verify that private content was successfully delivered to recipient * Checks multiple aspects to ensure delivery certainty */ function createContentDeliveryFilters(authorPubkey: string, recipientPubkey: string, articleId: string, messageEventId: string) { const filters: Array<{ kinds: number[] ids?: string[] authors: string[] '#p': string[] '#e': string[] limit: number }> = [ { kinds: [4], authors: [authorPubkey], '#p': [recipientPubkey], '#e': [articleId], limit: 1, }, ] if (messageEventId && filters[0]) { filters[0].ids = [messageEventId] } return filters } function setupContentDeliveryHandlers( sub: import('@/types/nostr-tools-extended').Subscription, status: ContentDeliveryStatus, finalize: (result: ContentDeliveryStatus) => void, isResolved: () => boolean ): void { sub.on('event', (event: Event) => { status.published = true status.verifiedOnRelay = true status.messageEventId = event.id status.retrievable = true finalize(status) }) sub.on('eose', () => { if (!status.published) { status.error = 'Message not found on relay' } finalize(status) }) setTimeout(() => { if (!isResolved()) { if (!status.published) { status.error = 'Timeout waiting for message verification' } finalize(status) } }, 5000) } function createContentDeliverySubscription( pool: import('nostr-tools').SimplePool, authorPubkey: string, recipientPubkey: string, articleId: string, messageEventId: string ) { const filters = createContentDeliveryFilters(authorPubkey, recipientPubkey, articleId, messageEventId) const relayUrl = getPrimaryRelaySync() return createSubscription(pool, [relayUrl], filters) } function createContentDeliveryPromise( sub: import('@/types/nostr-tools-extended').Subscription, status: ContentDeliveryStatus ): Promise { return new Promise((resolve) => { let resolved = false const finalize = (result: ContentDeliveryStatus) => { if (resolved) { return } resolved = true sub.unsub() resolve(result) } setupContentDeliveryHandlers(sub, status, finalize, () => resolved) }) } export function verifyContentDelivery( articleId: string, authorPubkey: string, recipientPubkey: string, messageEventId: string ): Promise { const status: ContentDeliveryStatus = { messageEventId, published: false, verifiedOnRelay: false, retrievable: false, } try { const pool = nostrService.getPool() if (!pool) { status.error = 'Pool not initialized' return Promise.resolve(status) } const sub = createContentDeliverySubscription(pool, authorPubkey, recipientPubkey, articleId, messageEventId) return createContentDeliveryPromise(sub, status) } catch (error) { status.error = error instanceof Error ? error.message : 'Unknown error' return Promise.resolve(status) } } /** * Check if recipient can retrieve the private message * This verifies that the message is accessible with the recipient's key */ export async function verifyRecipientCanRetrieve( articleId: string, authorPubkey: string, recipientPubkey: string, messageEventId: string ): Promise { try { const status = await verifyContentDelivery(articleId, authorPubkey, recipientPubkey, messageEventId) return status.retrievable && status.verifiedOnRelay } catch (error) { console.error('Error verifying recipient can retrieve', { articleId, recipientPubkey, messageEventId, error: error instanceof Error ? error.message : 'Unknown error', }) return false } }