SkateSpots/src/hooks/useNostrPublish.ts
2026-08-27 10:17:01 +07:00

104 lines
No EOL
3 KiB
TypeScript

import { useNostr } from "@nostrify/react";
import { useMutation, type UseMutationResult } from "@tanstack/react-query";
import { useCurrentUser } from "./useCurrentUser";
import type { NostrEvent } from "@nostrify/nostrify";
/**
* Publishes a Nostr event with retry logic and improved error handling
*/
export function useNostrPublish(): UseMutationResult<NostrEvent> {
const { nostr } = useNostr();
const { user } = useCurrentUser();
return useMutation({
mutationFn: async (t: Omit<NostrEvent, 'id' | 'pubkey' | 'sig'>) => {
if (!user) {
throw new Error("You must be logged in to publish events");
}
const tags = t.tags ?? [];
// Add the client tag if it doesn't exist
if (location.protocol === "https:" && !tags.some(([name]) => name === "client")) {
tags.push(["client", location.hostname]);
}
// Sign the event
const event = await user.signer.signEvent({
kind: t.kind,
content: t.content ?? "",
tags,
created_at: t.created_at ?? Math.floor(Date.now() / 1000),
});
// Publish with retry logic
await publishEventWithRetry(nostr, event);
return event;
},
onError: (error) => {
console.error("Failed to publish event:", error);
},
});
}
/**
* Publishes event with retry logic and partial success handling
*/
async function publishEventWithRetry(
nostr: any,
event: NostrEvent,
maxRetries = 1,
timeoutMs = 15000
): Promise<void> {
let lastError: Error | null = null;
// Try publishing with retries
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeoutMs);
try {
// Attempt to publish
await nostr.event(event, { signal: controller.signal });
return; // Success!
} finally {
clearTimeout(timeoutId);
}
} catch (error) {
lastError = error instanceof Error ? error : new Error(String(error));
// If this isn't the last attempt, wait before retrying
if (attempt < maxRetries) {
const backoffMs = 1000 * Math.pow(2, attempt);
await new Promise(resolve => setTimeout(resolve, backoffMs));
}
}
}
// All retries failed
if (lastError) {
// Provide user-friendly error messages
if (lastError.name === 'AbortError' || lastError.message.includes('timeout')) {
throw new Error(
'Publishing timed out. Your relays may be slow or unreachable. ' +
'Try checking your relay connections in Settings > Relays.'
);
} else if (lastError.message.includes('network') || lastError.message.includes('fetch')) {
throw new Error(
'Network error while publishing. Please check your internet connection and try again.'
);
} else {
throw new Error(
`Failed to publish after ${maxRetries + 1} attempts: ${lastError.message}`
);
}
}
throw new Error('Failed to publish event to any relay');
}