story-research-zapwall/lib/nostrPrivateMessages.ts
Nicolas Cantu 85e3e57ad5 Remove all control disabling comments
- Remove @ts-expect-error comment from _unusedExtractTags
- Remove eslint-disable comment from zapAggregation.ts and fix type properly
- Remove unused _unusedExtractTags function
- Remove unused decryptDecryptionKey import
- Fix extractTagsFromEvent return type to include all optional properties explicitly
- All errors fixed without disabling any controls
2025-12-27 22:27:47 +01:00

131 lines
3.8 KiB
TypeScript

import { Event, nip04 } from 'nostr-tools'
import { SimplePool } from 'nostr-tools'
import { decryptArticleContent, type DecryptionKey } from './articleEncryption'
const RELAY_URL = process.env.NEXT_PUBLIC_NOSTR_RELAY_URL ?? 'wss://relay.damus.io'
function createPrivateMessageFilters(eventId: string, publicKey: string, authorPubkey: string) {
return [
{
kinds: [4], // Encrypted direct messages
'#p': [publicKey],
'#e': [eventId], // Filter by event ID to find relevant private messages
authors: [authorPubkey], // Filter by author of the original article
limit: 10, // Limit to recent messages
},
]
}
function decryptContent(privateKey: string, event: Event): Promise<string | null> {
return Promise.resolve(nip04.decrypt(privateKey, event.pubkey, event.content)).then((decrypted) =>
decrypted ? decrypted : null
)
}
/**
* Get private content for an article (encrypted message from author)
* This function now returns the decryption key instead of the full content
*/
export function getPrivateContent(
pool: SimplePool,
eventId: string,
authorPubkey: string,
privateKey: string,
publicKey: string
): Promise<string | null> {
if (!privateKey || !pool || !publicKey) {
throw new Error('Private key not set or pool not initialized')
}
return new Promise((resolve) => {
let resolved = false
const sub = pool.sub([RELAY_URL], createPrivateMessageFilters(eventId, publicKey, authorPubkey))
const finalize = (result: string | null) => {
if (resolved) {
return
}
resolved = true
sub.unsub()
resolve(result)
}
sub.on('event', (event: Event) => {
void decryptContent(privateKey, event)
.then((content) => {
if (content) {
finalize(content)
}
})
.catch((e) => {
console.error('Error decrypting content:', e)
})
})
sub.on('eose', () => finalize(null))
setTimeout(() => finalize(null), 5000)
})
}
/**
* Get decryption key for an article from private messages
* Returns the decryption key and IV if found
*/
export async function getDecryptionKey(
pool: SimplePool,
eventId: string,
authorPubkey: string,
recipientPrivateKey: string,
recipientPublicKey: string
): Promise<DecryptionKey | null> {
if (!recipientPrivateKey || !pool || !recipientPublicKey) {
throw new Error('Private key not set or pool not initialized')
}
return new Promise((resolve) => {
let resolved = false
const sub = pool.sub([RELAY_URL], createPrivateMessageFilters(eventId, recipientPublicKey, authorPubkey))
const finalize = (result: DecryptionKey | null) => {
if (resolved) {
return
}
resolved = true
sub.unsub()
resolve(result)
}
sub.on('event', async (event: Event) => {
try {
const decryptedContent = await decryptContent(recipientPrivateKey, event)
if (decryptedContent) {
try {
// Try to parse as decryption key (new format)
const keyData = JSON.parse(decryptedContent) as DecryptionKey
if (keyData.key && keyData.iv) {
finalize(keyData)
return
}
} catch {
// If parsing fails, it might be old format (full content)
// Return null to indicate we need to use the old method
}
}
} catch (e) {
console.error('Error decrypting decryption key:', e)
}
})
sub.on('eose', () => finalize(null))
setTimeout(() => finalize(null), 5000)
})
}
/**
* Decrypt article content using the decryption key from private message
*/
export async function decryptArticleContentWithKey(
encryptedContent: string,
decryptionKey: DecryptionKey
): Promise<string> {
return decryptArticleContent(encryptedContent, decryptionKey.key, decryptionKey.iv)
}