156 lines
4.0 KiB
TypeScript
156 lines
4.0 KiB
TypeScript
import { nostrService } from './nostr'
|
|
import type { SimplePoolWithSub } from '@/types/nostr-tools-extended'
|
|
import { getPrimaryRelaySync } from './config'
|
|
|
|
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: SimplePoolWithSub['sub'] extends (...args: any[]) => infer R ? R : never,
|
|
status: ContentDeliveryStatus,
|
|
finalize: (result: ContentDeliveryStatus) => void,
|
|
isResolved: () => boolean
|
|
): void {
|
|
sub.on('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: SimplePoolWithSub,
|
|
authorPubkey: string,
|
|
recipientPubkey: string,
|
|
articleId: string,
|
|
messageEventId: string
|
|
) {
|
|
const filters = createContentDeliveryFilters(authorPubkey, recipientPubkey, articleId, messageEventId)
|
|
const relayUrl = getPrimaryRelaySync()
|
|
return pool.sub([relayUrl], filters)
|
|
}
|
|
|
|
function createContentDeliveryPromise(
|
|
sub: SimplePoolWithSub['sub'] extends (...args: any[]) => infer R ? R : never,
|
|
status: ContentDeliveryStatus
|
|
): Promise<ContentDeliveryStatus> {
|
|
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<ContentDeliveryStatus> {
|
|
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 poolWithSub = pool as SimplePoolWithSub
|
|
const sub = createContentDeliverySubscription(poolWithSub, 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<boolean> {
|
|
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
|
|
}
|
|
}
|