lint fix wip

This commit is contained in:
Nicolas Cantu 2026-01-07 02:06:09 +01:00
parent 964f4aeb60
commit 9f7a0e1527
10 changed files with 535 additions and 97 deletions

View File

@ -4,10 +4,13 @@ import { storePrivateContent, getStoredPrivateContent } from './articleStorage'
import { buildTags } from './nostrTagSystem' import { buildTags } from './nostrTagSystem'
import { PLATFORM_SERVICE } from './platformConfig' import { PLATFORM_SERVICE } from './platformConfig'
import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator' import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator'
import { parseObjectId } from './urlGenerator' import { buildObjectId } from './urlGenerator'
import type { ArticleDraft, PublishedArticle } from './articlePublisher' import type { ArticleDraft, PublishedArticle } from './articlePublisher'
import type { AlbyInvoice } from '@/types/alby' import type { AlbyInvoice } from '@/types/alby'
import type { Review, Series } from '@/types/nostr' import type { Article, Review, Series } from '@/types/nostr'
import { writeOrchestrator } from './writeOrchestrator'
import { finalizeEvent } from 'nostr-tools'
import { hexToBytes } from 'nostr-tools/utils'
export interface ArticleUpdateResult extends PublishedArticle { export interface ArticleUpdateResult extends PublishedArticle {
originalArticleId: string originalArticleId: string
@ -36,6 +39,55 @@ async function ensurePresentation(authorPubkey: string): Promise<string> {
return presentation.id return presentation.id
} }
async function buildParsedArticleFromDraft(
draft: ArticleDraft,
invoice: AlbyInvoice,
authorPubkey: string
): Promise<{ article: Article; hash: string; version: number; index: number }> {
const category = draft.category === 'science-fiction' ? 'sciencefiction' : draft.category === 'scientific-research' ? 'research' : 'sciencefiction'
const hashId = await generatePublicationHashId({
pubkey: authorPubkey,
title: draft.title,
preview: draft.preview,
category,
seriesId: draft.seriesId ?? undefined,
bannerUrl: draft.bannerUrl ?? undefined,
zapAmount: draft.zapAmount,
})
const hash = hashId
const version = 0
const index = 0
const id = buildObjectId(hash, index, version)
const article: Article = {
id,
hash,
version,
index,
pubkey: authorPubkey,
title: draft.title,
preview: draft.preview,
content: '',
description: draft.preview,
contentDescription: draft.preview,
createdAt: Math.floor(Date.now() / 1000),
zapAmount: draft.zapAmount,
paid: false,
thumbnailUrl: draft.bannerUrl ?? '',
invoice: invoice.invoice,
paymentHash: invoice.paymentHash ?? undefined,
category: draft.category,
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
...(draft.pages && draft.pages.length > 0 ? { pages: draft.pages } : {}),
kindType: 'article',
}
return { article, hash, version, index }
}
async function publishPreviewWithInvoice( async function publishPreviewWithInvoice(
draft: ArticleDraft, draft: ArticleDraft,
invoice: AlbyInvoice, invoice: AlbyInvoice,
@ -43,9 +95,48 @@ async function publishPreviewWithInvoice(
presentationId: string, presentationId: string,
extraTags?: string[][] extraTags?: string[][]
): Promise<import('nostr-tools').Event | null> { ): Promise<import('nostr-tools').Event | null> {
const previewEvent = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags) // Build parsed article object
const publishedEvent = await nostrService.publishEvent(previewEvent) const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
return publishedEvent ?? null
// Build event template
const previewEventTemplate = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags)
// Set private key in orchestrator
const privateKey = nostrService.getPrivateKey()
if (!privateKey) {
throw new Error('Private key required for signing')
}
writeOrchestrator.setPrivateKey(privateKey)
// Finalize event
const secretKey = hexToBytes(privateKey)
const event = finalizeEvent(previewEventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'publication',
hash,
event,
parsed: article,
version,
hidden: false,
index,
},
relays
)
if (!result.success) {
return null
}
return event
} }
export async function publishSeries(params: { export async function publishSeries(params: {
@ -60,18 +151,27 @@ export async function publishSeries(params: {
ensureKeys(params.authorPubkey, params.authorPrivateKey) ensureKeys(params.authorPubkey, params.authorPrivateKey)
const {category} = params const {category} = params
requireCategory(category) requireCategory(category)
const event = await buildSeriesEvent(params, category)
const published = await nostrService.publishEvent(event) // Map category to new system
if (!published) { const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research'
throw new Error('Failed to publish series')
} // Generate hash ID from series data
const parsed = parseObjectId(published.id) const hashId = await generateSeriesHashId({
const {hash: parsedHash, version: parsedVersion, index: parsedIndex} = parsed pubkey: params.authorPubkey,
const hash = parsedHash ?? published.id title: params.title,
const version = parsedVersion ?? 0 description: params.description,
const index = parsedIndex ?? 0 category: newCategory,
return { coverUrl: params.coverUrl ?? undefined,
id: published.id, })
const hash = hashId
const version = 0
const index = 0
const id = buildObjectId(hash, index, version)
// Build parsed Series object
const parsedSeries: Series = {
id,
hash, hash,
version, version,
index, index,
@ -84,6 +184,46 @@ export async function publishSeries(params: {
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}), ...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
kindType: 'series', kindType: 'series',
} }
// Build event template
const eventTemplate = await buildSeriesEvent(params, category)
// Set private key in orchestrator
const privateKey = params.authorPrivateKey ?? nostrService.getPrivateKey()
if (!privateKey) {
throw new Error('Private key required for signing')
}
writeOrchestrator.setPrivateKey(privateKey)
// Finalize event
const secretKey = hexToBytes(privateKey)
const event = finalizeEvent(eventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'series',
hash,
event,
parsed: parsedSeries,
version,
hidden: false,
index,
},
relays
)
if (!result.success) {
throw new Error('Failed to publish series')
}
return parsedSeries
} }
async function buildSeriesEvent( async function buildSeriesEvent(
@ -166,18 +306,28 @@ export async function publishReview(params: {
ensureKeys(params.reviewerPubkey, params.authorPrivateKey) ensureKeys(params.reviewerPubkey, params.authorPrivateKey)
const {category} = params const {category} = params
requireCategory(category) requireCategory(category)
const event = await buildReviewEvent(params, category)
const published = await nostrService.publishEvent(event) // Map category to new system
if (!published) { const newCategory = category === 'science-fiction' ? 'sciencefiction' : 'research'
throw new Error('Failed to publish review')
} // Generate hash ID from review data
const parsed = parseObjectId(published.id) const { generateReviewHashId } = await import('./hashIdGenerator')
const {hash: parsedHash, version: parsedVersion, index: parsedIndex} = parsed const hashId = await generateReviewHashId({
const hash = parsedHash ?? published.id pubkey: params.reviewerPubkey,
const version = parsedVersion ?? 0 articleId: params.articleId,
const index = parsedIndex ?? 0 reviewerPubkey: params.reviewerPubkey,
return { content: params.content,
id: published.id, ...(params.title ? { title: params.title } : {}),
})
const hash = hashId
const version = 0
const index = 0
const id = buildObjectId(hash, index, version)
// Build parsed Review object
const parsedReview: Review = {
id,
hash, hash,
version, version,
index, index,
@ -186,11 +336,51 @@ export async function publishReview(params: {
reviewerPubkey: params.reviewerPubkey, reviewerPubkey: params.reviewerPubkey,
content: params.content, content: params.content,
description: params.content.substring(0, 200), description: params.content.substring(0, 200),
createdAt: published.created_at, createdAt: Math.floor(Date.now() / 1000),
...(params.title ? { title: params.title } : {}), ...(params.title ? { title: params.title } : {}),
...(params.text ? { text: params.text } : {}), ...(params.text ? { text: params.text } : {}),
kindType: 'review', kindType: 'review',
} }
// Build event template
const eventTemplate = await buildReviewEvent(params, category)
// Set private key in orchestrator
const privateKey = params.authorPrivateKey ?? nostrService.getPrivateKey()
if (!privateKey) {
throw new Error('Private key required for signing')
}
writeOrchestrator.setPrivateKey(privateKey)
// Finalize event
const secretKey = hexToBytes(privateKey)
const event = finalizeEvent(eventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'review',
hash,
event,
parsed: parsedReview,
version,
hidden: false,
index,
},
relays
)
if (!result.success) {
throw new Error('Failed to publish review')
}
return parsedReview
} }
async function buildReviewEvent( async function buildReviewEvent(

View File

@ -5,6 +5,12 @@ import { createArticleInvoice, createPreviewEvent } from './articleInvoice'
import { encryptArticleContent, encryptDecryptionKey } from './articleEncryption' import { encryptArticleContent, encryptDecryptionKey } from './articleEncryption'
import { storePrivateContent } from './articleStorage' import { storePrivateContent } from './articleStorage'
import type { PublishResult } from './publishResult' import type { PublishResult } from './publishResult'
import { writeOrchestrator } from './writeOrchestrator'
import { finalizeEvent } from 'nostr-tools'
import { hexToBytes } from 'nostr-tools/utils'
import { generatePublicationHashId } from './hashIdGenerator'
import { buildObjectId } from './urlGenerator'
import type { Article } from '@/types/nostr'
export function buildFailure(error?: string): PublishedArticle { export function buildFailure(error?: string): PublishedArticle {
const base: PublishedArticle = { const base: PublishedArticle = {
@ -15,6 +21,55 @@ export function buildFailure(error?: string): PublishedArticle {
return error ? { ...base, error } : base return error ? { ...base, error } : base
} }
async function buildParsedArticleFromDraft(
draft: ArticleDraft,
invoice: AlbyInvoice,
authorPubkey: string
): Promise<{ article: Article; hash: string; version: number; index: number }> {
const category = draft.category === 'science-fiction' ? 'sciencefiction' : draft.category === 'scientific-research' ? 'research' : 'sciencefiction'
const hashId = await generatePublicationHashId({
pubkey: authorPubkey,
title: draft.title,
preview: draft.preview,
category,
seriesId: draft.seriesId ?? undefined,
bannerUrl: draft.bannerUrl ?? undefined,
zapAmount: draft.zapAmount,
})
const hash = hashId
const version = 0
const index = 0
const id = buildObjectId(hash, index, version)
const article: Article = {
id,
hash,
version,
index,
pubkey: authorPubkey,
title: draft.title,
preview: draft.preview,
content: '',
description: draft.preview,
contentDescription: draft.preview,
createdAt: Math.floor(Date.now() / 1000),
zapAmount: draft.zapAmount,
paid: false,
thumbnailUrl: draft.bannerUrl ?? '',
invoice: invoice.invoice,
paymentHash: invoice.paymentHash ?? undefined,
category: draft.category,
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
...(draft.pages && draft.pages.length > 0 ? { pages: draft.pages } : {}),
kindType: 'article',
}
return { article, hash, version, index }
}
export async function publishPreview( export async function publishPreview(
draft: ArticleDraft, draft: ArticleDraft,
invoice: AlbyInvoice, invoice: AlbyInvoice,
@ -25,12 +80,64 @@ export async function publishPreview(
encryptedKey?: string, encryptedKey?: string,
returnStatus?: boolean returnStatus?: boolean
): Promise<import('nostr-tools').Event | null | PublishResult> { ): Promise<import('nostr-tools').Event | null | PublishResult> {
const previewEvent = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey) // Build parsed article object
if (returnStatus) { const { article, hash, version, index } = await buildParsedArticleFromDraft(draft, invoice, authorPubkey)
return await nostrService.publishEvent(previewEvent, true)
// Build event template
const previewEventTemplate = await createPreviewEvent(draft, invoice, authorPubkey, presentationId, extraTags, encryptedContent, encryptedKey)
// Set private key in orchestrator
const privateKey = nostrService.getPrivateKey()
if (!privateKey) {
throw new Error('Private key required for signing')
} }
const publishedEvent = await nostrService.publishEvent(previewEvent, false) writeOrchestrator.setPrivateKey(privateKey)
return publishedEvent ?? null
// Finalize event
const secretKey = hexToBytes(privateKey)
const event = finalizeEvent(previewEventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'publication',
hash,
event,
parsed: article,
version,
hidden: false,
index,
},
relays
)
if (!result.success) {
return null
}
if (returnStatus) {
// Return PublishResult format
const { publishResult } = await import('./publishResult')
return {
event,
relayStatuses: relays.map((relayUrl, idx) => {
const isSuccess = typeof result.published === 'object' && result.published.includes(relayUrl)
return {
relayUrl,
success: isSuccess,
error: isSuccess ? undefined : 'Failed to publish',
}
}),
}
}
return event
} }
export function buildArticleExtraTags(draft: ArticleDraft, _category: NonNullable<ArticleDraft['category']>): string[][] { export function buildArticleExtraTags(draft: ArticleDraft, _category: NonNullable<ArticleDraft['category']>): string[][] {

View File

@ -241,7 +241,7 @@ class NotificationDetector {
/** /**
* Get notification title based on type * Get notification title based on type
*/ */
private getNotificationTitle(type: NotificationType, _obj: CachedObject): string { private _getNotificationTitle(type: NotificationType, _obj: CachedObject): string {
switch (type) { switch (type) {
case 'purchase': case 'purchase':
return 'Nouvel achat' return 'Nouvel achat'
@ -263,7 +263,7 @@ class NotificationDetector {
/** /**
* Get notification message based on type * Get notification message based on type
*/ */
private getNotificationMessage(type: NotificationType, _obj: CachedObject): string { private _getNotificationMessage(type: NotificationType, _obj: CachedObject): string {
switch (type) { switch (type) {
case 'purchase': case 'purchase':
return `Vous avez acheté un article` return `Vous avez acheté un article`

View File

@ -150,6 +150,24 @@ export async function publishReviewTipNote(params: {
tags.push(['json', paymentJson]) tags.push(['json', paymentJson])
// Build parsed ReviewTip object
const parsedReviewTip: ReviewTip = {
id,
hash: hashId,
version: 0,
index: 0,
payerPubkey: params.payerPubkey,
articleId: params.articleId,
reviewId: params.reviewId,
reviewerPubkey: params.reviewerPubkey,
authorPubkey: params.authorPubkey,
amount: params.amount,
paymentHash: params.paymentHash,
createdAt: Math.floor(Date.now() / 1000),
...(params.text ? { text: params.text } : {}),
kindType: 'review_tip',
}
const content = params.text const content = params.text
? `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}\n\n${params.text}` ? `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}\n\n${params.text}`
: `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}` : `Review tip confirmed: ${params.amount} sats for review ${params.reviewId}`
@ -162,7 +180,37 @@ export async function publishReviewTipNote(params: {
} }
nostrService.setPrivateKey(params.payerPrivateKey) nostrService.setPrivateKey(params.payerPrivateKey)
return nostrService.publishEvent(eventTemplate) writeOrchestrator.setPrivateKey(params.payerPrivateKey)
// Finalize event
const secretKey = hexToBytes(params.payerPrivateKey)
const event = finalizeEvent(eventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'review_tip',
hash: hashId,
event,
parsed: parsedReviewTip,
version: 0,
hidden: false,
index: 0,
},
relays
)
if (!result.success) {
return null
}
return event
} }
/** /**
@ -237,6 +285,23 @@ export async function publishSponsoringNote(params: {
tags.push(['json', paymentJson]) tags.push(['json', paymentJson])
// Build parsed Sponsoring object
const parsedSponsoring: Sponsoring = {
id,
hash: hashId,
version: 0,
index: 0,
payerPubkey: params.payerPubkey,
authorPubkey: params.authorPubkey,
amount: params.amount,
paymentHash: params.paymentHash,
createdAt: Math.floor(Date.now() / 1000),
...(params.seriesId ? { seriesId: params.seriesId } : {}),
...(params.articleId ? { articleId: params.articleId } : {}),
...(params.text ? { text: params.text } : {}),
kindType: 'sponsoring',
}
const content = params.text const content = params.text
? `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...\n\n${params.text}` ? `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...\n\n${params.text}`
: `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...` : `Sponsoring confirmed: ${params.amount} sats for author ${params.authorPubkey.substring(0, 16)}...`
@ -249,5 +314,35 @@ export async function publishSponsoringNote(params: {
} }
nostrService.setPrivateKey(params.payerPrivateKey) nostrService.setPrivateKey(params.payerPrivateKey)
return nostrService.publishEvent(eventTemplate) writeOrchestrator.setPrivateKey(params.payerPrivateKey)
// Finalize event
const secretKey = hexToBytes(params.payerPrivateKey)
const event = finalizeEvent(eventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'sponsoring',
hash: hashId,
event,
parsed: parsedSponsoring,
version: 0,
hidden: false,
index: 0,
},
relays
)
if (!result.success) {
return null
}
return event
} }

View File

@ -141,8 +141,9 @@ export class PlatformTrackingService {
}) })
// Listen for EOSE via Service Worker messages // Listen for EOSE via Service Worker messages
const handleEOSE = (data: { relays: string[] }): void => { const handleEOSE = (data: unknown): void => {
if (data.relays.includes(relayUrl) && !eoseReceived) { const eoseData = data as { relays: string[] }
if (eoseData.relays.includes(relayUrl) && !eoseReceived) {
eoseReceived = true eoseReceived = true
finalize() finalize()
} }
@ -214,8 +215,9 @@ export class PlatformTrackingService {
}) })
// Listen for EOSE via Service Worker messages // Listen for EOSE via Service Worker messages
const handleEOSE = (data: { relays: string[] }): void => { const handleEOSE = (data: unknown): void => {
if (data.relays.includes(relayUrl) && !eoseReceived) { const eoseData = data as { relays: string[] }
if (eoseData.relays.includes(relayUrl) && !eoseReceived) {
eoseReceived = true eoseReceived = true
finalize() finalize()
} }

View File

@ -1,44 +1,21 @@
import { nostrService } from './nostr' import { nostrService } from './nostr'
import { PLATFORM_COMMISSIONS } from './platformCommissions' import { PLATFORM_COMMISSIONS } from './platformCommissions'
import type { Event } from 'nostr-tools' import type { Event } from 'nostr-tools'
import { objectCache } from './objectCache'
export async function fetchOriginalReviewEvent(reviewId: string): Promise<Event | null> { export async function fetchOriginalReviewEvent(reviewId: string): Promise<Event | null> {
const pool = nostrService.getPool() // Read only from IndexedDB cache
if (!pool) { const parsed = await objectCache.getById('review', reviewId)
throw new Error('Pool not initialized') if (parsed) {
// Get the event from cache
const event = await objectCache.getEventById('review', reviewId)
if (event) {
return event
}
} }
const { getPrimaryRelaySync } = await import('./config') // Not found in cache - return null (no network request)
const { createSubscription } = await import('@/types/nostr-tools-extended') return null
const relayUrl = getPrimaryRelaySync()
const filters = [
{
kinds: [1],
ids: [reviewId],
limit: 1,
},
]
return new Promise<Event | null>((resolve) => {
let resolved = false
const sub = createSubscription(pool, [relayUrl], filters)
const finalize = (value: Event | null): void => {
if (resolved) {
return
}
resolved = true
sub.unsub()
resolve(value)
}
sub.on('event', (event: Event) => {
finalize(event)
})
sub.on('eose', () => finalize(null))
setTimeout(() => finalize(null), 5000)
})
} }
export function buildRewardEvent(originalEvent: Event, reviewId: string): { export function buildRewardEvent(originalEvent: Event, reviewId: string): {
@ -72,7 +49,7 @@ export function checkIfAlreadyRewarded(originalEvent: Event, reviewId: string):
} }
export async function publishRewardEvent( export async function publishRewardEvent(
updatedEvent: { updatedEventTemplate: {
kind: number kind: number
created_at: number created_at: number
tags: string[][] tags: string[][]
@ -80,16 +57,82 @@ export async function publishRewardEvent(
}, },
reviewId: string reviewId: string
): Promise<void> { ): Promise<void> {
const publishedEvent = await nostrService.publishEvent(updatedEvent) try {
if (publishedEvent) { // Get original review to extract hash and parsed data
console.warn('Review updated with reward tag', { const originalEvent = await fetchOriginalReviewEvent(reviewId)
reviewId, if (!originalEvent) {
updatedEventId: publishedEvent.id, throw new Error('Original review event not found in cache')
timestamp: new Date().toISOString(), }
})
} else { const { parseReviewFromEvent } = await import('./nostrEventParsing')
console.error('Failed to publish updated review event', { const originalParsed = await parseReviewFromEvent(originalEvent)
if (!originalParsed) {
throw new Error('Failed to parse original review')
}
// Increment version for update
const newVersion = (originalParsed.version ?? 0) + 1
const {hash} = originalParsed
const index = originalParsed.index ?? 0
// Build updated parsed Review object
const updatedParsed = {
...originalParsed,
version: newVersion,
rewarded: true,
rewardAmount: PLATFORM_COMMISSIONS.review.total,
}
// Set private key in orchestrator
const privateKey = nostrService.getPrivateKey()
if (!privateKey) {
throw new Error('Private key required for signing')
}
const { writeOrchestrator } = await import('./writeOrchestrator')
writeOrchestrator.setPrivateKey(privateKey)
// Finalize event
const { finalizeEvent } = await import('nostr-tools')
const { hexToBytes } = await import('nostr-tools/utils')
const secretKey = hexToBytes(privateKey)
const event = finalizeEvent(updatedEventTemplate, secretKey)
// Get active relays
const { relaySessionManager } = await import('./relaySessionManager')
const activeRelays = await relaySessionManager.getActiveRelays()
const { getPrimaryRelay } = await import('./config')
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
// Publish via writeOrchestrator (parallel network + local write)
const result = await writeOrchestrator.writeAndPublish(
{
objectType: 'review',
hash,
event,
parsed: updatedParsed,
version: newVersion,
hidden: false,
index,
},
relays
)
if (result.success) {
console.warn('Review updated with reward tag', {
reviewId,
updatedEventId: event.id,
timestamp: new Date().toISOString(),
})
} else {
console.error('Failed to publish updated review event', {
reviewId,
timestamp: new Date().toISOString(),
})
}
} catch (error) {
console.error('Error publishing reward event', {
reviewId, reviewId,
error: error instanceof Error ? error.message : 'Unknown error',
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}) })
} }

View File

@ -8,7 +8,7 @@
import { SimplePool } from 'nostr-tools' import { SimplePool } from 'nostr-tools'
import { swClient } from './swClient' import { swClient } from './swClient'
import type { Event } from 'nostr-tools' import type { Event, Filter } from 'nostr-tools'
interface ConnectionState { interface ConnectionState {
relayUrl: string relayUrl: string
@ -220,8 +220,9 @@ class WebSocketService {
this.updateConnectionState(relayUrl, true) // Assume connected when subscribing this.updateConnectionState(relayUrl, true) // Assume connected when subscribing
}) })
// Create subscription // Create subscription - use first filter or empty filter
const sub = this.pool.subscribe(relays, filters, { const filter: Filter = (filters[0] as Filter) ?? {}
const sub = this.pool.subscribe(relays, filter, {
onevent: (event: Event): void => { onevent: (event: Event): void => {
// Notify Service Worker of new event via postMessage // Notify Service Worker of new event via postMessage
void swClient.sendMessage({ void swClient.sendMessage({
@ -261,7 +262,7 @@ class WebSocketService {
} }
// Update all connection states // Update all connection states
this.connectionStates.forEach((state, relayUrl) => { this.connectionStates.forEach((_state, relayUrl) => {
this.updateConnectionState(relayUrl, false) this.updateConnectionState(relayUrl, false)
}) })

View File

@ -42,7 +42,7 @@ class WriteOrchestrator {
params: WriteObjectParams, params: WriteObjectParams,
relays: string[] relays: string[]
): Promise<{ success: boolean; eventId: string; published: false | string[] }> { ): Promise<{ success: boolean; eventId: string; published: false | string[] }> {
const { objectType, hash, event, parsed, version, hidden, index, published = false } = params const { objectType, hash, event, parsed, version, hidden, index } = params
// Écriture en parallèle : réseau et local indépendamment // Écriture en parallèle : réseau et local indépendamment
const [networkResult, localResult] = await Promise.allSettled([ const [networkResult, localResult] = await Promise.allSettled([
@ -117,7 +117,7 @@ class WriteOrchestrator {
parsed, parsed,
version, version,
hidden, hidden,
index, ...(index !== undefined ? { index } : {}),
}, },
relays relays
) )

View File

@ -34,7 +34,7 @@ class WriteService {
} }
private createWorker(): Promise<void> { private createWorker(): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, _reject) => {
if (typeof window === 'undefined' || !window.Worker) { if (typeof window === 'undefined' || !window.Worker) {
// Fallback: write directly if Worker not available // Fallback: write directly if Worker not available
console.warn('[WriteService] Web Workers not available, using direct writes') console.warn('[WriteService] Web Workers not available, using direct writes')
@ -240,8 +240,8 @@ class WriteService {
} }
// Fallback: direct write // Fallback: direct write
const { notificationService } = await import('./notificationService') const { notificationService } = await import('./notificationService')
await notificationService.createNotificationDirect( await notificationService.createNotification({
type as Parameters<typeof notificationService.createNotificationDirect>[0], type: type as Parameters<typeof notificationService.createNotification>[0]['type'],
objectType, objectType,
objectId, objectId,
eventId, eventId,

View File

@ -297,7 +297,7 @@ export default function AuthorPage(): React.ReactElement {
const { presentation, series, totalSponsoring, loading, error, reload } = useAuthorData(hashIdOrPubkey ?? '') const { presentation, series, totalSponsoring, loading, error, reload } = useAuthorData(hashIdOrPubkey ?? '')
if (!hashIdOrPubkey) { if (!hashIdOrPubkey) {
return null return <div />
} }
// Get the actual pubkey from presentation // Get the actual pubkey from presentation