lint fix wip

This commit is contained in:
Nicolas Cantu 2026-01-07 03:45:01 +01:00
parent 17e4b10b1f
commit dace103da8
9 changed files with 354 additions and 128 deletions

View File

@ -1,22 +1,26 @@
import { useState, useEffect } from 'react'
import { nostrAuthService } from '@/lib/nostrAuth'
import type { SyncProgress } from '@/lib/userContentSync'
import { getLastSyncDate, setLastSyncDate as setLastSyncDateStorage, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
import { objectCache } from '@/lib/objectCache'
import { t } from '@/lib/i18n'
import { useSyncProgress } from '@/lib/hooks/useSyncProgress'
export function SyncProgressBar(): React.ReactElement | null {
console.warn('[SyncProgressBar] Component function called')
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
const [isSyncing, setIsSyncing] = useState(false)
const [lastSyncDate, setLastSyncDate] = useState<number | null>(null)
const [totalDays, setTotalDays] = useState<number>(0)
const [isInitialized, setIsInitialized] = useState(false)
const [connectionState, setConnectionState] = useState<{ connected: boolean; pubkey: string | null }>({ connected: false, pubkey: null })
const [error, setError] = useState<string | null>(null)
const { syncProgress, isSyncing, startMonitoring, stopMonitoring } = useSyncProgress({
onComplete: async () => {
await loadSyncStatus()
},
})
async function loadSyncStatus(): Promise<void> {
try {
const state = nostrAuthService.getState()
@ -90,46 +94,19 @@ export function SyncProgressBar(): React.ReactElement | null {
// Only auto-start if not recently synced
if (!isRecentlySynced && !isSyncing && connectionState.pubkey) {
console.warn('[SyncProgressBar] Starting auto-sync...')
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
try {
const { swClient } = await import('@/lib/swClient')
const isReady = await swClient.isReady()
if (isReady) {
await swClient.startUserSync(connectionState.pubkey)
// Progress is tracked via syncProgressManager
// Listen to syncProgressManager for updates
const { syncProgressManager } = await import('@/lib/syncProgressManager')
const checkProgress = (): void => {
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
setSyncProgress(currentProgress)
if (currentProgress.completed) {
setIsSyncing(false)
void loadSyncStatus()
}
}
}
// Check progress periodically
const progressInterval = setInterval(() => {
checkProgress()
const currentProgress = syncProgressManager.getProgress()
if (currentProgress?.completed) {
clearInterval(progressInterval)
}
}, 500)
// Cleanup after 60 seconds max
setTimeout(() => {
clearInterval(progressInterval)
setIsSyncing(false)
}, 60000)
startMonitoring()
} else {
setIsSyncing(false)
stopMonitoring()
}
} catch (autoSyncError) {
console.error('[SyncProgressBar] Error during auto-sync:', autoSyncError)
setIsSyncing(false)
stopMonitoring()
setError(autoSyncError instanceof Error ? autoSyncError.message : 'Erreur de synchronisation')
}
} else {
@ -145,9 +122,6 @@ export function SyncProgressBar(): React.ReactElement | null {
return
}
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
// Clear cache for user content (but keep other data)
await Promise.all([
objectCache.clear('author'),
@ -171,39 +145,14 @@ export function SyncProgressBar(): React.ReactElement | null {
const isReady = await swClient.isReady()
if (isReady) {
await swClient.startUserSync(state.pubkey)
// Progress is tracked via syncProgressManager
// Listen to syncProgressManager for updates
const { syncProgressManager } = await import('@/lib/syncProgressManager')
const checkProgress = (): void => {
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
setSyncProgress(currentProgress)
if (currentProgress.completed) {
setIsSyncing(false)
void loadSyncStatus()
}
}
}
// Check progress periodically
const progressInterval = setInterval(() => {
checkProgress()
const currentProgress = syncProgressManager.getProgress()
if (currentProgress?.completed) {
clearInterval(progressInterval)
}
}, 500)
// Cleanup after 60 seconds max
setTimeout(() => {
clearInterval(progressInterval)
setIsSyncing(false)
}, 60000)
startMonitoring()
} else {
setIsSyncing(false)
stopMonitoring()
}
}
} catch (resyncError) {
console.error('Error resynchronizing:', resyncError)
setIsSyncing(false)
stopMonitoring()
}
}

View File

@ -289,8 +289,16 @@ export async function fetchAuthorPresentationFromPool(
// Calculate totalSponsoring from cache before storing
const { getAuthorSponsoring } = await import('./sponsoring')
value.totalSponsoring = await getAuthorSponsoring(value.pubkey)
const { writeService } = await import('./writeService')
await writeService.writeObject('author', value.hash, event, value, tags.version ?? 0, tags.hidden, value.index, false)
const { writeObjectToCache } = await import('./helpers/writeObjectHelper')
await writeObjectToCache({
objectType: 'author',
hash: value.hash,
event,
parsed: value,
version: tags.version,
hidden: tags.hidden,
index: value.index,
})
}
}
}

View File

@ -8,6 +8,7 @@ import { extractTagsFromEvent } from '../nostrTagSystem'
import { parseObjectId } from '../urlGenerator'
import { getLatestVersion } from '../versionManager'
import type { ObjectType } from '../objectCache'
import { writeObjectToCache } from './writeObjectHelper'
export interface EventCacheConfig {
objectType: ObjectType
@ -75,8 +76,15 @@ export async function groupAndCacheEventsByHash(
const version = getVersion ? getVersion(latestEvent) : extractTagsFromEvent(latestEvent).version ?? 0
const hidden = getHidden ? getHidden(latestEvent) : extractTagsFromEvent(latestEvent).hidden ?? false
const { writeService } = await import('../writeService')
await writeService.writeObject(objectType, hash, latestEvent, extracted, version, hidden, index, false)
await writeObjectToCache({
objectType,
hash,
event: latestEvent,
parsed: extracted,
version,
hidden,
index,
})
}
}
@ -101,11 +109,16 @@ export async function cacheEventAsObject(
}
const index = (extracted as { index?: number })?.index ?? 0
const version = tags.version ?? 0
const hidden = tags.hidden ?? false
const { writeService } = await import('../writeService')
await writeService.writeObject(objectType, hash, event, extracted, version, hidden, index, false)
await writeObjectToCache({
objectType,
hash,
event,
parsed: extracted,
version: tags.version,
hidden: tags.hidden,
index,
})
return true
}

View File

@ -6,68 +6,79 @@
import type { Event } from 'nostr-tools'
import { extractTagsFromEvent } from '../nostrTagSystem'
import { extractPurchaseFromEvent, extractSponsoringFromEvent, extractReviewTipFromEvent } from '../metadataExtractor'
import { writeObjectToCache } from './writeObjectHelper'
export async function cachePurchases(events: Event[]): Promise<void> {
const { writeService } = await import('../writeService')
const { parsePurchaseFromEvent } = await import('../nostrEventParsing')
for (const event of events) {
const extracted = await extractPurchaseFromEvent(event)
if (extracted) {
const purchase = await parsePurchaseFromEvent(event)
if (purchase) {
const purchaseTyped = purchase
if (purchaseTyped.hash) {
await writeService.writeObject('purchase', purchaseTyped.hash, event, purchaseTyped, 0, false, purchaseTyped.index ?? 0, false)
}
if (purchase?.hash) {
await writeObjectToCache({
objectType: 'purchase',
hash: purchase.hash,
event,
parsed: purchase,
index: purchase.index,
})
}
}
}
}
export async function cacheSponsoring(events: Event[]): Promise<void> {
const { writeService } = await import('../writeService')
const { parseSponsoringFromEvent } = await import('../nostrEventParsing')
for (const event of events) {
const extracted = await extractSponsoringFromEvent(event)
if (extracted) {
const sponsoring = await parseSponsoringFromEvent(event)
if (sponsoring) {
const sponsoringTyped = sponsoring
if (sponsoringTyped.hash) {
await writeService.writeObject('sponsoring', sponsoringTyped.hash, event, sponsoringTyped, 0, false, sponsoringTyped.index ?? 0, false)
}
if (sponsoring?.hash) {
await writeObjectToCache({
objectType: 'sponsoring',
hash: sponsoring.hash,
event,
parsed: sponsoring,
index: sponsoring.index,
})
}
}
}
}
export async function cacheReviewTips(events: Event[]): Promise<void> {
const { writeService } = await import('../writeService')
const { parseReviewTipFromEvent } = await import('../nostrEventParsing')
for (const event of events) {
const extracted = await extractReviewTipFromEvent(event)
if (extracted) {
const reviewTip = await parseReviewTipFromEvent(event)
if (reviewTip) {
const reviewTipTyped = reviewTip
if (reviewTipTyped.hash) {
await writeService.writeObject('review_tip', reviewTipTyped.hash, event, reviewTipTyped, 0, false, reviewTipTyped.index ?? 0, false)
}
if (reviewTip?.hash) {
await writeObjectToCache({
objectType: 'review_tip',
hash: reviewTip.hash,
event,
parsed: reviewTip,
index: reviewTip.index,
})
}
}
}
}
export async function cachePaymentNotes(events: Event[]): Promise<void> {
const { writeService } = await import('../writeService')
for (const event of events) {
const tags = extractTagsFromEvent(event)
if (tags.type === 'payment' && tags.payment) {
await writeService.writeObject('payment_note', event.id, event, {
id: event.id,
type: 'payment_note',
eventId: event.id,
}, 0, false, 0, false)
await writeObjectToCache({
objectType: 'payment_note',
hash: event.id,
event,
parsed: {
id: event.id,
type: 'payment_note',
eventId: event.id,
},
})
}
}
}

View File

@ -0,0 +1,91 @@
/**
* Helper for writing objects to IndexedDB after extraction
* Centralizes the pattern of extract + writeObject with default parameters
* Optimizes imports by loading writeService once
*/
import type { Event } from 'nostr-tools'
import type { ObjectType } from '../objectCache'
import { extractTagsFromEvent } from '../nostrTagSystem'
let writeServiceCache: typeof import('../writeService').writeService | null = null
async function getWriteService(): Promise<typeof import('../writeService').writeService> {
if (!writeServiceCache) {
const { writeService } = await import('../writeService')
writeServiceCache = writeService
}
return writeServiceCache
}
export interface WriteObjectParams {
objectType: ObjectType
hash: string
event: Event
parsed: unknown
version?: number
hidden?: boolean
index?: number
published?: false | string[]
}
/**
* Write an object to IndexedDB cache
* Uses default values for version, hidden, index, and published if not provided
*/
export async function writeObjectToCache(params: WriteObjectParams): Promise<void> {
const {
objectType,
hash,
event,
parsed,
version,
hidden,
index,
published,
} = params
const tags = extractTagsFromEvent(event)
const writeService = await getWriteService()
await writeService.writeObject(
objectType,
hash,
event,
parsed,
version ?? tags.version ?? 0,
hidden ?? tags.hidden ?? false,
index ?? 0,
published ?? false
)
}
/**
* Extract and write an object from an event
* Combines extraction and writing in a single call
*/
export async function extractAndWriteObject<T extends { hash?: string; index?: number }>(
event: Event,
objectType: ObjectType,
extractor: (event: Event) => Promise<T | null>
): Promise<boolean> {
const extracted = await extractor(event)
if (!extracted) {
return false
}
const {hash} = extracted
if (!hash) {
return false
}
await writeObjectToCache({
objectType,
hash,
event,
parsed: extracted,
index: extracted.index,
})
return true
}

View File

@ -0,0 +1,107 @@
/**
* Custom hook for monitoring sync progress
* Centralizes the pattern of polling syncProgressManager and updating state
*/
import { useState, useEffect, useRef } from 'react'
import type { SyncProgress } from '../helpers/syncProgressHelper'
export interface UseSyncProgressOptions {
onComplete?: () => void | Promise<void>
pollInterval?: number
maxDuration?: number
}
export interface UseSyncProgressResult {
syncProgress: SyncProgress | null
isSyncing: boolean
startMonitoring: () => void
stopMonitoring: () => void
}
/**
* Hook to monitor sync progress from syncProgressManager
*/
export function useSyncProgress(options: UseSyncProgressOptions = {}): UseSyncProgressResult {
const { onComplete, pollInterval = 500, maxDuration = 60000 } = options
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
const [isSyncing, setIsSyncing] = useState(false)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const timeoutRef = useRef<NodeJS.Timeout | null>(null)
const onCompleteRef = useRef(onComplete)
const isMonitoringRef = useRef(false)
// Update onComplete ref when it changes
useEffect(() => {
onCompleteRef.current = onComplete
}, [onComplete])
const checkProgress = async (): Promise<void> => {
const { syncProgressManager } = await import('../syncProgressManager')
const currentProgress = syncProgressManager.getProgress()
if (currentProgress) {
setSyncProgress(currentProgress)
if (currentProgress.completed) {
setIsSyncing(false)
if (onCompleteRef.current) {
await onCompleteRef.current()
}
stopMonitoring()
}
}
}
const startMonitoring = (): void => {
if (isMonitoringRef.current) {
return
}
isMonitoringRef.current = true
setIsSyncing(true)
setSyncProgress({ currentStep: 0, totalSteps: 7, completed: false })
// Poll progress periodically
intervalRef.current = setInterval(() => {
void checkProgress()
}, pollInterval)
// Cleanup after max duration
timeoutRef.current = setTimeout(() => {
stopMonitoring()
}, maxDuration)
}
const stopMonitoring = (): void => {
if (!isMonitoringRef.current) {
return
}
isMonitoringRef.current = false
setIsSyncing(false)
if (intervalRef.current) {
clearInterval(intervalRef.current)
intervalRef.current = null
}
if (timeoutRef.current) {
clearTimeout(timeoutRef.current)
timeoutRef.current = null
}
}
// Cleanup on unmount
useEffect(() => {
return () => {
stopMonitoring()
}
}, [])
return {
syncProgress,
isSyncing,
startMonitoring,
stopMonitoring,
}
}

View File

@ -466,12 +466,15 @@ class NostrService {
const objectTypes: Array<import('./objectCache').ObjectType> = ['author', 'series', 'publication', 'review', 'purchase', 'sponsoring', 'review_tip', 'payment_note']
// First try to find in unpublished objects (faster)
for (const objectType of objectTypes) {
// Load writeService once
const { writeService } = await import('./writeService')
for (const objectType of objectTypes) {
try {
const unpublished = await objectCache.getUnpublished(objectType)
const matching = unpublished.find((obj) => obj.event.id === eventId)
if (matching) {
const { writeService } = await import('./writeService')
await writeService.updatePublished(objectType, matching.id, published)
return
}
@ -510,7 +513,6 @@ class NostrService {
})
if (found) {
const { writeService } = await import('./writeService')
await writeService.updatePublished(objectType, found, published)
return
}

View File

@ -256,6 +256,7 @@ class PlatformSyncService {
*/
private async processEvent(event: Event): Promise<void> {
const tags = extractTagsFromEvent(event)
const { writeObjectToCache } = await import('./helpers/writeObjectHelper')
// Log target event for debugging
if (event.id === '527d83e0af20bf23c3e104974090ccc21536ece72c24eb784b3642890f63b763') {
@ -289,49 +290,92 @@ class PlatformSyncService {
hash: parsed?.hash,
})
}
if (parsed && parsed.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('author', parsed.hash, event, parsed, tags.version ?? 0, tags.hidden, parsed.index, false)
if (parsed?.hash) {
await writeObjectToCache({
objectType: 'author',
hash: parsed.hash,
event,
parsed,
version: tags.version,
hidden: tags.hidden,
index: parsed.index,
})
if (event.id === '527d83e0af20bf23c3e104974090ccc21536ece72c24eb784b3642890f63b763') {
console.warn(`[PlatformSync] Target event cached successfully as author with hash:`, parsed.hash)
}
} else if (event.id === '527d83e0af20bf23c3e104974090ccc21536ece72c24eb784b3642890f63b763') {
console.warn(`[PlatformSync] Target event NOT cached: parsed=${parsed !== null}, hasHash=${parsed?.hash !== undefined}`)
}
console.warn(`[PlatformSync] Target event NOT cached: parsed=${parsed !== null}, hasHash=${parsed?.hash !== undefined}`)
}
} else if (tags.type === 'series') {
const parsed = await parseSeriesFromEvent(event)
if (parsed && parsed.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('series', parsed.hash, event, parsed, tags.version ?? 0, tags.hidden, parsed.index, false)
if (parsed?.hash) {
await writeObjectToCache({
objectType: 'series',
hash: parsed.hash,
event,
parsed,
version: tags.version,
hidden: tags.hidden,
index: parsed.index,
})
}
} else if (tags.type === 'publication') {
const parsed = await parseArticleFromEvent(event)
if (parsed && parsed.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('publication', parsed.hash, event, parsed, tags.version ?? 0, tags.hidden, parsed.index, false)
if (parsed?.hash) {
await writeObjectToCache({
objectType: 'publication',
hash: parsed.hash,
event,
parsed,
version: tags.version,
hidden: tags.hidden,
index: parsed.index,
})
}
} else if (tags.type === 'quote') {
const parsed = await parseReviewFromEvent(event)
if (parsed && parsed.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('review', parsed.hash, event, parsed, tags.version ?? 0, tags.hidden, parsed.index, false)
if (parsed?.hash) {
await writeObjectToCache({
objectType: 'review',
hash: parsed.hash,
event,
parsed,
version: tags.version,
hidden: tags.hidden,
index: parsed.index,
})
}
} else if (event.kind === 9735) {
// Zap receipts (kind 9735) can be sponsoring, purchase, or review_tip
const sponsoring = await parseSponsoringFromEvent(event)
if (sponsoring && sponsoring.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('sponsoring', sponsoring.hash, event, sponsoring, 0, false, sponsoring.index, false)
if (sponsoring?.hash) {
await writeObjectToCache({
objectType: 'sponsoring',
hash: sponsoring.hash,
event,
parsed: sponsoring,
index: sponsoring.index,
})
} else {
const purchase = await parsePurchaseFromEvent(event)
if (purchase && purchase.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('purchase', purchase.hash, event, purchase, 0, false, purchase.index, false)
if (purchase?.hash) {
await writeObjectToCache({
objectType: 'purchase',
hash: purchase.hash,
event,
parsed: purchase,
index: purchase.index,
})
} else {
const reviewTip = await parseReviewTipFromEvent(event)
if (reviewTip && reviewTip.hash) {
const { writeService } = await import('./writeService')
await writeService.writeObject('review_tip', reviewTip.hash, event, reviewTip, 0, false, reviewTip.index, false)
if (reviewTip?.hash) {
await writeObjectToCache({
objectType: 'review_tip',
hash: reviewTip.hash,
event,
parsed: reviewTip,
index: reviewTip.index,
})
}
}
}

View File

@ -84,6 +84,7 @@ async function executeWriteTask(task) {
// Listen for messages from main thread
self.addEventListener('message', (event) => {
// event is used to access event.data
const { type, data } = event.data
// Add to queue with unique ID
@ -120,7 +121,7 @@ async function handleWriteObject(data, taskId) {
const store = transaction.objectStore('objects')
// Vérifier si l'objet existe déjà pour préserver published
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch(() => null)
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch((_e) => null)
// Préserver published si existant et non fourni
const finalPublished = existing && published === false ? existing.published : (published ?? false)
@ -217,7 +218,7 @@ async function handleUpdatePublished(data, taskId) {
* Handle write multi-table request
* Transactions multi-tables : plusieurs transactions, logique de découpage côté worker
*/
async function handleWriteMultiTable(data, taskId) {
async function handleWriteMultiTable(data, _taskId) {
const { writes } = data // Array of { objectType, hash, event, parsed, version, hidden, index, published }
try {
@ -250,7 +251,7 @@ async function handleWriteMultiTable(data, taskId) {
finalId = `${hash}:${count}:${version}`
}
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch(() => null)
const existing = await executeTransactionOperation(store, (s) => s.get(finalId)).catch((_e) => null)
const finalPublished = existing && published === false ? existing.published : (published ?? false)
@ -281,7 +282,7 @@ async function handleWriteMultiTable(data, taskId) {
self.postMessage({
type: 'WRITE_MULTI_TABLE_SUCCESS',
data: { results, taskId },
data: { results },
})
} catch (error) {
throw new Error(`Failed to write multi-table: ${error.message}`)
@ -301,7 +302,7 @@ async function handleCreateNotification(data, taskId) {
// Vérifier si la notification existe déjà
const index = store.index('eventId')
const existing = await executeTransactionOperation(index, (idx) => idx.get(eventId)).catch(() => null)
const existing = await executeTransactionOperation(index, (idx) => idx.get(eventId)).catch((_e) => null)
if (existing) {
// Notification déjà existante
@ -343,7 +344,7 @@ async function handleCreateNotification(data, taskId) {
/**
* Handle log publication request
*/
async function handleLogPublication(data, taskId) {
async function handleLogPublication(data, _taskId) {
const { eventId, relayUrl, success, error, objectType, objectId } = data
try {
@ -417,7 +418,7 @@ function openDB(objectType) {
* Open IndexedDB for notifications
*/
function openNotificationDB() {
return openIndexedDB('nostr_notifications', 1, (db) => {
return openIndexedDB('nostr_notifications', 1, (db, _event) => {
if (!db.objectStoreNames.contains('notifications')) {
const store = db.createObjectStore('notifications', { keyPath: 'id' })
store.createIndex('type', 'type', { unique: false })