200 lines
6.4 KiB
TypeScript
200 lines
6.4 KiB
TypeScript
/**
|
|
* Publication log service - stores publication attempts and results in IndexedDB
|
|
*/
|
|
|
|
import { createIndexedDBHelper, type IndexedDBHelper } from './helpers/indexedDBHelper'
|
|
|
|
const DB_NAME = 'nostr_publish_log'
|
|
const DB_VERSION = 1
|
|
const STORE_NAME = 'publications'
|
|
|
|
interface PublicationLogEntry {
|
|
id: string // Event ID
|
|
eventId: string // Event ID (duplicate for easier querying)
|
|
relayUrl: string
|
|
success: boolean
|
|
error?: string
|
|
timestamp: number
|
|
objectType?: string // Type of object being published (author, series, publication, etc.)
|
|
objectId?: string // ID of the object in cache
|
|
}
|
|
|
|
interface LogPublicationParams {
|
|
eventId: string
|
|
relayUrl: string
|
|
success: boolean
|
|
error?: string
|
|
objectType?: string
|
|
objectId?: string
|
|
}
|
|
|
|
class PublishLogService {
|
|
private readonly dbHelper: IndexedDBHelper
|
|
|
|
constructor() {
|
|
this.dbHelper = createIndexedDBHelper({
|
|
dbName: DB_NAME,
|
|
version: DB_VERSION,
|
|
storeName: STORE_NAME,
|
|
keyPath: 'id',
|
|
indexes: [
|
|
{ name: 'eventId', keyPath: 'eventId', unique: false },
|
|
{ name: 'relayUrl', keyPath: 'relayUrl', unique: false },
|
|
{ name: 'timestamp', keyPath: 'timestamp', unique: false },
|
|
{ name: 'success', keyPath: 'success', unique: false },
|
|
],
|
|
onUpgrade: (db: IDBDatabase): void => {
|
|
// Note: autoIncrement is handled in the store creation, but IndexedDBHelper doesn't support it directly
|
|
// We need to handle this in the upgrade handler
|
|
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
|
const store = db.createObjectStore(STORE_NAME, { keyPath: 'id', autoIncrement: true })
|
|
store.createIndex('eventId', 'eventId', { unique: false })
|
|
store.createIndex('relayUrl', 'relayUrl', { unique: false })
|
|
store.createIndex('timestamp', 'timestamp', { unique: false })
|
|
store.createIndex('success', 'success', { unique: false })
|
|
}
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Log a publication attempt
|
|
* Utilise writeService pour écrire via Web Worker
|
|
*/
|
|
async logPublication(params: LogPublicationParams): Promise<void> {
|
|
// Utiliser writeService pour logger via Web Worker
|
|
const { writeService } = await import('./writeService')
|
|
await writeService.logPublication(params)
|
|
}
|
|
|
|
/**
|
|
* Log a publication attempt (ancienne méthode, conservée pour fallback dans writeService)
|
|
* @deprecated Utiliser logPublication qui utilise writeService
|
|
* @internal Utilisé uniquement par writeService en fallback
|
|
*/
|
|
async logPublicationDirect(params: LogPublicationParams): Promise<void> {
|
|
try {
|
|
const entry: PublicationLogEntry = {
|
|
id: `${params.eventId}_${params.relayUrl}_${Date.now()}`, // Unique ID
|
|
eventId: params.eventId,
|
|
relayUrl: params.relayUrl,
|
|
success: params.success,
|
|
...(params.error !== undefined ? { error: params.error } : {}),
|
|
timestamp: Date.now(),
|
|
...(params.objectType !== undefined ? { objectType: params.objectType } : {}),
|
|
...(params.objectId !== undefined ? { objectId: params.objectId } : {}),
|
|
}
|
|
|
|
await this.dbHelper.add(entry)
|
|
} catch (logError) {
|
|
console.error('[PublishLog] Error logging publication:', logError)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get publication logs for an event
|
|
*/
|
|
async getLogsForEvent(eventId: string): Promise<PublicationLogEntry[]> {
|
|
try {
|
|
return await this.dbHelper.getAllByIndex<PublicationLogEntry>('eventId', eventId)
|
|
} catch (error) {
|
|
console.error('[PublishLog] Error getting logs for event:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get publication logs for a relay
|
|
*/
|
|
async getLogsForRelay(relayUrl: string, limit: number = 100): Promise<PublicationLogEntry[]> {
|
|
try {
|
|
const entries: PublicationLogEntry[] = []
|
|
const store = await this.dbHelper.getStore('readonly')
|
|
const index = store.index('relayUrl')
|
|
|
|
return new Promise<PublicationLogEntry[]>((resolve, reject) => {
|
|
const request = index.openCursor(IDBKeyRange.only(relayUrl))
|
|
|
|
request.onsuccess = (event: globalThis.Event): void => {
|
|
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
|
|
if (cursor) {
|
|
entries.push(cursor.value as PublicationLogEntry)
|
|
if (entries.length < limit) {
|
|
cursor.continue()
|
|
} else {
|
|
resolve(entries.sort((a, b) => b.timestamp - a.timestamp))
|
|
}
|
|
} else {
|
|
resolve(entries.sort((a, b) => b.timestamp - a.timestamp))
|
|
}
|
|
}
|
|
|
|
request.onerror = (): void => {
|
|
if (request.error) {
|
|
reject(request.error)
|
|
} else {
|
|
reject(new Error('Unknown error opening cursor'))
|
|
}
|
|
}
|
|
})
|
|
} catch (error) {
|
|
console.error('[PublishLog] Error getting logs for relay:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get all publication logs (successful and failed)
|
|
*/
|
|
async getAllLogs(limit: number = 1000): Promise<PublicationLogEntry[]> {
|
|
try {
|
|
const entries: PublicationLogEntry[] = []
|
|
const store = await this.dbHelper.getStore('readonly')
|
|
const index = store.index('timestamp')
|
|
|
|
return new Promise<PublicationLogEntry[]>((resolve, reject) => {
|
|
const request = index.openCursor(null, 'prev') // Descending order
|
|
|
|
request.onsuccess = (event: globalThis.Event): void => {
|
|
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result
|
|
if (cursor) {
|
|
entries.push(cursor.value as PublicationLogEntry)
|
|
if (entries.length < limit) {
|
|
cursor.continue()
|
|
} else {
|
|
resolve(entries)
|
|
}
|
|
} else {
|
|
resolve(entries)
|
|
}
|
|
}
|
|
|
|
request.onerror = (): void => {
|
|
if (request.error) {
|
|
reject(request.error)
|
|
} else {
|
|
reject(new Error('Unknown error opening cursor'))
|
|
}
|
|
}
|
|
})
|
|
} catch (error) {
|
|
console.error('[PublishLog] Error getting all logs:', error)
|
|
return []
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get statistics for a relay
|
|
*/
|
|
async getRelayStats(relayUrl: string): Promise<{ total: number; success: number; failed: number }> {
|
|
const logs = await this.getLogsForRelay(relayUrl, 10000)
|
|
return {
|
|
total: logs.length,
|
|
success: logs.filter((log) => log.success).length,
|
|
failed: logs.filter((log) => !log.success).length,
|
|
}
|
|
}
|
|
}
|
|
|
|
export const publishLog = new PublishLogService()
|