lint fix wip

This commit is contained in:
Nicolas Cantu 2026-01-09 02:11:12 +01:00
parent 38941147cb
commit bb5cfa758c
19 changed files with 315 additions and 212 deletions

View File

@ -24,14 +24,14 @@ export function useArticlePayment(
const checkPaymentStatus = async (hash: string, userPubkey: string): Promise<void> => { const checkPaymentStatus = async (hash: string, userPubkey: string): Promise<void> => {
try { try {
const hasPaid = await paymentService.waitForArticlePayment( const hasPaid = await paymentService.waitForArticlePayment({
hash, paymentHash: hash,
article.id, articleId: article.id,
article.pubkey, articlePubkey: article.pubkey,
article.zapAmount, amount: article.zapAmount,
userPubkey, recipientPubkey: userPubkey,
300000 timeout: 300000,
) })
if (hasPaid) { if (hasPaid) {
const content = await nostrService.getPrivateContent(article.id, article.pubkey) const content = await nostrService.getPrivateContent(article.id, article.pubkey)

View File

@ -48,16 +48,16 @@ export async function writeObjectToCache(params: WriteObjectParams): Promise<voi
const tags = extractTagsFromEvent(event) const tags = extractTagsFromEvent(event)
const writeService = await getWriteService() const writeService = await getWriteService()
await writeService.writeObject( await writeService.writeObject({
objectType, objectType,
hash, hash,
event, event,
parsed, parsed,
version ?? tags.version ?? 0, version: version ?? tags.version ?? 0,
hidden ?? tags.hidden ?? false, hidden: hidden ?? tags.hidden ?? false,
index ?? 0, index: index ?? 0,
published ?? false published: published ?? false,
) })
} }
/** /**

View File

@ -107,14 +107,23 @@ class NostrService {
if (result.status === 'fulfilled') { if (result.status === 'fulfilled') {
successfulRelays.push(relayUrl) successfulRelays.push(relayUrl)
// Log successful publication // Log successful publication
void publishLog.logPublication(event.id, relayUrl, true, undefined) void publishLog.logPublication({
eventId: event.id,
relayUrl,
success: true,
})
} else { } else {
const error = result.reason const error = result.reason
const errorMessage = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
console.error(`[NostrService] Relay ${relayUrl} failed during publish:`, error) console.error(`[NostrService] Relay ${relayUrl} failed during publish:`, error)
relaySessionManager.markRelayFailed(relayUrl) relaySessionManager.markRelayFailed(relayUrl)
// Log failed publication // Log failed publication
void publishLog.logPublication(event.id, relayUrl, false, errorMessage) void publishLog.logPublication({
eventId: event.id,
relayUrl,
success: false,
error: errorMessage,
})
} }
}) })
@ -143,14 +152,23 @@ class NostrService {
if (result.status === 'fulfilled') { if (result.status === 'fulfilled') {
successfulRelays.push(relayUrl) successfulRelays.push(relayUrl)
// Log successful publication // Log successful publication
void publishLog.logPublication(event.id, relayUrl, true, undefined) void publishLog.logPublication({
eventId: event.id,
relayUrl,
success: true,
})
} else { } else {
const error = result.reason const error = result.reason
const errorMessage = error instanceof Error ? error.message : String(error) const errorMessage = error instanceof Error ? error.message : String(error)
console.error(`[NostrService] Relay ${relayUrl} failed during publish:`, error) console.error(`[NostrService] Relay ${relayUrl} failed during publish:`, error)
relaySessionManager.markRelayFailed(relayUrl) relaySessionManager.markRelayFailed(relayUrl)
// Log failed publication // Log failed publication
void publishLog.logPublication(event.id, relayUrl, false, errorMessage) void publishLog.logPublication({
eventId: event.id,
relayUrl,
success: false,
error: errorMessage,
})
} }
}) })

View File

@ -31,7 +31,13 @@ async function isValidZapReceipt(
// Import verification service dynamically to avoid circular dependencies // Import verification service dynamically to avoid circular dependencies
const { zapVerificationService } = await import('./zapVerification') const { zapVerificationService } = await import('./zapVerification')
return zapVerificationService.verifyZapReceiptForArticle(params.event, params.targetEventId, params.targetPubkey, params.userPubkey, params.amount) return zapVerificationService.verifyZapReceiptForArticle({
zapReceipt: params.event,
articleId: params.targetEventId,
articlePubkey: params.targetPubkey,
userPubkey: params.userPubkey,
expectedAmount: params.amount,
})
} }
/** /**

View File

@ -75,7 +75,13 @@ class NotificationService {
// Utiliser writeService pour créer la notification via Web Worker // Utiliser writeService pour créer la notification via Web Worker
const { writeService } = await import('./writeService') const { writeService } = await import('./writeService')
await writeService.createNotification(type, objectType, objectId, eventId, data) await writeService.createNotification({
type,
objectType,
objectId,
eventId,
...(data !== undefined ? { data } : {}),
})
} catch (error) { } catch (error) {
console.error('[NotificationService] Error creating notification:', error) console.error('[NotificationService] Error creating notification:', error)
} }

View File

@ -81,7 +81,7 @@ class ObjectCacheService {
*/ */
private async initDB(objectType: ObjectType): Promise<IDBDatabase> { private async initDB(objectType: ObjectType): Promise<IDBDatabase> {
const helper = this.getDBHelper(objectType) const helper = this.getDBHelper(objectType)
return await helper.init() return helper.init()
} }
/** /**
@ -102,51 +102,52 @@ class ObjectCacheService {
* Verifies and sets the index before insertion * Verifies and sets the index before insertion
* @param published - false if not published, or array of relay URLs that successfully published * @param published - false if not published, or array of relay URLs that successfully published
*/ */
async set( async set(params: {
objectType: ObjectType, objectType: ObjectType
hash: string, hash: string
event: NostrEvent, event: NostrEvent
parsed: unknown, parsed: unknown
version: number, version: number
hidden: boolean, hidden: boolean
index?: number, index?: number
published: false | string[] = false published?: false | string[]
): Promise<void> { }): Promise<void> {
try { try {
const helper = this.getDBHelper(objectType) const helper = this.getDBHelper(params.objectType)
// If index is not provided, calculate it by counting objects with the same hash // If index is not provided, calculate it by counting objects with the same hash
let finalIndex = index let finalIndex = params.index
if (finalIndex === undefined) { if (finalIndex === undefined) {
const count = await this.countObjectsWithHash(objectType, hash) const count = await this.countObjectsWithHash(params.objectType, params.hash)
finalIndex = count finalIndex = count
} }
const id = buildObjectId(hash, finalIndex, version) const id = buildObjectId(params.hash, finalIndex, params.version)
// Check if object already exists to preserve published status if updating // Check if object already exists to preserve published status if updating
const existing = await helper.get<CachedObject>(id).catch(() => null) const existing = await helper.get<CachedObject>(id).catch(() => null)
// If updating and published is not provided, preserve existing published status // If updating and published is not provided, preserve existing published status
const published = params.published ?? false
const finalPublished = existing && published === false ? existing.published : published const finalPublished = existing && published === false ? existing.published : published
const cached: CachedObject = { const cached: CachedObject = {
id, id,
hash, hash: params.hash,
hashId: hash, // Legacy field for backward compatibility hashId: params.hash, // Legacy field for backward compatibility
index: finalIndex, index: finalIndex,
event, event: params.event,
parsed, parsed: params.parsed,
version, version: params.version,
hidden, hidden: params.hidden,
createdAt: event.created_at, createdAt: params.event.created_at,
cachedAt: Date.now(), cachedAt: Date.now(),
published: finalPublished, published: finalPublished,
} }
await helper.put(cached) await helper.put(cached)
} catch (cacheError) { } catch (cacheError) {
console.error(`Error caching ${objectType} object:`, cacheError) console.error(`Error caching ${params.objectType} object:`, cacheError)
} }
} }

View File

@ -81,21 +81,21 @@ export class PaymentService {
/** /**
* Check if payment for an article has been completed * Check if payment for an article has been completed
*/ */
async checkArticlePayment( async checkArticlePayment(params: {
_paymentHash: string, paymentHash: string
articleId: string, articleId: string
articlePubkey: string, articlePubkey: string
amount: number, amount: number
userPubkey?: string userPubkey?: string
): Promise<boolean> { }): Promise<boolean> {
try { try {
// With Alby/WebLN, we rely on zap receipts for payment verification // With Alby/WebLN, we rely on zap receipts for payment verification
// since WebLN doesn't provide payment status checking // since WebLN doesn't provide payment status checking
const zapReceiptExists = await nostrService.checkZapReceipt( const zapReceiptExists = await nostrService.checkZapReceipt(
articlePubkey, params.articlePubkey,
articleId, params.articleId,
amount, params.amount,
userPubkey params.userPubkey
) )
return zapReceiptExists return zapReceiptExists
@ -109,22 +109,22 @@ export class PaymentService {
* Wait for payment completion with polling * Wait for payment completion with polling
* After payment is confirmed, sends private content to the user * After payment is confirmed, sends private content to the user
*/ */
waitForArticlePayment( waitForArticlePayment(params: {
paymentHash: string, paymentHash: string
articleId: string, articleId: string
articlePubkey: string, articlePubkey: string
amount: number, amount: number
recipientPubkey: string, recipientPubkey: string
timeout: number = 300000 // 5 minutes timeout?: number
): Promise<boolean> { }): Promise<boolean> {
return waitForArticlePaymentHelper( return waitForArticlePaymentHelper({
paymentHash, paymentHash: params.paymentHash,
articleId, articleId: params.articleId,
articlePubkey, articlePubkey: params.articlePubkey,
amount, amount: params.amount,
recipientPubkey, recipientPubkey: params.recipientPubkey,
timeout ...(params.timeout !== undefined ? { timeout: params.timeout } : {}),
) })
} }
/** /**

View File

@ -7,49 +7,61 @@ import { sendPrivateContentAfterPayment } from './paymentPollingMain'
* After payment is confirmed, sends private content to the user * After payment is confirmed, sends private content to the user
*/ */
async function pollPaymentUntilDeadline( async function pollPaymentUntilDeadline(
articleId: string, params: {
articlePubkey: string, articleId: string
amount: number, articlePubkey: string
recipientPubkey: string, amount: number
interval: number, recipientPubkey: string
interval: number
deadline: number deadline: number
}
): Promise<boolean> { ): Promise<boolean> {
try { try {
const zapReceiptExists = await nostrService.checkZapReceipt(articlePubkey, articleId, amount, recipientPubkey) const zapReceiptExists = await nostrService.checkZapReceipt(params.articlePubkey, params.articleId, params.amount, params.recipientPubkey)
if (zapReceiptExists) { if (zapReceiptExists) {
const zapReceiptId = await getZapReceiptId(articlePubkey, articleId, amount, recipientPubkey) const zapReceiptId = await getZapReceiptId(params.articlePubkey, params.articleId, params.amount, params.recipientPubkey)
await sendPrivateContentAfterPayment(articleId, recipientPubkey, amount, zapReceiptId) await sendPrivateContentAfterPayment(params.articleId, params.recipientPubkey, params.amount, zapReceiptId)
return true return true
} }
} catch (error) { } catch (error) {
console.error('Error checking zap receipt:', error) console.error('Error checking zap receipt:', error)
} }
if (Date.now() > deadline) { if (Date.now() > params.deadline) {
return false return false
} }
return new Promise<boolean>((resolve) => { return new Promise<boolean>((resolve) => {
setTimeout(() => { setTimeout(() => {
void pollPaymentUntilDeadline(articleId, articlePubkey, amount, recipientPubkey, interval, deadline) void pollPaymentUntilDeadline(params)
.then(resolve) .then(resolve)
.catch(() => resolve(false)) .catch(() => resolve(false))
}, interval) }, params.interval)
}) })
} }
export async function waitForArticlePayment( export async function waitForArticlePayment(
_paymentHash: string, params: {
articleId: string, paymentHash: string
articlePubkey: string, articleId: string
amount: number, articlePubkey: string
recipientPubkey: string, amount: number
timeout: number = 300000 // 5 minutes recipientPubkey: string
timeout?: number
}
): Promise<boolean> { ): Promise<boolean> {
const interval = 2000 const interval = 2000
const timeout = params.timeout ?? 300000
const deadline = Date.now() + timeout const deadline = Date.now() + timeout
try { try {
return pollPaymentUntilDeadline(articleId, articlePubkey, amount, recipientPubkey, interval, deadline) return pollPaymentUntilDeadline({
articleId: params.articleId,
articlePubkey: params.articlePubkey,
amount: params.amount,
recipientPubkey: params.recipientPubkey,
interval,
deadline,
})
} catch (error) { } catch (error) {
console.error('Wait for payment error:', error) console.error('Wait for payment error:', error)
return false return false

View File

@ -19,6 +19,15 @@ interface PublicationLogEntry {
objectId?: string // ID of the object in cache objectId?: string // ID of the object in cache
} }
interface LogPublicationParams {
eventId: string
relayUrl: string
success: boolean
error?: string
objectType?: string
objectId?: string
}
class PublishLogService { class PublishLogService {
private readonly dbHelper: IndexedDBHelper private readonly dbHelper: IndexedDBHelper
@ -52,17 +61,10 @@ class PublishLogService {
* Log a publication attempt * Log a publication attempt
* Utilise writeService pour écrire via Web Worker * Utilise writeService pour écrire via Web Worker
*/ */
async logPublication( async logPublication(params: LogPublicationParams): Promise<void> {
eventId: string,
relayUrl: string,
success: boolean,
error?: string,
objectType?: string,
objectId?: string
): Promise<void> {
// Utiliser writeService pour logger via Web Worker // Utiliser writeService pour logger via Web Worker
const { writeService } = await import('./writeService') const { writeService } = await import('./writeService')
await writeService.logPublication(eventId, relayUrl, success, error, objectType, objectId) await writeService.logPublication(params)
} }
/** /**
@ -70,24 +72,17 @@ class PublishLogService {
* @deprecated Utiliser logPublication qui utilise writeService * @deprecated Utiliser logPublication qui utilise writeService
* @internal Utilisé uniquement par writeService en fallback * @internal Utilisé uniquement par writeService en fallback
*/ */
async logPublicationDirect( async logPublicationDirect(params: LogPublicationParams): Promise<void> {
eventId: string,
relayUrl: string,
success: boolean,
error?: string,
objectType?: string,
objectId?: string
): Promise<void> {
try { try {
const entry: PublicationLogEntry = { const entry: PublicationLogEntry = {
id: `${eventId}_${relayUrl}_${Date.now()}`, // Unique ID id: `${params.eventId}_${params.relayUrl}_${Date.now()}`, // Unique ID
eventId, eventId: params.eventId,
relayUrl, relayUrl: params.relayUrl,
success, success: params.success,
...(error !== undefined ? { error } : {}), ...(params.error !== undefined ? { error: params.error } : {}),
timestamp: Date.now(), timestamp: Date.now(),
...(objectType !== undefined ? { objectType } : {}), ...(params.objectType !== undefined ? { objectType: params.objectType } : {}),
...(objectId !== undefined ? { objectId } : {}), ...(params.objectId !== undefined ? { objectId: params.objectId } : {}),
} }
await this.dbHelper.add(entry) await this.dbHelper.add(entry)

View File

@ -179,13 +179,26 @@ class PublishWorkerService {
if (status.success) { if (status.success) {
successfulRelays.push(relayUrl) successfulRelays.push(relayUrl)
// Log successful publication // Log successful publication
void publishLog.logPublication(obj.event.id, relayUrl, true, undefined, obj.objectType, obj.id) void publishLog.logPublication({
eventId: obj.event.id,
relayUrl,
success: true,
objectType: obj.objectType,
objectId: obj.id,
})
} else { } else {
const errorMessage = status.error ?? 'Unknown error' const errorMessage = status.error ?? 'Unknown error'
console.warn(`[PublishWorker] Relay ${relayUrl} failed for ${obj.objectType}:${obj.id}:`, errorMessage) console.warn(`[PublishWorker] Relay ${relayUrl} failed for ${obj.objectType}:${obj.id}:`, errorMessage)
relaySessionManager.markRelayFailed(relayUrl) relaySessionManager.markRelayFailed(relayUrl)
// Log failed publication // Log failed publication
void publishLog.logPublication(obj.event.id, relayUrl, false, errorMessage, obj.objectType, obj.id) void publishLog.logPublication({
eventId: obj.event.id,
relayUrl,
success: false,
error: errorMessage,
objectType: obj.objectType,
objectId: obj.id,
})
} }
}) })

View File

@ -3,7 +3,7 @@ import { getCachedObjectById } from './helpers/queryHelpers'
import { objectCache } from './objectCache' import { objectCache } from './objectCache'
export async function getPurchaseById(purchaseId: string, _timeoutMs: number = 5000): Promise<Purchase | null> { export async function getPurchaseById(purchaseId: string, _timeoutMs: number = 5000): Promise<Purchase | null> {
return await getCachedObjectById<Purchase>('purchase', purchaseId) return getCachedObjectById<Purchase>('purchase', purchaseId)
} }
export async function getPurchasesForArticle(articleId: string, _timeoutMs: number = 5000): Promise<Purchase[]> { export async function getPurchasesForArticle(articleId: string, _timeoutMs: number = 5000): Promise<Purchase[]> {

View File

@ -3,7 +3,7 @@ import { objectCache } from './objectCache'
import { getCachedObjectById } from './helpers/queryHelpers' import { getCachedObjectById } from './helpers/queryHelpers'
export async function getReviewTipById(reviewTipId: string, _timeoutMs: number = 5000): Promise<ReviewTip | null> { export async function getReviewTipById(reviewTipId: string, _timeoutMs: number = 5000): Promise<ReviewTip | null> {
return await getCachedObjectById<ReviewTip>('review_tip', reviewTipId) return getCachedObjectById<ReviewTip>('review_tip', reviewTipId)
} }
export async function getReviewTipsForArticle(articleId: string, _timeoutMs: number = 5000): Promise<ReviewTip[]> { export async function getReviewTipsForArticle(articleId: string, _timeoutMs: number = 5000): Promise<ReviewTip[]> {

View File

@ -22,5 +22,5 @@ export async function getSeriesByAuthor(authorPubkey: string, _timeoutMs: number
} }
export async function getSeriesById(seriesId: string, _timeoutMs: number = 2000): Promise<Series | null> { export async function getSeriesById(seriesId: string, _timeoutMs: number = 2000): Promise<Series | null> {
return await getCachedObjectById<Series>('series', seriesId) return getCachedObjectById<Series>('series', seriesId)
} }

View File

@ -28,11 +28,13 @@ export async function verifyTransactionBeforeTracking(
} }
export function buildTrackingData( export function buildTrackingData(
transactionId: string, params: {
authorPubkey: string, transactionId: string
authorMainnetAddress: string, authorPubkey: string
split: { total: number; authorSats: number; platformSats: number }, authorMainnetAddress: string
split: { total: number; authorSats: number; platformSats: number; authorMainnetAddress?: string }
verification: { confirmed: boolean; confirmations: number } verification: { confirmed: boolean; confirmations: number }
}
): { ): {
transactionId: string transactionId: string
authorPubkey: string authorPubkey: string
@ -45,15 +47,15 @@ export function buildTrackingData(
confirmations: number confirmations: number
} { } {
return { return {
transactionId, transactionId: params.transactionId,
authorPubkey, authorPubkey: params.authorPubkey,
authorMainnetAddress, authorMainnetAddress: params.authorMainnetAddress,
amount: split.total, amount: params.split.total,
authorAmount: split.authorSats, authorAmount: params.split.authorSats,
platformCommission: split.platformSats, platformCommission: params.split.platformSats,
timestamp: Math.floor(Date.now() / 1000), timestamp: Math.floor(Date.now() / 1000),
confirmed: verification.confirmed, confirmed: params.verification.confirmed,
confirmations: verification.confirmations, confirmations: params.verification.confirmations,
} }
} }
@ -89,11 +91,13 @@ export async function trackSponsoringPayment(
} }
const trackingData = buildTrackingData( const trackingData = buildTrackingData(
{
transactionId, transactionId,
authorPubkey, authorPubkey,
authorMainnetAddress, authorMainnetAddress,
split, split,
verification verification,
}
) )
await sponsoringTrackingService.trackSponsoringPayment(trackingData, authorPrivateKey) await sponsoringTrackingService.trackSponsoringPayment(trackingData, authorPrivateKey)

View File

@ -3,7 +3,7 @@ import { objectCache } from './objectCache'
import { getCachedObjectById } from './helpers/queryHelpers' import { getCachedObjectById } from './helpers/queryHelpers'
export async function getSponsoringById(sponsoringId: string, _timeoutMs: number = 5000): Promise<Sponsoring | null> { export async function getSponsoringById(sponsoringId: string, _timeoutMs: number = 5000): Promise<Sponsoring | null> {
return await getCachedObjectById<Sponsoring>('sponsoring', sponsoringId) return getCachedObjectById<Sponsoring>('sponsoring', sponsoringId)
} }
export async function getSponsoringByAuthor(authorPubkey: string, _timeoutMs: number = 5000): Promise<Sponsoring[]> { export async function getSponsoringByAuthor(authorPubkey: string, _timeoutMs: number = 5000): Promise<Sponsoring[]> {

View File

@ -152,12 +152,21 @@ class ServiceWorkerSyncHandler {
if (status.success) { if (status.success) {
successfulRelays.push(relayUrl) successfulRelays.push(relayUrl)
// Log successful publication // Log successful publication
void publishLog.logPublication(event.id, relayUrl, true, undefined) void publishLog.logPublication({
eventId: event.id,
relayUrl,
success: true,
})
} else { } else {
const errorMessage = status.error ?? 'Unknown error' const errorMessage = status.error ?? 'Unknown error'
console.error(`[SWSyncHandler] Relay ${relayUrl} failed:`, errorMessage) console.error(`[SWSyncHandler] Relay ${relayUrl} failed:`, errorMessage)
// Log failed publication // Log failed publication
void publishLog.logPublication(event.id, relayUrl, false, errorMessage) void publishLog.logPublication({
eventId: event.id,
relayUrl,
success: false,
error: errorMessage,
})
} }
}) })

View File

@ -53,7 +53,16 @@ class WriteOrchestrator {
.filter((relay): relay is string => relay !== null) .filter((relay): relay is string => relay !== null)
}), }),
// 2. Write to IndexedDB via Web Worker (en parallèle, avec published: false initialement) // 2. Write to IndexedDB via Web Worker (en parallèle, avec published: false initialement)
writeService.writeObject(objectType, hash, event, parsed, version, hidden, index, false), writeService.writeObject({
objectType,
hash,
event,
parsed,
version,
hidden,
...(index !== undefined ? { index } : {}),
published: false,
}),
]) ])
// Traiter le résultat réseau // Traiter le résultat réseau
@ -85,24 +94,24 @@ class WriteOrchestrator {
/** /**
* Create and publish event from template * Create and publish event from template
*/ */
async createAndPublishEvent( async createAndPublishEvent(params: {
eventTemplate: EventTemplate, eventTemplate: EventTemplate
relays: string[], relays: string[]
objectType: ObjectType, objectType: ObjectType
hash: string, hash: string
parsed: unknown, parsed: unknown
version: number, version: number
hidden: boolean, hidden: boolean
index?: number index?: number
): Promise<{ success: boolean; event: NostrEvent; published: false | string[] }> { }): Promise<{ success: boolean; event: NostrEvent; published: false | string[] }> {
if (!this.privateKey) { if (!this.privateKey) {
throw new Error('Private key not set') throw new Error('Private key not set')
} }
// Create event // Create event
const unsignedEvent: EventTemplate = { const unsignedEvent: EventTemplate = {
...eventTemplate, ...params.eventTemplate,
created_at: eventTemplate.created_at ?? Math.floor(Date.now() / 1000), created_at: params.eventTemplate.created_at ?? Math.floor(Date.now() / 1000),
} }
const secretKey = hexToBytes(this.privateKey) const secretKey = hexToBytes(this.privateKey)
@ -111,15 +120,15 @@ class WriteOrchestrator {
// Write and publish // Write and publish
const result = await this.writeAndPublish( const result = await this.writeAndPublish(
{ {
objectType, objectType: params.objectType,
hash, hash: params.hash,
event: finalizedEvent, event: finalizedEvent,
parsed, parsed: params.parsed,
version, version: params.version,
hidden, hidden: params.hidden,
...(index !== undefined ? { index } : {}), ...(params.index !== undefined ? { index: params.index } : {}),
}, },
relays params.relays
) )
return { return {

View File

@ -7,6 +7,34 @@
import type { NostrEvent } from 'nostr-tools' import type { NostrEvent } from 'nostr-tools'
import type { ObjectType } from './objectCache' import type { ObjectType } from './objectCache'
interface WriteObjectParams {
objectType: ObjectType
hash: string
event: NostrEvent
parsed: unknown
version: number
hidden: boolean
index?: number
published?: false | string[]
}
interface CreateNotificationParams {
type: string
objectType: string
objectId: string
eventId: string
data?: Record<string, unknown>
}
interface LogPublicationParams {
eventId: string
relayUrl: string
success: boolean
error?: string
objectType?: string
objectId?: string
}
class WriteService { class WriteService {
private writeWorker: Worker | null = null private writeWorker: Worker | null = null
private initPromise: Promise<void> | null = null private initPromise: Promise<void> | null = null
@ -91,19 +119,12 @@ class WriteService {
/** /**
* Write object to IndexedDB (via Web Worker) * Write object to IndexedDB (via Web Worker)
*/ */
async writeObject( async writeObject(params: WriteObjectParams): Promise<void> {
objectType: ObjectType,
hash: string,
event: NostrEvent,
parsed: unknown,
version: number,
hidden: boolean,
index?: number,
published: false | string[] = false
): Promise<void> {
try { try {
await this.init() await this.init()
const published = params.published ?? false
if (this.writeWorker) { if (this.writeWorker) {
// Send to worker // Send to worker
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
@ -113,7 +134,7 @@ class WriteService {
const handler = (event: MessageEvent): void => { const handler = (event: MessageEvent): void => {
const { type, data } = event.data const { type, data } = event.data
if (type === 'WRITE_OBJECT_SUCCESS' && data.hash === hash) { if (type === 'WRITE_OBJECT_SUCCESS' && data.hash === params.hash) {
clearTimeout(timeout) clearTimeout(timeout)
this.writeWorker?.removeEventListener('message', handler) this.writeWorker?.removeEventListener('message', handler)
resolve() resolve()
@ -129,13 +150,13 @@ class WriteService {
this.writeWorker.postMessage({ this.writeWorker.postMessage({
type: 'WRITE_OBJECT', type: 'WRITE_OBJECT',
data: { data: {
objectType, objectType: params.objectType,
hash, hash: params.hash,
event, event: params.event,
parsed, parsed: params.parsed,
version, version: params.version,
hidden, hidden: params.hidden,
index, index: params.index,
published, published,
}, },
}) })
@ -144,7 +165,16 @@ class WriteService {
} }
// Fallback: direct write // Fallback: direct write
const { objectCache } = await import('./objectCache') const { objectCache } = await import('./objectCache')
await objectCache.set(objectType, hash, event, parsed, version, hidden, index, published) await objectCache.set({
objectType: params.objectType,
hash: params.hash,
event: params.event,
parsed: params.parsed,
version: params.version,
hidden: params.hidden,
...(params.index !== undefined ? { index: params.index } : {}),
...(params.published !== undefined ? { published: params.published } : {}),
})
} catch (error) { } catch (error) {
console.error('[WriteService] Error writing object:', error) console.error('[WriteService] Error writing object:', error)
@ -205,13 +235,7 @@ class WriteService {
/** /**
* Create notification (via Web Worker) * Create notification (via Web Worker)
*/ */
async createNotification( async createNotification(params: CreateNotificationParams): Promise<void> {
type: string,
objectType: string,
objectId: string,
eventId: string,
data?: Record<string, unknown>
): Promise<void> {
try { try {
await this.init() await this.init()
@ -224,7 +248,7 @@ class WriteService {
const handler = (event: MessageEvent): void => { const handler = (event: MessageEvent): void => {
const { type: responseType, data: responseData } = event.data const { type: responseType, data: responseData } = event.data
if (responseType === 'CREATE_NOTIFICATION_SUCCESS' && responseData.eventId === eventId) { if (responseType === 'CREATE_NOTIFICATION_SUCCESS' && responseData.eventId === params.eventId) {
clearTimeout(timeout) clearTimeout(timeout)
this.writeWorker?.removeEventListener('message', handler) this.writeWorker?.removeEventListener('message', handler)
resolve() resolve()
@ -239,7 +263,13 @@ class WriteService {
this.writeWorker.addEventListener('message', handler) this.writeWorker.addEventListener('message', handler)
this.writeWorker.postMessage({ this.writeWorker.postMessage({
type: 'CREATE_NOTIFICATION', type: 'CREATE_NOTIFICATION',
data: { type, objectType, objectId, eventId, notificationData: data }, data: {
type: params.type,
objectType: params.objectType,
objectId: params.objectId,
eventId: params.eventId,
notificationData: params.data,
},
}) })
} }
}) })
@ -247,13 +277,13 @@ class WriteService {
// Fallback: direct write // Fallback: direct write
const { notificationService } = await import('./notificationService') const { notificationService } = await import('./notificationService')
const notificationParams: Parameters<typeof notificationService.createNotification>[0] = { const notificationParams: Parameters<typeof notificationService.createNotification>[0] = {
type: type as Parameters<typeof notificationService.createNotification>[0]['type'], type: params.type as Parameters<typeof notificationService.createNotification>[0]['type'],
objectType, objectType: params.objectType,
objectId, objectId: params.objectId,
eventId, eventId: params.eventId,
} }
if (data !== undefined) { if (params.data !== undefined) {
notificationParams.data = data notificationParams.data = params.data
} }
await notificationService.createNotification(notificationParams) await notificationService.createNotification(notificationParams)
@ -267,14 +297,7 @@ class WriteService {
/** /**
* Log publication (via Web Worker) * Log publication (via Web Worker)
*/ */
async logPublication( async logPublication(params: LogPublicationParams): Promise<void> {
eventId: string,
relayUrl: string,
success: boolean,
error?: string,
objectType?: string,
objectId?: string
): Promise<void> {
try { try {
await this.init() await this.init()
@ -282,13 +305,20 @@ class WriteService {
// Send to worker // Send to worker
this.writeWorker.postMessage({ this.writeWorker.postMessage({
type: 'LOG_PUBLICATION', type: 'LOG_PUBLICATION',
data: { eventId, relayUrl, success, error, objectType, objectId }, data: {
eventId: params.eventId,
relayUrl: params.relayUrl,
success: params.success,
error: params.error,
objectType: params.objectType,
objectId: params.objectId,
},
}) })
// Don't wait for response for logs (fire and forget) // Don't wait for response for logs (fire and forget)
} else { } else {
// Fallback: direct write // Fallback: direct write
const { publishLog } = await import('./publishLog') const { publishLog } = await import('./publishLog')
await publishLog.logPublicationDirect(eventId, relayUrl, success, error, objectType, objectId) await publishLog.logPublicationDirect(params)
} }
} catch (logError) { } catch (logError) {
console.error('[WriteService] Error logging publication:', logError) console.error('[WriteService] Error logging publication:', logError)

View File

@ -19,23 +19,23 @@ export class ZapVerificationService {
/** /**
* Check if a zap receipt is valid for a specific article and user * Check if a zap receipt is valid for a specific article and user
*/ */
verifyZapReceiptForArticle( verifyZapReceiptForArticle(params: {
zapReceipt: Event, zapReceipt: Event
articleId: string, articleId: string
articlePubkey: string, articlePubkey: string
_userPubkey: string, userPubkey: string
expectedAmount: number expectedAmount: number
): boolean { }): boolean {
if (!this.verifyZapReceiptSignature(zapReceipt)) { if (!this.verifyZapReceiptSignature(params.zapReceipt)) {
console.warn('Zap receipt signature verification failed') console.warn('Zap receipt signature verification failed')
return false return false
} }
return ( return (
this.isRecipientValid(zapReceipt, articlePubkey) && this.isRecipientValid(params.zapReceipt, params.articlePubkey) &&
this.isArticleReferenced(zapReceipt, articleId) && this.isArticleReferenced(params.zapReceipt, params.articleId) &&
this.isAmountValid(zapReceipt, expectedAmount) && this.isAmountValid(params.zapReceipt, params.expectedAmount) &&
this.isZapKind(zapReceipt) this.isZapKind(params.zapReceipt)
) )
} }