From 01dc9233edf40a74a1ef341415c25d0f6d73bf95 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 2 Aug 2025 09:58:28 -0500 Subject: [PATCH 1/3] Remove unnecessary `throw` commands --- src/lib/utils/websocket_utils.ts | 16 ++++++++-------- src/routes/publication/+page.server.ts | 10 +++++----- .../[type]/[identifier]/+layout.server.ts | 2 +- .../publication/[type]/[identifier]/+layout.ts | 4 ++-- .../[type]/[identifier]/+page.server.ts | 2 +- .../publication/[type]/[identifier]/+page.ts | 6 +++--- 6 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/lib/utils/websocket_utils.ts b/src/lib/utils/websocket_utils.ts index bad0818..f43f908 100644 --- a/src/lib/utils/websocket_utils.ts +++ b/src/lib/utils/websocket_utils.ts @@ -70,14 +70,14 @@ export async function fetchEventById(id: string): Promise { try { const event = await fetchNostrEvent({ ids: [id], limit: 1 }); if (!event) { - throw error(404, `Event not found for ID: ${id}`); + error(404, `Event not found for ID: ${id}`); } return event; } catch (err) { if (err && typeof err === "object" && "status" in err) { throw err; } - throw error(404, `Failed to fetch event by ID: ${err}`); + error(404, `Failed to fetch event by ID: ${err}`); } } @@ -88,14 +88,14 @@ export async function fetchEventByDTag(dTag: string): Promise { try { const event = await fetchNostrEvent({ "#d": [dTag], limit: 1 }); if (!event) { - throw error(404, `Event not found for d-tag: ${dTag}`); + error(404, `Event not found for d-tag: ${dTag}`); } return event; } catch (err) { if (err && typeof err === "object" && "status" in err) { throw err; } - throw error(404, `Failed to fetch event by d-tag: ${err}`); + error(404, `Failed to fetch event by d-tag: ${err}`); } } @@ -112,14 +112,14 @@ export async function fetchEventByNaddr(naddr: string): Promise { }; const event = await fetchNostrEvent(filter); if (!event) { - throw error(404, `Event not found for naddr: ${naddr}`); + error(404, `Event not found for naddr: ${naddr}`); } return event; } catch (err) { if (err && typeof err === "object" && "status" in err) { throw err; } - throw error(404, `Failed to fetch event by naddr: ${err}`); + error(404, `Failed to fetch event by naddr: ${err}`); } } @@ -131,13 +131,13 @@ export async function fetchEventByNevent(nevent: string): Promise { const decoded = neventDecode(nevent); const event = await fetchNostrEvent({ ids: [decoded.id], limit: 1 }); if (!event) { - throw error(404, `Event not found for nevent: ${nevent}`); + error(404, `Event not found for nevent: ${nevent}`); } return event; } catch (err) { if (err && typeof err === "object" && "status" in err) { throw err; } - throw error(404, `Failed to fetch event by nevent: ${err}`); + error(404, `Failed to fetch event by nevent: ${err}`); } } diff --git a/src/routes/publication/+page.server.ts b/src/routes/publication/+page.server.ts index 1b66af2..fa30a0d 100644 --- a/src/routes/publication/+page.server.ts +++ b/src/routes/publication/+page.server.ts @@ -25,17 +25,17 @@ export const load: PageServerLoad = ({ url }) => { if (id) { // Check if id is an naddr or nevent if (id.startsWith(IDENTIFIER_PREFIXES.NADDR)) { - throw redirect(301, `${ROUTES.NADDR}/${id}`); + redirect(301, `${ROUTES.NADDR}/${id}`); } else if (id.startsWith(IDENTIFIER_PREFIXES.NEVENT)) { - throw redirect(301, `${ROUTES.NEVENT}/${id}`); + redirect(301, `${ROUTES.NEVENT}/${id}`); } else { // Assume it's a hex ID - throw redirect(301, `${ROUTES.ID}/${id}`); + redirect(301, `${ROUTES.ID}/${id}`); } } else if (dTag) { - throw redirect(301, `${ROUTES.D_TAG}/${dTag}`); + redirect(301, `${ROUTES.D_TAG}/${dTag}`); } // If no query parameters, redirect to the start page - throw redirect(301, ROUTES.START); + redirect(301, ROUTES.START); }; \ No newline at end of file diff --git a/src/routes/publication/[type]/[identifier]/+layout.server.ts b/src/routes/publication/[type]/[identifier]/+layout.server.ts index f97639a..31e9c54 100644 --- a/src/routes/publication/[type]/[identifier]/+layout.server.ts +++ b/src/routes/publication/[type]/[identifier]/+layout.server.ts @@ -7,7 +7,7 @@ export const load: LayoutServerLoad = async ({ params, url }) => { // Validate the identifier type for SSR const validTypes = ['id', 'd', 'naddr', 'nevent']; if (!validTypes.includes(type)) { - throw error(400, `Unsupported identifier type: ${type}`); + error(400, `Unsupported identifier type: ${type}`); } // Provide basic metadata for SSR - actual fetching will happen on client diff --git a/src/routes/publication/[type]/[identifier]/+layout.ts b/src/routes/publication/[type]/[identifier]/+layout.ts index 0830e1a..d181095 100644 --- a/src/routes/publication/[type]/[identifier]/+layout.ts +++ b/src/routes/publication/[type]/[identifier]/+layout.ts @@ -39,7 +39,7 @@ export const load: LayoutLoad = async ({ params, url }) => { indexEvent = await fetchEventByNevent(identifier); break; default: - throw error(400, `Unsupported identifier type: ${type}`); + error(400, `Unsupported identifier type: ${type}`); } // Extract metadata for meta tags @@ -60,6 +60,6 @@ export const load: LayoutLoad = async ({ params, url }) => { }; } catch (err) { console.error('Failed to fetch publication:', err); - throw error(404, `Failed to load publication: ${err}`); + error(404, `Failed to load publication: ${err}`); } }; diff --git a/src/routes/publication/[type]/[identifier]/+page.server.ts b/src/routes/publication/[type]/[identifier]/+page.server.ts index 5695e77..eeffa46 100644 --- a/src/routes/publication/[type]/[identifier]/+page.server.ts +++ b/src/routes/publication/[type]/[identifier]/+page.server.ts @@ -7,7 +7,7 @@ export const load: PageServerLoad = async ({ params }) => { // Validate the identifier type for SSR const validTypes = ['id', 'd', 'naddr', 'nevent']; if (!validTypes.includes(type)) { - throw error(400, `Unsupported identifier type: ${type}`); + error(400, `Unsupported identifier type: ${type}`); } // Provide basic data for SSR - actual fetching will happen on client diff --git a/src/routes/publication/[type]/[identifier]/+page.ts b/src/routes/publication/[type]/[identifier]/+page.ts index 6de9d27..5d2d444 100644 --- a/src/routes/publication/[type]/[identifier]/+page.ts +++ b/src/routes/publication/[type]/[identifier]/+page.ts @@ -34,11 +34,11 @@ export const load: PageLoad = async ({ params }) => { indexEvent = await fetchEventByNevent(identifier); break; default: - throw error(400, `Unsupported identifier type: ${type}`); + error(400, `Unsupported identifier type: ${type}`); } if (!indexEvent) { - throw error(404, `Event not found for ${type}: ${identifier}`); + error(404, `Event not found for ${type}: ${identifier}`); } const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? ""; @@ -49,6 +49,6 @@ export const load: PageLoad = async ({ params }) => { }; } catch (err) { console.error('Failed to fetch publication:', err); - throw error(404, `Failed to load publication: ${err}`); + error(404, `Failed to load publication: ${err}`); } }; \ No newline at end of file From ff4162739a9cd1671484061f6730ace1ca14b72d Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 2 Aug 2025 11:18:55 -0500 Subject: [PATCH 2/3] Use functional programming for self-cleaning WebSocket callbacks --- src/lib/utils/websocket_utils.ts | 105 ++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 30 deletions(-) diff --git a/src/lib/utils/websocket_utils.ts b/src/lib/utils/websocket_utils.ts index f43f908..ab6ef5b 100644 --- a/src/lib/utils/websocket_utils.ts +++ b/src/lib/utils/websocket_utils.ts @@ -22,42 +22,87 @@ export interface NostrFilter { limit?: number; } +type ResolveCallback = (value: T | PromiseLike) => void; +type RejectCallback = (reason?: any) => void; +type EventHandler = (ev: Event) => void; +type EventHandlerReject = (reject: RejectCallback) => EventHandler; +type EventHandlerResolve = (resolve: ResolveCallback) => EventHandlerReject; + +function handleMessage( + ev: MessageEvent, + subId: string, + resolve: (event: NostrEvent) => void, + reject: (reason: any) => void +) { + const data = JSON.parse(ev.data); + + if (data[1] !== subId) { + return; + } + + switch (data[0]) { + case "EVENT": + break; + case "CLOSED": + reject(new Error(`[WebSocket Utils]: Subscription ${subId} closed`)); + break; + case "EOSE": + reject(new Error(`[WebSocket Utils]: Event not found`)); + break; + } + + const event = data[2] as NostrEvent; + if (!event) { + return; + } + + resolve(event); +} + +function handleError( + ev: Event, + reject: (reason: any) => void +) { + reject(ev); +} + export async function fetchNostrEvent(filter: NostrFilter): Promise { // TODO: Improve relay selection when relay management is implemented. const ws = await WebSocketPool.instance.acquire("wss://thecitadel.nostr1.com"); const subId = crypto.randomUUID(); + // AI-NOTE: Currying is used here to abstract the internal handler logic away from the WebSocket + // handling logic. The message and error handlers themselves can be refactored without affecting + // the WebSocket handling logic. + const curriedMessageHandler: (subId: string) => EventHandlerResolve = + (subId) => + (resolve) => + (reject) => + (ev: MessageEvent) => + handleMessage(ev, subId, resolve, reject); + const curriedErrorHandler: EventHandlerReject = + (reject) => + (ev: Event) => + handleError(ev, reject); + + // AI-NOTE: These variables store references to partially-applied handlers so that the `finally` + // block receives the correct references to clean up the listeners. + let messageHandler: EventHandler; + let errorHandler: EventHandler; + const res = new Promise((resolve, reject) => { - ws.addEventListener("message", (ev) => { - const data = JSON.parse(ev.data); - - if (data[1] !== subId) { - return; - } - - switch (data[0]) { - case "EVENT": - break; - case "CLOSED": - reject(new Error(`[WebSocket Utils]: Subscription ${subId} closed`)); - break; - case "EOSE": - reject(new Error(`[WebSocket Utils]: Event not found`)); - break; - } - - const event = data[2] as NostrEvent; - if (!event) { - return; - } - - resolve(event); - }); - - ws.addEventListener("error", (ev) => { - reject(ev); - }); - }).withTimeout(2000); + messageHandler = curriedMessageHandler(subId)(resolve)(reject); + errorHandler = curriedErrorHandler(reject); + + ws.addEventListener("message", messageHandler); + ws.addEventListener("error", errorHandler); + }) + .withTimeout(2000) + .finally(() => { + ws.removeEventListener("message", messageHandler); + ws.removeEventListener("error", errorHandler); + WebSocketPool.instance.release(ws); + }); ws.send(JSON.stringify(["REQ", subId, filter])); return res; From 08061688112749065f227be2b6f4daf1ef7f5935 Mon Sep 17 00:00:00 2001 From: buttercat1791 Date: Sat, 2 Aug 2025 23:02:07 -0500 Subject: [PATCH 3/3] Ensure SSR is allowed for page layouts --- src/routes/publication/[type]/[identifier]/+layout.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/routes/publication/[type]/[identifier]/+layout.ts b/src/routes/publication/[type]/[identifier]/+layout.ts index d181095..d8cab28 100644 --- a/src/routes/publication/[type]/[identifier]/+layout.ts +++ b/src/routes/publication/[type]/[identifier]/+layout.ts @@ -4,6 +4,8 @@ import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent import type { NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; import { browser } from "$app/environment"; +export const ssr = true; + export const load: LayoutLoad = async ({ params, url }) => { const { type, identifier } = params;