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( @@ -69,7 +69,7 @@ function handleError(
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
// 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.
@ -82,7 +82,11 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent> @@ -82,7 +82,11 @@ export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent>
const availableRelays = [...inboxRelays, ...outboxRelays];
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
@ -135,7 +139,7 @@ export async function fetchEventById(id: string): Promise<NostrEvent> { @@ -135,7 +139,7 @@ export async function fetchEventById(id: string): Promise<NostrEvent> {
try {
const event = await fetchNostrEvent({ ids: [id], limit: 1 });
if (!event) {
error(404, `Event not found for ID: ${id}`);
error(404, `Event not found for ID: ${id}. href="/events?id=${id}"`);
}
return event;
} catch (err) {
@ -153,7 +157,7 @@ export async function fetchEventByDTag(dTag: string): Promise<NostrEvent> { @@ -153,7 +157,7 @@ export async function fetchEventByDTag(dTag: string): Promise<NostrEvent> {
try {
const event = await fetchNostrEvent({ "#d": [dTag], limit: 1 });
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;
} catch (err) {
@ -177,7 +181,7 @@ export async function fetchEventByNaddr(naddr: string): Promise<NostrEvent> { @@ -177,7 +181,7 @@ export async function fetchEventByNaddr(naddr: string): Promise<NostrEvent> {
};
const event = await fetchNostrEvent(filter);
if (!event) {
error(404, `Event not found for naddr: ${naddr}`);
error(404, `Event not found for naddr: ${naddr}. href="/events?id=${naddr}"`);
}
return event;
} catch (err) {
@ -196,7 +200,7 @@ export async function fetchEventByNevent(nevent: string): Promise<NostrEvent> { @@ -196,7 +200,7 @@ export async function fetchEventByNevent(nevent: string): Promise<NostrEvent> {
const decoded = neventDecode(nevent);
const event = await fetchNostrEvent({ ids: [decoded.id], limit: 1 });
if (!event) {
error(404, `Event not found for nevent: ${nevent}`);
error(404, `Event not found for nevent: ${nevent}. href="/events?id=${nevent}"`);
}
return event;
} catch (err) {

123
src/routes/publication/+error.svelte

@ -3,28 +3,125 @@ @@ -3,28 +3,125 @@
import { Alert, P, Button } from "flowbite-svelte";
import { ExclamationCircleOutline } from "flowbite-svelte-icons";
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>
<main>
<Alert>
<div class="flex items-center space-x-2">
<ExclamationCircleOutline class="w-6 h-6" />
<span class="text-lg font-medium"> Failed to load publication. </span>
<main class="max-w-2xl mx-auto p-6">
<Alert class="border-l-4 border-red-500 bg-red-50 dark:bg-red-900/20">
<div class="flex items-center space-x-2 mb-4">
<ExclamationCircleOutline class="w-6 h-6 text-red-600 dark:text-red-400" />
<span class="text-lg font-medium text-red-800 dark:text-red-200">
Failed to load publication
</span>
</div>
<P size="sm">
Alexandria failed to find one or more of the events comprising this
publication.
</P>
<P size="xs">
{page.error?.message}
<P size="sm" class="text-gray-700 dark:text-gray-300 mb-4">
Alexandria failed to find one or more of the events comprising this publication.
</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">
<Button
class="btn-leather !w-fit"
size="sm"
onclick={() => invalidateAll()}
>
Try Again
🔄 Try Again
</Button>
<Button
class="btn-leather !w-fit"
@ -32,7 +129,7 @@ @@ -32,7 +129,7 @@
outline
onclick={() => goto("/")}
>
Return home
🏠 Return home
</Button>
</div>
</Alert>

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

@ -27,7 +27,28 @@ export const load: PageLoad = async ({ params }: { params: { type: string; ident @@ -27,7 +27,28 @@ export const load: PageLoad = async ({ params }: { params: { type: string; ident
}
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] ?? "";

Loading…
Cancel
Save