Browse Source

handled failed publishing

main
Silberengel 4 weeks ago
parent
commit
cd1ca702e9
  1. 135
      src/routes/api/repos/[npub]/[repo]/fork/+server.ts
  2. 33
      src/routes/repos/[npub]/[repo]/+page.svelte

135
src/routes/api/repos/[npub]/[repo]/fork/+server.ts

@ -8,7 +8,7 @@ import { RepoManager } from '$lib/services/git/repo-manager.js'; @@ -8,7 +8,7 @@ import { RepoManager } from '$lib/services/git/repo-manager.js';
import { DEFAULT_NOSTR_RELAYS, combineRelays, getGitUrl } from '$lib/config.js';
import { getUserRelays } from '$lib/services/nostr/user-relays.js';
import { NostrClient } from '$lib/services/nostr/nostr-client.js';
import { KIND } from '$lib/types/nostr.js';
import { KIND, type NostrEvent } from '$lib/types/nostr.js';
import { nip19 } from 'nostr-tools';
import { signEventWithNIP07 } from '$lib/services/nostr/nip07-signer.js';
import { OwnershipTransferService } from '$lib/services/nostr/ownership-transfer-service.js';
@ -22,6 +22,45 @@ const repoRoot = process.env.GIT_REPO_ROOT || '/repos'; @@ -22,6 +22,45 @@ const repoRoot = process.env.GIT_REPO_ROOT || '/repos';
const repoManager = new RepoManager(repoRoot);
const nostrClient = new NostrClient(DEFAULT_NOSTR_RELAYS);
/**
* Retry publishing an event with exponential backoff
* Attempts up to 3 times with delays: 1s, 2s, 4s
*/
async function publishEventWithRetry(
event: NostrEvent,
relays: string[],
eventName: string,
maxAttempts: number = 3
): Promise<{ success: string[]; failed: Array<{ relay: string; error: string }> }> {
let lastResult: { success: string[]; failed: Array<{ relay: string; error: string }> } | null = null;
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
console.log(`[Fork] Publishing ${eventName} - Attempt ${attempt}/${maxAttempts}...`);
lastResult = await nostrClient.publishEvent(event, relays);
if (lastResult.success.length > 0) {
console.log(`[Fork] ✓ ${eventName} published successfully to ${lastResult.success.length} relay(s): ${lastResult.success.join(', ')}`);
if (lastResult.failed.length > 0) {
console.warn(`[Fork] ⚠ Some relays failed: ${lastResult.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`);
}
return lastResult;
}
if (attempt < maxAttempts) {
const delayMs = Math.pow(2, attempt - 1) * 1000; // 1s, 2s, 4s
console.warn(`[Fork] ✗ ${eventName} failed on attempt ${attempt}. Retrying in ${delayMs}ms...`);
console.warn(`[Fork] Failed relays: ${lastResult.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`);
await new Promise(resolve => setTimeout(resolve, delayMs));
}
}
// All attempts failed
console.error(`[Fork] ✗ ${eventName} failed after ${maxAttempts} attempts`);
console.error(`[Fork] All relay failures: ${lastResult?.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`);
return lastResult!;
}
/**
* POST - Fork a repository
* Body: { userPubkey, forkName? }
@ -57,10 +96,12 @@ export const POST: RequestHandler = async ({ params, request }) => { @@ -57,10 +96,12 @@ export const POST: RequestHandler = async ({ params, request }) => {
// Decode user pubkey if needed
let userPubkeyHex = userPubkey;
try {
const userDecoded = nip19.decode(userPubkey);
if (userDecoded.type === 'npub') {
userPubkeyHex = userDecoded.data as string;
const userDecoded = nip19.decode(userPubkey) as { type: string; data: unknown };
// Type guard: check if it's an npub
if (userDecoded.type === 'npub' && typeof userDecoded.data === 'string') {
userPubkeyHex = userDecoded.data;
}
// If not npub, assume it's already hex
} catch {
// Assume it's already hex
}
@ -150,34 +191,108 @@ export const POST: RequestHandler = async ({ params, request }) => { @@ -150,34 +191,108 @@ export const POST: RequestHandler = async ({ params, request }) => {
const { outbox } = await getUserRelays(userPubkeyHex, nostrClient);
const combinedRelays = combineRelays(outbox);
const publishResult = await nostrClient.publishEvent(signedForkAnnouncement, combinedRelays);
console.log(`[Fork] Starting fork process for ${forkRepoName} by ${userNpub}`);
console.log(`[Fork] Using ${combinedRelays.length} relay(s): ${combinedRelays.join(', ')}`);
const publishResult = await publishEventWithRetry(
signedForkAnnouncement,
combinedRelays,
'fork announcement',
3
);
if (publishResult.success.length === 0) {
// Clean up repo if announcement failed
console.error(`[Fork] ✗ Fork announcement failed after all retries. Cleaning up repository.`);
await execAsync(`rm -rf "${forkRepoPath}"`).catch(() => {});
return error(500, 'Failed to publish fork announcement to relays');
const errorDetails = `All relays failed: ${publishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}`;
return json({
success: false,
error: 'Failed to publish fork announcement to relays after 3 attempts',
details: errorDetails,
eventName: 'fork announcement'
}, { status: 500 });
}
// Create and publish initial ownership proof (self-transfer event)
// This MUST succeed for the fork to be valid - without it, there's no proof of ownership on Nostr
const ownershipService = new OwnershipTransferService(combinedRelays);
const initialOwnershipEvent = ownershipService.createInitialOwnershipEvent(userPubkeyHex, forkRepoName);
const signedOwnershipEvent = await signEventWithNIP07(initialOwnershipEvent);
await nostrClient.publishEvent(signedOwnershipEvent, combinedRelays).catch(err => {
console.warn('Failed to publish initial ownership event for fork:', err);
});
const ownershipPublishResult = await publishEventWithRetry(
signedOwnershipEvent,
combinedRelays,
'ownership transfer event',
3
);
if (ownershipPublishResult.success.length === 0) {
// Clean up repo if ownership proof failed
console.error(`[Fork] ✗ Ownership transfer event failed after all retries. Cleaning up repository and publishing deletion request.`);
await execAsync(`rm -rf "${forkRepoPath}"`).catch(() => {});
// Publish deletion request (NIP-09) for the announcement since it's invalid without ownership proof
console.log(`[Fork] Publishing deletion request for invalid fork announcement...`);
const deletionRequest = {
kind: 5, // NIP-09: Event Deletion Request
pubkey: userPubkeyHex,
created_at: Math.floor(Date.now() / 1000),
content: 'Fork failed: ownership transfer event could not be published after 3 attempts. This announcement is invalid.',
tags: [
['a', `30617:${userPubkeyHex}:${forkRepoName}`], // Reference to the repo announcement
['k', KIND.REPO_ANNOUNCEMENT.toString()] // Kind of event being deleted
]
};
const signedDeletionRequest = await signEventWithNIP07(deletionRequest);
const deletionResult = await publishEventWithRetry(
signedDeletionRequest,
combinedRelays,
'deletion request',
3
);
if (deletionResult.success.length > 0) {
console.log(`[Fork] ✓ Deletion request published successfully`);
} else {
console.error(`[Fork] ✗ Failed to publish deletion request: ${deletionResult.failed.map(f => `${f.relay}: ${f.error}`).join(', ')}`);
}
const errorDetails = `Fork is invalid without ownership proof. All relays failed: ${ownershipPublishResult.failed.map(f => `${f.relay}: ${f.error}`).join('; ')}. Deletion request ${deletionResult.success.length > 0 ? 'published' : 'failed to publish'}.`;
return json({
success: false,
error: 'Failed to publish ownership transfer event to relays after 3 attempts',
details: errorDetails,
eventName: 'ownership transfer event'
}, { status: 500 });
}
// Provision the fork repo (this will create verification file and include self-transfer)
console.log(`[Fork] Provisioning fork repository...`);
await repoManager.provisionRepo(signedForkAnnouncement, signedOwnershipEvent, false);
console.log(`[Fork] ✓ Fork completed successfully!`);
console.log(`[Fork] - Repository: ${userNpub}/${forkRepoName}`);
console.log(`[Fork] - Announcement ID: ${signedForkAnnouncement.id}`);
console.log(`[Fork] - Ownership transfer ID: ${signedOwnershipEvent.id}`);
console.log(`[Fork] - Published to ${publishResult.success.length} relay(s) for announcement`);
console.log(`[Fork] - Published to ${ownershipPublishResult.success.length} relay(s) for ownership transfer`);
return json({
success: true,
fork: {
npub: userNpub,
repo: forkRepoName,
url: forkGitUrl,
announcementId: signedForkAnnouncement.id
announcementId: signedForkAnnouncement.id,
ownershipTransferId: signedOwnershipEvent.id,
publishedTo: {
announcement: publishResult.success.length,
ownershipTransfer: ownershipPublishResult.success.length
}
},
message: `Repository forked successfully! Published to ${publishResult.success.length} relay(s) for announcement and ${ownershipPublishResult.success.length} relay(s) for ownership proof.`
});
} catch (err) {
console.error('Error forking repository:', err);

33
src/routes/repos/[npub]/[repo]/+page.svelte

@ -156,22 +156,45 @@ @@ -156,22 +156,45 @@
error = null;
try {
console.log(`[Fork UI] Starting fork of ${npub}/${repo}...`);
const response = await fetch(`/api/repos/${npub}/${repo}/fork`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ userPubkey })
});
if (response.ok) {
const data = await response.json();
alert(`Repository forked successfully! Visit /repos/${data.fork.npub}/${data.fork.repo}`);
if (response.ok && data.success !== false) {
const message = data.message || `Repository forked successfully! Published to ${data.fork?.publishedTo?.announcement || 0} relay(s).`;
console.log(`[Fork UI] ✓ ${message}`);
console.log(`[Fork UI] - Fork location: /repos/${data.fork.npub}/${data.fork.repo}`);
console.log(`[Fork UI] - Announcement ID: ${data.fork.announcementId}`);
console.log(`[Fork UI] - Ownership Transfer ID: ${data.fork.ownershipTransferId}`);
alert(`✓ ${message}\n\nRedirecting to your fork...`);
goto(`/repos/${data.fork.npub}/${data.fork.repo}`);
} else {
const data = await response.json();
error = data.error || 'Failed to fork repository';
const errorMessage = data.error || 'Failed to fork repository';
const errorDetails = data.details ? `\n\nDetails: ${data.details}` : '';
const fullError = `${errorMessage}${errorDetails}`;
console.error(`[Fork UI] ✗ Fork failed: ${errorMessage}`);
if (data.details) {
console.error(`[Fork UI] Details: ${data.details}`);
}
if (data.eventName) {
console.error(`[Fork UI] Failed event: ${data.eventName}`);
}
error = fullError;
alert(`✗ Fork failed!\n\n${fullError}`);
}
} catch (err) {
error = err instanceof Error ? err.message : 'Failed to fork repository';
const errorMessage = err instanceof Error ? err.message : 'Failed to fork repository';
console.error(`[Fork UI] ✗ Unexpected error: ${errorMessage}`, err);
error = errorMessage;
alert(`✗ Fork failed!\n\n${errorMessage}`);
} finally {
forking = false;
}

Loading…
Cancel
Save