story-research-zapwall/lib/helpers/paymentNoteSyncHelper.ts
2026-01-07 03:10:40 +01:00

133 lines
3.7 KiB
TypeScript

/**
* Helper for syncing payment notes
* Handles the special case of payment notes which require multiple subscriptions
*/
import type { Event, Filter } from 'nostr-tools'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
import { PLATFORM_SERVICE } from '../platformConfig'
import { getPrimaryRelaySync } from '../config'
import { createSubscription } from '@/types/nostr-tools-extended'
import { tryWithRelayRotation } from '../relayRotation'
import { extractTagsFromEvent } from '../nostrTagSystem'
import { cachePaymentNotes } from './syncCacheHelpers'
export function buildPaymentNoteFilters(userPubkey: string, lastSyncDate: number): Filter[] {
return [
{
kinds: [1],
authors: [userPubkey],
'#payment': [''],
'#service': [PLATFORM_SERVICE],
since: lastSyncDate,
limit: 1000,
},
{
kinds: [1],
'#recipient': [userPubkey],
'#payment': [''],
'#service': [PLATFORM_SERVICE],
since: lastSyncDate,
limit: 1000,
},
]
}
export async function createPaymentNoteSubscriptions(
pool: SimplePoolWithSub,
filters: Filter[]
): Promise<Array<ReturnType<typeof createSubscription>>> {
let subscriptions: Array<ReturnType<typeof createSubscription>> = []
try {
const result = await tryWithRelayRotation(
pool as unknown as import('nostr-tools').SimplePool,
async (relayUrl, poolWithSub) => {
await updateProgressForRelay(relayUrl)
return filters.map((filter) => createSubscription(poolWithSub, [relayUrl], [filter]))
},
5000
)
subscriptions = result.flat()
} catch {
const usedRelayUrl = getPrimaryRelaySync()
subscriptions = filters.map((filter) => createSubscription(pool, [usedRelayUrl], [filter]))
}
if (subscriptions.length === 0) {
throw new Error('Failed to create subscriptions')
}
return subscriptions
}
async function updateProgressForRelay(relayUrl: string): Promise<void> {
const { syncProgressManager } = await import('../syncProgressManager')
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
syncProgressManager.setProgress({
...currentProgress,
currentStep: 0,
currentRelay: relayUrl,
})
}
}
export async function processPaymentNoteEvents(
subscriptions: Array<ReturnType<typeof createSubscription>>
): Promise<void> {
const events: Event[] = []
return new Promise<void>((resolve) => {
let finished = false
let eoseCount = 0
const done = async (): Promise<void> => {
if (finished) {
return
}
finished = true
subscriptions.forEach((sub) => sub.unsub())
await cachePaymentNotes(events)
resolve()
}
const handleEose = (): void => {
eoseCount++
if (eoseCount >= subscriptions.length) {
console.warn(`[Sync] EOSE for payment notes, received ${events.length} events`)
void done()
}
}
setupPaymentNoteSubscriptions(subscriptions, events, handleEose)
setTimeout((): void => {
if (!finished) {
console.warn(`[Sync] Timeout for payment notes, received ${events.length} events`)
}
void done()
}, 10000).unref?.()
})
}
function setupPaymentNoteSubscriptions(
subscriptions: Array<ReturnType<typeof createSubscription>>,
events: Event[],
onEose: () => void
): void {
subscriptions.forEach((sub) => {
sub.on('event', (event: Event): void => {
const tags = extractTagsFromEvent(event)
if (tags.type === 'payment' && tags.payment) {
console.warn('[Sync] Received payment note event:', event.id)
if (!events.some((e) => e.id === event.id)) {
events.push(event)
}
}
})
sub.on('eose', onEose)
})
}