Browse Source

improved error handling for missing publications

master
silberengel 7 months ago
parent
commit
b119c0010a
  1. 16
      src/lib/utils/websocket_utils.ts
  2. 123
      src/routes/publication/+error.svelte
  3. 23
      src/routes/publication/[type]/[identifier]/+page.ts

16
src/lib/utils/websocket_utils.ts

@ -69,7 +69,7 @@ function handleError(
reject(ev); reject(ev);
} }
export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent> { export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent | null> {
// AI-NOTE: Updated to use active relay stores instead of hardcoded relay URL // AI-NOTE: Updated to use active relay stores instead of hardcoded relay URL
// This ensures the function uses the user's configured relays and can find events // This ensures the function uses the user's configured relays and can find events
// across multiple relays rather than being limited to a single hardcoded relay. // across multiple relays rather than being limited to a single hardcoded relay.
@ -82,7 +82,11 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent>
const availableRelays = [...inboxRelays, ...outboxRelays]; const availableRelays = [...inboxRelays, ...outboxRelays];
if (availableRelays.length === 0) { if (availableRelays.length === 0) {
throw new Error("[WebSocket Utils]: No relays available for fetching events"); // AI-NOTE: Return null instead of throwing error when no relays are available
// This allows the publication routes to handle the case gracefully during preloading
// when relay stores haven't been populated yet
console.warn("[WebSocket Utils]: No relays available for fetching events, returning null");
return null;
} }
// Select a relay - prefer inbox relays if available, otherwise use any available relay // Select a relay - prefer inbox relays if available, otherwise use any available relay
@ -135,7 +139,7 @@ export async function fetchEventById(id: string): Promise<NostrEvent> {
try { try {
const event = await fetchNostrEvent({ ids: [id], limit: 1 }); const event = await fetchNostrEvent({ ids: [id], limit: 1 });
if (!event) { if (!event) {
error(404, `Event not found for ID: ${id}`); error(404, `Event not found for ID: ${id}. href="/events?id=${id}"`);
} }
return event; return event;
} catch (err) { } catch (err) {
@ -153,7 +157,7 @@ export async function fetchEventByDTag(dTag: string): Promise<NostrEvent> {
try { try {
const event = await fetchNostrEvent({ "#d": [dTag], limit: 1 }); const event = await fetchNostrEvent({ "#d": [dTag], limit: 1 });
if (!event) { if (!event) {
error(404, `Event not found for d-tag: ${dTag}`); error(404, `Event not found for d-tag: ${dTag}. href="/events?d=${dTag}"`);
} }
return event; return event;
} catch (err) { } catch (err) {
@ -177,7 +181,7 @@ export async function fetchEventByNaddr(naddr: string): Promise<NostrEvent> {
}; };
const event = await fetchNostrEvent(filter); const event = await fetchNostrEvent(filter);
if (!event) { if (!event) {
error(404, `Event not found for naddr: ${naddr}`); error(404, `Event not found for naddr: ${naddr}. href="/events?id=${naddr}"`);
} }
return event; return event;
} catch (err) { } catch (err) {
@ -196,7 +200,7 @@ export async function fetchEventByNevent(nevent: string): Promise<NostrEvent> {
const decoded = neventDecode(nevent); const decoded = neventDecode(nevent);
const event = await fetchNostrEvent({ ids: [decoded.id], limit: 1 }); const event = await fetchNostrEvent({ ids: [decoded.id], limit: 1 });
if (!event) { if (!event) {
error(404, `Event not found for nevent: ${nevent}`); error(404, `Event not found for nevent: ${nevent}. href="/events?id=${nevent}"`);
} }
return event; return event;
} catch (err) { } catch (err) {

123
src/routes/publication/+error.svelte

@ -3,28 +3,125 @@
import { Alert, P, Button } from "flowbite-svelte"; import { Alert, P, Button } from "flowbite-svelte";
import { ExclamationCircleOutline } from "flowbite-svelte-icons"; import { ExclamationCircleOutline } from "flowbite-svelte-icons";
import { page } from "$app/state"; import { page } from "$app/state";
// Parse error message to extract search parameters and format it nicely
function parseErrorMessage(message: string): {
errorType: string;
identifier: string;
searchUrl?: string;
shortIdentifier?: string;
} {
const searchLinkMatch = message.match(/href="([^"]+)"/);
let searchUrl: string | undefined;
let baseMessage = message;
if (searchLinkMatch) {
searchUrl = searchLinkMatch[1];
baseMessage = message.replace(/href="[^"]+"/, '').trim();
}
// Extract error type and identifier from the message
const match = baseMessage.match(/Event not found for (\w+): (.+)/);
if (match) {
const errorType = match[1];
const fullIdentifier = match[2];
const shortIdentifier = fullIdentifier.length > 50
? fullIdentifier.substring(0, 47) + '...'
: fullIdentifier;
return {
errorType,
identifier: fullIdentifier,
searchUrl,
shortIdentifier
};
}
return {
errorType: 'unknown',
identifier: baseMessage,
searchUrl,
shortIdentifier: baseMessage.length > 50
? baseMessage.substring(0, 47) + '...'
: baseMessage
};
}
$: errorInfo = page.error?.message ? parseErrorMessage(page.error.message) : {
errorType: 'unknown',
identifier: '',
shortIdentifier: ''
};
</script> </script>
<main> <main class="max-w-2xl mx-auto p-6">
<Alert> <Alert class="border-l-4 border-red-500 bg-red-50 dark:bg-red-900/20">
<div class="flex items-center space-x-2"> <div class="flex items-center space-x-2 mb-4">
<ExclamationCircleOutline class="w-6 h-6" /> <ExclamationCircleOutline class="w-6 h-6 text-red-600 dark:text-red-400" />
<span class="text-lg font-medium"> Failed to load publication. </span> <span class="text-lg font-medium text-red-800 dark:text-red-200">
Failed to load publication
</span>
</div> </div>
<P size="sm">
Alexandria failed to find one or more of the events comprising this <P size="sm" class="text-gray-700 dark:text-gray-300 mb-4">
publication. Alexandria failed to find one or more of the events comprising this publication.
</P>
<P size="xs">
{page.error?.message}
</P> </P>
<div class="bg-gray-100 dark:bg-gray-800 rounded-lg p-4 mb-4">
<div class="flex items-start space-x-2">
<span class="text-sm font-medium text-gray-600 dark:text-gray-400 min-w-0">
Error Type:
</span>
<span class="text-sm text-gray-800 dark:text-gray-200 font-mono">
{errorInfo.errorType}
</span>
</div>
<div class="flex items-start space-x-2 mt-2">
<span class="text-sm font-medium text-gray-600 dark:text-gray-400 min-w-0">
Identifier:
</span>
<div class="flex-1 min-w-0">
<div class="text-sm text-gray-800 dark:text-gray-200 font-mono break-all">
{errorInfo.shortIdentifier}
</div>
{#if errorInfo.identifier.length > 50}
<details class="mt-2">
<summary class="text-xs text-blue-600 dark:text-blue-400 cursor-pointer hover:underline">
Show full identifier
</summary>
<div class="mt-2 text-xs text-gray-600 dark:text-gray-400 font-mono break-all">
{errorInfo.identifier}
</div>
</details>
{/if}
</div>
</div>
</div>
{#if errorInfo.searchUrl}
<div class="mb-4">
<Button
class="btn-leather !w-fit bg-blue-600 hover:bg-blue-700 text-white"
size="sm"
onclick={() => {
if (errorInfo.searchUrl) {
goto(errorInfo.searchUrl);
}
}}
>
🔍 Search for this event
</Button>
</div>
{/if}
<div class="flex space-x-2"> <div class="flex space-x-2">
<Button <Button
class="btn-leather !w-fit" class="btn-leather !w-fit"
size="sm" size="sm"
onclick={() => invalidateAll()} onclick={() => invalidateAll()}
> >
Try Again 🔄 Try Again
</Button> </Button>
<Button <Button
class="btn-leather !w-fit" class="btn-leather !w-fit"
@ -32,7 +129,7 @@
outline outline
onclick={() => goto("/")} onclick={() => goto("/")}
> >
Return home 🏠 Return home
</Button> </Button>
</div> </div>
</Alert> </Alert>

23
src/routes/publication/[type]/[identifier]/+page.ts

@ -27,7 +27,28 @@ export const load: PageLoad = async ({ params }: { params: { type: string; ident
} }
if (!indexEvent) { if (!indexEvent) {
error(404, `Event not found for ${type}: ${identifier}`); // AI-NOTE: Handle case where no relays are available during preloading
// This prevents 404 errors when relay stores haven't been populated yet
console.warn(`[Publication Load] Event not found for ${type}: ${identifier} - may be due to no relays available`);
// Create appropriate search link based on type
let searchParam = '';
switch (type) {
case 'id':
searchParam = `id=${identifier}`;
break;
case 'd':
searchParam = `d=${identifier}`;
break;
case 'naddr':
case 'nevent':
searchParam = `id=${identifier}`;
break;
default:
searchParam = `q=${identifier}`;
}
error(404, `Event not found for ${type}: ${identifier}. href="/events?${searchParam}"`);
} }
const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? ""; const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? "";

Loading…
Cancel
Save