From dace103da8d67d96e27f1d8c93c2c821233bb653 Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Wed, 7 Jan 2026 03:45:01 +0100 Subject: [PATCH] lint fix wip --- components/SyncProgressBar.tsx | 77 +++------------ lib/articlePublisherHelpersPresentation.ts | 12 ++- lib/helpers/eventCacheHelper.ts | 25 +++-- lib/helpers/syncCacheHelpers.ts | 59 +++++++----- lib/helpers/writeObjectHelper.ts | 91 ++++++++++++++++++ lib/hooks/useSyncProgress.ts | 107 +++++++++++++++++++++ lib/nostr.ts | 6 +- lib/platformSync.ts | 90 ++++++++++++----- public/writeWorker.js | 15 +-- 9 files changed, 354 insertions(+), 128 deletions(-) create mode 100644 lib/helpers/writeObjectHelper.ts create mode 100644 lib/hooks/useSyncProgress.ts diff --git a/components/SyncProgressBar.tsx b/components/SyncProgressBar.tsx index 8f19a23..190c1c2 100644 --- a/components/SyncProgressBar.tsx +++ b/components/SyncProgressBar.tsx @@ -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(null) - const [isSyncing, setIsSyncing] = useState(false) const [lastSyncDate, setLastSyncDate] = useState(null) const [totalDays, setTotalDays] = useState(0) const [isInitialized, setIsInitialized] = useState(false) const [connectionState, setConnectionState] = useState<{ connected: boolean; pubkey: string | null }>({ connected: false, pubkey: null }) const [error, setError] = useState(null) + const { syncProgress, isSyncing, startMonitoring, stopMonitoring } = useSyncProgress({ + onComplete: async () => { + await loadSyncStatus() + }, + }) + async function loadSyncStatus(): Promise { 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() } } diff --git a/lib/articlePublisherHelpersPresentation.ts b/lib/articlePublisherHelpersPresentation.ts index 3aa37d3..2ff0eb0 100644 --- a/lib/articlePublisherHelpersPresentation.ts +++ b/lib/articlePublisherHelpersPresentation.ts @@ -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, + }) } } } diff --git a/lib/helpers/eventCacheHelper.ts b/lib/helpers/eventCacheHelper.ts index b794be3..7e51bc6 100644 --- a/lib/helpers/eventCacheHelper.ts +++ b/lib/helpers/eventCacheHelper.ts @@ -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 } diff --git a/lib/helpers/syncCacheHelpers.ts b/lib/helpers/syncCacheHelpers.ts index f4d1042..94f99df 100644 --- a/lib/helpers/syncCacheHelpers.ts +++ b/lib/helpers/syncCacheHelpers.ts @@ -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 { - 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 { - 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 { - 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 { - 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, + }, + }) } } } diff --git a/lib/helpers/writeObjectHelper.ts b/lib/helpers/writeObjectHelper.ts new file mode 100644 index 0000000..fea3a37 --- /dev/null +++ b/lib/helpers/writeObjectHelper.ts @@ -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 { + 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 { + 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( + event: Event, + objectType: ObjectType, + extractor: (event: Event) => Promise +): Promise { + 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 +} diff --git a/lib/hooks/useSyncProgress.ts b/lib/hooks/useSyncProgress.ts new file mode 100644 index 0000000..000df83 --- /dev/null +++ b/lib/hooks/useSyncProgress.ts @@ -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 + 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(null) + const [isSyncing, setIsSyncing] = useState(false) + const intervalRef = useRef(null) + const timeoutRef = useRef(null) + const onCompleteRef = useRef(onComplete) + const isMonitoringRef = useRef(false) + + // Update onComplete ref when it changes + useEffect(() => { + onCompleteRef.current = onComplete + }, [onComplete]) + + const checkProgress = async (): Promise => { + 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, + } +} diff --git a/lib/nostr.ts b/lib/nostr.ts index e5b7500..396f1ec 100644 --- a/lib/nostr.ts +++ b/lib/nostr.ts @@ -466,12 +466,15 @@ class NostrService { const objectTypes: Array = ['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 } diff --git a/lib/platformSync.ts b/lib/platformSync.ts index efd75eb..cd3078f 100644 --- a/lib/platformSync.ts +++ b/lib/platformSync.ts @@ -256,6 +256,7 @@ class PlatformSyncService { */ private async processEvent(event: Event): Promise { 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, + }) } } } diff --git a/public/writeWorker.js b/public/writeWorker.js index 51f3220..806dcac 100644 --- a/public/writeWorker.js +++ b/public/writeWorker.js @@ -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 })