diff --git a/src/lib/components/publications/PublicationFeed.svelte b/src/lib/components/publications/PublicationFeed.svelte index 674eb5a..8156cfe 100644 --- a/src/lib/components/publications/PublicationFeed.svelte +++ b/src/lib/components/publications/PublicationFeed.svelte @@ -290,9 +290,9 @@ }; // Debounced search function - const debouncedSearch = debounce(async (query: string) => { + const debouncedSearch = debounce((query: string | undefined) => { console.debug("[PublicationFeed] Search query changed:", query); - if (query.trim()) { + if (query && query.trim()) { const filtered = filterEventsBySearch(allIndexEvents); eventsInView = filtered.slice(0, 30); endOfFeed = filtered.length <= 30; @@ -303,10 +303,6 @@ }, 300); $effect(() => { - console.debug( - "[PublicationFeed] Search query effect triggered:", - props.searchQuery, - ); debouncedSearch(props.searchQuery); }); diff --git a/src/lib/navigator/EventNetwork/NodeTooltip.svelte b/src/lib/navigator/EventNetwork/NodeTooltip.svelte index 8066d4c..8e95b6e 100644 --- a/src/lib/navigator/EventNetwork/NodeTooltip.svelte +++ b/src/lib/navigator/EventNetwork/NodeTooltip.svelte @@ -145,7 +145,7 @@
- + {node.title || "Untitled"}
diff --git a/src/lib/utils.ts b/src/lib/utils.ts index bc2a2ab..00576d5 100644 --- a/src/lib/utils.ts +++ b/src/lib/utils.ts @@ -1,7 +1,21 @@ import type { NDKEvent } from "@nostr-dev-kit/ndk"; import { nip19 } from "nostr-tools"; import { getMatchingTags } from "./utils/nostrUtils.ts"; -import { AddressPointer, EventPointer } from "nostr-tools/nip19"; +import type { AddressPointer, EventPointer } from "nostr-tools/nip19"; + +export class DecodeError extends Error { + constructor(message: string) { + super(message); + this.name = "DecodeError"; + } +} + +export class InvalidKindError extends DecodeError { + constructor(message: string) { + super(message); + this.name = "InvalidKindError"; + } +} export function neventEncode(event: NDKEvent, relays: string[]) { return nip19.neventEncode({ @@ -31,39 +45,41 @@ export function nprofileEncode(pubkey: string, relays: string[]) { } /** - * Decodes an naddr identifier and returns the decoded data + * Decodes a nostr identifier (naddr, nevent) and returns the decoded data. + * @param identifier The nostr identifier to decode. + * @param expectedType The expected type of the decoded data ('naddr' or 'nevent'). + * @returns The decoded data. */ -export function naddrDecode(naddr: string): AddressPointer { +function decodeNostrIdentifier( + identifier: string, + expectedType: "naddr" | "nevent", +): T { try { - if (!naddr.startsWith('naddr')) { - throw new Error('Invalid naddr format'); + if (!identifier.startsWith(expectedType)) { + throw new InvalidKindError(`Invalid ${expectedType} format`); } - const decoded = nip19.decode(naddr); - if (decoded.type !== 'naddr') { - throw new Error('Decoded result is not an naddr'); + const decoded = nip19.decode(identifier); + if (decoded.type !== expectedType) { + throw new InvalidKindError(`Decoded result is not an ${expectedType}`); } - return decoded.data; + return decoded.data as T; } catch (error) { - throw new Error(`Failed to decode naddr: ${error}`); + throw new DecodeError(`Failed to decode ${expectedType}: ${error}`); } } +/** + * Decodes an naddr identifier and returns the decoded data + */ +export function naddrDecode(naddr: string): AddressPointer { + return decodeNostrIdentifier(naddr, "naddr"); +} + /** * Decodes an nevent identifier and returns the decoded data */ export function neventDecode(nevent: string): EventPointer { - try { - if (!nevent.startsWith('nevent')) { - throw new Error('Invalid nevent format'); - } - const decoded = nip19.decode(nevent); - if (decoded.type !== 'nevent') { - throw new Error('Decoded result is not an nevent'); - } - return decoded.data; - } catch (error) { - throw new Error(`Failed to decode nevent: ${error}`); - } + return decodeNostrIdentifier(nevent, "nevent"); } export function formatDate(unixtimestamp: number) { @@ -206,7 +222,8 @@ Array.prototype.findIndexAsync = function ( * @param wait The number of milliseconds to delay * @returns A debounced version of the function */ -export function debounce unknown>( +// deno-lint-ignore no-explicit-any +export function debounce any>( func: T, wait: number, ): (...args: Parameters) => void { diff --git a/src/lib/utils/event_search.ts b/src/lib/utils/event_search.ts index 25319c0..5330ebb 100644 --- a/src/lib/utils/event_search.ts +++ b/src/lib/utils/event_search.ts @@ -1,7 +1,8 @@ import { ndkInstance } from "../ndk.ts"; import { fetchEventWithFallback } from "./nostrUtils.ts"; import { nip19 } from "nostr-tools"; -import { NDKEvent, NDKFilter } from "@nostr-dev-kit/ndk"; +import type { NDKFilter } from "@nostr-dev-kit/ndk"; +import { NDKEvent } from "@nostr-dev-kit/ndk"; import { get } from "svelte/store"; import { wellKnownUrl, isValidNip05Address } from "./search_utils.ts"; import { TIMEOUTS, VALIDATION } from "./search_constants.ts"; diff --git a/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts b/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts index 10ec1a7..41e4df9 100644 --- a/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts +++ b/src/lib/utils/markup/advancedAsciidoctorPostProcessor.ts @@ -32,9 +32,11 @@ export async function postProcessAdvancedAsciidoctorHtml( } if ( typeof globalThis !== "undefined" && - typeof globalThis.MathJax?.typesetPromise === "function" + // deno-lint-ignore no-explicit-any + typeof (globalThis as any).MathJax?.typesetPromise === "function" ) { - setTimeout(() => globalThis.MathJax.typesetPromise(), 0); + // deno-lint-ignore no-explicit-any + setTimeout(() => (globalThis as any).MathJax.typesetPromise(), 0); } return processedHtml; } catch (error) { diff --git a/src/lib/utils/network_detection.ts b/src/lib/utils/network_detection.ts index 40bb568..e69543a 100644 --- a/src/lib/utils/network_detection.ts +++ b/src/lib/utils/network_detection.ts @@ -153,10 +153,11 @@ export function getRelaySetForNetworkCondition( */ export function startNetworkMonitoring( onNetworkChange: (condition: NetworkCondition) => void, - checkInterval: number = 60000 // Increased to 60 seconds to reduce spam + checkInterval: number = 60000, // Increased to 60 seconds to reduce spam ): () => void { let lastCondition: NetworkCondition | null = null; - let intervalId: number | null = null; + // deno-lint-ignore no-explicit-any + let intervalId: any = null; const checkNetwork = async () => { try { diff --git a/src/lib/utils/nostrUtils.ts b/src/lib/utils/nostrUtils.ts index 91d3309..813f1e5 100644 --- a/src/lib/utils/nostrUtils.ts +++ b/src/lib/utils/nostrUtils.ts @@ -12,6 +12,8 @@ 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 = ''; @@ -668,3 +670,84 @@ 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 { + 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 { + 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 { + 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 { + 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}`); + } +} diff --git a/src/lib/utils/search_types.ts b/src/lib/utils/search_types.ts index 134ceff..a537edb 100644 --- a/src/lib/utils/search_types.ts +++ b/src/lib/utils/search_types.ts @@ -1,4 +1,4 @@ -import { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk"; +import type { NDKEvent, NDKFilter, NDKSubscription } from "@nostr-dev-kit/ndk"; /** * Extended NostrProfile interface for search results diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 7ac4f69..59bc393 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -8,12 +8,13 @@ import { loginMethodStorageKey } from "../lib/stores/userStore.ts"; import Pharos, { pharosInstance } from "../lib/parser.ts"; import type { LayoutLoad } from "./$types"; import { get } from "svelte/store"; +import { browser } from "$app/environment"; -export const load: LayoutLoad = () => { - // Initialize NDK with new relay management system - const ndk = initNdk(); - ndkInstance.set(ndk); - +/** + * Attempts to restore the user's authentication session from localStorage. + * Handles extension, Amber (NIP-46), and npub login methods. + */ +async function restoreAuthSession() { try { const pubkey = getPersistedLogin(); const loginMethod = localStorage.getItem(loginMethodStorageKey); @@ -111,9 +112,19 @@ export const load: LayoutLoad = () => { `Failed to restore login: ${e}\n\nContinuing with anonymous session.`, ); } +} + +export const load: LayoutLoad = () => { + // Initialize NDK with new relay management system + const ndk = initNdk(); + ndkInstance.set(ndk); + + if (browser) { + restoreAuthSession(); + } const parser = new Pharos(ndk); - pharosInstance.set(parser); + pharosInstance.set(parser); return { ndk, diff --git a/src/routes/publication/+page.server.ts b/src/routes/publication/+page.server.ts index 29fc5a6..1b66af2 100644 --- a/src/routes/publication/+page.server.ts +++ b/src/routes/publication/+page.server.ts @@ -1,6 +1,22 @@ import { redirect } from "@sveltejs/kit"; import type { PageServerLoad } from "./$types"; +// Route pattern constants +const ROUTES = { + PUBLICATION_BASE: "/publication", + NADDR: "/publication/naddr", + NEVENT: "/publication/nevent", + ID: "/publication/id", + D_TAG: "/publication/d", + START: "/start", +} as const; + +// Identifier prefixes +const IDENTIFIER_PREFIXES = { + NADDR: "naddr", + NEVENT: "nevent", +} as const; + export const load: PageServerLoad = ({ url }) => { const id = url.searchParams.get("id"); const dTag = url.searchParams.get("d"); @@ -8,19 +24,18 @@ export const load: PageServerLoad = ({ url }) => { // Handle backward compatibility for old query-based routes if (id) { // Check if id is an naddr or nevent - if (id.startsWith("naddr")) { - throw redirect(301, `/publication/naddr/${id}`); - } else if (id.startsWith("nevent")) { - throw redirect(301, `/publication/nevent/${id}`); + if (id.startsWith(IDENTIFIER_PREFIXES.NADDR)) { + throw redirect(301, `${ROUTES.NADDR}/${id}`); + } else if (id.startsWith(IDENTIFIER_PREFIXES.NEVENT)) { + throw redirect(301, `${ROUTES.NEVENT}/${id}`); } else { // Assume it's a hex ID - throw redirect(301, `/publication/id/${id}`); + throw redirect(301, `${ROUTES.ID}/${id}`); } } else if (dTag) { - throw redirect(301, `/publication/d/${dTag}`); + throw redirect(301, `${ROUTES.D_TAG}/${dTag}`); } - // If no query parameters, redirect to the start page or show publication feed\ - // AI-TODO: Redirect to a "not found" page. - throw redirect(301, "/start"); + // If no query parameters, redirect to the start page + throw 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 a9ddd3c..1209f7b 100644 --- a/src/routes/publication/[type]/[identifier]/+layout.server.ts +++ b/src/routes/publication/[type]/[identifier]/+layout.server.ts @@ -1,95 +1,12 @@ import { error } from "@sveltejs/kit"; import type { LayoutServerLoad } from "./$types"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; -import { getActiveRelaySetAsNDKRelaySet } from "../../../../lib/ndk.ts"; -import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts"; -import { naddrDecode, neventDecode } from "../../../../lib/utils.ts"; -import type NDK from "@nostr-dev-kit/ndk"; - -// AI-TODO: Use `fetchEventWithFallback` from `nostrUtils.ts` to retrieve events in this file. - -/** - * Fetches an event by hex ID - */ -async function fetchEventById(ndk: NDK, id: string): Promise { - try { - const event = await ndk.fetchEvent(id); - if (!event) { - throw new Error(`Event not found for ID: ${id}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by d tag - */ -async function fetchEventByDTag(ndk: NDK, dTag: string): Promise { - try { - const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); - const events = await ndk.fetchEvents( - { "#d": [dTag] }, - { closeOnEose: false }, - relaySet, - ); - - if (!events || events.size === 0) { - throw new Error(`Event not found for d tag: ${dTag}`); - } - - // Choose the event with the latest created_at timestamp when multiple events share the same d tag - const sortedEvents = Array.from(events).sort((a, b) => (b.created_at || 0) - (a.created_at || 0)); - return sortedEvents[0]; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by naddr identifier - */ -async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise { - try { - const decoded = naddrDecode(naddr); - const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); - - const filter = { - kinds: [decoded.kind], - authors: [decoded.pubkey], - "#d": [decoded.identifier], - }; - - const event = await ndk.fetchEvent(filter, { closeOnEose: false }, relaySet); - if (!event) { - throw new Error(`Event not found for naddr: ${naddr}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by nevent identifier - */ -async function fetchEventByNevent(ndk: NDK, nevent: string): Promise { - try { - const decoded = neventDecode(nevent); - const event = await ndk.fetchEvent(decoded.id); - if (!event) { - throw new Error(`Event not found for nevent: ${nevent}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} +import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts"; export const load: LayoutServerLoad = async ({ params, parent, url }) => { const { type, identifier } = params; - const { ndk } = await parent(); + // deno-lint-ignore no-explicit-any + const { ndk } = (await parent()) as any; if (!ndk) { throw error(500, "NDK not available"); diff --git a/src/routes/publication/[type]/[identifier]/+page.server.ts b/src/routes/publication/[type]/[identifier]/+page.server.ts index 3c033c7..95b58fe 100644 --- a/src/routes/publication/[type]/[identifier]/+page.server.ts +++ b/src/routes/publication/[type]/[identifier]/+page.server.ts @@ -1,95 +1,12 @@ import { error } from "@sveltejs/kit"; import type { PageServerLoad } from "./$types"; import type { NDKEvent } from "@nostr-dev-kit/ndk"; -import { getActiveRelaySetAsNDKRelaySet } from "../../../../lib/ndk.ts"; -import { getMatchingTags } from "../../../../lib/utils/nostrUtils.ts"; -import { naddrDecode, neventDecode } from "../../../../lib/utils.ts"; -import type NDK from "@nostr-dev-kit/ndk"; - -// AI-TODO: Use `fetchEventWithFallback` from `nostrUtils.ts` to retrieve events in this file. - -/** - * Fetches an event by hex ID - */ -async function fetchEventById(ndk: NDK, id: string): Promise { - try { - const event = await ndk.fetchEvent(id); - if (!event) { - throw new Error(`Event not found for ID: ${id}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by d tag - */ -async function fetchEventByDTag(ndk: NDK, dTag: string): Promise { - try { - const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); // true for inbox relays - const events = await ndk.fetchEvents( - { "#d": [dTag] }, - { closeOnEose: false }, - relaySet, - ); - - if (!events || events.size === 0) { - throw new Error(`Event not found for d tag: ${dTag}`); - } - - // AI-NOTE: Choose the event with the latest created_at timestamp when multiple events share the same d tag - const sortedEvents = Array.from(events).sort((a, b) => (b.created_at || 0) - (a.created_at || 0)); - return sortedEvents[0]; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by naddr identifier - */ -async function fetchEventByNaddr(ndk: NDK, naddr: string): Promise { - try { - const decoded = naddrDecode(naddr); - const relaySet = await getActiveRelaySetAsNDKRelaySet(ndk, true); - - const filter = { - kinds: [decoded.kind], - authors: [decoded.pubkey], - "#d": [decoded.identifier], - }; - - const event = await ndk.fetchEvent(filter, { closeOnEose: false }, relaySet); - if (!event) { - throw new Error(`Event not found for naddr: ${naddr}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} - -/** - * Fetches an event by nevent identifier - */ -async function fetchEventByNevent(ndk: NDK, nevent: string): Promise { - try { - const decoded = neventDecode(nevent); - const event = await ndk.fetchEvent(decoded.id); - if (!event) { - throw new Error(`Event not found for nevent: ${nevent}`); - } - return event; - } catch (err) { - throw error(404, `Failed to fetch publication root event.\n${err}`); - } -} +import { getMatchingTags, fetchEventById, fetchEventByDTag, fetchEventByNaddr, fetchEventByNevent } from "../../../../lib/utils/nostrUtils.ts"; export const load: PageServerLoad = async ({ params, parent }) => { const { type, identifier } = params; - const { ndk } = await parent(); + // deno-lint-ignore no-explicit-any + const { ndk } = (await parent()) as any; if (!ndk) { throw error(500, "NDK not available");