Browse Source

Use utils based on raw WebSockets for SSR

master
buttercat1791 8 months ago
parent
commit
aa76910bc4
  1. 83
      src/lib/utils/nostrUtils.ts
  2. 143
      src/lib/utils/websocket_utils.ts
  3. 29
      src/routes/publication/[type]/[identifier]/+layout.server.ts
  4. 28
      src/routes/publication/[type]/[identifier]/+page.server.ts
  5. 17
      src/routes/publication/[type]/[identifier]/+page.svelte

83
src/lib/utils/nostrUtils.ts

@ -12,8 +12,6 @@ import { schnorr } from "@noble/curves/secp256k1"; @@ -12,8 +12,6 @@ import { schnorr } from "@noble/curves/secp256k1";
import { bytesToHex } from "@noble/hashes/utils";
import { wellKnownUrl } from "./search_utility.ts";
import { VALIDATION } from "./search_constants.ts";
import { error } from "@sveltejs/kit";
import { naddrDecode, neventDecode } from "../utils.ts";
const badgeCheckSvg =
'<svg class="w-6 h-6 text-gray-800 dark:text-white" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="currentColor" viewBox="0 0 24 24"><path fill-rule="evenodd" d="M12 2c-.791 0-1.55.314-2.11.874l-.893.893a.985.985 0 0 1-.696.288H7.04A2.984 2.984 0 0 0 4.055 7.04v1.262a.986.986 0 0 1-.288.696l-.893.893a2.984 2.984 0 0 0 0 4.22l.893.893a.985.985 0 0 1 .288.696v1.262a2.984 2.984 0 0 0 2.984 2.984h1.262c.261 0 .512.104.696.288l.893.893a2.984 2.984 0 0 0 4.22 0l.893-.893a.985.985 0 0 1 .696-.288h1.262a2.984 2.984 0 0 0 2.984-2.984V15.7c0-.261.104-.512.288-.696l.893-.893a2.984 2.984 0 0 0 0-4.22l-.893-.893a.985.985 0 0 1-.288-.696V7.04a2.984 2.984 0 0 0-2.984-2.984h-1.262a.985.985 0 0 1-.696-.288l-.893-.893A2.984 2.984 0 0 0 12 2Zm3.683 7.73a1 1 0 1 0-1.414-1.413l-4.253 4.253-1.277-1.277a1 1 0 0 0-1.415 1.414l1.985 1.984a1 1 0 0 0 1.414 0l4.96-4.96Z" clip-rule="evenodd"/></svg>';
@ -673,84 +671,3 @@ export function prefixNostrAddresses(content: string): string { @@ -673,84 +671,3 @@ export function prefixNostrAddresses(content: string): string {
return `nostr:${match}`;
});
}
// Added functions for fetching events by various identifiers
/**
* Fetches an event by hex ID, throwing a SvelteKit 404 error if not found.
*/
export async function fetchEventById(ndk: NDK, id: string): Promise<NDKEvent> {
try {
const event = await fetchEventWithFallback(ndk, id);
if (!event) {
throw 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}`);
}
}
/**
* Fetches an event by d tag, throwing a 404 if not found.
*/
export async function fetchEventByDTag(ndk: NDK, dTag: string): Promise<NDKEvent> {
try {
const event = await fetchEventWithFallback(ndk, { "#d": [dTag], limit: 1 });
if (!event) {
throw 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}`);
}
}
/**
* Fetches an event by naddr identifier.
*/
export async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise<NDKEvent> {
try {
const decoded = naddrDecode(naddr);
const filter = {
kinds: [decoded.kind],
authors: [decoded.pubkey],
"#d": [decoded.identifier],
};
const event = await fetchEventWithFallback(ndk, filter);
if (!event) {
throw 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}`);
}
}
/**
* Fetches an event by nevent identifier.
*/
export async function fetchEventByNevent(ndk: NDK, nevent: string): Promise<NDKEvent> {
try {
const decoded = neventDecode(nevent);
const event = await fetchEventWithFallback(ndk, decoded.id);
if (!event) {
throw 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}`);
}
}

143
src/lib/utils/websocket_utils.ts

@ -0,0 +1,143 @@ @@ -0,0 +1,143 @@
import { WebSocketPool } from "../data_structures/websocket_pool.ts";
import { error } from "@sveltejs/kit";
import { naddrDecode, neventDecode } from "../utils.ts";
export interface NostrEvent {
id: string;
pubkey: string;
created_at: number;
kind: number;
tags: string[][];
content: string;
sig: string;
}
export interface NostrFilter {
ids?: string[];
authors?: string[];
kinds?: number[];
[tag: `#${string}`]: string[] | undefined;
since?: number;
until?: number;
limit?: number;
}
export async function fetchNostrEvent(filter: NostrFilter): Promise<NostrEvent> {
// TODO: Improve relay selection when relay management is implemented.
const ws = await WebSocketPool.instance.acquire("wss://thecitadel.nostr1.com");
const subId = crypto.randomUUID();
const res = new Promise<NostrEvent | null>((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":
resolve(null);
break;
}
const event = data[2] as NostrEvent;
if (!event) {
return;
}
resolve(event);
});
ws.addEventListener("error", (ev) => {
reject(ev);
});
}).withTimeout(2000);
ws.send(JSON.stringify(["REQ", subId, filter]));
return res;
}
/**
* Fetches an event by hex ID, throwing a SvelteKit 404 error if not found.
*/
export async function fetchEventById(id: string): Promise<NostrEvent> {
try {
const event = await fetchNostrEvent({ ids: [id], limit: 1 });
if (!event) {
throw 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}`);
}
}
/**
* Fetches an event by d tag, throwing a 404 if not found.
*/
export async function fetchEventByDTag(dTag: string): Promise<NostrEvent> {
try {
const event = await fetchNostrEvent({ "#d": [dTag], limit: 1 });
if (!event) {
throw 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}`);
}
}
/**
* Fetches an event by naddr identifier.
*/
export async function fetchEventByNaddr(naddr: string): Promise<NostrEvent> {
try {
const decoded = naddrDecode(naddr);
const filter = {
kinds: [decoded.kind],
authors: [decoded.pubkey],
"#d": [decoded.identifier],
};
const event = await fetchNostrEvent(filter);
if (!event) {
throw 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}`);
}
}
/**
* Fetches an event by nevent identifier.
*/
export async function fetchEventByNevent(nevent: string): Promise<NostrEvent> {
try {
const decoded = neventDecode(nevent);
const event = await fetchNostrEvent({ ids: [decoded.id], limit: 1 });
if (!event) {
throw 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}`);
}
}

29
src/routes/publication/[type]/[identifier]/+layout.server.ts

@ -1,44 +1,35 @@ @@ -1,44 +1,35 @@
import { error } from "@sveltejs/kit";
import type { LayoutServerLoad } from "./$types";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts";
import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent, NostrEvent } from "../../../../lib/utils/websocket_utils.ts";
export const load: LayoutServerLoad = async ({ params, parent, url }) => {
export const load: LayoutServerLoad = async ({ params, url }) => {
const { type, identifier } = params;
// TODO: Remove the need for NDK in nostrUtils dependencies, since NDK is not available on the server.
// deno-lint-ignore no-explicit-any
const { ndk } = (await parent()) as any;
if (!ndk) {
throw error(500, "NDK not available");
}
let indexEvent: NDKEvent;
let indexEvent: NostrEvent;
// Handle different identifier types
switch (type) {
case 'id':
indexEvent = await fetchEventById(ndk, identifier);
indexEvent = await fetchEventById(identifier);
break;
case 'd':
indexEvent = await fetchEventByDTag(ndk, identifier);
indexEvent = await fetchEventByDTag(identifier);
break;
case 'naddr':
indexEvent = await fetchEventByNaddr(ndk, identifier);
indexEvent = await fetchEventByNaddr(identifier);
break;
case 'nevent':
indexEvent = await fetchEventByNevent(ndk, identifier);
indexEvent = await fetchEventByNevent(identifier);
break;
default:
throw error(400, `Unsupported identifier type: ${type}`);
}
// Extract metadata for meta tags
const title = getMatchingTags(indexEvent, "title")[0]?.[1] || "Alexandria Publication";
const summary = getMatchingTags(indexEvent, "summary")[0]?.[1] ||
const title = indexEvent.tags.find((tag) => tag[0] === "title")?.[1] || "Alexandria Publication";
const summary = indexEvent.tags.find((tag) => tag[0] === "summary")?.[1] ||
"Alexandria is a digital library, utilizing Nostr events for curated publications and wiki pages.";
const image = getMatchingTags(indexEvent, "image")[0]?.[1] || "/screenshots/old_books.jpg";
const image = indexEvent.tags.find((tag) => tag[0] === "image")?.[1] || "/screenshots/old_books.jpg";
const currentUrl = `${url.origin}${url.pathname}`;
return {

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

@ -1,42 +1,38 @@ @@ -1,42 +1,38 @@
import { error } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import type { NDKEvent } from "@nostr-dev-kit/ndk";
import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts";
import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent, NostrEvent } from "../../../../lib/utils/websocket_utils.ts";
export const load: PageServerLoad = async ({ params, parent }) => {
export const load: PageServerLoad = async ({ params }) => {
const { type, identifier } = params;
// deno-lint-ignore no-explicit-any
const { ndk } = (await parent()) as any;
if (!ndk) {
throw error(500, "NDK not available");
}
let indexEvent: NDKEvent;
let indexEvent: NostrEvent | null;
// Handle different identifier types
switch (type) {
case 'id':
indexEvent = await fetchEventById(ndk, identifier);
indexEvent = await fetchEventById(identifier);
break;
case 'd':
indexEvent = await fetchEventByDTag(ndk, identifier);
indexEvent = await fetchEventByDTag(identifier);
break;
case 'naddr':
indexEvent = await fetchEventByNaddr(ndk, identifier);
indexEvent = await fetchEventByNaddr(identifier);
break;
case 'nevent':
indexEvent = await fetchEventByNevent(ndk, identifier);
indexEvent = await fetchEventByNevent(identifier);
break;
default:
throw error(400, `Unsupported identifier type: ${type}`);
}
const publicationType = getMatchingTags(indexEvent, "type")[0]?.[1];
if (!indexEvent) {
throw error(404, `Event not found for ${type}: ${identifier}`);
}
const publicationType = indexEvent.tags.find((tag) => tag[0] === "type")?.[1] ?? "";
return {
publicationType,
indexEvent,
ndk, // Pass ndk to the page for the publication tree
};
};

17
src/routes/publication/[type]/[identifier]/+page.svelte

@ -1,6 +1,5 @@ @@ -1,6 +1,5 @@
<script lang="ts">
import Publication from "$lib/components/publications/Publication.svelte";
import { TextPlaceholder } from "flowbite-svelte";
import type { PageProps } from "./$types";
import { onDestroy, onMount, setContext } from "svelte";
import Processor from "asciidoctor";
@ -9,12 +8,14 @@ @@ -9,12 +8,14 @@
import { TableOfContents } from "$lib/components/publications/table_of_contents.svelte";
import { page } from "$app/state";
import { goto } from "$app/navigation";
import { createNDKEvent } from "$lib/utils/nostrUtils";
let { data }: PageProps = $props();
const publicationTree = new SveltePublicationTree(data.indexEvent, data.ndk);
const indexEvent = createNDKEvent(data.ndk, data.indexEvent);
const publicationTree = new SveltePublicationTree(indexEvent, data.ndk);
const toc = new TableOfContents(
data.indexEvent.tagAddress(),
indexEvent.tagAddress(),
publicationTree,
page.url.pathname ?? "",
);
@ -40,7 +41,7 @@ @@ -40,7 +41,7 @@
db.onsuccess = () => {
const transaction = db.result.transaction(["bookmarks"], "readwrite");
const store = transaction.objectStore("bookmarks");
const bookmarkKey = `${data.indexEvent.tagAddress()}`;
const bookmarkKey = `${indexEvent.tagAddress()}`;
store.put({ key: bookmarkKey, address });
};
});
@ -58,7 +59,7 @@ @@ -58,7 +59,7 @@
db.onsuccess = () => {
const transaction = db.result.transaction(["bookmarks"], "readonly");
const store = transaction.objectStore("bookmarks");
const bookmarkKey = `${data.indexEvent.tagAddress()}`;
const bookmarkKey = `${indexEvent.tagAddress()}`;
const request = store.get(bookmarkKey);
request.onsuccess = () => {
@ -83,13 +84,13 @@ @@ -83,13 +84,13 @@
<ArticleNav
publicationType={data.publicationType}
rootId={data.indexEvent.id}
indexEvent={data.indexEvent}
indexEvent={indexEvent}
/>
<main class="publication {data.publicationType}">
<Publication
rootAddress={data.indexEvent.tagAddress()}
rootAddress={indexEvent.tagAddress()}
publicationType={data.publicationType}
indexEvent={data.indexEvent}
indexEvent={indexEvent}
/>
</main>
Loading…
Cancel
Save