story-research-zapwall/lib/platformTracking.ts

176 lines
5.3 KiB
TypeScript

import { Event } from 'nostr-tools'
import { nostrService } from './nostr'
import { PLATFORM_NPUB } from './platformConfig'
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
import type { ContentDeliveryTracking } from './platformTrackingTypes'
import { buildTrackingEvent } from './platformTrackingEvents'
import { parseTrackingEvent, createArticleDeliveriesSubscription, createRecipientDeliveriesSubscription } from './platformTrackingQueries'
export type { ContentDeliveryTracking } from './platformTrackingTypes'
/**
* Platform tracking service
* Publishes tracking events on Nostr for content delivery verification
* These events are signed by the platform and can be queried for audit purposes
*/
export class PlatformTrackingService {
private readonly platformPubkey: string = PLATFORM_NPUB
private async publishTrackingEvent(event: Event): Promise<void> {
const pool = nostrService.getPool()
if (!pool) {
throw new Error('Pool not initialized')
}
const { getPrimaryRelaySync } = await import('./config')
const relayUrl = getPrimaryRelaySync()
const pubs = pool.publish([relayUrl], event)
await Promise.all(pubs)
}
private validateTrackingPool(): { pool: SimplePoolWithSub; authorPubkey: string } | null {
const pool = nostrService.getPool()
if (!pool) {
console.error('Pool not initialized for platform tracking')
return null
}
const authorPubkey = nostrService.getPublicKey()
if (!authorPubkey) {
console.error('Author public key not available for tracking')
return null
}
return { pool: pool as SimplePoolWithSub, authorPubkey }
}
/**
* Publish a content delivery tracking event
* This event is published by the author but tagged for platform tracking
* The platform can query these events to track all content deliveries
*/
async trackContentDelivery(
tracking: ContentDeliveryTracking,
authorPrivateKey: string
): Promise<string | null> {
try {
const validation = this.validateTrackingPool()
if (!validation) {
return null
}
const { authorPubkey } = validation
const event = buildTrackingEvent(tracking, authorPubkey, authorPrivateKey, this.platformPubkey)
await this.publishTrackingEvent(event)
console.log('Platform tracking event published', {
eventId: event.id,
articleId: tracking.articleId,
recipientPubkey: tracking.recipientPubkey,
messageEventId: tracking.messageEventId,
authorAmount: tracking.authorAmount,
platformCommission: tracking.platformCommission,
timestamp: new Date().toISOString(),
})
return event.id
} catch (error) {
console.error('Error publishing platform tracking event', {
articleId: tracking.articleId,
recipientPubkey: tracking.recipientPubkey,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(),
})
return null
}
}
/**
* Query tracking events for an article
* Returns all delivery tracking events for a specific article
*/
async getArticleDeliveries(articleId: string): Promise<ContentDeliveryTracking[]> {
try {
const pool = nostrService.getPool()
if (!pool) {
return []
}
return new Promise((resolve) => {
const deliveries: ContentDeliveryTracking[] = []
let resolved = false
const sub = createArticleDeliveriesSubscription(pool, articleId, this.platformPubkey)
const finalize = () => {
if (resolved) {
return
}
resolved = true
sub.unsub()
resolve(deliveries)
}
sub.on('event', (event: Event) => {
const delivery = parseTrackingEvent(event)
if (delivery) {
deliveries.push(delivery)
}
})
sub.on('eose', finalize)
setTimeout(finalize, 5000)
})
} catch (error) {
console.error('Error querying article deliveries', {
articleId,
error: error instanceof Error ? error.message : 'Unknown error',
})
return []
}
}
/**
* Query all deliveries for a recipient
*/
async getRecipientDeliveries(recipientPubkey: string): Promise<ContentDeliveryTracking[]> {
try {
const pool = nostrService.getPool()
if (!pool) {
return []
}
return new Promise((resolve) => {
const deliveries: ContentDeliveryTracking[] = []
let resolved = false
const sub = createRecipientDeliveriesSubscription(pool, recipientPubkey, this.platformPubkey)
const finalize = () => {
if (resolved) {
return
}
resolved = true
sub.unsub()
resolve(deliveries)
}
sub.on('event', (event: Event) => {
const delivery = parseTrackingEvent(event)
if (delivery) {
deliveries.push(delivery)
}
})
sub.on('eose', finalize)
setTimeout(finalize, 5000)
})
} catch (error) {
console.error('Error querying recipient deliveries', {
recipientPubkey,
error: error instanceof Error ? error.message : 'Unknown error',
})
return []
}
}
}
export const platformTracking = new PlatformTrackingService()