5 changed files with 174 additions and 126 deletions
@ -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}`); |
||||||
|
} |
||||||
|
} |
||||||
@ -1,42 +1,38 @@ |
|||||||
import { error } from "@sveltejs/kit"; |
import { error } from "@sveltejs/kit"; |
||||||
import type { PageServerLoad } from "./$types"; |
import type { PageServerLoad } from "./$types"; |
||||||
import type { NDKEvent } from "@nostr-dev-kit/ndk"; |
import { fetchEventByDTag, fetchEventById, fetchEventByNaddr, fetchEventByNevent, NostrEvent } from "../../../../lib/utils/websocket_utils.ts"; |
||||||
import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts";
|
|
||||||
|
|
||||||
export const load: PageServerLoad = async ({ params, parent }) => { |
export const load: PageServerLoad = async ({ params }) => { |
||||||
const { type, identifier } = params; |
const { type, identifier } = params; |
||||||
// deno-lint-ignore no-explicit-any
|
|
||||||
const { ndk } = (await parent()) as any; |
|
||||||
|
|
||||||
if (!ndk) { |
let indexEvent: NostrEvent | null; |
||||||
throw error(500, "NDK not available"); |
|
||||||
} |
|
||||||
|
|
||||||
let indexEvent: NDKEvent; |
|
||||||
|
|
||||||
// Handle different identifier types
|
// Handle different identifier types
|
||||||
switch (type) { |
switch (type) { |
||||||
case 'id': |
case 'id': |
||||||
indexEvent = await fetchEventById(ndk, identifier); |
indexEvent = await fetchEventById(identifier); |
||||||
break; |
break; |
||||||
case 'd': |
case 'd': |
||||||
indexEvent = await fetchEventByDTag(ndk, identifier); |
indexEvent = await fetchEventByDTag(identifier); |
||||||
break; |
break; |
||||||
case 'naddr': |
case 'naddr': |
||||||
indexEvent = await fetchEventByNaddr(ndk, identifier); |
indexEvent = await fetchEventByNaddr(identifier); |
||||||
break; |
break; |
||||||
case 'nevent': |
case 'nevent': |
||||||
indexEvent = await fetchEventByNevent(ndk, identifier); |
indexEvent = await fetchEventByNevent(identifier); |
||||||
break; |
break; |
||||||
default: |
default: |
||||||
throw error(400, `Unsupported identifier type: ${type}`); |
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 { |
return { |
||||||
publicationType, |
publicationType, |
||||||
indexEvent, |
indexEvent, |
||||||
ndk, // Pass ndk to the page for the publication tree
|
|
||||||
}; |
}; |
||||||
};
|
};
|
||||||
Loading…
Reference in new issue