cyphertap
Version:
Nostr, Lightning and Ecash on a single Button component
198 lines (197 loc) • 8.74 kB
JavaScript
// src/lib/client/services/unpublishedEvents.ts
import { get } from 'svelte/store';
import { BROWSER as browser } from 'esm-env';
import { getNDK, relayConnectionStatus } from '../stores/nostr.js';
import { createDebug } from '../utils/debug.js';
// Create debug logger
const d = createDebug('unpublished-events');
// Configuration
const RETRY_INTERVAL = 60 * 1000; // 1 minute
const MAX_RETRIES_PER_SESSION = 10; // Limit retries to prevent excessive attempts
// State management
let isCheckingUnpublishedEvents = false;
let retryCount = 0;
let retryInterval = null;
// Add module-level variable to store the unsubscribe function
let unsubscribeRelayStatus = null;
/**
* Attempts to publish all unpublished events
* @returns Promise that resolves to the number of events attempted
*/
export async function publishUnpublishedEvents() {
// Prevent concurrent processing
if (isCheckingUnpublishedEvents || !browser) {
return 0;
}
const ndk = getNDK();
if (!ndk || !ndk.cacheAdapter || typeof ndk.cacheAdapter.getUnpublishedEvents !== 'function') {
d.warn('NDK or cache adapter not available, or getUnpublishedEvents method missing');
return 0;
}
// Check connection status
const connectionStatus = get(relayConnectionStatus);
const connectedRelays = connectionStatus.filter(relay => relay.connected);
if (connectedRelays.length === 0) {
d.log('No connected relays, skipping unpublished events check');
return 0;
}
try {
isCheckingUnpublishedEvents = true;
d.log('Checking for unpublished events...');
// Get unpublished events from cache
const unpublishedEvents = await ndk.cacheAdapter.getUnpublishedEvents();
if (!unpublishedEvents || unpublishedEvents.length === 0) {
d.log('No unpublished events found');
return 0;
}
d.log(`Found ${unpublishedEvents.length} unpublished events to process`);
// Track successfully published events
const successfullyPublished = [];
// Process each event
for (const { event, relays } of unpublishedEvents) {
try {
// Set the NDK instance for the event
event.ndk = ndk;
// Create a promise to track publishing
const publishPromise = new Promise((resolve) => {
// Set a timeout for this event's publication attempt
setTimeout(() => {
resolve(); // Resolve after timeout to continue processing
}, 5000); // 5 second timeout per event
});
// Filter to get only connected relays that match our target relays
const connectedRelayUrls = connectedRelays.map(r => r.url);
const targetRelayUrls = relays && relays.length > 0
? relays.filter(url => connectedRelayUrls.includes(url))
: connectedRelayUrls;
if (targetRelayUrls.length === 0) {
d.log(`No targeted relays available for event ${event.id?.slice(0, 8)}`);
continue;
}
d.log(`Attempting to publish event ${event.id?.slice(0, 8)} to ${targetRelayUrls.length} relays`);
try {
// Get actual relay objects from the NDK pool
const relayObjects = targetRelayUrls
.map(url => ndk.pool.getRelay(url))
.filter(relay => relay !== undefined);
if (relayObjects.length === 0) {
d.log(`Could not find any relay objects for event ${event.id?.slice(0, 8)}`);
continue;
}
// Publish to each relay individually
const publishPromises = relayObjects.map(relay => {
return new Promise(resolve => {
try {
// Log the attempt
d.log(`Publishing to relay: ${relay.url}`);
// Use the NDK's publish method
//ndk.publish(event, [relay]);
event.publish(undefined, undefined, 1);
resolve();
}
catch (err) {
d.error(`Error publishing to relay ${relay.url}:`, err);
resolve(); // Resolve anyway to continue
}
});
});
// Wait for all publish attempts
await Promise.all(publishPromises);
// If we get here, consider the event successfully published
successfullyPublished.push(event.id);
}
catch (error) {
d.error(`Error publishing event ${event.id?.slice(0, 8)}:`, error);
}
// Wait for the publish attempt to complete
await publishPromise;
}
catch (error) {
d.error(`Failed to publish event ${event.id?.slice(0, 8)}:`, error);
}
}
// Clean up successfully published events
if (successfullyPublished.length > 0 && typeof ndk.cacheAdapter.discardUnpublishedEvent === 'function') {
for (const eventId of successfullyPublished) {
try {
ndk.cacheAdapter.discardUnpublishedEvent(eventId);
d.log(`Removed ${eventId.slice(0, 8)} from unpublished events cache`);
}
catch (err) {
d.error(`Failed to remove event ${eventId.slice(0, 8)} from cache:`, err);
}
}
}
d.log(`Successfully published ${successfullyPublished.length} out of ${unpublishedEvents.length} events`);
return unpublishedEvents.length;
}
catch (error) {
d.error('Error processing unpublished events:', error);
return 0;
}
finally {
isCheckingUnpublishedEvents = false;
}
}
/**
* Starts the monitoring of unpublished events
*/
export function startUnpublishedEventsMonitor() {
if (!browser || retryInterval) {
return;
}
d.log('Starting unpublished events monitor');
// Initial check
publishUnpublishedEvents().catch(err => {
d.error('Failed initial unpublished events check:', err);
});
// Set up interval for periodic checks
retryInterval = setInterval(async () => {
retryCount++;
if (retryCount > MAX_RETRIES_PER_SESSION) {
d.log(`Reached maximum retry attempts (${MAX_RETRIES_PER_SESSION}), stopping monitor`);
stopUnpublishedEventsMonitor();
return;
}
const count = await publishUnpublishedEvents().catch(err => {
d.error('Failed to check unpublished events:', err);
return 0;
});
// If no unpublished events and we've done several checks, we can slow down
if (count === 0 && retryCount > 3) {
d.log('No unpublished events found after multiple checks, reducing frequency');
stopUnpublishedEventsMonitor();
retryInterval = setInterval(() => {
publishUnpublishedEvents().catch(d.error);
}, RETRY_INTERVAL * 5); // Check less frequently
}
}, RETRY_INTERVAL);
// Also check whenever connection status changes
// Store the unsubscribe function at module level so it can be accessed by stopUnpublishedEventsMonitor
unsubscribeRelayStatus = relayConnectionStatus.subscribe(status => {
const connectedCount = status.filter(s => s.connected).length;
if (connectedCount > 0) {
d.log(`Relay connection detected (${connectedCount} relays), checking unpublished events`);
publishUnpublishedEvents().catch(d.error);
}
});
// Remove the problematic code that was causing the error
// No more function reassignment here
}
/**
* Stops the monitoring of unpublished events
*/
export function stopUnpublishedEventsMonitor() {
if (retryInterval) {
clearInterval(retryInterval);
retryInterval = null;
d.log('Stopped unpublished events monitor');
}
// Add the unsubscribe logic directly in the original function
if (unsubscribeRelayStatus) {
unsubscribeRelayStatus();
unsubscribeRelayStatus = null;
d.log('Unsubscribed from relay connection status');
}
retryCount = 0;
}