askexperts
Version:
AskExperts SDK: build and use AI experts - ask them questions and pay with bitcoin on an open protocol
201 lines • 7.25 kB
JavaScript
/**
* Relay utilities for NIP-174
*/
import { debugError } from "./debug.js";
/**
* Publishes an event to multiple relays
*
* @param event - The event to publish
* @param relays - Array of relay URLs
* @param timeout - Timeout in milliseconds (default: 5000)
* @returns Promise resolving to array of relay URLs where the event was successfully published
*/
export async function publishToRelays(event, relays, pool, timeout = 5000) {
const successfulRelays = [];
try {
// Create an array of promises for publishing to each relay
const publishPromises = relays.map(async (relayUrl) => {
try {
// Set up a promise that will be resolved on successful publish or rejected on timeout
const publishPromise = new Promise(async (resolve, reject) => {
try {
// Connect to the relay
const relay = await pool.ensureRelay(relayUrl, {
connectionTimeout: timeout,
});
// Publish the event
await relay.publish(event);
// If successful, add to the list
successfulRelays.push(relayUrl);
resolve();
}
catch (err) {
reject(err);
}
});
// Set up a timeout promise
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error(`Publish to ${relayUrl} timed out after ${timeout}ms`)), timeout);
});
// Race the publish against the timeout
await Promise.race([publishPromise, timeoutPromise]);
}
catch (error) {
// Log the error but don't fail the entire operation
debugError(`Failed to publish to ${relayUrl}:`, error);
}
});
// Wait for all publish attempts to complete
await Promise.all(publishPromises);
}
catch (error) {
debugError("Error in publishToRelays:", error);
}
// Return the list of relays where publication was successful
return successfulRelays;
}
/**
* Subscribes to events from multiple relays
*
* @param filters - Array of filters
* @param relays - Array of relay URLs
* @param options - Subscription options
* @returns Subscription object
*/
export function subscribeToRelays(filters, relays, pool, options = {}) {
return pool.subscribeMany(relays, filters, options);
}
/**
* Fetches events from multiple relays
*
* @param filters - Array of filters
* @param relays - Array of relay URLs
* @param timeout - Timeout in milliseconds (default: 5000)
* @returns Promise resolving to array of events
*/
export async function fetchFromRelays(filter, relays, pool, timeout = 5000) {
// Fetch and sort properly
return (await pool.querySync(relays, filter, { maxWait: timeout })).sort((a, b) => b.created_at - a.created_at);
}
/**
* Waits for a specific event matching the filter
*
* @param filter - Filter to match events
* @param relays - Array of relay URLs
* @param timeout - Timeout in milliseconds (default: 30000)
* @returns Promise resolving to the matched event or null if timeout
*/
export async function waitForEvent(filter, relays, pool, timeout = 30000) {
return new Promise((resolve) => {
let timeoutId;
let resolved = false;
const sub = pool.subscribeMany(relays, [filter], {
onevent(event) {
if (!resolved) {
resolved = true;
clearTimeout(timeoutId);
sub.close();
resolve(event);
}
},
});
timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
sub.close();
resolve(null);
}
}, timeout);
});
}
/**
* Creates an async iterable for events matching the filter
*
* @param filter - Filter to match events
* @param relays - Array of relay URLs
* @param options - Options for the subscription
* @returns Async iterable yielding events
*/
export function createEventStream(filter, relays, pool, options = {}) {
return {
[Symbol.asyncIterator]() {
const queue = [];
let done = false;
let error = null;
let resolveNext = null;
const sub = pool.subscribeMany(relays, [filter], {
onevent(event) {
if (done)
return;
if (resolveNext) {
resolveNext({ value: event, done: false });
resolveNext = null;
}
else {
queue.push(event);
}
},
oneose() {
if (options.closeOnEose && !done) {
done = true;
if (resolveNext) {
resolveNext({ value: undefined, done: true });
resolveNext = null;
}
}
},
});
// Set timeout if specified
let timeoutId = null;
if (options.timeout) {
timeoutId = setTimeout(() => {
if (!done) {
done = true;
if (resolveNext) {
resolveNext({ value: undefined, done: true });
resolveNext = null;
}
}
}, options.timeout);
}
return {
async next() {
if (error) {
throw error;
}
if (done) {
return { value: undefined, done: true };
}
if (queue.length > 0) {
return { value: queue.shift(), done: false };
}
return new Promise((resolve) => {
resolveNext = resolve;
});
},
async return() {
if (!done) {
done = true;
sub.close();
if (timeoutId) {
clearTimeout(timeoutId);
}
}
return { value: undefined, done: true };
},
async throw(err) {
if (!done) {
done = true;
error = err instanceof Error ? err : new Error(String(err));
sub.close();
if (timeoutId) {
clearTimeout(timeoutId);
}
}
throw error;
},
};
},
};
}
//# sourceMappingURL=relay.js.map